Postgresql源码(56)可扩展类型分析ExpandedObject/ExpandedRecord

2022-06-30 15:18:55 浏览数 (1)

相关 Postgresql源码(51)变长类型实现(valena.c)) Postgresql源码(56)可扩展类型分析ExpandedObject/ExpandedRecord

总结

ExpandedObjectHeader总结:在应用时注意第七点,有的函数(datumCopy)需要的不是EOH头(4B头),是需要一个eoh_rw_ptr指针(1b_e头)。在使用函数前确认好datum传什么进去。

  1. 使用EOH表示的数据叫做expended表示;使用tuple表示的数据叫做flatten表示flatten格式数据必须是4b头的varlena对象
  2. 注意1:ExpandedObjectHeader内部的两个变量:eoh_rw_ptr、eoh_ro_ptr是varattrib_1b_e结构,里见data部分记录着ExpandedObjectHeader指针,指向自己。
  3. 注意2:ExpandedObjectHeader结构体的头是4B头,vl_len_永远是-1 = 0b11111111 11111111 11111111 11111111
  4. eoh_rw_ptr、eoh_ro_ptr指向的是varattrib_1b_e为首的结构,可读、可写的信息记录在varattrib_1b_e的tag中,所以拿到一个指针如果不知道读写,用DatumIsReadWriteExpandedObject宏通过varattrib_1b_e的tag来判断可读、可写。
  5. eoh_rw_ptr、eoh_ro_ptr两个变量在栈上存了两个1b_e结构,data部分只存一个指针,指向EOH的起始位置(指自己)
  6. EOH类型的内存申请是在自带的MemoryContext中的,释放也是直接释放这个context即可,可以通过修改parent的方法TransferExpandedObject延长声明周期
  7. **一般使用的Datum传的指针都是1b_e结构,用的时候先转成EOH在使用(1b_e是什么?Postgresql源码(51)变长类型实现(valena.c))
  8. 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.

代码语言:javascript复制
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

  1. 根据传入的typeid拿到tupledesc(typeid是rowtype,例如下面sql)
  2. 申请上下文"expanded record",申请内存存放ExpandedRecordHeader后面跟着tupdesc->natts * (sizeof(Datum) sizeof(bool))表示每一列留一对:dvalues/dnulls表示value指针和非空布尔型。
  3. EOH_init_header:初始化EOH头部
  4. 记录desc:erh->er_tupdesc = tupdesc
  5. 如果tupdesc->tdrefcount >= 0,有别人引用,注册回调函数;
代码语言:javascript复制
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是紧凑的,适合存储不适合计算)

  1. 上下文切换过去MemoryContextSwitchTo(erh->hdr.eoh_context)
  2. 在自己的上下文中复制一个tuple:newtuple = heap_copytuple(tuple)
  3. 记录数据:
    1. erh->fvalue = newtuple;
    2. erh->fstartptr = (char *) newtuple->t_data;
    3. erh->fendptr = ((char *) newtuple->t_data) newtuple->t_len;
  4. 释放老的erh->fvalue

0 人点赞