相关
《Postgresql源码(51)变长类型实现(valena.c)》
《Postgresql源码(56)可扩展类型分析ExpandedObject/ExpandedRecord》
《Postgresql源码(87)数组构造与计算(Flat格式与Expand格式)》
背景
ExpandedObjectHeader是什么?
-
例如数组、行等复杂数据类型通常都有紧凑的磁盘格式,不便于修改,因为修改的时候必须把剩下的全部拷贝一遍。PG提供了"expended"表示,这种表示只在内存中使用,并且针对计算做了更多优化。
-
在实践中,ExpandedObjectHeader是扩展类型的通用头,具体实现现在有两个,ExpandedRecordHeader和ExpandedArrayHeader。
-
ExpandedRecordHeader是比较常用的行扩展类型,可以理解为元组的另一种格式,也可以和元组的磁盘格式互相转换(磁盘格式HeapTupleData)
总结
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 数据结构
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
四种类型有类似这样的继承关系:
varatt_expanded :ExpandedObjectHeader指针
|
|
ExpandedObjectHeader :扩展类型通用head
| \
| |
ExpandedRecordHeader ExpandedArrayHeader :两个扩展类型,扩展行类型和扩展数据类型
mermaid test:
2 宏
#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指针
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的指针
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
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下面
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
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返回
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