本篇内容主要讲解“PostgreSQL ExecAgg中调用的函数是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“PostgreSQL ExecAgg中调用的函数是什么”吧!
一、数据结构
AggState
聚合函数执行时状态结构体,内含AggStatePerAgg等结构体
//在nodeAgg.c中私有的结构体
typedef struct AggStatePerAggData *AggStatePerAgg;
typedef struct AggStatePerTransData *AggStatePerTrans;
typedef struct AggStatePerGroupData *AggStatePerGroup;
typedef struct AggStatePerPhaseData *AggStatePerPhase;
typedef struct AggStatePerHashData *AggStatePerHash;
typedef struct AggState
{
//第一个字段是NodeTag(继承自ScanState)
ScanState ss;
//targetlist和quals中所有的Aggref
List *aggs;
//链表的大小(可以为0)
int numaggs;
//pertrans条目大小
int numtrans;
//Agg策略模式
AggStrategy aggstrategy;
//agg-splitting模式,参见nodes.h
AggSplit aggsplit;
//指向当前步骤数据的指针
AggStatePerPhase phase;
//步骤数(包括0)
int numphases;
//当前步骤
int current_phase;
//per-Aggref信息
AggStatePerAgg peragg;
//per-Trans状态信息
AggStatePerTrans pertrans;
//长生命周期数据的ExprContexts(hashtable)
ExprContext *hashcontext;
////长生命周期数据的ExprContexts(每一个GS使用)
ExprContext **aggcontexts;
//输入表达式的ExprContext
ExprContext *tmpcontext;
#define FIELDNO_AGGSTATE_CURAGGCONTEXT 14
//当前活跃的aggcontext
ExprContext *curaggcontext;
//当前活跃的aggregate(如存在)
AggStatePerAgg curperagg;
#define FIELDNO_AGGSTATE_CURPERTRANS 16
//当前活跃的trans state
AggStatePerTrans curpertrans;
//输入结束?
bool input_done;
//Agg扫描结束?
bool agg_done;
//最后一个grouping set
int projected_set;
#define FIELDNO_AGGSTATE_CURRENT_SET 20
//将要解析的当前grouping set
int current_set;
//当前投影操作的分组列
Bitmapset *grouped_cols;
//倒序的分组列链表
List *all_grouped_cols;
//-------- 下面的列用于grouping set步骤数据
//所有步骤中最大的sets大小
int maxsets;
//所有步骤的数组
AggStatePerPhase phases;
//对于phases > 1,已排序的输入信息
Tuplesortstate *sort_in;
//对于下一个步骤,输入已拷贝
Tuplesortstate *sort_out;
//排序结果的slot
TupleTableSlot *sort_slot;
//------- 下面的列用于AGG_PLAIN和AGG_SORTED模式:
//per-group指针的grouping set编号数组
AggStatePerGroup *pergroups;
//当前组的第一个元组拷贝
HeapTuple grp_firstTuple;
//--------- 下面的列用于AGG_HASHED和AGG_MIXED模式:
//是否已填充hash表?
bool table_filled;
//hash桶数?
int num_hashes;
//相应的哈希表数据数组
AggStatePerHash perhash;
//per-group指针的grouping set编号数组
AggStatePerGroup *hash_pergroup;
//---------- agg输入表达式解析支持
#define FIELDNO_AGGSTATE_ALL_PERGROUPS 34
//首先是->pergroups,然后是hash_pergroup
AggStatePerGroup *all_pergroups;
//投影实现机制
ProjectionInfo *combinedproj;
} AggState;
//nodeag .c支持的基本选项
#define AGGSPLITOP_COMBINE 0x01
#define AGGSPLITOP_SKIPFINAL 0x02
#define AGGSPLITOP_SERIALIZE 0x04
#define AGGSPLITOP_DESERIALIZE 0x08
//支持的操作模式
typedef enum AggSplit
{
//基本 : 非split聚合
AGGSPLIT_SIMPLE = 0,
//部分聚合的初始步骤,序列化
AGGSPLIT_INITIAL_SERIAL = AGGSPLITOP_SKIPFINAL | AGGSPLITOP_SERIALIZE,
//部分聚合的最终步骤,反序列化
AGGSPLIT_FINAL_DESERIAL = AGGSPLITOP_COMBINE | AGGSPLITOP_DESERIALIZE
} AggSplit;
//测试AggSplit选择了哪些基本选项
#define DO_AGGSPLIT_COMBINE(as) (((as) & AGGSPLITOP_COMBINE) != 0)
#define DO_AGGSPLIT_SKIPFINAL(as) (((as) & AGGSPLITOP_SKIPFINAL) != 0)
#define DO_AGGSPLIT_SERIALIZE(as) (((as) & AGGSPLITOP_SERIALIZE) != 0)
#define DO_AGGSPLIT_DESERIALIZE(as) (((as) & AGGSPLITOP_DESERIALIZE) != 0)
二、源码解读
ExecAgg接收从outer子计划返回的元组合适的属性上为每一个聚合函数(出现在投影列或节点表达式)执行聚合.需要聚合的元组数量依赖于是否已分组或者选择普通聚合.在已分组的聚合操作宏,为每一个组产生结果行;普通聚合,整个查询只有一个结果行.
不管哪种情况,每一个聚合结果值都会存储在表达式上下文中(ExecProject会解析结果元组)
static TupleTableSlot *
ExecAgg(PlanState *pstate)
{
AggState *node = castNode(AggState, pstate);
TupleTableSlot *result = NULL;
CHECK_FOR_INTERRUPTS();
if (!node->agg_done)
{
//基于策略进行分发
switch (node->phase->aggstrategy)
{
case AGG_HASHED:
if (!node->table_filled)
agg_fill_hash_table(node);
//填充后,执行MIXED
case AGG_MIXED:
result = agg_retrieve_hash_table(node);
break;
case AGG_PLAIN:
case AGG_SORTED:
result = agg_retrieve_direct(node);
break;
}
if (!TupIsNull(result))
return result;
}
return NULL;
}
agg_retrieve_hash_table
ExecAgg(Hash实现版本):在hash表中检索组
大体实现逻辑如下:
1.初始化相关变量,如上下文/peragg等
2.未完成,循环
2.1从perhash数据结构中获取slot
2.2调用ScanTupleHashTable获取条目
2.3如返回的条目为NULL,切换到下一个set,如已完成检索,则设置标记,退出
2.4如返回的条目不为NULL,则:
2.4.1重置内econtext上下文
2.4.2存储最小化元组
2.4.3重置firstSlot,存储该虚拟元组
2.4.4准备投影slot并执行最终的聚合运算,投影后如结果不为NULL,则返回此结果.
static TupleTableSlot *
agg_retrieve_hash_table(AggState *aggstate)
{
ExprContext *econtext;
AggStatePerAgg peragg;
AggStatePerGroup pergroup;
TupleHashEntryData *entry;
TupleTableSlot *firstSlot;
TupleTableSlot *result;
AggStatePerHash perhash;
econtext = aggstate->ss.ps.ps_ExprContext;
peragg = aggstate->peragg;
firstSlot = aggstate->ss.ss_ScanTupleSlot;
perhash = &aggstate->perhash[aggstate->current_set];
while (!aggstate->agg_done)
{
//------------- 选好
//获取Slot
TupleTableSlot *hashslot = perhash->hashslot;
int i;
//检查中断
CHECK_FOR_INTERRUPTS();
entry = ScanTupleHashTable(perhash->hashtable, &perhash->hashiter);
if (entry == NULL)
{
//条目为NULL,切换到下一个set
int nextset = aggstate->current_set + 1;
if (nextset < aggstate->num_hashes)
{
select_current_set(aggstate, nextset, true);
perhash = &aggstate->perhash[aggstate->current_set];
ResetTupleHashIterator(perhash->hashtable, &perhash->hashiter);
continue;
}
else
{
//已完成检索,设置标记,退出
aggstate->agg_done = true;
return NULL;
}
}
ResetExprContext(econtext);
ExecStoreMinimalTuple(entry->firstTuple, hashslot, false);
slot_getallattrs(hashslot);
//清理元组
//重置firstSlot
ExecClearTuple(firstSlot);
memset(firstSlot->tts_isnull, true,
firstSlot->tts_tupleDescriptor->natts * sizeof(bool));
for (i = 0; i < perhash->numhashGrpCols; i++)
{
//重置firstSlot
int varNumber = perhash->hashGrpColIdxInput[i] - 1;
firstSlot->tts_values[varNumber] = hashslot->tts_values[i];
firstSlot->tts_isnull[varNumber] = hashslot->tts_isnull[i];
}
ExecStoreVirtualTuple(firstSlot);
pergroup = (AggStatePerGroup) entry->additional;
econtext->ecxt_outertuple = firstSlot;
//准备投影slot
prepare_projection_slot(aggstate,
econtext->ecxt_outertuple,
aggstate->current_set);
//最终的聚合操作
finalize_aggregates(aggstate, peragg, pergroup);
//投影
result = project_aggregates(aggstate);
if (result)
return result;
}
//没有更多的groups了,返回NULL
return NULL;
}
#define ScanTupleHashTable(htable, iter) \
tuplehash_iterate(htable->hashtab, iter)
TupleTableSlot *
ExecStoreMinimalTuple(MinimalTuple mtup,
TupleTableSlot *slot,
bool shouldFree)
{
Assert(mtup != NULL);
Assert(slot != NULL);
Assert(slot->tts_tupleDescriptor != NULL);
if (slot->tts_shouldFree)
heap_freetuple(slot->tts_tuple);
if (slot->tts_shouldFreeMin)
heap_free_minimal_tuple(slot->tts_mintuple);
if (BufferIsValid(slot->tts_buffer))
ReleaseBuffer(slot->tts_buffer);
slot->tts_buffer = InvalidBuffer;
slot->tts_isempty = false;
slot->tts_shouldFree = false;
slot->tts_shouldFreeMin = shouldFree;
slot->tts_tuple = &slot->tts_minhdr;
slot->tts_mintuple = mtup;
slot->tts_minhdr.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET;
slot->tts_minhdr.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET);
//因为不允许访问,因此无需设置t_sefl或者t_tableOid
//标记已提取状态无效
slot->tts_nvalid = 0;
return slot;
}
TupleTableSlot *
ExecStoreVirtualTuple(TupleTableSlot *slot)
{
Assert(slot != NULL);
Assert(slot->tts_tupleDescriptor != NULL);
Assert(slot->tts_isempty);
slot->tts_isempty = false;
slot->tts_nvalid = slot->tts_tupleDescriptor->natts;
return slot;
}
三、跟踪分析
测试脚本
-- 创建数据表,插入测试数据
drop table if exists t_agg_simple;
create table t_agg_simple(bh varchar(20),c1 int,c2 int,c3 int,c4 int,c5 int,c6 int);
insert into t_agg_simple select 'GZ01',col,col,col,col,col,col from generate_series(1,1) as col;
insert into t_agg_simple select 'GZ02',col,col,col,col,col,col from generate_series(2,2) as col;
insert into t_agg_simple select 'GZ03',col,col,col,col,col,col from generate_series(3,3) as col;
insert into t_agg_simple select 'GZ04',col,col,col,col,col,col from generate_series(4,4) as col;
insert into t_agg_simple select 'GZ05',col,col,col,col,col,col from generate_series(5,5) as col;
-- 禁用并行
set max_parallel_workers_per_gather=0;
select bh,avg(c1),min(c1),max(c2) from t_agg_simple group by bh;
跟踪分析
Breakpoint 1, agg_retrieve_hash_table (aggstate=0x2929640) at nodeAgg.c:1969
1969 econtext = aggstate->ss.ps.ps_ExprContext;
(gdb)
输入参数
(gdb) p *aggstate
$1 = {ss = {ps = {type = T_AggState, plan = 0x2849a30, state = 0x2929428, ExecProcNode = 0x6ee438 <ExecAgg>,
ExecProcNodeReal = 0x6ee438 <ExecAgg>, instrument = 0x0, worker_instrument = 0x0, worker_jit_instrument = 0x0,
qual = 0x0, lefttree = 0x2929bb0, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0,
ps_ResultTupleSlot = 0x292a7b0, ps_ExprContext = 0x2929af0, ps_ProjInfo = 0x292a8f0, scandesc = 0x2929f00},
ss_currentRelation = 0x0, ss_currentScanDesc = 0x0, ss_ScanTupleSlot = 0x292a458}, aggs = 0x292ae00, numaggs = 3,
numtrans = 3, aggstrategy = AGG_HASHED, aggsplit = AGGSPLIT_SIMPLE, phase = 0x292aef8, numphases = 1, current_phase = 0,
peragg = 0x29463e0, pertrans = 0x29483f0, hashcontext = 0x2929a30, aggcontexts = 0x2929858, tmpcontext = 0x2929878,
curaggcontext = 0x2929a30, curperagg = 0x0, curpertrans = 0x2949c80, input_done = false, agg_done = false,
projected_set = -1, current_set = 0, grouped_cols = 0x0, all_grouped_cols = 0x292b090, maxsets = 1, phases = 0x292aef8,
sort_in = 0x0, sort_out = 0x0, sort_slot = 0x0, pergroups = 0x0, grp_firstTuple = 0x0, table_filled = true,
num_hashes = 1, perhash = 0x292af50, hash_pergroup = 0x29465f8, all_pergroups = 0x29465f8, combinedproj = 0x0}
(gdb)
1.初始化相关变量,如上下文/peragg等
(gdb) n
1970 peragg = aggstate->peragg;
(gdb)
1971 firstSlot = aggstate->ss.ss_ScanTupleSlot;
(gdb)
1977 perhash = &aggstate->perhash[aggstate->current_set];
(gdb)
1983 while (!aggstate->agg_done)
(gdb) p *peragg
$2 = {aggref = 0x293a458, transno = 0, finalfn_oid = 0, finalfn = {fn_addr = 0x0, fn_oid = 0, fn_nargs = 0,
fn_strict = false, fn_retset = false, fn_stats = 0 '\000', fn_extra = 0x0, fn_mcxt = 0x0, fn_expr = 0x0},
numFinalArgs = 1, aggdirectargs = 0x0, resulttypeLen = 4, resulttypeByVal = true, shareable = true}
(gdb) p *peragg->aggref
$3 = {xpr = {type = T_Aggref}, aggfnoid = 2116, aggtype = 23, aggcollid = 0, inputcollid = 0, aggtranstype = 23,
aggargtypes = 0x293a518, aggdirectargs = 0x0, args = 0x293a628, aggorder = 0x0, aggdistinct = 0x0, aggfilter = 0x0,
aggstar = false, aggvariadic = false, aggkind = 110 'n', agglevelsup = 0, aggsplit = AGGSPLIT_SIMPLE, location = 26}
(gdb) p *perhash
$4 = {hashtable = 0x2946890, hashiter = {cur = 0, end = 0, done = false}, hashslot = 0x292b238, hashfunctions = 0x292b2d0,
eqfuncoids = 0x2946700, numCols = 1, numhashGrpCols = 1, largestGrpColIdx = 1, hashGrpColIdxInput = 0x2946660,
hashGrpColIdxHash = 0x2946680, aggnode = 0x2849a30}
(gdb) p aggstate->current_set
$5 = 0
(gdb)
2.未完成,循环
2.1从perhash数据结构中获取slot
(gdb) n
1985 TupleTableSlot *hashslot = perhash->hashslot;
(gdb)
1988 CHECK_FOR_INTERRUPTS();
(gdb) p *hashslot
$6 = {type = T_TupleTableSlot, tts_isempty = false, tts_shouldFree = false, tts_shouldFreeMin = false, tts_slow = false,
tts_tuple = 0x0, tts_tupleDescriptor = 0x292b120, tts_mcxt = 0x2929310, tts_buffer = 0, tts_nvalid = 1,
tts_values = 0x292b298, tts_isnull = 0x292b2a0, tts_mintuple = 0x0, tts_minhdr = {t_len = 0, t_self = {ip_blkid = {
bi_hi = 0, bi_lo = 0}, ip_posid = 0}, t_tableOid = 0, t_data = 0x0}, tts_off = 0, tts_fixedTupleDescriptor = true}
(gdb)
2.2调用ScanTupleHashTable获取条目
(gdb) n
1993 entry = ScanTupleHashTable(perhash->hashtable, &perhash->hashiter);
(gdb) p perhash->hashiter
$7 = {cur = 0, end = 0, done = false}
(gdb) step
tuplehash_iterate (tb=0x2946720, iter=0x292af58) at ../../../src/include/lib/simplehash.h:829
829 while (!iter->done)
(gdb) n
833 elem = &tb->data[iter->cur];
(gdb) p *tb
$8 = {size = 256, members = 5, sizemask = 255, grow_threshold = 230, data = 0x2950a00, ctx = 0x2929310,
private_data = 0x2946890}
(gdb) p *tb->data
$9 = {firstTuple = 0x0, additional = 0x0, status = 0, hash = 0}
(gdb) p *iter
$10 = {cur = 0, end = 0, done = false}
(gdb) n
836 iter->cur = (iter->cur - 1) & tb->sizemask;
(gdb) n
838 if ((iter->cur & tb->sizemask) == (iter->end & tb->sizemask))
(gdb) p iter->cur
$11 = 255
(gdb)
$12 = 255
(gdb) p iter->cur & tb->sizemask
$13 = 255
(gdb) p iter->end & tb->sizemask
$14 = 0
(gdb) n
840 if (elem->status == SH_STATUS_IN_USE)
(gdb) p *elem
$15 = {firstTuple = 0x0, additional = 0x0, status = 0, hash = 0}
(gdb) n
829 while (!iter->done)
(gdb)
833 elem = &tb->data[iter->cur];
(gdb)
836 iter->cur = (iter->cur - 1) & tb->sizemask;
(gdb)
838 if ((iter->cur & tb->sizemask) == (iter->end & tb->sizemask))
(gdb)
840 if (elem->status == SH_STATUS_IN_USE)
(gdb)
829 while (!iter->done)
(gdb) finish
Run till exit from #0 tuplehash_iterate (tb=0x2946720, iter=0x292af58) at ../../../src/include/lib/simplehash.h:829
0x00000000006eed70 in agg_retrieve_hash_table (aggstate=0x2929640) at nodeAgg.c:1993
1993 entry = ScanTupleHashTable(perhash->hashtable, &perhash->hashiter);
Value returned is $16 = (TupleHashEntryData *) 0x2951d08
(gdb)
2.3如返回的条目为NULL,切换到下一个set,如已完成检索,则设置标记,退出
2.4如返回的条目不为NULL,则:
2.4.1重置内econtext上下文
2.4.2存储最小化元组
2.4.3重置firstSlot,存储该虚拟元组
2.4.4准备投影slot并执行最终的聚合运算,投影后如结果不为NULL,则返回此结果.
(gdb) n
1994 if (entry == NULL)
(gdb)
2027 ResetExprContext(econtext);
(gdb)
2033 ExecStoreMinimalTuple(entry->firstTuple, hashslot, false);
(gdb)
2034 slot_getallattrs(hashslot);
(gdb)
2036 ExecClearTuple(firstSlot);
(gdb)
2038 firstSlot->tts_tupleDescriptor->natts * sizeof(bool));
(gdb)
2037 memset(firstSlot->tts_isnull, true,
(gdb)
2040 for (i = 0; i < perhash->numhashGrpCols; i++)
(gdb) x/21x entry->firstTuple->t_bits
0x2942a87: 0x5a470b00 0x7f7e3530 0x7f7f7f7f 0x7f7f7f7f
0x2942a97: 0x0000407f 0x00000000 0x00003000 0x00000000
0x2942aa7: 0x9425c000 0x00000002 0x00000500 0x00000000
0x2942ab7: 0x7f000000 0x7f7f7f7f 0x0000057f 0x00000000
0x2942ac7: 0x7f000000 0x7f7f7f7f 0x942b087f 0x00000002
0x2942ad7: 0x7f000000
(gdb) x/21c entry->firstTuple->t_bits
0x2942a87: 0 '\000' 11 '\v' 71 'G' 90 'Z' 48 '0' 53 '5' 126 '~' 127 '\177'
0x2942a8f: 127 '\177' 127 '\177' 127 '\177' 127 '\177' 127 '\177' 127 '\177' 127 '\177' 127 '\177'
0x2942a97: 127 '\177' 64 '@' 0 '\000' 0 '\000' 0 '\000'
(gdb) n
2042 int varNumber = perhash->hashGrpColIdxInput[i] - 1;
(gdb)
2044 firstSlot->tts_values[varNumber] = hashslot->tts_values[i];
(gdb)
2045 firstSlot->tts_isnull[varNumber] = hashslot->tts_isnull[i];
(gdb)
2040 for (i = 0; i < perhash->numhashGrpCols; i++)
(gdb)
2047 ExecStoreVirtualTuple(firstSlot);
(gdb)
2049 pergroup = (AggStatePerGroup) entry->additional;
(gdb) p *entry
$1 = {firstTuple = 0x2942a78, additional = 0x2942ab0, status = 1, hash = 1229618635}
(gdb) p *entry->firstTuple
$2 = {t_len = 21, mt_padding = "\000\000\000\000\000", t_infomask2 = 1, t_infomask = 2, t_hoff = 24 '\030',
t_bits = 0x2942a87 ""}
(gdb)
获取结果
(gdb) n
2055 econtext->ecxt_outertuple = firstSlot;
(gdb) p *pergroup
$3 = {transValue = 5, transValueIsNull = false, noTransValue = false}
(gdb) n
2057 prepare_projection_slot(aggstate,
(gdb)
2061 finalize_aggregates(aggstate, peragg, pergroup);
(gdb)
2063 result = project_aggregates(aggstate);
(gdb)
2064 if (result)
(gdb) p result
$4 = (TupleTableSlot *) 0x2927920
(gdb) p *result
$5 = {type = T_TupleTableSlot, tts_isempty = false, tts_shouldFree = false, tts_shouldFreeMin = false, tts_slow = false,
tts_tuple = 0x0, tts_tupleDescriptor = 0x2927708, tts_mcxt = 0x2926480, tts_buffer = 0, tts_nvalid = 4,
tts_values = 0x2927980, tts_isnull = 0x29279a0, tts_mintuple = 0x0, tts_minhdr = {t_len = 0, t_self = {ip_blkid = {
bi_hi = 0, bi_lo = 0}, ip_posid = 0}, t_tableOid = 0, t_data = 0x0}, tts_off = 0, tts_fixedTupleDescriptor = true}
(gdb) p *result->tts_values
$6 = 43264648
(gdb) p *result->tts_tupleDescriptor
$7 = {natts = 4, tdtypeid = 2249, tdtypmod = -1, tdhasoid = false, tdrefcount = -1, constr = 0x0, attrs = 0x2927728}
(gdb) x/32x result->tts_values
0x2927980: 0x88 0x2a 0x94 0x02 0x00 0x00 0x00 0x00
0x2927988: 0x88 0x47 0x94 0x02 0x00 0x00 0x00 0x00
0x2927990: 0x05 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x2927998: 0x05 0x00 0x00 0x00 0x00 0x00 0x00 0x00
(gdb) p *result->tts_tupleDescriptor->attrs
$8 = {attrelid = 0, attname = {data = "bh", '\000' <repeats 61 times>}, atttypid = 1043, attstattarget = -1, attlen = -1,
attnum = 1, attndims = 0, attcacheoff = -1, atttypmod = 24, attbyval = false, attstorage = 120 'x', attalign = 105 'i',
attnotnull = false, atthasdef = false, atthasmissing = false, attidentity = 0 '\000', attisdropped = false,
attislocal = true, attinhcount = 0, attcollation = 100}
(gdb) p result->tts_tupleDescriptor->attrs[1]
$9 = {attrelid = 0, attname = {data = "avg", '\000' <repeats 60 times>}, atttypid = 1700, attstattarget = -1, attlen = -1,
attnum = 2, attndims = 0, attcacheoff = -1, atttypmod = -1, attbyval = false, attstorage = 109 'm', attalign = 105 'i',
attnotnull = false, atthasdef = false, atthasmissing = false, attidentity = 0 '\000', attisdropped = false,
attislocal = true, attinhcount = 0, attcollation = 0}
(gdb) p result->tts_tupleDescriptor->attrs[2]
$10 = {attrelid = 0, attname = {data = "min", '\000' <repeats 60 times>}, atttypid = 23, attstattarget = -1, attlen = 4,
attnum = 3, attndims = 0, attcacheoff = -1, atttypmod = -1, attbyval = true, attstorage = 112 'p', attalign = 105 'i',
attnotnull = false, atthasdef = false, atthasmissing = false, attidentity = 0 '\000', attisdropped = false,
attislocal = true, attinhcount = 0, attcollation = 0}
(gdb) p result->tts_tupleDescriptor->attrs[3]
$11 = {attrelid = 0, attname = {data = "max", '\000' <repeats 60 times>}, atttypid = 23, attstattarget = -1, attlen = 4,
attnum = 4, attndims = 0, attcacheoff = -1, atttypmod = -1, attbyval = true, attstorage = 112 'p', attalign = 105 'i',
attnotnull = false, atthasdef = false, atthasmissing = false, attidentity = 0 '\000', attisdropped = false,
attislocal = true, attinhcount = 0, attcollation = 0}
到此,相信大家对“PostgreSQL ExecAgg中调用的函数是什么”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!