【PostgreSQL内核学习:表达式】

发布于:2025-09-13 ⋅ 阅读:(19) ⋅ 点赞:(0)

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 postgresql-18 beta2 的开源代码和《PostgresSQL数据库内核分析》一书

ExecInitExpr

  ExecInitExprPostgreSQL 查询执行引擎中的一个核心函数,用于初始化一个表达式树Expr),将其转换为可执行的状态(ExprState。表达式树通常表示 SQL 查询中的条件、计算或聚合操作(如 WHERE 子句、计算列等)。
  我将用一个简单的 SQL 查询示例,结合 ExecInitExpr 函数的每一步,通俗解释其作用。假设我们有一个 SQL 查询:

SELECT name, age + 10 AS new_age FROM users WHERE age > 30;

  这个查询中的表达式是 age + 10age > 30ExecInitExpr 的任务是把这些表达式准备成可执行的状态。下面是每一步的通俗解释,结合这个 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 为例)

  1. 检查空表达式:如果 SQL 没有 WHERE 或计算列(比如 SELECT * FROM users),直接返回空,不需要处理。
  2. 创建计划书:为表达式(如 age + 10age > 30)创建一个“执行计划书”,记录表达式和查询的上下文。
  3. 准备环境:确保表达式用到的列(比如 age)可以从表中读取,可能还包括准备函数或聚合操作。
  4. 翻译表达式:把表达式翻译成计算机能懂的步骤,比如“取 age,加 10,存结果”。
  5. 添加结束标志:在步骤最后加一个“完成”标志,告诉计算机表达式处理完了。
  6. 检查并返回:确认计划书没问题,返回给查询执行器,准备运行。

ExecCreateExprSetupSteps

  该函数是表达式初始化的“总指挥”,负责为 SQL 表达式(如 age + 10age > 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 + 10age > 30,发现需要从 users 表取 age,记录下来;如果有子查询,也会记下来。
  • ExecPushExprSetupSteps:根据记录,生成指令,比如“从 users 表取 age 字段”或“执行子查询”,确保表达式能顺利运行。

ExecInitExprRec

  ExecInitExprRecPostgreSQL 查询执行引擎中用于将 SQL 表达式(如 age + 10age > 30)递归翻译成可执行指令的核心函数
  它遍历表达式树的每个节点(如字段 age、常量 10 或运算符 +),为每个节点生成具体的执行步骤(ExprEvalStep),并将这些步骤添加到 ExprState 的步骤列表中。这些步骤告诉计算机如何一步步计算表达式的结果,比如“从表中取 age 值”“加上 10”“比较是否大于 30”。最终,这些步骤会被 ExecEvalExpr 使用来执行表达式。

主要流程(以 SQL 为例)

  对于 SELECT name, age + 10 AS new_age FROM users WHERE age > 30

  • 输入:表达式 age + 10age > 30
  • 处理过程
    • 对于 ageVar 节点):生成指令“从 users 表取 age 列”。
    • 对于 10Const 节点):生成指令“使用常量 10”。
    • 对于 +(运算符):生成指令“将 age10 相加”。
    • 对于 age > 30:类似地,为 age30> 生成相应指令。
  • 输出一组有序的执行步骤,存储在 ExprState->steps,供后续执行 age + 10age > 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 语句)来阐明其作用。表格将保持简介、易读,并以通俗的方式描述功能。


表格:ExecInitExprReccase 分支汇总

节点类型 (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 + 10age > 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_DATECURRENT_USER。生成指令调用对应的值函数。
T_XmlExpr XML 表达式 EEOP_XMLEXPR 处理 SQL 中的 XML 操作,如 XMLELEMENT(name, 'data')。生成指令计算参数并构造 XML。
T_JsonValueExpr JSON 值表达式 递归处理 raw_exprformatted_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 NULLage 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 TRUEcondition 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 查询高效执行。

为什么重要?

  ExecInitExprRecPostgreSQLSQL 表达式的抽象树转换为可执行指令的核心函数。每个 case 处理一种表达式类型(如字段、常量、函数、逻辑运算等),生成对应的指令,确保查询引擎能正确计算结果。表格清晰展示了每种节点类型的处理逻辑,帮助理解 PostgreSQL 如何将复杂 SQL 翻译为可执行步骤。

ExprEvalPushStep

  ExprEvalPushStepPostgreSQL 查询执行引擎中用于将单个表达式执行步骤(ExprEvalStep)添加到 ExprState 的步骤列表(steps)的函数。它的作用就像把一条具体的执行指令(如“从表中取 age 值”或“计算 age + 10”)写入一个“执行计划笔记本”。
  它会检查当前“笔记本”是否有空间,如果没有就初始化或扩展空间,然后将指令写入,确保表达式(如 age + 10age > 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 + 10age > 30 需要生成执行步骤:

  1. 检查是否需要初始化 steps 数组:
    • 代码if (es->steps_alloc == 0)
    • 功能:如果 steps 数组(存储指令的“笔记本”)还没创建,就分配一个能存 16 条指令的空间。
    • SQL 例子:为 age + 10 的执行计划创建一个空的“指令笔记本”,准备记录如何计算 age + 10

  1. 检查 steps 数组是否已满并扩展:

    • 代码else if (es->steps_alloc == es->steps_len)
    • 功能:如果“笔记本”满了(已有指令数等于分配的槽位数),将槽位数翻倍并重新分配更大的空间。
    • SQL 例子:如果 age + 10age > 30 的指令太多(比如超过 16 条),就把“笔记本”扩展到 32 条指令的容量。

  1. 添加新步骤到 steps 数组:

    • 代码memcpy(&es->steps[es->steps_len++], s, sizeof(ExprEvalStep))
    • 功能:将新的执行步骤(如“取 age 值”或“加 10”)复制到“笔记本”中,并增加指令计数。
    • SQL 例子:把“从 users 表取 age”或“加 10”的指令写入“笔记本”,记录在 age + 10 的执行计划中。

ExecReadyExpr

  ExecReadyExprPostgreSQL 查询执行引擎中为表达式状态(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 + 10age > 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

  ExecInterpExprPostgreSQL 查询执行引擎中负责解释执行表达式的核心函数。它通过逐条处理 ExprState 中的操作码(ExprEvalOp),执行 SQL 表达式的每一步计算(如 age + 10age > 30。函数使用一个跳转表(dispatch_table)支持高效的指令分派,处理从简单字段获取(如 age)到复杂操作(如 SUM(age)、逻辑运算 ANDJSON 处理)的各种表达式。它支持短路求值(如 AND/OR 逻辑)、NULL 处理、类型转换等,确保表达式结果正确返回,同时通过内联简单操作和外联复杂操作优化性能。这个函数是查询执行的“执行者”,将 ExecInitExprRecExecReadyInterpretedExpr 准备的计划书转化为实际结果。

/*
 * 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;
}

表格:ExecInterpExprcase 分支汇总

操作码 (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 取值 直接取 usersage 列的值。
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 usersage 值赋给结果列。
EEOP_ASSIGN_TMP 赋值临时值到结果 复制 state->resvalueresultslot 将临时计算结果(如 age + 10)存入结果列。
EEOP_ASSIGN_TMP_MAKE_RO 赋值临时值并设为只读 复制并调用 MakeExpandedObjectReadOnlyInternal age + 10 结果存为只读值,避免修改。
EEOP_CONST 获取常量值 设置 resvalueresnull 直接返回常量(如 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 取查询参数,如 $1age > $1
EEOP_PARAM_CALLBACK 获取扩展参数 调用扩展模块的 paramfunc 允许扩展模块提供参数值。
EEOP_PARAM_SET 设置参数值 调用 ExecEvalParamSet 设置动态参数值。
EEOP_CASE_TESTVAL 获取 CASE 测试值 opecontext 取值 CASE age WHEN 30 THEN ...age 值。
EEOP_DOMAIN_TESTVAL 获取域测试值 opecontext 取值 取域类型的值,类似 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 + 10SUM(age))的通俗解释。每个操作码对应一种执行步骤,支持从简单常量到复杂聚合的处理。
  • SQL 示例:以 SELECT name, age + 10 AS new_age FROM users WHERE age > 30 为基础,扩展到其他场景(如 SUM(age)CASEJSON_OBJECT),直观展示每步的作用。
  • 通俗解释:用“取值”“计算”“检查”等简单语言描述,确保易于理解。例如,EEOP_CONST 就是“直接返回 10”,EEOP_FUNCEXPR 是“执行加法 age + 10”。
  • 重要性ExecInterpExprPostgreSQL 执行 SQL 表达式的核心,直接决定了查询结果的计算效率和正确性。它通过跳转表和优化路径(如短路求值)提高性能,同时支持复杂表达式的灵活处理,是连接计划准备和结果输出的关键环节。

总结

  PostgreSQL 的表达式执行过程通过 ExecInitExprRecExprEvalPushStepExecReadyExprExecReadyInterpretedExprExecInterpExprStillValidExecInterpExpr 协同完成,从解析 SQL 表达式到生成并执行指令序列。ExecInitExprRec 将表达式(如 age + 10)分解为操作码序列,ExprEvalPushStep 将这些指令存入 ExprStateExecReadyExpr 决定执行方式(JIT 或解释执行),并调用 ExecReadyInterpretedExpr 准备解释执行环境,优化简单表达式并设置初始检查函数 ExecInterpExprStillValidExecInterpExprStillValid 验证表达式有效性后调用 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 &gt; 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

在这里插入图片描述

参考文档:

  1. Postgresql源码(85)查询执行——表达式解析器分析(select 1+1如何执行)
  2. Postgresql源码(130)ExecInterpExpr转换为IR的流程