目 录
学习参考书籍、网站或博文:
- 参考书籍:《PostgreSQL数据库内核分析》
- Lex & Yacc 点击前往
- Postgresql源码学习之词法和语法分析 点击前往
聚集函数概述
PostgreSQL
支持聚集函数。 一个聚集函数从多个输入行中计算出一个结果。 比如,我们有在一个行集合上计算count
(计数)、sum
(和)、avg
(均值)、max
(最大值)和min
(最小值)的函数。
PostgreSQL
中的聚集函数用状态值
和状态转换函数
定义。也就是,一个聚集操作使用一个状态值,它在每一个后续输入行被处理时被更新。要定义一个新的聚集函数,我们要为状态值选择一种数据类型、一个状态的初始值和一个状态转换函数。状态转换函数接收前一个状态值和该聚集当前行的输入值,并且返回一个新的状态值。万一该聚集的预期结果与需要保存在运行状态之中的数据不同,还能指定一个最终函数
。最终函数接收结束状态值并且返回作为聚集结果的任何东西。原则上,转换函数
和最终函数
只是也可以在聚集环境之外使用的普通函数(实际上,通常出于性能的原因,会创建特殊的只能作为聚集的一部分工作的转换函数)。
因此,除了该聚集的用户所见的参数和结果数据类型之外,还有一种可能不同于参数和结果状态的内部状态值数据类型。
自定义聚集函数有对应的SQL语法Postgresql定义聚集函数 点击前往,本次学习重点在内核源码是如何定义聚集函数,即系统聚集函数的定义及处理流程。
相关系统目录介绍
函数的相关信息都保存在pg_proc系统目录中(pg_proc系统目录介绍 点击前往),可以通过查看该系统表可知PostgreSQL
数据库中的聚集函数,该系统表的字段prokind
代表函数类型,其中f
表示普通函数,p
表示过程,a
表示聚集函数,w
表示窗口函数,如下以avg
函数举例
postgres=# select * from pg_proc where prokind = 'a' and proname = 'avg';
oid | proname | pronamespace | proowner | prolang | procost | prorows | provariadic | prosupport | prokind | prosecdef | proleakproof | proisstrict | proretset | provolatile | proparallel | pronargs | pronargdefaults | prorettype | proargtypes | proallargtypes | proargmodes | proargnames | proargdefaults | protrftypes | prosrc | probin | proconfig | proacl
------+---------+--------------+----------+---------+---------+---------+-------------+------------+---------+-----------+--------------+-------------+-----------+-------------+-------------+----------+-----------------+------------+-------------+----------------+-------------+-------------+----------------+-------------+-----------------+--------+-----------+--------
2100 | avg | 11 | 10 | 12 | 1 | 0 | 0 | - | a | f | f | f | f | i | s | 1 | 0 | 1700 | 20 | | | | | | aggregate_dummy | | |
2101 | avg | 11 | 10 | 12 | 1 | 0 | 0 | - | a | f | f | f | f | i | s | 1 | 0 | 1700 | 23 | | | | | | aggregate_dummy | | |
2102 | avg | 11 | 10 | 12 | 1 | 0 | 0 | - | a | f | f | f | f | i | s | 1 | 0 | 1700 | 21 | | | | | | aggregate_dummy | | |
2103 | avg | 11 | 10 | 12 | 1 | 0 | 0 | - | a | f | f | f | f | i | s | 1 | 0 | 1700 | 1700 | | | | | | aggregate_dummy | | |
2104 | avg | 11 | 10 | 12 | 1 | 0 | 0 | - | a | f | f | f | f | i | s | 1 | 0 | 701 | 700 | | | | | | aggregate_dummy | | |
2105 | avg | 11 | 10 | 12 | 1 | 0 | 0 | - | a | f | f | f | f | i | s | 1 | 0 | 701 | 701 | | | | | | aggregate_dummy | | |
2106 | avg | 11 | 10 | 12 | 1 | 0 | 0 | - | a | f | f | f | f | i | s | 1 | 0 | 1186 | 1186 | | | | | | aggregate_dummy | | |
(7 rows)
postgres=#
如上图所示,聚集函数avg
在pg_proc
中的相关内容如上,一共7行,对应了avg
函数不同的入参类型和返回值类型,其中prosrc
都是aggregate_dummy
,实际上该函数并非avg
函数对应的底层处理函数,而真正的聚集函数相关的处理函数保存在pg_aggregate
系统目录下
名称 | 类型 | 引用 | 描述 |
---|---|---|---|
aggfnoid | regproc | pg_proc.oid | 聚集函数在pg_proc中的OID |
aggkind | char | 聚集类型: n表示“普通”聚集, o表示“有序集”聚集,或者 h表示“假想集”聚集 | |
aggnumdirectargs | int2 | 一个有序集或者假想集聚集的直接(非聚集)参数的数量,一个可变数组算作一个参数。 如果等于pronargs,该聚集必定是可变的并且该可变数组描述聚集参数以 及最终直接参数。对于普通聚集总是为零。 | |
aggtransfn | regproc | pg_proc.oid | 转移函数 |
aggfinalfn | regproc | pg_proc.oid | 最终函数(如果没有就为零) |
aggcombinefn | regproc | pg_proc.oid | 结合函数(如果没有就为零) |
aggserialfn | regproc | pg_proc.oid | 序列化函数(如果没有就为零) |
aggdeserialfn | regproc | pg_proc.oid | 反序列化函数(如果没有就为零) |
aggmtransfn | regproc | pg_proc.oid | 用于移动聚集模式的向前转移函数(如果没有就为零) |
aggminvtransfn | regproc | pg_proc.oid | 用于移动聚集模式的反向转移函数(如果没有就为零) |
aggmfinalfn | regproc | pg_proc.oid | 用于移动聚集模式的最终函数(如果没有就为零) |
aggfinalextra | bool | 为真则向 aggfinalfn传递额外的哑参数 | |
aggmfinalextra | bool | 为真则向 aggmfinalfn传递额外的哑参数 | |
aggfinalmodify | char | aggfinalfn是否修改传递状态值: 如果是只读则为r, 如果不能在aggfinalfn之后应用aggtransfn则为s, 如果它修改该值则为w | |
aggmfinalmodify | char | 和aggfinalmodify类似,但是用于aggmfinalfn | |
aggsortop | oid | pg_operator.oid | 相关联的排序操作符(如果没有则为0) |
aggtranstype | oid | pg_type.oid | 聚集函数的内部转移(状态)数据的数据类型 |
aggtransspace | int4 | 转移状态数据的近似平均尺寸(字节),或者为零表示使用一个默认估算值 | |
aggmtranstype | oid | pg_type.oid | 聚集函数用于移动聚集欧氏的内部转移(状态)数据的数据类型(如果没有则为零) |
aggmtransspace | int4 | 转移状态数据的近似平均尺寸(字节),或者为零表示使用一个默认估算值 | |
agginitval | text | 转移状态的初始值。这是一个文本域,它包含初始值的外部字符串表现形式。如果这个域为空,则转移状态值从空值开始。 | |
aggminitval | text | 用于移动聚集模式的转移状态初值。这是一个文本域,它包含了以其文本字符串形式表达的初值。 如果这个域为空,则转移状态值从空值开始。 |
依旧以avg
函数为例,通过查看系统表pg_aggregate
可以看到该聚集函数对应的相关处理函数,如转换函数,最终函数,结合函数
等
postgres=# select * from pg_aggregate where aggfnoid = 2100;
aggfnoid | aggkind | aggnumdirectargs | aggtransfn | aggfinalfn | aggcombinefn | aggserialfn | aggdeserialfn | aggmtransfn | aggminvtransfn | aggmfinalfn | aggfinalextra | aggmfinalextra | aggfinalmodify | aggmfinalmodify | aggsortop | aggtranstype | aggtransspace | aggmtranstype | aggmtransspace | agginitval | aggminitval
----------------+---------+------------------+----------------+------------------+------------------+--------------------+----------------------+----------------+--------------------+------------------+---------------+----------------+----------------+-----------------+-----------+--------------+---------------+---------------+----------------+------------+-------------
pg_catalog.avg | n | 0 | int8_avg_accum | numeric_poly_avg | int8_avg_combine | int8_avg_serialize | int8_avg_deserialize | int8_avg_accum | int8_avg_accum_inv | numeric_poly_avg | f | f | r | r | 0 | 2281 | 48 | 2281 | 48 | |
(1 row)
postgres=# select * from pg_aggregate where aggfnoid = 2101;
aggfnoid | aggkind | aggnumdirectargs | aggtransfn | aggfinalfn | aggcombinefn | aggserialfn | aggdeserialfn | aggmtransfn | aggminvtransfn | aggmfinalfn | aggfinalextra | aggmfinalextra | aggfinalmodify | aggmfinalmodify | aggsortop | aggtranstype | aggtransspace | aggmtranstype | aggmtransspace | agginitval | aggminitval
----------------+---------+------------------+----------------+------------+------------------+-------------+---------------+----------------+--------------------+-------------+---------------+----------------+----------------+-----------------+-----------+--------------+---------------+---------------+----------------+------------+-------------
pg_catalog.avg | n | 0 | int4_avg_accum | int8_avg | int4_avg_combine | - | - | int4_avg_accum | int4_avg_accum_inv | int8_avg | f | f | r | r
| 0 | 1016 | 0 | 1016 | 0 | {0,0} | {0,0}
(1 row)
postgres=# select * from pg_aggregate where aggfnoid = 2102;
aggfnoid | aggkind | aggnumdirectargs | aggtransfn | aggfinalfn | aggcombinefn | aggserialfn | aggdeserialfn | aggmtransfn | aggminvtransfn | aggmfinalfn | aggfinalextra | aggmfinalextra | aggfinalmodify | aggmfinalmodify | aggsortop | aggtranstype | aggtransspace | aggmtranstype | aggmtransspace | agginitval | aggminitval
----------------+---------+------------------+----------------+------------+------------------+-------------+---------------+----------------+--------------------+-------------+---------------+----------------+----------------+-----------------+-----------+--------------+---------------+---------------+----------------+------------+-------------
pg_catalog.avg | n | 0 | int2_avg_accum | int8_avg | int4_avg_combine | - | - | int2_avg_accum | int2_avg_accum_inv | int8_avg | f | f | r | r
| 0 | 1016 | 0 | 1016 | 0 | {0,0} | {0,0}
(1 row)
postgres=#
一个简单的聚集函数由一个或者多个普通函数组成: 一个状态转换函数aggtransfn
和一个可选的最终计算函数aggfinalfn
。
aggfnoid
为2100
对应在pg_proc
表中的第一条,函数入参为int8
,返回值为numeric
,其对应的状态转换函数为int8_avg_accum
,最终计算函数为numeric_poly_avg
,这两个函数即该聚集函数在内核中的相关处理函数。
PostgreSQL
创建一个数据类型 stype
的临时变量来保持聚集的当前内部状态。对每一个输入行,聚集参数值会被计算并且状态转换函数会被调用,它用当前状态值和新参数值计算一个新的内部状态值。 等所有行都被处理完后,最终函数会被调用一次来计算该聚集的返回值。如果没有最终函数,则最终的状态值会被返回。状态转换函数和最终函数的相关信息也保存在pg_proc
系统表中
#该函数的入参类型为2281 20,其中参数1:2281对应的类型为internal,即上一次转换函数的返回值
# 参数2:20 对应类型为int8,即当前行输入行的数据类型
# 函数返回值:2281对应internal
postgres=# select * from pg_proc where proname = 'int8_avg_accum';
oid | proname | pronamespace | proowner | prolang | procost | prorows | provariadic | prosupport | prokind | prosecdef | proleakproof | proisstrict | proretset | provolatile | proparallel | pronargs | pronargdefaults | prorettype | proargtypes | proallargtypes | proargmodes | proargnames | proargdefaults | protrftypes | prosrc | probin | proconfig | proacl
------+----------------+--------------+----------+---------+---------+---------+-------------+------------+---------+-----------+--------------+-------------+-----------+-------------+-------------+----------+-----------------+------------+-------------+----------------+-------------+-------------+----------------+-------------+----------------+--------+-----------+--------
2746 | int8_avg_accum | 11 | 10 | 12 | 1 | 0 | 0 | - | f | f | f | f | f | i | s | 2 | 0 | 2281 | 2281 20 | | | | | | int8_avg_accum | | |
(1 row)
#该函数的入参类型为2281 对应类型为internal,即经过上述转换函数转换之后的返回值
# 函数返回值:1700 对应类型为numeric,即根据转换后的状态值进行最终的计算
postgres=# select * from pg_proc where proname = 'numeric_poly_avg';
oid | proname | pronamespace | proowner | prolang | procost | prorows | provariadic | prosupport | prokind | prosecdef | proleakproof | proisstrict | proretset | provolatile | proparallel | pronargs | pronargdefaults | prorettype | proargtypes | proallargtypes | proargmodes | proargnames | proargdefaults | protrftypes | prosrc | probin | proconfig | proacl
------+------------------+--------------+----------+---------+---------+---------+-------------+------------+---------+-----------+--------------+-------------+-----------+-------------+-------------+----------+-----------------+------------+-------------+----------------+-------------+-------------+----------------+-------------+------------------+--------+-----------+--------
3389 | numeric_poly_avg | 11 | 10 | 12 | 1 | 0 | 0 | - | f | f | f | f | f | i | s | 1 | 0 |
1700 | 2281 | | | | | | numeric_poly_avg | | |
(1 row)
postgres=#
对于avg函数,计算平均值,对于输入值,我们最终需要在临时状态中保存当前分组的和以及当前分组对于的总处理数个数,内核中在转换时定义了一个新的数据类型的临时变量来保存聚集的当前内部状态值,该类型对应结构如下:
typedef struct Int128AggState
{
bool calcSumX2; /* 如果为true,则计算平方和 */
int64 N; /* 已处理数的计数 */
int128 sumX; /* 已处理数之和 */
int128 sumX2; /* 已处理数的平方和 */
} Int128AggState;
//src/backend/utils/adt/numeric.c
/*
* Transition function for int8 input when we don't need sumX2.
*/
Datum
int8_avg_accum(PG_FUNCTION_ARGS)
{
PolyNumAggState *state; //临时变量,用于保存转换状态值的新类型
/* arg[0]对应当前状态值,如果为NULL,说明第一次调用该状态转换函数 */
state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
/* 在第一次调用时创建状态数据 */
if (state == NULL)
state = makePolyNumAggState(fcinfo, false);
/* 处理arg[1],该参数为当前输入行对应的参数值 */
if (!PG_ARGISNULL(1))
{
#ifdef HAVE_INT128
do_int128_accum(state, (int128) PG_GETARG_INT64(1));
#else
Numeric newval;
/* 如果没有定义HAVE_INT128宏,则将该参数值转换为Numeric类型,再进行求和计算 */
newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
PG_GETARG_DATUM(1)));
do_numeric_accum(state, newval); //求和
#endif
}
PG_RETURN_POINTER(state);
}
/* 用于计算平均值的最终函数 */
Datum
numeric_poly_avg(PG_FUNCTION_ARGS)
{
#ifdef HAVE_INT128
PolyNumAggState *state;
NumericVar result;
Datum countd,
sumd;
state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
/* If there were no non-null inputs, return NULL */
if (state == NULL || state->N == 0)
PG_RETURN_NULL();
init_var(&result);
int128_to_numericvar(state->sumX, &result);
/* 将状态值中保存的处理数总个数N转换为NUMERIC类型 */
countd = DirectFunctionCall1(int8_numeric,
Int64GetDatumFast(state->N));
/* 状态值中保存的处理数总和 */
sumd = NumericGetDatum(make_result(&result));
free_var(&result);
/* 计算平均值 */
PG_RETURN_DATUM(DirectFunctionCall2(numeric_div, sumd, countd));
#else
return numeric_avg(fcinfo);
#endif
}
源码相关函数简介
通过上面的介绍,我们已经了解关于聚集函数相关的底层处理函数如何去查看,接下来,我们根据内核源码,来分析聚集函数的执行流程,执行阶段入口函数ExecWindowAgg
。
相关数据结构
typedef struct WindowAggState
{
ScanState ss; /* its first field is NodeTag */
/* these fields are filled in by ExecInitExpr: */
List *funcs; /* targetlist中的所有WindowFunc节点 */
int numfuncs; /* 窗口函数总数 */
int numaggs; /* 普通聚合数 */
WindowStatePerFunc perfunc; /* 该结构存储窗口函数的OID及执行结果等信息 */
WindowStatePerAgg peragg; /* 该结构存储聚合函数内部处理的函数oid(转换函数、最终函数)及执行结果等信息 */
ExprState *partEqfunction; /* 分区列的相等函数 */
ExprState *ordEqfunction; /* 用于排序列的相等函数 */
Tuplestorestate *buffer; /* 存储当前分区的行 */
int current_ptr; /* read pointer # for current row */
int framehead_ptr; /* read pointer # for frame head, if used */
int frametail_ptr; /* read pointer # for frame tail, if used */
int grouptail_ptr; /* read pointer # for group tail, if used */
int64 spooled_rows; /* 缓冲区中的行总数 */
int64 currentpos; /* 分区中当前行的位置 */
int64 frameheadpos; /* 当前帧头位置 */
int64 frametailpos; /* 当前帧尾位置(frame end+1) */
/* use struct pointer to avoid including windowapi.h here */
struct WindowObjectData *agg_winobj; /* 用于聚合获取的winobj */
int64 aggregatedbase; /* 当前聚合的起始行 */
int64 aggregatedupto; /* 在此之前被聚合的行 */
int frameOptions; /* frame_clause options, see WindowDef */
ExprState *startOffset; /* 起始边界偏移量的表达式 */
ExprState *endOffset; /* 结束边界偏移量的表达式 */
Datum startOffsetValue; /* startOffset 评估的结果 */
Datum endOffsetValue; /* endOffset 评估的结果 */
/* 这些字段与 RANGE offset PRECEDING/FOLLOWING 一起使用: */
FmgrInfo startInRangeFunc; /* in_range function for startOffset */
FmgrInfo endInRangeFunc; /* in_range function for endOffset */
Oid inRangeColl; /* collation for in_range tests */
bool inRangeAsc; /* use ASC sort order for in_range tests? */
bool inRangeNullsFirst; /* nulls sort first for in_range tests? */
/* these fields are used in GROUPS mode: */
int64 currentgroup; /* peer group # of current row in partition */
int64 frameheadgroup; /* peer group # of frame head row */
int64 frametailgroup; /* peer group # of frame tail row */
int64 groupheadpos; /* current row's peer group head position */
int64 grouptailpos; /* " " " " tail position (group end+1) */
MemoryContext partcontext; /* 分区数据有效期的上下文 */
MemoryContext aggcontext; /* 聚合工作数据的共享上下文 */
MemoryContext curaggcontext; /* 当前聚合工作数据的上下文 */
ExprContext *tmpcontext; /* 用于短期计算的上下文 */
bool all_first; /* 如果扫描正在启动,则为true */
bool all_done; /* 如果扫描完成,则为true */
bool partition_spooled; /* 如果当前分区中的所有元组都已存储到元组存储中,则为true */
bool more_partitions; /* 如果在此分区之后有更多分区,则为true */
bool framehead_valid; /* 如果当前行的 frameheadpos 是最新的,则为真 */
bool frametail_valid; /* 如果当前行的 frametailpos 是最新的,则为真 */
bool grouptail_valid; /* 如果当前行的 grouptailpos 是最新的,则为真*/
TupleTableSlot *first_part_slot; /* 当前分区或下一分区的第一个元组*/
TupleTableSlot *framehead_slot; /* 当前帧的第一个元组 */
TupleTableSlot *frametail_slot; /* 当前帧后的第一个元组 */
/* 从tuplestore取回的元组的临时槽 */
TupleTableSlot *agg_row_slot;
TupleTableSlot *temp_slot_1;
TupleTableSlot *temp_slot_2;
} WindowAggState;
/*
* 对于普通聚合窗口函数,我们也有其中之一。
*/
typedef struct WindowStatePerAggData
{
/* 转换函数的Oid */
Oid transfn_oid;
Oid invtransfn_oid; /* may be InvalidOid */
Oid finalfn_oid; /* may be InvalidOid */
/*
* 转换函数的 fmgr 查找数据 --- 仅当相应的 oid 不是 InvalidOid 时才有效。
* 特别注意 fn_strict 标志保留在这里。
*/
FmgrInfo transfn;
FmgrInfo invtransfn;
FmgrInfo finalfn;
int numFinalArgs; /* 传递给 finalfn 的参数数量 */
/*
* 来自 pg_aggregate 条目的初始值
*/
Datum initValue;
bool initValueIsNull;
/*
* 当前帧边界的缓存值
*/
Datum resultValue;
bool resultValueIsNull;
/*
* 我们需要agg的输入、结果和转换数据类型的 len 和 byval 信息,
* 以便了解如何复制/删除值。
*/
int16 inputtypeLen,
resulttypeLen,
transtypeLen;
bool inputtypeByVal,
resulttypeByVal,
transtypeByVal;
int wfuncno; /* index of associated PerFuncData */
/* 上下文保持转换值和可能的其他辅助数据 */
MemoryContext aggcontext; /* 可能是私有的,或者 winstate->aggcontext */
/*当前转换值 */
Datum transValue; /* current transition value */
bool transValueIsNull;
int64 transValueCount; /* 当前聚合的行数 */
/* Data local to eval_windowaggregates() */
bool restart; /* 需要在这个循环中重启这个agg吗? */
} WindowStatePerAggData;
聚集函数的计算
相关调用入口
ExecWindowAgg
---->eval_windowaggregates
下面主要分析eval_windowaggregates
函数的相关处理流程
/*
* 评估用作窗口函数的普通聚合
* 这在两个方面不同于 nodeAgg.c。 首先,如果窗口的框架
* 开始位置移动,我们使用逆转换函数(如果存在)从转换值中删除行。
* 其次,我们希望能够在将更多数据聚合到相同的转换值后重复调用聚合最终函数。
* 这不是 nodeAgg.c 要求的行为
*/
static void
eval_windowaggregates(WindowAggState *winstate)
{
WindowStatePerAgg peraggstate;
int wfuncno,
numaggs,
numaggs_restart,
i;
int64 aggregatedupto_nonrestarted;
MemoryContext oldContext;
ExprContext *econtext;
WindowObject agg_winobj;
TupleTableSlot *agg_row_slot;
TupleTableSlot *temp_slot;
numaggs = winstate->numaggs;
if (numaggs == 0)
return; /* nothing to do */
/* 最终输出执行在 ps_ExprContext */
econtext = winstate->ss.ps.ps_ExprContext;
agg_winobj = winstate->agg_winobj;
agg_row_slot = winstate->agg_row_slot;
temp_slot = winstate->temp_slot_1;
/*
* 如果窗口的帧开始子句是 UNBOUNDED_PRECEDING 并且没有指定排除子句,那么窗口帧由一个
* 从分区的开始向前延伸的一组连续的行,行只进入帧,从不退出它,因为当前行向前推进。
* 这使得可以使用增量评估聚合的策略:我们运行转换函数将每一行添加到帧中,
* 并在我们运行最终函数时运行需要当前的聚合值。
* 这比为每个当前行重新运行整个聚合计算的简单方法要有效得多。
* 它确实假设最终函数不会损坏正在运行的转换值,但我们在 nodeAgg.c 中也有相同的假设
* (当它重新扫描现有哈希表时)。
*
* 如果帧开始确实有时会移动,只要连续的行共享相同的帧头,我们仍然可以像上面那样进行优化,
* 但是如果帧头移动超过前一个头,我们会尝试使用聚合的逆转换函数删除这些行。
* 此函数将聚合的当前状态恢复到删除的行最初从未被聚合时的状态。 反向转换函数可以选择返回 NULL,
* 表示该函数无法从聚合中删除元组。 如果发生这种情况,或者如果聚合根本没有逆转换函数,
* 我们必须对新帧边界内的所有元组重新执行聚合。
*
* 如果有任何排除条款,那么我们可能必须聚合一组不连续的行,
* 因此我们对每一行进行 punt 和重新计算。
*(对于某些帧结束选择,可能帧始终是连续的,但这是稍后研究的优化。)
*
* 在许多常见情况下,多行共享相同的帧,因此具有相同的聚合值。
*(特别是,如果 RANGE 窗口中没有 ORDER BY,那么所有行都是对等的,
* 因此它们都有等于整个分区的窗口框架。)我们通过在到达第一行时计算一次聚合值来优化这种情况
* 一个对等组,然后返回所有后续行的保存值。
*
* 'aggregatedupto' 跟踪尚未累积到聚合转换值中的第一行。
* 每当我们开始一个新的对等组时,我们都会向前累积到对等组的末尾。
*/
/*
* 首先,更新帧头位置。
* 帧头不应该向后移动,如果它向后移动,下面的代码将无法处理,
* 所以为了安全起见,如果它向后移动,我们会报错。
update_frameheadpos(winstate);
if (winstate->frameheadpos < winstate->aggregatedbase)
elog(ERROR, "window frame head moved backward");
/*
* 如果帧与前一行相比没有变化,我们可以重新使用之前保存在此函数底部的结果值。
* 由于我们还不知道当前帧的结束,因此无法完全检查。
* 但是如果frame end mode是UNBOUNDED FOLLOWING或者CURRENT ROW,
* 且没有指定排除子句,并且当前行在前一行的frame之内,那么两个frame的结束必须重合。
* 请注意,在第一行 aggregatedbase == aggregatedupto,这意味着此测试必须失败,
* 因此我们不需要在此处明确检查“没有前一行”的情况
*/
if (winstate->aggregatedbase == winstate->frameheadpos &&
(winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING |
FRAMEOPTION_END_CURRENT_ROW)) &&
!(winstate->frameOptions & FRAMEOPTION_EXCLUSION) &&
winstate->aggregatedbase <= winstate->currentpos &&
winstate->aggregatedupto > winstate->currentpos)
{
for (i = 0; i < numaggs; i++)
{
peraggstate = &winstate->peragg[i];
wfuncno = peraggstate->wfuncno;
econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue;
econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull;
}
return;
}
/*----------
* 初始化重启标志.
*
* 我们重新启动聚合:
* 如果我们正在处理分区中的第一行,
* 或者如果帧头部移动并且我们不能使用反向转换函数,
* 或者我们有一个 EXCLUSION 子句,
* 或者如果新帧不与旧帧重叠
*
* 请注意,在最后一种情况下我们并不严格需要重新启动,但如果我们无论如何都要从聚合中删除所有行,
* 那么重新启动肯定会更快。
*----------
*/
numaggs_restart = 0;
for (i = 0; i < numaggs; i++)
{
peraggstate = &winstate->peragg[i];
if (winstate->currentpos == 0 ||
(winstate->aggregatedbase != winstate->frameheadpos &&
!OidIsValid(peraggstate->invtransfn_oid)) ||
(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
winstate->aggregatedupto <= winstate->frameheadpos)
{
peraggstate->restart = true;
numaggs_restart++;
}
else
peraggstate->restart = false;
}
/*
* 如果我们有任何可能移动的聚合,请尝试通过从聚合中删除从帧顶部掉落的输入行来推进聚合基
* 以匹配帧的头部。 这可能会失败,即 advance_windowaggregate_base() 可能返回 false,
* 在这种情况下,我们将在下面重新启动该聚合。
*/
while (numaggs_restart < numaggs &&
winstate->aggregatedbase < winstate->frameheadpos)
{
/*
* 获取被删除的下一个元组。 这应该永远不会失败,因为我们以前应该来过这里。
*/
if (!window_gettupleslot(agg_winobj, winstate->aggregatedbase,
temp_slot))
elog(ERROR, "could not re-fetch previously fetched frame row");
/* 设置元组上下文以评估聚合参数 */
winstate->tmpcontext->ecxt_outertuple = temp_slot;
/*
* 对窗口中的每个聚合函数执行反向转换,除非它已被标记为需要重新启动。
*/
for (i = 0; i < numaggs; i++)
{
bool ok;
peraggstate = &winstate->peragg[i];
if (peraggstate->restart)
continue;
wfuncno = peraggstate->wfuncno;
ok = advance_windowaggregate_base(winstate,
&winstate->perfunc[wfuncno],
peraggstate);
if (!ok)
{
/* 反向转换功能失败,必须重新启动 */
peraggstate->restart = true;
numaggs_restart++;
}
}
/* 在每个元组之后重置每个输入元组的上下文 */
ResetExprContext(winstate->tmpcontext);
/* 并推进聚合行状态 */
winstate->aggregatedbase++;
ExecClearTuple(temp_slot);
}
/*
* 如果我们成功地推进了所有聚合的基行,aggregatedbase 现在等于 frameheadpos;
* 但是如果我们失败了,我们必须强行更新 aggregatedbase。
*/
winstate->aggregatedbase = winstate->frameheadpos;
/*
* 如果我们为聚合创建了一个标记指针,将其向上推到帧头,这样 tuplestore 就可以丢弃不需要的行。
*/
if (agg_winobj->markptr >= 0)
WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
/*
* 现在重新启动需要它的聚合
*
* 我们假设如果任何聚合重新启动,使用共享上下文的聚合总是重新启动,
* 如果是这种情况,我们可能会因此清理共享的aggcontext。
* 如果私有 aggcontexts 拥有的聚合重新启动,则由 initialize_windowaggregate() 重置。
* 如果我们不重新启动聚合,我们需要释放之前为它保存的任何结果,否则我们会泄漏内存。
*/
if (numaggs_restart > 0)
MemoryContextResetAndDeleteChildren(winstate->aggcontext);
for (i = 0; i < numaggs; i++)
{
peraggstate = &winstate->peragg[i];
/*
* 如果有任何聚合
* 使用共享 aggcontext的聚合必须重新启动
*/
Assert(peraggstate->aggcontext != winstate->aggcontext ||
numaggs_restart == 0 ||
peraggstate->restart);
if (peraggstate->restart)
{
wfuncno = peraggstate->wfuncno;
/*
* 如下函数主要用于初始化WindowStatePerAggData结构体
* 主要包含transValue、transValueIsNull、
* transValueCount、resultValue、resultValueIsNull等值
*/
initialize_windowaggregate(winstate,
&winstate->perfunc[wfuncno],
peraggstate);
}
else if (!peraggstate->resultValueIsNull)
{
if (!peraggstate->resulttypeByVal)
pfree(DatumGetPointer(peraggstate->resultValue));
peraggstate->resultValue = (Datum) 0;
peraggstate->resultValueIsNull = true;
}
}
/*
* 非重新启动的聚合现在包含 aggregatedbase(即 frameheadpos)和 aggregatedupto 之间的行,
* 而重新启动的聚合不包含任何行。 如果有任何重新启动的聚合,我们必须因此在 frameheadpos 处
* 重新开始聚合,否则我们可以简单地在 aggregatedupto 处继续。 我们必须记住 aggregatedupto
* 的旧值,以了解跳过推进非重启聚合的时间。 如果我们修改 aggregatedupto,我们还必须根据下面
* 的循环不变量清除 agg_row_slot。
*/
aggregatedupto_nonrestarted = winstate->aggregatedupto;
if (numaggs_restart > 0 &&
winstate->aggregatedupto != winstate->frameheadpos)
{
winstate->aggregatedupto = winstate->frameheadpos;
ExecClearTuple(agg_row_slot);
}
/*
* 前进,直到我们到达不在帧中(或分区末尾)的行。
*
* 请注意循环不变式:agg_row_slot 为空或将行保存在 aggregatedupto 位置。
* 我们在处理一行后推进 aggregatedupto。
*/
for (;;)
{
int ret;
/* 如果我们还没有获取下一行 */
if (TupIsNull(agg_row_slot))
{
if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
agg_row_slot))
break; /* must be end of partition */
}
/*
* 如果帧中没有更多的行,则退出循环。 如果当前行不在帧中但帧中可能有更多行,则跳过聚合。
*/
ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
if (ret < 0)
break;
if (ret == 0)
goto next_tuple;
/* 设置元组上下文以评估聚合参数 */
winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
/* 将行累积到聚合中 */
for (i = 0; i < numaggs; i++)
{
peraggstate = &winstate->peragg[i];
/* 非重新启动的 aggs 跳过直到 aggregatedupto_nonrestarted */
if (!peraggstate->restart &&
winstate->aggregatedupto < aggregatedupto_nonrestarted)
continue;
wfuncno = peraggstate->wfuncno;
/* 调用转换函数将当前值进行转换 */
advance_windowaggregate(winstate,
&winstate->perfunc[wfuncno],
peraggstate);
}
next_tuple:
/* 在每个元组之后重置每个输入元组的上下文 */
ResetExprContext(winstate->tmpcontext);
/* 并推进聚合行状态 */
winstate->aggregatedupto++;
ExecClearTuple(agg_row_slot);
}
/* The frame's end is not supposed to move backwards, ever */
Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
/*
* 完成聚合并填充result/isnull 字段。
*/
for (i = 0; i < numaggs; i++)
{
Datum *result;
bool *isnull;
peraggstate = &winstate->peragg[i];
wfuncno = peraggstate->wfuncno;
result = &econtext->ecxt_aggvalues[wfuncno];
isnull = &econtext->ecxt_aggnulls[wfuncno];
/* 调用聚合最终函数计算 */
finalize_windowaggregate(winstate,
&winstate->perfunc[wfuncno],
peraggstate,
result, isnull);
/*
* 保存结果以防下一行共享同一帧.
*
* XXX在一些frame mode中,eg ROWS/END_CURRENT_ROW,我们可以提前知道
* 下一行不可能共享相同的帧。 是否值得检测并跳过这段代码?
*/
if (!peraggstate->resulttypeByVal && !*isnull)
{
oldContext = MemoryContextSwitchTo(peraggstate->aggcontext);
peraggstate->resultValue =
datumCopy(*result,
peraggstate->resulttypeByVal,
peraggstate->resulttypeLen);
MemoryContextSwitchTo(oldContext);
}
else
{
peraggstate->resultValue = *result;
}
peraggstate->resultValueIsNull = *isnull;
}
}
/*
* advance_windowaggregate
* parallel to advance_aggregates in nodeAgg.c
*/
/* 调用相应的转换函数处理当前输入值 */
static void
advance_windowaggregate(WindowAggState *winstate,
WindowStatePerFunc perfuncstate,
WindowStatePerAgg peraggstate)
{
LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
WindowFuncExprState *wfuncstate = perfuncstate->wfuncstate;
int numArguments = perfuncstate->numArguments;
Datum newVal;
ListCell *arg;
int i;
MemoryContext oldContext;
ExprContext *econtext = winstate->tmpcontext;
ExprState *filter = wfuncstate->aggfilter;
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
/* 跳过任何被过滤掉的东西 */
if (filter)
{
bool isnull;
Datum res = ExecEvalExpr(filter, econtext, &isnull);
if (isnull || !DatumGetBool(res))
{
MemoryContextSwitchTo(oldContext);
return;
}
}
/* 我们从 1 开始,因为第 0 个 arg 将是转换值
* 第一个参数为当前值入参,对该参数先进行表达式求值计算
*
*/
i = 1;
foreach(arg, wfuncstate->args)
{
ExprState *argstate = (ExprState *) lfirst(arg);
fcinfo->args[i].value = ExecEvalExpr(argstate, econtext,
&fcinfo->args[i].isnull);
i++;
}
if (peraggstate->transfn.fn_strict)
{
/*
* 对于严格的 transfn,当有 NULL 输入时什么也不会发生; 我们只保留之前的transValue。
* 注意 transValueCount 也没有改变。
*/
for (i = 1; i <= numArguments; i++)
{
if (fcinfo->args[i].isnull)
{
MemoryContextSwitchTo(oldContext);
return;
}
}
/*
* 对于初始值为 NULL 的严格转换函数,我们使用第一个非 NULL 输入作为初始状态。
* (我们已经检查过 agg 的输入类型与其转换类型是二进制兼容的,所以在这里直接复制是可以的。)
*
* 如果它是 pass-by-ref,我们必须将数据复制到 aggcontext 中。
* 我们不需要 pfree 旧的 transValue,因为它是 NULL。
*/
if (peraggstate->transValueCount == 0 && peraggstate->transValueIsNull)
{
MemoryContextSwitchTo(peraggstate->aggcontext);
peraggstate->transValue = datumCopy(fcinfo->args[1].value,
peraggstate->transtypeByVal,
peraggstate->transtypeLen);
peraggstate->transValueIsNull = false;
peraggstate->transValueCount = 1;
MemoryContextSwitchTo(oldContext);
return;
}
if (peraggstate->transValueIsNull)
{
/*
* 不要使用 NULL 输入调用严格函数。 请注意,尽管进行了上述测试,
* 但如果 transfn 是严格的并且在先前的循环中返回 NULL,
* 则仍有可能到达此处。 如果发生这种情况,我们将一直传播 NULL 到最后。
* 不过,只有在没有逆转换函数的情况下才会发生这种情况,因为我们不允许在
* 有逆转换函数时转换回 NULL
*/
MemoryContextSwitchTo(oldContext);
Assert(!OidIsValid(peraggstate->invtransfn_oid));
return;
}
}
/*
* 确定调用转换函数。 调用时设置 winstate->curaggcontext,
* 以供 AggCheckCallContext 使用。
*/
InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
numArguments + 1,
perfuncstate->winCollation,
(void *) winstate, NULL);
fcinfo->args[0].value = peraggstate->transValue;
fcinfo->args[0].isnull = peraggstate->transValueIsNull;
winstate->curaggcontext = peraggstate->aggcontext;
newVal = FunctionCallInvoke(fcinfo);
winstate->curaggcontext = NULL;
/*
* 移动聚合转换函数不得返回 null,请参阅 advance_windogaggregate_base()。
*/
if (fcinfo->isnull && OidIsValid(peraggstate->invtransfn_oid))
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("moving-aggregate transition function must not return null")));
/*
* 我们必须跟踪 transValue 中包含的行数,因为要删除最后一个输入
* advance_windowaggregate_base() 不能调用反向转换函数,
* 而只是将 transValue 重置回其初始值。
*/
peraggstate->transValueCount++;
/*
* 如果是 pass-by-ref 数据类型,必须将新值复制到 aggcontext 中并释放之前的 transValue。
* 但是如果 transfn 返回一个指向它的第一个输入的指针,我们就不需要做任何事情。
* 此外,如果 transfn 返回一个指向 R/W 扩展对象的指针,该对象已经是 aggcontext 的子对象,
* 假设我们可以采用该值而不复制它。
*/
if (!peraggstate->transtypeByVal &&
DatumGetPointer(newVal) != DatumGetPointer(peraggstate->transValue))
{
if (!fcinfo->isnull)
{
MemoryContextSwitchTo(peraggstate->aggcontext);
if (DatumIsReadWriteExpandedObject(newVal,
false,
peraggstate->transtypeLen) &&
MemoryContextGetParent(DatumGetEOHP(newVal)->eoh_context) == CurrentMemoryContext)
/* do nothing */ ;
else
newVal = datumCopy(newVal,
peraggstate->transtypeByVal,
peraggstate->transtypeLen);
}
if (!peraggstate->transValueIsNull)
{
if (DatumIsReadWriteExpandedObject(peraggstate->transValue,
false,
peraggstate->transtypeLen))
DeleteExpandedObject(peraggstate->transValue);
else
pfree(DatumGetPointer(peraggstate->transValue));
}
}
MemoryContextSwitchTo(oldContext);
peraggstate->transValue = newVal;
peraggstate->transValueIsNull = fcinfo->isnull;
}