PostgreSQL内核学习:表达式
声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 postgresql-18 beta2 的开源代码和《PostgresSQL数据库内核分析》一书
ExecInitExpr
ExecInitExpr
是 PostgreSQL
查询执行引擎中的一个核心函数,用于初始化一个表达式树(Expr
),将其转换为可执行的状态(ExprState
)。表达式树通常表示 SQL
查询中的条件、计算或聚合操作(如 WHERE
子句、计算列等)。
我将用一个简单的 SQL
查询示例,结合 ExecInitExpr
函数的每一步,通俗解释其作用。假设我们有一个 SQL
查询:
SELECT name, age + 10 AS new_age FROM users WHERE age > 30;
这个查询中的表达式是 age + 10
和 age > 30
。ExecInitExpr
的任务是把这些表达式准备成可执行的状态。下面是每一步的通俗解释,结合这个 SQL
示例:
/*
* ExecInitExpr: 准备一个表达式树以供执行
* 类似于把 SQL 表达式(如 age + 10 或 age > 30)翻译成计算机能执行的指令。
*/
ExprState *
ExecInitExpr(Expr *node, PlanState *parent)
{
ExprState *state; // 声明一个“执行计划书”,用来记录怎么执行表达式
ExprEvalStep scratch = {0}; // 准备一个临时“指令卡”,用来写执行步骤
/* 特殊情况:如果没有表达式(比如 SQL 没写 WHERE),直接返回空 */
if (node == NULL)
return NULL;
// SQL 例子:如果没有 WHERE 子句,直接返回空,不需要处理表达式。
/* 初始化“执行计划书”,分配空间并记录表达式和上下文 */
state = makeNode(ExprState); // 创建一个空的“执行计划书”
state->expr = node; // 记录表达式,比如“age + 10”或“age > 30”
state->parent = parent; // 记录这是哪个查询计划的一部分
state->ext_params = NULL; // 暂时没有外部参数
// SQL 例子:为“age + 10”创建一个计划书,标记它属于 SELECT 查询。
/* 根据需要添加初始化步骤,比如准备变量或函数 */
ExecCreateExprSetupSteps(state, (Node *) node); // 准备执行环境,比如确保“age”列可以读取
// SQL 例子:确保“age”列的数据可以从 users 表中取出来。
/* 编译表达式,生成具体的执行步骤 */
ExecInitExprRec(node, state, &state->resvalue, &state->resnull); // 把“age + 10”翻译成“取 age,加 10,存结果”
// SQL 例子:为“age + 10”生成步骤:1. 取 age 值,2. 加 10,3. 保存结果到 new_age。
/* 添加一个“完成”指令,表示表达式处理完了 */
scratch.opcode = EEOP_DONE; // 写一张“结束”指令卡
ExprEvalPushStep(state, &scratch); // 把“结束”指令加到计划书
// SQL 例子:告诉计算机“age + 10”算完了,可以返回结果。
/* 最后检查,确保计划书可以执行 */
ExecReadyExpr(state); // 检查计划书,确保没问题,可以开始运行
// SQL 例子:确认“age + 10”和“age > 30”的执行步骤都准备好了。
/* 返回准备好的执行计划书 */
return state;
// SQL 例子:返回一个包含“age + 10”和“age > 30”执行步骤的计划书,供查询使用。
}
主要流程(以 SQL 为例)
- 检查空表达式:如果
SQL
没有WHERE
或计算列(比如SELECT * FROM users
),直接返回空,不需要处理。 - 创建计划书:为表达式(如
age + 10
或age > 30
)创建一个“执行计划书”,记录表达式和查询的上下文。 - 准备环境:确保表达式用到的列(比如
age
)可以从表中读取,可能还包括准备函数或聚合操作。 - 翻译表达式:把表达式翻译成计算机能懂的步骤,比如“取
age
,加10
,存结果”。 - 添加结束标志:在步骤最后加一个“完成”标志,告诉计算机表达式处理完了。
- 检查并返回:确认计划书没问题,返回给查询执行器,准备运行。
ExecCreateExprSetupSteps
该函数是表达式初始化的“总指挥”,负责为 SQL
表达式(如 age + 10
或 age > 30
)生成执行前的准备步骤。它先通过 expr_setup_walker
检查表达式需要哪些数据(比如字段 age
或子查询),然后调用 ExecPushExprSetupSteps
把这些需求翻译成具体的初始化指令。就像在执行 SQL
前,先列一个清单,写好“要从 users
表取 age
字段”的准备工作。
/*
* ExecCreateExprSetupSteps: 为表达式添加执行前的初始化步骤
* 类似于在执行 SQL 表达式(如 age + 10)前,检查需要准备哪些数据或环境。
*/
static void
ExecCreateExprSetupSteps(ExprState *state, Node *node)
{
ExprSetupInfo info = {0, 0, 0, NIL}; // 创建一个“准备清单”,记录需要哪些初始化工作
// SQL 例子:为“age + 10”创建一个清单,记录需要从 users 表取 age。
/* 扫描表达式,找出需要的初始化工作 */
expr_setup_walker(node, &info); // 检查表达式树,标记需要的数据(如 age 字段)
// SQL 例子:扫描“age + 10”,发现需要从 users 表取 age 字段。
/* 根据清单生成初始化步骤 */
ExecPushExprSetupSteps(state, &info); // 根据清单生成具体的准备步骤
// SQL 例子:为读取 age 字段生成指令,确保执行时能找到 age 的值。
}
expr_setup_walker
该函数像一个“侦察员”,递归遍历 SQL
表达式的树结构,找出所有需要的数据或特殊操作。它会记录表达式中用到的字段(比如 age
)来自哪里(普通表、内连接表或外连接表),以及是否有特殊的子查询(MULTIEXPR
类型)。它会忽略聚合函数(如 SUM
)或窗口函数的参数,因为这些不在当前上下文执行。最终,它把这些信息存到“准备清单”中,供后续生成指令用。
/*
* expr_setup_walker: 检查表达式树,记录需要的初始化工作
* 像是在 SQL 表达式中找需要用到的字段或子查询,记下来以便准备。
*/
static bool
expr_setup_walker(Node *node, ExprSetupInfo *info)
{
if (node == NULL) // 如果没有表达式节点,直接退出
return false;
// SQL 例子:如果没有 WHERE 或计算列,直接返回。
if (IsA(node, Var)) // 如果节点是一个字段(变量)
{
Var *variable = (Var *) node; // 获取字段信息
AttrNumber attnum = variable->varattno; // 获取字段编号(如 age 的列号)
switch (variable->varno) // 根据字段来源分类
{
case INNER_VAR: // 字段来自内表(比如内连接的表)
info->last_inner = Max(info->last_inner, attnum); // 记录内表需要的最大字段编号
break;
// SQL 例子:如果 age 来自内连接的表,记录需要取 age。
case OUTER_VAR: // 字段来自外表
info->last_outer = Max(info->last_outer, attnum); // 记录外表需要的最大字段编号
break;
// SQL 例子:如果 age 来自外连接的表,记录需要取 age。
default: // 字段来自普通表扫描
info->last_scan = Max(info->last_scan, attnum); // 记录扫描表需要的最大字段编号
break;
// SQL 例子:age 来自 users 表,记录需要从 users 表取 age。
}
return false; // 字段处理完,不需要继续深入
}
/* 收集特殊的子查询(MULTIEXPR 类型) */
if (IsA(node, SubPlan)) // 如果节点是一个子查询
{
SubPlan *subplan = (SubPlan *) node; // 获取子查询信息
if (subplan->subLinkType == MULTIEXPR_SUBLINK) // 如果是 MULTIEXPR 子查询
info->multiexpr_subplans = lappend(info->multiexpr_subplans, subplan); // 记录到清单
// SQL 例子:如果有子查询(如 SELECT ... WHERE id IN (SELECT ...)),记录需要准备子查询。
}
/* 忽略聚合函数、窗口函数、分组函数的参数,因为它们不在当前上下文执行 */
if (IsA(node, Aggref)) // 聚合函数(如 SUM)
return false; // 不处理其参数
// SQL 例子:如果有 SUM(age),忽略 SUM 内部的 age。
if (IsA(node, WindowFunc)) // 窗口函数(如 ROW_NUMBER)
return false; // 不处理其参数
if (IsA(node, GroupingFunc)) // 分组函数
return false; // 不处理其参数
/* 递归检查表达式的子节点 */
return expression_tree_walker(node, expr_setup_walker, info);
// SQL 例子:继续检查“age + 10”中的 age 和 10,确保所有部分都记录。
}
ExecPushExprSetupSteps
这个函数是“执行计划的装配工”,根据 expr_setup_walker
提供的清单,生成具体的初始化指令。它为表达式中用到的字段(来自内表、外表或普通表)创建“读取数据”的指令,比如“从 users
表取 age
”。如果有特殊的子查询(MULTIEXPR
类型),它还会生成执行子查询的指令。这些指令会被添加到 ExprState
的计划书中,确保 SQL
表达式执行时能顺利获取数据。
/*
* ExecPushExprSetupSteps: 根据清单生成初始化步骤
* 像是在 SQL 执行前,生成具体的指令来准备字段或子查询。
*/
static void
ExecPushExprSetupSteps(ExprState *state, ExprSetupInfo *info)
{
ExprEvalStep scratch = {0}; // 创建一个临时“指令卡”
ListCell *lc; // 用于遍历子查询列表
scratch.resvalue = NULL; // 初始化结果值为空
scratch.resnull = NULL; // 初始化结果是否为空的标志
/* 为内表字段生成读取指令 */
if (info->last_inner > 0) // 如果表达式用到内表字段
{
scratch.opcode = EEOP_INNER_FETCHSOME; // 设置指令为“从内表取字段”
scratch.d.fetch.last_var = info->last_inner; // 设置需要的最大字段编号
scratch.d.fetch.fixed = false; // 非固定格式
scratch.d.fetch.kind = NULL; // 未知类型
scratch.d.fetch.known_desc = NULL; // 未知描述
if (ExecComputeSlotInfo(state, &scratch)) // 计算字段信息
ExprEvalPushStep(state, &scratch); // 添加指令到计划书
// SQL 例子:如果 age 来自内连接的表,生成指令从内表取 age。
}
/* 为外表字段生成读取指令 */
if (info->last_outer > 0) // 如果表达式用到外表字段
{
scratch.opcode = EEOP_OUTER_FETCHSOME; // 设置指令为“从外表取字段”
scratch.d.fetch.last_var = info->last_outer; // 设置需要的最大字段编号
scratch.d.fetch.fixed = false; // 非固定格式
scratch.d.fetch.kind = NULL; // 未知类型
scratch.d.fetch.known_desc = NULL; // 未知描述
if (ExecComputeSlotInfo(state, &scratch)) // 计算字段信息
ExprEvalPushStep(state, &scratch); // 添加指令到计划书
// SQL 例子:如果 age 来自外连接的表,生成指令从外表取 age。
}
/* 为扫描表字段生成读取指令 */
if (info->last_scan > 0) // 如果表达式用到普通表字段
{
scratch.opcode = EEOP_SCAN_FETCHSOME; // 设置指令为“从扫描表取字段”
scratch.d.fetch.last_var = info->last_scan; // 设置需要的最大字段编号
scratch.d.fetch.fixed = false; // 非固定格式
scratch.d.fetch.kind = NULL; // 未知类型
scratch.d.fetch.known_desc = NULL; // 未知描述
if (ExecComputeSlotInfo(state, &scratch)) // 计算字段信息
ExprEvalPushStep(state, &scratch); // 添加指令到计划书
// SQL 例子:age 来自 users 表,生成指令从 users 表取 age。
}
/* 为 MULTIEXPR 子查询生成执行步骤 */
foreach(lc, info->multiexpr_subplans) // 遍历所有子查询
{
SubPlan *subplan = (SubPlan *) lfirst(lc); // 获取子查询
Assert(subplan->subLinkType == MULTIEXPR_SUBLINK); // 确保是 MULTIEXPR 类型
/* 执行子查询,忽略结果但存储到指定位置 */
ExecInitSubPlanExpr(subplan, state, &state->resvalue, &state->resnull);
// SQL 例子:如果有子查询(如 IN (SELECT ...)),生成执行子查询的指令。
}
}
注:MULTIEXPR 是什么?
在 PostgreSQL
源码里,MULTIEXPR
(对应枚举值 T_MultiExpr
)代表 多值表达式(Multiple expressions placeholder
)。
- 它主要用于多列子查询或多列
VALUES
列表 等场景。- 一个
MULTIEXPR
节点可以表示「一组表达式结果」,而不是单个Expr
。
换句话说:普通的 Expr
节点返回一个值,而 MULTIEXPR
节点表示一组值(tuple-like
)。
主要流程(以 SQL 为例)
ExecCreateExprSetupSteps
:像在执行SELECT name, age + 10 FROM users WHERE age > 30
前,先列出需要准备的工作(取age
字段),然后生成准备指令。expr_setup_walker
:检查age + 10
和age > 30
,发现需要从users
表取age
,记录下来;如果有子查询,也会记下来。ExecPushExprSetupSteps
:根据记录,生成指令,比如“从users
表取age
字段”或“执行子查询”,确保表达式能顺利运行。
ExecInitExprRec
ExecInitExprRec
是 PostgreSQL
查询执行引擎中用于将 SQL
表达式(如 age + 10
或 age > 30
)递归翻译成可执行指令的核心函数。
它遍历表达式树的每个节点(如字段 age
、常量 10
或运算符 +
),为每个节点生成具体的执行步骤(ExprEvalStep
),并将这些步骤添加到 ExprState
的步骤列表中。这些步骤告诉计算机如何一步步计算表达式的结果,比如“从表中取 age
值”“加上 10
”“比较是否大于 30
”。最终,这些步骤会被 ExecEvalExpr
使用来执行表达式。
主要流程(以 SQL 为例)
对于 SELECT name, age + 10 AS new_age FROM users WHERE age > 30
:
- 输入:表达式
age + 10
和age > 30
。 - 处理过程:
- 对于
age
(Var
节点):生成指令“从users
表取age
列”。 - 对于
10
(Const
节点):生成指令“使用常量10
”。 - 对于
+
(运算符):生成指令“将age
和10
相加”。 - 对于
age > 30
:类似地,为age
、30
和>
生成相应指令。
- 对于
- 输出:一组有序的执行步骤,存储在
ExprState->steps
中,供后续执行age + 10
和age > 30
。
/*
* Append the steps necessary for the evaluation of node to ExprState->steps,
* possibly recursing into sub-expressions of node.
*
* node - expression to evaluate
* state - ExprState to whose ->steps to append the necessary operations
* resv / resnull - where to store the result of the node into
*/
static void
ExecInitExprRec(Expr *node, ExprState *state,
Datum *resv, bool *resnull)
{
ExprEvalStep scratch = {0};
/* Guard against stack overflow due to overly complex expressions */
check_stack_depth();
/* Step's output location is always what the caller gave us */
Assert(resv != NULL && resnull != NULL);
scratch.resvalue = resv;
scratch.resnull = resnull;
/* cases should be ordered as they are in enum NodeTag */
switch (nodeTag(node))
{
case T_Var:
{
Var *variable = (Var *) node;
if (variable->varattno == InvalidAttrNumber)
{
/* whole-row Var */
ExecInitWholeRowVar(&scratch, variable, state);
}
else if (variable->varattno <= 0)
{
/* system column */
scratch.d.var.attnum = variable->varattno;
scratch.d.var.vartype = variable->vartype;
switch (variable->varno)
{
case INNER_VAR:
scratch.opcode = EEOP_INNER_SYSVAR;
break;
case OUTER_VAR:
scratch.opcode = EEOP_OUTER_SYSVAR;
break;
/* INDEX_VAR is handled by default case */
default:
scratch.opcode = EEOP_SCAN_SYSVAR;
break;
}
}
else
{
/* regular user column */
scratch.d.var.attnum = variable->varattno - 1;
scratch.d.var.vartype = variable->vartype;
switch (variable->varno)
{
case INNER_VAR:
scratch.opcode = EEOP_INNER_VAR;
break;
case OUTER_VAR:
scratch.opcode = EEOP_OUTER_VAR;
break;
/* INDEX_VAR is handled by default case */
default:
scratch.opcode = EEOP_SCAN_VAR;
break;
}
}
ExprEvalPushStep(state, &scratch);
break;
}
case T_Const:
{
Const *con = (Const *) node;
scratch.opcode = EEOP_CONST;
scratch.d.constval.value = con->constvalue;
scratch.d.constval.isnull = con->constisnull;
ExprEvalPushStep(state, &scratch);
break;
}
case T_Param:
{
Param *param = (Param *) node;
ParamListInfo params;
switch (param->paramkind)
{
case PARAM_EXEC:
scratch.opcode = EEOP_PARAM_EXEC;
scratch.d.param.paramid = param->paramid;
scratch.d.param.paramtype = param->paramtype;
ExprEvalPushStep(state, &scratch);
break;
case PARAM_EXTERN:
/*
* If we have a relevant ParamCompileHook, use it;
* otherwise compile a standard EEOP_PARAM_EXTERN
* step. ext_params, if supplied, takes precedence
* over info from the parent node's EState (if any).
*/
if (state->ext_params)
params = state->ext_params;
else if (state->parent &&
state->parent->state)
params = state->parent->state->es_param_list_info;
else
params = NULL;
if (params && params->paramCompile)
{
params->paramCompile(params, param, state,
resv, resnull);
}
else
{
scratch.opcode = EEOP_PARAM_EXTERN;
scratch.d.param.paramid = param->paramid;
scratch.d.param.paramtype = param->paramtype;
ExprEvalPushStep(state, &scratch);
}
break;
default:
elog(ERROR, "unrecognized paramkind: %d",
(int) param->paramkind);
break;
}
break;
}
case T_Aggref:
{
Aggref *aggref = (Aggref *) node;
scratch.opcode = EEOP_AGGREF;
scratch.d.aggref.aggno = aggref->aggno;
if (state->parent && IsA(state->parent, AggState))
{
AggState *aggstate = (AggState *) state->parent;
aggstate->aggs = lappend(aggstate->aggs, aggref);
}
else
{
/* planner messed up */
elog(ERROR, "Aggref found in non-Agg plan node");
}
ExprEvalPushStep(state, &scratch);
break;
}
case T_GroupingFunc:
{
GroupingFunc *grp_node = (GroupingFunc *) node;
Agg *agg;
if (!state->parent || !IsA(state->parent, AggState) ||
!IsA(state->parent->plan, Agg))
elog(ERROR, "GroupingFunc found in non-Agg plan node");
scratch.opcode = EEOP_GROUPING_FUNC;
agg = (Agg *) (state->parent->plan);
if (agg->groupingSets)
scratch.d.grouping_func.clauses = grp_node->cols;
else
scratch.d.grouping_func.clauses = NIL;
ExprEvalPushStep(state, &scratch);
break;
}
case T_WindowFunc:
{
WindowFunc *wfunc = (WindowFunc *) node;
WindowFuncExprState *wfstate = makeNode(WindowFuncExprState);
wfstate->wfunc = wfunc;
if (state->parent && IsA(state->parent, WindowAggState))
{
WindowAggState *winstate = (WindowAggState *) state->parent;
int nfuncs;
winstate->funcs = lappend(winstate->funcs, wfstate);
nfuncs = ++winstate->numfuncs;
if (wfunc->winagg)
winstate->numaggs++;
/* for now initialize agg using old style expressions */
wfstate->args = ExecInitExprList(wfunc->args,
state->parent);
wfstate->aggfilter = ExecInitExpr(wfunc->aggfilter,
state->parent);
/*
* Complain if the windowfunc's arguments contain any
* windowfuncs; nested window functions are semantically
* nonsensical. (This should have been caught earlier,
* but we defend against it here anyway.)
*/
if (nfuncs != winstate->numfuncs)
ereport(ERROR,
(errcode(ERRCODE_WINDOWING_ERROR),
errmsg("window function calls cannot be nested")));
}
else
{
/* planner messed up */
elog(ERROR, "WindowFunc found in non-WindowAgg plan node");
}
scratch.opcode = EEOP_WINDOW_FUNC;
scratch.d.window_func.wfstate = wfstate;
ExprEvalPushStep(state, &scratch);
break;
}
case T_MergeSupportFunc:
{
/* must be in a MERGE, else something messed up */
if (!state->parent ||
!IsA(state->parent, ModifyTableState) ||
((ModifyTableState *) state->parent)->operation != CMD_MERGE)
elog(ERROR, "MergeSupportFunc found in non-merge plan node");
scratch.opcode = EEOP_MERGE_SUPPORT_FUNC;
ExprEvalPushStep(state, &scratch);
break;
}
case T_SubscriptingRef:
{
SubscriptingRef *sbsref = (SubscriptingRef *) node;
ExecInitSubscriptingRef(&scratch, sbsref, state, resv, resnull);
break;
}
case T_FuncExpr:
{
FuncExpr *func = (FuncExpr *) node;
ExecInitFunc(&scratch, node,
func->args, func->funcid, func->inputcollid,
state);
ExprEvalPushStep(state, &scratch);
break;
}
case T_OpExpr:
{
OpExpr *op = (OpExpr *) node;
ExecInitFunc(&scratch, node,
op->args, op->opfuncid, op->inputcollid,
state);
ExprEvalPushStep(state, &scratch);
break;
}
case T_DistinctExpr:
{
DistinctExpr *op = (DistinctExpr *) node;
ExecInitFunc(&scratch, node,
op->args, op->opfuncid, op->inputcollid,
state);
/*
* Change opcode of call instruction to EEOP_DISTINCT.
*
* XXX: historically we've not called the function usage
* pgstat infrastructure - that seems inconsistent given that
* we do so for normal function *and* operator evaluation. If
* we decided to do that here, we'd probably want separate
* opcodes for FUSAGE or not.
*/
scratch.opcode = EEOP_DISTINCT;
ExprEvalPushStep(state, &scratch);
break;
}
case T_NullIfExpr:
{
NullIfExpr *op = (NullIfExpr *) node;
ExecInitFunc(&scratch, node,
op->args, op->opfuncid, op->inputcollid,
state);
/*
* If first argument is of varlena type, we'll need to ensure
* that the value passed to the comparison function is a
* read-only pointer.
*/
scratch.d.func.make_ro =
(get_typlen(exprType((Node *) linitial(op->args))) == -1);
/*
* Change opcode of call instruction to EEOP_NULLIF.
*
* XXX: historically we've not called the function usage
* pgstat infrastructure - that seems inconsistent given that
* we do so for normal function *and* operator evaluation. If
* we decided to do that here, we'd probably want separate
* opcodes for FUSAGE or not.
*/
scratch.opcode = EEOP_NULLIF;
ExprEvalPushStep(state, &scratch);
break;
}
case T_ScalarArrayOpExpr:
{
ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
Expr *scalararg;
Expr *arrayarg;
FmgrInfo *finfo;
FunctionCallInfo fcinfo;
AclResult aclresult;
Oid cmpfuncid;
/*
* Select the correct comparison function. When we do hashed
* NOT IN clauses, the opfuncid will be the inequality
* comparison function and negfuncid will be set to equality.
* We need to use the equality function for hash probes.
*/
if (OidIsValid(opexpr->negfuncid))
{
Assert(OidIsValid(opexpr->hashfuncid));
cmpfuncid = opexpr->negfuncid;
}
else
cmpfuncid = opexpr->opfuncid;
Assert(list_length(opexpr->args) == 2);
scalararg = (Expr *) linitial(opexpr->args);
arrayarg = (Expr *) lsecond(opexpr->args);
/* Check permission to call function */
aclresult = object_aclcheck(ProcedureRelationId, cmpfuncid,
GetUserId(),
ACL_EXECUTE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_FUNCTION,
get_func_name(cmpfuncid));
InvokeFunctionExecuteHook(cmpfuncid);
if (OidIsValid(opexpr->hashfuncid))
{
aclresult = object_aclcheck(ProcedureRelationId, opexpr->hashfuncid,
GetUserId(),
ACL_EXECUTE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_FUNCTION,
get_func_name(opexpr->hashfuncid));
InvokeFunctionExecuteHook(opexpr->hashfuncid);
}
/* Set up the primary fmgr lookup information */
finfo = palloc0(sizeof(FmgrInfo));
fcinfo = palloc0(SizeForFunctionCallInfo(2));
fmgr_info(cmpfuncid, finfo);
fmgr_info_set_expr((Node *) node, finfo);
InitFunctionCallInfoData(*fcinfo, finfo, 2,
opexpr->inputcollid, NULL, NULL);
/*
* If hashfuncid is set, we create a EEOP_HASHED_SCALARARRAYOP
* step instead of a EEOP_SCALARARRAYOP. This provides much
* faster lookup performance than the normal linear search
* when the number of items in the array is anything but very
* small.
*/
if (OidIsValid(opexpr->hashfuncid))
{
/* Evaluate scalar directly into left function argument */
ExecInitExprRec(scalararg, state,
&fcinfo->args[0].value, &fcinfo->args[0].isnull);
/*
* Evaluate array argument into our return value. There's
* no danger in that, because the return value is
* guaranteed to be overwritten by
* EEOP_HASHED_SCALARARRAYOP, and will not be passed to
* any other expression.
*/
ExecInitExprRec(arrayarg, state, resv, resnull);
/* And perform the operation */
scratch.opcode = EEOP_HASHED_SCALARARRAYOP;
scratch.d.hashedscalararrayop.inclause = opexpr->useOr;
scratch.d.hashedscalararrayop.finfo = finfo;
scratch.d.hashedscalararrayop.fcinfo_data = fcinfo;
scratch.d.hashedscalararrayop.saop = opexpr;
ExprEvalPushStep(state, &scratch);
}
else
{
/* Evaluate scalar directly into left function argument */
ExecInitExprRec(scalararg, state,
&fcinfo->args[0].value,
&fcinfo->args[0].isnull);
/*
* Evaluate array argument into our return value. There's
* no danger in that, because the return value is
* guaranteed to be overwritten by EEOP_SCALARARRAYOP, and
* will not be passed to any other expression.
*/
ExecInitExprRec(arrayarg, state, resv, resnull);
/* And perform the operation */
scratch.opcode = EEOP_SCALARARRAYOP;
scratch.d.scalararrayop.element_type = InvalidOid;
scratch.d.scalararrayop.useOr = opexpr->useOr;
scratch.d.scalararrayop.finfo = finfo;
scratch.d.scalararrayop.fcinfo_data = fcinfo;
scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
ExprEvalPushStep(state, &scratch);
}
break;
}
case T_BoolExpr:
{
BoolExpr *boolexpr = (BoolExpr *) node;
int nargs = list_length(boolexpr->args);
List *adjust_jumps = NIL;
int off;
ListCell *lc;
/* allocate scratch memory used by all steps of AND/OR */
if (boolexpr->boolop != NOT_EXPR)
scratch.d.boolexpr.anynull = (bool *) palloc(sizeof(bool));
/*
* For each argument evaluate the argument itself, then
* perform the bool operation's appropriate handling.
*
* We can evaluate each argument into our result area, since
* the short-circuiting logic means we only need to remember
* previous NULL values.
*
* AND/OR is split into separate STEP_FIRST (one) / STEP (zero
* or more) / STEP_LAST (one) steps, as each of those has to
* perform different work. The FIRST/LAST split is valid
* because AND/OR have at least two arguments.
*/
off = 0;
foreach(lc, boolexpr->args)
{
Expr *arg = (Expr *) lfirst(lc);
/* Evaluate argument into our output variable */
ExecInitExprRec(arg, state, resv, resnull);
/* Perform the appropriate step type */
switch (boolexpr->boolop)
{
case AND_EXPR:
Assert(nargs >= 2);
if (off == 0)
scratch.opcode = EEOP_BOOL_AND_STEP_FIRST;
else if (off + 1 == nargs)
scratch.opcode = EEOP_BOOL_AND_STEP_LAST;
else
scratch.opcode = EEOP_BOOL_AND_STEP;
break;
case OR_EXPR:
Assert(nargs >= 2);
if (off == 0)
scratch.opcode = EEOP_BOOL_OR_STEP_FIRST;
else if (off + 1 == nargs)
scratch.opcode = EEOP_BOOL_OR_STEP_LAST;
else
scratch.opcode = EEOP_BOOL_OR_STEP;
break;
case NOT_EXPR:
Assert(nargs == 1);
scratch.opcode = EEOP_BOOL_NOT_STEP;
break;
default:
elog(ERROR, "unrecognized boolop: %d",
(int) boolexpr->boolop);
break;
}
scratch.d.boolexpr.jumpdone = -1;
ExprEvalPushStep(state, &scratch);
adjust_jumps = lappend_int(adjust_jumps,
state->steps_len - 1);
off++;
}
/* adjust jump targets */
foreach(lc, adjust_jumps)
{
ExprEvalStep *as = &state->steps[lfirst_int(lc)];
Assert(as->d.boolexpr.jumpdone == -1);
as->d.boolexpr.jumpdone = state->steps_len;
}
break;
}
case T_SubPlan:
{
SubPlan *subplan = (SubPlan *) node;
/*
* Real execution of a MULTIEXPR SubPlan has already been
* done. What we have to do here is return a dummy NULL record
* value in case this targetlist element is assigned
* someplace.
*/
if (subplan->subLinkType == MULTIEXPR_SUBLINK)
{
scratch.opcode = EEOP_CONST;
scratch.d.constval.value = (Datum) 0;
scratch.d.constval.isnull = true;
ExprEvalPushStep(state, &scratch);
break;
}
ExecInitSubPlanExpr(subplan, state, resv, resnull);
break;
}
case T_FieldSelect:
{
FieldSelect *fselect = (FieldSelect *) node;
/* evaluate row/record argument into result area */
ExecInitExprRec(fselect->arg, state, resv, resnull);
/* and extract field */
scratch.opcode = EEOP_FIELDSELECT;
scratch.d.fieldselect.fieldnum = fselect->fieldnum;
scratch.d.fieldselect.resulttype = fselect->resulttype;
scratch.d.fieldselect.rowcache.cacheptr = NULL;
ExprEvalPushStep(state, &scratch);
break;
}
case T_FieldStore:
{
FieldStore *fstore = (FieldStore *) node;
TupleDesc tupDesc;
ExprEvalRowtypeCache *rowcachep;
Datum *values;
bool *nulls;
int ncolumns;
ListCell *l1,
*l2;
/* find out the number of columns in the composite type */
tupDesc = lookup_rowtype_tupdesc(fstore->resulttype, -1);
ncolumns = tupDesc->natts;
ReleaseTupleDesc(tupDesc);
/* create workspace for column values */
values = (Datum *) palloc(sizeof(Datum) * ncolumns);
nulls = (bool *) palloc(sizeof(bool) * ncolumns);
/* create shared composite-type-lookup cache struct */
rowcachep = palloc(sizeof(ExprEvalRowtypeCache));
rowcachep->cacheptr = NULL;
/* emit code to evaluate the composite input value */
ExecInitExprRec(fstore->arg, state, resv, resnull);
/* next, deform the input tuple into our workspace */
scratch.opcode = EEOP_FIELDSTORE_DEFORM;
scratch.d.fieldstore.fstore = fstore;
scratch.d.fieldstore.rowcache = rowcachep;
scratch.d.fieldstore.values = values;
scratch.d.fieldstore.nulls = nulls;
scratch.d.fieldstore.ncolumns = ncolumns;
ExprEvalPushStep(state, &scratch);
/* evaluate new field values, store in workspace columns */
forboth(l1, fstore->newvals, l2, fstore->fieldnums)
{
Expr *e = (Expr *) lfirst(l1);
AttrNumber fieldnum = lfirst_int(l2);
Datum *save_innermost_caseval;
bool *save_innermost_casenull;
if (fieldnum <= 0 || fieldnum > ncolumns)
elog(ERROR, "field number %d is out of range in FieldStore",
fieldnum);
/*
* Use the CaseTestExpr mechanism to pass down the old
* value of the field being replaced; this is needed in
* case the newval is itself a FieldStore or
* SubscriptingRef that has to obtain and modify the old
* value. It's safe to reuse the CASE mechanism because
* there cannot be a CASE between here and where the value
* would be needed, and a field assignment can't be within
* a CASE either. (So saving and restoring
* innermost_caseval is just paranoia, but let's do it
* anyway.)
*
* Another non-obvious point is that it's safe to use the
* field's values[]/nulls[] entries as both the caseval
* source and the result address for this subexpression.
* That's okay only because (1) both FieldStore and
* SubscriptingRef evaluate their arg or refexpr inputs
* first, and (2) any such CaseTestExpr is directly the
* arg or refexpr input. So any read of the caseval will
* occur before there's a chance to overwrite it. Also,
* if multiple entries in the newvals/fieldnums lists
* target the same field, they'll effectively be applied
* left-to-right which is what we want.
*/
save_innermost_caseval = state->innermost_caseval;
save_innermost_casenull = state->innermost_casenull;
state->innermost_caseval = &values[fieldnum - 1];
state->innermost_casenull = &nulls[fieldnum - 1];
ExecInitExprRec(e, state,
&values[fieldnum - 1],
&nulls[fieldnum - 1]);
state->innermost_caseval = save_innermost_caseval;
state->innermost_casenull = save_innermost_casenull;
}
/* finally, form result tuple */
scratch.opcode = EEOP_FIELDSTORE_FORM;
scratch.d.fieldstore.fstore = fstore;
scratch.d.fieldstore.rowcache = rowcachep;
scratch.d.fieldstore.values = values;
scratch.d.fieldstore.nulls = nulls;
scratch.d.fieldstore.ncolumns = ncolumns;
ExprEvalPushStep(state, &scratch);
break;
}
case T_RelabelType:
{
/* relabel doesn't need to do anything at runtime */
RelabelType *relabel = (RelabelType *) node;
ExecInitExprRec(relabel->arg, state, resv, resnull);
break;
}
case T_CoerceViaIO:
{
CoerceViaIO *iocoerce = (CoerceViaIO *) node;
Oid iofunc;
bool typisvarlena;
Oid typioparam;
FunctionCallInfo fcinfo_in;
/* evaluate argument into step's result area */
ExecInitExprRec(iocoerce->arg, state, resv, resnull);
/*
* Prepare both output and input function calls, to be
* evaluated inside a single evaluation step for speed - this
* can be a very common operation.
*
* We don't check permissions here as a type's input/output
* function are assumed to be executable by everyone.
*/
if (state->escontext == NULL)
scratch.opcode = EEOP_IOCOERCE;
else
scratch.opcode = EEOP_IOCOERCE_SAFE;
/* lookup the source type's output function */
scratch.d.iocoerce.finfo_out = palloc0(sizeof(FmgrInfo));
scratch.d.iocoerce.fcinfo_data_out = palloc0(SizeForFunctionCallInfo(1));
getTypeOutputInfo(exprType((Node *) iocoerce->arg),
&iofunc, &typisvarlena);
fmgr_info(iofunc, scratch.d.iocoerce.finfo_out);
fmgr_info_set_expr((Node *) node, scratch.d.iocoerce.finfo_out);
InitFunctionCallInfoData(*scratch.d.iocoerce.fcinfo_data_out,
scratch.d.iocoerce.finfo_out,
1, InvalidOid, NULL, NULL);
/* lookup the result type's input function */
scratch.d.iocoerce.finfo_in = palloc0(sizeof(FmgrInfo));
scratch.d.iocoerce.fcinfo_data_in = palloc0(SizeForFunctionCallInfo(3));
getTypeInputInfo(iocoerce->resulttype,
&iofunc, &typioparam);
fmgr_info(iofunc, scratch.d.iocoerce.finfo_in);
fmgr_info_set_expr((Node *) node, scratch.d.iocoerce.finfo_in);
InitFunctionCallInfoData(*scratch.d.iocoerce.fcinfo_data_in,
scratch.d.iocoerce.finfo_in,
3, InvalidOid, NULL, NULL);
/*
* We can preload the second and third arguments for the input
* function, since they're constants.
*/
fcinfo_in = scratch.d.iocoerce.fcinfo_data_in;
fcinfo_in->args[1].value = ObjectIdGetDatum(typioparam);
fcinfo_in->args[1].isnull = false;
fcinfo_in->args[2].value = Int32GetDatum(-1);
fcinfo_in->args[2].isnull = false;
fcinfo_in->context = (Node *) state->escontext;
ExprEvalPushStep(state, &scratch);
break;
}
case T_ArrayCoerceExpr:
{
ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
Oid resultelemtype;
ExprState *elemstate;
/* evaluate argument into step's result area */
ExecInitExprRec(acoerce->arg, state, resv, resnull);
resultelemtype = get_element_type(acoerce->resulttype);
if (!OidIsValid(resultelemtype))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("target type is not an array")));
/*
* Construct a sub-expression for the per-element expression;
* but don't ready it until after we check it for triviality.
* We assume it hasn't any Var references, but does have a
* CaseTestExpr representing the source array element values.
*/
elemstate = makeNode(ExprState);
elemstate->expr = acoerce->elemexpr;
elemstate->parent = state->parent;
elemstate->ext_params = state->ext_params;
elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
ExecInitExprRec(acoerce->elemexpr, elemstate,
&elemstate->resvalue, &elemstate->resnull);
if (elemstate->steps_len == 1 &&
elemstate->steps[0].opcode == EEOP_CASE_TESTVAL)
{
/* Trivial, so we need no per-element work at runtime */
elemstate = NULL;
}
else
{
/* Not trivial, so append a DONE step */
scratch.opcode = EEOP_DONE;
ExprEvalPushStep(elemstate, &scratch);
/* and ready the subexpression */
ExecReadyExpr(elemstate);
}
scratch.opcode = EEOP_ARRAYCOERCE;
scratch.d.arraycoerce.elemexprstate = elemstate;
scratch.d.arraycoerce.resultelemtype = resultelemtype;
if (elemstate)
{
/* Set up workspace for array_map */
scratch.d.arraycoerce.amstate =
(ArrayMapState *) palloc0(sizeof(ArrayMapState));
}
else
{
/* Don't need workspace if there's no subexpression */
scratch.d.arraycoerce.amstate = NULL;
}
ExprEvalPushStep(state, &scratch);
break;
}
case T_ConvertRowtypeExpr:
{
ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
ExprEvalRowtypeCache *rowcachep;
/* cache structs must be out-of-line for space reasons */
rowcachep = palloc(2 * sizeof(ExprEvalRowtypeCache));
rowcachep[0].cacheptr = NULL;
rowcachep[1].cacheptr = NULL;
/* evaluate argument into step's result area */
ExecInitExprRec(convert->arg, state, resv, resnull);
/* and push conversion step */
scratch.opcode = EEOP_CONVERT_ROWTYPE;
scratch.d.convert_rowtype.inputtype =
exprType((Node *) convert->arg);
scratch.d.convert_rowtype.outputtype = convert->resulttype;
scratch.d.convert_rowtype.incache = &rowcachep[0];
scratch.d.convert_rowtype.outcache = &rowcachep[1];
scratch.d.convert_rowtype.map = NULL;
ExprEvalPushStep(state, &scratch);
break;
}
/* note that CaseWhen expressions are handled within this block */
case T_CaseExpr:
{
CaseExpr *caseExpr = (CaseExpr *) node;
List *adjust_jumps = NIL;
Datum *caseval = NULL;
bool *casenull = NULL;
ListCell *lc;
/*
* If there's a test expression, we have to evaluate it and
* save the value where the CaseTestExpr placeholders can find
* it.
*/
if (caseExpr->arg != NULL)
{
/* Evaluate testexpr into caseval/casenull workspace */
caseval = palloc(sizeof(Datum));
casenull = palloc(sizeof(bool));
ExecInitExprRec(caseExpr->arg, state,
caseval, casenull);
/*
* Since value might be read multiple times, force to R/O
* - but only if it could be an expanded datum.
*/
if (get_typlen(exprType((Node *) caseExpr->arg)) == -1)
{
/* change caseval in-place */
scratch.opcode = EEOP_MAKE_READONLY;
scratch.resvalue = caseval;
scratch.resnull = casenull;
scratch.d.make_readonly.value = caseval;
scratch.d.make_readonly.isnull = casenull;
ExprEvalPushStep(state, &scratch);
/* restore normal settings of scratch fields */
scratch.resvalue = resv;
scratch.resnull = resnull;
}
}
/*
* Prepare to evaluate each of the WHEN clauses in turn; as
* soon as one is true we return the value of the
* corresponding THEN clause. If none are true then we return
* the value of the ELSE clause, or NULL if there is none.
*/
foreach(lc, caseExpr->args)
{
CaseWhen *when = (CaseWhen *) lfirst(lc);
Datum *save_innermost_caseval;
bool *save_innermost_casenull;
int whenstep;
/*
* Make testexpr result available to CaseTestExpr nodes
* within the condition. We must save and restore prior
* setting of innermost_caseval fields, in case this node
* is itself within a larger CASE.
*
* If there's no test expression, we don't actually need
* to save and restore these fields; but it's less code to
* just do so unconditionally.
*/
save_innermost_caseval = state->innermost_caseval;
save_innermost_casenull = state->innermost_casenull;
state->innermost_caseval = caseval;
state->innermost_casenull = casenull;
/* evaluate condition into CASE's result variables */
ExecInitExprRec(when->expr, state, resv, resnull);
state->innermost_caseval = save_innermost_caseval;
state->innermost_casenull = save_innermost_casenull;
/* If WHEN result isn't true, jump to next CASE arm */
scratch.opcode = EEOP_JUMP_IF_NOT_TRUE;
scratch.d.jump.jumpdone = -1; /* computed later */
ExprEvalPushStep(state, &scratch);
whenstep = state->steps_len - 1;
/*
* If WHEN result is true, evaluate THEN result, storing
* it into the CASE's result variables.
*/
ExecInitExprRec(when->result, state, resv, resnull);
/* Emit JUMP step to jump to end of CASE's code */
scratch.opcode = EEOP_JUMP;
scratch.d.jump.jumpdone = -1; /* computed later */
ExprEvalPushStep(state, &scratch);
/*
* Don't know address for that jump yet, compute once the
* whole CASE expression is built.
*/
adjust_jumps = lappend_int(adjust_jumps,
state->steps_len - 1);
/*
* But we can set WHEN test's jump target now, to make it
* jump to the next WHEN subexpression or the ELSE.
*/
state->steps[whenstep].d.jump.jumpdone = state->steps_len;
}
/* transformCaseExpr always adds a default */
Assert(caseExpr->defresult);
/* evaluate ELSE expr into CASE's result variables */
ExecInitExprRec(caseExpr->defresult, state,
resv, resnull);
/* adjust jump targets */
foreach(lc, adjust_jumps)
{
ExprEvalStep *as = &state->steps[lfirst_int(lc)];
Assert(as->opcode == EEOP_JUMP);
Assert(as->d.jump.jumpdone == -1);
as->d.jump.jumpdone = state->steps_len;
}
break;
}
case T_CaseTestExpr:
{
/*
* Read from location identified by innermost_caseval. Note
* that innermost_caseval could be NULL, if this node isn't
* actually within a CaseExpr, ArrayCoerceExpr, etc structure.
* That can happen because some parts of the system abuse
* CaseTestExpr to cause a read of a value externally supplied
* in econtext->caseValue_datum. We'll take care of that
* scenario at runtime.
*/
scratch.opcode = EEOP_CASE_TESTVAL;
scratch.d.casetest.value = state->innermost_caseval;
scratch.d.casetest.isnull = state->innermost_casenull;
ExprEvalPushStep(state, &scratch);
break;
}
case T_ArrayExpr:
{
ArrayExpr *arrayexpr = (ArrayExpr *) node;
int nelems = list_length(arrayexpr->elements);
ListCell *lc;
int elemoff;
/*
* Evaluate by computing each element, and then forming the
* array. Elements are computed into scratch arrays
* associated with the ARRAYEXPR step.
*/
scratch.opcode = EEOP_ARRAYEXPR;
scratch.d.arrayexpr.elemvalues =
(Datum *) palloc(sizeof(Datum) * nelems);
scratch.d.arrayexpr.elemnulls =
(bool *) palloc(sizeof(bool) * nelems);
scratch.d.arrayexpr.nelems = nelems;
/* fill remaining fields of step */
scratch.d.arrayexpr.multidims = arrayexpr->multidims;
scratch.d.arrayexpr.elemtype = arrayexpr->element_typeid;
/* do one-time catalog lookup for type info */
get_typlenbyvalalign(arrayexpr->element_typeid,
&scratch.d.arrayexpr.elemlength,
&scratch.d.arrayexpr.elembyval,
&scratch.d.arrayexpr.elemalign);
/* prepare to evaluate all arguments */
elemoff = 0;
foreach(lc, arrayexpr->elements)
{
Expr *e = (Expr *) lfirst(lc);
ExecInitExprRec(e, state,
&scratch.d.arrayexpr.elemvalues[elemoff],
&scratch.d.arrayexpr.elemnulls[elemoff]);
elemoff++;
}
/* and then collect all into an array */
ExprEvalPushStep(state, &scratch);
break;
}
case T_RowExpr:
{
RowExpr *rowexpr = (RowExpr *) node;
int nelems = list_length(rowexpr->args);
TupleDesc tupdesc;
int i;
ListCell *l;
/* Build tupdesc to describe result tuples */
if (rowexpr->row_typeid == RECORDOID)
{
/* generic record, use types of given expressions */
tupdesc = ExecTypeFromExprList(rowexpr->args);
/* ... but adopt RowExpr's column aliases */
ExecTypeSetColNames(tupdesc, rowexpr->colnames);
/* Bless the tupdesc so it can be looked up later */
BlessTupleDesc(tupdesc);
}
else
{
/* it's been cast to a named type, use that */
tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1);
}
/*
* In the named-type case, the tupdesc could have more columns
* than are in the args list, since the type might have had
* columns added since the ROW() was parsed. We want those
* extra columns to go to nulls, so we make sure that the
* workspace arrays are large enough and then initialize any
* extra columns to read as NULLs.
*/
Assert(nelems <= tupdesc->natts);
nelems = Max(nelems, tupdesc->natts);
/*
* Evaluate by first building datums for each field, and then
* a final step forming the composite datum.
*/
scratch.opcode = EEOP_ROW;
scratch.d.row.tupdesc = tupdesc;
/* space for the individual field datums */
scratch.d.row.elemvalues =
(Datum *) palloc(sizeof(Datum) * nelems);
scratch.d.row.elemnulls =
(bool *) palloc(sizeof(bool) * nelems);
/* as explained above, make sure any extra columns are null */
memset(scratch.d.row.elemnulls, true, sizeof(bool) * nelems);
/* Set up evaluation, skipping any deleted columns */
i = 0;
foreach(l, rowexpr->args)
{
Form_pg_attribute att = TupleDescAttr(tupdesc, i);
Expr *e = (Expr *) lfirst(l);
if (!att->attisdropped)
{
/*
* Guard against ALTER COLUMN TYPE on rowtype since
* the RowExpr was created. XXX should we check
* typmod too? Not sure we can be sure it'll be the
* same.
*/
if (exprType((Node *) e) != att->atttypid)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("ROW() column has type %s instead of type %s",
format_type_be(exprType((Node *) e)),
format_type_be(att->atttypid))));
}
else
{
/*
* Ignore original expression and insert a NULL. We
* don't really care what type of NULL it is, so
* always make an int4 NULL.
*/
e = (Expr *) makeNullConst(INT4OID, -1, InvalidOid);
}
/* Evaluate column expr into appropriate workspace slot */
ExecInitExprRec(e, state,
&scratch.d.row.elemvalues[i],
&scratch.d.row.elemnulls[i]);
i++;
}
/* And finally build the row value */
ExprEvalPushStep(state, &scratch);
break;
}
case T_RowCompareExpr:
{
RowCompareExpr *rcexpr = (RowCompareExpr *) node;
int nopers = list_length(rcexpr->opnos);
List *adjust_jumps = NIL;
ListCell *l_left_expr,
*l_right_expr,
*l_opno,
*l_opfamily,
*l_inputcollid;
ListCell *lc;
/*
* Iterate over each field, prepare comparisons. To handle
* NULL results, prepare jumps to after the expression. If a
* comparison yields a != 0 result, jump to the final step.
*/
Assert(list_length(rcexpr->largs) == nopers);
Assert(list_length(rcexpr->rargs) == nopers);
Assert(list_length(rcexpr->opfamilies) == nopers);
Assert(list_length(rcexpr->inputcollids) == nopers);
forfive(l_left_expr, rcexpr->largs,
l_right_expr, rcexpr->rargs,
l_opno, rcexpr->opnos,
l_opfamily, rcexpr->opfamilies,
l_inputcollid, rcexpr->inputcollids)
{
Expr *left_expr = (Expr *) lfirst(l_left_expr);
Expr *right_expr = (Expr *) lfirst(l_right_expr);
Oid opno = lfirst_oid(l_opno);
Oid opfamily = lfirst_oid(l_opfamily);
Oid inputcollid = lfirst_oid(l_inputcollid);
int strategy;
Oid lefttype;
Oid righttype;
Oid proc;
FmgrInfo *finfo;
FunctionCallInfo fcinfo;
get_op_opfamily_properties(opno, opfamily, false,
&strategy,
&lefttype,
&righttype);
proc = get_opfamily_proc(opfamily,
lefttype,
righttype,
BTORDER_PROC);
if (!OidIsValid(proc))
elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
BTORDER_PROC, lefttype, righttype, opfamily);
/* Set up the primary fmgr lookup information */
finfo = palloc0(sizeof(FmgrInfo));
fcinfo = palloc0(SizeForFunctionCallInfo(2));
fmgr_info(proc, finfo);
fmgr_info_set_expr((Node *) node, finfo);
InitFunctionCallInfoData(*fcinfo, finfo, 2,
inputcollid, NULL, NULL);
/*
* If we enforced permissions checks on index support
* functions, we'd need to make a check here. But the
* index support machinery doesn't do that, and thus
* neither does this code.
*/
/* evaluate left and right args directly into fcinfo */
ExecInitExprRec(left_expr, state,
&fcinfo->args[0].value, &fcinfo->args[0].isnull);
ExecInitExprRec(right_expr, state,
&fcinfo->args[1].value, &fcinfo->args[1].isnull);
scratch.opcode = EEOP_ROWCOMPARE_STEP;
scratch.d.rowcompare_step.finfo = finfo;
scratch.d.rowcompare_step.fcinfo_data = fcinfo;
scratch.d.rowcompare_step.fn_addr = finfo->fn_addr;
/* jump targets filled below */
scratch.d.rowcompare_step.jumpnull = -1;
scratch.d.rowcompare_step.jumpdone = -1;
ExprEvalPushStep(state, &scratch);
adjust_jumps = lappend_int(adjust_jumps,
state->steps_len - 1);
}
/*
* We could have a zero-column rowtype, in which case the rows
* necessarily compare equal.
*/
if (nopers == 0)
{
scratch.opcode = EEOP_CONST;
scratch.d.constval.value = Int32GetDatum(0);
scratch.d.constval.isnull = false;
ExprEvalPushStep(state, &scratch);
}
/* Finally, examine the last comparison result */
scratch.opcode = EEOP_ROWCOMPARE_FINAL;
scratch.d.rowcompare_final.rctype = rcexpr->rctype;
ExprEvalPushStep(state, &scratch);
/* adjust jump targets */
foreach(lc, adjust_jumps)
{
ExprEvalStep *as = &state->steps[lfirst_int(lc)];
Assert(as->opcode == EEOP_ROWCOMPARE_STEP);
Assert(as->d.rowcompare_step.jumpdone == -1);
Assert(as->d.rowcompare_step.jumpnull == -1);
/* jump to comparison evaluation */
as->d.rowcompare_step.jumpdone = state->steps_len - 1;
/* jump to the following expression */
as->d.rowcompare_step.jumpnull = state->steps_len;
}
break;
}
case T_CoalesceExpr:
{
CoalesceExpr *coalesce = (CoalesceExpr *) node;
List *adjust_jumps = NIL;
ListCell *lc;
/* We assume there's at least one arg */
Assert(coalesce->args != NIL);
/*
* Prepare evaluation of all coalesced arguments, after each
* one push a step that short-circuits if not null.
*/
foreach(lc, coalesce->args)
{
Expr *e = (Expr *) lfirst(lc);
/* evaluate argument, directly into result datum */
ExecInitExprRec(e, state, resv, resnull);
/* if it's not null, skip to end of COALESCE expr */
scratch.opcode = EEOP_JUMP_IF_NOT_NULL;
scratch.d.jump.jumpdone = -1; /* adjust later */
ExprEvalPushStep(state, &scratch);
adjust_jumps = lappend_int(adjust_jumps,
state->steps_len - 1);
}
/*
* No need to add a constant NULL return - we only can get to
* the end of the expression if a NULL already is being
* returned.
*/
/* adjust jump targets */
foreach(lc, adjust_jumps)
{
ExprEvalStep *as = &state->steps[lfirst_int(lc)];
Assert(as->opcode == EEOP_JUMP_IF_NOT_NULL);
Assert(as->d.jump.jumpdone == -1);
as->d.jump.jumpdone = state->steps_len;
}
break;
}
case T_MinMaxExpr:
{
MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
int nelems = list_length(minmaxexpr->args);
TypeCacheEntry *typentry;
FmgrInfo *finfo;
FunctionCallInfo fcinfo;
ListCell *lc;
int off;
/* Look up the btree comparison function for the datatype */
typentry = lookup_type_cache(minmaxexpr->minmaxtype,
TYPECACHE_CMP_PROC);
if (!OidIsValid(typentry->cmp_proc))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("could not identify a comparison function for type %s",
format_type_be(minmaxexpr->minmaxtype))));
/*
* If we enforced permissions checks on index support
* functions, we'd need to make a check here. But the index
* support machinery doesn't do that, and thus neither does
* this code.
*/
/* Perform function lookup */
finfo = palloc0(sizeof(FmgrInfo));
fcinfo = palloc0(SizeForFunctionCallInfo(2));
fmgr_info(typentry->cmp_proc, finfo);
fmgr_info_set_expr((Node *) node, finfo);
InitFunctionCallInfoData(*fcinfo, finfo, 2,
minmaxexpr->inputcollid, NULL, NULL);
scratch.opcode = EEOP_MINMAX;
/* allocate space to store arguments */
scratch.d.minmax.values =
(Datum *) palloc(sizeof(Datum) * nelems);
scratch.d.minmax.nulls =
(bool *) palloc(sizeof(bool) * nelems);
scratch.d.minmax.nelems = nelems;
scratch.d.minmax.op = minmaxexpr->op;
scratch.d.minmax.finfo = finfo;
scratch.d.minmax.fcinfo_data = fcinfo;
/* evaluate expressions into minmax->values/nulls */
off = 0;
foreach(lc, minmaxexpr->args)
{
Expr *e = (Expr *) lfirst(lc);
ExecInitExprRec(e, state,
&scratch.d.minmax.values[off],
&scratch.d.minmax.nulls[off]);
off++;
}
/* and push the final comparison */
ExprEvalPushStep(state, &scratch);
break;
}
case T_SQLValueFunction:
{
SQLValueFunction *svf = (SQLValueFunction *) node;
scratch.opcode = EEOP_SQLVALUEFUNCTION;
scratch.d.sqlvaluefunction.svf = svf;
ExprEvalPushStep(state, &scratch);
break;
}
case T_XmlExpr:
{
XmlExpr *xexpr = (XmlExpr *) node;
int nnamed = list_length(xexpr->named_args);
int nargs = list_length(xexpr->args);
int off;
ListCell *arg;
scratch.opcode = EEOP_XMLEXPR;
scratch.d.xmlexpr.xexpr = xexpr;
/* allocate space for storing all the arguments */
if (nnamed)
{
scratch.d.xmlexpr.named_argvalue =
(Datum *) palloc(sizeof(Datum) * nnamed);
scratch.d.xmlexpr.named_argnull =
(bool *) palloc(sizeof(bool) * nnamed);
}
else
{
scratch.d.xmlexpr.named_argvalue = NULL;
scratch.d.xmlexpr.named_argnull = NULL;
}
if (nargs)
{
scratch.d.xmlexpr.argvalue =
(Datum *) palloc(sizeof(Datum) * nargs);
scratch.d.xmlexpr.argnull =
(bool *) palloc(sizeof(bool) * nargs);
}
else
{
scratch.d.xmlexpr.argvalue = NULL;
scratch.d.xmlexpr.argnull = NULL;
}
/* prepare argument execution */
off = 0;
foreach(arg, xexpr->named_args)
{
Expr *e = (Expr *) lfirst(arg);
ExecInitExprRec(e, state,
&scratch.d.xmlexpr.named_argvalue[off],
&scratch.d.xmlexpr.named_argnull[off]);
off++;
}
off = 0;
foreach(arg, xexpr->args)
{
Expr *e = (Expr *) lfirst(arg);
ExecInitExprRec(e, state,
&scratch.d.xmlexpr.argvalue[off],
&scratch.d.xmlexpr.argnull[off]);
off++;
}
/* and evaluate the actual XML expression */
ExprEvalPushStep(state, &scratch);
break;
}
case T_JsonValueExpr:
{
JsonValueExpr *jve = (JsonValueExpr *) node;
Assert(jve->raw_expr != NULL);
ExecInitExprRec(jve->raw_expr, state, resv, resnull);
Assert(jve->formatted_expr != NULL);
ExecInitExprRec(jve->formatted_expr, state, resv, resnull);
break;
}
case T_JsonConstructorExpr:
{
JsonConstructorExpr *ctor = (JsonConstructorExpr *) node;
List *args = ctor->args;
ListCell *lc;
int nargs = list_length(args);
int argno = 0;
if (ctor->func)
{
ExecInitExprRec(ctor->func, state, resv, resnull);
}
else if ((ctor->type == JSCTOR_JSON_PARSE && !ctor->unique) ||
ctor->type == JSCTOR_JSON_SERIALIZE)
{
/* Use the value of the first argument as result */
ExecInitExprRec(linitial(args), state, resv, resnull);
}
else
{
JsonConstructorExprState *jcstate;
jcstate = palloc0(sizeof(JsonConstructorExprState));
scratch.opcode = EEOP_JSON_CONSTRUCTOR;
scratch.d.json_constructor.jcstate = jcstate;
jcstate->constructor = ctor;
jcstate->arg_values = (Datum *) palloc(sizeof(Datum) * nargs);
jcstate->arg_nulls = (bool *) palloc(sizeof(bool) * nargs);
jcstate->arg_types = (Oid *) palloc(sizeof(Oid) * nargs);
jcstate->nargs = nargs;
foreach(lc, args)
{
Expr *arg = (Expr *) lfirst(lc);
jcstate->arg_types[argno] = exprType((Node *) arg);
if (IsA(arg, Const))
{
/* Don't evaluate const arguments every round */
Const *con = (Const *) arg;
jcstate->arg_values[argno] = con->constvalue;
jcstate->arg_nulls[argno] = con->constisnull;
}
else
{
ExecInitExprRec(arg, state,
&jcstate->arg_values[argno],
&jcstate->arg_nulls[argno]);
}
argno++;
}
/* prepare type cache for datum_to_json[b]() */
if (ctor->type == JSCTOR_JSON_SCALAR)
{
bool is_jsonb =
ctor->returning->format->format_type == JS_FORMAT_JSONB;
jcstate->arg_type_cache =
palloc(sizeof(*jcstate->arg_type_cache) * nargs);
for (int i = 0; i < nargs; i++)
{
JsonTypeCategory category;
Oid outfuncid;
Oid typid = jcstate->arg_types[i];
json_categorize_type(typid, is_jsonb,
&category, &outfuncid);
jcstate->arg_type_cache[i].outfuncid = outfuncid;
jcstate->arg_type_cache[i].category = (int) category;
}
}
ExprEvalPushStep(state, &scratch);
}
if (ctor->coercion)
{
Datum *innermost_caseval = state->innermost_caseval;
bool *innermost_isnull = state->innermost_casenull;
state->innermost_caseval = resv;
state->innermost_casenull = resnull;
ExecInitExprRec(ctor->coercion, state, resv, resnull);
state->innermost_caseval = innermost_caseval;
state->innermost_casenull = innermost_isnull;
}
}
break;
case T_JsonIsPredicate:
{
JsonIsPredicate *pred = (JsonIsPredicate *) node;
ExecInitExprRec((Expr *) pred->expr, state, resv, resnull);
scratch.opcode = EEOP_IS_JSON;
scratch.d.is_json.pred = pred;
ExprEvalPushStep(state, &scratch);
break;
}
case T_JsonExpr:
{
JsonExpr *jsexpr = castNode(JsonExpr, node);
/*
* No need to initialize a full JsonExprState For
* JSON_TABLE(), because the upstream caller tfuncFetchRows()
* is only interested in the value of formatted_expr.
*/
if (jsexpr->op == JSON_TABLE_OP)
ExecInitExprRec((Expr *) jsexpr->formatted_expr, state,
resv, resnull);
else
ExecInitJsonExpr(jsexpr, state, resv, resnull, &scratch);
break;
}
case T_NullTest:
{
NullTest *ntest = (NullTest *) node;
if (ntest->nulltesttype == IS_NULL)
{
if (ntest->argisrow)
scratch.opcode = EEOP_NULLTEST_ROWISNULL;
else
scratch.opcode = EEOP_NULLTEST_ISNULL;
}
else if (ntest->nulltesttype == IS_NOT_NULL)
{
if (ntest->argisrow)
scratch.opcode = EEOP_NULLTEST_ROWISNOTNULL;
else
scratch.opcode = EEOP_NULLTEST_ISNOTNULL;
}
else
{
elog(ERROR, "unrecognized nulltesttype: %d",
(int) ntest->nulltesttype);
}
/* initialize cache in case it's a row test */
scratch.d.nulltest_row.rowcache.cacheptr = NULL;
/* first evaluate argument into result variable */
ExecInitExprRec(ntest->arg, state,
resv, resnull);
/* then push the test of that argument */
ExprEvalPushStep(state, &scratch);
break;
}
case T_BooleanTest:
{
BooleanTest *btest = (BooleanTest *) node;
/*
* Evaluate argument, directly into result datum. That's ok,
* because resv/resnull is definitely not used anywhere else,
* and will get overwritten by the below EEOP_BOOLTEST_IS_*
* step.
*/
ExecInitExprRec(btest->arg, state, resv, resnull);
switch (btest->booltesttype)
{
case IS_TRUE:
scratch.opcode = EEOP_BOOLTEST_IS_TRUE;
break;
case IS_NOT_TRUE:
scratch.opcode = EEOP_BOOLTEST_IS_NOT_TRUE;
break;
case IS_FALSE:
scratch.opcode = EEOP_BOOLTEST_IS_FALSE;
break;
case IS_NOT_FALSE:
scratch.opcode = EEOP_BOOLTEST_IS_NOT_FALSE;
break;
case IS_UNKNOWN:
/* Same as scalar IS NULL test */
scratch.opcode = EEOP_NULLTEST_ISNULL;
break;
case IS_NOT_UNKNOWN:
/* Same as scalar IS NOT NULL test */
scratch.opcode = EEOP_NULLTEST_ISNOTNULL;
break;
default:
elog(ERROR, "unrecognized booltesttype: %d",
(int) btest->booltesttype);
}
ExprEvalPushStep(state, &scratch);
break;
}
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
ExecInitCoerceToDomain(&scratch, ctest, state,
resv, resnull);
break;
}
case T_CoerceToDomainValue:
{
/*
* Read from location identified by innermost_domainval. Note
* that innermost_domainval could be NULL, if we're compiling
* a standalone domain check rather than one embedded in a
* larger expression. In that case we must read from
* econtext->domainValue_datum. We'll take care of that
* scenario at runtime.
*/
scratch.opcode = EEOP_DOMAIN_TESTVAL;
/* we share instruction union variant with case testval */
scratch.d.casetest.value = state->innermost_domainval;
scratch.d.casetest.isnull = state->innermost_domainnull;
ExprEvalPushStep(state, &scratch);
break;
}
case T_CurrentOfExpr:
{
scratch.opcode = EEOP_CURRENTOFEXPR;
ExprEvalPushStep(state, &scratch);
break;
}
case T_NextValueExpr:
{
NextValueExpr *nve = (NextValueExpr *) node;
scratch.opcode = EEOP_NEXTVALUEEXPR;
scratch.d.nextvalueexpr.seqid = nve->seqid;
scratch.d.nextvalueexpr.seqtypid = nve->typeId;
ExprEvalPushStep(state, &scratch);
break;
}
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
break;
}
}
我将为 ExecInitExprRec
函数中的所有 case
分支创建一个表格,汇总每种节点类型(NodeTag
)对应的处理情况,基于提供的代码片段。表格将列出每个 case
的节点类型、描述、操作码(opcode
)或主要操作,以及通俗的解释,结合 SQL 查询 SELECT name, age + 10 AS new_age FROM users WHERE age > 30
或其他相关示例(如 SUM(age)
或 CASE
语句)来阐明其作用。表格将保持简介、易读,并以通俗的方式描述功能。
表格:ExecInitExprRec
中 case
分支汇总
节点类型 (NodeTag) | 描述 | 操作码 (opcode) 或主要操作 | 通俗解释(以 SQL 为例) |
---|---|---|---|
T_Var | 字段(变量),如表中的列 | EEOP_INNER_VAR , EEOP_OUTER_VAR , EEOP_SCAN_VAR , EEOP_INNER_SYSVAR , EEOP_OUTER_SYSVAR , EEOP_SCAN_SYSVAR , 或调用 ExecInitWholeRowVar |
处理 SQL 中的列,如 age 。根据字段来源(普通表、内连接表、外连接表、系统列或整行引用),生成指令“从表中取 age 值”。例如,age + 10 中的 age 会生成读取列的指令。 |
T_Const | 常量,如数字或字符串 | EEOP_CONST |
处理 SQL 中的常量,如 10 或 'John' 。生成指令“直接使用值 10”并记录是否为 NULL。例如,age + 10 中的 10 会生成保存常量的指令。 |
T_Param | 参数,如查询中的占位符 | EEOP_PARAM_EXEC , EEOP_PARAM_EXTERN , 或调用 paramCompile |
处理 SQL 中的参数,如 WHERE age > $1 中的 $1 。生成指令从查询参数中取值,或者通过外部参数钩子处理。例如,$1 可能是用户输入的 30 。 |
T_Aggref | 聚合函数,如 SUM , COUNT |
EEOP_AGGREF |
处理 SQL 中的聚合函数,如 SUM(age) 。将聚合函数添加到父 AggState 并生成调用聚合的指令。确保在聚合计划中正确处理,例如 SELECT SUM(age) FROM users 。 |
T_GroupingFunc | 分组函数,如 GROUPING |
EEOP_GROUPING_FUNC |
处理 SQL 中的 GROUPING 函数,用于 GROUP BY 中的分组集。生成指令计算分组标识,例如 GROUP BY CUBE(age, name) 中的 GROUPING(age) 。 |
T_WindowFunc | 窗口函数,如 ROW_NUMBER |
EEOP_WINDOW_FUNC |
处理 SQL 中的窗口函数,如 ROW_NUMBER() OVER (PARTITION BY name) 。初始化窗口函数状态并添加到父 WindowAggState ,生成调用窗口函数的指令。 |
T_MergeSupportFunc | MERGE 语句的支持函数 | EEOP_MERGE_SUPPORT_FUNC |
处理 SQL MERGE 语句中的支持函数。生成调用相关函数的指令,确保在 MERGE 计划中正确执行。 |
T_SubscriptingRef | 数组或复合类型的下标引用 | 调用 ExecInitSubscriptingRef |
处理 SQL 中的数组或复合类型访问,如 array[1] 。生成指令访问数组元素或复合类型字段。 |
T_FuncExpr | 普通函数调用 | 调用 ExecInitFunc |
处理 SQL 中的函数,如 UPPER(name) 。生成调用函数的指令,处理函数参数和结果。 |
T_OpExpr | 运算符表达式,如 + , > |
调用 ExecInitFunc |
处理 SQL 中的运算符,如 age + 10 或 age > 30 。生成调用运算符函数的指令,例如“加 10”或“比较大于 30”。 |
T_DistinctExpr | DISTINCT ON 表达式 |
EEOP_DISTINCT |
处理 SQL 中的 DISTINCT 比较,如 SELECT DISTINCT ON (name) ... 。生成指令比较是否相等,类似 = 运算符。 |
T_NullIfExpr | NULLIF 表达式 |
EEOP_NULLIF |
处理 SQL 中的 NULLIF(name, 'John') 。生成指令比较参数,如果相等返回 NULL,否则返回第一个参数。 |
T_ScalarArrayOpExpr | 数组操作,如 IN |
EEOP_SCALARARRAYOP , EEOP_HASHED_SCALARARRAYOP |
处理 SQL 中的数组操作,如 age IN (30, 40) 。生成指令比较值是否在数组中,支持哈希优化以提高性能。 |
T_BoolExpr | 布尔表达式,如 AND , OR , NOT |
EEOP_BOOL_AND_STEP* , EEOP_BOOL_OR_STEP* , EEOP_BOOL_NOT_STEP |
处理 SQL 中的逻辑运算,如 age > 30 AND name = 'John' 。生成短路求值的指令,例如“如果第一个条件为假,跳过后续”。 |
T_SubPlan | 子查询 | EEOP_CONST (MULTIEXPR), 或调用 ExecInitSubPlanExpr |
处理 SQL 中的子查询,如 WHERE id IN (SELECT ...) 。对于 MULTIEXPR 类型返回空值,其他类型生成执行子查询的指令。 |
T_FieldSelect | 从复合类型选择字段 | EEOP_FIELDSELECT |
处理 SQL 中的复合类型字段访问,如 user.address.city 。生成指令从复合类型中提取指定字段。 |
T_FieldStore | 修改复合类型字段 | EEOP_FIELDSTORE_DEFORM , EEOP_FIELDSTORE_FORM |
处理 SQL 中的复合类型字段更新,如 user.address = ROW('New York', ...) 。生成指令分解和重组复合类型。 |
T_RelabelType | 类型转换(无运行时操作) | 无操作码,直接递归处理子表达式 | 处理 SQL 中的显式类型转换,如 CAST(age AS float) 。仅递归处理子表达式,无需额外指令。 |
T_CoerceViaIO | 通过输入/输出函数进行类型转换 | EEOP_IOCOERCE , EEOP_IOCOERCE_SAFE |
处理 SQL 中的类型转换,如 age::text 。生成指令调用类型的输入/输出函数进行转换。 |
T_ArrayCoerceExpr | 数组元素类型转换 | EEOP_ARRAYCOERCE |
处理 SQL 中的数组元素转换,如 ARRAY[1, 2]::text[] 。生成指令对数组元素逐个转换。 |
T_ConvertRowtypeExpr | 行类型转换 | EEOP_CONVERT_ROWTYPE |
处理 SQL 中的行类型转换,如 ROW(age, name)::new_type 。生成指令将一种行类型转换为另一种。 |
T_CaseExpr | CASE 表达式 |
EEOP_JUMP_IF_NOT_TRUE , EEOP_JUMP , EEOP_MAKE_READONLY |
处理 SQL 中的 CASE WHEN age > 30 THEN 'Adult' ELSE 'Young' END 。生成条件跳转和结果选择的指令。 |
T_CaseTestExpr | CASE 测试值 |
EEOP_CASE_TESTVAL |
处理 CASE 语句中的测试值,如 CASE age WHEN 30 THEN ... 。生成指令读取 CASE 的测试值。 |
T_ArrayExpr | 数组构造 | EEOP_ARRAYEXPR |
处理 SQL 中的数组构造,如 ARRAY[1, 2, 3] 。生成指令计算数组元素并构造数组。 |
T_RowExpr | 行构造 | EEOP_ROW |
处理 SQL 中的行构造,如 ROW(age, name) 。生成指令计算各字段并构造行值。 |
T_RowCompareExpr | 行比较 | EEOP_ROWCOMPARE_STEP , EEOP_ROWCOMPARE_FINAL |
处理 SQL 中的行比较,如 ROW(age, name) > ROW(30, 'John') 。生成指令逐字段比较并得出结果。 |
T_CoalesceExpr | COALESCE 表达式 |
EEOP_JUMP_IF_NOT_NULL |
处理 SQL 中的 COALESCE(age, 0) 。生成指令返回第一个非 NULL 值。 |
T_MinMaxExpr | GREATEST , LEAST 表达式 |
EEOP_MINMAX |
处理 SQL 中的 GREATEST(age, 18) 或 LEAST(age, 65) 。生成指令比较参数并返回最大/最小值。 |
T_SQLValueFunction | SQL 值函数,如 CURRENT_DATE |
EEOP_SQLVALUEFUNCTION |
处理 SQL 中的值函数,如 CURRENT_DATE 或 CURRENT_USER 。生成指令调用对应的值函数。 |
T_XmlExpr | XML 表达式 | EEOP_XMLEXPR |
处理 SQL 中的 XML 操作,如 XMLELEMENT(name, 'data') 。生成指令计算参数并构造 XML。 |
T_JsonValueExpr | JSON 值表达式 | 递归处理 raw_expr 和 formatted_expr |
处理 SQL 中的 JSON 值表达式,如 JSON_VALUE(json_col, '$.path') 。递归处理原始和格式化表达式。 |
T_JsonConstructorExpr | JSON 构造表达式 | EEOP_JSON_CONSTRUCTOR , 或递归处理子表达式 |
处理 SQL 中的 JSON 构造,如 JSON_OBJECT('key': value) 。生成指令构造 JSON 数据,或处理特定子表达式。 |
T_JsonIsPredicate | JSON 谓词,如 IS JSON |
EEOP_IS_JSON |
处理 SQL 中的 json_col IS JSON 。生成指令检查值是否为 JSON。 |
T_JsonExpr | JSON 表达式,如 JSON_TABLE |
EEOP_JSON_CONSTRUCTOR , 或递归处理 formatted_expr |
处理 SQL 中的 JSON_TABLE 或其他 JSON 表达式。生成指令处理 JSON 数据或递归处理格式化表达式。 |
T_NullTest | NULL 测试,如 IS NULL |
EEOP_NULLTEST_ISNULL , EEOP_NULLTEST_ISNOTNULL , EEOP_NULLTEST_ROWISNULL , EEOP_NULLTEST_ROWISNOTNULL |
处理 SQL 中的 age IS NULL 或 age IS NOT NULL 。生成指令检查值或行是否为 NULL。 |
T_BooleanTest | 布尔测试,如 IS TRUE |
EEOP_BOOLTEST_IS_TRUE , EEOP_BOOLTEST_IS_NOT_TRUE , EEOP_BOOLTEST_IS_FALSE , EEOP_BOOLTEST_IS_NOT_FALSE , 或重用 NULL 测试 |
处理 SQL 中的 condition IS TRUE 或 condition IS FALSE 。生成指令检查布尔值状态。 |
T_CoerceToDomain | 域类型约束 | 调用 ExecInitCoerceToDomain |
处理 SQL 中的域类型检查,如 age::mydomain 。生成指令验证值是否符合域约束。 |
T_CoerceToDomainValue | 域值引用 | EEOP_DOMAIN_TESTVAL |
处理域类型中的值引用。生成指令读取域值,类似 CASE 测试值。 |
T_CurrentOfExpr | CURRENT OF 表达式 |
EEOP_CURRENTOFEXPR |
处理 SQL 中的 WHERE CURRENT OF cursor 。生成指令处理游标当前位置。 |
T_NextValueExpr | 序列函数,如 nextval |
EEOP_NEXTVALUEEXPR |
处理 SQL 中的 nextval('seq') 。生成指令获取序列的下一个值。 |
default | 未知节点类型 | 抛出错误 (elog(ERROR) ) |
如果遇到无法识别的节点类型,抛出错误,表明计划有误。 |
说明
- 表格内容:每个
case
对应一种表达式节点类型,描述其功能、生成的主要操作码(或调用的函数),并用SQL
示例解释其作用。操作码(如EEOP_*
)是PostgreSQL
执行引擎的指令,告诉系统如何处理表达式。 - SQL 示例:基于
SELECT name, age + 10 AS new_age FROM users WHERE age > 30
,并扩展到其他常见SQL
构造(如SUM(age)
,CASE
,JSON_OBJECT
等),以便直观理解。 - 通俗解释:每个节点的处理都被简化为“生成什么指令”“做什么事”,例如“从表取值”“计算加法”“检查是否
NULL
”等。 - 完整性:表格覆盖了代码中所有的
case
分支,基于提供的代码片段。每个分支都专注于为特定表达式类型生成执行步骤,确保SQL
查询高效执行。
为什么重要?
ExecInitExprRec
是 PostgreSQL
将 SQL
表达式的抽象树转换为可执行指令的核心函数。每个 case
处理一种表达式类型(如字段、常量、函数、逻辑运算等),生成对应的指令,确保查询引擎能正确计算结果。表格清晰展示了每种节点类型的处理逻辑,帮助理解 PostgreSQL
如何将复杂 SQL
翻译为可执行步骤。
ExprEvalPushStep
ExprEvalPushStep
是 PostgreSQL
查询执行引擎中用于将单个表达式执行步骤(ExprEvalStep
)添加到 ExprState
的步骤列表(steps
)的函数。它的作用就像把一条具体的执行指令(如“从表中取 age
值”或“计算 age + 10
”)写入一个“执行计划笔记本”。
它会检查当前“笔记本”是否有空间,如果没有就初始化或扩展空间,然后将指令写入,确保表达式(如 age + 10
或 age > 30
)的执行步骤按顺序保存,供后续查询执行使用。这个函数是 ExecInitExprRec
等函数的辅助工具,确保所有生成的指令都能被正确记录。
/*
* ExprEvalPushStep: 向 ExprState->steps 添加一个表达式执行步骤
* 类似于把一条执行指令(如“从表取 age 值”)添加到 SQL 表达式的执行计划书中。
* 注意:这个过程可能会重新分配 steps 数组的内存,所以在构建表达式时不能直接使用 steps 数组的指针。
*/
void
ExprEvalPushStep(ExprState *es, const ExprEvalStep *s)
{
/* 如果 steps 数组尚未分配,初始化它 */
if (es->steps_alloc == 0)
{
es->steps_alloc = 16; // 分配 16 个槽位给 steps 数组,像准备一个能存 16 条指令的笔记本
es->steps = palloc(sizeof(ExprEvalStep) * es->steps_alloc); // 创建 steps 数组
// SQL 例子:为“age + 10”的执行计划创建一个空的“指令笔记本”。
}
/* 如果 steps 数组已满,扩展它 */
else if (es->steps_alloc == es->steps_len)
{
es->steps_alloc *= 2; // 将槽位数翻倍,比如从 16 变成 32
es->steps = repalloc(es->steps, sizeof(ExprEvalStep) * es->steps_alloc); // 重新分配更大的数组
// SQL 例子:如果“age + 10”和“age > 30”的指令太多,笔记本满了,就换一个更大的笔记本。
}
/* 将新的执行步骤复制到 steps 数组 */
memcpy(&es->steps[es->steps_len++], s, sizeof(ExprEvalStep)); // 把指令(如“取 age 值”)写入笔记本,并更新指令计数
// SQL 例子:把“从 users 表取 age”或“加 10”的指令写入计划书,准备执行。
}
主要流程(以 SQL 为例)
假设我们处理 SQL
查询 SELECT name, age + 10 AS new_age FROM users WHERE age > 30
,其中表达式 age + 10
和 age > 30
需要生成执行步骤:
- 检查是否需要初始化
steps
数组:- 代码:
if (es->steps_alloc == 0)
- 功能:如果
steps
数组(存储指令的“笔记本”)还没创建,就分配一个能存16
条指令的空间。 SQL
例子:为age + 10
的执行计划创建一个空的“指令笔记本”,准备记录如何计算age + 10
。
- 代码:
检查
steps
数组是否已满并扩展:- 代码:
else if (es->steps_alloc == es->steps_len)
- 功能:如果“笔记本”满了(已有指令数等于分配的槽位数),将槽位数翻倍并重新分配更大的空间。
SQL
例子:如果age + 10
和age > 30
的指令太多(比如超过16
条),就把“笔记本”扩展到32
条指令的容量。
- 代码:
添加新步骤到
steps
数组:- 代码:
memcpy(&es->steps[es->steps_len++], s, sizeof(ExprEvalStep))
- 功能:将新的执行步骤(如“取
age
值”或“加10
”)复制到“笔记本”中,并增加指令计数。 SQL
例子:把“从users
表取age
”或“加10
”的指令写入“笔记本”,记录在age + 10
的执行计划中。
- 代码:
ExecReadyExpr
ExecReadyExpr
是 PostgreSQL
查询执行引擎中为表达式状态(ExprState
)做最终执行准备的函数。它的作用就像检查 SQL
表达式的“执行计划书”(如 age + 10
的计算步骤)是否准备好运行。它首先尝试使用 JIT
(即时编译)来优化执行速度,如果 JIT
不可用或失败,则调用 ExecReadyInterpretedExpr
准备解释执行方式。
这个函数是执行表达式的最后一步,确保 ExprState
可以被 ExecEvalExpr
正确执行。它设计为可扩展,未来可能支持更多执行方式(如 JIT
或其他优化)。
/*
* ExecReadyExpr: 为已编译的表达式准备执行
* 类似于检查 SQL 表达式的“执行计划书”是否准备好,确保可以运行。
* 目前只调用解释执行的准备函数,但未来可能支持其他执行方式(如 JIT 编译)。
* 因此,应使用此函数,而不是直接调用 ExecReadyInterpretedExpr。
*/
static void
ExecReadyExpr(ExprState *state)
{
/* 尝试使用 JIT 编译表达式,如果成功则直接返回 */
if (jit_compile_expr(state))
return;
// SQL 例子:检查是否可以用 JIT(即时编译)加速“age + 10”的执行,如果可以就用更快的方式。
/* 如果 JIT 不可用,准备解释执行 */
ExecReadyInterpretedExpr(state);
// SQL 例子:如果 JIT 不行,就按常规方式准备“age + 10”的执行计划。
}
ExecReadyInterpretedExpr
ExecReadyInterpretedExpr
是为解释执行方式准备 ExprState
的函数,类似于为 SQL
表达式的“执行计划书”做最后检查和优化。它确保解释器环境已初始化,验证计划书有效(有步骤且以 EEOP_DONE
结尾),并为简单表达式选择快速执行路径(如直接取字段或常量)。如果启用了直接线程化,它还会优化指令跳转地址以提高性能。对于复杂表达式,它设置标准的解释执行函数。这个函数确保 age + 10
或 age > 30
的执行计划可以高效、正确地运行。
/*
* ExecReadyInterpretedExpr: 为解释执行准备 ExprState
* 类似于为 SQL 表达式的“执行计划书”做最后检查和优化,确保可以按步骤运行。
*/
void
ExecReadyInterpretedExpr(ExprState *state)
{
/* 确保解释器的初始化工作已完成 */
ExecInitInterpreter();
// SQL 例子:确保计算“age + 10”的解释器环境已设置好,比如内存和基本函数。
/* 检查表达式是否有效 */
Assert(state->steps_len >= 1); // 确保计划书至少有一条指令
Assert(state->steps[state->steps_len - 1].opcode == EEOP_DONE); // 确保最后一条指令是“结束”
// SQL 例子:检查“age + 10”的计划书不为空,且以“完成”指令结尾。
/* 如果已初始化,跳过重复工作 */
if (state->flags & EEO_FLAG_INTERPRETER_INITIALIZED)
return;
// SQL 例子:如果“age + 10”的计划书已经准备好,就不重复处理。
/* 设置初始执行函数,用于验证属性和变量是否匹配 */
state->evalfunc = ExecInterpExprStillValid;
// SQL 例子:设置一个检查函数,确保“age”列在执行时仍然有效(比如表结构没变)。
/* 确保未启用直接线程化 */
Assert((state->flags & EEO_FLAG_DIRECT_THREADED) == 0);
// SQL 例子:确认“age + 10”的计划书还没用高级优化(直接线程化)。
/* 标记已初始化,简化后续处理 */
state->flags |= EEO_FLAG_INTERPRETER_INITIALIZED;
// SQL 例子:标记“age + 10”的计划书已准备好,避免重复初始化。
/* 为简单表达式选择快速执行路径 */
if (state->steps_len == 5)
{
ExprEvalOp step0 = state->steps[0].opcode;
ExprEvalOp step1 = state->steps[1].opcode;
ExprEvalOp step2 = state->steps[2].opcode;
ExprEvalOp step3 = state->steps[3].opcode;
if (step0 == EEOP_INNER_FETCHSOME &&
step1 == EEOP_HASHDATUM_SET_INITVAL &&
step2 == EEOP_INNER_VAR &&
step3 == EEOP_HASHDATUM_NEXT32)
{
state->evalfunc_private = (void *) ExecJustHashInnerVarWithIV;
return;
}
}
else if (state->steps_len == 4)
{
/* 类似处理,检查特定步骤模式,设置快速函数 */
// SQL 例子:如果“age”是简单内表字段,选择快速函数直接取值。
}
else if (state->steps_len == 3)
{
/* 检查简单模式,如取字段或赋值 */
// SQL 例子:如果只是“从 users 表取 age”,用快速函数直接处理。
}
else if (state->steps_len == 2)
{
ExprEvalOp step0 = state->steps[0].opcode;
if (step0 == EEOP_CONST)
{
state->evalfunc_private = ExecJustConst;
return;
}
// SQL 例子:如果表达式只是常量“10”,直接用快速函数返回常量值。
}
// SQL 例子:检查“age + 10”的指令数,如果是简单模式(如只取 age),用快速函数加速。
/* 如果启用了直接线程化,优化指令跳转地址 */
#if defined(EEO_USE_COMPUTED_GOTO)
for (int off = 0; off < state->steps_len; off++)
{
ExprEvalStep *op = &state->steps[off];
op->opcode = EEO_OPCODE(op->opcode); // 将操作码替换为跳转地址
}
state->flags |= EEO_FLAG_DIRECT_THREADED; // 标记已启用直接线程化
// SQL 例子:将“age + 10”的指令优化为直接跳转,提高执行速度。
#endif
/* 设置默认解释执行函数 */
state->evalfunc_private = ExecInterpExpr;
// SQL 例子:如果没有快速路径,就用标准函数逐条执行“age + 10”的指令。
}
我将为 ExecInterpExpr
函数中的所有 case
分支创建一个表格,汇总每种操作码(ExprEvalOp
)对应的处理情况,基于提供的代码片段。表格将列出每个 case
的操作码、描述、主要操作,以及通俗的解释,结合 SQL 查询 SELECT name, age + 10 AS new_age FROM users WHERE age > 30
或其他相关示例(如 SUM(age)
、CASE
语句)来阐明其作用。表格将保持简介、易读,并以通俗的方式描述功能。之后,我会用一段话描述该函数的主要功能。
ExecInterpExpr
ExecInterpExpr
是 PostgreSQL
查询执行引擎中负责解释执行表达式的核心函数。它通过逐条处理 ExprState
中的操作码(ExprEvalOp
),执行 SQL
表达式的每一步计算(如 age + 10
或 age > 30
)。函数使用一个跳转表(dispatch_table
)支持高效的指令分派,处理从简单字段获取(如 age
)到复杂操作(如 SUM(age)
、逻辑运算 AND
、JSON
处理)的各种表达式。它支持短路求值(如 AND
/OR
逻辑)、NULL
处理、类型转换等,确保表达式结果正确返回,同时通过内联简单操作和外联复杂操作优化性能。这个函数是查询执行的“执行者”,将 ExecInitExprRec
和 ExecReadyInterpretedExpr
准备的计划书转化为实际结果。
/*
* Evaluate expression identified by "state" in the execution context
* given by "econtext". *isnull is set to the is-null flag for the result,
* and the Datum value is the function result.
*
* As a special case, return the dispatch table's address if state is NULL.
* This is used by ExecInitInterpreter to set up the dispatch_table global.
* (Only applies when EEO_USE_COMPUTED_GOTO is defined.)
*/
static Datum
ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
{
ExprEvalStep *op;
TupleTableSlot *resultslot;
TupleTableSlot *innerslot;
TupleTableSlot *outerslot;
TupleTableSlot *scanslot;
/*
* This array has to be in the same order as enum ExprEvalOp.
*/
#if defined(EEO_USE_COMPUTED_GOTO)
static const void *const dispatch_table[] = {
&&CASE_EEOP_DONE,
&&CASE_EEOP_INNER_FETCHSOME,
&&CASE_EEOP_OUTER_FETCHSOME,
&&CASE_EEOP_SCAN_FETCHSOME,
&&CASE_EEOP_INNER_VAR,
&&CASE_EEOP_OUTER_VAR,
&&CASE_EEOP_SCAN_VAR,
&&CASE_EEOP_INNER_SYSVAR,
&&CASE_EEOP_OUTER_SYSVAR,
&&CASE_EEOP_SCAN_SYSVAR,
&&CASE_EEOP_WHOLEROW,
&&CASE_EEOP_ASSIGN_INNER_VAR,
&&CASE_EEOP_ASSIGN_OUTER_VAR,
&&CASE_EEOP_ASSIGN_SCAN_VAR,
&&CASE_EEOP_ASSIGN_TMP,
&&CASE_EEOP_ASSIGN_TMP_MAKE_RO,
&&CASE_EEOP_CONST,
&&CASE_EEOP_FUNCEXPR,
&&CASE_EEOP_FUNCEXPR_STRICT,
&&CASE_EEOP_FUNCEXPR_FUSAGE,
&&CASE_EEOP_FUNCEXPR_STRICT_FUSAGE,
&&CASE_EEOP_BOOL_AND_STEP_FIRST,
&&CASE_EEOP_BOOL_AND_STEP,
&&CASE_EEOP_BOOL_AND_STEP_LAST,
&&CASE_EEOP_BOOL_OR_STEP_FIRST,
&&CASE_EEOP_BOOL_OR_STEP,
&&CASE_EEOP_BOOL_OR_STEP_LAST,
&&CASE_EEOP_BOOL_NOT_STEP,
&&CASE_EEOP_QUAL,
&&CASE_EEOP_JUMP,
&&CASE_EEOP_JUMP_IF_NULL,
&&CASE_EEOP_JUMP_IF_NOT_NULL,
&&CASE_EEOP_JUMP_IF_NOT_TRUE,
&&CASE_EEOP_NULLTEST_ISNULL,
&&CASE_EEOP_NULLTEST_ISNOTNULL,
&&CASE_EEOP_NULLTEST_ROWISNULL,
&&CASE_EEOP_NULLTEST_ROWISNOTNULL,
&&CASE_EEOP_BOOLTEST_IS_TRUE,
&&CASE_EEOP_BOOLTEST_IS_NOT_TRUE,
&&CASE_EEOP_BOOLTEST_IS_FALSE,
&&CASE_EEOP_BOOLTEST_IS_NOT_FALSE,
&&CASE_EEOP_PARAM_EXEC,
&&CASE_EEOP_PARAM_EXTERN,
&&CASE_EEOP_PARAM_CALLBACK,
&&CASE_EEOP_PARAM_SET,
&&CASE_EEOP_CASE_TESTVAL,
&&CASE_EEOP_MAKE_READONLY,
&&CASE_EEOP_IOCOERCE,
&&CASE_EEOP_IOCOERCE_SAFE,
&&CASE_EEOP_DISTINCT,
&&CASE_EEOP_NOT_DISTINCT,
&&CASE_EEOP_NULLIF,
&&CASE_EEOP_SQLVALUEFUNCTION,
&&CASE_EEOP_CURRENTOFEXPR,
&&CASE_EEOP_NEXTVALUEEXPR,
&&CASE_EEOP_ARRAYEXPR,
&&CASE_EEOP_ARRAYCOERCE,
&&CASE_EEOP_ROW,
&&CASE_EEOP_ROWCOMPARE_STEP,
&&CASE_EEOP_ROWCOMPARE_FINAL,
&&CASE_EEOP_MINMAX,
&&CASE_EEOP_FIELDSELECT,
&&CASE_EEOP_FIELDSTORE_DEFORM,
&&CASE_EEOP_FIELDSTORE_FORM,
&&CASE_EEOP_SBSREF_SUBSCRIPTS,
&&CASE_EEOP_SBSREF_OLD,
&&CASE_EEOP_SBSREF_ASSIGN,
&&CASE_EEOP_SBSREF_FETCH,
&&CASE_EEOP_DOMAIN_TESTVAL,
&&CASE_EEOP_DOMAIN_NOTNULL,
&&CASE_EEOP_DOMAIN_CHECK,
&&CASE_EEOP_HASHDATUM_SET_INITVAL,
&&CASE_EEOP_HASHDATUM_FIRST,
&&CASE_EEOP_HASHDATUM_FIRST_STRICT,
&&CASE_EEOP_HASHDATUM_NEXT32,
&&CASE_EEOP_HASHDATUM_NEXT32_STRICT,
&&CASE_EEOP_CONVERT_ROWTYPE,
&&CASE_EEOP_SCALARARRAYOP,
&&CASE_EEOP_HASHED_SCALARARRAYOP,
&&CASE_EEOP_XMLEXPR,
&&CASE_EEOP_JSON_CONSTRUCTOR,
&&CASE_EEOP_IS_JSON,
&&CASE_EEOP_JSONEXPR_PATH,
&&CASE_EEOP_JSONEXPR_COERCION,
&&CASE_EEOP_JSONEXPR_COERCION_FINISH,
&&CASE_EEOP_AGGREF,
&&CASE_EEOP_GROUPING_FUNC,
&&CASE_EEOP_WINDOW_FUNC,
&&CASE_EEOP_MERGE_SUPPORT_FUNC,
&&CASE_EEOP_SUBPLAN,
&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
&&CASE_EEOP_AGG_DESERIALIZE,
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_NULLS,
&&CASE_EEOP_AGG_PLAIN_PERGROUP_NULLCHECK,
&&CASE_EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL,
&&CASE_EEOP_AGG_PLAIN_TRANS_STRICT_BYVAL,
&&CASE_EEOP_AGG_PLAIN_TRANS_BYVAL,
&&CASE_EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYREF,
&&CASE_EEOP_AGG_PLAIN_TRANS_STRICT_BYREF,
&&CASE_EEOP_AGG_PLAIN_TRANS_BYREF,
&&CASE_EEOP_AGG_PRESORTED_DISTINCT_SINGLE,
&&CASE_EEOP_AGG_PRESORTED_DISTINCT_MULTI,
&&CASE_EEOP_AGG_ORDERED_TRANS_DATUM,
&&CASE_EEOP_AGG_ORDERED_TRANS_TUPLE,
&&CASE_EEOP_LAST
};
StaticAssertDecl(lengthof(dispatch_table) == EEOP_LAST + 1,
"dispatch_table out of whack with ExprEvalOp");
if (unlikely(state == NULL))
return PointerGetDatum(dispatch_table);
#else
Assert(state != NULL);
#endif /* EEO_USE_COMPUTED_GOTO */
/* setup state */
op = state->steps;
resultslot = state->resultslot;
innerslot = econtext->ecxt_innertuple;
outerslot = econtext->ecxt_outertuple;
scanslot = econtext->ecxt_scantuple;
#if defined(EEO_USE_COMPUTED_GOTO)
EEO_DISPATCH();
#endif
EEO_SWITCH()
{
EEO_CASE(EEOP_DONE)
{
goto out;
}
EEO_CASE(EEOP_INNER_FETCHSOME)
{
CheckOpSlotCompatibility(op, innerslot);
slot_getsomeattrs(innerslot, op->d.fetch.last_var);
EEO_NEXT();
}
EEO_CASE(EEOP_OUTER_FETCHSOME)
{
CheckOpSlotCompatibility(op, outerslot);
slot_getsomeattrs(outerslot, op->d.fetch.last_var);
EEO_NEXT();
}
EEO_CASE(EEOP_SCAN_FETCHSOME)
{
CheckOpSlotCompatibility(op, scanslot);
slot_getsomeattrs(scanslot, op->d.fetch.last_var);
EEO_NEXT();
}
EEO_CASE(EEOP_INNER_VAR)
{
int attnum = op->d.var.attnum;
/*
* Since we already extracted all referenced columns from the
* tuple with a FETCHSOME step, we can just grab the value
* directly out of the slot's decomposed-data arrays. But let's
* have an Assert to check that that did happen.
*/
Assert(attnum >= 0 && attnum < innerslot->tts_nvalid);
*op->resvalue = innerslot->tts_values[attnum];
*op->resnull = innerslot->tts_isnull[attnum];
EEO_NEXT();
}
EEO_CASE(EEOP_OUTER_VAR)
{
int attnum = op->d.var.attnum;
/* See EEOP_INNER_VAR comments */
Assert(attnum >= 0 && attnum < outerslot->tts_nvalid);
*op->resvalue = outerslot->tts_values[attnum];
*op->resnull = outerslot->tts_isnull[attnum];
EEO_NEXT();
}
EEO_CASE(EEOP_SCAN_VAR)
{
int attnum = op->d.var.attnum;
/* See EEOP_INNER_VAR comments */
Assert(attnum >= 0 && attnum < scanslot->tts_nvalid);
*op->resvalue = scanslot->tts_values[attnum];
*op->resnull = scanslot->tts_isnull[attnum];
EEO_NEXT();
}
EEO_CASE(EEOP_INNER_SYSVAR)
{
ExecEvalSysVar(state, op, econtext, innerslot);
EEO_NEXT();
}
EEO_CASE(EEOP_OUTER_SYSVAR)
{
ExecEvalSysVar(state, op, econtext, outerslot);
EEO_NEXT();
}
EEO_CASE(EEOP_SCAN_SYSVAR)
{
ExecEvalSysVar(state, op, econtext, scanslot);
EEO_NEXT();
}
EEO_CASE(EEOP_WHOLEROW)
{
/* too complex for an inline implementation */
ExecEvalWholeRowVar(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_ASSIGN_INNER_VAR)
{
int resultnum = op->d.assign_var.resultnum;
int attnum = op->d.assign_var.attnum;
/*
* We do not need CheckVarSlotCompatibility here; that was taken
* care of at compilation time. But see EEOP_INNER_VAR comments.
*/
Assert(attnum >= 0 && attnum < innerslot->tts_nvalid);
Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
resultslot->tts_values[resultnum] = innerslot->tts_values[attnum];
resultslot->tts_isnull[resultnum] = innerslot->tts_isnull[attnum];
EEO_NEXT();
}
EEO_CASE(EEOP_ASSIGN_OUTER_VAR)
{
int resultnum = op->d.assign_var.resultnum;
int attnum = op->d.assign_var.attnum;
/*
* We do not need CheckVarSlotCompatibility here; that was taken
* care of at compilation time. But see EEOP_INNER_VAR comments.
*/
Assert(attnum >= 0 && attnum < outerslot->tts_nvalid);
Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
resultslot->tts_values[resultnum] = outerslot->tts_values[attnum];
resultslot->tts_isnull[resultnum] = outerslot->tts_isnull[attnum];
EEO_NEXT();
}
EEO_CASE(EEOP_ASSIGN_SCAN_VAR)
{
int resultnum = op->d.assign_var.resultnum;
int attnum = op->d.assign_var.attnum;
/*
* We do not need CheckVarSlotCompatibility here; that was taken
* care of at compilation time. But see EEOP_INNER_VAR comments.
*/
Assert(attnum >= 0 && attnum < scanslot->tts_nvalid);
Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
resultslot->tts_values[resultnum] = scanslot->tts_values[attnum];
resultslot->tts_isnull[resultnum] = scanslot->tts_isnull[attnum];
EEO_NEXT();
}
EEO_CASE(EEOP_ASSIGN_TMP)
{
int resultnum = op->d.assign_tmp.resultnum;
Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
resultslot->tts_values[resultnum] = state->resvalue;
resultslot->tts_isnull[resultnum] = state->resnull;
EEO_NEXT();
}
EEO_CASE(EEOP_ASSIGN_TMP_MAKE_RO)
{
int resultnum = op->d.assign_tmp.resultnum;
Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
resultslot->tts_isnull[resultnum] = state->resnull;
if (!resultslot->tts_isnull[resultnum])
resultslot->tts_values[resultnum] =
MakeExpandedObjectReadOnlyInternal(state->resvalue);
else
resultslot->tts_values[resultnum] = state->resvalue;
EEO_NEXT();
}
EEO_CASE(EEOP_CONST)
{
*op->resnull = op->d.constval.isnull;
*op->resvalue = op->d.constval.value;
EEO_NEXT();
}
/*
* Function-call implementations. Arguments have previously been
* evaluated directly into fcinfo->args.
*
* As both STRICT checks and function-usage are noticeable performance
* wise, and function calls are a very hot-path (they also back
* operators!), it's worth having so many separate opcodes.
*
* Note: the reason for using a temporary variable "d", here and in
* other places, is that some compilers think "*op->resvalue = f();"
* requires them to evaluate op->resvalue into a register before
* calling f(), just in case f() is able to modify op->resvalue
* somehow. The extra line of code can save a useless register spill
* and reload across the function call.
*/
EEO_CASE(EEOP_FUNCEXPR)
{
FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
Datum d;
fcinfo->isnull = false;
d = op->d.func.fn_addr(fcinfo);
*op->resvalue = d;
*op->resnull = fcinfo->isnull;
EEO_NEXT();
}
EEO_CASE(EEOP_FUNCEXPR_STRICT)
{
FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
NullableDatum *args = fcinfo->args;
int nargs = op->d.func.nargs;
Datum d;
/* strict function, so check for NULL args */
for (int argno = 0; argno < nargs; argno++)
{
if (args[argno].isnull)
{
*op->resnull = true;
goto strictfail;
}
}
fcinfo->isnull = false;
d = op->d.func.fn_addr(fcinfo);
*op->resvalue = d;
*op->resnull = fcinfo->isnull;
strictfail:
EEO_NEXT();
}
EEO_CASE(EEOP_FUNCEXPR_FUSAGE)
{
/* not common enough to inline */
ExecEvalFuncExprFusage(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_FUNCEXPR_STRICT_FUSAGE)
{
/* not common enough to inline */
ExecEvalFuncExprStrictFusage(state, op, econtext);
EEO_NEXT();
}
/*
* If any of its clauses is FALSE, an AND's result is FALSE regardless
* of the states of the rest of the clauses, so we can stop evaluating
* and return FALSE immediately. If none are FALSE and one or more is
* NULL, we return NULL; otherwise we return TRUE. This makes sense
* when you interpret NULL as "don't know": perhaps one of the "don't
* knows" would have been FALSE if we'd known its value. Only when
* all the inputs are known to be TRUE can we state confidently that
* the AND's result is TRUE.
*/
EEO_CASE(EEOP_BOOL_AND_STEP_FIRST)
{
*op->d.boolexpr.anynull = false;
/*
* EEOP_BOOL_AND_STEP_FIRST resets anynull, otherwise it's the
* same as EEOP_BOOL_AND_STEP - so fall through to that.
*/
/* FALL THROUGH */
}
EEO_CASE(EEOP_BOOL_AND_STEP)
{
if (*op->resnull)
{
*op->d.boolexpr.anynull = true;
}
else if (!DatumGetBool(*op->resvalue))
{
/* result is already set to FALSE, need not change it */
/* bail out early */
EEO_JUMP(op->d.boolexpr.jumpdone);
}
EEO_NEXT();
}
EEO_CASE(EEOP_BOOL_AND_STEP_LAST)
{
if (*op->resnull)
{
/* result is already set to NULL, need not change it */
}
else if (!DatumGetBool(*op->resvalue))
{
/* result is already set to FALSE, need not change it */
/*
* No point jumping early to jumpdone - would be same target
* (as this is the last argument to the AND expression),
* except more expensive.
*/
}
else if (*op->d.boolexpr.anynull)
{
*op->resvalue = (Datum) 0;
*op->resnull = true;
}
else
{
/* result is already set to TRUE, need not change it */
}
EEO_NEXT();
}
/*
* If any of its clauses is TRUE, an OR's result is TRUE regardless of
* the states of the rest of the clauses, so we can stop evaluating
* and return TRUE immediately. If none are TRUE and one or more is
* NULL, we return NULL; otherwise we return FALSE. This makes sense
* when you interpret NULL as "don't know": perhaps one of the "don't
* knows" would have been TRUE if we'd known its value. Only when all
* the inputs are known to be FALSE can we state confidently that the
* OR's result is FALSE.
*/
EEO_CASE(EEOP_BOOL_OR_STEP_FIRST)
{
*op->d.boolexpr.anynull = false;
/*
* EEOP_BOOL_OR_STEP_FIRST resets anynull, otherwise it's the same
* as EEOP_BOOL_OR_STEP - so fall through to that.
*/
/* FALL THROUGH */
}
EEO_CASE(EEOP_BOOL_OR_STEP)
{
if (*op->resnull)
{
*op->d.boolexpr.anynull = true;
}
else if (DatumGetBool(*op->resvalue))
{
/* result is already set to TRUE, need not change it */
/* bail out early */
EEO_JUMP(op->d.boolexpr.jumpdone);
}
EEO_NEXT();
}
EEO_CASE(EEOP_BOOL_OR_STEP_LAST)
{
if (*op->resnull)
{
/* result is already set to NULL, need not change it */
}
else if (DatumGetBool(*op->resvalue))
{
/* result is already set to TRUE, need not change it */
/*
* No point jumping to jumpdone - would be same target (as
* this is the last argument to the AND expression), except
* more expensive.
*/
}
else if (*op->d.boolexpr.anynull)
{
*op->resvalue = (Datum) 0;
*op->resnull = true;
}
else
{
/* result is already set to FALSE, need not change it */
}
EEO_NEXT();
}
EEO_CASE(EEOP_BOOL_NOT_STEP)
{
/*
* Evaluation of 'not' is simple... if expr is false, then return
* 'true' and vice versa. It's safe to do this even on a
* nominally null value, so we ignore resnull; that means that
* NULL in produces NULL out, which is what we want.
*/
*op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue));
EEO_NEXT();
}
EEO_CASE(EEOP_QUAL)
{
/* simplified version of BOOL_AND_STEP for use by ExecQual() */
/* If argument (also result) is false or null ... */
if (*op->resnull ||
!DatumGetBool(*op->resvalue))
{
/* ... bail out early, returning FALSE */
*op->resnull = false;
*op->resvalue = BoolGetDatum(false);
EEO_JUMP(op->d.qualexpr.jumpdone);
}
/*
* Otherwise, leave the TRUE value in place, in case this is the
* last qual. Then, TRUE is the correct answer.
*/
EEO_NEXT();
}
EEO_CASE(EEOP_JUMP)
{
/* Unconditionally jump to target step */
EEO_JUMP(op->d.jump.jumpdone);
}
EEO_CASE(EEOP_JUMP_IF_NULL)
{
/* Transfer control if current result is null */
if (*op->resnull)
EEO_JUMP(op->d.jump.jumpdone);
EEO_NEXT();
}
EEO_CASE(EEOP_JUMP_IF_NOT_NULL)
{
/* Transfer control if current result is non-null */
if (!*op->resnull)
EEO_JUMP(op->d.jump.jumpdone);
EEO_NEXT();
}
EEO_CASE(EEOP_JUMP_IF_NOT_TRUE)
{
/* Transfer control if current result is null or false */
if (*op->resnull || !DatumGetBool(*op->resvalue))
EEO_JUMP(op->d.jump.jumpdone);
EEO_NEXT();
}
EEO_CASE(EEOP_NULLTEST_ISNULL)
{
*op->resvalue = BoolGetDatum(*op->resnull);
*op->resnull = false;
EEO_NEXT();
}
EEO_CASE(EEOP_NULLTEST_ISNOTNULL)
{
*op->resvalue = BoolGetDatum(!*op->resnull);
*op->resnull = false;
EEO_NEXT();
}
EEO_CASE(EEOP_NULLTEST_ROWISNULL)
{
/* out of line implementation: too large */
ExecEvalRowNull(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_NULLTEST_ROWISNOTNULL)
{
/* out of line implementation: too large */
ExecEvalRowNotNull(state, op, econtext);
EEO_NEXT();
}
/* BooleanTest implementations for all booltesttypes */
EEO_CASE(EEOP_BOOLTEST_IS_TRUE)
{
if (*op->resnull)
{
*op->resvalue = BoolGetDatum(false);
*op->resnull = false;
}
/* else, input value is the correct output as well */
EEO_NEXT();
}
EEO_CASE(EEOP_BOOLTEST_IS_NOT_TRUE)
{
if (*op->resnull)
{
*op->resvalue = BoolGetDatum(true);
*op->resnull = false;
}
else
*op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue));
EEO_NEXT();
}
EEO_CASE(EEOP_BOOLTEST_IS_FALSE)
{
if (*op->resnull)
{
*op->resvalue = BoolGetDatum(false);
*op->resnull = false;
}
else
*op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue));
EEO_NEXT();
}
EEO_CASE(EEOP_BOOLTEST_IS_NOT_FALSE)
{
if (*op->resnull)
{
*op->resvalue = BoolGetDatum(true);
*op->resnull = false;
}
/* else, input value is the correct output as well */
EEO_NEXT();
}
EEO_CASE(EEOP_PARAM_EXEC)
{
/* out of line implementation: too large */
ExecEvalParamExec(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_PARAM_EXTERN)
{
/* out of line implementation: too large */
ExecEvalParamExtern(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_PARAM_CALLBACK)
{
/* allow an extension module to supply a PARAM_EXTERN value */
op->d.cparam.paramfunc(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_PARAM_SET)
{
/* out of line, unlikely to matter performance-wise */
ExecEvalParamSet(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_CASE_TESTVAL)
{
/*
* Normally upper parts of the expression tree have setup the
* values to be returned here, but some parts of the system
* currently misuse {caseValue,domainValue}_{datum,isNull} to set
* run-time data. So if no values have been set-up, use
* ExprContext's. This isn't pretty, but also not *that* ugly,
* and this is unlikely to be performance sensitive enough to
* worry about an extra branch.
*/
if (op->d.casetest.value)
{
*op->resvalue = *op->d.casetest.value;
*op->resnull = *op->d.casetest.isnull;
}
else
{
*op->resvalue = econtext->caseValue_datum;
*op->resnull = econtext->caseValue_isNull;
}
EEO_NEXT();
}
EEO_CASE(EEOP_DOMAIN_TESTVAL)
{
/*
* See EEOP_CASE_TESTVAL comment.
*/
if (op->d.casetest.value)
{
*op->resvalue = *op->d.casetest.value;
*op->resnull = *op->d.casetest.isnull;
}
else
{
*op->resvalue = econtext->domainValue_datum;
*op->resnull = econtext->domainValue_isNull;
}
EEO_NEXT();
}
EEO_CASE(EEOP_MAKE_READONLY)
{
/*
* Force a varlena value that might be read multiple times to R/O
*/
if (!*op->d.make_readonly.isnull)
*op->resvalue =
MakeExpandedObjectReadOnlyInternal(*op->d.make_readonly.value);
*op->resnull = *op->d.make_readonly.isnull;
EEO_NEXT();
}
EEO_CASE(EEOP_IOCOERCE)
{
/*
* Evaluate a CoerceViaIO node. This can be quite a hot path, so
* inline as much work as possible. The source value is in our
* result variable.
*
* Also look at ExecEvalCoerceViaIOSafe() if you change anything
* here.
*/
char *str;
/* call output function (similar to OutputFunctionCall) */
if (*op->resnull)
{
/* output functions are not called on nulls */
str = NULL;
}
else
{
FunctionCallInfo fcinfo_out;
fcinfo_out = op->d.iocoerce.fcinfo_data_out;
fcinfo_out->args[0].value = *op->resvalue;
fcinfo_out->args[0].isnull = false;
fcinfo_out->isnull = false;
str = DatumGetCString(FunctionCallInvoke(fcinfo_out));
/* OutputFunctionCall assumes result isn't null */
Assert(!fcinfo_out->isnull);
}
/* call input function (similar to InputFunctionCall) */
if (!op->d.iocoerce.finfo_in->fn_strict || str != NULL)
{
FunctionCallInfo fcinfo_in;
Datum d;
fcinfo_in = op->d.iocoerce.fcinfo_data_in;
fcinfo_in->args[0].value = PointerGetDatum(str);
fcinfo_in->args[0].isnull = *op->resnull;
/* second and third arguments are already set up */
fcinfo_in->isnull = false;
d = FunctionCallInvoke(fcinfo_in);
*op->resvalue = d;
/* Should get null result if and only if str is NULL */
if (str == NULL)
{
Assert(*op->resnull);
Assert(fcinfo_in->isnull);
}
else
{
Assert(!*op->resnull);
Assert(!fcinfo_in->isnull);
}
}
EEO_NEXT();
}
EEO_CASE(EEOP_IOCOERCE_SAFE)
{
ExecEvalCoerceViaIOSafe(state, op);
EEO_NEXT();
}
EEO_CASE(EEOP_DISTINCT)
{
/*
* IS DISTINCT FROM must evaluate arguments (already done into
* fcinfo->args) to determine whether they are NULL; if either is
* NULL then the result is determined. If neither is NULL, then
* proceed to evaluate the comparison function, which is just the
* type's standard equality operator. We need not care whether
* that function is strict. Because the handling of nulls is
* different, we can't just reuse EEOP_FUNCEXPR.
*/
FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
/* check function arguments for NULLness */
if (fcinfo->args[0].isnull && fcinfo->args[1].isnull)
{
/* Both NULL? Then is not distinct... */
*op->resvalue = BoolGetDatum(false);
*op->resnull = false;
}
else if (fcinfo->args[0].isnull || fcinfo->args[1].isnull)
{
/* Only one is NULL? Then is distinct... */
*op->resvalue = BoolGetDatum(true);
*op->resnull = false;
}
else
{
/* Neither null, so apply the equality function */
Datum eqresult;
fcinfo->isnull = false;
eqresult = op->d.func.fn_addr(fcinfo);
/* Must invert result of "="; safe to do even if null */
*op->resvalue = BoolGetDatum(!DatumGetBool(eqresult));
*op->resnull = fcinfo->isnull;
}
EEO_NEXT();
}
/* see EEOP_DISTINCT for comments, this is just inverted */
EEO_CASE(EEOP_NOT_DISTINCT)
{
FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
if (fcinfo->args[0].isnull && fcinfo->args[1].isnull)
{
*op->resvalue = BoolGetDatum(true);
*op->resnull = false;
}
else if (fcinfo->args[0].isnull || fcinfo->args[1].isnull)
{
*op->resvalue = BoolGetDatum(false);
*op->resnull = false;
}
else
{
Datum eqresult;
fcinfo->isnull = false;
eqresult = op->d.func.fn_addr(fcinfo);
*op->resvalue = eqresult;
*op->resnull = fcinfo->isnull;
}
EEO_NEXT();
}
EEO_CASE(EEOP_NULLIF)
{
/*
* The arguments are already evaluated into fcinfo->args.
*/
FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
Datum save_arg0 = fcinfo->args[0].value;
/* if either argument is NULL they can't be equal */
if (!fcinfo->args[0].isnull && !fcinfo->args[1].isnull)
{
Datum result;
/*
* If first argument is of varlena type, it might be an
* expanded datum. We need to ensure that the value passed to
* the comparison function is a read-only pointer. However,
* if we end by returning the first argument, that will be the
* original read-write pointer if it was read-write.
*/
if (op->d.func.make_ro)
fcinfo->args[0].value =
MakeExpandedObjectReadOnlyInternal(save_arg0);
fcinfo->isnull = false;
result = op->d.func.fn_addr(fcinfo);
/* if the arguments are equal return null */
if (!fcinfo->isnull && DatumGetBool(result))
{
*op->resvalue = (Datum) 0;
*op->resnull = true;
EEO_NEXT();
}
}
/* Arguments aren't equal, so return the first one */
*op->resvalue = save_arg0;
*op->resnull = fcinfo->args[0].isnull;
EEO_NEXT();
}
EEO_CASE(EEOP_SQLVALUEFUNCTION)
{
/*
* Doesn't seem worthwhile to have an inline implementation
* efficiency-wise.
*/
ExecEvalSQLValueFunction(state, op);
EEO_NEXT();
}
EEO_CASE(EEOP_CURRENTOFEXPR)
{
/* error invocation uses space, and shouldn't ever occur */
ExecEvalCurrentOfExpr(state, op);
EEO_NEXT();
}
EEO_CASE(EEOP_NEXTVALUEEXPR)
{
/*
* Doesn't seem worthwhile to have an inline implementation
* efficiency-wise.
*/
ExecEvalNextValueExpr(state, op);
EEO_NEXT();
}
EEO_CASE(EEOP_ARRAYEXPR)
{
/* too complex for an inline implementation */
ExecEvalArrayExpr(state, op);
EEO_NEXT();
}
EEO_CASE(EEOP_ARRAYCOERCE)
{
/* too complex for an inline implementation */
ExecEvalArrayCoerce(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_ROW)
{
/* too complex for an inline implementation */
ExecEvalRow(state, op);
EEO_NEXT();
}
EEO_CASE(EEOP_ROWCOMPARE_STEP)
{
FunctionCallInfo fcinfo = op->d.rowcompare_step.fcinfo_data;
Datum d;
/* force NULL result if strict fn and NULL input */
if (op->d.rowcompare_step.finfo->fn_strict &&
(fcinfo->args[0].isnull || fcinfo->args[1].isnull))
{
*op->resnull = true;
EEO_JUMP(op->d.rowcompare_step.jumpnull);
}
/* Apply comparison function */
fcinfo->isnull = false;
d = op->d.rowcompare_step.fn_addr(fcinfo);
*op->resvalue = d;
/* force NULL result if NULL function result */
if (fcinfo->isnull)
{
*op->resnull = true;
EEO_JUMP(op->d.rowcompare_step.jumpnull);
}
*op->resnull = false;
/* If unequal, no need to compare remaining columns */
if (DatumGetInt32(*op->resvalue) != 0)
{
EEO_JUMP(op->d.rowcompare_step.jumpdone);
}
EEO_NEXT();
}
EEO_CASE(EEOP_ROWCOMPARE_FINAL)
{
int32 cmpresult = DatumGetInt32(*op->resvalue);
RowCompareType rctype = op->d.rowcompare_final.rctype;
*op->resnull = false;
switch (rctype)
{
/* EQ and NE cases aren't allowed here */
case ROWCOMPARE_LT:
*op->resvalue = BoolGetDatum(cmpresult < 0);
break;
case ROWCOMPARE_LE:
*op->resvalue = BoolGetDatum(cmpresult <= 0);
break;
case ROWCOMPARE_GE:
*op->resvalue = BoolGetDatum(cmpresult >= 0);
break;
case ROWCOMPARE_GT:
*op->resvalue = BoolGetDatum(cmpresult > 0);
break;
default:
Assert(false);
break;
}
EEO_NEXT();
}
EEO_CASE(EEOP_MINMAX)
{
/* too complex for an inline implementation */
ExecEvalMinMax(state, op);
EEO_NEXT();
}
EEO_CASE(EEOP_FIELDSELECT)
{
/* too complex for an inline implementation */
ExecEvalFieldSelect(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_FIELDSTORE_DEFORM)
{
/* too complex for an inline implementation */
ExecEvalFieldStoreDeForm(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_FIELDSTORE_FORM)
{
/* too complex for an inline implementation */
ExecEvalFieldStoreForm(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_SBSREF_SUBSCRIPTS)
{
/* Precheck SubscriptingRef subscript(s) */
if (op->d.sbsref_subscript.subscriptfunc(state, op, econtext))
{
EEO_NEXT();
}
else
{
/* Subscript is null, short-circuit SubscriptingRef to NULL */
EEO_JUMP(op->d.sbsref_subscript.jumpdone);
}
}
EEO_CASE(EEOP_SBSREF_OLD)
EEO_CASE(EEOP_SBSREF_ASSIGN)
EEO_CASE(EEOP_SBSREF_FETCH)
{
/* Perform a SubscriptingRef fetch or assignment */
op->d.sbsref.subscriptfunc(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_CONVERT_ROWTYPE)
{
/* too complex for an inline implementation */
ExecEvalConvertRowtype(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_SCALARARRAYOP)
{
/* too complex for an inline implementation */
ExecEvalScalarArrayOp(state, op);
EEO_NEXT();
}
EEO_CASE(EEOP_HASHED_SCALARARRAYOP)
{
/* too complex for an inline implementation */
ExecEvalHashedScalarArrayOp(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_DOMAIN_NOTNULL)
{
/* too complex for an inline implementation */
ExecEvalConstraintNotNull(state, op);
EEO_NEXT();
}
EEO_CASE(EEOP_DOMAIN_CHECK)
{
/* too complex for an inline implementation */
ExecEvalConstraintCheck(state, op);
EEO_NEXT();
}
EEO_CASE(EEOP_HASHDATUM_SET_INITVAL)
{
*op->resvalue = op->d.hashdatum_initvalue.init_value;
*op->resnull = false;
EEO_NEXT();
}
EEO_CASE(EEOP_HASHDATUM_FIRST)
{
FunctionCallInfo fcinfo = op->d.hashdatum.fcinfo_data;
/*
* Save the Datum on non-null inputs, otherwise store 0 so that
* subsequent NEXT32 operations combine with an initialized value.
*/
if (!fcinfo->args[0].isnull)
*op->resvalue = op->d.hashdatum.fn_addr(fcinfo);
else
*op->resvalue = (Datum) 0;
*op->resnull = false;
EEO_NEXT();
}
EEO_CASE(EEOP_HASHDATUM_FIRST_STRICT)
{
FunctionCallInfo fcinfo = op->d.hashdatum.fcinfo_data;
if (fcinfo->args[0].isnull)
{
/*
* With strict we have the expression return NULL instead of
* ignoring NULL input values. We've nothing more to do after
* finding a NULL.
*/
*op->resnull = true;
*op->resvalue = (Datum) 0;
EEO_JUMP(op->d.hashdatum.jumpdone);
}
/* execute the hash function and save the resulting value */
*op->resvalue = op->d.hashdatum.fn_addr(fcinfo);
*op->resnull = false;
EEO_NEXT();
}
EEO_CASE(EEOP_HASHDATUM_NEXT32)
{
FunctionCallInfo fcinfo = op->d.hashdatum.fcinfo_data;
uint32 existinghash;
existinghash = DatumGetUInt32(op->d.hashdatum.iresult->value);
/* combine successive hash values by rotating */
existinghash = pg_rotate_left32(existinghash, 1);
/* leave the hash value alone on NULL inputs */
if (!fcinfo->args[0].isnull)
{
uint32 hashvalue;
/* execute hash func and combine with previous hash value */
hashvalue = DatumGetUInt32(op->d.hashdatum.fn_addr(fcinfo));
existinghash = existinghash ^ hashvalue;
}
*op->resvalue = UInt32GetDatum(existinghash);
*op->resnull = false;
EEO_NEXT();
}
EEO_CASE(EEOP_HASHDATUM_NEXT32_STRICT)
{
FunctionCallInfo fcinfo = op->d.hashdatum.fcinfo_data;
if (fcinfo->args[0].isnull)
{
/*
* With strict we have the expression return NULL instead of
* ignoring NULL input values. We've nothing more to do after
* finding a NULL.
*/
*op->resnull = true;
*op->resvalue = (Datum) 0;
EEO_JUMP(op->d.hashdatum.jumpdone);
}
else
{
uint32 existinghash;
uint32 hashvalue;
existinghash = DatumGetUInt32(op->d.hashdatum.iresult->value);
/* combine successive hash values by rotating */
existinghash = pg_rotate_left32(existinghash, 1);
/* execute hash func and combine with previous hash value */
hashvalue = DatumGetUInt32(op->d.hashdatum.fn_addr(fcinfo));
*op->resvalue = UInt32GetDatum(existinghash ^ hashvalue);
*op->resnull = false;
}
EEO_NEXT();
}
EEO_CASE(EEOP_XMLEXPR)
{
/* too complex for an inline implementation */
ExecEvalXmlExpr(state, op);
EEO_NEXT();
}
EEO_CASE(EEOP_JSON_CONSTRUCTOR)
{
/* too complex for an inline implementation */
ExecEvalJsonConstructor(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_IS_JSON)
{
/* too complex for an inline implementation */
ExecEvalJsonIsPredicate(state, op);
EEO_NEXT();
}
EEO_CASE(EEOP_JSONEXPR_PATH)
{
/* too complex for an inline implementation */
EEO_JUMP(ExecEvalJsonExprPath(state, op, econtext));
}
EEO_CASE(EEOP_JSONEXPR_COERCION)
{
/* too complex for an inline implementation */
ExecEvalJsonCoercion(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_JSONEXPR_COERCION_FINISH)
{
/* too complex for an inline implementation */
ExecEvalJsonCoercionFinish(state, op);
EEO_NEXT();
}
EEO_CASE(EEOP_AGGREF)
{
/*
* Returns a Datum whose value is the precomputed aggregate value
* found in the given expression context.
*/
int aggno = op->d.aggref.aggno;
Assert(econtext->ecxt_aggvalues != NULL);
*op->resvalue = econtext->ecxt_aggvalues[aggno];
*op->resnull = econtext->ecxt_aggnulls[aggno];
EEO_NEXT();
}
EEO_CASE(EEOP_GROUPING_FUNC)
{
/* too complex/uncommon for an inline implementation */
ExecEvalGroupingFunc(state, op);
EEO_NEXT();
}
EEO_CASE(EEOP_WINDOW_FUNC)
{
/*
* Like Aggref, just return a precomputed value from the econtext.
*/
WindowFuncExprState *wfunc = op->d.window_func.wfstate;
Assert(econtext->ecxt_aggvalues != NULL);
*op->resvalue = econtext->ecxt_aggvalues[wfunc->wfuncno];
*op->resnull = econtext->ecxt_aggnulls[wfunc->wfuncno];
EEO_NEXT();
}
EEO_CASE(EEOP_MERGE_SUPPORT_FUNC)
{
/* too complex/uncommon for an inline implementation */
ExecEvalMergeSupportFunc(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_SUBPLAN)
{
/* too complex for an inline implementation */
ExecEvalSubPlan(state, op, econtext);
EEO_NEXT();
}
/* evaluate a strict aggregate deserialization function */
EEO_CASE(EEOP_AGG_STRICT_DESERIALIZE)
{
/* Don't call a strict deserialization function with NULL input */
if (op->d.agg_deserialize.fcinfo_data->args[0].isnull)
EEO_JUMP(op->d.agg_deserialize.jumpnull);
/* fallthrough */
}
/* evaluate aggregate deserialization function (non-strict portion) */
EEO_CASE(EEOP_AGG_DESERIALIZE)
{
FunctionCallInfo fcinfo = op->d.agg_deserialize.fcinfo_data;
AggState *aggstate = castNode(AggState, state->parent);
MemoryContext oldContext;
/*
* We run the deserialization functions in per-input-tuple memory
* context.
*/
oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
fcinfo->isnull = false;
*op->resvalue = FunctionCallInvoke(fcinfo);
*op->resnull = fcinfo->isnull;
MemoryContextSwitchTo(oldContext);
EEO_NEXT();
}
/*
* Check that a strict aggregate transition / combination function's
* input is not NULL.
*/
EEO_CASE(EEOP_AGG_STRICT_INPUT_CHECK_ARGS)
{
NullableDatum *args = op->d.agg_strict_input_check.args;
int nargs = op->d.agg_strict_input_check.nargs;
for (int argno = 0; argno < nargs; argno++)
{
if (args[argno].isnull)
EEO_JUMP(op->d.agg_strict_input_check.jumpnull);
}
EEO_NEXT();
}
EEO_CASE(EEOP_AGG_STRICT_INPUT_CHECK_NULLS)
{
bool *nulls = op->d.agg_strict_input_check.nulls;
int nargs = op->d.agg_strict_input_check.nargs;
for (int argno = 0; argno < nargs; argno++)
{
if (nulls[argno])
EEO_JUMP(op->d.agg_strict_input_check.jumpnull);
}
EEO_NEXT();
}
/*
* Check for a NULL pointer to the per-group states.
*/
EEO_CASE(EEOP_AGG_PLAIN_PERGROUP_NULLCHECK)
{
AggState *aggstate = castNode(AggState, state->parent);
AggStatePerGroup pergroup_allaggs =
aggstate->all_pergroups[op->d.agg_plain_pergroup_nullcheck.setoff];
if (pergroup_allaggs == NULL)
EEO_JUMP(op->d.agg_plain_pergroup_nullcheck.jumpnull);
EEO_NEXT();
}
/*
* Different types of aggregate transition functions are implemented
* as different types of steps, to avoid incurring unnecessary
* overhead. There's a step type for each valid combination of having
* a by value / by reference transition type, [not] needing to the
* initialize the transition value for the first row in a group from
* input, and [not] strict transition function.
*
* Could optimize further by splitting off by-reference for
* fixed-length types, but currently that doesn't seem worth it.
*/
EEO_CASE(EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL)
{
AggState *aggstate = castNode(AggState, state->parent);
AggStatePerTrans pertrans = op->d.agg_trans.pertrans;
AggStatePerGroup pergroup =
&aggstate->all_pergroups[op->d.agg_trans.setoff][op->d.agg_trans.transno];
Assert(pertrans->transtypeByVal);
if (pergroup->noTransValue)
{
/* If transValue has not yet been initialized, do so now. */
ExecAggInitGroup(aggstate, pertrans, pergroup,
op->d.agg_trans.aggcontext);
/* copied trans value from input, done this round */
}
else if (likely(!pergroup->transValueIsNull))
{
/* invoke transition function, unless prevented by strictness */
ExecAggPlainTransByVal(aggstate, pertrans, pergroup,
op->d.agg_trans.aggcontext,
op->d.agg_trans.setno);
}
EEO_NEXT();
}
/* see comments above EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL */
EEO_CASE(EEOP_AGG_PLAIN_TRANS_STRICT_BYVAL)
{
AggState *aggstate = castNode(AggState, state->parent);
AggStatePerTrans pertrans = op->d.agg_trans.pertrans;
AggStatePerGroup pergroup =
&aggstate->all_pergroups[op->d.agg_trans.setoff][op->d.agg_trans.transno];
Assert(pertrans->transtypeByVal);
if (likely(!pergroup->transValueIsNull))
ExecAggPlainTransByVal(aggstate, pertrans, pergroup,
op->d.agg_trans.aggcontext,
op->d.agg_trans.setno);
EEO_NEXT();
}
/* see comments above EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL */
EEO_CASE(EEOP_AGG_PLAIN_TRANS_BYVAL)
{
AggState *aggstate = castNode(AggState, state->parent);
AggStatePerTrans pertrans = op->d.agg_trans.pertrans;
AggStatePerGroup pergroup =
&aggstate->all_pergroups[op->d.agg_trans.setoff][op->d.agg_trans.transno];
Assert(pertrans->transtypeByVal);
ExecAggPlainTransByVal(aggstate, pertrans, pergroup,
op->d.agg_trans.aggcontext,
op->d.agg_trans.setno);
EEO_NEXT();
}
/* see comments above EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL */
EEO_CASE(EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYREF)
{
AggState *aggstate = castNode(AggState, state->parent);
AggStatePerTrans pertrans = op->d.agg_trans.pertrans;
AggStatePerGroup pergroup =
&aggstate->all_pergroups[op->d.agg_trans.setoff][op->d.agg_trans.transno];
Assert(!pertrans->transtypeByVal);
if (pergroup->noTransValue)
ExecAggInitGroup(aggstate, pertrans, pergroup,
op->d.agg_trans.aggcontext);
else if (likely(!pergroup->transValueIsNull))
ExecAggPlainTransByRef(aggstate, pertrans, pergroup,
op->d.agg_trans.aggcontext,
op->d.agg_trans.setno);
EEO_NEXT();
}
/* see comments above EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL */
EEO_CASE(EEOP_AGG_PLAIN_TRANS_STRICT_BYREF)
{
AggState *aggstate = castNode(AggState, state->parent);
AggStatePerTrans pertrans = op->d.agg_trans.pertrans;
AggStatePerGroup pergroup =
&aggstate->all_pergroups[op->d.agg_trans.setoff][op->d.agg_trans.transno];
Assert(!pertrans->transtypeByVal);
if (likely(!pergroup->transValueIsNull))
ExecAggPlainTransByRef(aggstate, pertrans, pergroup,
op->d.agg_trans.aggcontext,
op->d.agg_trans.setno);
EEO_NEXT();
}
/* see comments above EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL */
EEO_CASE(EEOP_AGG_PLAIN_TRANS_BYREF)
{
AggState *aggstate = castNode(AggState, state->parent);
AggStatePerTrans pertrans = op->d.agg_trans.pertrans;
AggStatePerGroup pergroup =
&aggstate->all_pergroups[op->d.agg_trans.setoff][op->d.agg_trans.transno];
Assert(!pertrans->transtypeByVal);
ExecAggPlainTransByRef(aggstate, pertrans, pergroup,
op->d.agg_trans.aggcontext,
op->d.agg_trans.setno);
EEO_NEXT();
}
EEO_CASE(EEOP_AGG_PRESORTED_DISTINCT_SINGLE)
{
AggStatePerTrans pertrans = op->d.agg_presorted_distinctcheck.pertrans;
AggState *aggstate = castNode(AggState, state->parent);
if (ExecEvalPreOrderedDistinctSingle(aggstate, pertrans))
EEO_NEXT();
else
EEO_JUMP(op->d.agg_presorted_distinctcheck.jumpdistinct);
}
EEO_CASE(EEOP_AGG_PRESORTED_DISTINCT_MULTI)
{
AggState *aggstate = castNode(AggState, state->parent);
AggStatePerTrans pertrans = op->d.agg_presorted_distinctcheck.pertrans;
if (ExecEvalPreOrderedDistinctMulti(aggstate, pertrans))
EEO_NEXT();
else
EEO_JUMP(op->d.agg_presorted_distinctcheck.jumpdistinct);
}
/* process single-column ordered aggregate datum */
EEO_CASE(EEOP_AGG_ORDERED_TRANS_DATUM)
{
/* too complex for an inline implementation */
ExecEvalAggOrderedTransDatum(state, op, econtext);
EEO_NEXT();
}
/* process multi-column ordered aggregate tuple */
EEO_CASE(EEOP_AGG_ORDERED_TRANS_TUPLE)
{
/* too complex for an inline implementation */
ExecEvalAggOrderedTransTuple(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_LAST)
{
/* unreachable */
Assert(false);
goto out;
}
}
out:
*isnull = state->resnull;
return state->resvalue;
}
表格:ExecInterpExpr
中 case
分支汇总
操作码 (ExprEvalOp) | 描述 | 主要操作 | 通俗解释(以 SQL 为例) |
---|---|---|---|
EEOP_DONE | 结束表达式执行 | 跳转到 out ,返回结果 |
表示“age + 10”计算完成,返回结果(如 40)。 |
EEOP_INNER_FETCHSOME | 从内表获取属性 | slot_getsomeattrs 获取指定字段 |
从内表(如 JOIN 的左表)取 age 值,确保字段可用。 |
EEOP_OUTER_FETCHSOME | 从外表获取属性 | slot_getsomeattrs 获取指定字段 |
从外表(如 JOIN 的右表)取 age 值,确保字段可用。 |
EEOP_SCAN_FETCHSOME | 从扫描表获取属性 | slot_getsomeattrs 获取指定字段 |
从当前扫描表(如 users )取 age 值。 |
EEOP_INNER_VAR | 获取内表字段值 | 从 innerslot 取值 |
直接取内表 age 列的值(如 30)。 |
EEOP_OUTER_VAR | 获取外表字段值 | 从 outerslot 取值 |
直接取外表 age 列的值。 |
EEOP_SCAN_VAR | 获取扫描表字段值 | 从 scanslot 取值 |
直接取 users 表 age 列的值。 |
EEOP_INNER_SYSVAR | 获取内表系统列 | 调用 ExecEvalSysVar |
取内表的系统列(如 ctid ),用于元数据操作。 |
EEOP_OUTER_SYSVAR | 获取外表系统列 | 调用 ExecEvalSysVar |
取外表的系统列(如 oid )。 |
EEOP_SCAN_SYSVAR | 获取扫描表系统列 | 调用 ExecEvalSysVar |
取 users 表的系统列。 |
EEOP_WHOLEROW | 获取整行引用 | 调用 ExecEvalWholeRowVar |
取整行(如 users.* ),用于 ROW 构造。 |
EEOP_ASSIGN_INNER_VAR | 赋值内表字段到结果 | 复制 innerslot 值到 resultslot |
将内表 age 值赋给结果列(如 new_age )。 |
EEOP_ASSIGN_OUTER_VAR | 赋值外表字段到结果 | 复制 outerslot 值到 resultslot |
将外表 age 值赋给结果列。 |
EEOP_ASSIGN_SCAN_VAR | 赋值扫描表字段到结果 | 复制 scanslot 值到 resultslot |
将 users 表 age 值赋给结果列。 |
EEOP_ASSIGN_TMP | 赋值临时值到结果 | 复制 state->resvalue 到 resultslot |
将临时计算结果(如 age + 10 )存入结果列。 |
EEOP_ASSIGN_TMP_MAKE_RO | 赋值临时值并设为只读 | 复制并调用 MakeExpandedObjectReadOnlyInternal |
将 age + 10 结果存为只读值,避免修改。 |
EEOP_CONST | 获取常量值 | 设置 resvalue 和 resnull |
直接返回常量(如 10 )用于 age + 10 。 |
EEOP_FUNCEXPR | 调用普通函数 | 调用函数并存储结果 | 执行 UPPER(name) ,返回大写 name 。 |
EEOP_FUNCEXPR_STRICT | 调用严格函数 | 检查参数非 NULL 后调用 | 执行 SUBSTRING(name, 1, 3) ,若参数 NULL 则返回 NULL。 |
EEOP_FUNCEXPR_FUSAGE | 调用带外键函数 | 调用 ExecEvalFuncExprFusage |
处理带外键的函数(如触发器相关)。 |
EEOP_FUNCEXPR_STRICT_FUSAGE | 调用严格带外键函数 | 调用 ExecEvalFuncExprStrictFusage |
处理严格模式的带外键函数。 |
EEOP_BOOL_AND_STEP_FIRST | 开始 AND 逻辑 | 重置 anynull |
开始处理 age > 30 AND name = 'John' ,初始化 NULL 跟踪。 |
EEOP_BOOL_AND_STEP | AND 逻辑步骤 | 检查 FALSE 或 NULL,短路跳转 | 检查 age > 30 ,若 FALSE 则跳到结束,返回 FALSE。 |
EEOP_BOOL_AND_STEP_LAST | 结束 AND 逻辑 | 确定最终结果(TRUE、FALSE、NULL) | 完成 age > 30 AND name = 'John' ,根据 NULL 返回结果。 |
EEOP_BOOL_OR_STEP_FIRST | 开始 OR 逻辑 | 重置 anynull |
开始处理 age > 30 OR name = 'John' ,初始化 NULL 跟踪。 |
EEOP_BOOL_OR_STEP | OR 逻辑步骤 | 检查 TRUE 或 NULL,短路跳转 | 检查 age > 30 ,若 TRUE 则跳到结束,返回 TRUE。 |
EEOP_BOOL_OR_STEP_LAST | 结束 OR 逻辑 | 确定最终结果(TRUE、FALSE、NULL) | 完成 age > 30 OR name = 'John' ,根据 NULL 返回结果。 |
EEOP_BOOL_NOT_STEP | NOT 逻辑 | 反转布尔值 | 处理 NOT (age > 30) ,将 TRUE 变 FALSE 或反之。 |
EEOP_QUAL | 条件检查 | 检查 FALSE 或 NULL,短路返回 FALSE | 检查 age > 30 ,若 FALSE 或 NULL 则返回 FALSE。 |
EEOP_JUMP | 无条件跳转 | 跳转到指定步骤 | 跳到 CASE 语句的下一个分支。 |
EEOP_JUMP_IF_NULL | NULL 时跳转 | 若结果 NULL 则跳转 | 若 age 为 NULL,跳过 age + 10 的计算。 |
EEOP_JUMP_IF_NOT_NULL | 非 NULL 时跳转 | 若结果非 NULL 则跳转 | 若 age 非 NULL,继续计算 age + 10 。 |
EEOP_JUMP_IF_NOT_TRUE | 非 TRUE 时跳转 | 若结果 NULL 或 FALSE 则跳转 | 若 age > 30 为 FALSE,跳到 CASE 的 ELSE 分支。 |
EEOP_NULLTEST_ISNULL | 检查是否 NULL | 返回是否为 NULL | 处理 age IS NULL ,返回 TRUE 或 FALSE。 |
EEOP_NULLTEST_ISNOTNULL | 检查是否非 NULL | 返回是否非 NULL | 处理 age IS NOT NULL ,返回 TRUE 或 FALSE。 |
EEOP_NULLTEST_ROWISNULL | 检查行是否 NULL | 调用 ExecEvalRowNull |
检查 ROW(age, name) 是否全 NULL。 |
EEOP_NULLTEST_ROWISNOTNULL | 检查行是否非 NULL | 调用 ExecEvalRowNotNull |
检查 ROW(age, name) 是否有非 NULL 值。 |
EEOP_BOOLTEST_IS_TRUE | 检查是否 TRUE | 返回 TRUE 或 FALSE | 处理 age > 30 IS TRUE ,若 NULL 则返回 FALSE。 |
EEOP_BOOLTEST_IS_NOT_TRUE | 检查是否非 TRUE | 返回非 TRUE 结果 | 处理 age > 30 IS NOT TRUE ,若 NULL 则返回 TRUE。 |
EEOP_BOOLTEST_IS_FALSE | 检查是否 FALSE | 返回 FALSE 或 TRUE | 处理 age > 30 IS FALSE ,若 NULL 则返回 FALSE。 |
EEOP_BOOLTEST_IS_NOT_FALSE | 检查是否非 FALSE | 返回非 FALSE 结果 | 处理 age > 30 IS NOT FALSE ,若 NULL 则返回 TRUE。 |
EEOP_PARAM_EXEC | 获取执行参数 | 调用 ExecEvalParamExec |
取子查询参数,如 (SELECT id FROM t WHERE x = age) 。 |
EEOP_PARAM_EXTERN | 获取外部参数 | 调用 ExecEvalParamExtern |
取查询参数,如 $1 在 age > $1 。 |
EEOP_PARAM_CALLBACK | 获取扩展参数 | 调用扩展模块的 paramfunc |
允许扩展模块提供参数值。 |
EEOP_PARAM_SET | 设置参数值 | 调用 ExecEvalParamSet |
设置动态参数值。 |
EEOP_CASE_TESTVAL | 获取 CASE 测试值 | 从 op 或 econtext 取值 |
取 CASE age WHEN 30 THEN ... 的 age 值。 |
EEOP_DOMAIN_TESTVAL | 获取域测试值 | 从 op 或 econtext 取值 |
取域类型的值,类似 CASE 测试值。 |
EEOP_MAKE_READONLY | 设置只读值 | 调用 MakeExpandedObjectReadOnlyInternal |
将 name 值设为只读,防止修改。 |
EEOP_IOCOERCE | 类型转换(I/O) | 调用输入/输出函数 | 处理 age::text ,将 age 转为字符串。 |
EEOP_IOCOERCE_SAFE | 安全类型转换 | 调用 ExecEvalCoerceViaIOSafe |
安全处理 age::text ,避免错误。 |
EEOP_DISTINCT | IS DISTINCT FROM | 检查 NULL 或调用比较函数 | 处理 age IS DISTINCT FROM 30 ,比较是否不同。 |
EEOP_NOT_DISTINCT | IS NOT DISTINCT FROM | 检查 NULL 或调用比较函数 | 处理 age IS NOT DISTINCT FROM 30 ,比较是否相等。 |
EEOP_NULLIF | NULLIF 表达式 | 检查相等返回 NULL 或第一个值 | 处理 NULLIF(name, 'John') ,若相等返回 NULL。 |
EEOP_SQLVALUEFUNCTION | SQL 值函数 | 调用 ExecEvalSQLValueFunction |
处理 CURRENT_DATE ,返回当前日期。 |
EEOP_CURRENTOFEXPR | CURRENT OF 表达式 | 调用 ExecEvalCurrentOfExpr |
处理 WHERE CURRENT OF cursor ,取游标位置。 |
EEOP_NEXTVALUEEXPR | 序列函数 | 调用 ExecEvalNextValueExpr |
处理 nextval('seq') ,获取序列值。 |
EEOP_ARRAYEXPR | 数组构造 | 调用 ExecEvalArrayExpr |
处理 ARRAY[1, 2, 3] ,构造数组。 |
EEOP_ARRAYCOERCE | 数组类型转换 | 调用 ExecEvalArrayCoerce |
处理 ARRAY[1, 2]::text[] ,转换数组元素类型。 |
EEOP_ROW | 行构造 | 调用 ExecEvalRow |
处理 ROW(age, name) ,构造行值。 |
EEOP_ROWCOMPARE_STEP | 行比较步骤 | 调用比较函数,检查相等 | 处理 ROW(age, name) > ROW(30, 'John') ,逐字段比较。 |
EEOP_ROWCOMPARE_FINAL | 行比较最终结果 | 根据比较类型返回结果 | 完成 ROW(age, name) > ROW(30, 'John') ,返回 TRUE/FALSE。 |
EEOP_MINMAX | GREATEST/LEAST | 调用 ExecEvalMinMax |
处理 GREATEST(age, 18) ,返回最大值。 |
EEOP_FIELDSELECT | 字段选择 | 调用 ExecEvalFieldSelect |
处理 user.address.city ,取复合类型字段。 |
EEOP_FIELDSTORE_DEFORM | 分解字段存储 | 调用 ExecEvalFieldStoreDeForm |
分解复合类型字段以更新。 |
EEOP_FIELDSTORE_FORM | 构造字段存储 | 调用 ExecEvalFieldStoreForm |
构造复合类型字段。 |
EEOP_SBSREF_SUBSCRIPTS | 下标预检查 | 检查下标是否 NULL | 处理 array[1] ,检查下标有效性。 |
EEOP_SBSREF_OLD, EEOP_SBSREF_ASSIGN, EEOP_SBSREF_FETCH | 数组下标操作 | 调用 subscriptfunc |
处理 array[1] 的获取或赋值。 |
EEOP_CONVERT_ROWTYPE | 行类型转换 | 调用 ExecEvalConvertRowtype |
处理 ROW(age, name)::new_type ,转换行类型。 |
EEOP_SCALARARRAYOP | 数组操作 | 调用 ExecEvalScalarArrayOp |
处理 age IN (30, 40) ,检查值是否在数组中。 |
EEOP_HASHED_SCALARARRAYOP | 哈希数组操作 | 调用 ExecEvalHashedScalarArrayOp |
优化 age IN (30, 40) ,用哈希加速。 |
EEOP_DOMAIN_NOTNULL | 域非 NULL 检查 | 调用 ExecEvalConstraintNotNull |
检查域类型值是否非 NULL。 |
EEOP_DOMAIN_CHECK | 域约束检查 | 调用 ExecEvalConstraintCheck |
检查域类型值是否符合约束。 |
EEOP_HASHDATUM_SET_INITVAL | 设置哈希初始值 | 设置初始哈希值 | 为哈希计算(如 hash_agg )设置初始值。 |
EEOP_HASHDATUM_FIRST | 首次哈希计算 | 调用哈希函数 | 计算 age 的首次哈希值,用于分组。 |
EEOP_HASHDATUM_FIRST_STRICT | 严格首次哈希 | 检查 NULL 后计算哈希 | 若 age 为 NULL,返回 NULL,否则计算哈希。 |
EEOP_HASHDATUM_NEXT32 | 后续哈希计算 | 旋转并组合哈希值 | 继续计算哈希,合并多个值(如多列分组)。 |
EEOP_HASHDATUM_NEXT32_STRICT | 严格后续哈希 | 检查 NULL 后组合哈希 | 若输入 NULL,返回 NULL,否则合并哈希。 |
EEOP_XMLEXPR | XML 表达式 | 调用 ExecEvalXmlExpr |
处理 XMLELEMENT(name, 'data') ,构造 XML。 |
EEOP_JSON_CONSTRUCTOR | JSON 构造 | 调用 ExecEvalJsonConstructor |
处理 JSON_OBJECT('key': age) ,构造 JSON。 |
EEOP_IS_JSON | JSON 谓词 | 调用 ExecEvalJsonIsPredicate |
处理 json_col IS JSON ,检查是否为 JSON。 |
EEOP_JSONEXPR_PATH | JSON 路径表达式 | 调用 ExecEvalJsonExprPath |
处理 JSON_VALUE(json_col, '$.path') ,提取 JSON 值。 |
EEOP_JSONEXPR_COERCION | JSON 类型转换 | 调用 ExecEvalJsonCoercion |
处理 JSON 值的类型转换。 |
EEOP_JSONEXPR_COERCION_FINISH | 完成 JSON 类型转换 | 调用 ExecEvalJsonCoercionFinish |
完成 JSON 类型转换的收尾工作。 |
EEOP_AGGREF | 聚合函数引用 | 从 econtext 取预计算聚合值 |
处理 SUM(age) ,返回预计算的和。 |
EEOP_GROUPING_FUNC | 分组函数 | 调用 ExecEvalGroupingFunc |
处理 GROUPING(age) ,返回分组标识。 |
EEOP_WINDOW_FUNC | 窗口函数 | 从 econtext 取预计算值 |
处理 ROW_NUMBER() OVER (...) ,返回窗口函数结果。 |
EEOP_MERGE_SUPPORT_FUNC | MERGE 支持函数 | 调用 ExecEvalMergeSupportFunc |
处理 MERGE 语句中的支持函数。 |
EEOP_SUBPLAN | 子查询 | 调用 ExecEvalSubPlan |
处理 WHERE id IN (SELECT ...) ,执行子查询。 |
EEOP_AGG_STRICT_DESERIALIZE | 严格聚合反序列化 | 检查 NULL 后调用反序列化 | 处理严格聚合的反序列化,若输入 NULL 则跳转。 |
EEOP_AGG_DESERIALIZE | 聚合反序列化 | 调用反序列化函数 | 处理聚合状态的反序列化。 |
EEOP_AGG_STRICT_INPUT_CHECK_ARGS | 严格聚合输入检查(参数) | 检查参数非 NULL | 检查 SUM(age) 的输入参数是否 NULL。 |
EEOP_AGG_STRICT_INPUT_CHECK_NULLS | 严格聚合输入检查(NULL 数组) | 检查 NULL 数组 | 检查 SUM(age) 的输入是否 NULL。 |
EEOP_AGG_PLAIN_PERGROUP_NULLCHECK | 聚合组状态检查 | 检查组状态是否 NULL | 检查 SUM(age) 的组状态是否存在。 |
EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL | 严格值传递聚合初始化 | 初始化或调用值传递转换 | 初始化 SUM(age) 的严格值传递状态。 |
EEOP_AGG_PLAIN_TRANS_STRICT_BYVAL | 严格值传递聚合 | 调用值传递转换 | 处理 SUM(age) 的严格值传递转换。 |
EEOP_AGG_PLAIN_TRANS_BYVAL | 值传递聚合 | 调用值传递转换 | 处理 SUM(age) 的值传递转换。 |
EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYREF | 严格引用传递聚合初始化 | 初始化或调用引用传递转换 | 初始化 SUM(age) 的严格引用传递状态。 |
EEOP_AGG_PLAIN_TRANS_STRICT_BYREF | 严格引用传递聚合 | 调用引用传递转换 | 处理 SUM(age) 的严格引用传递转换。 |
EEOP_AGG_PLAIN_TRANS_BYREF | 引用传递聚合 | 调用引用传递转换 | 处理 SUM(age) 的引用传递转换。 |
EEOP_AGG_PRESORTED_DISTINCT_SINGLE | 单列预排序去重 | 调用 ExecEvalPreOrderedDistinctSingle |
处理 SELECT DISTINCT age ,检查单列去重。 |
EEOP_AGG_PRESORTED_DISTINCT_MULTI | 多列预排序去重 | 调用 ExecEvalPreOrderedDistinctMulti |
处理 SELECT DISTINCT age, name ,检查多列去重。 |
EEOP_AGG_ORDERED_TRANS_DATUM | 单列有序聚合 | 调用 ExecEvalAggOrderedTransDatum |
处理单列有序聚合转换。 |
EEOP_AGG_ORDERED_TRANS_TUPLE | 多列有序聚合 | 调用 ExecEvalAggOrderedTransTuple |
处理多列有序聚合转换。 |
EEOP_LAST | 不可达 | 抛出错误 | 表示代码错误,正常情况下不会到达。 |
说明与重要性
- 表格内容:表格覆盖了
ExecInterpExpr
中的所有case
分支,列出操作码、功能描述、主要操作,以及基于SQL
示例(如age + 10
、SUM(age)
)的通俗解释。每个操作码对应一种执行步骤,支持从简单常量到复杂聚合的处理。 - SQL 示例:以
SELECT name, age + 10 AS new_age FROM users WHERE age > 30
为基础,扩展到其他场景(如SUM(age)
、CASE
、JSON_OBJECT
),直观展示每步的作用。 - 通俗解释:用“取值”“计算”“检查”等简单语言描述,确保易于理解。例如,
EEOP_CONST
就是“直接返回10
”,EEOP_FUNCEXPR
是“执行加法age + 10
”。 - 重要性:
ExecInterpExpr
是PostgreSQL
执行SQL
表达式的核心,直接决定了查询结果的计算效率和正确性。它通过跳转表和优化路径(如短路求值)提高性能,同时支持复杂表达式的灵活处理,是连接计划准备和结果输出的关键环节。
总结
PostgreSQL
的表达式执行过程通过 ExecInitExprRec
、ExprEvalPushStep
、ExecReadyExpr
、ExecReadyInterpretedExpr
、ExecInterpExprStillValid
和 ExecInterpExpr
协同完成,从解析 SQL
表达式到生成并执行指令序列。ExecInitExprRec
将表达式(如 age + 10
)分解为操作码序列,ExprEvalPushStep
将这些指令存入 ExprState
。ExecReadyExpr
决定执行方式(JIT
或解释执行),并调用 ExecReadyInterpretedExpr
准备解释执行环境,优化简单表达式并设置初始检查函数 ExecInterpExprStillValid
。ExecInterpExprStillValid
验证表达式有效性后调用 ExecInterpExpr
,后者逐条执行指令,计算最终结果。这些函数共同确保 SQL
表达式的正确、高效执行,贯穿从计划生成到结果返回的整个流程。
[SQL 查询解析]
|
| (解析 SQL: SELECT name, age + 10 AS new_age FROM users WHERE age > 30)
v
[ExecInitExprRec]
| (生成执行步骤: 为 age + 10 创建指令,如“取 age”“加 10”)
|----> [ExprEvalPushStep]
| (添加步骤到 ExprState: 将“取 age”“加 10”写入“计划书”)
| |
| v
| [ExprState.steps] (存储指令序列)
|
v
[ExecReadyExpr]
| (决定执行方式: 尝试 JIT 或解释执行)
|----> [jit_compile_expr] (尝试 JIT 编译 age + 10,若成功直接执行)
|----> [ExecReadyInterpretedExpr]
| (准备解释执行: 检查计划书,优化简单表达式)
| |
| v
| [ExecInitInterpreter] (初始化解释器环境)
| [Set evalfunc to ExecInterpExprStillValid] (设置首次检查函数)
| [Optimize simple cases] (为简单表达式如 age 设置快速路径)
| [Enable direct threading] (若启用,优化跳转地址)
| [Set evalfunc_private to ExecInterpExpr] (默认解释执行函数)
|
v
[ExecInterpExprStillValid]
| (首次执行检查: 验证 age 列有效性)
|----> [CheckExprStillValid] (检查表结构是否匹配)
|----> [Set evalfunc to evalfunc_private] (设置实际执行函数)
|----> [Call evalfunc]
| |
| v
| [ExecInterpExpr]
| (执行表达式: 逐条处理指令,计算 age + 10)
| |
| v
| [dispatch_table] (跳转表,分派指令如 EEOP_CONST、EEOP_FUNCEXPR)
| [Process steps] (执行“取 age”“加 10”,返回 40)
|
v
[Return Result] (返回 age + 10 的结果,如 40)
graph TD
A[SQL 查询解析<br>解析 `SELECT name, age + 10 AS new_age FROM users WHERE age > 30`] --> B[ExecInitExpr<br>初始化 ExprState,为 `age + 10` 创建执行计划书]
B -->|检查 node 是否为 NULL| C{node == NULL?}
C -->|是| D[Return NULL<br>返回空,无需处理表达式]
C -->|否| E[makeNode ExprState<br>创建空的 ExprState,记录表达式和上下文]
E --> F[ExecCreateExprSetupSteps<br>准备执行环境,确保 `age` 列可读取]
E --> G[ExecInitExprRec<br>生成操作码序列,如 `取 age`、`加 10`]
G --> H[ExprEvalPushStep<br>将指令 `取 age`、`加 10` 存入 ExprState.steps]
H --> I[ExprState.steps<br>存储指令序列,如 EEOP_INNER_VAR、EEOP_CONST]
E --> J[ExprEvalPushStep<br>添加 EEOP_DONE 指令,表示表达式完成]
J --> I
E --> K[ExecReadyExpr<br>决定执行方式,尝试 JIT 或解释执行]
K -->|尝试 JIT| L[jit_compile_expr<br>编译 `age + 10` 为机器代码,若成功直接执行]
K -->|解释执行| M[ExecReadyInterpretedExpr<br>准备解释执行,检查计划书并优化]
M --> N[ExecInitInterpreter<br>初始化解释器环境,准备内存和函数]
M --> O[Check steps validity<br>验证 ExprState.steps 不为空且以 EEOP_DONE 结束]
M --> P[Set evalfunc to ExecInterpExprStillValid<br>设置首次检查函数]
M --> Q[Optimize simple expressions<br>为简单表达式如 `age` 设置快速路径]
M --> R[Enable direct threading<br>若启用,优化指令跳转地址]
M --> S[Set evalfunc_private to ExecInterpExpr<br>设置默认解释执行函数]
M --> T[ExecInterpExprStillValid<br>检查 `age` 列有效性并执行表达式]
T --> U[CheckExprStillValid<br>验证 `age` 列与表结构匹配]
T --> V[Set evalfunc to evalfunc_private<br>设置实际执行函数,如 ExecInterpExpr]
T --> W[ExecInterpExpr<br>逐条执行指令,计算 `age + 10`]
W --> X[dispatch_table<br>分派指令,如 EEOP_CONST、EEOP_FUNCEXPR]
W --> Y[Process steps<br>执行 `取 age=30`、`加 10`,得到 40]
W --> Z[Return Result<br>返回 `age + 10` 的结果,如 40]
L --> Z