共计 29982 个字符,预计需要花费 75 分钟才能阅读完成。
本篇内容主要讲解“PostgreSQL 执行查询时获取元组属性值实现方法是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让丸趣 TV 小编来带大家学习“PostgreSQL 执行查询时获取元组属性值实现方法是什么”吧!
测试数据如下:
[local]:5432 pg12@testdb=# create table t_getattrs(id int,col_varchar varchar(20),col_char char(10),col_double float,col_numeric numeric);
CREATE TABLE
Time: 12425.991 ms (00:12.426)
[local]:5432 pg12@testdb=# insert into t_getattrs values(1, test , test ,1234.45,12345.77777);
INSERT 0 1
Time: 30.281 ms
[local]:5432 pg12@testdb=# select * from t_getattrs;
一、数据结构
TupleTableSlot
/*----------
* The executor stores tuples in a tuple table which is a List of
* independent TupleTableSlots. There are several cases we need to handle:
* 1. physical tuple in a disk buffer page
* 2. physical tuple constructed in palloc ed memory
* 3. minimal physical tuple constructed in palloc ed memory
* 4. virtual tuple consisting of Datum/isnull arrays
* 执行器在 tuple table 中存储元组, 这个表是各自独立的 TupleTableSlots 链表.
* 有以下情况需要处理:
* 1. 磁盘缓存页中的物理元组
* 2. 在已分配内存中构造的物理元组
* 3. 在已分配内存中构造的 minimal 物理元组
* 4. 含有 Datum/isnull 数组的 virtual 虚拟元组
*
* The first two cases are similar in that they both deal with materialized
* tuples, but resource management is different. For a tuple in a disk page
* we need to hold a pin on the buffer until the TupleTableSlot s reference
* to the tuple is dropped; while for a palloc d tuple we usually want the
* tuple pfree d when the TupleTableSlot s reference is dropped.
* 最上面 2 种情况跟 物化 元组的处理方式类似, 但资源管理是不同的.
* 对于在磁盘页中的元组, 需要 pin 在缓存中直至 TupleTableSlot 依赖的元组被清除,
* 而对于通过 palloc 分配的元组在 TupleTableSlot 依赖被清除后通常希望使用 pfree 释放
*
* A minimal tuple is handled similarly to a palloc d regular tuple.
* At present, minimal tuples never are stored in buffers, so there is no
* parallel to case 1. Note that a minimal tuple has no system columns .
* (Actually, it could have an OID, but we have no need to access the OID.)
* minimal 元组与通常的 palloc 分配的元组处理类似.
* 截止目前为止, minimal 元组不会存储在缓存中, 因此对于第一种情况不会存在并行的问题.
* 注意 minimal 没有 system columns 系统列
* (实际上, 可以有 OID, 但不需要访问 OID 列)
*
* A virtual tuple is an optimization used to minimize physical data
* copying in a nest of plan nodes. Any pass-by-reference Datums in the
* tuple point to storage that is not directly associated with the
* TupleTableSlot; generally they will point to part of a tuple stored in
* a lower plan node s output TupleTableSlot, or to a function result
* constructed in a plan node s per-tuple econtext. It is the responsibility
* of the generating plan node to be sure these resources are not released
* for as long as the virtual tuple needs to be valid. We only use virtual
* tuples in the result slots of plan nodes --- tuples to be copied anywhere
* else need to be materialized into physical tuples. Note also that a
* virtual tuple does not have any system columns .
* virtual 元组是用于在嵌套计划节点中拷贝时最小化物理数据的优化.
* 所有通过引用传递指向与 TupleTableSlot 非直接相关的存储的元组的 Datums 使用,
* 通常它们会指向存储在低层节点输出的 TupleTableSlot 中的元组的一部分,
* 或者指向在计划节点的 per-tuple 内存上下文 econtext 中构造的函数结果.
* 产生计划节点的时候有责任确保这些资源未被释放, 确保 virtual 元组是有效的.
* 我们使用计划节点中的结果 slots 中的虚拟元组 --- 元组会拷贝到其他地方需要 物化 到物理元组中.
* 注意 virtual 元组不需要有 system columns
*
* It is also possible for a TupleTableSlot to hold both physical and minimal
* copies of a tuple. This is done when the slot is requested to provide
* the format other than the one it currently holds. (Originally we attempted
* to handle such requests by replacing one format with the other, but that
* had the fatal defect of invalidating any pass-by-reference Datums pointing
* into the existing slot contents.) Both copies must contain identical data
* payloads when this is the case.
* TupleTableSlot 包含物理和 minimal 元组拷贝是可能的.
* 在 slot 需要提供格式化而不是当前持有的格式时会出现这种情况.
* (原始的情况是我们准备通过另外一种格式进行替换来处理这种请求, 但在校验引用传递 Datums 时会出现致命错误)
* 同时在这种情况下, 拷贝必须含有唯一的数据 payloads.
*
* The Datum/isnull arrays of a TupleTableSlot serve double duty. When the
* slot contains a virtual tuple, they are the authoritative data. When the
* slot contains a physical tuple, the arrays contain data extracted from
* the tuple. (In this state, any pass-by-reference Datums point into
* the physical tuple.) The extracted information is built lazily ,
* ie, only as needed. This serves to avoid repeated extraction of data
* from the physical tuple.
* TupleTableSlot 中的 Datum/isnull 数组有双重职责.
* 在 slot 包含虚拟元组时, 它们是 authoritative(权威) 数据.
* 在 slot 包含物理元组时, 时包含从元组中提取的数据的数组.
* (在这种情况下, 所有通过引用传递的 Datums 指向物理元组)
* 提取的信息通过 lazily 在需要的时候才构建.
* 这样可以避免从物理元组的重复数据提取.
*
* A TupleTableSlot can also be empty , holding no valid data. This is
* the only valid state for a freshly-created slot that has not yet had a
* tuple descriptor assigned to it. In this state, tts_isempty must be
* true, tts_shouldFree false, tts_tuple NULL, tts_buffer InvalidBuffer,
* and tts_nvalid zero.
* TupleTableSlot 可能为 empty , 没有有效数据.
* 对于新鲜创建仍未分配描述的的 slot 来说这是唯一有效的状态.
* 在这种状态下,tts_isempty 必须为 T,tts_shouldFree 为 F, tts_tuple 为 NULL,
* tts_buffer 为 InvalidBuffer,tts_nvalid 为 0.
*
* The tupleDescriptor is simply referenced, not copied, by the TupleTableSlot
* code. The caller of ExecSetSlotDescriptor() is responsible for providing
* a descriptor that will live as long as the slot does. (Typically, both
* slots and descriptors are in per-query memory and are freed by memory
* context deallocation at query end; so it s not worth providing any extra
* mechanism to do more. However, the slot will increment the tupdesc
* reference count if a reference-counted tupdesc is supplied.)
* tupleDescriptor 只是简单的引用并没有通过 TupleTableSlot 中的代码进行拷贝.
* ExecSetSlotDescriptor() 的调用者有责任提供与 slot 生命周期一样的描述符.
* ( 典型的, 不管是 slots 还是描述符会在 per-query 内存中,
* 并且会在查询结束时通过内存上下文的析构器释放, 因此不需要提供额外的机制来处理.
* 但是, 如果使用了引用计数型 tupdesc,slot 会增加 tupdesc 引用计数 )
*
* When tts_shouldFree is true, the physical tuple is owned by the slot
* and should be freed when the slot s reference to the tuple is dropped.
* 在 tts_shouldFree 为 T 的情况下, 物理元组由 slot 持有, 并且在 slot 引用元组被清除时释放内存.
*
* If tts_buffer is not InvalidBuffer, then the slot is holding a pin
* on the indicated buffer page; drop the pin when we release the
* slot s reference to that buffer. (tts_shouldFree should always be
* false in such a case, since presumably tts_tuple is pointing at the
* buffer page.)
* 如 tts_buffer 不是 InvalidBuffer, 那么 slot 持有缓存页中的 pin, 在释放引用该 buffer 的 slot 时会清除该 pin.
* (tts_shouldFree 通常来说应为 F, 因为 tts_tuple 会指向缓存页)
*
* tts_nvalid indicates the number of valid columns in the tts_values/isnull
* arrays. When the slot is holding a virtual tuple this must be equal
* to the descriptor s natts. When the slot is holding a physical tuple
* this is equal to the number of columns we have extracted (we always
* extract columns from left to right, so there are no holes).
* tts_nvalid 指示了 tts_values/isnull 数组中的有效列数.
* 如果 slot 含有虚拟元组, 该字段必须跟描述符的 natts 一样.
* 在 slot 含有物理元组时, 该字段等于我们提取的列数.
* (我们通常从左到右提取列, 因此不会有空洞存在)
*
* tts_values/tts_isnull are allocated when a descriptor is assigned to the
* slot; they are of length equal to the descriptor s natts.
* 在描述符分配给 slot 时 tts_values/tts_isnull 会被分配内存, 长度与描述符 natts 长度一样.
*
* tts_mintuple must always be NULL if the slot does not hold a minimal
* tuple. When it does, tts_mintuple points to the actual MinimalTupleData
* object (the thing to be pfree d if tts_shouldFreeMin is true). If the slot
* has only a minimal and not also a regular physical tuple, then tts_tuple
* points at tts_minhdr and the fields of that struct are set correctly
* for access to the minimal tuple; in particular, tts_minhdr.t_data points
* MINIMAL_TUPLE_OFFSET bytes before tts_mintuple. This allows column
* extraction to treat the case identically to regular physical tuples.
* 如果 slot 没有包含 minimal 元组,tts_mintuple 通常必须为 NULL.
* 如含有, 则 tts_mintuple 执行实际的 MinimalTupleData 对象 (如 tts_shouldFreeMin 为 T, 则需要通过 pfree 释放内存).
* 如果 slot 只有一个 minimal 而没有通常的物理元组, 那么 tts_tuple 指向 tts_minhdr,
* 结构体的其他字段会被正确的设置为用于访问 minimal 元组.
* 特别的, tts_minhdr.t_data 指向 tts_mintuple 前的 MINIMAL_TUPLE_OFFSET 字节.
* 这可以让列提取可以独立处理通常的物理元组.
*
* tts_slow/tts_off are saved state for slot_deform_tuple, and should not
* be touched by any other code.
* tts_slow/tts_off 用于存储 slot_deform_tuple 状态, 不应通过其他代码修改.
*----------
*/
typedef struct TupleTableSlot
NodeTag type;//Node 标记
// 如 slot 为空, 则为 T
bool tts_isempty; /* true = slot is empty */
// 是否需要 pfree tts_tuple?
bool tts_shouldFree; /* should pfree tts_tuple? */
// 是否需要 pfree tts_mintuple?
bool tts_shouldFreeMin; /* should pfree tts_mintuple? */
#define FIELDNO_TUPLETABLESLOT_SLOW 4
// 为 slot_deform_tuple 存储状态?
bool tts_slow; /* saved state for slot_deform_tuple */
#define FIELDNO_TUPLETABLESLOT_TUPLE 5
// 物理元组, 如为虚拟元组则为 NULL
HeapTuple tts_tuple; /* physical tuple, or NULL if virtual */
#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 6
//slot 中的元组描述符
TupleDesc tts_tupleDescriptor; /* slot s tuple descriptor */
//slot 所在的上下文
MemoryContext tts_mcxt; /* slot itself is in this context */
// 元组缓存, 如无则为 InvalidBuffer
Buffer tts_buffer; /* tuple s buffer, or InvalidBuffer */
#define FIELDNO_TUPLETABLESLOT_NVALID 9
//tts_values 中的有效值
int tts_nvalid; /* # of valid values in tts_values */
#define FIELDNO_TUPLETABLESLOT_VALUES 10
// 当前每个属性的值
Datum *tts_values; /* current per-attribute values */
#define FIELDNO_TUPLETABLESLOT_ISNULL 11
//isnull 数组
bool *tts_isnull; /* current per-attribute isnull flags */
//minimal 元组, 如无则为 NULL
MinimalTuple tts_mintuple; /* minimal tuple, or NULL if none */
// 在 minimal 情况下的工作空间
HeapTupleData tts_minhdr; /* workspace for minimal-tuple-only case */
#define FIELDNO_TUPLETABLESLOT_OFF 14
//slot_deform_tuple 的存储状态
uint32 tts_off; /* saved state for slot_deform_tuple */
// 不能被变更的描述符 (固定描述符)
bool tts_fixedTupleDescriptor; /* descriptor can t be changed */
} TupleTableSlot;
/* base tuple table slot type */
typedef struct TupleTableSlot
NodeTag type;//Node 标记
#define FIELDNO_TUPLETABLESLOT_FLAGS 1
uint16 tts_flags; /* 布尔状态;Boolean states */
#define FIELDNO_TUPLETABLESLOT_NVALID 2
AttrNumber tts_nvalid; /* 在 tts_values 中有多少有效的 values;# of valid values in tts_values */
const TupleTableSlotOps *const tts_ops; /* slot 的实际实现;implementation of slot */
#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 4
TupleDesc tts_tupleDescriptor; /* slot 的元组描述符;slot s tuple descriptor */
#define FIELDNO_TUPLETABLESLOT_VALUES 5
Datum *tts_values; /* 当前属性值;current per-attribute values */
#define FIELDNO_TUPLETABLESLOT_ISNULL 6
bool *tts_isnull; /* 当前属性 isnull 标记;current per-attribute isnull flags */
MemoryContext tts_mcxt; /* 内存上下文; slot itself is in this context */
} TupleTableSlot;
/* routines for a TupleTableSlot implementation */
//TupleTableSlot 的 小程序
struct TupleTableSlotOps
/* Minimum size of the slot */
//slot 的最小化大小
size_t base_slot_size;
/* Initialization. */
// 初始化方法
void (*init)(TupleTableSlot *slot);
/* Destruction. */
// 析构方法
void (*release)(TupleTableSlot *slot);
/*
* Clear the contents of the slot. Only the contents are expected to be
* cleared and not the tuple descriptor. Typically an implementation of
* this callback should free the memory allocated for the tuple contained
* in the slot.
* 清除 slot 中的内容。 * 只希望清除内容,而不希望清除元组描述符。 * 通常,这个回调的实现应该释放为 slot 中包含的元组分配的内存。 */
void (*clear)(TupleTableSlot *slot);
/*
* Fill up first natts entries of tts_values and tts_isnull arrays with
* values from the tuple contained in the slot. The function may be called
* with natts more than the number of attributes available in the tuple,
* in which case it should set tts_nvalid to the number of returned
* columns.
* 用 slot 中包含的元组的值填充 tts_values 和 tts_isnull 数组的第一个 natts 条目。 * 在调用该函数时,natts 可能多于元组中可用属性的数量,在这种情况下, * 应该将 tts_nvalid 设置为返回列的数量。 */
void (*getsomeattrs)(TupleTableSlot *slot, int natts);
/*
* Returns value of the given system attribute as a datum and sets isnull
* to false, if it s not NULL. Throws an error if the slot type does not
* support system attributes.
* 将给定系统属性的值作为基准返回,如果不为 NULL, * 则将 isnull 设置为 false。如果 slot 类型不支持系统属性,则引发错误。 */
Datum (*getsysattr)(TupleTableSlot *slot, int attnum, bool *isnull);
/*
* Make the contents of the slot solely depend on the slot, and not on
* underlying resources (like another memory context, buffers, etc).
* 使 slot 的内容完全依赖于 slot,而不是底层资源 (如另一个内存上下文、缓冲区等)。 */
void (*materialize)(TupleTableSlot *slot);
/*
* Copy the contents of the source slot into the destination slot s own
* context. Invoked using callback of the destination slot.
* 将源 slot 的内容复制到目标 slot 自己的上下文中。 * 使用目标 slot 的回调函数调用。 */
void (*copyslot) (TupleTableSlot *dstslot, TupleTableSlot *srcslot);
/*
* Return a heap tuple owned by the slot. It is slot s responsibility to
* free the memory consumed by the heap tuple. If the slot can not own a
* heap tuple, it should not implement this callback and should set it as
* NULL.
* 返回 slot“拥有”的堆元组。 * slot 负责释放堆元组分配的内存。 * 如果 slot 不能“拥有”堆元组,它不应该实现这个回调函数,应该将它设置为 NULL。 */
HeapTuple (*get_heap_tuple)(TupleTableSlot *slot);
/*
* Return a minimal tuple owned by the slot. It is slot s responsibility
* to free the memory consumed by the minimal tuple. If the slot can not
* own a minimal tuple, it should not implement this callback and should
* set it as NULL.
* 返回 slot“拥有”的最小元组。 * slot 负责释放最小元组分配的内存。 * 如果 slot 不能“拥有”最小元组,它不应该实现这个回调函数,应该将它设置为 NULL。 */
MinimalTuple (*get_minimal_tuple)(TupleTableSlot *slot);
/*
* Return a copy of heap tuple representing the contents of the slot. The
* copy needs to be palloc d in the current memory context. The slot
* itself is expected to remain unaffected. It is *not* expected to have
* meaningful system columns in the copy. The copy is not be owned by
* the slot i.e. the caller has to take responsibilty to free memory
* consumed by the slot.
* 返回表示 slot 内容的堆元组副本。 * 需要在当前内存上下文中对副本进行内存分配 palloc。 * 预计 slot 本身不会受到影响。 * 它不希望在副本中有有意义的“系统列”。副本不是 slot“拥有”的,即调用方必须负责释放 slot 消耗的内存。 */
HeapTuple (*copy_heap_tuple)(TupleTableSlot *slot);
/*
* Return a copy of minimal tuple representing the contents of the slot. The
* copy needs to be palloc d in the current memory context. The slot
* itself is expected to remain unaffected. It is *not* expected to have
* meaningful system columns in the copy. The copy is not be owned by
* the slot i.e. the caller has to take responsibilty to free memory
* consumed by the slot.
* 返回表示 slot 内容的最小元组的副本。 * 需要在当前内存上下文中对副本进行 palloc。 * 预计 slot 本身不会受到影响。 * 它不希望在副本中有有意义的“系统列”。副本不是 slot“拥有”的,即调用方必须负责释放 slot 消耗的内存。 */
MinimalTuple (*copy_minimal_tuple)(TupleTableSlot *slot);
typedef struct tupleDesc
int natts; /* tuple 中的属性数量;number of attributes in the tuple */
Oid tdtypeid; /* tuple 类型的组合类型 ID;composite type ID for tuple type */
int32 tdtypmod; /* tuple 类型的 typmode;typmod for tuple type */
int tdrefcount; /* 依赖计数, 如为 -1, 则没有依赖;reference count, or -1 if not counting */
TupleConstr *constr; /* 约束, 如无则为 NULL;constraints, or NULL if none */
/* attrs[N] is the description of Attribute Number N+1 */
//attrs[N] 是第 N + 1 个属性的描述符
FormData_pg_attribute attrs[FLEXIBLE_ARRAY_MEMBER];
} *TupleDesc;
二、源码解读
static void
tts_heap_getsomeattrs(TupleTableSlot *slot, int natts)
HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
Assert(!TTS_EMPTY(slot));
slot_deform_heap_tuple(slot, hslot- tuple, hslot- off, natts);
* slot_deform_heap_tuple
* Given a TupleTableSlot, extract data from the slot s physical tuple
* into its Datum/isnull arrays. Data is extracted up through the
* natts th column (caller must ensure this is a legal column number).
* 给定一个 TupleTableSlot,从其中提取数据到 Datum/isnull 数组中。 * 数据根据第 N 个属性列来进行提取。 *
* This is essentially an incremental version of heap_deform_tuple:
* on each call we extract attributes up to the one needed, without
* re-computing information about previously extracted attributes.
* slot- tts_nvalid is the number of attributes already extracted.
* slot- tts_nvalid 是已完成提取的属性格式。 *
* This is marked as always inline, so the different offp for different types
* of slots gets optimized away.
*/
static pg_attribute_always_inline void
slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp,
int natts)
// 元组描述符
TupleDesc tupleDesc = slot- tts_tupleDescriptor;
// 列值数组
Datum *values = slot- tts_values;
//isnull 标记数组
bool *isnull = slot- tts_isnull;
// 头部信息
HeapTupleHeader tup = tuple- t_data;
bool hasnulls = HeapTupleHasNulls(tuple);
// 属性编号
int attnum;
// 指向元组数据的指针
char *tp; /* ptr to tuple data */
// 偏移
uint32 off; /* offset in tuple data */
// 指向 tuple 中的 null bitmap
bits8 *bp = tup- t_bits; /* ptr to null bitmap in tuple */
// 是否可以使用 / 设置 attcacheoff
bool slow; /* can we use/set attcacheoff? */
/* We can only fetch as many attributes as the tuple has. */
// 只能提取元组中有的属性, 获取元组个数
natts = Min(HeapTupleHeaderGetNatts(tuple- t_data), natts);
/*
* Check whether the first call for this tuple, and initialize or restore
* loop state.
* 是否第一次调用?
*/
attnum = slot- tts_nvalid;
if (attnum == 0)
{
/* Start from the first attribute */
// 从第一个属性开始
off = 0;
slow = false;
}
else
{
/* Restore state from previous execution */
// 从上一次执行中恢复状态
off = *offp;
slow = TTS_SLOW(slot);
}
// 调整指针位置
tp = (char *) tup + tup- t_hoff;
for (; attnum natts; attnum++)
{
// 获取列值
Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
if (hasnulls att_isnull(attnum, bp))
{
//null
values[attnum] = (Datum) 0;
isnull[attnum] = true;
slow = true; /* can t use attcacheoff anymore */
continue;
}
isnull[attnum] = false;
if (!slow thisatt- attcacheoff = 0)
off = thisatt- attcacheoff;
else if (thisatt- attlen == -1)
{
/*
* We can only cache the offset for a varlena attribute if the
* offset is already suitably aligned, so that there would be no
* pad bytes in any case: then the offset will be valid for either
* an aligned or unaligned value.
* varlena: 无论是否对齐, 偏移都是有效的.
*/
if (!slow
off == att_align_nominal(off, thisatt- attalign))
thisatt- attcacheoff = off;
else
{
off = att_align_pointer(off, thisatt- attalign, -1,
tp + off);
slow = true;
}
}
else
{
/* not varlena, so safe to use att_align_nominal */
// 非 varlena: 使用 att_align_nominal
off = att_align_nominal(off, thisatt- attalign);
if (!slow)
thisatt- attcacheoff = off;
}
// 获取列值
values[attnum] = fetchatt(thisatt, tp + off);
// 调整偏移
off = att_addlength_pointer(off, thisatt- attlen, tp + off);
if (thisatt- attlen = 0)
slow = true; /* can t use attcacheoff anymore */
}
/*
* Save state for next execution
* 存储状态
*/
slot- tts_nvalid = attnum;
*offp = off;
if (slow)
slot- tts_flags |= TTS_FLAG_SLOW;
else
slot- tts_flags = ~TTS_FLAG_SLOW;
/* Accessor for the i th attribute of tupdesc. */
#define TupleDescAttr(tupdesc, i) ((tupdesc)- attrs[(i)])
#define fetchatt(A,T) fetch_att(T, (A)- attbyval, (A)- attlen)
* Same, but work from byval/len parameters rather than Form_pg_attribute.
*/
#if SIZEOF_DATUM == 8
#define fetch_att(T,attbyval,attlen) \
( \
(attbyval) ? \
( \
(attlen) == (int) sizeof(Datum) ? \
*((Datum *)(T)) \
: \
( \
(attlen) == (int) sizeof(int32) ? \
Int32GetDatum(*((int32 *)(T))) \
: \
( \
(attlen) == (int) sizeof(int16) ? \
Int16GetDatum(*((int16 *)(T))) \
: \
( \
AssertMacro((attlen) == 1), \
CharGetDatum(*((char *)(T))) \
) \
) \
) \
) \
: \
PointerGetDatum((char *) (T)) \
#else /* SIZEOF_DATUM != 8 */
#define fetch_att(T,attbyval,attlen) \
( \
(attbyval) ? \
( \
(attlen) == (int) sizeof(int32) ? \
Int32GetDatum(*((int32 *)(T))) \
: \
( \
(attlen) == (int) sizeof(int16) ? \
Int16GetDatum(*((int16 *)(T))) \
: \
( \
AssertMacro((attlen) == 1), \
CharGetDatum(*((char *)(T))) \
) \
) \
) \
: \
PointerGetDatum((char *) (T)) \
#endif /* SIZEOF_DATUM == 8 */
* DatumGetPointer
* Returns pointer value of a datum.
*/
#define DatumGetPointer(X) ((Pointer) (X))
* PointerGetDatum
* Returns datum representation for a pointer.
*/
#define PointerGetDatum(X) ((Datum) (X))
三、跟踪分析
执行 SQL:
[local]:5432 pg12@testdb=# select * from t_getattrs;
启动 gdb,进入断点
(gdb) b slot_deform_heap_tuple
Breakpoint 3 at 0x6fdeac: file execTuples.c, line 892.
(gdb) c
Continuing.
Breakpoint 3, slot_deform_heap_tuple (slot=0x12312a0, tuple=0x1231880, offp=0x12312e8, natts=5)
at execTuples.c:892
892 TupleDesc tupleDesc = slot- tts_tupleDescriptor;
(gdb) bt
#0 slot_deform_heap_tuple (slot=0x12312a0, tuple=0x1231880, offp=0x12312e8, natts=5) at execTuples.c:892
#1 0x00000000006fd7d6 in tts_buffer_heap_getsomeattrs (slot=0x12312a0, natts=5) at execTuples.c:676
#2 0x00000000006ff7a9 in slot_getsomeattrs_int (slot=0x12312a0, attnum=5) at execTuples.c:1877
#3 0x000000000048ed13 in slot_getsomeattrs (slot=0x12312a0, attnum=5)
at ../../../../src/include/executor/tuptable.h:345
#4 0x000000000048ed39 in slot_getallattrs (slot=0x12312a0) at ../../../../src/include/executor/tuptable.h:357
#5 0x000000000048f88a in printtup (slot=0x12312a0, self=0x1239a50) at printtup.c:392
#6 0x00000000006efc3c in ExecutePlan (estate=0x1230e38, planstate=0x1231090, use_parallel_mode=false,
operation=CMD_SELECT, sendTuples=true, numberTuples=0, direction=ForwardScanDirection, dest=0x1239a50,
execute_once=true) at execMain.c:1685
#7 0x00000000006ed9df in standard_ExecutorRun (queryDesc=0x121b978, direction=ForwardScanDirection, count=0,
execute_once=true) at execMain.c:364
#8 0x00000000006ed815 in ExecutorRun (queryDesc=0x121b978, direction=ForwardScanDirection, count=0,
execute_once=true) at execMain.c:308
#9 0x00000000008f1010 in PortalRunSelect (portal=0x11b9c08, forward=true, count=0, dest=0x1239a50)
at pquery.c:929
#10 0x00000000008f0cae in PortalRun (portal=0x11b9c08, count=9223372036854775807, isTopLevel=true,
run_once=true, dest=0x1239a50, altdest=0x1239a50, completionTag=0x7ffd32962250 ) at pquery.c:770
#11 0x00000000008ead35 in exec_simple_query (query_string=0x1152d98 select * from t_getattrs;)
at postgres.c:1215
#12 0x00000000008eefa5 in PostgresMain (argc=1, argv=0x117fda8, dbname=0x117fbf0 testdb ,
username=0x114fab8 pg12 ) at postgres.c:4236
#13 0x0000000000845915 in BackendRun (port=0x1175bc0) at postmaster.c:4431
#14 0x00000000008450f3 in BackendStartup (port=0x1175bc0) at postmaster.c:4122
---Type return to continue, or q return to quit---
#15 0x000000000084132f in ServerLoop () at postmaster.c:1704
#16 0x0000000000840be5 in PostmasterMain (argc=1, argv=0x114da70) at postmaster.c:1377
#17 0x0000000000761469 in main (argc=1, argv=0x114da70) at main.c:228
(gdb)
(gdb)
输入参数
(gdb) p *slot -- 元组 slot
$1 = {type = T_TupleTableSlot, tts_flags = 16, tts_nvalid = 0, tts_ops = 0xc3e780 TTSOpsBufferHeapTuple ,
tts_tupleDescriptor = 0x7fe1af2fd7a8, tts_values = 0x1231310, tts_isnull = 0x1231338, tts_mcxt = 0x1230d20,
tts_tid = {ip_blkid = {bi_hi = 0, bi_lo = 0}, ip_posid = 1}, tts_tableOid = 131110}
(gdb) p tuple
$2 = (HeapTuple) 0x1231880
(gdb) p *tuple -- 元组
$3 = {t_len = 67, t_self = {ip_blkid = {bi_hi = 0, bi_lo = 0}, ip_posid = 1}, t_tableOid = 131110,
t_data = 0x7fe18396e438}
(gdb) p *offp -- 偏移
$4 = 0
(gdb) p natts -- 5 个属性
$5 = 5
(gdb)
初始化相关变量
(gdb) n
893 Datum *values = slot- tts_values;
(gdb)
894 bool *isnull = slot- tts_isnull;
(gdb)
895 HeapTupleHeader tup = tuple- t_data;
(gdb) p *values
$6 = 0
(gdb) p *isnull
$7 = false
(gdb) n
896 bool hasnulls = HeapTupleHasNulls(tuple);
(gdb)
900 bits8 *bp = tup- t_bits; /* ptr to null bitmap in tuple */
(gdb)
904 natts = Min(HeapTupleHeaderGetNatts(tuple- t_data), natts);
(gdb) p *bp
$8 = 0 \000
(gdb) n
910 attnum = slot- tts_nvalid;
(gdb) p natts
$9 = 5
(gdb) n
911 if (attnum == 0)
(gdb) p attnum
$10 = 0
(gdb)
首次执行, 设置偏移等信息以及初始化元组数据指针
(gdb) n
914 off = 0;
(gdb)
915 slow = false;
(gdb)
924 tp = (char *) tup + tup- t_hoff;
(gdb) p tup- t_hoff
$11 = 24 \030
(gdb) p *tup -- 元组头部信息
$12 = {t_choice = {t_heap = {t_xmin = 14764, t_xmax = 0, t_field3 = {t_cid = 0, t_xvac = 0}}, t_datum = { datum_len_ = 14764, datum_typmod = 0, datum_typeid = 0}}, t_ctid = {ip_blkid = {bi_hi = 0, bi_lo = 0},
ip_posid = 1}, t_infomask2 = 5, t_infomask = 2306, t_hoff = 24 \030 , t_bits = 0x7fe18396e44f }
(gdb)
开始循环获取每个属性的值
(gdb) n
926 for (; attnum natts; attnum++)
(gdb)
928 Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
(gdb)
930 if (hasnulls att_isnull(attnum, bp))
属性信息
(gdb) p thisatt
$13 = (Form_pg_attribute) 0x7fe1af2fd7c0
(gdb) p *thisatt
$14 = {attrelid = 131110, attname = {data = id , \000 repeats 61 times}, atttypid = 23, attstattarget = -1,
attlen = 4, attnum = 1, attndims = 0, attcacheoff = 0, atttypmod = -1, attbyval = true, attstorage = 112 p ,
attalign = 105 i , attnotnull = false, atthasdef = false, atthasmissing = false, attidentity = 0 \000 ,
attgenerated = 0 \000 , attisdropped = false, attislocal = true, attinhcount = 0, attcollation = 0}
(gdb)
获取第 1 个属性值,即 id 的值。注意:fetchatt 执行的逻辑是 Int32GetDatum(*((int32 *)(T)))
(gdb) p thisatt- attbyval
$18 = true
(gdb) p thisatt- attlen
$19 = 4
(gdb) p SIZEOF_DATUM
$24 = 8
(gdb) p (int) sizeof(Datum)
$26 = 8
(gdb) p (int) sizeof(int32)
$27 = 4
(gdb)
(gdb) p Int32GetDatum(*((int32 *)(tp+off)))
$25 = 1
(attlen) == (int) sizeof(int32) ? \
Int32GetDatum(*((int32 *)(T))) \
###
获取第 2 个属性值,即 col_varchar 的值。注意:fetchatt 执行的逻辑是 PointerGetDatum((char *) (T))
(gdb) n
973 if (thisatt- attlen = 0)
(gdb)
926 for (; attnum natts; attnum++)
(gdb)
928 Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
(gdb)
930 if (hasnulls att_isnull(attnum, bp))
(gdb) p *thisatt
$28 = {attrelid = 131110, attname = {data = col_varchar , \000 repeats 52 times}, atttypid = 1043,
attstattarget = -1, attlen = -1, attnum = 2, attndims = 0, attcacheoff = 4, atttypmod = 24, attbyval = false,
attstorage = 120 x , attalign = 105 i , attnotnull = false, atthasdef = false, atthasmissing = false,
attidentity = 0 \000 , attgenerated = 0 \000 , attisdropped = false, attislocal = true, attinhcount = 0,
attcollation = 100}
(gdb) n
938 isnull[attnum] = false;
(gdb)
940 if (!slow thisatt- attcacheoff = 0)
(gdb)
941 off = thisatt- attcacheoff;
(gdb)
969 values[attnum] = fetchatt(thisatt, tp + off);
(gdb) p off
$29 = 4
(gdb) p PointerGetDatum((char *) (tp+off))
$30 = 140606552073300
(gdb) x/5c PointerGetDatum((char *) (tp+off))
0x7fe18396e454: 11 \v 116 t 101 e 115 s 116 t
(gdb) p (char *)PointerGetDatum((char *) (tp+off))
$32 = 0x7fe18396e454 \vtest\027test
(gdb)
获取第 2 个属性值,即 col_char 的值。注意:fetchatt 执行的逻辑是 PointerGetDatum((char *) (T))
(gdb) n
971 off = att_addlength_pointer(off, thisatt- attlen, tp + off);
(gdb)
973 if (thisatt- attlen = 0)
(gdb) p off
$33 = 9
(gdb) p thisatt- attlen
$34 = -1
(gdb) n
974 slow = true; /* can t use attcacheoff anymore */
(gdb)
926 for (; attnum natts; attnum++)
(gdb)
928 Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
(gdb)
930 if (hasnulls att_isnull(attnum, bp))
(gdb)
938 isnull[attnum] = false;
(gdb)
940 if (!slow thisatt- attcacheoff = 0)
(gdb)
942 else if (thisatt- attlen == -1)
(gdb)
950 if (!slow
(gdb)
955 off = att_align_pointer(off, thisatt- attalign, -1,
(gdb)
957 slow = true;
(gdb) p off
$35 = 9
(gdb) n
969 values[attnum] = fetchatt(thisatt, tp + off);
(gdb) p *thisatt
$36 = {attrelid = 131110, attname = {data = col_char , \000 repeats 55 times}, atttypid = 1042,
attstattarget = -1, attlen = -1, attnum = 3, attndims = 0, attcacheoff = -1, atttypmod = 14, attbyval = false,
attstorage = 120 x , attalign = 105 i , attnotnull = false, atthasdef = false, atthasmissing = false,
attidentity = 0 \000 , attgenerated = 0 \000 , attisdropped = false, attislocal = true, attinhcount = 0,
attcollation = 100}
(gdb) p (char *)PointerGetDatum((char *) (tp+off))
$37 = 0x7fe18396e459 \027test
(gdb)
获取第 4 个属性值,即 col_double 的值。注意:fetchatt 执行的逻辑是 *((Datum *)(T))
(gdb) n
971 off = att_addlength_pointer(off, thisatt- attlen, tp + off);
(gdb)
973 if (thisatt- attlen = 0)
(gdb) p off
$38 = 20
(gdb) n
974 slow = true; /* can t use attcacheoff anymore */
(gdb)
926 for (; attnum natts; attnum++)
(gdb)
928 Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
(gdb)
930 if (hasnulls att_isnull(attnum, bp))
(gdb)
938 isnull[attnum] = false;
(gdb)
940 if (!slow thisatt- attcacheoff = 0)
(gdb)
942 else if (thisatt- attlen == -1)
(gdb)
963 off = att_align_nominal(off, thisatt- attalign);
(gdb)
965 if (!slow)
(gdb) p off
$39 = 24
(gdb) n
969 values[attnum] = fetchatt(thisatt, tp + off);
(gdb) p *thisatt
$40 = {attrelid = 131110, attname = {data = col_double , \000 repeats 53 times}, atttypid = 701,
attstattarget = -1, attlen = 8, attnum = 4, attndims = 0, attcacheoff = -1, atttypmod = -1, attbyval = true,
attstorage = 112 p , attalign = 100 d , attnotnull = false, atthasdef = false, atthasmissing = false,
attidentity = 0 \000 , attgenerated = 0 \000 , attisdropped = false, attislocal = true, attinhcount = 0,
attcollation = 0}
(gdb) p *((Datum *)(tp+off))
$41 = 4653143983961984205
(gdb) p *(double *)((Datum *)(tp+off))
$49 = 1234.45
(gdb)
获取第 5 个属性值,即 col_numeric 的值。注意:fetchatt 执行的逻辑是 PointerGetDatum((char *) (T))
(gdb) n
971 off = att_addlength_pointer(off, thisatt- attlen, tp + off);
(gdb)
973 if (thisatt- attlen = 0)
(gdb) p off
$50 = 32
(gdb) n
926 for (; attnum natts; attnum++)
(gdb)
928 Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
(gdb)
930 if (hasnulls att_isnull(attnum, bp))
(gdb)
938 isnull[attnum] = false;
(gdb)
940 if (!slow thisatt- attcacheoff = 0)
(gdb)
942 else if (thisatt- attlen == -1)
(gdb)
950 if (!slow
(gdb)
955 off = att_align_pointer(off, thisatt- attalign, -1,
(gdb)
957 slow = true;
(gdb)
969 values[attnum] = fetchatt(thisatt, tp + off);
(gdb) p *thisatt
$51 = {attrelid = 131110, attname = {data = col_numeric , \000 repeats 52 times}, atttypid = 1700,
attstattarget = -1, attlen = -1, attnum = 5, attndims = 0, attcacheoff = -1, atttypmod = -1, attbyval = false,
attstorage = 109 m , attalign = 105 i , attnotnull = false, atthasdef = false, atthasmissing = false,
attidentity = 0 \000 , attgenerated = 0 \000 , attisdropped = false, attislocal = true, attinhcount = 0,
attcollation = 0}
(gdb) p PointerGetDatum((char *) (tp+off))
$52 = 140606552073328
(gdb) x/32c PointerGetDatum((char *) (tp+off))
0x7fe18396e470: 23 \027 -127 \201 -126 \202 1 \001 0 \000 41 ) 9 \t 97 a
0x7fe18396e478: 30 \036 88 X 27 \033 0 \000 0 \000 0 \000 0 \000 0 \000
0x7fe18396e480: 0 \000 0 \000 0 \000 0 \000 0 \000 0 \000 0 \000 0 \000
0x7fe18396e488: 0 \000 0 \000 0 \000 0 \000 0 \000 0 \000 0 \000 0 \000
(gdb) x/32x PointerGetDatum((char *) (tp+off))
0x7fe18396e470: 0x17 0x81 0x82 0x01 0x00 0x29 0x09 0x61
0x7fe18396e478: 0x1e 0x58 0x1b 0x00 0x00 0x00 0x00 0x00
0x7fe18396e480: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fe18396e488: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
(gdb)
完成函数调用
(gdb) n
971 off = att_addlength_pointer(off, thisatt- attlen, tp + off);
(gdb) p values[attnum]
$55 = 140606552073328
(gdb) n
973 if (thisatt- attlen = 0)
(gdb)
974 slow = true; /* can t use attcacheoff anymore */
(gdb)
926 for (; attnum natts; attnum++)
(gdb)
980 slot- tts_nvalid = attnum;
(gdb)
981 *offp = off;
(gdb)
982 if (slow)
(gdb)
983 slot- tts_flags |= TTS_FLAG_SLOW;
(gdb)
986 }
(gdb)
tts_buffer_heap_getsomeattrs (slot=0x12312a0, natts=5) at execTuples.c:677
677 }
(gdb)
到此,相信大家对“PostgreSQL 执行查询时获取元组属性值实现方法是什么”有了更深的了解,不妨来实际操作一番吧!这里是丸趣 TV 网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!