相关 Postgresql源码(51)变长类型实现(valena.c)) Postgresql源码(56)可扩展类型分析ExpandedObject/ExpandedRecord
总结
ExpandedObjectHeader总结:在应用时注意第七点,有的函数(datumCopy)需要的不是EOH头(4B头),是需要一个eoh_rw_ptr指针(1b_e头)。在使用函数前确认好datum传什么进去。
- 使用EOH表示的数据叫做expended表示;使用tuple表示的数据叫做flatten表示(flatten格式数据必须是4b头的varlena对象)
- 注意1:ExpandedObjectHeader内部的两个变量:eoh_rw_ptr、eoh_ro_ptr是varattrib_1b_e结构,里见data部分记录着ExpandedObjectHeader指针,指向自己。
- 注意2:ExpandedObjectHeader结构体的头是4B头,vl_len_永远是
-1
=0b11111111 11111111 11111111 11111111
- eoh_rw_ptr、eoh_ro_ptr指向的是varattrib_1b_e为首的结构,可读、可写的信息记录在varattrib_1b_e的tag中,所以拿到一个指针如果不知道读写,用DatumIsReadWriteExpandedObject宏通过varattrib_1b_e的tag来判断可读、可写。
- eoh_rw_ptr、eoh_ro_ptr两个变量在栈上存了两个1b_e结构,data部分只存一个指针,指向EOH的起始位置(指自己)
- EOH类型的内存申请是在自带的MemoryContext中的,释放也是直接释放这个context即可,可以通过修改parent的方法TransferExpandedObject延长声明周期
- **一般使用的Datum传的指针都是1b_e结构,用的时候先转成EOH在使用(1b_e是什么?Postgresql源码(51)变长类型实现(valena.c))
- EOH中提供了两个函数:get_flat_size、flatten_into
- get_flat_size:计算flattened表示的情况下需要多大空间
- flatten_into:调用get_flat_size拿到大小,申请好相应的空间,然后调用flatten_into,传入空间大小做交叉检查,然后构造flatten表示形式的元组,构造到result指向的内存中
ExpandedRecordHeader总结
- ExpandedRecordHeader是ExpandedObjectHeader的实现之一
- ExpandedObjectHeader是用于存放复杂类型的结构体,是tuple的一层封装
- ExpandedObjectHeader提供两类函数接口:控制类 和 数据读写类
- 控制类用于构造结构体、清空结构体等
- 数据写:例如把tuple的数据转换成EOR数据
- 数据读:例如从EOR中读取数据返回
扩展类型使用变长类型hdr来实现(遵循PG约定),下面第一部分分析header和相关函数,第二部分分析具体的扩展类型实现ExpandedRecordHeader。
一、扩展类型header:ExpandedObjectHeader
1 数据结构
代码语言:javascript复制struct ExpandedObjectHeader
{
/* Phony varlena header */
int32 vl_len_; /* always EOH_HEADER_MAGIC, see below */
const ExpandedObjectMethods *eoh_methods;
MemoryContext eoh_context;
char eoh_rw_ptr[EXPANDED_POINTER_SIZE]; // varattrib_1b_e 头加个 ExpandedObjectHeader指针
char eoh_ro_ptr[EXPANDED_POINTER_SIZE]; // varattrib_1b_e 头加个 ExpandedObjectHeader指针
};
typedef struct ExpandedObjectHeader ExpandedObjectHeader;
typedef struct varatt_expanded
{
ExpandedObjectHeader *eohptr;
} varatt_expanded;
typedef struct
{
uint8 va_header; /* Always 0x80 or 0x01 */
uint8 va_tag; /* Type of datum */
char va_data[FLEXIBLE_ARRAY_MEMBER]; /* Type-specific data */
} varattrib_1b_e;
varatt_expanded是什么?
- varatt_expanded是一种toast pointer,表示行外数据存储的一种格式
- varatt_expanded是内存中的数据格式,数据不一定在物理上是连续的,便于计算但不便于存储的数据格式
- varatt_expanded是用于指向拥有ExpandedObjectHeader头的指针,拥有ExpandedObjectHeader头的数据类型目前有两个
- ExpandedArrayHeader
- ExpandedRecordHeader
ExpandedObjectHeader是什么?
- 例如数组、行等复杂数据类型通常都有紧凑的磁盘格式,不便于修改,因为修改的时候必须把剩下的全部拷贝一遍
- 因此PG提供了"expended"表示,这种表示只在内存中使用,并且针对计算做了更多优化;数据类型必须提供将"expended"表示转回"flattened form"的方法;扩展对象是为了在多个操作中存活下来,但不会存活太长时间; 例如PL/pgSQL 过程中的局部变量
- 注意header中为读、写分配了两块栈内存,大小为:
EXPANDED_POINTER_SIZE = VARHDRSZ_EXTERNAL sizeof(varatt_expanded)
保存了varattrib_1b_e的va_header、va_tag和一个指向ExpandedObjectHeader的指针eohptr。所以用这两个指针拿出去的是varattrib_1b_e头的指针。 - eoh_rw_ptr、eoh_ro_ptr指向的是varattrib_1b_e为首的结构,可读、可写的信息记录在varattrib_1b_e的tag中,所以拿到一个指针如果不知道读写,用DatumIsReadWriteExpandedObject来判断是不是可写的,具体会用varattrib_1b_e的tag来判断。
- 注意header中的头4字节永远是
-1
=0b11111111 11111111 11111111 11111111
,按varattrib_4b的规范来看,低2位是表示类型的,这里是11
四种类型有类似这样的继承关系:
代码语言:javascript复制varatt_expanded :ExpandedObjectHeader指针
|
|
ExpandedObjectHeader :扩展类型通用head
|
| |
ExpandedRecordHeader ExpandedArrayHeader :两个扩展类型,扩展行类型和扩展数据类型
mermaid test:
#mermaid-svg-NplipRgANL0SJUZj {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-NplipRgANL0SJUZj .error-icon{fill:#552222;}#mermaid-svg-NplipRgANL0SJUZj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NplipRgANL0SJUZj .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-NplipRgANL0SJUZj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NplipRgANL0SJUZj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NplipRgANL0SJUZj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NplipRgANL0SJUZj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NplipRgANL0SJUZj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NplipRgANL0SJUZj .marker.cross{stroke:#333333;}#mermaid-svg-NplipRgANL0SJUZj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NplipRgANL0SJUZj g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-NplipRgANL0SJUZj g.classGroup text .title{font-weight:bolder;}#mermaid-svg-NplipRgANL0SJUZj .nodeLabel,#mermaid-svg-NplipRgANL0SJUZj .edgeLabel{color:#131300;}#mermaid-svg-NplipRgANL0SJUZj .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-NplipRgANL0SJUZj .label text{fill:#131300;}#mermaid-svg-NplipRgANL0SJUZj .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-NplipRgANL0SJUZj .classTitle{font-weight:bolder;}#mermaid-svg-NplipRgANL0SJUZj .node rect,#mermaid-svg-NplipRgANL0SJUZj .node circle,#mermaid-svg-NplipRgANL0SJUZj .node ellipse,#mermaid-svg-NplipRgANL0SJUZj .node polygon,#mermaid-svg-NplipRgANL0SJUZj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NplipRgANL0SJUZj .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-NplipRgANL0SJUZj g.clickable{cursor:pointer;}#mermaid-svg-NplipRgANL0SJUZj g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-NplipRgANL0SJUZj g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-NplipRgANL0SJUZj .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-NplipRgANL0SJUZj .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-NplipRgANL0SJUZj .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-NplipRgANL0SJUZj .dashed-line{stroke-dasharray:3;}#mermaid-svg-NplipRgANL0SJUZj #compositionStart,#mermaid-svg-NplipRgANL0SJUZj .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NplipRgANL0SJUZj #compositionEnd,#mermaid-svg-NplipRgANL0SJUZj .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NplipRgANL0SJUZj #dependencyStart,#mermaid-svg-NplipRgANL0SJUZj .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NplipRgANL0SJUZj #dependencyStart,#mermaid-svg-NplipRgANL0SJUZj .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NplipRgANL0SJUZj #extensionStart,#mermaid-svg-NplipRgANL0SJUZj .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NplipRgANL0SJUZj #extensionEnd,#mermaid-svg-NplipRgANL0SJUZj .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NplipRgANL0SJUZj #aggregationStart,#mermaid-svg-NplipRgANL0SJUZj .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NplipRgANL0SJUZj #aggregationEnd,#mermaid-svg-NplipRgANL0SJUZj .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-NplipRgANL0SJUZj .edgeTerminals{font-size:11px;}#mermaid-svg-NplipRgANL0SJUZj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
varatt_expanded
ExpandedObjectHeader *eohptr
ExpandedObjectHeader
int32 vl_len_
const ExpandedObjectMethods *eoh_methods
MemoryContext eoh_context
char eoh_rw_ptrEXPANDED_POINTER_SIZE
char eoh_ro_ptrEXPANDED_POINTER_SIZE
ExpandedRecordHeader
ExpandedArrayHeader
2 宏
代码语言:javascript复制#define EOH_HEADER_MAGIC (-1)
/* EOH的va_header永远是-1,注意这里是EOH的header,这里用的4b头;里面eoh_rw_ptr用的是1b_e结构 */
#define VARATT_IS_EXPANDED_HEADER(PTR)
(((varattrib_4b *) (PTR))->va_4byte.va_header == (uint32) EOH_HEADER_MAGIC)
#define EOHPGetRWDatum(eohptr) PointerGetDatum((eohptr)->eoh_rw_ptr)
#define EOHPGetRODatum(eohptr) PointerGetDatum((eohptr)->eoh_ro_ptr)
/* 给datum判断能不能写,注意这里的datum其实就对应着eoh_rw_ptr或eoh_ro_ptr,采用1b_e结构 */
#define DatumIsReadWriteExpandedObject(d, isnull, typlen)
(((isnull) || (typlen) != -1) ? false :
VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
#define MakeExpandedObjectReadOnly(d, isnull, typlen)
(((isnull) || (typlen) != -1) ? (d) :
MakeExpandedObjectReadOnlyInternal(d))
2 函数
DatumGetEOHP
DatumGetEOHP:输入1b_e,返回EOH指针
代码语言:javascript复制ExpandedObjectHeader *
DatumGetEOHP(Datum d)
{
varattrib_1b_e *datum = (varattrib_1b_e *) DatumGetPointer(d);
varatt_expanded ptr;
memcpy(&ptr, VARDATA_EXTERNAL(datum), sizeof(ptr));
return ptr.eohptr;
}
EOH_init_header
EOH_init_header:输入eoh指针,初始化eoh结构,主要是eoh的两个1b_e结构的data部分,保存指向eoh的指针
代码语言:javascript复制void
EOH_init_header(ExpandedObjectHeader *eohptr,
const ExpandedObjectMethods *methods,
MemoryContext obj_context)
{
varatt_expanded ptr;
eohptr->vl_len_ = EOH_HEADER_MAGIC;
eohptr->eoh_methods = methods;
eohptr->eoh_context = obj_context;
ptr.eohptr = eohptr;
SET_VARTAG_EXTERNAL(eohptr->eoh_rw_ptr, VARTAG_EXPANDED_RW);
memcpy(VARDATA_EXTERNAL(eohptr->eoh_rw_ptr), &ptr, sizeof(ptr));
SET_VARTAG_EXTERNAL(eohptr->eoh_ro_ptr, VARTAG_EXPANDED_RO);
memcpy(VARDATA_EXTERNAL(eohptr->eoh_ro_ptr), &ptr, sizeof(ptr));
}
MakeExpandedObjectReadOnlyInternal
输入给一个1b_e指针,返回一个1b_e指向的eoh的只读部分的1b_e
代码语言:javascript复制Datum
MakeExpandedObjectReadOnlyInternal(Datum d)
{
ExpandedObjectHeader *eohptr;
/* Nothing to do if not a read-write expanded-object pointer */
if (!VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
return d;
/* Now safe to extract the object pointer */
eohptr = DatumGetEOHP(d);
/* Return the built-in read-only pointer instead of given pointer */
return EOHPGetRODatum(eohptr);
}
TransferExpandedObject
TransferExpandedObject把memorycontext挂在新的parent下面
代码语言:javascript复制Datum
TransferExpandedObject(Datum d, MemoryContext new_parent)
{
ExpandedObjectHeader *eohptr = DatumGetEOHP(d);
/* Assert caller gave a R/W pointer */
Assert(VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)));
/* Transfer ownership */
MemoryContextSetParent(eohptr->eoh_context, new_parent);
/* Return the object's standard read-write pointer */
return EOHPGetRWDatum(eohptr);
}
DeleteExpandedObject
DeleteExpandedObject删除memorycontext
代码语言:javascript复制void
DeleteExpandedObject(Datum d)
{
ExpandedObjectHeader *eohptr = DatumGetEOHP(d);
/* Assert caller gave a R/W pointer */
Assert(VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)));
/* Kill it */
MemoryContextDelete(eohptr->eoh_context);
}
3 函数指针
EOH的第二个变量提供了两个函数指针的位置,功能:
- get_flat_size: compute space needed for flattened representation (total,including header)
- get_flat_size:计算flattened表示的情况下需要多大空间
- flatten_into: construct flattened representation in the caller-allocated space at *result, of size allocated_size (which will always be the result of a preceding get_flat_size call; it’s passed for cross-checking).
- flatten_into:调用get_flat_size拿到大小,申请好相应的空间,然后调用flatten_into,传入空间大小做交叉检查,然后构造flatten表示形式的元组,构造到result指向的内存中。
flattene格式数据必须是4b头的varlena对象
The flattened representation must be a valid in-line, non-compressed,4-byte-header varlena object.
typedef Size (*EOM_get_flat_size_method) (ExpandedObjectHeader *eohptr);
typedef void (*EOM_flatten_into_method) (ExpandedObjectHeader *eohptr,
void *result, Size allocated_size);
/* Struct of function pointers for an expanded object's methods */
typedef struct ExpandedObjectMethods
{
EOM_get_flat_size_method get_flat_size;
EOM_flatten_into_method flatten_into;
} ExpandedObjectMethods;
二、扩展类型实现:ExpandedRecordHeader
函数
(管理)make_expanded_record_from_typeid
用传入类型构造EOH_RECORD
- 根据传入的typeid拿到tupledesc(typeid是rowtype,例如下面sql)
- 申请上下文"expanded record",申请内存存放ExpandedRecordHeader后面跟着tupdesc->natts * (sizeof(Datum) sizeof(bool))表示每一列留一对:dvalues/dnulls表示value指针和非空布尔型。
- EOH_init_header:初始化EOH头部
- 记录desc:erh->er_tupdesc = tupdesc
- 如果
tupdesc->tdrefcount >= 0
,有别人引用,注册回调函数;
drop table tf1;
create table tf1(c1 int, c2 int, c3 varchar(32), c4 varchar(32), c5 int);
insert into tf1 values(1,1000, 'China','Dalian', 23000);
insert into tf1 values(2,4000, 'Janpan', 'Tokio', 45000);
insert into tf1 values(3,1500, 'China', 'Xian', 25000);
insert into tf1 values(4,300, 'China', 'Changsha', 24000);
insert into tf1 values(5,400,'USA','New York', 35000);
insert into tf1 values(6,5000, 'USA', 'Bostom', 15000);
CREATE OR REPLACE PROCEDURE tfun1(id int) AS $$
DECLARE
row1 tf1%ROWTYPE;
row2 tf1%ROWTYPE;
BEGIN
select * into row1 from tf1 where c1 = 1;
select * into row1 from tf1 where c1 = id;
raise notice 'row1(record) %', row1;
select * into row2 from tf1 where c1 = 1;
row2.c2 = id;
raise notice 'row2(record) %', row2;
raise notice 'p_rrow2.c2(rowtypec) %', row2.c2;
END;
$$ LANGUAGE plpgsql;
CALL tfun1(1);
(读写)expanded_record_lookup_field
用tupledesc匹配有没有传入的列名,如果有返回true并填入ExpandedRecordFieldInfo返回
代码语言:javascript复制typedef struct ExpandedRecordFieldInfo
{
int fnumber; /* field's attr number in record */
Oid ftypeid; /* field's type/typmod info */
int32 ftypmod;
Oid fcollation; /* field's collation if any */
} ExpandedRecordFieldInfo;
(读写)expanded_record_set_fields
用传入tuple向EOH_RECORD中塞值
(例如上面case中:select * into row1 from tf1 where c1 = 1;
,整个句子删除into row1后,用SPI去PG中执行,执行完了拿到tuple后,用这个函数给EOH_RECORD中塞值)(为什么不直接用tuple?因为tuple是紧凑的,适合存储不适合计算)
- 上下文切换过去
MemoryContextSwitchTo(erh->hdr.eoh_context)
- 在自己的上下文中复制一个tuple:
newtuple = heap_copytuple(tuple)
- 记录数据:
erh->fvalue = newtuple;
erh->fstartptr = (char *) newtuple->t_data;
erh->fendptr = ((char *) newtuple->t_data) newtuple->t_len;
- 释放老的
erh->fvalue