前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql源码(56)可扩展类型分析ExpandedObject/ExpandedRecord

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

作者头像
mingjie
发布2022-06-30 15:18:55
3940
发布2022-06-30 15:18:55
举报
文章被收录于专栏:Postgresql源码分析

相关 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
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-06-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 总结
  • 一、扩展类型header:ExpandedObjectHeader
    • 1 数据结构
      • 2 宏
        • 2 函数
          • DatumGetEOHP
          • EOH_init_header
          • MakeExpandedObjectReadOnlyInternal
          • TransferExpandedObject
          • DeleteExpandedObject
        • 3 函数指针
        • 二、扩展类型实现:ExpandedRecordHeader
          • 函数
            • (管理)make_expanded_record_from_typeid
            • (读写)expanded_record_lookup_field
            • (读写)expanded_record_set_fields
        相关产品与服务
        数据保险箱
        数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档