前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql源码(76)执行器专用元组格式TupleTableSlot

Postgresql源码(76)执行器专用元组格式TupleTableSlot

作者头像
mingjie
发布2022-09-26 21:51:34
5820
发布2022-09-26 21:51:34
举报

相关 《Postgresql源码(56)可扩展类型分析ExpandedObject/ExpandedRecord》 《Postgresql源码(58)元组拼接heap_form_tuple剖析》

0 总结

  1. TupleTableSlot本身带了tts_values、tts_isnull能存数据。
  2. TupleTableSlot的衍生结构【HeapTupleTableSlot】多了个物理tuple的指针,能挂一个物理tuple,用的时候解开到tts_values、tts_isnull中。
  3. HeapTupleTableSlot的衍生结构【BufferHeapTupleTableSlot】又多了个buffer,他会和buffer关联起来,释放或materialize后时候需要把buffer释放掉。
  4. TupleTableSlot的衍生结构【MinimalTupleTableSlot】在基类的基础上了记录了物理元组tuple和mintuple,mintuple表示物理元组去掉HeapTuple头和HeapTupleHeader头之后剩下的部分:isnull/dataums,纯数据部分。
  5. TupleTableSlot的衍生结构【VirtualTupleTableSlot】就比较简单的了,只多了个char *data,用于记录任何数据,可以满足各种场景的需求。

1 前言

PG中的元组有多种表现形式,之前介绍过两种了:

  • 【flattened格式】【物理存储格式】HeapTupleData
  • 【expanded格式】【内存计算格式】ExpandedObjectHeader

今天介绍第三种元组格式:

  • 执行器专用格式】TupleTableSlot

执行器对元组格式的要求非常灵活,例如select 1;表达式结果、select a,b,c from t;投影临时结果等等。

各种各样结果都需要返回元组形式的数据,所以PG引入了一种通用格式保存数据:TupleTableSlot。

2 复习:物理存储格式、内存计算格式

  • 【flattened格式】【物理存储格式】HeapTupleData
  • 【expanded格式】【内存计算格式】ExpandedObjectHeader

3 TupleTableSlot

3.1 tuptable.h注释

  • 执行器使用TupleTableSlots组装成List在各个执行节点间传递元组数据。
  • 目前内置四种不同的TupleTableSlots,类型按照分配的成员函数TupleTableSlotOps来区分。
    • TTSOpsBufferHeapTuple:在buffer页面中的物理元组
    • TTSOpsHeapTuple:在palloc内存中构造的物理元组
    • TTSOpsMinimalTuple:在palloc内存中构造的“最小”物理元组(物理元组去掉事务信息)
    • TTSOpsVirtual:只有Values和NULL bitmap组成的虚拟元组(只有物理元组的后两部分)
  • 四种类型的说明:
    • 第一、二种类似都是用于管理物理元组,区别是resource manager不同。
    • 对于在buffer中拿的元组,需要一直拿着pin,直到tts对tuple的引用删除后,才可以放pin。
    • 对于在内存中拿的元组,也是引用删除后才可以pfree。
    • 第三种minimal永远不会保存到buffer中;没有系统列。
    • 第四种virtual是一种优化,用于减少嵌套plan节点之间的物理复制;没有系统列。
  • TupleTableSlot 的 values/null bitmap 数组具有双重作用:
    • 对于virtual类型,这就是全部数据了。
    • 对于其他类型,数组保存了从tuple解出来的数据。(注意,物理元组的values的里面有很多传引用的值,真正的值记录在物理元组中,这里只是记录了引用指针)。
    • 所有的数据提取都是惰性的,避免从物理元组中重复提取数据。
  • TupleTableSlot 也可以是empty,由 tts_flags 中设置的标志 TTS_FLAG_EMPTY 表示不包含有效数据。 对于尚未分配元组描述符的新创建的slot,empty是唯一有效的状态。 在这种状态下,不应在 tts_flags 中设置 TTS_SHOULDFREE,tts_tuple 必须为 NULL 并且 tts_nvalid 为零。
  • tupleDescriptor 只是由 TupleTableSlot 代码引用,而不是复制。 ExecSetSlotDescriptor() 的调用者负责提供一个描述符,该描述符的生命周期必须与slot一样长。

TupleTableSlot基类型

代码语言:javascript
复制
typedef struct TupleTableSlot
{
	NodeTag		type;
	uint16		tts_flags;                      /* Boolean states */
	AttrNumber	tts_nvalid;                     /* # of valid values in tts_values */
	const TupleTableSlotOps *const tts_ops;     /* implementation of slot */
	TupleDesc	tts_tupleDescriptor;            /* slot's tuple descriptor */
	Datum	   *tts_values;                     /* current per-attribute values */
	bool	   *tts_isnull;                     /* current per-attribute isnull flags */
	MemoryContext tts_mcxt;                     /* slot itself is in this context */
	ItemPointerData tts_tid;                    /* stored tuple's tid */
	Oid			tts_tableOid;                   /* table oid of tuple */
} TupleTableSlot;
  • tts_values/tts_isnull 要么在创建槽时(提供描述符时)分配,要么在将描述符分配给槽时分配; 它们的长度等于描述符的 natts。
  • 当在 tts_flags 中设置 TTS_SHOULDFREE 时,物理元组由插槽“拥有”,并且应该在插槽对元组的引用被删除时释放。
  • TTS_FLAG_SLOW 标志是 slot_deform_heap_tuple 的保存状态,不应被任何其他代码修改。

3.2 类型关系

3.3 四套函数总结

四个结构体对应了四套成员函数,下面是一些总结:

?

TTSOpsHeapTuple

TTSOpsBufferHeapTuple

TTSOpsVirtual

TTSOpsMinimalTuple

init

min结构记录了一个完整的头HeapTupleData非指针,初始化时tuple指向这个头

release

clear

如果有TTS_FLAG_SHOULDFREE标记需要释放引用的heaptuple

如果有TTS_FLAG_SHOULDFREE标记需要释放引用的heaptuple;如果记了buffer还要releasebuffer

如果有TTS_FLAG_SHOULDFREE标记需要释放下data的内容

如果有TTS_FLAG_SHOULDFREE标记需要释放pfree mintuple的值

getsomeattrs

slot_deform_heap_tuple从物理tuple把数据拆到Datum/isnull数组注意这里只是引用没有内存拷贝

同本行第二列

不支持

同本行第二列

getsysattr

从物理tuple里面拿系统列

同本行第二列

不支持

不支持

materialize

切到slot自己的内存上下文在重建或复制一遍物理tuple,使slot和物理tuple解绑

同左面,不同的是如果是copytuple需要释放之前的buffer

按desc的格式,tts_values的内容拼接元组,把结果放到data指向新申请的空间中

同本行第二列,不同的是只拷贝值的部分,不拷贝元组的两个头

copyslot

调用copy_heap_tuple拷物理元组,再拷贝剩下的

同本行第二列

把源tts_values拷贝给目标tts_values,然后调用materialize给目标slot构造data即可

同本行第二列

get_heap_tuple

返回物理元组

同本行第二列

get_minimal_tuple

返回mintuple

copy_heap_tuple

拷贝物理元组heap_copytuple

同本行第二列

heap_form_tuple拼接

拷贝并把元组头重建出来返回一个物理元组

copy_minimal_tuple

只把isnull bitmap/Datum拷贝出来

同本行第二列

heap_form_minimal_tuple拼接

只拷贝mini元组

TTSOpsHeapTuple

代码语言:javascript
复制
const TupleTableSlotOps TTSOpsHeapTuple = {
	.base_slot_size = sizeof(HeapTupleTableSlot),
	.init = tts_heap_init,
	.release = tts_heap_release,
	.clear = tts_heap_clear,
	.getsomeattrs = tts_heap_getsomeattrs,
	.getsysattr = tts_heap_getsysattr,
	.materialize = tts_heap_materialize,
	.copyslot = tts_heap_copyslot,
	.get_heap_tuple = tts_heap_get_heap_tuple,

	/* A heap tuple table slot can not "own" a minimal tuple. */
	.get_minimal_tuple = NULL,
	.copy_heap_tuple = tts_heap_copy_heap_tuple,
	.copy_minimal_tuple = tts_heap_copy_minimal_tuple
};

TTSOpsBufferHeapTuple

代码语言:javascript
复制
const TupleTableSlotOps TTSOpsBufferHeapTuple = {
	.base_slot_size = sizeof(BufferHeapTupleTableSlot),
	.init = tts_buffer_heap_init,
	.release = tts_buffer_heap_release,
	.clear = tts_buffer_heap_clear,
	.getsomeattrs = tts_buffer_heap_getsomeattrs,
	.getsysattr = tts_buffer_heap_getsysattr,
	.materialize = tts_buffer_heap_materialize,
	.copyslot = tts_buffer_heap_copyslot,
	.get_heap_tuple = tts_buffer_heap_get_heap_tuple,

	/* A buffer heap tuple table slot can not "own" a minimal tuple. */
	.get_minimal_tuple = NULL,
	.copy_heap_tuple = tts_buffer_heap_copy_heap_tuple,
	.copy_minimal_tuple = tts_buffer_heap_copy_minimal_tuple
};

TTSOpsVirtual

代码语言:javascript
复制
const TupleTableSlotOps TTSOpsVirtual = {
	.base_slot_size = sizeof(VirtualTupleTableSlot),
	.init = tts_virtual_init,
	.release = tts_virtual_release,
	.clear = tts_virtual_clear,
	.getsomeattrs = tts_virtual_getsomeattrs,
	.getsysattr = tts_virtual_getsysattr,
	.materialize = tts_virtual_materialize,
	.copyslot = tts_virtual_copyslot,

	/*
	 * A virtual tuple table slot can not "own" a heap tuple or a minimal
	 * tuple.
	 */
	.get_heap_tuple = NULL,
	.get_minimal_tuple = NULL,
	.copy_heap_tuple = tts_virtual_copy_heap_tuple,
	.copy_minimal_tuple = tts_virtual_copy_minimal_tuple
};

TTSOpsMinimalTuple

代码语言:javascript
复制
const TupleTableSlotOps TTSOpsMinimalTuple = {
	.base_slot_size = sizeof(MinimalTupleTableSlot),
	.init = tts_minimal_init,
	.release = tts_minimal_release,
	.clear = tts_minimal_clear,
	.getsomeattrs = tts_minimal_getsomeattrs,
	.getsysattr = tts_minimal_getsysattr,
	.materialize = tts_minimal_materialize,
	.copyslot = tts_minimal_copyslot,

	/* A minimal tuple table slot can not "own" a heap tuple. */
	.get_heap_tuple = NULL,
	.get_minimal_tuple = tts_minimal_get_minimal_tuple,
	.copy_heap_tuple = tts_minimal_copy_heap_tuple,
	.copy_minimal_tuple = tts_minimal_copy_minimal_tuple
};

4 fill_val的入参dataP为什么是二级指针?

如果只有一列使用一级指针也是OK的,代码里面进入就执行了:

char *data = *dataP;

后面都在对data进行操作,这里的data和*dataP都指向内存的数据区域,最后再执行:

*dataP = data;

也就是让dataP重新指向data。

但是多列时会有循环去调用fill_val函数,每次都需要调整data的位置,所以最后的:

*dataP = data;

会把data的位置向后移动,来放下一个列的数据。

代码语言:javascript
复制
----------------------       <----- tuple
| HeapTuple          |
----------------------       <----- td     <----- tuple->t_data
| HeapTupleHeader    |
----------------------       <----- td + td->t_hoff    
| isnull             |
----------------------       <------ data    <------ *dataP
| Datums             |     下一列: <------ data    <------ *dataP
|                    |
|                    |     下一列: <------ data    <------ *dataP
|                    |
----------------------   

fill_val

代码语言:javascript
复制
static inline void
fill_val(Form_pg_attribute att,
		 bits8 **bit,
		 int *bitmask,
		 char **dataP,          
		 uint16 *infomask,
		 Datum datum,
		 bool isnull)
{
	Size		data_length;
	char	   *data = *dataP;

	/*
	 * If we're building a null bitmap, set the appropriate bit for the
	 * current column value here.
	 */
	if (bit != NULL)
	{
		if (*bitmask != HIGHBIT)
			*bitmask <<= 1;
		else
		{
			*bit += 1;
			**bit = 0x0;
			*bitmask = 1;
		}

		if (isnull)
		{
			*infomask |= HEAP_HASNULL;
			return;
		}

		**bit |= *bitmask;
	}

	/*
	 * XXX we use the att_align macros on the pointer value itself, not on an
	 * offset.  This is a bit of a hack.
	 */
	if (att->attbyval)
	{
		/* pass-by-value */
		data = (char *) att_align_nominal(data, att->attalign);
		store_att_byval(data, datum, att->attlen);
		data_length = att->attlen;
	}
	else if (att->attlen == -1)
	{
		/* varlena */
		Pointer		val = DatumGetPointer(datum);

		*infomask |= HEAP_HASVARWIDTH;
		if (VARATT_IS_EXTERNAL(val))
		{
			if (VARATT_IS_EXTERNAL_EXPANDED(val))
			{
				/*
				 * we want to flatten the expanded value so that the
				 * constructed tuple doesn't depend on it
				 */
				ExpandedObjectHeader *eoh = DatumGetEOHP(datum);

				data = (char *) att_align_nominal(data,
												  att->attalign);
				data_length = EOH_get_flat_size(eoh);
				EOH_flatten_into(eoh, data, data_length);
			}
			else
			{
				*infomask |= HEAP_HASEXTERNAL;
				/* no alignment, since it's short by definition */
				data_length = VARSIZE_EXTERNAL(val);
				memcpy(data, val, data_length);
			}
		}
		else if (VARATT_IS_SHORT(val))
		{
			/* no alignment for short varlenas */
			data_length = VARSIZE_SHORT(val);
			memcpy(data, val, data_length);
		}
		else if (VARLENA_ATT_IS_PACKABLE(att) &&
				 VARATT_CAN_MAKE_SHORT(val))
		{
			/* convert to short varlena -- no alignment */
			data_length = VARATT_CONVERTED_SHORT_SIZE(val);
			SET_VARSIZE_SHORT(data, data_length);
			memcpy(data + 1, VARDATA(val), data_length - 1);
		}
		else
		{
			/* full 4-byte header varlena */
			data = (char *) att_align_nominal(data,
											  att->attalign);
			data_length = VARSIZE(val);
			memcpy(data, val, data_length);
		}
	}
	else if (att->attlen == -2)
	{
		/* cstring ... never needs alignment */
		*infomask |= HEAP_HASVARWIDTH;
		Assert(att->attalign == TYPALIGN_CHAR);
		data_length = strlen(DatumGetCString(datum)) + 1;
		memcpy(data, DatumGetPointer(datum), data_length);
	}
	else
	{
		/* fixed-length pass-by-reference */
		data = (char *) att_align_nominal(data, att->attalign);
		Assert(att->attlen > 0);
		data_length = att->attlen;
		memcpy(data, DatumGetPointer(datum), data_length);
	}

	data += data_length;
	*dataP = data;
}
本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-08-31,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0 总结
  • 1 前言
  • 2 复习:物理存储格式、内存计算格式
  • 3 TupleTableSlot
    • 3.1 tuptable.h注释
      • 3.2 类型关系
        • 3.3 四套函数总结
          • TTSOpsHeapTuple
          • TTSOpsBufferHeapTuple
          • TTSOpsVirtual
          • TTSOpsMinimalTuple
      • 4 fill_val的入参dataP为什么是二级指针?
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
      http://www.vxiaotou.com