From 46f5f7ecfae47c5a919bb486a16c476eafce8b1c Mon Sep 17 00:00:00 2001 From: TerryTongJ Date: Mon, 4 Sep 2023 10:52:29 +0800 Subject: [PATCH] Update execQual.cpp --- src/gausskernel/runtime/executor/execQual.cpp | 1693 +++++++++++++---- 1 file changed, 1275 insertions(+), 418 deletions(-) diff --git a/src/gausskernel/runtime/executor/execQual.cpp b/src/gausskernel/runtime/executor/execQual.cpp index 53f20f393..e748fd591 100644 --- a/src/gausskernel/runtime/executor/execQual.cpp +++ b/src/gausskernel/runtime/executor/execQual.cpp @@ -14,25 +14,25 @@ * ------------------------------------------------------------------------- */ /* - * INTERFACE ROUTINES - * ExecEvalExpr - (now a macro) evaluate an expression, return a datum - * ExecEvalExprSwitchContext - same, but switch into eval memory context - * ExecQual - return true/false if qualification is satisfied - * ExecProject - form a new tuple by projecting the given tuple - * - * NOTES - * The more heavily used ExecEvalExpr routines, such as ExecEvalScalarVar, - * are hotspots. Making these faster will speed up the entire system. - * - * ExecProject() is used to make tuple projections. Rather then - * trying to speed it up, the execution plan should be pre-processed - * to facilitate attribute sharing between nodes wherever possible, - * instead of doing needless copying. -cim 5/31/91 - * - * During expression evaluation, we check_stack_depth only in - * ExecMakeFunctionResult (and substitute routines) rather than at every - * single node. This is a compromise that trades off precision of the - * stack limit setting to gain speed. +* 接口例程 +* ExecEvalExpr - (现在是一个宏)计算一个表达式,返回一个数据 +* ExecEvalExprSwitchContext - 相同,但切换到 eval 内存上下文 +* ExecQual - 如果满足资格则返回 true/false +* ExecProject - 通过投影给定的元组形成一个新的元组 +* +* 注释 +* 使用较多的ExecEvalExpr例程,如ExecEvalScalarVar, +* 是热点。 使这些更快将加快整个系统的速度。 +* +* ExecProject() 用于进行元组投影。 而不是 +* 为了加快速度,应该对执行计划进行预处理 +* 尽可能促进节点之间的属性共享, +* 而不是进行不必要的复制。 -cim 5/31/91 +* +* 在表达式求值期间,我们仅检查_stack_深度 +* ExecMakeFunctionResult(和替代例程)而不是每次 +* 单节点。 这是一种折衷方案,以牺牲精度为代价 +* 堆栈限制设置以提高速度。 */ #include "postgres.h" #include "knl/knl_variable.h" @@ -74,7 +74,7 @@ #include "catalog/pg_proc_fn.h" #include "access/tuptoaster.h" -/* static function decls */ +/* static function decls 函数声明*/ static Datum ExecEvalArrayRef(ArrayRefExprState* astate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone); static bool isAssignmentIndirectionExpr(ExprState* exprstate); static Datum ExecEvalAggref(AggrefExprState* aggref, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone); @@ -157,81 +157,95 @@ static void check_huge_clob_paramter(FunctionCallInfoData* fcinfo, bool is_have_ THR_LOCAL PLpgSQL_execstate* plpgsql_estate = NULL; /* ---------------------------------------------------------------- - * ExecEvalExpr routines - * - * Recursively evaluate a targetlist or qualification expression. - * - * Each of the following routines having the signature - * Datum ExecEvalFoo(ExprState *expression, - * ExprContext *econtext, - * bool *isNull, - * ExprDoneCond *isDone); - * is responsible for evaluating one type or subtype of ExprState node. - * They are normally called via the ExecEvalExpr macro, which makes use of - * the function pointer set up when the ExprState node was built by - * ExecInitExpr. (In some cases, we change this pointer later to avoid - * re-executing one-time overhead.) - * - * Note: for notational simplicity we declare these functions as taking the - * specific type of ExprState that they work on. This requires casting when - * assigning the function pointer in ExecInitExpr. Be careful that the - * function signature is declared correctly, because the cast suppresses - * automatic checking! - * - * - * All these functions share this calling convention: - * - * Inputs: - * expression: the expression state tree to evaluate - * econtext: evaluation context information - * - * Outputs: - * return value: Datum value of result - * *isNull: set to TRUE if result is NULL (actual return value is - * meaningless if so); set to FALSE if non-null result - * *isDone: set to indicator of set-result status - * - * A caller that can only accept a singleton (non-set) result should pass - * NULL for isDone; if the expression computes a set result then an error - * will be reported via ereport. If the caller does pass an isDone pointer - * then *isDone is set to one of these three states: - * ExprSingleResult singleton result (not a set) - * ExprMultipleResult return value is one element of a set - * ExprEndResult there are no more elements in the set - * When ExprMultipleResult is returned, the caller should invoke - * ExecEvalExpr() repeatedly until ExprEndResult is returned. ExprEndResult - * is returned after the last real set element. For convenience isNull will - * always be set TRUE when ExprEndResult is returned, but this should not be - * taken as indicating a NULL element of the set. Note that these return - * conventions allow us to distinguish among a singleton NULL, a NULL element - * of a set, and an empty set. - * - * The caller should already have switched into the temporary memory - * context econtext->ecxt_per_tuple_memory. The convenience entry point - * ExecEvalExprSwitchContext() is provided for callers who don't prefer to - * do the switch in an outer loop. We do not do the switch in these routines - * because it'd be a waste of cycles during nested expression evaluation. - * ---------------------------------------------------------------- - */ +* ExecEvalExpr 例程 +* +* 递归地评估目标列表或限定表达式。 +* +* 以下每个例程都有签名 +* Datum ExecEvalFoo(ExprState *表达式, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +* 负责评估 ExprState 节点的一种类型或子类型。 +* 它们通常通过 ExecEvalExpr 宏调用,该宏使用 +* ExprState节点构建时设置的函数指针 +* 执行初始化表达式。 (在某些情况下,我们稍后会更改此指针以避免 +* 重新执行一次性开销。) +* +* 注意:为了符号简单起见,我们将这些函数声明为 +* 他们所处理的特定类型的 ExprState。 这需要在以下情况下进行铸造: +* 在 ExecInitExpr 中分配函数指针。 请注意 +* 函数签名被正确声明,因为强制转换抑制了 +* 自动检查! +* +* +* 所有这些函数都共享这个调用约定: +* +* 输入: +* 表达式:要评估的表达式状态树 +* econtext:评估上下文信息 +* +* 输出: +* 返回值:结果的数据值 +* *isNull:如果结果为 NULL,则设置为 TRUE(实际返回值为 +* 如果是这样则毫无意义); 如果结果非空则设置为 FALSE +* *isDone:设置为设置结果状态指示器 +* +* 只能接受单例(非设置)结果的调用者应该通过 +* isDone 为 NULL; 如果表达式计算出一组结果,则出现错误 +* 将通过 ereport 进行报告。 如果调用者确实传递了 isDone 指针 +* 然后 *isDone 设置为以下三种状态之一: +* ExprSingleResult 单例结果(不是集合) +* ExprMultipleResult返回值是集合中的一个元素 +* ExprEndResult 集合中没有更多元素 +* 当ExprMultipleResult返回时,调用者应该调用 +* 重复ExecEvalExpr()直到返回ExprEndResult。 表达式结束结果 +* 在最后一个实数集合元素之后返回。 为了方便 isNull 会 +* 返回 ExprEndResult 时始终设置为 TRUE,但这不应该 +* 视为指示集合的 NULL 元素。 请注意,这些返回 +* 约定允许我们区分单例 NULL、NULL 元素 +* 一个集合,一个空集合。 +* +* 调用者应该已经切换到临时内存中 +* 上下文econtext->ecxt_per_tuple_memory。 便利的切入点 +* ExecEvalExprSwitchContext() 是为不喜欢的调用者提供的 +* 在外循环中进行切换。 我们不在这些例程中进行切换 +* 因为在嵌套表达式求值期间会浪费循环。 +* ------------------------------------------------- ---------------- +*/ /* ---------- - * ExecEvalArrayRef - * - * This function takes an ArrayRef and returns the extracted Datum - * if it's a simple reference, or the modified array value if it's - * an array assignment (i.e., array element or slice insertion). - * - * NOTE: if we get a NULL result from a subscript expression, we return NULL - * when it's an array reference, or raise an error when it's an assignment. - * - * NOTE: we deliberately refrain from applying DatumGetArrayTypeP() here, - * even though that might seem natural, because this code needs to support - * both varlena arrays and fixed-length array types. DatumGetArrayTypeP() - * only works for the varlena kind. The routines we call in arrayfuncs.c - * have to know the difference (that's what they need refattrlength for). +* 执行EvalArrayRef +* +* 该函数接受 ArrayRef 并返回提取的 Datum +* 如果是简单引用,则为修改后的数组值 +* 数组赋值(即数组元素或切片插入)。 +* +* 注意:如果我们从下标表达式得到 NULL 结果,我们返回 NULL +* 当它是数组引用时,或者当它是赋值时引发错误。 +* +* 注意:我们故意不在这里应用 DatumGetArrayTypeP(), +* 尽管这看起来很自然,因为此代码需要支持 +* varlena 数组和定长数组类型。 DatumGetArrayTypeP() +* 仅适用于 varlena 类型。 我们在 arrayfuncs.c 中调用的例程 +* 必须知道区别(这就是他们需要 refattrlength 的目的)。 * ---------- */ + + static Datum ExecEvalArrayRef(ArrayRefExprState* astate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) { + //从给定的 `ArrayRefExprState` 中提取信息,包括数组引用表达式、赋值表达式以及与数组及其元素相关的各种元数据 + + + /* + 附上ArrayRef + Expr xpr: 用于支持表达式树结构的基类。 + Oid refarraytype: 数组的实际类型(数组类型本身的类型)。 + Oid refelemtype: 数组元素的类型。 + int32 reftypmod: 类型修饰符,适用于数组和其元素。 + Oid refcollid: 如果适用,表示数组元素的排序规则的 OID;如果没有排序规则,则为 InvalidOid。 + List* refupperindexpr: 一个列表,其中的表达式用于评估上层数组索引。 + List* reflowerindexpr: 一个列表,其中的表达式用于评估下层数组索引。 + Expr* refexpr: 一个表达式,用于评估为数组值的表达式。 + Expr* refassgnexpr: 如果是赋值操作,表示源值的表达式;如果是提取操作,为 NULL。 + */ ArrayRef* arrayRef = (ArrayRef*)astate->xprstate.expr; ArrayType* array_source = NULL; ArrayType* resultArray = NULL; @@ -248,16 +262,30 @@ static Datum ExecEvalArrayRef(ArrayRefExprState* astate, ExprContext* econtext, /* * If refexpr yields NULL, and it's a fetch, then result is NULL. In the * assignment case, we'll cons up something below. - */ + * 如果 refexpr 产生 NULL,并且它是一次提取,则结果为 NULL。 在分配的情况下,我们将在下面做一些事情。 + */ + /* + + 1. Datum 是无符号整型 + + 2. 如果计算结果是 NULL(*isNull 为 true),并且计算已经完成且是因为结果集结束(*isDone 的值是 ExprEndResult), + 那么返回 NULL。这表示整个结果集已经计算结束,不再有更多的结果。这通常在处理多行结果的情况下使用,当所有结果行都被处理完毕后,计算结果会返回 NULL。 + + 3. 如果计算结果是 NULL,并且不是因为结果集结束,但是这不是一个赋值表达式,那么同样返回 NULL。 + 这表示在一些情况下,计算结果为 NULL 会被忽略,例如,如果计算结果是一个函数调用的返回值,但函数返回了 NULL,并且这个结果不需要赋值给任何变量。 + */ if (*isNull) { if (isDone && *isDone == ExprEndResult) - return (Datum)NULL; /* end of set result */ + return (Datum)NULL; /* end of set result 设定结果结束 */ if (!isAssignment) return (Datum)NULL; } + ExecTableOfIndexInfo execTableOfIndexInfo; - initExecTableOfIndexInfo(&execTableOfIndexInfo, econtext); + initExecTableOfIndexInfo(&execTableOfIndexInfo, econtext); //execTableOfIndexInfo 定义 ExecEvalParamExternTableOfIndex((Node*)astate->refexpr->expr, &execTableOfIndexInfo); + + if (u_sess->SPI_cxt.cur_tableof_index != NULL) { u_sess->SPI_cxt.cur_tableof_index->tableOfIndexType = execTableOfIndexInfo.tableOfIndexType; u_sess->SPI_cxt.cur_tableof_index->tableOfIndex = execTableOfIndexInfo.tableOfIndex; @@ -289,7 +317,10 @@ static Datum ExecEvalArrayRef(ArrayRefExprState* astate, ExprContext* econtext, pfree(DatumGetPointer(exprValue)); } if (execTableOfIndexInfo.isnestedtable) { - /* for nested table, we should take inner table's array and skip current indx */ + /* + for nested table, we should take inner table's array and skip current indx + 对于嵌套表格(nested table),应该获取内部表格(inner table)的数组,并跳过当前的索引 + */ if (node == NULL || index == -1) { eisnull = true; } else { @@ -350,14 +381,15 @@ static Datum ExecEvalArrayRef(ArrayRefExprState* astate, ExprContext* econtext, return (Datum)NULL; } } - /* this can't happen unless parser messed up */ + /* this can't happen unless parser messed up 解析器出现错误的情况下才会发生这种情况 */ if (i != j) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmodule(MOD_EXECUTOR), (errmsg("upper and lower index lists are not same length (%d, %d)", i, j)))); lIndex = lower.indx; - } else + } + else lIndex = NULL; if (isAssignment) { @@ -378,13 +410,23 @@ static Datum ExecEvalArrayRef(ArrayRefExprState* astate, ExprContext* econtext, * * Since fetching the old element might be a nontrivial expense, do it * only if the argument appears to actually need it. + * + * 如果在赋值操作中存在嵌套情况。 + * 在这种情况下,refassgnexpr 本身可能是一个 FieldStore 或 ArrayRef, + * 需要获取并修改要替换的数组元素或片段的先前值。如果是这种情况,就需要从数组中提取该值, + * 并通过 econtext 的 caseValue 传递下去。可以安全地重用 CASE 机制, + * 因为在这里和需要值的地方之间不会出现 CASE,而数组赋值也不能在 CASE 中出现。 + * (因此保存和恢复 caseValue 只是一种谨慎措施,但还是要这样做。) + * + * 由于获取旧元素可能是一个较大的开销,只有在实际需要的情况下才进行这个操作。 + * 也就是说,只有在嵌套赋值操作中,需要对原来数组元素进行修改时,才会进行这个额外的操作,以避免不必要的性能开销 */ save_datum = econtext->caseValue_datum; save_isNull = econtext->caseValue_isNull; if (isAssignmentIndirectionExpr(astate->refassgnexpr)) { if (*isNull) { - /* whole array is null, so any element or slice is too */ + /* 整个数组是空值(NULL),因此任何元素或片段也都是空值(NULL)。 */ econtext->caseValue_datum = (Datum)0; econtext->caseValue_isNull = true; } else if (lIndex == NULL) { @@ -409,13 +451,15 @@ static Datum ExecEvalArrayRef(ArrayRefExprState* astate, ExprContext* econtext, econtext->caseValue_isNull = false; } } else { - /* argument shouldn't need caseValue, but for safety set it null */ + /* 参数本身不应该需要caseValue,但出于安全考虑,将其设置为NULL。 */ econtext->caseValue_datum = (Datum)0; econtext->caseValue_isNull = true; } /* - * Evaluate the value to be assigned into the array. + * 计算要赋值到数组中的值。这是指在赋值操作中, + * 需要对要赋给数组元素或切片的值进行求值的过程。 + * 系统会根据表达式计算出一个值,然后将这个值赋给数组中的指定位置。 */ sourceData = ExecEvalExpr(astate->refassgnexpr, econtext, &eisnull, NULL); @@ -423,9 +467,12 @@ static Datum ExecEvalArrayRef(ArrayRefExprState* astate, ExprContext* econtext, econtext->caseValue_isNull = save_isNull; /* - * For an assignment to a fixed-length array type, both the original - * array and the value to be assigned into it must be non-NULL, else - * we punt and return the original array. + * 对于一个固定长度数组类型的赋值操作, + * 原始数组和要赋给它的值都必须是非NULL的, + * 否则我们会放弃赋值操作并返回原始数组。 + * + * 确保在进行赋值操作时,原始数组和要赋值的值都是有效的,否则可能会导致不正确的结果。 + * 如果原始数组或要赋值的值为NULL,那么该赋值操作会被忽略,返回原始数组。 */ if (astate->refattrlength > 0) /* fixed-length array? */ if (eisnull || *isNull) @@ -436,6 +483,10 @@ static Datum ExecEvalArrayRef(ArrayRefExprState* astate, ExprContext* econtext, * by substituting an empty (zero-dimensional) array; insertion of the * new element will result in a singleton array value. It does not * matter whether the new element is NULL. + * 对于对可变长度数组的赋值操作,我们处理原始数组为NULL的情况, + * 通过替换为空(零维)数组;插入新元素将导致一个单一元素的数组值。 + * 新元素是否为NULL并不重要。 + * 在这种情况下,将使用空数组作为原始数组的替代,以确保新元素可以正确插入并形成新的数组值。 */ if (*isNull) { array_source = construct_empty_array(arrayRef->refelemtype); @@ -465,7 +516,7 @@ static Datum ExecEvalArrayRef(ArrayRefExprState* astate, ExprContext* econtext, astate->refelemalign); return PointerGetDatum(resultArray); } - /* for nested table, if get inner table's elem, need cover elem type */ + /* 对于嵌套表,如果获取内部表的元素,需要考虑元素的类型。 */ if (list_length(astate->refupperindexpr) > i && i > 0 && plpgsql_estate) { if (plpgsql_estate->curr_nested_table_type != typOid) { plpgsql_estate->curr_nested_table_type = ARR_ELEMTYPE(array_source); @@ -478,7 +529,7 @@ static Datum ExecEvalArrayRef(ArrayRefExprState* astate, ExprContext* econtext, if (lIndex == NULL) { if (unlikely(i == 0)) { - /* get nested table's inner table */ + /* 获取嵌套表的内部表 */ *isNull = eisnull; return (Datum)array_source; } else { @@ -505,11 +556,17 @@ static Datum ExecEvalArrayRef(ArrayRefExprState* astate, ExprContext* econtext, } /* - * Helper for ExecEvalArrayRef: is expr a nested FieldStore or ArrayRef - * that might need the old element value passed down? - * - * (We could use this in ExecEvalFieldStore too, but in that case passing - * the old value is so cheap there's no need.) + * 用于 `ExecEvalArrayRef` 的辅助函数: + * 函数判断给定的表达式是否是嵌套的 `FieldStore` 或者 `ArrayRef`,并且是否可能需要将旧元素值传递下来。 + * + * 需要注意的是,虽然在 `ExecEvalFieldStore` 中传递旧值非常廉价, + * 但是在 `ExecEvalArrayRef` 中,可能需要进行更多的操作,所以才有了这个函数的判断。 + * + * + * 检查给定的 exprstate 是否为 FieldStoreState 或 ArrayRefExprState 类型的节点。 + * 如果是 FieldStoreState,那么它会进一步检查其 arg 是否为 CaseTestExpr。类似地, + * 如果是 ArrayRefExprState,那么它会检查其 refexpr 是否为 CaseTestExpr。 + * 如果这些条件成立,函数会返回 true,表示这个表达式可能需要将旧元素值传递下来。 */ static bool isAssignmentIndirectionExpr(ExprState* exprstate) { @@ -534,6 +591,9 @@ static bool isAssignmentIndirectionExpr(ExprState* exprstate) * * Returns a Datum whose value is the value of the precomputed * aggregate found in the given expression context. + * `ExecEvalAggref` 函数用于在给定的表达式上下文中返回预先计算的聚合的值。 + * 它会计算并返回一个 `Datum` 值,这个值是在给定的表达式上下文中找到的预计算聚合的值。 + * 这通常用于计算聚合函数的结果,以便将其集成到查询执行中。 * ---------------------------------------------------------------- */ static Datum ExecEvalAggref(AggrefExprState* aggref, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -556,6 +616,8 @@ static Datum ExecEvalAggref(AggrefExprState* aggref, ExprContext* econtext, bool * * Returns a Datum whose value is the value of the precomputed * window function found in the given expression context. + * 首先会进行一些安全检查,检查窗口函数表达式状态是否有效。 + * 从窗口函数状态中获取相应的计算结果,并将其返回作为一个 Datum 值。 * ---------------------------------------------------------------- */ static Datum ExecEvalWindowFunc(WindowFuncExprState* wfunc, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -582,6 +644,14 @@ static Datum ExecEvalWindowFunc(WindowFuncExprState* wfunc, ExprContext* econtex * Note: ExecEvalScalarVar is executed only the first time through in a given * plan; it changes the ExprState's function pointer to pass control directly * to ExecEvalScalarVarFast after making one-time checks. + * + * ExecEvalScalarVar用于在给定的表达式上下文中返回一个标量(非整行)范围变量的值。 + * 接收一个表达式上下文作为参数,并根据该上下文返回一个Datum值,该值是与特定范围变量相关联的值。 + * + * 需要注意的是,ExecEvalScalarVar函数仅在执行计划的第一次迭代中执行一次。 + * 在第一次执行时,它会进行一些一次性的检查,并将ExprState的函数指针更改为直接将控制权传递给ExecEvalScalarVarFast函数。 + * + * 此后,在后续的执行中,会直接调用ExecEvalScalarVarFast函数来执行计算,以避免重复的一次性检查。 * ---------------------------------------------------------------- */ static Datum ExecEvalScalarVar(ExprState* exprstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -589,11 +659,17 @@ static Datum ExecEvalScalarVar(ExprState* exprstate, ExprContext* econtext, bool Var* variable = (Var*)exprstate->expr; TupleTableSlot* slot = NULL; AttrNumber attnum; +/* + 这段代码的作用是获取变量(Var)表达式节点中的信息,以便后续从相应的槽中获取对应的属性值。 + - `Var* variable = (Var*)exprstate->expr;`:将表达式节点 `exprstate` 转换为变量(Var)类型,以便访问其属性。 + - `TupleTableSlot* slot = NULL;`:初始化一个指向槽的指针,稍后会根据变量的来源设置对应的槽。 + - `AttrNumber attnum;`:初始化一个属性编号变量,稍后会根据变量的属性编号设置其值。 +*/ if (isDone != NULL) *isDone = ExprSingleResult; - /* Get the input slot and attribute number we want */ + /* 获取我们想要的输入插槽(slot)和属性编号(attribute number) */ switch (variable->varno) { case INNER_VAR: /* get the tuple from the inner node */ slot = econtext->ecxt_innertuple; @@ -632,12 +708,23 @@ static Datum ExecEvalScalarVar(ExprState* exprstate, ExprContext* econtext, bool * generated by ExecTypeFromTL(), and that can't guarantee to generate an * accurate typmod in all cases, because some expression node types don't * carry typmod. + * + * + * 如果它是用户属性,检查有效性(虚假的系统属性号将在表的 `getattr` 内部被捕获)。我们在这里要检查的是一个可能性, + * 即自计划树创建以来,属性的类型是否发生了更改。理想情况下,计划将会失效并且不会被重新使用, + * 但为了万一,我们保留了这些防护措施。幸运的是,在第一次执行时进行一次检查就足够了。 + * + * 注意:我们允许引用已删除的属性。在这种情况下,表的 `getattr` 将会强制返回一个 NULL 结果。 + * + * 注意:理想情况下,我们还应该检查 `typmod` 以及 `typid`, + * 但目前这似乎不太实际:在许多情况下,元组描述符将会由 `ExecTypeFromTL()` 生成, + * 但不能保证在所有情况下都能生成准确的 `typmod`,因为某些表达式节点类型不携带 `typmod`。 */ if (attnum > 0) { TupleDesc slot_tupdesc = slot->tts_tupleDescriptor; Form_pg_attribute attr; - if (attnum > slot_tupdesc->natts) /* should never happen */ + if (attnum > slot_tupdesc->natts) /* 这是不应该出现的错误,如果出现则按下面方式报错 */ ereport(ERROR, (errcode(ERRCODE_INVALID_ATTRIBUTE), errmodule(MOD_EXECUTOR), @@ -645,7 +732,7 @@ static Datum ExecEvalScalarVar(ExprState* exprstate, ExprContext* econtext, bool attr = slot_tupdesc->attrs[attnum - 1]; - /* can't check type if dropped, since atttypid is probably 0 */ + /* 如果属性被删除,无法检查类型,因为`atttypid`可能为0。 */ if (!attr->attisdropped) { if (variable->vartype != attr->atttypid) ereport(ERROR, @@ -658,10 +745,10 @@ static Datum ExecEvalScalarVar(ExprState* exprstate, ExprContext* econtext, bool } } - /* Skip the checking on future executions of node */ + /* 在将来的节点执行中跳过检查。 */ exprstate->evalfunc = ExecEvalScalarVarFast; - /* Fetch the value from the slot */ + /* 从槽中获取值 */ return tableam_tslot_getattr(slot, attnum, isNull); } @@ -669,6 +756,11 @@ static Datum ExecEvalScalarVar(ExprState* exprstate, ExprContext* econtext, bool * ExecEvalScalarVarFast * * Returns a Datum for a scalar variable. + + + * 这个函数是在 ExecEvalScalarVar 函数中执行的第一次后续执行中调用的,目的是跳过一些一次性的检查和设置 + * 这段代码为标量变量的快速评估提供了一种优化方法,跳过了一些不需要在每次执行中重复执行的检查。 + * * ---------------------------------------------------------------- */ static Datum ExecEvalScalarVarFast(ExprState* exprstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -680,7 +772,9 @@ static Datum ExecEvalScalarVarFast(ExprState* exprstate, ExprContext* econtext, if (isDone != NULL) *isDone = ExprSingleResult; - /* Get the input slot and attribute number we want */ + /* Get the input slot and attribute number we want + INNER_VAR:从内部节点获取元组。 + OUTER_VAR:从外部节点获取元组。*/ switch (variable->varno) { case INNER_VAR: /* get the tuple from the inner node */ slot = econtext->ecxt_innertuple; @@ -713,11 +807,21 @@ static Datum ExecEvalScalarVarFast(ExprState* exprstate, ExprContext* econtext, * given plan; it changes the ExprState's function pointer to pass control * directly to ExecEvalWholeRowFast or ExecEvalWholeRowSlow after making * one-time checks. + * + * 用于从槽中获取整行范围变量(Whole-Row Range Variable)的值。 + * 这个函数在执行计划的第一次后续执行中调用,目的是跳过一些一次性的检查和设置, + * 并根据情况将控制权传递给 ExecEvalWholeRowFast 或 ExecEvalWholeRowSlow 函数。 + * * ---------------------------------------------------------------- */ -static Datum ExecEvalWholeRowVar( - WholeRowVarExprState* wrvstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) +static Datum ExecEvalWholeRowVar(WholeRowVarExprState* wrvstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) { + + /* + * Var* variable = (Var*)wrvstate->xprstate.expr;:将表达式节点 xprstate 转换为变量(Var)类型,以便访问其属性。 + * TupleTableSlot* slot = NULL;:初始化一个指向槽的指针,稍后会根据变量的来源设置对应的槽。 + * bool needslow = false;:初始化一个标志,用于表示是否需要慢速方式获取整行变量的值。 + */ Var* variable = (Var*)wrvstate->xprstate.expr; TupleTableSlot* slot = NULL; bool needslow = false; @@ -725,7 +829,7 @@ static Datum ExecEvalWholeRowVar( if (isDone != NULL) *isDone = ExprSingleResult; - /* This was checked by ExecInitExpr */ + /* This was checked by ExecInitExpr 属性编号 varattno 应为 InvalidAttrNumber 这一条件已经在 ExecInitExpr 函数中进行了检查 */ Assert(variable->varattno == InvalidAttrNumber); /* Get the input slot we want */ @@ -745,16 +849,14 @@ static Datum ExecEvalWholeRowVar( } /* - * If the input tuple came from a subquery, it might contain "resjunk" - * columns (such as GROUP BY or ORDER BY columns), which we don't want to - * keep in the whole-row result. We can get rid of such columns by - * passing the tuple through a JunkFilter --- but to make one, we have to - * lay our hands on the subquery's targetlist. Fortunately, there are not - * very many cases where this can happen, and we can identify all of them - * by examining our parent PlanState. We assume this is not an issue in - * standalone expressions that don't have parent plans. (Whole-row Vars - * can occur in such expressions, but they will always be referencing - * table rows.) + 如果输入的元组来自子查询,它可能包含一些 "resjunk" 列(比如 GROUP BY 或 ORDER BY 列), + 我们不希望将这些列保留在整行结果中。 + 通过将元组通过 JunkFilter 过滤,我们可以摆脱这些列。 + 然而,为了创建一个 JunkFilter,我们需要获得子查询的目标列表。 + 但是有很少的情况会出现这种情况,我们可以通过检查父级 PlanState 来识别所有这些情况。 + 我们假设在不具有父计划的独立表达式中,这不是一个问题。(虽然整行变量可能出现在这些表达式中,但它们总是引用表行。) + + 下面是对父级PlanState的识别 */ if (wrvstate->parent) { PlanState* subplan = NULL; @@ -774,7 +876,7 @@ static Datum ExecEvalWholeRowVar( bool junk_filter_needed = false; ListCell* tlist = NULL; - /* Detect whether subplan tlist actually has any junk columns */ + /* 检测子查询计划的目标列表是否实际上有任何 "resjunk" 列。 */ foreach (tlist, subplan->plan->targetlist) { TargetEntry* tle = (TargetEntry*)lfirst(tlist); @@ -784,7 +886,12 @@ static Datum ExecEvalWholeRowVar( } } - /* If so, build the junkfilter in the query memory context */ + /* If so, build the junkfilter in the query memory context + 如果需要构建 JunkFilter,我们会在查询内存上下文中构建它。 + + 切换到查询内存上下文,然后使用子查询计划的目标列表初始化 JunkFilter。 + 还需要确定是否需要在 TupleSlot 中存储 OID。 + */ if (junk_filter_needed) { MemoryContext oldcontext; @@ -797,13 +904,14 @@ static Datum ExecEvalWholeRowVar( } } - /* Apply the junkfilter if any */ + /* Apply the junkfilter if any 如果存在junk列则运用过滤器junkFilter */ if (wrvstate->wrv_junkFilter != NULL) slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot); /* * If the Var identifies a named composite type, we must check that the * actual tuple type is compatible with it. + * 如果变量标识了一个命名的复合类型,我们必须检查实际的元组类型是否与之兼容。 */ if (variable->vartype != RECORDOID) { TupleDesc var_tupdesc; @@ -821,6 +929,12 @@ static Datum ExecEvalWholeRowVar( * regardless of the dropped column type). If we find a dropped * column and cannot verify that case (1) holds, we have to use * ExecEvalWholeRowSlow to check (2) for each row. + * 我们实际上只关心属性的数量和数据类型。 + * 另外,在目标类型中被删除的列上的类型不匹配可以忽略, + * 只要(1)物理存储匹配,或者(2)实际列值为 NULL。 + * 情况(1)有助于处理一些关于过时的缓存计划的情况, + * 而情况(2)在某些情况下是期望的行为,比如在将数据插入到一个包含删除列的表中(规划器通常会生成一个 INT4 NULL,无论删除列的类型是什么)。 + * 如果我们发现了一个被删除的列,并且无法验证情况(1),那么我们必须使用 `ExecEvalWholeRowSlow` 在每一行中检查情况(2)。 */ var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1); @@ -837,6 +951,11 @@ static Datum ExecEvalWholeRowVar( var_tupdesc->natts))); for (i = 0; i < var_tupdesc->natts; i++) { + /* + 遍历了两个元组描述符的属性,对于每个属性,它比较属性的类型(atttypid)。如果类型匹配,就继续检查下一个属性。 + 如果类型不匹配,会根据属性是否被删除(attisdropped)进行不同的错误报告, + 以及在类型不匹配但长度和对齐方式相同的情况下,将 needslow 设置为 true,表示需要在运行时检查是否为 null。 + */ Form_pg_attribute vattr = var_tupdesc->attrs[i]; Form_pg_attribute sattr = slot_tupdesc->attrs[i]; @@ -872,6 +991,12 @@ static Datum ExecEvalWholeRowVar( * ExecEvalWholeRowFast * * Returns a Datum for a whole-row variable. + * 一旦第一次执行时完成了检查,后续的执行就可以跳过这些检查, + * 因为在第一次执行时已经确定了这些属性的兼容性或其他信息,后续的执行中不需要再次执行这些检查。 + * + * 这段代码的目的是为了将整行变量表示的元组数据复制到新的内存中, + * 并返回一个指向这块内存的指针作为 Datum 值。 + * 这样,在执行整行变量表达式时,就可以使用这个 Datum 值来表示整行的数据。 * ---------------------------------------------------------------- */ static Datum ExecEvalWholeRowFast( @@ -913,6 +1038,8 @@ static Datum ExecEvalWholeRowFast( * If it's a RECORD Var, we'll use the slot's type ID info. It's likely * that the slot's type is also RECORD; if so, make sure it's been * "blessed", so that the Datum can be interpreted later. + * 当整行变量的类型为 RECORD 时,它可能在槽中使用相同的 RECORD 类型。 + * 如果是这种情况,需要确保槽中的 RECORD 类型已经被“标记”(blessed),以便稍后可以正确地解释这个 Datum 值 */ slot_tupdesc = slot->tts_tupleDescriptor; if (variable->vartype == RECORDOID) { @@ -926,6 +1053,9 @@ static Datum ExecEvalWholeRowFast( /* * We have to make a copy of the tuple so we can safely insert the Datum * overhead fields, which are not set in on-disk tuples. + * + * 这段代码使用 palloc 函数分配了一块内存, + * 大小为原始元组的长度(tuple->t_len)。然后,通过 memcpy_s 函数将原始元组的数据复制到新分配的内存中,以创建副本。 */ dtuple = (HeapTupleHeader)palloc(tuple->t_len); rc = memcpy_s((char*)dtuple, tuple->t_len, (char*)tuple->t_data, tuple->t_len); @@ -936,6 +1066,9 @@ static Datum ExecEvalWholeRowFast( /* * If the Var identifies a named composite type, label the tuple with that * type; otherwise use what is in the tupleDesc. + * 检查整行变量(Var)是否标识了一个命名的复合类型。 + * 如果整行变量的类型是命名的复合类型(RECORDOID),则使用元组的描述信息(tupleDesc)来设置元组的类型标识和类型修饰符。 + * 最后,代码将指向新副本的指针转换为 Datum 类型,并返回给调用者。 */ if (variable->vartype != RECORDOID) { HeapTupleHeaderSetTypeId(dtuple, variable->vartype); @@ -953,6 +1086,7 @@ static Datum ExecEvalWholeRowFast( * * Returns a Datum for a whole-row variable, in the "slow" case where * we can't just copy the subplan's output. + * 在无法直接复制子查询输出的“慢速”情况下,针对整行变量返回一个 Datum 值。 * ---------------------------------------------------------------- */ static Datum ExecEvalWholeRowSlow( @@ -971,7 +1105,11 @@ static Datum ExecEvalWholeRowSlow( *isDone = ExprSingleResult; *isNull = false; - /* Get the input slot we want */ + /* Get the input slot we want + * 如果变量的 varno 是 INNER_VAR,表示需要从内部节点获取元组数据,那么就将 slot 设置为 econtext 的 ecxt_innertuple,即从内部节点获取元组数据。 + * 如果变量的 varno 是 OUTER_VAR,表示需要从外部节点获取元组数据,那么就将 slot 设置为 econtext 的 ecxt_outertuple,即从外部节点获取元组数据。 + * 对于其他情况,即不是 INNER_VAR 也不是 OUTER_VAR,那么默认情况是从关系(表)进行扫描的节点获取元组数据,将 slot 设置为 econtext 的 ecxt_scantuple。 + */ switch (variable->varno) { case INNER_VAR: /* get the tuple from the inner node */ slot = econtext->ecxt_innertuple; @@ -1015,6 +1153,9 @@ static Datum ExecEvalWholeRowSlow( /* * We have to make a copy of the tuple so we can safely insert the Datum * overhead fields, which are not set in on-disk tuples. + * 过复制原始元组数据并在其头部插入所需的信息,生成一个新的堆元组。 + * 这种方法能够确保新的堆元组可以正确地被解释和处理。 + * 这种处理方式在无法直接复制整个子计划输出的情况下,生成一个“慢速”版本的整行数据,以供后续的处理和解释。 */ dtuple = (HeapTupleHeader)palloc(tuple->t_len); rc = memcpy_s((char*)dtuple, tuple->t_len, (char*)tuple->t_data, tuple->t_len); @@ -1037,6 +1178,8 @@ static Datum ExecEvalWholeRowSlow( * Note that for pass-by-ref datatypes, we return a pointer to the * actual constant node. This is one of the reasons why functions * must treat their input arguments as read-only. + * ExecEvalConst 函数用于在给定的表达式上下文中计算常量表达式,并返回结果值。 + * 如果常量是游标类型的话,还会将游标选项数据复制到上下文中。 * ---------------------------------------------------------------- */ static Datum ExecEvalConst(ExprState* exprstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -1059,6 +1202,7 @@ static Datum ExecEvalConst(ExprState* exprstate, ExprContext* econtext, bool* is /* ---------------------------------------------------------------- * ExecEvalRownum: Returns the rownum + * ExecEvalRownum 函数用于计算行号表达式的结果,返回表示行号的数据类型值。 * ---------------------------------------------------------------- */ static Datum ExecEvalRownum(RownumState* exprstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -1078,6 +1222,7 @@ static Datum ExecEvalRownum(RownumState* exprstate, ExprContext* econtext, bool* * ExecEvalParamExec * * Returns the value of a PARAM_EXEC parameter. + * ExecEvalParamExec 函数用于计算 PARAM_EXEC 参数的值,通过执行与参数关联的子计划来获取参数值,并更新相应的参数执行数据。 * ---------------------------------------------------------------- */ static Datum ExecEvalParamExec(ExprState* exprstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -1111,6 +1256,10 @@ static Datum ExecEvalParamExec(ExprState* exprstate, ExprContext* econtext, bool * ExecEvalParamExtern * * Returns the value of a PARAM_EXTERN parameter. + * + * + * ExecEvalParamExtern 函数用于计算 PARAM_EXTERN 参数的值, + * 从外部参数列表中获取参数值,并在必要时检查参数数据类型的匹配性。如果找不到参数,函数会抛出错误。 * ---------------------------------------------------------------- */ static Datum ExecEvalParamExtern(ExprState* exprstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -1124,16 +1273,25 @@ static Datum ExecEvalParamExtern(ExprState* exprstate, ExprContext* econtext, bo /* * PARAM_EXTERN parameters must be sought in ecxt_param_list_info. + * 系统会将外部传入的参数存储在 ecxt_param_list_info 中。 + * 当需要计算 PARAM_EXTERN 参数的值时,就可以通过参数编号从该列表中获取相应的参数值。 */ if (paramInfo && thisParamId > 0 && thisParamId <= paramInfo->numParams) { ParamExternData* prm = ¶mInfo->params[thisParamId - 1]; - /* give hook a chance in case parameter is dynamic */ + /* give hook a chance in case parameter is dynamic + * 当参数是动态的时候,意味着参数的值在查询执行过程中可能会发生变化, + * 而不是在执行计划生成时就固定下来的。 + * 钩子函数(Hook)允许外部的处理逻辑来计算或获取参数的最新值,以便在执行表达式时使用。 + */ if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL) (*paramInfo->paramFetch)(paramInfo, thisParamId); if (OidIsValid(prm->ptype)) { - /* safety check in case hook did something unexpected */ + /* + * safety check in case hook did something unexpected + * 系统在调用钩子函数之后进行了一个检查,确保钩子函数返回的数据类型与预期的参数类型匹配。 + */ if (prm->ptype != expression->paramtype) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), @@ -1157,6 +1315,11 @@ static Datum ExecEvalParamExtern(ExprState* exprstate, ExprContext* econtext, bo return (Datum)0; /* keep compiler quiet */ } + +/* + * 用于初始化类型为`ExecTableOfIndexInfo`的结构体。 + * 结构体用于管理与索引相关的一些信息 + */ void initExecTableOfIndexInfo(ExecTableOfIndexInfo* execTableOfIndexInfo, ExprContext* econtext) { execTableOfIndexInfo->econtext = econtext; @@ -1168,7 +1331,10 @@ void initExecTableOfIndexInfo(ExecTableOfIndexInfo* execTableOfIndexInfo, ExprCo execTableOfIndexInfo->paramtype = InvalidOid; } -/* this function is only used for getting table of index inout param */ +/* this function is only used for getting table of index inout param + * 用于从传入的节点中获取与索引表相关的输入参数信息,并将这些信息填充到提供的 ExecTableOfIndexInfo 结构体中。 + * 用于处理获取索引表输入参数的情况。 + */ static bool get_tableofindex_param(Node* node, ExecTableOfIndexInfo* execTableOfIndexInfo) { if (node == NULL) @@ -1181,6 +1347,11 @@ static bool get_tableofindex_param(Node* node, ExecTableOfIndexInfo* execTableOf return false; } + +/* + * 判断给定的函数 OID 是否属于一组与表类型功能相关的函数。 + * 它会检查给定的函数 OID 是否在一组特定的 OID 范围内,这个范围可能与数组函数相关,或者函数 OID 是否与特定的与数组索引删除相关的函数 OID 相匹配。 + */ static bool IsTableOfFunc(Oid funcOid) { const Oid array_function_start_oid = 7881; @@ -1193,7 +1364,10 @@ static bool IsTableOfFunc(Oid funcOid) /* ---------------------------------------------------------------- * ExecEvalParamExternTableOfIndex * - * Returns the value of a PARAM_EXTERN table of index and type parameter . + * Returns the value of a PARAM_EXTERN table of index and type parameter .\ + * 获取一个 PARAM_EXTERN 类型的参数,该参数被假设为指向一个索引表的表达式。 + * 如果传入的节点是一个 Param,函数会从该节点中提取必要的信息,将这些信息存储到 ExecTableOfIndexInfo 结构中, + * 然后调用 ExecEvalParamExternTableOfIndexById 函数来处理这个参数。 * ---------------------------------------------------------------- */ void ExecEvalParamExternTableOfIndex(Node* node, ExecTableOfIndexInfo* execTableOfIndexInfo) @@ -1203,6 +1377,14 @@ void ExecEvalParamExternTableOfIndex(Node* node, ExecTableOfIndexInfo* execTable } } + +/* + * ExecEvalParamExternTableOfIndexById + * + * 根据参数信息从 ecxt_param_list_info 中寻找匹配的 PARAM_EXTERN 参数, + * 如果找到,并且参数信息包含了索引表的相关信息,就将这些信息存储到 ExecTableOfIndexInfo 结构中,然后返回 true。 + * 如果没有找到匹配的参数,或者找到的参数信息不包含索引表的相关信息,就返回 false。 + */ bool ExecEvalParamExternTableOfIndexById(ExecTableOfIndexInfo* execTableOfIndexInfo) { if (execTableOfIndexInfo->paramid == -1) { @@ -1257,6 +1439,9 @@ bool ExecEvalParamExternTableOfIndexById(ExecTableOfIndexInfo* execTableOfIndexI * to use these. Ex: overpaid(EMP) might call GetAttributeByNum(). * Note: these are actually rather slow because they do a typcache * lookup on each call. + * 用于在执行 ExecEvalOper 或 ExecEvalFunc 时, + * 从给定的 HeapTupleHeader 中获取特定属性号(attrno)对应的属性值。 + * 这些函数主要用于在C函数中处理元组属性时调用 */ Datum GetAttributeByNum(HeapTupleHeader tuple, AttrNumber attrno, bool* isNull) { @@ -1288,6 +1473,9 @@ Datum GetAttributeByNum(HeapTupleHeader tuple, AttrNumber attrno, bool* isNull) * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set all * the fields in the struct just in case user tries to inspect system * columns. + * + * 确保了在调用 heap_getattr 函数时,提供了足够的上下文信息,以便准确地处理属性的访问。 + * 如果只传递裸露的 HeapTupleHeader,可能会缺少必要的上下文信息,导致属性访问的错误或异常行为。 */ tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); ItemPointerSetInvalid(&(tmptup.t_self)); @@ -1308,7 +1496,15 @@ Datum GetAttributeByNum(HeapTupleHeader tuple, AttrNumber attrno, bool* isNull) return result; } - +/* + * GetAttributeName + * + * 该函数会遍历给定的元组数据,查找与给定属性名匹配的属性,然后返回该属性的值 + * + * + * 主要目的是根据属性名获取元组中的属性值,并在必要时进行错误处理和警告。 + * 它是执行查询计划中的关键步骤之一,用于正确计算表达式中涉及的属性值。 + */ Datum GetAttributeByName(HeapTupleHeader tuple, const char* attname, bool* isNull) { AttrNumber attrno; @@ -1328,7 +1524,9 @@ Datum GetAttributeByName(HeapTupleHeader tuple, const char* attname, bool* isNul errmsg("a NULL isNull pointer was passed when get attribute by name."))); if (tuple == NULL) { - /* Kinda bogus but compatible with old behavior... */ + /* Kinda bogus but compatible with old behavior... + 返回了一些虚假的值,这样做是为了与以前的代码行为保持一致,以便在旧的代码中继续正常工作。 + */ *isNull = true; return (Datum)0; } @@ -1355,6 +1553,9 @@ Datum GetAttributeByName(HeapTupleHeader tuple, const char* attname, bool* isNul * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set all * the fields in the struct just in case user tries to inspect system * columns. + * heap_getattr 需要一个 HeapTuple 而不是裸露的 HeapTupleHeader。 + * 为了避免用户试图检查系统列时的问题,函数在使用之前设置了 HeapTupleData 结构体的所有字段。 + * 在使用 heap_getattr 获取属性值时,能够提供足够的上下文信息以及元数据,从而正确地访问属性值并避免可能的问题。 */ tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); ItemPointerSetInvalid(&(tmptup.t_self)); @@ -1382,6 +1583,10 @@ Datum GetAttributeByName(HeapTupleHeader tuple, const char* attname, bool* isNul * @inPara actual_arg_types: the type array of actual func args'. * @inPara fcache: the FuncExprState of this functin. * @return Oid: the real func return type. + * + * 此函数用于根据实际函数参数类型查找函数的真实返回类型。 + * 函数接受三个参数: + * arg_num 表示函数的参数数量,actual_arg_types 是实际函数参数的类型数组,fcache 是 FuncExprState 结构,表示函数表达式的状态。 */ static Oid getRealFuncRetype(int arg_num, Oid* actual_arg_types, FuncExprState* fcache) { @@ -1399,7 +1604,9 @@ static Oid getRealFuncRetype(int arg_num, Oid* actual_arg_types, FuncExprState* oidvector* proargs = ProcedureGetArgTypes(proctup); Oid* declared_arg_types = proargs->values; - /* Find the real return type based on the declared arg types and actual arg types.*/ + /* Find the real return type based on the declared arg types and actual arg types. + 根据函数声明的参数类型和实际传入的参数类型来确定函数的真实返回类型。 + */ rettype = enforce_generic_type_consistency(actual_arg_types, declared_arg_types, arg_num, rettype, false); ReleaseSysCache(proctup); @@ -1408,13 +1615,15 @@ static Oid getRealFuncRetype(int arg_num, Oid* actual_arg_types, FuncExprState* /* * Check whether the function is a set function supported by the vector engine. + * 接受一个函数的 Oid(对象标识符)作为参数,然后根据这个 Oid 的值判断是否是向量引擎支持的集合函数。 */ static bool isVectorEngineSupportSetFunc(Oid funcid) { switch (funcid) { case OID_REGEXP_SPLIT_TO_TABLE: // regexp_split_to_table - case OID_REGEXP_SPLIT_TO_TABLE_NO_FLAG: // regexp_split_to_table - case OID_ARRAY_UNNEST: // unnest + // regexp_split_to_table 用于基于正则表达式将字符串拆分为多个子字符串,并将这些子字符串作为单独的行返回。 + case OID_REGEXP_SPLIT_TO_TABLE_NO_FLAG: + case OID_ARRAY_UNNEST: // unnest 用于展开一个数组或多维数组,并将其元素作为单独的行返回。 return true; break; default: @@ -1423,8 +1632,11 @@ static bool isVectorEngineSupportSetFunc(Oid funcid) } } + /* * init_fcache - initialize a FuncExprState node during first use + * 用于在首次使用时初始化 FuncExprState 结构的函数 init_fcache。 + * 在查询计划中执行函数表达式时,这个函数用于设置函数的执行状态和所需的上下文。 */ template static void init_fcache( @@ -1443,6 +1655,10 @@ static void init_fcache( * fail, as parser should check sooner. But possibly it might fail if * server has been compiled with FUNC_MAX_ARGS smaller than some functions * declared in pg_proc? + * 对函数参数数量 nargs 进行的安全检查。 + * 在正常情况下,这个检查不应该失败,因为解析器应该在更早的阶段检查参数数量。 + * 但是,如果服务器编译时的 FUNC_MAX_ARGS 比某些在 pg_proc 中声明的函数参数数量要小,那么可能会导致检查失败。 + * 安全检查,为了确保调用函数时不会超过函数参数的最大数量限制。 */ if (list_length(fcache->args) > FUNC_MAX_ARGS) ereport(ERROR, @@ -1452,11 +1668,14 @@ static void init_fcache( FUNC_MAX_ARGS, FUNC_MAX_ARGS))); - /* Set up the primary fmgr lookup information */ + /* Set up the primary fmgr lookup information + 在初始化过程中设置主要的函数管理器(fmgr)查找信息。*/ fmgr_info_cxt(foid, &(fcache->func), fcacheCxt); fmgr_info_set_expr((Node*)fcache->xprstate.expr, &(fcache->func)); - /* palloc args in fcache's context */ + /* palloc args in fcache's context + 内存上下文中使用 palloc 函数来为参数分配内存,这样在函数执行结束后,这些分配的内存空间会被正确地释放,避免了内存泄漏的问题。 + */ oldcontext = MemoryContextSwitchTo(fcacheCxt); /* Initialize the function call parameter struct as well */ if (vectorized) @@ -1479,13 +1698,15 @@ static void init_fcache( } else { genericRuntime = fcache->fcinfo_data.flinfo->genericRuntime; - /* if internalFinfo is not null, release the internalFinfo's memory and set the pointer to null */ + /* if internalFinfo is not null, release the internalFinfo's memory and set the pointer to null + 如果 internalFinfo 不为 null,就释放掉它所指向的内存,并将指针设置为 null。*/ if (genericRuntime->internalFinfo != NULL) { FreeFunctionCallInfoData(*(genericRuntime->internalFinfo)); genericRuntime->internalFinfo = NULL; } - /* reset the memory for reuse */ + /* reset the memory for reuse + 在重用函数执行状态时,要确保对内部数据结构进行适当的复位操作,以保证状态的正确性和一致性。 */ rc = memset_s(genericRuntime->args, sizeof(GenericFunRuntimeArg) * genericRuntime->compacity, 0, @@ -1512,6 +1733,16 @@ static void init_fcache( * reduce the memory. * * NOTE: To avoid memory wasting and memory fragments, we free and initilized a new GenericFunRuntimeArg. + * + * a) 当 nargs(函数的实际参数个数)大于 genericRuntime->compacity(之前分配的数组容量)时, + * 意味着之前分配的内存不足以容纳所有的参数。 + * 在这种情况下,需要扩大数组的大小,以便能够存储更多的参数。 + * b) 当 nargs 小于等于 VECTOR_GENERIC_FUNCTION_PREALLOCED_ARGS(预分配的数组容量), + * 但实际分配的内存远远超过这个值时,为了避免内存浪费和碎片化,需要缩小数组的大小。 + * + * 为了避免内存浪费和碎片化,这段注释中提出了一种策略:在需要调整数组大小时, + * 会释放当前的 GenericFunRuntimeArg 数组,然后初始化一个新的数组来代替它。 + * 这样做可以确保内存始终用于存储实际的参数数据,避免不必要的内存浪费。 */ if (unlikely(nargs > genericRuntime->compacity) || (unlikely(genericRuntime->compacity > VECTOR_GENERIC_FUNCTION_PREALLOCED_ARGS) && @@ -1545,7 +1776,9 @@ static void init_fcache( i++; } - /* Find the real return type for func with return type like ANYELEMENT. */ + /* Find the real return type for func with return type like ANYELEMENT. + 在处理返回类型为 ANYELEMENT(任意元素类型)的函数时,找到其实际的返回类型。 + */ fcache->fcinfo_data.flinfo->fn_rettype = getRealFuncRetype(i, actual_arg_types, fcache); pfree_ext(actual_arg_types); } @@ -1564,7 +1797,11 @@ static void init_fcache( } fcache->funcResultDesc = NULL; } else { - /* If function returns set, prepare expected tuple descriptor */ + /* If function returns set, prepare expected tuple descriptor + 返回集合类型(Set-returning)函数时,如何准备期望的元组描述符(Tuple Descriptor)。 + + 通过这个过程,函数可以在查询执行过程中正确地处理返回集合类型函数的多行结果,为每行结果准备合适的元组描述符以便于数据处理和显示。 + */ if (fcache->func.fn_retset && needDescForSets) { TypeFuncClass functypclass; Oid funcrettype; @@ -1574,7 +1811,14 @@ static void init_fcache( functypclass = get_expr_result_type(fcache->func.fn_expr, &funcrettype, &tupdesc); /* Must save tupdesc in fcache's context */ - oldmemcontext = MemoryContextSwitchTo(fcacheCxt); + oldmemcontext = MemoryContextSwitchTo(fcacheCxt);、 + + /* + 如果函数返回类型为复合数据类型(TYPEFUNC_COMPOSITE),即一个表的行类型,那么复制元组描述符以确保安全性。 + 如果函数返回类型为基本数据类型(TYPEFUNC_SCALAR),即标量数据类型,创建一个只有一个列的元组描述符。 + 如果函数返回类型为 RECORD 类型(TYPEFUNC_RECORD),该处理方式在当前的逻辑上下文下有效。 + 如果返回类型不属于上述类型,不会为函数返回结果准备元组描述符,将 fcache->funcResultDesc 设置为 NULL。 + */ if (functypclass == TYPEFUNC_COMPOSITE) { /* Composite data type, e.g. a table's row type */ @@ -1609,6 +1853,9 @@ static void init_fcache( fcache->shutdown_reg = false; } +/* + 调用 init_fcache 函数的向量化版本(即使用了向量化技术的版本)来初始化函数表达式状态。 +*/ void initVectorFcache(Oid foid, Oid input_collation, FuncExprState* fcache, MemoryContext fcacheCxt) { init_fcache(foid, input_collation, fcache, fcacheCxt, false); @@ -1617,6 +1864,8 @@ void initVectorFcache(Oid foid, Oid input_collation, FuncExprState* fcache, Memo /* * callback function in case a FuncExpr returning a set needs to be shut down * before it has been run to completion + * 用于在一个返回集合类型的 FuncExpr 在运行完成之前被关闭。 + * 以确保释放相关资源,避免内存泄漏或其他问题。 */ static void ShutdownFuncExpr(Datum arg) { @@ -1631,10 +1880,13 @@ static void ShutdownFuncExpr(Datum arg) tuplestore_end(fcache->funcResultStore); fcache->funcResultStore = NULL; - /* Clear any active set-argument state */ + /* Clear any active set-argument state + 这段注释的意思是在函数执行结束后,清除任何活动的集合参数状态。 + */ fcache->setArgsValid = false; - /* execUtils will deregister the callback... */ + /* execUtils will deregister the callback... + 当函数表达式状态完成其执行并被释放时,回调函数将不再被调用。 */ fcache->shutdown_reg = false; } @@ -1649,6 +1901,13 @@ static void ShutdownFuncExpr(Datum arg) * NOTE: because the shutdown callback will be called during plan rescan, * must be prepared to re-do this during any node execution; cannot call * just once during expression initialization + * + * + * 通过缓存来优化获取TupleDesc的过程。 + * PS:TupleDesc是描述行的结构的数据结构(例如,列数据类型、名称等)。 + * 为行类型获取TupleDesc可能会消耗资源,因此缓存它可以提高性能。 + * + * 作用:为了避免重复查找行类型的 TupleDesc,提高执行效率,并确保在表达式执行结束时释放相关的资源,防止资源泄漏。 */ static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod, TupleDesc* cache_field, ExprContext* econtext) { @@ -1659,10 +1918,12 @@ static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod, TupleDesc* cache_ tupDesc = lookup_rowtype_tupdesc(type_id, typmod); if (*cache_field) { - /* Release old tupdesc; but callback is already registered */ + /* Release old tupdesc; but callback is already registered + 首先释放旧的TupleDesc。但是,由于注册了回调函数,即使释放了旧的TupleDesc,它不会立即被销毁,直到回调函数被调用*/ ReleaseTupleDesc(*cache_field); } else { - /* Need to register shutdown callback to release tupdesc */ + /* Need to register shutdown callback to release tupdesc + 需要注册关闭回调函数来释放TupleDesc。 */ RegisterExprContextCallback(econtext, ShutdownTupleDescRef, PointerGetDatum(cache_field)); } *cache_field = tupDesc; @@ -1672,6 +1933,7 @@ static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod, TupleDesc* cache_ /* * Callback function to release a tupdesc refcount at expression tree shutdown + * 用于在表达式树关闭时释放tupdesc引用计数的回调函数 */ static void ShutdownTupleDescRef(Datum arg) { @@ -1684,6 +1946,11 @@ static void ShutdownTupleDescRef(Datum arg) /* * Evaluate arguments for a function. + * 评估函数的参数 + * 作用: + * 评估函数调用时的参数,计算参数的实际值。 + * 处理集合参数和引用游标参数的情况。 + * 为SQL 提供可能需要的变量编号信息。 */ template static ExprDoneCond ExecEvalFuncArgs( @@ -1735,6 +2002,8 @@ static ExprDoneCond ExecEvalFuncArgs( * We allow only one argument to have a set value; we'd need much * more complexity to keep track of multiple set arguments (cf. * ExecTargetList) and it doesn't seem worth it. + * 我们只允许一个参数具有集合值; + * 要跟踪多个集合参数(类似于 ExecTargetList)需要更复杂的机制,但似乎不值得这样做 */ if (argIsDone != ExprSingleResult) ereport(ERROR, @@ -1758,6 +2027,10 @@ static ExprDoneCond ExecEvalFuncArgs( * tuplestore function result. We must set up a funcResultSlot (unless * already done in a previous call cycle) and verify that the function * returned the expected tuple descriptor. + * + * 为从元组存储中提取结果行做准备,设置合适的插槽。 + * 校验函数返回的元组描述符是否匹配预期。 + * 注册清理回调函数,以确保资源能够在合适的时候释放 */ static void ExecPrepareTuplestoreResult( FuncExprState* fcache, ExprContext* econtext, Tuplestorestate* resultStore, TupleDesc resultDesc) @@ -1765,7 +2038,9 @@ static void ExecPrepareTuplestoreResult( fcache->funcResultStore = resultStore; if (fcache->funcResultSlot == NULL) { - /* Create a slot so we can read data out of the tuplestore */ + /* Create a slot so we can read data out of the tuplestore + 创建一个槽(slot),以便我们可以从元组存储中读取数据。 + */ TupleDesc slotDesc; MemoryContext oldcontext; @@ -1774,6 +2049,9 @@ static void ExecPrepareTuplestoreResult( /* * If we were not able to determine the result rowtype from context, * and the function didn't return a tupdesc, we have to fail. + * 如果在上下文中无法确定函数返回的结果的行类型, + * 并且函数也没有返回元组描述符, + * 那么会触发一个错误。这是为了确保函数结果的一致性和正确性。 */ if (fcache->funcResultDesc) slotDesc = fcache->funcResultDesc; @@ -1794,6 +2072,8 @@ static void ExecPrepareTuplestoreResult( /* * If function provided a tupdesc, cross-check it. We only really need to * do this for functions returning RECORD, but might as well do it always. + * 如果函数提供了一个元组描述符(tupdesc),则进行交叉检查。 + * 虽然我们只需要对返回 RECORD 类型的函数进行此操作,但也可以对所有情况都执行。 */ if (resultDesc) { if (fcache->funcResultDesc) @@ -1803,12 +2083,19 @@ static void ExecPrepareTuplestoreResult( * If it is a dynamically-allocated TupleDesc, free it: it is * typically allocated in a per-query context, so we must avoid * leaking it across multiple usages. + * 如果元组描述符(TupleDesc)是动态分配的(即其 tdrefcount 为 -1),则需要将其释放。 + * 这是因为动态分配的元组描述符通常是在每个查询的上下文中进行分配的, + * 而为了避免内存泄漏,需要确保在每次使用后都将其正确释放。 */ if (resultDesc->tdrefcount == -1) FreeTupleDesc(resultDesc); } - /* Register cleanup callback if we didn't already */ + /* Register cleanup callback if we didn't already + 如果之前没有注册过清理回调函数,就会通过 RegisterExprContextCallback 函数注册一个清理回调函数, + 这样在函数执行结束时,会执行清理回调函数中指定的清理操作,确保已分配的资源被正确释放。 + 这是一种防止内存泄漏的重要机制。 + */ if (!fcache->shutdown_reg) { RegisterExprContextCallback(econtext, ShutdownFuncExpr, PointerGetDatum(fcache)); fcache->shutdown_reg = true; @@ -1824,6 +2111,14 @@ static void ExecPrepareTuplestoreResult( * Also, we can ignore type mismatch on columns that are dropped in the * destination type, so long as the physical storage matches. This is * helpful in some cases involving out-of-date cached plans. + * + * 实际上我们只关心属性的数量和数据类型是否一致。 + * 另外,如果目标类型中被删除的列的物理存储仍然匹配,那么我们可以忽略在目标类型中出现的类型不匹配的情况。 + * 这在一些涉及过期缓存计划的情况下非常有用。也就是说,如果查询的目标类型删除了某些列,但是函数返回的元组类型仍然包含这些列, + * 只要它们的物理存储是匹配的,就不会触发错误。这有助于处理一些已过期的缓存计划,以避免因元组结构的轻微变化而引发错误。 + * + * 这段代码的作用是检查函数返回的元组类型(src_tupdesc)是否与查询期望的元组类型(dst_tupdesc)匹配, + * 或者是否可以被认为匹配。如果它们不匹配,会触发一个错误报告(ereport)。 */ static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc) { @@ -1862,6 +2157,11 @@ static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc) } } + +/* + * 这段代码主要用于处理包含 SQL 语言和 OUT 参数的函数的结果。 + * 它从结果元组中解析出一个单独的值,并对结果进行必要的转换和处理,以满足特定的需求。 + */ static void set_result_for_plpgsql_language_function_with_outparam(FuncExprState *fcache, Datum *result, bool *isNull) { if (!IsA(fcache->xprstate.expr, FuncExpr)) { @@ -1903,11 +2203,21 @@ static void set_result_for_plpgsql_language_function_with_outparam(FuncExprState * @bool isSetReturnFunc - indicate function returns a set. *The execution process of the ExecMakeFunctionResult function is as follows. * (1) Check whether funcResultStore exists, if so, get the result and return it -(2) The calculated parameter values are stored in fcinfo. -(3) Pass the parameter into the expression function to calculate the expression, -first determine whether the parameter args exists null, and then determine the return mode of the function that returns the set, -SFRM_ValuePerCall mode is to return a value each time the call, The SFRM_Materialize schema is the result set instantiated in Tuplestore. -(4) Calculate and return results according to different modes. + * (2) The calculated parameter values are stored in fcinfo. + * (3) Pass the parameter into the expression function to calculate the expression, + * first determine whether the parameter args exists null, and then determine the return mode of the function that returns the set, + * SFRM_ValuePerCall mode is to return a value each time the call, The SFRM_Materialize schema is the result set instantiated in Tuplestore. + * (4) Calculate and return results according to different modes. + * 这段代码用于执行一个函数的参数计算和函数本身的计算。函数 `init_fcache` 在 `FuncExprState` 上已经运行过了。 + * 这个函数处理了最一般的情况,其中函数或其参数之一可以返回一个集合。 + * `ExecMakeFunctionResult` 函数的执行过程如下: + * 1. 首先检查是否存在 `funcResultStore`,如果存在,则获取结果并返回。 + * 2. 计算的参数值存储在 `fcinfo` 中。 + * 3. 将参数传递给表达式函数,计算表达式。首先判断参数 `args` 是否存在 `null` 值, + * 然后确定返回集合的函数的返回模式,`SFRM_ValuePerCall` 模式表示每次调用返回一个值, + * `SFRM_Materialize` 模式表示结果集被实例化为 `Tuplestore`。 + * 4. 根据不同的模式计算并返回结果。 + * 实现了对函数及其参数的计算过程,处理了函数返回集合的情况,可以根据模板参数的不同编译不同的函数逻辑,从而优化性能。 */ template static Datum ExecMakeFunctionResult(FuncExprState* fcache, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -1927,13 +2237,17 @@ static Datum ExecMakeFunctionResult(FuncExprState* fcache, ExprContext* econtext restart: - /* Guard against stack overflow due to overly complex expressions */ + /* Guard against stack overflow due to overly complex expressions + 在执行表达式的过程中,对于过于复杂的表达式,防止由于堆栈溢出而导致程序崩溃。 + */ check_stack_depth(); /* * If a previous call of the function returned a set result in the form of * a tuplestore, continue reading rows from the tuplestore until it's * empty. + * 如果之前的函数调用以元组存储的形式返回了一个集合结果, + * 在这次函数调用中会继续从元组存储中读取行,直到元组存储为空为止。 */ if (fcache->funcResultStore) { /* it was provided before ... */ @@ -1941,29 +2255,43 @@ restart: ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("set-valued function called in context that cannot accept a set"))); } + /* + 这段代码块用于从元组存储中获取下一个结果行,然后根据函数返回值的类型,将其作为整个元组或标量值返回。 + */ if (tuplestore_gettupleslot(fcache->funcResultStore, true, false, fcache->funcResultSlot)) { *isDone = ExprMultipleResult; if (fcache->funcReturnsTuple) { - /* We must return the whole tuple as a Datum. */ + /* We must return the whole tuple as a Datum. + 如果函数返回的结果是一个元组(即一行数据),那么必须将整个元组作为一个 Datum 类型的值返回。 + */ *isNull = false; return ExecFetchSlotTupleDatum(fcache->funcResultSlot); } else { - /* Extract the first column and return it as a scalar. */ + /* + Extract the first column and return it as a scalar. + 从一个元组(一行数据)中提取第一个列的值,并将它作为一个标量值返回。 + */ Assert(fcache->funcResultSlot != NULL); - /* Get the Table Accessor Method*/ + /* Get the Table Accessor Method 获取表访问方法*/ return tableam_tslot_getattr(fcache->funcResultSlot, 1, isNull); } } - /* Exhausted the tuplestore, so clean up */ + /* Exhausted the tuplestore, so clean up + 元组已经耗尽,清理 + */ tuplestore_end(fcache->funcResultStore); fcache->funcResultStore = NULL; - /* We are done unless there was a set-valued argument */ + /* We are done unless there was a set-valued argument + 除非存在一个返回集合值的参数,否则已经完成 + */ if (!fcache->setHasSetArg) { *isDone = ExprEndResult; *isNull = true; return (Datum)0; } - /* If there was, continue evaluating the argument values */ + /* If there was, continue evaluating the argument values + 如果有返回集合值的参数存在,继续评估参数值。 + */ Assert(!fcache->setArgsValid); } @@ -1972,11 +2300,17 @@ restart: * function manager. We skip the evaluation if it was already done in the * previous call (ie, we are continuing the evaluation of a set-valued * function). Otherwise, collect the current argument values into fcinfo. + * arguments 是一组在传递给函数管理器之前需要评估的表达式列表。 + * 如果在前一个调用中已经执行了评估(即,我们正在继续对返回集合值的函数进行评估), + * 则我们跳过评估。否则,将当前的参数值收集到 fcinfo 中。 */ fcinfo = &fcache->fcinfo_data; if (has_cursor_return) { - /* init returnCursor to store out-args cursor info on ExprContext*/ + /* init returnCursor to store out-args cursor info on ExprContext + 初始化 returnCursor 来存储输出参数游标信息在 ExprContext 中。 + 否则,将 returnCursor 设置为 NULL。 + */ fcinfo->refcursor_data.returnCursor = (Cursor_Data*)palloc0(sizeof(Cursor_Data) * fcinfo->refcursor_data.return_number); } else { @@ -1984,7 +2318,10 @@ restart: } if (has_refcursor) { - /* init argCursor to store in-args cursor info on ExprContext*/ + /* init argCursor to store in-args cursor info on ExprContext + 初始化 argCursor 来存储输入参数游标信息在 ExprContext 中。 + 同时,为了处理参数索引,还初始化了一个整数数组 var_dno,用于跟踪参数在 ExprContext 中的位置。 + */ fcinfo->refcursor_data.argCursor = (Cursor_Data*)palloc0(sizeof(Cursor_Data) * fcinfo->nargs); var_dno = (int*)palloc0(sizeof(int) * fcinfo->nargs); for (i = 0; i < fcinfo->nargs; i++) { @@ -1999,7 +2336,9 @@ restart: else argDone = ExecEvalFuncArgs(fcinfo, arguments, econtext); if (argDone == ExprEndResult) { - /* input is an empty set, so return an empty set. */ + /* input is an empty set, so return an empty set. + 输入是空集合,那么返回空集合 + */ *isNull = true; if (isDone != NULL) *isDone = ExprEndResult; @@ -2010,19 +2349,30 @@ restart: } hasSetArg = (argDone != ExprSingleResult); } else { - /* Re-use callinfo from previous evaluation */ + /* Re-use callinfo from previous evaluation + 在之前的评估中已经重用了参数信息。 + + 如果在之前的调用中已经评估了参数, + 并且在当前调用中参数的值没有发生变化, + 那么就可以直接使用之前的评估结果,而不必重新计算。 + 这种优化可以减少不必要的计算开销,提高执行效率。 + */ hasSetArg = fcache->setHasSetArg; - /* Reset flag (we may set it again below) */ + /* Reset flag (we may set it again below) + 重用了之前评估结果的情况下,重置一个标志位。*/ fcache->setArgsValid = false; } /* * Now call the function, passing the evaluated parameter values. + * 现在调用函数,将评估后的参数值传递给函数。 + * 使用fcinfo中存储的参数值来实际调用函数,并获取其返回值。这样,函数的计算结果就可以被获取并处理了。 */ if (fcache->func.fn_retset || hasSetArg) { /* * We need to return a set result. Complain if caller not ready to * accept one. + * 我们需要返回一个集合(set)的结果。如果调用者不准备好接收集合结果,则会报错。 */ if (isDone == NULL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -2032,6 +2382,9 @@ restart: * Prepare a resultinfo node for communication. If the function * doesn't itself return set, we don't pass the resultinfo to the * function, but we need to fill it in anyway for internal use. + * 为通信准备一个结果信息(resultinfo)节点。 + * 如果函数本身不返回集合,我们不会将结果信息传递给函数, + * 但是我们仍然需要填充它,以供内部使用。 */ if (fcache->func.fn_retset) fcinfo->resultinfo = (Node*)&rsinfo; @@ -2039,9 +2392,14 @@ restart: rsinfo.econtext = econtext; rsinfo.expectedDesc = fcache->funcResultDesc; rsinfo.allowedModes = (int)(SFRM_ValuePerCall | SFRM_Materialize); - /* note we do not set SFRM_Materialize_Random or _Preferred */ + /* note we do not set SFRM_Materialize_Random or _Preferred + 注意,我们没有设置 SFRM_Materialize_Random 或 _Preferred 标志。 + 这指示在函数的执行中,没有要求将结果集实例化为 Tuplestore, + 也没有优先选择将结果集实例化为 Tuplestore。 + 这可能影响函数的执行和结果的处理方式。 + */ rsinfo.returnMode = SFRM_ValuePerCall; - /* isDone is filled below */ + /* isDone is filled below*/ rsinfo.setResult = NULL; rsinfo.setDesc = NULL; @@ -2052,11 +2410,16 @@ restart: * argument value and start the function over again. We might have to * do it more than once, if the function produces an empty result set * for a particular input value. + * 这个循环处理了同时存在集合参数和返回集合的函数的情况。 + * 一旦我们用完了函数针对特定参数值的所有返回值, + * 我们必须获取下一个参数值,并重新开始执行函数。 + * 我们可能需要多次这样做,如果函数对于特定输入值产生了一个空的结果集。 */ for (;;) { /* * If function is strict, and there are any NULL arguments, skip * calling the function (at least for this set of args). + * 如果函数是严格的(strict),并且存在任何 NULL 参数,就跳过调用函数(至少对于这组参数)。 */ bool callit = true; @@ -2085,6 +2448,8 @@ restart: * If SRF is strict and has any NULL arguments, this SRF * need return empty set, so such rows were omitted entirely * from the result set. + * 对于严格的集合返回函数(SRF),对于 NULL 输入,结果是一个空集。 + * 如果严格的 SRF 存在任何 NULL 参数,那么这个 SRF 需要返回一个空集,因此这些行完全被从结果集中省略。 */ result = (Datum)0; *isNull = true; @@ -2096,6 +2461,10 @@ restart: * like "select plain_function(set_returning_function(...))". * If some of the SRF outputs are NULL, and the plain function * is strict, we expect to get NULL results for such rows + * 对于严格的非集合返回函数(non-SRF),对于 NULL 输入,结果是一个 NULL。 + * 这个分支是为了处理严格的嵌套函数, + * 比如 "select plain_function(set_returning_function(...))" 这样的情况。 + * 如果一些集合返回函数的输出为 NULL,而普通函数是严格的,我们期望对这些行得到 NULL 的结果。 */ result = (Datum)0; *isNull = true; @@ -2104,7 +2473,8 @@ restart: if (has_refcursor && econtext->plpgsql_estate != NULL) { PLpgSQL_execstate* estate = econtext->plpgsql_estate; - /* copy in-args cursor option info */ + /* copy in-args cursor option info + 复制输入参数游标选项信息。 */ for (i = 0; i < fcinfo->nargs; i++) { if (var_dno[i] >= 0) { int dno = var_dno[i]; @@ -2122,7 +2492,10 @@ restart: if (fcinfo->refcursor_data.return_number > 0) { /* copy function returns cursor option info. * for simple expr in exec_eval_expr, we can not get the result type, - * so cursor_return_data mallocs here. + * so cursor_return_data mallocs here. + * 复制函数返回的游标选项信息。 + * 对于在 exec_eval_expr 中的简单表达式,我们无法获取结果类型, + * 因此在这里分配 cursor_return_data 的内存。 */ if (estate->cursor_return_data == NULL && estate->tuple_store_cxt != NULL) { MemoryContext oldcontext = MemoryContextSwitchTo(estate->tuple_store_cxt); @@ -2149,6 +2522,7 @@ restart: * Got a result from current argument. If function itself * returns set, save the current argument values to re-use * on the next call. + * 从当前参数获得结果。如果函数本身返回集合,保存当前参数值以便在下次调用时重用。 */ if (fcache->func.fn_retset && *isDone == ExprMultipleResult) { fcache->setHasSetArg = hasSetArg; @@ -2163,6 +2537,7 @@ restart: /* * Make sure we say we are returning a set, even if the * function itself doesn't return sets. + * 确保返回一个集合,即使函数本身不返回集合。 */ if (hasSetArg) { *isDone = ExprMultipleResult; @@ -2170,14 +2545,14 @@ restart: break; } } else if (rsinfo.returnMode == SFRM_Materialize) { - /* check we're on the same page as the function author */ + /* check we're on the same page as the function author 检测是否与函数达成一致 */ if (rsinfo.isDone != ExprSingleResult) ereport(ERROR, (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), errmsg("table-function protocol for materialize mode was not followed"))); if (rsinfo.setResult != NULL) { - /* prepare to return values from the tuplestore */ + /* prepare to return values from the tuplestore 准备从元组存储中返回值 */ ExecPrepareTuplestoreResult(fcache, econtext, rsinfo.setResult, rsinfo.setDesc); - /* remember whether we had set arguments */ + /* remember whether we had set arguments 记住是否有集合函数 */ fcache->setHasSetArg = hasSetArg; /* loop back to top to start returning from tuplestore */ goto restart; @@ -2193,10 +2568,12 @@ restart: /* Else, done with this argument */ if (!hasSetArg) { - break; /* input not a set, so done */ + break; /* input not a set, so done 输入不是集合则不用循环*/ } - /* Re-eval args to get the next element of the input set */ + /* Re-eval args to get the next element of the input set + 重新评估参数以获取输入集合的下一个元素 + */ if (has_refcursor) { argDone = ExecEvalFuncArgs(fcinfo, arguments, econtext, var_dno); } else { @@ -2214,6 +2591,7 @@ restart: /* * If we reach here, loop around to run the function on the new * argument. + * 如果到达这里,就会回到循环的开头,对新的参数重新运行函数。 */ } } else { @@ -2225,6 +2603,12 @@ restart: * possible to get here if an argument sometimes produces set results * and sometimes scalar results. For example, a CASE expression might * call a set-returning function in only some of its arms. + * + * 非集合情况:要简单得多。 + * + * 在常见情况下,这段代码路径是不可达的,因为我们会选择ExecMakeFunctionResultNoSets。 + * 然而,如果一个参数有时产生集合结果,有时产生标量结果,就有可能会到达这里。 + * 例如,CASE表达式可能在其中的某些分支中调用返回集合的函数。 */ if (isDone != NULL) *isDone = ExprSingleResult; @@ -2273,6 +2657,16 @@ restart: * Template parameter: * @bool has_cursor_return - need store out-args cursor info. * @bool has_refcursor - need store in-args cursor info. + * + * ExecMakeFunctionResultNoSets是ExecMakeFunctionResult的简化版本,只能处理非集合情况。这个版本经过手工调优以提高性能。 + * 这个函数使用模板参数,可以编译不同的函数,从而减少汇编指令,提高性能。 + * + * 模板参数: + * @bool has_cursor_return - 是否需要存储输出参数的游标信息。 + * @bool has_refcursor - 是否需要存储输入参数的游标信息。 + * + * 该函数主要完成了对非集合情况下函数调用的计算和处理, + * 包括参数计算、函数执行、结果处理等。同时,根据函数的严格模式、是否支持事务等情况,进行了一些特殊处理。 */ template static Datum ExecMakeFunctionResultNoSets( @@ -2321,6 +2715,9 @@ static Datum ExecMakeFunctionResultNoSets( * way the GUC stacking works: The transaction boundary would have to pop * the proconfig setting off the stack. That restriction could be lifted * by redesigning the GUC nesting mechanism a bit. + * 在函数中设置了配置项(proconfig)时,不能允许事务命令,这是因为 GUC(全局用户配置)的堆栈机制会影响事务的界限。 + * 解决这个问题的方式是在函数内部禁止执行事务命令。 + * 这种限制可以通过重新设计 GUC 嵌套机制来解决,但是目前需要保持这种行为以确保代码的一致性和正确性。 */ if (!fcache->prokind) { bool isNullSTP = false; @@ -2334,7 +2731,8 @@ static Datum ExecMakeFunctionResultNoSets( node->atomic = true; stp_set_commit_rollback_err_msg(STP_XACT_GUC_IN_OPT_CLAUSE); } - /* immutable or stable function should not support commit/rollback */ + /* immutable or stable function should not support commit/rollback + 对于具有 "immutable" 或 "stable" 特性的函数,不应该支持事务的提交(COMMIT)或回滚(ROLLBACK)操作。*/ bool isNullVolatile = false; Datum provolatile = SysCacheGetAttr(PROCOID, tp, Anum_pg_proc_provolatile, &isNullVolatile); if (!isNullVolatile && CharGetDatum(provolatile) != PROVOLATILE_VOLATILE) { @@ -2350,7 +2748,8 @@ static Datum ExecMakeFunctionResultNoSets( fcache->prokind = 'f'; } - /* if proIsProcedure is ture means it was a stored procedure */ + /* if proIsProcedure is ture means it was a stored procedure + 如果 proIsProcedure 为真(true),则表示这个函数是一个存储过程(stored procedure)。 */ u_sess->SPI_cxt.is_stp = savedIsSTP; ReleaseSysCache(tp); } else { @@ -2359,7 +2758,8 @@ static Datum ExecMakeFunctionResultNoSets( } } - /* Guard against stack overflow due to overly complex expressions */ + /* Guard against stack overflow due to overly complex expressions + 防止由于过于复杂的表达式而导致栈溢出的问题。 */ check_stack_depth(); if (isDone != NULL) @@ -2368,13 +2768,16 @@ static Datum ExecMakeFunctionResultNoSets( econtext->plpgsql_estate = plpgsql_estate; plpgsql_estate = NULL; - /* inlined, simplified version of ExecEvalFuncArgs */ + /* inlined, simplified version of ExecEvalFuncArgs + “ExecEvalFuncArgs”的内联和简化版本。 */ fcinfo = &fcache->fcinfo_data; - /* init the number of arguments to a function*/ + /* init the number of arguments to a function + 初始化函数参数的数量。*/ InitFunctionCallInfoArgs(*fcinfo, list_length(fcache->args), 1); - /* Only allow commit at CN, therefore need to set callcontext in CN only */ + /* Only allow commit at CN, therefore need to set callcontext in CN only + 在协调节点上执行一些操作,以限制在协调节点上允许提交事务。 */ if (supportTranaction) { fcinfo->context = (Node *)node; } @@ -2383,8 +2786,15 @@ static Datum ExecMakeFunctionResultNoSets( * Incause of connet_by_root() and sys_connect_by_path() we need get the * current scan tuple slot so attach the econtext here * + * 在执行connect_by_root()和sys_connect_by_path()函数时, + * 需要获取当前的扫描元组槽(scan tuple slot),并在这里附加上执行上下文(econtext)。 + * + * * NOTE: Have to revisit!! so I don't have better solution to handle the case * where scantuple is available in built in funct + * 在执行这些函数时,需要使用当前的扫描元组槽, + * 但是在内置函数(built-in function)中可能无法直接访问到扫描元组槽。 + * 因此,可能需要在这里附加执行上下文(econtext),以便在内置函数中可以访问到当前的扫描元组槽。 */ if (fcinfo->flinfo->fn_oid == CONNECT_BY_ROOT_FUNCOID || fcinfo->flinfo->fn_oid == SYS_CONNECT_BY_PATH_FUNCOID) { @@ -2393,7 +2803,8 @@ static Datum ExecMakeFunctionResultNoSets( } if (has_cursor_return) { - /* init returnCursor to store out-args cursor info on ExprContext*/ + /* init returnCursor to store out-args cursor info on ExprContext + 对于输出参数的游标信息,需要初始化returnCursor,以便在ExprContext上存储这些游标信息。*/ fcinfo->refcursor_data.returnCursor = (Cursor_Data*)palloc0(sizeof(Cursor_Data) * fcinfo->refcursor_data.return_number); } else { @@ -2401,7 +2812,8 @@ static Datum ExecMakeFunctionResultNoSets( } if (has_refcursor) { - /* init argCursor to store in-args cursor info on ExprContext */ + /* init argCursor to store in-args cursor info on ExprContext + 参数上下文(ExprContext)中初始化argCursor,以存储输入参数的游标信息。*/ fcinfo->refcursor_data.argCursor = (Cursor_Data*)palloc0(sizeof(Cursor_Data) * fcinfo->nargs); var_dno = (int*)palloc0(sizeof(int) * fcinfo->nargs); for (i = 0; i < fcinfo->nargs; i++) { @@ -2441,7 +2853,8 @@ static Datum ExecMakeFunctionResultNoSets( u_sess->SPI_cxt.cur_tableof_index->tableOfIndex = execTableOfIndexInfo.tableOfIndex; u_sess->SPI_cxt.cur_tableof_index->tableOfNestLayer = execTableOfIndexInfo.tableOfLayers; /* for nest table of output, save layer of this var tableOfGetNestLayer in ExecEvalArrayRef, - or set to zero for get whole nest table. */ + or set to zero for get whole nest table. + 处理嵌套表输出时,存储变量的tableOfGetNestLayer层级信息,并在ExecEvalArrayRef函数中使用这个信息。 */ u_sess->SPI_cxt.cur_tableof_index->tableOfGetNestLayer = -1; } @@ -2456,6 +2869,7 @@ static Datum ExecMakeFunctionResultNoSets( /* * If function is strict, and there are any NULL arguments, skip calling * the function and return NULL. + * 有输入为Null则跳过 */ if (fcache->func.fn_strict) { while (--i >= 0) { @@ -2475,10 +2889,13 @@ static Datum ExecMakeFunctionResultNoSets( fcinfo->isnull = false; check_huge_clob_paramter(fcinfo, is_have_huge_clob); + /* + * 根据条件选择不同的路径来执行函数调用,其中涉及到全局性能监控器的管理以及对特定参数类型的处理。 + */ if (u_sess->instr_cxt.global_instr != NULL && fcinfo->flinfo->fn_addr == plpgsql_call_handler) { StreamInstrumentation* save_global_instr = u_sess->instr_cxt.global_instr; u_sess->instr_cxt.global_instr = NULL; - result = FunctionCallInvoke(fcinfo); // node will be free at here or else; + result = FunctionCallInvoke(fcinfo); // node will be free at here or else; node会在这里或者其他地方被释放 u_sess->instr_cxt.global_instr = save_global_instr; } else { if (fcinfo->argTypes[0] == CLOBOID && fcinfo->argTypes[1] == CLOBOID && fcinfo->flinfo->fn_addr == textcat) { @@ -2499,7 +2916,7 @@ static Datum ExecMakeFunctionResultNoSets( if (has_refcursor && econtext->plpgsql_estate != NULL) { PLpgSQL_execstate* estate = econtext->plpgsql_estate; for (i = 0; i < fcinfo->nargs; i++) { - /* copy in-args cursor option info */ + /* copy in-args cursor option info 将传入参数中的游标选项信息进行复制 */ if (var_dno[i] >= 0) { int dno = var_dno[i]; Cursor_Data* cursor_data = &fcinfo->refcursor_data.argCursor[i]; @@ -2517,6 +2934,8 @@ static Datum ExecMakeFunctionResultNoSets( /* copy function returns cursor option info. * for simple expr in exec_eval_expr, we can not get the result type, * so cursor_return_data mallocs here. + * 在函数调用完成后,如果函数的返回类型是游标类型(例如REFCURSOR), + * 则需要将函数返回的游标选项信息复制到一个新的数据结构中。 */ if (estate->cursor_return_data == NULL) { estate->cursor_return_data = (Cursor_Data*)palloc0(sizeof(Cursor_Data)); @@ -2555,6 +2974,8 @@ static Datum ExecMakeFunctionResultNoSets( * @in Funcid - function oid * @in fcinfo - function call info * @return - has refcursor + * + * 判断函数是否具有参数类型为refcursor,或者函数的返回类型是否为refcursor。 */ static bool func_has_refcursor_args(Oid Funcid, FunctionCallInfoData* fcinfo) { @@ -2572,12 +2993,15 @@ static bool func_has_refcursor_args(Oid Funcid, FunctionCallInfoData* fcinfo) /* * function may be deleted after clist be searched. + clist(可能是一个链表或集合)被搜索后可能会被删除 */ if (!HeapTupleIsValid(proctup)) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function doesn't exist "))); } - /* get the all args informations, only "in" parameters if p_argmodes is null */ + /* get the all args informations, only "in" parameters if p_argmodes is null + 如果p_argmodes参数为NULL,则只获取输入参数(即"IN"参数)的信息。 + */ allarg = get_func_arg_info(proctup, &p_argtypes, &p_argnames, &p_argmodes); procStruct = (Form_pg_proc)GETSTRUCT(proctup); @@ -2609,6 +3033,10 @@ static bool func_has_refcursor_args(Oid Funcid, FunctionCallInfoData* fcinfo) * * Evaluate a table function, producing a materialized result in a Tuplestore * object. + * 函数负责评估一个表函数,并在一个 Tuplestore 对象中产生一个实体化的结果。 + * 表函数是一个返回一组行的函数,它的结果需要以一种可迭代和处理的方式进行存储。 + * + * 实现了对表函数的评估和结果的存储,支持不同的返回模式和情况。 */ Tuplestorestate* ExecMakeTableFunctionResult( ExprState* funcexpr, ExprContext* econtext, TupleDesc expectedDesc, bool randomAccess, FunctionScanState* node) @@ -2644,7 +3072,9 @@ Tuplestorestate* ExecMakeTableFunctionResult( #endif bool needResetErrMsg = (u_sess->SPI_cxt.forbidden_commit_rollback_err_msg[0] == '\0'); - /* Only allow commit at CN, therefore only need to set atomic and relevant check at CN level. */ + /* Only allow commit at CN, therefore only need to set atomic and relevant check at CN level. + 只允许在协调节点 (Coordinator Node,简称 CN) 上执行提交操作(commit), + 因此只需要在协调节点级别设置 atomic 和相关的检查。 */ if (supportTranaction && IsA(funcexpr->expr, FuncExpr)) { fexpr = (FuncExpr*)funcexpr->expr; char prokind = (reinterpret_cast(funcexpr))->prokind; @@ -2660,6 +3090,10 @@ Tuplestorestate* ExecMakeTableFunctionResult( * way the GUC stacking works: The transaction boundary would have to pop * the proconfig setting off the stack. That restriction could be lifted * by redesigning the GUC nesting mechanism a bit. + * + * 如果 proconfig 配置设置了,就不能允许执行事务命令, + * 因为现有的 GUC(Grand Unified Configuration)堆栈机制会造成问题:事务边界需要从堆栈中弹出 proconfig 的设置。 + * 如果需要解决这个限制,可能需要对 GUC 嵌套机制进行重新设计。 */ if (!prokind) { HeapTuple tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid)); @@ -2668,7 +3102,8 @@ Tuplestorestate* ExecMakeTableFunctionResult( elog(ERROR, "cache lookup failed for function %u", fexpr->funcid); } - /* immutable or stable function do not support commit/rollback */ + /* immutable or stable function do not support commit/rollback + 对于 "immutable" 或 "stable" 的函数,不支持执行事务的提交(commit)或回滚(rollback)操作。*/ bool isNullVolatile = false; Datum provolatile = SysCacheGetAttr(PROCOID, tp, Anum_pg_proc_provolatile, &isNullVolatile); if (!isNullVolatile && CharGetDatum(provolatile) != PROVOLATILE_VOLATILE) { @@ -2683,7 +3118,8 @@ Tuplestorestate* ExecMakeTableFunctionResult( } else { (reinterpret_cast(funcexpr))->prokind = 'f'; } - /* if proIsProcedure means it was a stored procedure */ + /* if proIsProcedure means it was a stored procedure + 一个存储过程 */ u_sess->SPI_cxt.is_stp = savedIsSTP; if (!heap_attisnull(tp, Anum_pg_proc_proconfig, NULL) || u_sess->SPI_cxt.is_proconfig_set) { u_sess->SPI_cxt.is_proconfig_set = true; @@ -2714,6 +3150,10 @@ Tuplestorestate* ExecMakeTableFunctionResult( * generic-expression case, the expression doesn't actually get to see the * resultinfo, but set it up anyway because we use some of the fields as * our own state variables. + * 准备一个 resultinfo 节点,以便进行通信。 + * 即使不期望一个集合结果,我们仍然会做这个准备,这样我们就可以传递 expectedDesc。 + * 在通用表达式情况下,表达式实际上不会看到 resultinfo, + * 但我们仍然进行设置,因为我们将其中一些字段用作我们自己的状态变量。 */ rsinfo.type = T_ReturnSetInfo; rsinfo.econtext = econtext; @@ -2735,6 +3175,10 @@ Tuplestorestate* ExecMakeTableFunctionResult( * it via the general ExecEvalExpr() code; the only difference is that we * don't get a chance to pass a special ReturnSetInfo to any functions * buried in the expression. + * 通常情况下,传递的表达式树应该是 FuncExprState 类型的,因为语法规则只允许在表函数引用的顶层进行函数调用。 + * 然而,如果函数不返回集合,规划器可能会通过常量折叠(constant-folding)或内联(inlining)来替换函数调用。 + * 因此,如果我们看到其他类型的表达式节点,就会通过通用的 ExecEvalExpr() 代码来执行它, + * 唯一的区别是我们没有机会向嵌在表达式中的任何函数传递特殊的 ReturnSetInfo。 */ if (funcexpr && IsA(funcexpr, FuncExprState) && IsA(funcexpr->expr, FuncExpr)) { FuncExprState* fcache = (FuncExprState*)funcexpr; @@ -2770,14 +3214,18 @@ Tuplestorestate* ExecMakeTableFunctionResult( int cursor_return_number = fcinfo.refcursor_data.return_number; if (cursor_return_number > 0) { - /* init returnCursor to store out-args cursor info on FunctionScan context*/ + /* init returnCursor to store out-args cursor info on FunctionScan context + 初始化 returnCursor 变量,该变量用于在 FunctionScan 上下文中存储输出参数的游标信息。 + */ fcinfo.refcursor_data.returnCursor = (Cursor_Data*)palloc0(sizeof(Cursor_Data) * cursor_return_number); } else { fcinfo.refcursor_data.returnCursor = NULL; } if (has_refcursor) { - /* init argCursor to store in-args cursor info on FunctionScan context*/ + /* init argCursor to store in-args cursor info on FunctionScan context + 初始化 argCursor 变量,该变量用于在 FunctionScan 上下文中存储输入参数的游标信息。 + */ fcinfo.refcursor_data.argCursor = (Cursor_Data*)palloc0(sizeof(Cursor_Data) * fcinfo.nargs); var_dno = (int*)palloc0(sizeof(int) * fcinfo.nargs); int rc = memset_s(var_dno, sizeof(int) * fcinfo.nargs, -1, sizeof(int) * fcinfo.nargs); @@ -2791,12 +3239,20 @@ Tuplestorestate* ExecMakeTableFunctionResult( * argument values would disappear when we reset the context in the * inner loop. So do it in caller context. Perhaps we should make a * separate context just to hold the evaluated arguments? + * + * 这段注释提到了一个优化问题,即在函数调用过程中对函数参数进行求值时的上下文管理。 + * + * 注释指出,在理想情况下,我们应该在每个元组的上下文中对函数参数进行求值。 + * 这是因为在循环的内部循环中,当我们重置上下文时,参数值会丢失。 + * 这是因为每个元组的上下文在循环的每次迭代中都会被重置,这样可以确保内存不会累积增加, + * 但也会导致在循环的每次迭代中重新计算参数值,造成性能损失。 */ if (has_refcursor) argDone = ExecEvalFuncArgs(&fcinfo, fcache->args, econtext, var_dno); else argDone = ExecEvalFuncArgs(&fcinfo, fcache->args, econtext); - /* We don't allow sets in the arguments of the table function */ + /* We don't allow sets in the arguments of the table function + 在表函数的参数中不允许使用“集合(sets) */ if (argDone != ExprSingleResult) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -2806,6 +3262,9 @@ Tuplestorestate* ExecMakeTableFunctionResult( * If function is strict, and there are any NULL arguments, skip * calling the function and act like it returned NULL (or an empty * set, in the returns-set case). + * + * 严格模式下的函数在处理参数时要求参数不能为NULL, + * 如果有任何参数是NULL,函数将直接返回NULL值,而不会进行实际的计算或处理。 */ if (fcache->func.fn_strict) { int i; @@ -2816,19 +3275,25 @@ Tuplestorestate* ExecMakeTableFunctionResult( } } } else { - /* Treat funcexpr as a generic expression */ + /* Treat funcexpr as a generic expression + 如果 funcexpr 不是一个标准的函数调用表达式, + 即不符合函数调用的语法和特征, + 那么将把它视为一般的表达式,而不是一个函数调用。 + */ direct_function_call = false; InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, (Node*)node, NULL); } /* * Switch to short-lived context for calling the function or expression. + 切换到短期内存上下文以调用函数或表达式。 */ MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); /* * Loop to handle the ValuePerCall protocol (which is also the same * behavior needed in the generic ExecEvalExpr path). + * 用于处理 ValuePerCall 协议,这也是在通用的 ExecEvalExpr 路径中所需要的行为。 */ for (;;) { Datum result; @@ -2839,10 +3304,17 @@ Tuplestorestate* ExecMakeTableFunctionResult( * reset per-tuple memory context before each call of the function or * expression. This cleans up any local memory the function may leak * when called. + * + * 重置每个元组的内存上下文。 + * 这个操作的目的是清除函数在执行时可能泄漏的任何本地内存。 + * 当函数执行时,它可能会分配一些临时内存, + * 例如在计算过程中创建的临时变量、数据结构等。 + * 这些临时内存通常在函数调用结束后不再需要,但如果不进行适当的清理,可能会导致内存泄漏。 */ ResetExprContext(econtext); - /* Call the function or expression one time */ + /* Call the function or expression one time + 循环中调用函数或表达式一次。 */ if (direct_function_call) { pgstat_init_function_usage(&fcinfo, &fcusage); @@ -2890,10 +3362,14 @@ Tuplestorestate* ExecMakeTableFunctionResult( result = ExecEvalExpr(funcexpr, econtext, &fcinfo.isnull, &rsinfo.isDone); } - /* Which protocol does function want to use? */ + /* Which protocol does function want to use? + 根据函数的返回协议(protocol)和返回模式(returnMode),对函数的返回结果进行处理。 + */ if (rsinfo.returnMode == SFRM_ValuePerCall) { /* * Check for end of result set. + * 检查是否已经到达函数返回结果集的末尾。 + * 在处理函数的返回结果时,根据不同的返回模式和协议,可能需要在循环中逐步处理函数返回的多个结果。 */ if (rsinfo.isDone == ExprEndResult) { break; @@ -2906,6 +3382,12 @@ Tuplestorestate* ExecMakeTableFunctionResult( * "continue" to get another row). For a function not returning * set, we fall out of the loop; we'll cons up an all-nulls result * row below. + * 对于返回为 NULL 值的元组类型,注释中指出这种情况下无法从返回值中得到有用的信息。 + * 如果函数是一个返回多个结果的集合函数(returnsSet 为真),则认为这是一个协议违规(protocol violation), + * 因为在集合函数中不允许返回 NULL 的元组类型。 + * 对于不返回集合的函数(returnsSet 为假),则会跳出循环,不再继续处理,相当于认为函数的返回结果为 NULL 值。 + * 另一种可能的选择是忽略这个 NULL 值的结果,继续获取下一个结果,但是这段代码选择了在某些情况下报错, + * 或者跳出循环,以确保结果集的一致性。 */ if (returnsTuple && fcinfo.isnull && !has_out_param) { if (!returnsSet) { @@ -2918,6 +3400,7 @@ Tuplestorestate* ExecMakeTableFunctionResult( /* * If first time through, build tupdesc and tuplestore for result + * 在第一次循环中的操作,用于为结果构建元组描述符(tupdesc)和元组存储(tuplestore)。 */ if (first_time) { oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); @@ -2925,6 +3408,8 @@ Tuplestorestate* ExecMakeTableFunctionResult( /* * Use the type info embedded in the rowtype Datum to look * up the needed tupdesc. Make a copy for the query. + * 在函数返回的元组数据中嵌入的类型信息, + * 以及如何使用这些信息查找所需的元组描述符(tupdesc)并为查询创建其副本。 */ HeapTupleHeader td; @@ -2939,6 +3424,7 @@ Tuplestorestate* ExecMakeTableFunctionResult( } else { /* * Scalar type, so make a single-column descriptor + * 当函数的返回结果是标量(scalar)类型时,会创建一个单列的元组描述符(tupdesc)。 */ tupdesc = CreateTemplateTupleDesc(1, false, TAM_HEAP); TupleDescInitEntry(tupdesc, (AttrNumber)1, "column", funcrettype, -1, 0); @@ -2951,6 +3437,7 @@ Tuplestorestate* ExecMakeTableFunctionResult( /* * Store current resultset item. + * 将当前的结果项存储到结果集中。 */ if (returnsTuple) { HeapTupleHeader td; @@ -2960,6 +3447,8 @@ Tuplestorestate* ExecMakeTableFunctionResult( /* * Verify all returned rows have same subtype; necessary in * case the type is RECORD. + * 在函数返回的多个结果项中,验证所有的结果项是否具有相同的子类型。 + * 这是在处理函数返回复杂类型(如 RECORD)时需要注意的问题。 */ if ((HeapTupleHeaderGetTypeId(td) != tupdesc->tdtypeid || HeapTupleHeaderGetTypMod(td) != tupdesc->tdtypmod) && @@ -2978,6 +3467,8 @@ Tuplestorestate* ExecMakeTableFunctionResult( /* * tuplestore_puttuple needs a HeapTuple not a bare * HeapTupleHeader, but it doesn't need all the fields. + * 在将结果项存储到 tuplestore 中时,需要将 HeapTupleHeader 转换成 HeapTuple, + * 但是并不需要转换所有的字段信息。 */ tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; @@ -2989,6 +3480,7 @@ Tuplestorestate* ExecMakeTableFunctionResult( /* * Are we done? + * 检查是否完成 */ if (rsinfo.isDone != ExprMultipleResult) { break; @@ -3017,6 +3509,9 @@ no_function_result: * If we got nothing from the function (ie, an empty-set or NULL result), * we have to create the tuplestore to return, and if it's a * non-set-returning function then insert a single all-nulls row. + * 如果从函数得不到任何结果(即,空集或NULL结果), + * 则需要创建一个用于返回的 tuplestore。如果函数不返回集合, + * 那么需要插入一行全为 NULL 的记录。 */ if (rsinfo.setResult == NULL) { MemoryContextSwitchTo(econtext->ecxt_per_query_memory); @@ -3041,6 +3536,8 @@ no_function_result: /* * If function provided a tupdesc, cross-check it. We only really need to * do this for functions returning RECORD, but might as well do it always. + * 如果函数提供了一个 tupdesc(元组描述), + * 则进行交叉检查。虽然实际上只有返回 RECORD 类型的函数需要这样做,但为了保险起见,无论何时都可以进行交叉检查。 */ if (rsinfo.setDesc) { tupledesc_match(expectedDesc, rsinfo.setDesc); @@ -3049,6 +3546,9 @@ no_function_result: * If it is a dynamically-allocated TupleDesc, free it: it is * typically allocated in a per-query context, so we must avoid * leaking it across multiple usages. + * 如果 tupdesc(元组描述)是动态分配的,就释放它。 + * 通常情况下,tupdesc 是在每个查询上下文中分配的, + * 所以我们必须避免在多个使用情况之间泄漏它。 */ if (rsinfo.setDesc->tdrefcount == -1) FreeTupleDesc(rsinfo.setDesc); @@ -3067,14 +3567,18 @@ no_function_result: } /* reset the u_sess->SPI_cxt.is_stp, u_sess->SPI_cxt.is_proconfig_set - and error message value */ + and error message value + 重置 u_sess->SPI_cxt.is_stp、u_sess->SPI_cxt.is_proconfig_set 和错误消息的值。 + */ u_sess->SPI_cxt.is_stp = savedIsSTP; u_sess->SPI_cxt.is_proconfig_set = savedProConfigIsSet; if (needResetErrMsg) { stp_reset_commit_rolback_err_msg(); } - /* All done, pass back the tuplestore */ + /* All done, pass back the tuplestore + 函数完成,返回结果tuplestore + */ return rsinfo.setResult; } @@ -3084,6 +3588,14 @@ no_function_result: * * Evaluate the functional result of a list of arguments by calling the * function manager. + * + * ExecEvalFunc:这个函数用于计算一个函数的结果。它接受一个函数表达式节点(FuncExprState), + * 并调用函数管理器来执行这个函数。函数管理器负责找到对应的函数实现并执行它。 + * 这个函数可能返回一个标量值或者一个结果集。 + * + * ExecEvalOper:这个函数用于计算一个操作符(比如 +、-、*、/ 等)的结果。 + * 类似于 ExecEvalFunc,它也接受一个操作符表达式节点(OpExprState),并调用函数管理器来执行相应的操作。 + * 操作符管理器负责找到对应的操作符实现并执行它。 * ---------------------------------------------------------------- */ /* ---------------------------------------------------------------- @@ -3093,15 +3605,29 @@ no_function_result: (1) Initialize the FuncExprState node by init_fcache function, including initialization parameters, memory management, etc. (2) Judge whether the returned result is of set type according to the data in the FuncExprState function, and call the corresponding function to calculate the result. + + 初始化:ExecEvalFunc函数开始时通过init_fcache函数初始化FuncExprState节点。 + 这个初始化包括设置各种参数和管理执行函数所需的内存。 + + 结果类型:然后,函数会判断返回的结果是否为集合类型。 + 在这段代码的上下文中,"集合"指的是由返回多行的函数返回的结果。 + 代码会检查FuncExprState结构中的数据,以确定结果是预期的集合还是单个值。 + + 计算:根据结果预期是集合还是单个值,会调用相应的函数来计算结果。 + 如果结果是集合,这可能涉及迭代多行结果集并针对每行执行某些计算。 + 如果结果是单个值,函数将基于提供的参数计算该值。 */ static Datum ExecEvalFunc(FuncExprState* fcache, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) { - /* This is called only the first time through */ + /* This is called only the first time through + ExecEvalFunc函数被调用仅在第一次通过时。 + */ FuncExpr* func = (FuncExpr*)fcache->xprstate.expr; Oid target_type = InvalidOid; Oid source_type = InvalidOid; - /* Initialize function lookup info */ + /* Initialize function lookup info + 初始化函数的查找信息 */ init_fcache(func->funcid, func->inputcollid, fcache, econtext->ecxt_per_query_memory, true); bool has_refcursor = func_has_refcursor_args(func->funcid, &fcache->fcinfo_data); @@ -3142,6 +3668,13 @@ static Datum ExecEvalFunc(FuncExprState* fcache, ExprContext* econtext, bool* is * or any of its input expressions can return a set. Otherwise, invoke * ExecMakeFunctionResultNoSets. In either case, change the evalfunc * pointer to go directly there on subsequent uses. + * + * 如果函数本身或其任何输入参数的表达式可能返回一个集合, + * 那么选择调用ExecMakeFunctionResult函数来处理计算结果。 + * + * 如果函数既不返回集合,也没有任何输入参数的表达式返回集合, + * 那么选择调用ExecMakeFunctionResultNoSets函数来处理计算结果。 + */ if (fcache->func.fn_retset) { if (has_refcursor) { @@ -3287,6 +3820,14 @@ static Datum ExecEvalOper(FuncExprState* fcache, ExprContext* econtext, bool* is * function. Note that this is *always* derived from the equals * operator, but since we need special processing of the arguments * we can not simply reuse ExecEvalOper() or ExecEvalFunc(). + * + * ExecEvalDistinct函数用于处理IS DISTINCT FROM操作, + * 该操作用于判断两个值是否不同(不等), + * 并且在判断时需要特殊处理参数是否为NULL的情况。 + * + * 首先,函数会对两个参数进行判断,看它们是否有一个是NULL。 + * 如果有至少一个参数是NULL,那么结果就已经知道了,不需要继续计算,直接返回结果。 + * 如果两个参数都不是NULL,那么函数会继续执行,并根据等于操作的特性,判断这两个参数是否相等,得出最终的结果 * ---------------------------------------------------------------- */ static Datum ExecEvalDistinct(FuncExprState* fcache, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -3295,13 +3836,15 @@ static Datum ExecEvalDistinct(FuncExprState* fcache, ExprContext* econtext, bool FunctionCallInfo fcinfo; ExprDoneCond argDone; - /* Set default values for result flags: non-null, not a set result */ + /* Set default values for result flags: non-null, not a set result + 为结果标志设置默认值。 */ *isNull = false; if (isDone != NULL) *isDone = ExprSingleResult; /* * Initialize function cache if first time through + 第一次执行该函数,则初始化缓存 */ if (fcache->func.fn_oid == InvalidOid) { DistinctExpr* op = (DistinctExpr*)fcache->xprstate.expr; @@ -3312,6 +3855,7 @@ static Datum ExecEvalDistinct(FuncExprState* fcache, ExprContext* econtext, bool /* * Evaluate arguments + * 评估参数 */ fcinfo = &fcache->fcinfo_data; argDone = ExecEvalFuncArgs(fcinfo, fcache->args, econtext); @@ -3320,16 +3864,20 @@ static Datum ExecEvalDistinct(FuncExprState* fcache, ExprContext* econtext, bool Assert(fcinfo->nargs == 2); if (fcinfo->argnull[0] && fcinfo->argnull[1]) { - /* Both NULL? Then is not distinct... */ + /* Both NULL? Then is not distinct... + 如果都是 NULL,说明它们并不是不同的,因此设置结果为 FALSE。 */ result = BoolGetDatum(FALSE); } else if (fcinfo->argnull[0] || fcinfo->argnull[1]) { - /* Only one is NULL? Then is distinct... */ + /* Only one is NULL? Then is distinct... + 如果只有一个表达式为 NULL,说明它们是不同的,因此设置结果为 TRUE。*/ result = BoolGetDatum(TRUE); } else { fcinfo->isnull = false; result = FunctionCallInvoke(fcinfo); *isNull = fcinfo->isnull; - /* Must invert result of "=" */ + /* Must invert result of "=" + 如果只有一个表达式为 NULL,说明它们是不同的,因此设置结果为 TRUE。 + */ result = BoolGetDatum(!DatumGetBool(result)); } @@ -3343,6 +3891,12 @@ static Datum ExecEvalDistinct(FuncExprState* fcache, ExprContext* econtext, bool * and we combine the results across all array elements using OR and AND * (for ANY and ALL respectively). Of course we short-circuit as soon as * the result is known. + * 评估形如 "scalar op ANY/ALL (array)" 的表达式, + * 其中 "scalar" 是一个标量值,"op" 是一个操作符,"ANY" 和 "ALL" 是量词, + * "(array)" 是一个数组。这类表达式通常用于比较标量值和数组的元素。 + * + * "scalar op ANY(array)": 对数组的每个元素执行标量值与操作符的比较,只要有一个元素满足条件,结果就为真。 + * "scalar op ALL(array)": 对数组的每个元素执行标量值与操作符的比较,只有所有元素都满足条件,结果才为真。 */ static Datum ExecEvalScalarArrayOp( ScalarArrayOpExprState* sstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -3363,13 +3917,15 @@ static Datum ExecEvalScalarArrayOp( bits8* bitmap = NULL; int bitmask; - /* Set default values for result flags: non-null, not a set result */ + /* Set default values for result flags: non-null, not a set result + 设置结果标志的默认值,确保结果不是 NULL,并且不是一个集合。 */ *isNull = false; if (isDone != NULL) *isDone = ExprSingleResult; /* * Initialize function cache if first time through + * 第一次执行此部分代码,将会初始化函数缓存。 */ if (sstate->fxprstate.func.fn_oid == InvalidOid) { init_fcache( @@ -3381,7 +3937,9 @@ static Datum ExecEvalScalarArrayOp( * Evaluate arguments */ fcinfo = &sstate->fxprstate.fcinfo_data; - /* init the number of arguments to a function. */ + /* init the number of arguments to a function. + 初始化一个函数的参数数量 + */ InitFunctionCallInfoArgs(*fcinfo, 2, 1); argDone = ExecEvalFuncArgs(fcinfo, sstate->fxprstate.args, econtext); if (argDone != ExprSingleResult) @@ -3392,6 +3950,12 @@ static Datum ExecEvalScalarArrayOp( /* * If the array is NULL then we return NULL --- it's not very meaningful * to do anything else, even if the operator isn't strict. + * 如果数组为NULL,则返回NULL,即使操作符不是严格的,这也没有太多意义。 + * + * 处理方式保持了一致性并遵循了NULL的语义。 + * 即使操作符本身不是严格的,这个规则仍然成立。 + * 因为在NULL的情况下,操作符的具体逻辑也是不确定的,因此返回NULL是一种合理的方式。 + * 这有助于避免在处理数组运算时产生不确定或不一致的结果。 */ if (fcinfo->argnull[1]) { *isNull = true; @@ -3405,6 +3969,9 @@ static Datum ExecEvalScalarArrayOp( * flag. This is correct even if the scalar is NULL; since we would * evaluate the operator zero times, it matters not whether it would want * to return NULL. + * 如果数组为空,根据useOr标志,我们返回FALSE或TRUE。 + * 即使标量值为NULL,这种处理也是正确的。 + * 因为如果数组为空,运算符将不会被计算,所以它是否返回NULL并不重要。 */ nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr)); if (nitems <= 0) @@ -3413,6 +3980,8 @@ static Datum ExecEvalScalarArrayOp( /* * If the scalar is NULL, and the function is strict, return NULL; no * point in iterating the loop. + * 如果标量值为NULL并且函数被定义为严格模式(即fn_strict为真),则返回NULL。 + * 在这种情况下,没有必要进行循环迭代计算,因为严格模式的函数在输入为NULL时直接返回NULL。 */ if (fcinfo->argnull[0] && sstate->fxprstate.func.fn_strict) { *isNull = true; @@ -3422,6 +3991,9 @@ static Datum ExecEvalScalarArrayOp( /* * We arrange to look up info about the element type only once per series * of calls, assuming the element type doesn't change underneath us. + * 将在一系列调用中只查找有关元素类型的信息一次, + * 假设元素类型不会在我们的操作过程中发生更改。 + * 这样可以避免在每次循环迭代中都进行元素类型的查找,提高性能效率。 */ if (sstate->element_type != ARR_ELEMTYPE(arr)) { get_typlenbyvalalign(ARR_ELEMTYPE(arr), &sstate->typlen, &sstate->typbyval, &sstate->typalign); @@ -3434,7 +4006,7 @@ static Datum ExecEvalScalarArrayOp( result = BoolGetDatum(!useOr); resultnull = false; - /* Loop over the array elements */ + /* Loop over the array elements 遍历数组*/ s = (char*)ARR_DATA_PTR(arr); bitmap = ARR_NULLBITMAP(arr); bitmask = 1; @@ -3443,7 +4015,7 @@ static Datum ExecEvalScalarArrayOp( Datum elt; Datum thisresult; - /* Get array element, checking for NULL */ + /* Get array element, checking for NULL 检查NULL值 */ if (bitmap && (*bitmap & bitmask) == 0) { fcinfo->arg[1] = (Datum)0; fcinfo->argnull[1] = true; @@ -3455,7 +4027,7 @@ static Datum ExecEvalScalarArrayOp( fcinfo->argnull[1] = false; } - /* Call comparison function */ + /* Call comparison function 调用comparison函数*/ if (fcinfo->argnull[1] && sstate->fxprstate.func.fn_strict) { fcinfo->isnull = true; thisresult = (Datum)0; @@ -3464,14 +4036,16 @@ static Datum ExecEvalScalarArrayOp( thisresult = FunctionCallInvoke(fcinfo); } - /* Combine results per OR or AND semantics */ + /* Combine results per OR or AND semantics + 根据逻辑操作符(OR 或者 AND)的语义,我们在循环迭代中将结果进行组合。 + */ if (fcinfo->isnull) resultnull = true; else if (useOr) { if (DatumGetBool(thisresult)) { result = BoolGetDatum(true); resultnull = false; - break; /* needn't look at any more elements */ + break; /* needn't look at any more elements 不需要再继续查看其他元素。*/ } } else { if (!DatumGetBool(thisresult)) { @@ -3481,7 +4055,8 @@ static Datum ExecEvalScalarArrayOp( } } - /* advance bitmap pointer if any */ + /* advance bitmap pointer if any + 如果存在位图(bitmap),则将位图指针前进(移动到下一个位置) */ if (bitmap != NULL) { bitmask <<= 1; if (bitmask == 0x100) { @@ -3508,6 +4083,8 @@ static Datum ExecEvalScalarArrayOp( * clause in the qualification, but appears lower (as a function * argument, for example), or in the target list. Not that you * need to know this, mind you... + * 评估布尔表达式的代码块,涉及到逻辑运算符的短路求值(short-circuiting)。 + * 在SQL查询的条件表达式中,AND 和 OR 运算符可以引起短路求值,从而减少不必要的计算。 * ---------------------------------------------------------------- */ static Datum ExecEvalNot(BoolExprState* notclause, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -3523,6 +4100,7 @@ static Datum ExecEvalNot(BoolExprState* notclause, ExprContext* econtext, bool* /* * if the expression evaluates to null, then we just cascade the null back * to whoever called us. + * 检查表达式的结果是否为 NULL,如果是,则将 NULL 值传递回调用此函数的地方。 */ if (*isNull) return expr_value; @@ -3530,6 +4108,7 @@ static Datum ExecEvalNot(BoolExprState* notclause, ExprContext* econtext, bool* /* * evaluation of 'not' is simple.. expr is false, then return 'true' and * vice versa. + * 实现了逻辑 NOT 操作的计算逻辑。 */ return BoolGetDatum(!DatumGetBool(expr_value)); } @@ -3537,10 +4116,16 @@ static Datum ExecEvalNot(BoolExprState* notclause, ExprContext* econtext, bool* /* ---------------------------------------------------------------- * ExecEvalOr * ---------------------------------------------------------------- - *The main execution process of ExecEvalOr function is as follows. -(1) Traverse child expression clauses. -(2) Use the function ExecEvalExpr to call the expression calculation function in clause and calculate the result. -(3) To judge the results, if there is a result in the or expression that meets the conditions, it will jump out of the loop and return directly. + * The main execution process of ExecEvalOr function is as follows. + (1) Traverse child expression clauses. + (2) Use the function ExecEvalExpr to call the expression calculation function in clause and calculate the result. + (3) To judge the results, if there is a result in the or expression that meets the conditions, it will jump out of the loop and return directly. + * ExecEvalOr 函数实现了逻辑 OR 操作的计算逻辑。以下是其主要执行过程的描述: + 1. 遍历子表达式子句:`ExecEvalOr` 函数首先会遍历 OR 表达式的各个子表达式,这些子表达式即为 OR 表达式的操作数。 + 2. 计算子表达式的结果:对于每个子表达式,函数会使用 `ExecEvalExpr` 函数调用表达式的计算函数来计算子表达式的结果。 + 3. 判断结果:在计算每个子表达式的结果后,函数会检查是否有任何一个子表达式的结果为真(非零)。 + 如果有任何一个子表达式的结果为真,那么整个 OR 表达式的结果就为真,函数会跳出循环并直接返回真值。 + * 综上所述,`ExecEvalOr` 函数的作用是计算逻辑 OR 表达式的结果,通过检查每个子表达式的结果,如果有一个为真,则返回真;否则,返回假。 */ static Datum ExecEvalOr(BoolExprState* orExpr, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) { @@ -3565,6 +4150,16 @@ static Datum ExecEvalOr(BoolExprState* orExpr, ExprContext* econtext, bool* isNu * 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. + * 如果在 OR 表达式中的任何子句评估为 TRUE,则整体 OR 结果为 TRUE,无论其他子句的状态如何。 + * 因此,如果至少有一个子句是 TRUE,就没有必要继续评估剩余的子句,结果可以立即确定为 TRUE。 + * + * 如果没有任何子句评估为 TRUE,但有一个或多个子句评估为 NULL,则 OR 表达式的结果为 NULL。 + * 这是因为 NULL 可以被解释为“不知道”,如果至少有一个子句是 TRUE,则整体结果仍然可以是 TRUE。 + * + * 如果所有已知的输入(子句)都评估为 FALSE,但有一个或多个子句评估为 NULL,结果仍然是 NULL。 + * 这是因为存在“不知道”(NULL)的值,阻止我们自信地确定整体结果应为 TRUE 还是 FALSE。有可能如果我们知道其值,其中一个 NULL 子句可能是 TRUE。 + * + * 仅当所有已知的输入都评估为 FALSE 时,我们才可以有把握地得出 OR 表达式的整体结果为 FALSE */ foreach (clause, clauses) { ExprState* clausestate = (ExprState*)lfirst(clause); @@ -3574,6 +4169,8 @@ static Datum ExecEvalOr(BoolExprState* orExpr, ExprContext* econtext, bool* isNu /* * if we have a non-null true result, then return it. + * 如果在遍历 OR 表达式的子句时,发现某个子句的结果既不是 NULL 也是 TRUE, + * 那么就直接返回这个结果,而不需要继续评估其他子句。 */ if (*isNull) AnyNull = true; /* remember we got a null */ @@ -3581,7 +4178,9 @@ static Datum ExecEvalOr(BoolExprState* orExpr, ExprContext* econtext, bool* isNu return clause_value; } - /* AnyNull is true if at least one clause evaluated to NULL */ + /* AnyNull is true if at least one clause evaluated to NULL + 如果有至少一个子句的结果是 NULL,那么整个 OR 表达式的结果也是 NULL。 + */ *isNull = AnyNull; return BoolGetDatum(false); } @@ -3608,6 +4207,10 @@ static Datum ExecEvalAnd(BoolExprState* andExpr, ExprContext* econtext, bool* is * we return NULL; otherwise we return TRUE. This makes sense when you * interpret NULL as "don't know", using the same sort of reasoning as for * OR, above. + * 根据逻辑规则,只要有一个子句的结果是 false,整个 AND 表达式的结果就是 false, + * 因此代码在发现子句结果为 false 时会立即返回 false。如果没有子句的结果为 false, + * 但至少有一个子句的结果是 NULL,那么整个 AND 表达式的结果会被判定为 NULL。 + * 如果所有子句的结果都是 true 或者 NULL,那么整个 AND 表达式的结果将会是 true。 */ foreach (clause, clauses) { ExprState* clausestate = (ExprState*)lfirst(clause); @@ -3634,6 +4237,7 @@ static Datum ExecEvalAnd(BoolExprState* andExpr, ExprContext* econtext, bool* is * * Evaluate a rowtype coercion operation. This may require * rearranging field positions. + * 实现了对行类型强制转换的逻辑 * ---------------------------------------------------------------- */ static Datum ExecEvalConvertRowtype( @@ -3647,13 +4251,19 @@ static Datum ExecEvalConvertRowtype( tupDatum = ExecEvalExpr(cstate->arg, econtext, isNull, isDone); - /* this test covers the isDone exception too: */ + /* this test covers the isDone exception too: + if (*isNull) 的部分,它不仅适用于检查是否为 NULL 的情况, + 还适用于检查是否出现了执行异常(isDone exception)。 + */ if (*isNull) return tupDatum; tuple = DatumGetHeapTupleHeader(tupDatum); - /* Lookup tupdescs if first time through or after rescan */ + /* Lookup tupdescs if first time through or after rescan + 在第一次执行或重新扫描时需要查找表描述(tupdescs)。 + 在执行表达式时,需要根据具体的数据类型获取对应的表描述信息,以便正确地解释和处理数据。 + */ if (cstate->indesc == NULL) { get_cached_rowtype(exprType((Node*)convert->arg), -1, &cstate->indesc, econtext); cstate->initialized = false; @@ -3666,14 +4276,22 @@ static Datum ExecEvalConvertRowtype( Assert(HeapTupleHeaderGetTypeId(tuple) == cstate->indesc->tdtypeid); Assert(HeapTupleHeaderGetTypMod(tuple) == cstate->indesc->tdtypmod); - /* if first time through, initialize conversion map */ + /* if first time through, initialize conversion map + 如果是第一次执行转换操作,就需要初始化转换映射(conversion map)。 + */ if (!cstate->initialized) { MemoryContext old_cxt; - /* allocate map in long-lived memory context */ + /* allocate map in long-lived memory context + 需要在长时间存活的内存上下文(long-lived memory context)中分配内存。 + */ old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - /* prepare map from old to new attribute numbers */ + /* prepare map from old to new attribute numbers + 准备一个从旧属性号到新属性号的映射(map), + 用于在转换一个行类型(rowtype)到另一个行类型时, + 确定哪些属性对应于哪些属性。 + */ cstate->map = convert_tuples_by_name(cstate->indesc, cstate->outdesc, gettext_noop("could not convert row type")); cstate->initialized = true; @@ -3683,12 +4301,14 @@ static Datum ExecEvalConvertRowtype( /* * No-op if no conversion needed (not clear this can happen here). + * 它表示如果没有需要进行转换的情况,就直接返回原始的输入数据(行类型)。 */ if (cstate->map == NULL) return tupDatum; /* * do_convert_tuple needs a HeapTuple not a bare HeapTupleHeader. + * 需要传递一个完整的 HeapTuple 而不仅仅是一个堆元组头部。 */ tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); tmptup.t_data = tuple; @@ -3705,6 +4325,12 @@ static Datum ExecEvalConvertRowtype( * inside the WHEN clauses, and will have expressions * for results. * - thomas 1998-11-09 + * 评估 CASE 表达式,并根据 WHEN 子句的条件判断来确定返回的值。 + * + * 在评估过程中,会依次遍历 WHEN 子句,检查其布尔表达式的值是否为真。 + * 如果找到了第一个满足条件的 WHEN 子句,就会返回相应的结果表达式的值。 + * 如果没有任何 WHEN 子句满足条件,那么会返回 ELSE 子句(如果有的话)的结果表达式的值。 + * 如果既没有满足条件的 WHEN 子句,也没有 ELSE 子句,那么返回 NULL。 * ---------------------------------------------------------------- */ static Datum ExecEvalCase(CaseExprState* caseExpr, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -3730,6 +4356,15 @@ static Datum ExecEvalCase(CaseExprState* caseExpr, ExprContext* econtext, bool* * * 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. + * + * 当存在测试表达式(test expression)时,如何处理测试表达式的计算和结果的保存? + * + * 当存在测试表达式时,CASE 表达式需要首先计算测试表达式的值, + * 然后将该值保存在特定的位置,以便之后的 CaseTestExpr 占位符可以访问到它。 + * + * 需要保存和恢复先前设置的 econtext 中的 caseValue 字段,以防该节点位于更大的 CASE 表达式内部。 + * 不要在计算测试表达式值之前分配给 econtext 字段,而是在从测试表达式的计算返回后再进行赋值。 + * 不要将 &econtext->caseValue_isNull 传递给递归调用,以避免在递归调用内部与该变量发生别名问题,特别是当测试表达式本身包含另一个 CASE 表达式时。 */ save_datum = econtext->caseValue_datum; save_isNull = econtext->caseValue_isNull; @@ -3744,6 +4379,10 @@ static Datum ExecEvalCase(CaseExprState* caseExpr, ExprContext* econtext, bool* * we evaluate each of the WHEN clauses in turn, as soon as one is true we * return the corresponding result. If none are true then we return the * value of the default clause, or NULL if there is none. + * 依次计算每个 WHEN 子句中的条件表达式。 + * 一旦找到第一个条件为真的 WHEN 子句,就返回该子句的结果表达式的值。 + * 如果所有的 WHEN 子句的条件都为假,那么返回 CASE 表达式中的默认子句(ELSE 子句)的结果表达式的值。 + * 如果没有默认子句,或者默认子句的条件也为假,那么返回 NULL 值 */ foreach (clause, clauses) { CaseWhenState* wclause = (CaseWhenState*)lfirst(clause); @@ -3756,6 +4395,8 @@ static Datum ExecEvalCase(CaseExprState* caseExpr, ExprContext* econtext, bool* * if we have a true test, then we return the result, since the case * statement is satisfied. A NULL result from the test is not * considered true. + * 如果在遍历 CASE 表达式的各个分支时, + * 发现某个 WHEN 子句的条件表达式为真(且不为 NULL),那么就会返回该分支的结果表达式的值。 */ if (DatumGetBool(clause_value) && !clause_isNull) { econtext->caseValue_datum = save_datum; @@ -3779,6 +4420,7 @@ static Datum ExecEvalCase(CaseExprState* caseExpr, ExprContext* econtext, bool* * ExecEvalCaseTestExpr * * Return the value stored by CASE. + * 返回CASE内存的值 */ static Datum ExecEvalCaseTestExpr(ExprState* exprstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) { @@ -3796,6 +4438,11 @@ static Datum ExecEvalCaseTestExpr(ExprState* exprstate, ExprContext* econtext, b * * A bit is set if the corresponding expression is NOT part of the set of * grouping expressions in the current grouping set. + * + * 用于计算聚合操作中的分组表达式。 + * 对于给定的一组表达式,函数返回一个位掩码(bitmask),其中每个位对应于一个表达式,而右侧的位是最低有效位。 + * + * 如果对应的表达式不是当前分组集合中的分组表达式之一,则相应的位被设置为 1;如果对应的表达式是分组表达式之一,则相应的位被设置为 0。 */ static Datum ExecEvalGroupingFuncExpr( GroupingFuncExprState* gstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -3824,6 +4471,7 @@ static Datum ExecEvalGroupingFuncExpr( /* ---------------------------------------------------------------- * ExecEvalArray - ARRAY[] expressions + * 评估ARRAY[]表达式。 * ---------------------------------------------------------------- */ static Datum ExecEvalArray(ArrayExprState* astate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -3836,13 +4484,15 @@ static Datum ExecEvalArray(ArrayExprState* astate, ExprContext* econtext, bool* int dims[MAXDIM]; int lbs[MAXDIM]; - /* Set default values for result flags: non-null, not a set result */ + /* Set default values for result flags: non-null, not a set result + 为了确保在没有实际元素的空数组情况下,结果标志被正确地设置为非空且不是一个集合结果 + */ *isNull = false; if (isDone != NULL) *isDone = ExprSingleResult; if (!arrayExpr->multidims) { - /* Elements are presumably of scalar type */ + /* Elements are presumably of scalar type 元素是标量类型 */ int nelems; Datum* dvalues = NULL; bool* dnulls = NULL; @@ -3851,7 +4501,8 @@ static Datum ExecEvalArray(ArrayExprState* astate, ExprContext* econtext, bool* ndims = 1; nelems = list_length(astate->elements); - /* Shouldn't happen here, but if length is 0, return empty array */ + /* Shouldn't happen here, but if length is 0, return empty array + 长度为0返回空数组,但这是不该发生的。*/ if (nelems == 0) return PointerGetDatum(construct_empty_array(element_type)); @@ -3866,14 +4517,16 @@ static Datum ExecEvalArray(ArrayExprState* astate, ExprContext* econtext, bool* i++; } - /* setup for 1-D array of the given length */ + /* setup for 1-D array of the given length + 针对给定长度设置一个一维数组 */ dims[0] = nelems; lbs[0] = 1; result = construct_md_array( dvalues, dnulls, ndims, dims, lbs, element_type, astate->elemlength, astate->elembyval, astate->elemalign); } else { - /* Must be nested array expressions */ + /* Must be nested array expressions + 只处理嵌套数组表达式 */ int nbytes = 0; int nitems = 0; int outer_nelems = 0; @@ -3899,7 +4552,9 @@ static Datum ExecEvalArray(ArrayExprState* astate, ExprContext* econtext, bool* subbytes = (int*)palloc(i * sizeof(int)); subnitems = (int*)palloc(i * sizeof(int)); - /* loop through and get data area from each element */ + /* loop through and get data area from each element + 遍历数组的每个元素,并从每个元素中获取数据区域 + */ foreach (element, astate->elements) { ExprState* e = (ExprState*)lfirst(element); bool eisnull = false; @@ -3908,7 +4563,8 @@ static Datum ExecEvalArray(ArrayExprState* astate, ExprContext* econtext, bool* int this_ndims; arraydatum = ExecEvalExpr(e, econtext, &eisnull, NULL); - /* temporarily ignore null subarrays */ + /* temporarily ignore null subarrays + 暂时忽略掉包含 NULL 值的子数组 */ if (eisnull) { haveempty = true; continue; @@ -3916,7 +4572,9 @@ static Datum ExecEvalArray(ArrayExprState* astate, ExprContext* econtext, bool* array = DatumGetArrayTypeP(arraydatum); - /* run-time double-check on element type */ + /* run-time double-check on element type + 在运行时对元素类型进行双重检查。 + */ if (element_type != ARR_ELEMTYPE(array)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), @@ -3927,14 +4585,17 @@ static Datum ExecEvalArray(ArrayExprState* astate, ExprContext* econtext, bool* format_type_be(element_type)))); this_ndims = ARR_NDIM(array); - /* temporarily ignore zero-dimensional subarrays */ + /* temporarily ignore zero-dimensional subarrays + 暂时忽略零维子数组*/ if (this_ndims <= 0) { haveempty = true; continue; } if (firstone) { - /* Get sub-array details from first member */ + /* Get sub-array details from first member + 在处理嵌套数组时,从第一个成员中获取子数组的维度和边界等信息, + 以便后续能够正确地处理其他成员的子数组。 */ elem_ndims = this_ndims; ndims = elem_ndims + 1; if (ndims <= 0 || ndims > MAXDIM) @@ -3955,7 +4616,9 @@ static Datum ExecEvalArray(ArrayExprState* astate, ExprContext* econtext, bool* firstone = false; } else { - /* Check other sub-arrays are compatible */ + /* Check other sub-arrays are compatible + 检查其他子数组是否与第一个子数组兼容 + */ if (elem_ndims != this_ndims || memcmp(elem_dims, ARR_DIMS(array), elem_ndims * sizeof(int)) != 0 || memcmp(elem_lbs, ARR_LBOUND(array), elem_ndims * sizeof(int)) != 0) ereport(ERROR, @@ -3979,16 +4642,19 @@ static Datum ExecEvalArray(ArrayExprState* astate, ExprContext* econtext, bool* * otherwise, if some were and some weren't, raise error. (Note: we * must special-case this somehow to avoid trying to generate a 1-D * array formed from empty arrays. It's not ideal...) + * 如果所有的项都是 NULL 或空数组,返回一个空数组; + * 否则,如果既有非空项又有空项,就会引发错误。 + * (注意:我们必须以某种特殊方式处理这种情况,以避免尝试生成由空数组形成的一维数组。这并不理想...) */ if (haveempty) { - if (ndims == 0) /* didn't find any nonempty array */ + if (ndims == 0) /* didn't find any nonempty array没找到任何非空数组 */ return PointerGetDatum(construct_empty_array(element_type)); ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("multidimensional arrays must have array expressions with matching dimensions"))); } - /* setup for multi-D array */ + /* setup for multi-D array 设置多维数组 */ dims[0] = outer_nelems; lbs[0] = 1; for (i = 1; i < ndims; i++) { @@ -4000,7 +4666,7 @@ static Datum ExecEvalArray(ArrayExprState* astate, ExprContext* econtext, bool* dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems); nbytes += dataoffset; } else { - dataoffset = 0; /* marker for no null bitmap */ + dataoffset = 0; /* marker for no null bitmap 无空值位图的标记 */ nbytes += ARR_OVERHEAD_NONULLS(ndims); } @@ -4019,7 +4685,9 @@ static Datum ExecEvalArray(ArrayExprState* astate, ExprContext* econtext, bool* int len = (nbytes - ARR_DATA_OFFSET(result)); iitem = 0; for (i = 0; i < outer_nelems; i++) { - /* make sure the destMax of memcpy_s should never be zero. */ + /* make sure the destMax of memcpy_s should never be zero. + 确保 memcpy_s 的 destMax 参数永远不为零。 + */ if (subbytes[i] != 0) { rc = memcpy_s(dat, len, subdata[i], subbytes[i]); securec_check(rc, "\0", "\0"); @@ -4038,6 +4706,7 @@ static Datum ExecEvalArray(ArrayExprState* astate, ExprContext* econtext, bool* /* ---------------------------------------------------------------- * ExecEvalRow - ROW() expressions + * 作用是评估 ROW() 表达式,并构建代表行的复合值。 * ---------------------------------------------------------------- */ static Datum ExecEvalRow(RowExprState* rstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -4050,21 +4719,25 @@ static Datum ExecEvalRow(RowExprState* rstate, ExprContext* econtext, bool* isNu int i; errno_t rc = EOK; - /* Set default values for result flags: non-null, not a set result */ + /* Set default values for result flags: non-null, not a set result + 为结果标志设置默认值:非 NULL,不是一个集合的结果。 + */ *isNull = false; if (isDone != NULL) *isDone = ExprSingleResult; - /* Allocate workspace */ + /* Allocate workspace 分配工作空间 */ natts = rstate->tupdesc->natts; values = (Datum*)palloc0(natts * sizeof(Datum)); isnull = (bool*)palloc(natts * sizeof(bool)); - /* preset to nulls in case rowtype has some later-added columns */ + /* preset to nulls in case rowtype has some later-added columns + 在某些后续添加的列的情况下,预先将其设置为 NULL 值。 + */ rc = memset_s(isnull, natts * sizeof(bool), true, natts * sizeof(bool)); securec_check(rc, "\0", "\0"); - /* Evaluate field values */ + /* Evaluate field values 评估字段值*/ i = 0; foreach (arg, rstate->args) { ExprState* e = (ExprState*)lfirst(arg); @@ -4083,6 +4756,7 @@ static Datum ExecEvalRow(RowExprState* rstate, ExprContext* econtext, bool* isNu /* ---------------------------------------------------------------- * ExecEvalRowCompare - ROW() comparison-op ROW() + * 评估 ROW() 表达式比较的一部分 * ---------------------------------------------------------------- */ static Datum ExecEvalRowCompare(RowCompareExprState* rstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -4096,7 +4770,7 @@ static Datum ExecEvalRowCompare(RowCompareExprState* rstate, ExprContext* econte if (isDone != NULL) *isDone = ExprSingleResult; - *isNull = true; /* until we get a result */ + *isNull = true; /* until we get a result 表明在开始阶段,将 isNull 设置为 true,即暂时将结果标志设置为 NULL。*/ i = 0; forboth(l, rstate->largs, r, rstate->rargs) @@ -4109,18 +4783,20 @@ static Datum ExecEvalRowCompare(RowCompareExprState* rstate, ExprContext* econte locfcinfo.arg[0] = ExecEvalExpr(le, econtext, &locfcinfo.argnull[0], NULL); locfcinfo.arg[1] = ExecEvalExpr(re, econtext, &locfcinfo.argnull[1], NULL); if (rstate->funcs[i].fn_strict && (locfcinfo.argnull[0] || locfcinfo.argnull[1])) - return (Datum)0; /* force NULL result */ + return (Datum)0; /* force NULL result 强制为NULL结果 */ locfcinfo.isnull = false; cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo)); if (locfcinfo.isnull) return (Datum)0; /* force NULL result */ if (cmpresult != 0) - break; /* no need to compare remaining columns */ + break; /* no need to compare remaining columns 不需要比较剩余的列*/ i++; } switch (rctype) { - /* EQ and NE cases aren't allowed here */ + /* EQ and NE cases aren't allowed here + 这里不允许使用 EQ 和 NE 情况 + */ case ROWCOMPARE_LT: result = (cmpresult < 0); break; @@ -4138,7 +4814,7 @@ static Datum ExecEvalRowCompare(RowCompareExprState* rstate, ExprContext* econte (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmodule(MOD_EXECUTOR), errmsg("unrecognized RowCompareType: %d", (int)rctype))); - result = 0; /* keep compiler quiet */ + result = 0; /* keep compiler quiet 确保 result 变量在每个代码路径上都有一个定义的值*/ break; } @@ -4148,6 +4824,7 @@ static Datum ExecEvalRowCompare(RowCompareExprState* rstate, ExprContext* econte /* ---------------------------------------------------------------- * ExecEvalCoalesce + * 用于执行 COALESCE 表达式,这是一种逻辑表达式,用于从一系列值中选择第一个非 NULL 的值 * ---------------------------------------------------------------- */ static Datum ExecEvalCoalesce( @@ -4158,7 +4835,9 @@ static Datum ExecEvalCoalesce( if (isDone != NULL) *isDone = ExprSingleResult; - /* Simply loop through until something NOT NULL is found */ + /* Simply loop through until something NOT NULL is found + 找到非NULL并返还 + */ foreach (arg, coalesceExpr->args) { ExprState* e = (ExprState*)lfirst(arg); Datum value; @@ -4175,6 +4854,7 @@ static Datum ExecEvalCoalesce( /* ---------------------------------------------------------------- * ExecEvalMinMax + * 用于执行Min() 和 Max()聚合表达式,它们用于找到一组值中的最小值或最大值。 * ---------------------------------------------------------------- */ static Datum ExecEvalMinMax(MinMaxExprState* minmaxExpr, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -4214,7 +4894,7 @@ static Datum ExecEvalMinMax(MinMaxExprState* minmaxExpr, ExprContext* econtext, locfcinfo.arg[1] = value; locfcinfo.isnull = false; cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo)); - if (locfcinfo.isnull) /* probably should not happen */ + if (locfcinfo.isnull) /* probably should not happen 一般情况下,比较函数应该返回一个非 NULL 的结果。如果比较函数返回了 NULL,那么可能是出现了一些意外的情况,需要进行检查和处理。 */ continue; if (cmpresult > 0 && op == IS_LEAST) result = value; @@ -4228,6 +4908,8 @@ static Datum ExecEvalMinMax(MinMaxExprState* minmaxExpr, ExprContext* econtext, /* ---------------------------------------------------------------- * ExecEvalXml + * 执行各种 XML 表达式, + * 包括 XMLCONCAT、XMLFOREST、XMLELEMENT、XMLPARSE、XMLPI、XMLROOT、XMLSERIALIZE 和 DOCUMENT 表达式。 * ---------------------------------------------------------------- */ static Datum ExecEvalXml(XmlExprState* xmlExpr, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -4304,7 +4986,9 @@ static Datum ExecEvalXml(XmlExprState* xmlExpr, ExprContext* econtext, bool* isN text* data = NULL; bool preserve_whitespace = false; - /* arguments are known to be text, bool */ + /* arguments are known to be text, bool + "参数已知为文本和布尔类型 + */ Assert(list_length(xmlExpr->args) == 2); e = (ExprState*)linitial(xmlExpr->args); @@ -4328,7 +5012,9 @@ static Datum ExecEvalXml(XmlExprState* xmlExpr, ExprContext* econtext, bool* isN ExprState* e = NULL; text* argument = NULL; - /* optional argument is known to be text */ + /* optional argument is known to be text + 参数已知为文本 + */ Assert(list_length(xmlExpr->args) <= 1); if (xmlExpr->args) { @@ -4351,8 +5037,9 @@ static Datum ExecEvalXml(XmlExprState* xmlExpr, ExprContext* econtext, bool* isN xmltype* data = NULL; text* version = NULL; int standalone; - - /* arguments are known to be xml, text, int */ + /* arguments are known to be xml, text, int + 参数已知为 XML、文本和整数类型 + */ Assert(list_length(xmlExpr->args) == 3); e = (ExprState*)linitial(xmlExpr->args); @@ -4380,7 +5067,7 @@ static Datum ExecEvalXml(XmlExprState* xmlExpr, ExprContext* econtext, bool* isN case IS_XMLSERIALIZE: { ExprState* e = NULL; - /* argument type is known to be xml */ + /* argument type is known to be xml 参数已知为XML类型*/ Assert(list_length(xmlExpr->args) == 1); e = (ExprState*)linitial(xmlExpr->args); @@ -4396,7 +5083,7 @@ static Datum ExecEvalXml(XmlExprState* xmlExpr, ExprContext* econtext, bool* isN case IS_DOCUMENT: { ExprState* e = NULL; - /* optional argument is known to be xml */ + /* optional argument is known to be xml 可选参数已知为XML类型 */ Assert(list_length(xmlExpr->args) == 1); e = (ExprState*)linitial(xmlExpr->args); @@ -4425,6 +5112,9 @@ static Datum ExecEvalXml(XmlExprState* xmlExpr, ExprContext* econtext, bool* isN * Note that this is *always* derived from the equals operator, * but since we need special processing of the arguments * we can not simply reuse ExecEvalOper() or ExecEvalFunc(). + * NULLIF 函数的作用是在两个参数相等时返回 NULL + * 由于相等性是等于操作符的一个重要概念,因此 NULLIF 的行为与等于操作符的某种变体相关联。 + * 但是,由于我们需要对参数进行特殊处理,所以不能简单地重用其他函数执行逻辑 * ---------------------------------------------------------------- */ static Datum ExecEvalNullIf(FuncExprState* nullIfExpr, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -4438,6 +5128,7 @@ static Datum ExecEvalNullIf(FuncExprState* nullIfExpr, ExprContext* econtext, bo /* * Initialize function cache if first time through + * 根据第一次执行的需要来初始化函数缓存 */ if (nullIfExpr->func.fn_oid == InvalidOid) { NullIfExpr* op = (NullIfExpr*)nullIfExpr->xprstate.expr; @@ -4455,36 +5146,44 @@ static Datum ExecEvalNullIf(FuncExprState* nullIfExpr, ExprContext* econtext, bo ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("NULLIF does not support set arguments"))); Assert(fcinfo->nargs == 2); - /* if either argument is NULL they can't be equal */ + /* if either argument is NULL they can't be equal 如果两个参数中的任何一个是 NULL,那么它们不能相等 */ if (!fcinfo->argnull[0] && !fcinfo->argnull[1]) { fcinfo->isnull = false; result = FunctionCallInvoke(fcinfo); - /* if the arguments are equal return null */ + /* if the arguments are equal return null 如果参数相等返回NULL */ if (!fcinfo->isnull && DatumGetBool(result)) { *isNull = true; return (Datum)0; } } - /* else return first argument */ + /* else return first argument 如果不相等返回第一个参数 */ *isNull = fcinfo->argnull[0]; return fcinfo->arg[0]; } +/* + * 用于检查给定行元组(Tuple)是否符合特定的空值测试(NullTest) + * + */ static Datum CheckRowTypeIsNull(TupleDesc tupDesc, HeapTupleData tmptup, NullTest *ntest) { int att; for (att = 1; att <= tupDesc->natts; att++) { - /* ignore dropped columns */ + /* ignore dropped columns 跳过已删除的列,只对实际存在的列执行空值测试。 */ if (tupDesc->attrs[att - 1]->attisdropped) continue; if (tableam_tops_tuple_attisnull(&tmptup, att, tupDesc)) { - /* null field disproves IS NOT NULL */ + /* null field disproves IS NOT NULL + 如果在进行空值测试时发现某一列的值为 NULL,那么这个 NULL 值将使得该列不满足 IS NOT NULL 的条件。 + */ if (ntest->nulltesttype == IS_NOT_NULL) return BoolGetDatum(false); } else { - /* non-null field disproves IS NULL */ + /* non-null field disproves IS NULL + 如果某一列的值不为 NULL,而空值测试的类型是 IS_NULL + */ if (ntest->nulltesttype == IS_NULL) return BoolGetDatum(false); } @@ -4493,6 +5192,9 @@ static Datum CheckRowTypeIsNull(TupleDesc tupDesc, HeapTupleData tmptup, NullTes return BoolGetDatum(true); } +/* + 用于执行与空值测试相关的逻辑,以判断行元组是否满足特定的条件。 + */ static Datum CheckRowTypeIsNullForAFormat(TupleDesc tupDesc, HeapTupleData tmptup, NullTest *ntest) { int att; @@ -4523,6 +5225,7 @@ static Datum CheckRowTypeIsNullForAFormat(TupleDesc tupDesc, HeapTupleData tmptu * ExecEvalNullTest * * Evaluate a NullTest node. + * 用于执行空值测试(NullTest)节点的函数。 * ---------------------------------------------------------------- */ static Datum ExecEvalNullTest(NullTestState* nstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -4533,7 +5236,7 @@ static Datum ExecEvalNullTest(NullTestState* nstate, ExprContext* econtext, bool result = ExecEvalExpr(nstate->arg, econtext, isNull, isDone); if (isDone && *isDone == ExprEndResult) - return result; /* nothing to check */ + return result; /* nothing to check 检查是否达到结果,是的话则无需继续函数 */ if (ntest->argisrow && !(*isNull)) { /* @@ -4561,6 +5264,16 @@ static Datum ExecEvalNullTest(NullTestState* nstate, ExprContext* econtext, bool * (,"(,)") | f | f * (,) | t | f * + * + * SQL标准定义了对于非空行类型参数的 IS NULL 和 IS NOT NULL 测试: + * + * 如果一个非空行类型中的所有字段都为 NULL,那么 R IS NULL 为真。 + * 如果一个非空行类型中没有字段为 NULL,那么 R IS NOT NULL 为真。 + * 这个定义故意不递归地处理,意味着它只执行原始的 attisnull 测试,而不会递归地检查是否所有字段都是 NULL 或者都不是 NULL。 + * 标准中没有考虑到零字段行(没有字段的行),但在这里,我们将其视为同时满足这两个谓词。 + * + * 总之,注释解释了对于非空行类型的空值测试行为,以及在实际代码中如何处理这些情况,以保持与标准定义的一致性。 + * */ HeapTupleHeader tuple; Oid tupType; @@ -4573,11 +5286,14 @@ static Datum ExecEvalNullTest(NullTestState* nstate, ExprContext* econtext, bool tupType = HeapTupleHeaderGetTypeId(tuple); tupTypmod = HeapTupleHeaderGetTypMod(tuple); - /* Lookup tupdesc if first time through or if type changes */ + /* Lookup tupdesc if first time through or if type changes + 根据行类型的标识符和修饰符,获取相应的行类型描述符。这可以避免重复的描述符创建和查找,并提高执行效率 + */ tupDesc = get_cached_rowtype(tupType, tupTypmod, &nstate->argdesc, econtext); /* * heap_attisnull needs a HeapTuple not a bare HeapTupleHeader. + * 需要传递一个完整的 HeapTuple 而不是仅仅是 HeapTupleHeader。 */ tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); tmptup.t_data = tuple; @@ -4588,7 +5304,9 @@ static Datum ExecEvalNullTest(NullTestState* nstate, ExprContext* econtext, bool return CheckRowTypeIsNull(tupDesc, tmptup, ntest); } } else { - /* Simple scalar-argument case, or a null rowtype datum */ + /* Simple scalar-argument case, or a null rowtype datum + 了对于标量参数和 NULL 行类型参数的处理方式,根据 NullTest 的类型进行判断并返回相应的结果。 + */ switch (ntest->nulltesttype) { case IS_NULL: if (*isNull) { @@ -4616,6 +5334,8 @@ static Datum ExecEvalNullTest(NullTestState* nstate, ExprContext* econtext, bool * ExecEvalHashFilter * * Evaluate a HashFilter node. + * 评估一个 HashFilter 节点。 + * HashFilter 节点通常在分布式数据库中用于优化分布键的过滤操作,以减少不必要的数据传输。 * ---------------------------------------------------------------- */ static Datum ExecEvalHashFilter(HashFilterState* hstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -4643,8 +5363,8 @@ static Datum ExecEvalHashFilter(HashFilterState* hstate, ExprContext* econtext, value = ExecEvalExpr(e, econtext, isNull, isDone); int null_value_dn_index = (hstate->nodelist != NULL) ? hstate->nodelist[0] - : /* fetch first dn in group's dn list */ - 0; /* fetch first dn index */ + : /* fetch first dn in group's dn list 取第一个DataNode的索引值作为null_value_dn_index*/ + 0; /* fetch first dn index 获取第一个DataNode的索引值 */ if (*isNull) { if (null_value_dn_index == u_sess->pgxc_cxt.PGXCNodeId) { @@ -4665,15 +5385,21 @@ static Datum ExecEvalHashFilter(HashFilterState* hstate, ExprContext* econtext, } } - /* If has non null value, it should get nodeId and deside if need filter the value or not. */ + /* If has non null value, it should get nodeId and deside if need filter the value or not. + 在具有非空值的情况下的逻辑。如果存在非空值,代码将获取节点ID并决定是否需要对该值进行过滤。 + */ if (hasNonNullValue) { modulo = hstate->bucketMap[abs((int)hashValue) & (hstate->bucketCnt - 1)]; nodeIndex = hstate->nodelist[modulo]; /* If there are null value and non null value, and the last value in distkey is null, - we should set isNull is false. */ + we should set isNull is false. + 如果分布键中既有空值又有非空值,代码会将isNull设置为假(false),以确保即使最后一个分布键值是null,也会返回非null的结果。 + */ *isNull = false; - /* Look into the handles and return correct position in array */ + /* Look into the handles and return correct position in array + 查找句柄(handles)并返回数组中的正确位置。 + */ if (nodeIndex == u_sess->pgxc_cxt.PGXCNodeId) return BoolGetDatum(true); else @@ -4686,17 +5412,19 @@ static Datum ExecEvalHashFilter(HashFilterState* hstate, ExprContext* econtext, * ExecEvalBooleanTest * * Evaluate a BooleanTest node. + * 评估一个BooleanTest节点的函数,用于执行布尔测试操作。 * ---------------------------------------------------------------- */ static Datum ExecEvalBooleanTest(GenericExprState* bstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) { BooleanTest* btest = (BooleanTest*)bstate->xprstate.expr; Datum result; - + + //评估表达式参数 result = ExecEvalExpr(bstate->arg, econtext, isNull, isDone); if (isDone && *isDone == ExprEndResult) - return result; /* nothing to check */ + return result; /* nothing to check 以及完成了评估,则无需继续下去*/ switch (btest->booltesttype) { case IS_TRUE: @@ -4758,6 +5486,10 @@ static Datum ExecEvalBooleanTest(GenericExprState* bstate, ExprContext* econtext * Test the provided data against the domain constraint(s). If the data * passes the constraint specifications, pass it through (return the * datum) otherwise throw an error. + * 对领域(Domain)的强制转换操作的函数实现。 + * 将测试提供的数据是否满足领域的约束条件。 + * 如果数据满足约束条件,则直接返回数据。 + * 如果数据不满足约束条件,将抛出一个错误。 */ static Datum ExecEvalCoerceToDomain( CoerceToDomainState* cstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -4792,6 +5524,9 @@ static Datum ExecEvalCoerceToDomain( * nodes. We must save and restore prior setting of * econtext's domainValue fields, in case this node is * itself within a check expression for another domain. + * 在评估领域的检查约束表达式时,可能会涉及嵌套的情况, + * 即一个领域的检查约束表达式中又包含了另一个领域的强制转换操作。 + * 为了确保在嵌套情况下正确处理这些表达式的评估,需要保存和恢复上下文中的domainValue字段。 */ save_datum = econtext->domainValue_datum; save_isNull = econtext->domainValue_isNull; @@ -4821,7 +5556,9 @@ static Datum ExecEvalCoerceToDomain( } } - /* If all has gone well (constraints did not fail) return the datum */ + /* If all has gone well (constraints did not fail) return the datum + 在成功通过所有领域约束的检查后,返回强制转换操作的结果值(datum)的情况。 + */ return result; } @@ -4829,6 +5566,8 @@ static Datum ExecEvalCoerceToDomain( * ExecEvalCoerceToDomainValue * * Return the value stored by CoerceToDomain. + * 负责返回由 CoerceToDomain 操作存储的值。 + * 函数允许表达式评估过程中的其他部分检索之前在执行 CoerceToDomain 操作时存储的强制转换后的值和空值状态。 */ static Datum ExecEvalCoerceToDomainValue( ExprState* exprstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -4843,6 +5582,8 @@ static Datum ExecEvalCoerceToDomainValue( * ExecEvalFieldSelect * * Evaluate a FieldSelect node. + * 从复合类型(例如行或记录)中选择特定的字段值,并进行一系列的错误检查和数据类型验证。 + * 如果选择的字段不合法,或者字段的值为空,或者数据类型不匹配,代码会相应地处理并返回结果。 * ---------------------------------------------------------------- */ static Datum ExecEvalFieldSelect(FieldSelectState* fstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -4860,7 +5601,9 @@ static Datum ExecEvalFieldSelect(FieldSelectState* fstate, ExprContext* econtext tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull, isDone); - /* this test covers the isDone exception too: */ + /* this test covers the isDone exception too: + 这个条件判断覆盖了空值和已结束状态的情况。 + */ if (*isNull) return tupDatum; @@ -4869,13 +5612,18 @@ static Datum ExecEvalFieldSelect(FieldSelectState* fstate, ExprContext* econtext tupType = HeapTupleHeaderGetTypeId(tuple); tupTypmod = HeapTupleHeaderGetTypMod(tuple); - /* Lookup tupdesc if first time through or if type changes */ + /* Lookup tupdesc if first time through or if type changes + 当首次进入函数或者数据类型发生变化时,需要查找对应的表描述(TupleDesc)。 + */ tupDesc = get_cached_rowtype(tupType, tupTypmod, &fstate->argdesc, econtext); /* * Find field's attr record. Note we don't support system columns here: a * datum tuple doesn't have valid values for most of the interesting * system columns anyway. + * 代码中进行的字段操作是针对表中的普通列,而不是系统列。 + * 数据元组(datum tuple)并不包含对系统列的有效值。 + * 在这个上下文中,代码的设计不考虑处理系统列,因为它们在这种情况下并不具有意义或有效值。 */ if (fieldnum <= 0) /* should never happen */ ereport(ERROR, @@ -4889,7 +5637,7 @@ static Datum ExecEvalFieldSelect(FieldSelectState* fstate, ExprContext* econtext errmsg("attribute number %d exceeds number of columns %d", fieldnum, tupDesc->natts))); attr = tupDesc->attrs[fieldnum - 1]; - /* Check for dropped column, and force a NULL result if so */ + /* 检查指定的字段是否已经被删除(dropped column),如果是的话,则将结果强制设为 NULL,并返回一个空的 Datum 值。 */ if (attr->attisdropped) { *isNull = true; return (Datum)0; @@ -4897,6 +5645,7 @@ static Datum ExecEvalFieldSelect(FieldSelectState* fstate, ExprContext* econtext /* Check for type mismatch --- possible after ALTER COLUMN TYPE? */ /* As in ExecEvalScalarVar, we should but can't check typmod */ + //用于检查字段数据类型是否与预期数据类型相匹配,以及通过注释提及的限制情况。 if (fselect->resulttype != attr->atttypid) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), @@ -4905,7 +5654,9 @@ static Datum ExecEvalFieldSelect(FieldSelectState* fstate, ExprContext* econtext format_type_be(attr->atttypid), format_type_be(fselect->resulttype)))); - /* heap_getattr needs a HeapTuple not a bare HeapTupleHeader */ + /* heap_getattr needs a HeapTuple not a bare HeapTupleHeader + 需要的参数是一个完整的堆元组(HeapTuple),而不仅仅是堆元组头部(HeapTupleHeader) + */ tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); tmptup.t_data = tuple; @@ -4917,6 +5668,7 @@ static Datum ExecEvalFieldSelect(FieldSelectState* fstate, ExprContext* econtext * ExecEvalFieldStore * * Evaluate a FieldStore node. + * 对于 FieldStore 节点的求值操作。 * ---------------------------------------------------------------- */ static Datum ExecEvalFieldStore(FieldStoreState* fstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -4938,10 +5690,12 @@ static Datum ExecEvalFieldStore(FieldStoreState* fstate, ExprContext* econtext, if (isDone != NULL && *isDone == ExprEndResult) return tupDatum; - /* Lookup tupdesc if first time through or after rescan */ + /* Lookup tupdesc if first time through or after rescan + 首次执行或重新扫描(rescan)时,需要查找相应的元组描述(TupleDesc) + */ tupDesc = get_cached_rowtype(fstore->resulttype, -1, &fstate->argdesc, econtext); - /* Allocate workspace */ + /* 空间分配 */ values = (Datum*)palloc(tupDesc->natts * sizeof(Datum)); isnull = (bool*)palloc(tupDesc->natts * sizeof(bool)); @@ -4949,6 +5703,7 @@ static Datum ExecEvalFieldStore(FieldStoreState* fstate, ExprContext* econtext, /* * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader. We * set all the fields in the struct just in case. + * 执行 heap_deform_tuple 操作时,需要传递一个完整的 HeapTuple 而不是仅仅是 HeapTupleHeader */ HeapTupleHeader tuphdr; HeapTupleData tmptup; @@ -4966,7 +5721,9 @@ static Datum ExecEvalFieldStore(FieldStoreState* fstate, ExprContext* econtext, tableam_tops_deform_tuple(&tmptup, tupDesc, values, isnull); } else { - /* Convert null input tuple into an all-nulls row */ + /* Convert null input tuple into an all-nulls row + 当输入的 HeapTuple 为 NULL 时,需要将其转换成一个全为 NULL 的行(row) + */ rc = memset_s(isnull, tupDesc->natts * sizeof(bool), true, tupDesc->natts * sizeof(bool)); securec_check(rc, "\0", "\0"); } @@ -4985,13 +5742,11 @@ static Datum ExecEvalFieldStore(FieldStoreState* fstate, ExprContext* econtext, Assert(fieldnum > 0 && fieldnum <= tupDesc->natts); /* - * 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 ArrayRef 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 - * the caseValue is just paranoia, but let's do it anyway.) + * 在字段存储操作中,可能会出现这样的情况:需要将新的值放入某个字段,而这个新值本身可能是一个复杂的表达式,需要使用到字段的旧值。 + * 为了解决这个问题,代码使用了 CaseTestExpr 机制,将字段的旧值传递给新值的表达式。 + * 这样,新值的表达式可以通过访问 CaseTestExpr 得到旧值,从而在需要的情况下进行修改。 + * 在这里使用 CaseTestExpr 是因为在字段赋值操作中不存在 CASE 表达式,因此可以安全地重用这个机制。 + * 尽管在这个上下文中保存和恢复 caseValue 可能有些多余,但为了保险起见,仍然进行了这些操作。 */ econtext->caseValue_datum = values[fieldnum - 1]; econtext->caseValue_isNull = isnull[fieldnum - 1]; @@ -5014,6 +5769,7 @@ static Datum ExecEvalFieldStore(FieldStoreState* fstate, ExprContext* econtext, * ExecEvalRelabelType * * Evaluate a RelabelType node. + * 对于 RelabelType 节点的求值 * ---------------------------------------------------------------- */ static Datum ExecEvalRelabelType(GenericExprState* exprstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -5025,6 +5781,8 @@ static Datum ExecEvalRelabelType(GenericExprState* exprstate, ExprContext* econt * ExecEvalCoerceViaIO * * Evaluate a CoerceViaIO node. + * 对于 CoerceViaIO 节点的求值 + * 对源表达式求值,然后使用 I/O 转换函数将其转换为目标数据类型,返回转换后的值。 * ---------------------------------------------------------------- */ static Datum ExecEvalCoerceViaIO(CoerceViaIOState* iostate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -5039,13 +5797,13 @@ static Datum ExecEvalCoerceViaIO(CoerceViaIOState* iostate, ExprContext* econtex return inputval; /* nothing to do */ if (*isNull) - string = NULL; /* output functions are not called on nulls */ + string = NULL; /* output functions are not called on nulls 对于 NULL 值,输出函数(output functions)不会被调用。 */ else string = OutputFunctionCall(&iostate->outfunc, inputval); result = InputFunctionCall(&iostate->infunc, string, iostate->intypioparam, -1); - /* The input function cannot change the null/not-null status */ + /* 输入函数(input functions)不能改变 NULL/非NULL状态 */ return result; } @@ -5053,6 +5811,7 @@ static Datum ExecEvalCoerceViaIO(CoerceViaIOState* iostate, ExprContext* econtex * ExecEvalArrayCoerceExpr * * Evaluate an ArrayCoerceExpr node. + * 计算 ArrayCoerceExpr 节点 的值 * ---------------------------------------------------------------- */ static Datum ExecEvalArrayCoerceExpr( @@ -5071,40 +5830,50 @@ static Datum ExecEvalArrayCoerceExpr( return result; /* nothing to do */ /* - * If it's binary-compatible, modify the element type in the array header, - * but otherwise leave the array as we received it. + * 如果目标元素类型与输入元素类型是二进制兼容的,意味着它们在二进制表示上可以无损地转换。 + * 在这种情况下,代码会修改数组头部的元素类型为目标元素类型,并返回修改后的数组。 + * + * 如果目标元素类型与输入元素类型不是二进制兼容的,代码不会进行任何操作,直接返回输入的数组。 */ if (!OidIsValid(acoerce->elemfuncid)) { - /* Detoast input array if necessary, and copy in any case */ + /* Detoast input array if necessary, and copy in any case + 如果输入的数组需要被解压缩(detoasted)的话,会执行解压缩操作。 + 一定会执行复制操作 + */ array = DatumGetArrayTypePCopy(result); ARR_ELEMTYPE(array) = astate->resultelemtype; PG_RETURN_ARRAYTYPE_P(array); } - /* Detoast input array if necessary, but don't make a useless copy */ + /* Detoast input array if necessary, but don't make a useless copy + 若需要解压则解压,但不会进行无用的复制操作 + */ array = DatumGetArrayTypeP(result); - /* Initialize function cache if first time through */ + /* Initialize function cache if first time through + 在首次经过此代码段时,初始化函数缓存。 + */ if (astate->elemfunc.fn_oid == InvalidOid) { AclResult aclresult; - /* Check permission to call function */ + /* Check permission to call function 检查权限,是否能够调用 */ aclresult = pg_proc_aclcheck(acoerce->elemfuncid, GetUserId(), ACL_EXECUTE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(acoerce->elemfuncid)); - /* Set up the primary fmgr lookup information */ + /* Set up the primary fmgr lookup information 设置主要的函数管理器(Function Manager)查找信息。*/ fmgr_info_cxt(acoerce->elemfuncid, &(astate->elemfunc), econtext->ecxt_per_query_memory); fmgr_info_set_expr((Node*)acoerce, &(astate->elemfunc)); } /* * Use array_map to apply the function to each array element. - * + * 使用 array_map 函数将指定的函数应用到数组的每个元素上。 * We pass on the desttypmod and isExplicit flags whether or not the * function wants them. - * + * 不管函数是否需要这些参数,我们都会将 desttypmod 和 isExplicit 参数传递给函数。 * Note: coercion functions are assumed to not use collation. + * 需要注意的是,这里假设强制转换函数不会使用排序规则(collation)。也就是说,在这个上下文中,我们不考虑排序规则的影响。 */ InitFunctionCallInfoData(locfcinfo, &(astate->elemfunc), 3, InvalidOid, NULL, NULL); locfcinfo.arg[0] = PointerGetDatum(array); @@ -5123,6 +5892,9 @@ static Datum ExecEvalArrayCoerceExpr( * The planner must convert CURRENT OF into a TidScan qualification. * So, we have to be able to do ExecInitExpr on a CurrentOfExpr, * but we shouldn't ever actually execute it. + * 用于对当前表达式(CURRENT OF)进行求值。 + * 计划器(planner)必须将 CURRENT OF 转换为 TidScan 的限制条件。 + * 因此,我们需要能够在 CurrentOfExpr 上执行 ExecInitExpr,但实际上我们不会真正执行它。 * ---------------------------------------------------------------- */ static Datum ExecEvalCurrentOfExpr(ExprState* exprstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) @@ -5136,6 +5908,9 @@ static Datum ExecEvalCurrentOfExpr(ExprState* exprstate, ExprContext* econtext, * ExecEvalExprSwitchContext * * Same as ExecEvalExpr, but get into the right allocation context explicitly. + * 它的作用类似于 ExecEvalExpr,但是明确地在正确的内存上下文中执行。 + * 将上下文切换为 econtext->ecxt_per_tuple_memory,这是一个适合于元组级别操作的内存上下文。 + * 在表达式求值完成后,它会恢复原来的上下文。这种方法可以确保在求值期间分配的内存会在求值结束后被正确释放,从而避免内存泄漏。 */ Datum ExecEvalExprSwitchContext(ExprState* expression, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) { @@ -5151,39 +5926,36 @@ Datum ExecEvalExprSwitchContext(ExprState* expression, ExprContext* econtext, bo /* * ExecInitExpr: prepare an expression tree for execution * - * This function builds and returns an ExprState tree paralleling the given - * Expr node tree. The ExprState tree can then be handed to ExecEvalExpr - * for execution. Because the Expr tree itself is read-only as far as - * ExecInitExpr and ExecEvalExpr are concerned, several different executions - * of the same plan tree can occur concurrently. + * 此函数用于构建并返回与给定的 Expr 节点树相对应的 ExprState 树。 + * 这个 ExprState 树可以被传递给 ExecEvalExpr 函数进行执行。 + * 由于 Expr 树本身对于 ExecInitExpr 和 ExecEvalExpr 是只读的,因此同一个计划树的多个执行可以同时进行。 * - * This must be called in a memory context that will last as long as repeated - * executions of the expression are needed. Typically the context will be - * the same as the per-query context of the associated ExprContext. + * 此函数必须在一个内存上下文中调用,该上下文的生命周期要足够长,以满足表达式需要多次重复执行的要求。 + * 通常情况下,该内存上下文应该与相关的 ExprContext 的每个查询上下文(per-query context)相同。 * - * Any Aggref, WindowFunc, or SubPlan nodes found in the tree are added to the - * lists of such nodes held by the parent PlanState. Otherwise, we do very - * little initialization here other than building the state-node tree. Any - * nontrivial work associated with initializing runtime info for a node should - * happen during the first actual evaluation of that node. (This policy lets - * us avoid work if the node is never actually evaluated.) + * 如果在表达式树中发现了 Aggref、WindowFunc 或 SubPlan 节点,那么这些节点将会被添加到父级 PlanState 中维护的相应节点列表中。 + * 这表示在执行计划状态的上下文中会记录这些特殊节点的存在。 + * 对于其他类型的节点,除了构建状态节点树以外,这里几乎不进行任何初始化工作。实际上,对于这些节点的非常规初始化工作应该在首次实际评估该节点时进行。 + * 通过在首次实际评估节点时进行初始化,可以避免在表达式初始化阶段做过多的工作,因为这些工作可能在节点未被实际执行的情况下就会被浪费。 + * 这种策略确保了在需要时才会进行实际的初始化操作。 * - * Note: there is no ExecEndExpr function; we assume that any resource - * cleanup needed will be handled by just releasing the memory context - * in which the state tree is built. Functions that require additional - * cleanup work can register a shutdown callback in the ExprContext. + * 几个关键点: + * 在表达式初始化过程中,没有专门的 ExecEndExpr 函数。这与一些其他节点类型(如计划节点)不同,后者可能需要专门的结束函数来进行资源清理。 + * 在表达式初始化期间,创建的状态树以及相关的资源分配,都在特定的内存上下文中进行。释放这个内存上下文就能够释放与之关联的所有资源。 + * 如果某些特定的函数需要额外的清理工作,那么它们可以在 ExprContext 中注册一个关闭回调(shutdown callback)。这个回调会在上下文被销毁时自动触发,从而实现必要的资源清理操作。 * - * 'node' is the root of the expression tree to examine - * 'parent' is the PlanState node that owns the expression. + * node 参数是待执行的表达式树的根节点,函数将根据这个根节点递归地构建对应的 ExprState 树。 + * parent 参数是拥有这个表达式的 PlanState 节点。 + * 这表示这个表达式在执行上下文中的位置,函数会根据需要将表达式的内部节点添加到 PlanState 的相关列表中,如聚合函数、窗口函数、子查询等。 * - * 'parent' may be NULL if we are preparing an expression that is not - * associated with a plan tree. (If so, it can't have aggs or subplans.) - * This case should usually come through ExecPrepareExpr, not directly here. - *The execution process of the ExecInitExpr function is as follows. -(1) Determine whether the input node is empty. If it is empty,return NULL directly, indicating that there is no restriction for expression. -(2) According to the type of node input,Initialize variable evalfunc which is the execution function corresponding to node, -If the node has parameters or expressions, the function ExecInitExpr will be recursively called and ExprState tree will be generated. -(3) Return ExprState tree, and execute the expression recursively according to ExprState tree. + * parent 参数可以为空(NULL),表示正在准备一个不与计划树相关联的表达式。 + * 在这种情况下,表达式不能包含聚合函数或子查询等复杂结构。这种情况通常通过 ExecPrepareExpr 函数来调用,而不是直接在这里调用。 + * + * 执行过程简述: + * (1) 判断输入节点是否为空,如果为空,则直接返回 NULL,表示表达式没有限制。 + * (2) 根据输入节点的类型,初始化变量 evalfunc,该变量是与节点对应的执行函数。 + * 如果节点具有参数或表达式,则会递归调用 ExecInitExpr,并生成 ExprState 树。 + * (3) 返回 ExprState 树,并根据 ExprState 树递归执行表达式。 */ ExprState* ExecInitExpr(Expr* node, PlanState* parent) { @@ -5195,12 +5967,16 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) return NULL; } - /* Guard against stack overflow due to overly complex expressions */ + /* Guard against stack overflow due to overly complex expressions + 防止由于过于复杂的表达式导致堆栈溢出 + */ check_stack_depth(); switch (nodeTag(node)) { case T_Var: - /* varattno == InvalidAttrNumber means it's a whole-row Var */ + /* varattno == InvalidAttrNumber means it's a whole-row Var + 如果varattno的值为InvalidAttrNumber,那么表示这个变量(Var)引用的是整个行(whole-row Var),而不是特定的列 + */ if (((Var*)node)->varattno == InvalidAttrNumber) { WholeRowVarExprState* wstate = makeNode(WholeRowVarExprState); @@ -5263,6 +6039,10 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) * aggregates; nested agg functions are semantically * nonsensical. (This should have been caught earlier, * but we defend against it here anyway.) + * 如果聚合函数的参数中包含任何聚合函数, + * 那么会发出警告。嵌套的聚合函数在语义上是不合理的, + * 因此这段代码会检查是否有嵌套的聚合函数存在。 + * 虽然这种情况应该在更早的阶段就被捕获,但这里仍然会进行检查以防止这种情况的发生。 */ if (naggs != aggstate->numaggs) ereport(ERROR, @@ -5330,6 +6110,9 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) * 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( @@ -5350,7 +6133,7 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) astate->reflowerindexpr = (List*)ExecInitExpr((Expr*)aref->reflowerindexpr, parent); astate->refexpr = ExecInitExpr(aref->refexpr, parent); astate->refassgnexpr = ExecInitExpr(aref->refassgnexpr, parent); - /* do one-time catalog lookups for type info */ + /* do one-time catalog lookups for type info 进行一次性的目录查询以获取类型信息 */ astate->refattrlength = get_typlen(aref->refarraytype); get_typlenbyvalalign( aref->refelemtype, &astate->refelemlength, &astate->refelembyval, &astate->refelemalign); @@ -5438,7 +6221,9 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) sstate = ExecInitSubPlan(subplan, parent); - /* Add SubPlanState nodes to parent->subPlan */ + /* Add SubPlanState nodes to parent->subPlan + 将SubPlanState节点添加到父级PlanState的subPlan列表中 + */ parent->subPlan = lappend(parent->subPlan, sstate); state = (ExprState*)sstate; @@ -5492,10 +6277,10 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) iostate->xprstate.evalfunc = (ExprStateEvalFunc)ExecEvalCoerceViaIO; iostate->arg = ExecInitExpr(iocoerce->arg, parent); - /* lookup the result type's input function */ + /* lookup the result type's input function 在执行阶段查找结果类型的输入函数。 */ getTypeInputInfo(iocoerce->resulttype, &iofunc, &iostate->intypioparam); fmgr_info(iofunc, &iostate->infunc); - /* lookup the input type's output function */ + /* lookup the input type's output function 查找输入类型输出函数 */ getTypeOutputInfo(exprType((Node*)iocoerce->arg), &iofunc, &typisvarlena); fmgr_info(iofunc, &iostate->outfunc); state = (ExprState*)iostate; @@ -5509,7 +6294,7 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) astate->resultelemtype = get_element_type(acoerce->resulttype); if (astate->resultelemtype == InvalidOid) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("target type is not an array"))); - /* Arrays over domains aren't supported yet */ + /* Arrays over domains aren't supported yet 当前还不支持对领域(domain)上的数组进行操作。*/ Assert(getBaseType(astate->resultelemtype) == astate->resultelemtype); astate->elemfunc.fn_oid = InvalidOid; /* not initialized */ astate->amstate = (ArrayMapState*)palloc0(sizeof(ArrayMapState)); @@ -5561,7 +6346,7 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) outlist = lappend(outlist, estate); } astate->elements = outlist; - /* do one-time catalog lookup for type info */ + /* do one-time catalog lookup for type info 需要在初始化过程中进行一次性的目录查询,以获取数据类型的相关信息。 */ get_typlenbyvalalign( arrayexpr->element_typeid, &astate->elemlength, &astate->elembyval, &astate->elemalign); state = (ExprState*)astate; @@ -5575,17 +6360,19 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) int i; rstate->xprstate.evalfunc = (ExprStateEvalFunc)ExecEvalRow; - /* Build tupdesc to describe result tuples */ + /* Build tupdesc to describe result tuples 需要构建一个用于描述结果元组的元组描述符(tupdesc)*/ if (rowexpr->row_typeid == RECORDOID) { /* generic record, use runtime type assignment */ rstate->tupdesc = ExecTypeFromExprList(rowexpr->args, rowexpr->colnames, TAM_HEAP); BlessTupleDesc(rstate->tupdesc); - /* we won't need to redo this at runtime */ + /* we won't need to redo this at runtime + 需要使用运行时的方式为“generic record”类型的结果进行类型赋值, + 以便在计算结果时能够正确地构建和表示这种复杂的记录类型。*/ } else { - /* it's been cast to a named type, use that */ + /* it's been cast to a named type, use that 如果表达式被转换为一个命名类型,那么就使用该命名类型对应的元组描述符(tupdesc) */ rstate->tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1); } - /* Set up evaluation, skipping any deleted columns */ + /* Set up evaluation, skipping any deleted columns 设置表达式的评估过程,并在评估过程中跳过已删除的列。 */ Assert(list_length(rowexpr->args) <= rstate->tupdesc->natts); attrs = rstate->tupdesc->attrs; i = 0; @@ -5596,9 +6383,13 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) if (!attrs[i]->attisdropped) { /* * Guard against ALTER COLUMN TYPE on rowtype since - * the RowExpr was created. XXX should we check + * the RowExpr was created. + * 对行类型进行了一种保护措施,以确保在行类型发生 ALTER COLUMN TYPE 操作后,不会与 RowExpr 的预期结果不一致。 + * XXX should we check * typmod too? Not sure we can be sure it'll be the * same. + * 是否应该检查 typmod(类型修改标识),因为不能确定 typmod 是否会保持不变。 + * 这可能会影响行类型的一致性,因此需要根据实际情况来判断是否需要在 ALTER COLUMN TYPE 后重新评估 RowExpr 表达式。 */ if (exprType((Node*)e) != attrs[i]->atttypid) ereport(ERROR, @@ -5611,6 +6402,7 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) * Ignore original expression and insert a NULL. We * don't really care what type of NULL it is, so * always make an int4 NULL. + * 对 SELECT 查询进行某些转换或优化时,可能需要忽略原始表达式,并插入一个 NULL 值。 */ e = (Expr*)makeNullConst(INT4OID, -1, InvalidOid); } @@ -5674,6 +6466,9 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) * functions, we'd need to make a check here. But the * index support machinery doesn't do that, and neither * does this code. + * 尽管理论上可以对索引支持函数进行权限检查, + * 但是当前的索引支持机制和执行计划代码并不执行这种权限检查。 + * 这是因为索引支持函数通常是高度受限且受信任的操作,执行权限检查可能会带来额外的性能开销。 */ fmgr_info(proc, &(rstate->funcs[i])); rstate->collations[i] = inputcollid; @@ -5714,7 +6509,9 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) outlist = lappend(outlist, estate); } mstate->args = outlist; - /* Look up the btree comparison function for the datatype */ + /* Look up the btree comparison function for the datatype + 查找了适用于特定数据类型的B树比较函数,以确保在执行索引操作时能够正确地进行数据比较和排序。 + */ typentry = lookup_type_cache(minmaxexpr->minmaxtype, TYPECACHE_CMP_PROC); if (!OidIsValid(typentry->cmp_proc)) ereport(ERROR, @@ -5727,6 +6524,8 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) * functions, we'd need to make a check here. But the index * support machinery doesn't do that, and neither does this * code. + * 尽管可能需要在索引支持函数上执行权限检查, + * 但实际情况是索引支持机制不会进行这样的权限检查,因此这段代码也不会执行权限检查。 */ fmgr_info(typentry->cmp_proc, &(mstate->cfunc)); state = (ExprState*)mstate; @@ -5832,7 +6631,9 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) foreach (l, (List*)node) { outlist = lappend(outlist, ExecInitExpr((Expr*)lfirst(l), parent)); } - /* Don't fall through to the "common" code below */ + /* Don't fall through to the "common" code below + 在当前上下文中,执行通用代码并没有意义,因此建议立即退出或跳过通用代码的执行。 + */ gstrace_exit(GS_TRC_ID_ExecInitExpr); return (ExprState*)outlist; } @@ -5850,7 +6651,9 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) break; } - /* Common code for all state-node types */ + /* Common code for all state-node types + 下面的代码是适用于所有状态节点类型的共同代码 + */ state->expr = node; if (nodeTag(node) != T_TargetEntry) @@ -5863,13 +6666,18 @@ ExprState* ExecInitExpr(Expr* node, PlanState* parent) /* * ExecPrepareExpr --- initialize for expression execution outside a normal * Plan tree context. - * + * 用于在正常的计划树上下文之外初始化表达式的执行。 * This differs from ExecInitExpr in that we don't assume the caller is * already running in the EState's per-query context. Also, we run the * passed expression tree through expression_planner() to prepare it for * execution. (In ordinary Plan trees the regular planning process will have * made the appropriate transformations on expressions, but for standalone * expressions this won't have happened.) + * + * 函数与 ExecInitExpr 函数的区别在于: + * 它不假设调用者已经在 EState 的每个查询上下文中运行。 + * 它将传入的表达式树通过 expression_planner() 运行,以准备表达式执行。 + * 在普通的计划树中,常规的规划过程会对表达式进行适当的转换,但是对于独立的表达式来说,这一步可能没有执行。 */ ExprState* ExecPrepareExpr(Expr* node, EState* estate) { @@ -5894,28 +6702,23 @@ ExprState* ExecPrepareExpr(Expr* node, EState* estate) /* ---------------------------------------------------------------- * ExecQual * - * Evaluates a conjunctive boolean expression (qual list) and - * returns true iff none of the subexpressions are false. - * (We also return true if the list is empty.) + * 评估合取(AND)的布尔表达式(qual 列表), + * 并且当且仅当所有的子表达式都为 true 时返回 true(同时也包括列表为空的情况)。 * - * If some of the subexpressions yield NULL but none yield FALSE, - * then the result of the conjunction is NULL (ie, unknown) - * according to three-valued boolean logic. In this case, - * we return the value specified by the "resultForNull" parameter. + * 如果一些子表达式的结果为 NULL,但没有子表达式的结果为 FALSE, + * 根据三值布尔逻辑,合取操作的结果为 NULL(即未知)。 + * 在这种情况下,函数会根据传入的 "resultForNull" 参数来决定返回什么值。 + * 如果 "resultForNull" 参数为 true,则返回 true;如果 "resultForNull" 参数为 false,则返回 false。 + * 这就是在三值逻辑下处理 NULL 值的方式。 * - * Callers evaluating WHERE clauses should pass resultForNull=FALSE, - * since SQL specifies that tuples with null WHERE results do not - * get selected. On the other hand, callers evaluating constraint - * conditions should pass resultForNull=TRUE, since SQL also specifies - * that NULL constraint conditions are not failures. - * - * NOTE: it would not be correct to use this routine to evaluate an - * AND subclause of a boolean expression; for that purpose, a NULL - * result must be returned as NULL so that it can be properly treated - * in the next higher operator (cf. ExecEvalAnd and ExecEvalOr). - * This routine is only used in contexts where a complete expression - * is being evaluated and we know that NULL can be treated the same - * as one boolean result or the other. + * 在评估 WHERE 子句时,调用者应该将 resultForNull 参数设置为 FALSE, + * 因为 SQL 规定具有 NULL WHERE 子句结果的元组不会被选择。 + * 另一方面,在评估约束条件时,调用者应该将 resultForNull 参数设置为 TRUE, + * 因为 SQL 同样规定 NULL 约束条件不算失败。这个参数的设置取决于应用的上下文和SQL语义的要求。 + * + * 注意:在评估布尔表达式的 AND 子句时,使用这个函数是不正确的; + * 出于这个目的,必须将 NULL 结果作为 NULL 返回,以便在下一个更高级别的运算符中可以正确处理它(比如 ExecEvalAnd 和 ExecEvalOr)。 + * 这个函数只在评估完整表达式的上下文中使用,我们知道在这种情况下,NULL 可以被视为与一个布尔结果或另一个布尔结果相同 * * ---------------------------------------------------------------- */ @@ -5927,6 +6730,7 @@ bool ExecQual(List* qual, ExprContext* econtext, bool resultForNull) /* * debugging stuff + * 调试信息 */ EV_printf("ExecQual: qual is "); EV_nodeDisplay(qual); @@ -5934,20 +6738,19 @@ bool ExecQual(List* qual, ExprContext* econtext, bool resultForNull) /* * Run in short-lived per-tuple context while computing expressions. + * 在计算表达式时,在短暂的每个元组上下文中运行。 */ oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); - /* - * Evaluate the qual conditions one at a time. If we find a FALSE result, - * we can stop evaluating and return FALSE --- the AND result must be - * FALSE. Also, if we find a NULL result when resultForNull is FALSE, we - * can stop and return FALSE --- the AND result must be FALSE or NULL in - * that case, and the caller doesn't care which. - * - * If we get to the end of the list, we can return TRUE. This will happen - * when the AND result is indeed TRUE, or when the AND result is NULL (one - * or more NULL subresult, with all the rest TRUE) and the caller has - * specified resultForNull = TRUE. + /* + * 逐个评估 qual 条件。如果我们发现一个 FALSE 结果, + * 我们可以停止评估并返回 FALSE --- AND 结果必须是 FALSE。 + * 另外,如果我们在 resultForNull 为 FALSE 时发现一个 NULL 结果, + * 我们可以停止并返回 FALSE --- 在这种情况下,AND 结果必须是 FALSE 或 NULL, + * 调用者不关心是哪种情况。 + * 如果我们遍历完列表,我们可以返回 TRUE。 + * 这将在 AND 结果确实为 TRUE 时发生,或者当 AND 结果为 NULL(一个或多个 NULL 子结果,其余全部为 TRUE)时发生, + * 并且调用者已经指定了 resultForNull = TRUE。 */ result = true; @@ -5977,16 +6780,18 @@ bool ExecQual(List* qual, ExprContext* econtext, bool resultForNull) } /* - * Number of items in a tlist (including any resjunk items!) + * ExecTargetListLength + * tlist 中的项目数量(包括任何 resjunk 项目!) */ int ExecTargetListLength(List* targetlist) { - /* This used to be more complex, but fjoins are dead */ + /* 这曾经更加复杂,但是 fjoin 已经废弃了 */ return list_length(targetlist); } /* * Number of items in a tlist, not including any resjunk items + * 计算一个 tlist(目标列表)中项的数量,但不包括任何被标记为 "resjunk" 的项。 */ int ExecCleanTargetListLength(List* targetlist) { @@ -6003,13 +6808,18 @@ int ExecCleanTargetListLength(List* targetlist) return len; } +/* + * 用于获取数据库中的一个元组(tuple) + */ static HeapTuple get_tuple(Relation relation, ItemPointer tid) { Buffer user_buf = InvalidBuffer; HeapTuple tuple = NULL; HeapTuple new_tuple = NULL; - /* alloc mem for old tuple and set tuple id */ + /* alloc mem for old tuple and set tuple id + 为旧的元组分配内存并设置元组的标识 + */ tuple = (HeapTupleData *)heaptup_alloc(BLCKSZ); tuple->t_data = (HeapTupleHeader)((char *)tuple + HEAPTUPLESIZE); Assert(tid != NULL); @@ -6027,7 +6837,11 @@ static HeapTuple get_tuple(Relation relation, ItemPointer tid) return new_tuple; } +/* + check_huge_clob_paramter,它看起来是用于检查是否允许在函数参数中使用 "huge clob",并在不支持的情况下引发错误。 + */ static void check_huge_clob_paramter(FunctionCallInfoData* fcinfo, bool is_have_huge_clob) + { if (!is_have_huge_clob || IsSystemObjOid(fcinfo->flinfo->fn_oid)) { return; @@ -6042,6 +6856,9 @@ static void check_huge_clob_paramter(FunctionCallInfoData* fcinfo, bool is_have_ } +/* + 函数 is_external_clob,用于判断给定的数据是否为外部 CLOB 类型。 + */ bool is_external_clob(Oid type_oid, bool is_null, Datum value) { if (type_oid == CLOBOID && !is_null && VARATT_IS_EXTERNAL_LOB(value)) { @@ -6050,6 +6867,9 @@ bool is_external_clob(Oid type_oid, bool is_null, Datum value) return false; } +/* + 函数 is_huge_clob,用于判断给定的数据是否为 "huge clob" 类型。 + */ bool is_huge_clob(Oid type_oid, bool is_null, Datum value) { if (!is_external_clob(type_oid, is_null, value)) { @@ -6058,7 +6878,7 @@ bool is_huge_clob(Oid type_oid, bool is_null, Datum value) struct varatt_lob_pointer* lob_pointer = (varatt_lob_pointer*)(VARDATA_EXTERNAL(value)); bool is_huge_clob = false; - /* get relation by relid */ + /* get relation by relid 通过relid获取关系表 */ ItemPointerData tuple_ctid; tuple_ctid.ip_blkid.bi_hi = lob_pointer->bi_hi; tuple_ctid.ip_blkid.bi_lo = lob_pointer->bi_lo; @@ -6080,9 +6900,12 @@ bool is_huge_clob(Oid type_oid, bool is_null, Datum value) return is_huge_clob; } +/* + fetch_lob_value_from_tuple,用于从一个元组中获取 LOB 的值。 + */ Datum fetch_lob_value_from_tuple(varatt_lob_pointer* lob_pointer, Oid update_oid, bool* is_null) { - /* get relation by relid */ + /* get relation by relid 通过 relid 获取关系(表) */ ItemPointerData tuple_ctid; tuple_ctid.ip_blkid.bi_hi = lob_pointer->bi_hi; tuple_ctid.ip_blkid.bi_lo = lob_pointer->bi_lo; @@ -6131,22 +6954,19 @@ Datum fetch_lob_value_from_tuple(varatt_lob_pointer* lob_pointer, Oid update_oid /* * ExecTargetList - * Evaluates a targetlist with respect to the given - * expression context. Returns TRUE if we were able to create - * a result, FALSE if we have exhausted a set-valued expression. + * 用于在给定的表达式上下文中对目标列表进行评估。 + * 它返回 TRUE,如果我们能够生成一个结果,如果我们已经用完了一个集合值表达式则返回 FALSE。 * - * Results are stored into the passed values and isnull arrays. - * The caller must provide an itemIsDone array that persists across calls. + * 结果被存储到传递的 values 和 isnull 数组中。调用者必须提供一个 itemIsDone 数组,该数组在调用之间保持持久性。 * - * As with ExecEvalExpr, the caller should pass isDone = NULL if not - * prepared to deal with sets of result tuples. Otherwise, a return - * of *isDone = ExprMultipleResult signifies a set element, and a return - * of *isDone = ExprEndResult signifies end of the set of tuple. - * We assume that *isDone has been initialized to ExprSingleResult by caller. - * The execution process of the ExecTargetList function is as follows. -(1) Iterate over the expressions in targetlist. -(2) Calculation of expression results. -(3) Judge the itemIsDone[resind] parameter in the results and generate the final tuple. + * 与 ExecEvalExpr 一样, + * 如果调用者不准备处理结果元组集合,则应将 isDone 设置为 NULL。 + * 否则,*isDone = ExprMultipleResult 表示一个集合元素,*isDone = ExprEndResult 表示结果元组集合的结束。 + * 我们假设调用者已经通过调用者将 *isDone 初始化为 ExprSingleResult。 + * ExecTargetList 函数的执行过程: + * 遍历目标列表中的表达式。\ + * 计算表达式结果。 + * 判断结果中的 itemIsDone[resind] 参数,并生成最终的元组。 */ static bool ExecTargetList(List* targetlist, ExprContext* econtext, Datum* values, bool* isnull, ExprDoneCond* itemIsDone, ExprDoneCond* isDone) @@ -6157,13 +6977,15 @@ static bool ExecTargetList(List* targetlist, ExprContext* econtext, Datum* value /* * Run in short-lived per-tuple context while computing expressions. + * 在计算表达式过程中运行于短期的每个元组上下文中。 */ oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); /* * evaluate all the expressions in the target list + * 对目标列表中的所有表达式进行评估。 */ - haveDoneSets = false; /* any exhausted set exprs in tlist? */ + haveDoneSets = false; /* haveDoneSets 的初始值为 false,用于表示是否在目标列表中存在已经用完的集合表达式。*/ foreach (tl, targetlist) { GenericExprState* gstate = (GenericExprState*)lfirst(tl); @@ -6186,7 +7008,7 @@ static bool ExecTargetList(List* targetlist, ExprContext* econtext, Datum* value isClobAndNotNull = (IsA(tle->expr, Param)) && (!isnull[resind]) && (((Param*)tle->expr)->paramtype == CLOBOID || ((Param*)tle->expr)->paramtype == BLOBOID); if (isClobAndNotNull) { - /* if is big lob, fetch and copy from toast */ + /* if is big lob, fetch and copy from toast 如果是大型数据LOB,则从Toast存储中取出并复制 */ if (VARATT_IS_HUGE_TOAST_POINTER(values[resind])) { Datum new_attr = (Datum)0; Oid update_oid = econtext->ecxt_scantuple != NULL ? @@ -6202,17 +7024,21 @@ static bool ExecTargetList(List* targetlist, ExprContext* econtext, Datum* value ELOG_FIELD_NAME_END; if (itemIsDone[resind] != ExprSingleResult) { - /* We have a set-valued expression in the tlist */ + /* We have a set-valued expression in the tlist + 目标列表中至少有一个表达式生成了集合值,即结果可能是一组值而不是一个单独的值。 + */ if (isDone == NULL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context when calculate targetlist that cannot accept a " "set"))); if (itemIsDone[resind] == ExprMultipleResult) { - /* we have undone sets in the tlist, set flag */ + /* we have undone sets in the tlist, set flag + 将 isDone 设置为 ExprMultipleResult,以便后续的处理可以识别出这是一个集合元素。 + */ *isDone = ExprMultipleResult; } else { - /* we have done sets in the tlist, set flag for that */ + /* we have done sets in the tlist, set flag for that 以及完成,设置为True */ haveDoneSets = true; } } @@ -6221,10 +7047,12 @@ static bool ExecTargetList(List* targetlist, ExprContext* econtext, Datum* value if (haveDoneSets) { /* * note: can't get here unless we verified isDone != NULL + * 只有在isDone不为NULL时,才会执行 */ if (*isDone == ExprSingleResult) { /* * all sets are done, so report that tlist expansion is complete. + * 在所有集合值表达式都已经计算完成后,报告目标列表的展开过程完成。 */ *isDone = ExprEndResult; MemoryContextSwitchTo(oldContext); @@ -6233,6 +7061,7 @@ static bool ExecTargetList(List* targetlist, ExprContext* econtext, Datum* value /* * We have some done and some undone sets. Restart the done ones * so that we can deliver a tuple (if possible). + * 在目标列表中同时存在已完成和未完成集合值表达式时的情况,以及为了生成元组而需要重新启动已完成集合的逻辑。 */ foreach (tl, targetlist) { GenericExprState* gstate = (GenericExprState*)lfirst(tl); @@ -6246,6 +7075,8 @@ static bool ExecTargetList(List* targetlist, ExprContext* econtext, Datum* value /* * Oh dear, this item is returning an empty set. Guess * we can't make a tuple after all. + * 表达式已经计算完成,但是它返回了一个空的集合。 + * 无法生成一个元组。这意味着无法从目标列表中的这些表达式中构建一个完整的结果元组 */ *isDone = ExprEndResult; break; @@ -6257,8 +7088,10 @@ static bool ExecTargetList(List* targetlist, ExprContext* econtext, Datum* value * If we cannot make a tuple because some sets are empty, we still * have to cycle the nonempty sets to completion, else resources * will not be released from subplans etc. - * + * 如果由于某些集合为空而无法生成一个元组,仍然需要将非空集合继续计算完成。 + * 如果不计算这些非空集合值表达式,可能会导致资源没有被释放,例如子查询(subplans)等。这可能会导致资源泄漏或其他问题。 * XXX is that still necessary? + * 是否仍然需要这样做。这表示在某些情况下,可能已经不再需要继续计算非空集合值表达式。这个疑问可能源于代码的历史演进或性能优化的考虑。 */ if (*isDone == ExprEndResult) { foreach (tl, targetlist) { @@ -6277,7 +7110,7 @@ static bool ExecTargetList(List* targetlist, ExprContext* econtext, Datum* value } } - /* Report success */ + /* Report success 报告成功 */ MemoryContextSwitchTo(oldContext); return true; @@ -6288,28 +7121,33 @@ static bool ExecTargetList(List* targetlist, ExprContext* econtext, Datum* value * * projects a tuple based on projection info and stores * it in the previously specified tuple table slot. - * + * ExecProject 函数用于根据投影信息(projection info)生成一个元组,并将其存储在之前指定的元组表(tuple table)槽(slot)中。 + * * Note: the result is always a virtual tuple; therefore it * may reference the contents of the exprContext's scan tuples * and/or temporary results constructed in the exprContext. * If the caller wishes the result to be valid longer than that * data will be valid, he must call ExecMaterializeSlot on the * result slot. + * ExecProject 函数生成的结果始终是一个虚拟元组(virtual tuple)。 + * 这表示生成的元组不是实际的物理元组,而是一种虚拟的、临时的表示。虚拟元组可能引用表达式上下文(exprContext)中的扫描元组数据和/或在表达式上下文中构建的临时结果。 */ TupleTableSlot* ExecProject(ProjectionInfo* projInfo, ExprDoneCond* isDone) { /* - * sanity checks + * sanity checks 合理性检查 */ Assert(projInfo != NULL); /* - * get the projection info we want + * get the projection info we want 获取所需的投影信息 */ TupleTableSlot *slot = projInfo->pi_slot; ExprContext *econtext = projInfo->pi_exprContext; - /* Assume single result row until proven otherwise */ + /* Assume single result row until proven otherwise + 在没有进一步验证之前,假设结果只包含单行数据 + */ if (isDone != NULL) *isDone = ExprSingleResult; @@ -6317,6 +7155,8 @@ TupleTableSlot* ExecProject(ProjectionInfo* projInfo, ExprDoneCond* isDone) * Clear any former contents of the result slot. This makes it safe for * us to use the slot's Datum/isnull arrays as workspace. (Also, we can * return the slot as-is if we decide no rows can be projected.) + * 清除结果槽的任何先前内容 + * 确保可以安全地将该槽用作工作空间。 */ (void)ExecClearTuple(slot); @@ -6324,6 +7164,8 @@ TupleTableSlot* ExecProject(ProjectionInfo* projInfo, ExprDoneCond* isDone) * Force extraction of all input values that we'll need. The * Var-extraction loops below depend on this, and we are also prefetching * all attributes that will be referenced in the generic expressions. + * 强制提取所有输入值,这些值将在后续的操作中使用。 + * 用于确保在后续的代码中可以访问到所有必要的输入数据,以及为了性能考虑,提前获取了所有可能被用到的属性数据。 */ if (projInfo->pi_lastInnerVar > 0) { tableam_tslot_getsomeattrs(econtext->ecxt_innertuple, projInfo->pi_lastInnerVar); @@ -6340,6 +7182,8 @@ TupleTableSlot* ExecProject(ProjectionInfo* projInfo, ExprDoneCond* isDone) /* * Assign simple Vars to result by direct extraction of fields from source * slots ... a mite ugly, but fast ... + * 通过直接从源槽(source slots)中提取字段来将简单的变量(Vars)赋值给结果。 + * 方法可能看起来不太美观,但它具有高效的特点。 */ int numSimpleVars = projInfo->pi_numSimpleVars; if (numSimpleVars > 0) { @@ -6350,7 +7194,7 @@ TupleTableSlot* ExecProject(ProjectionInfo* projInfo, ExprDoneCond* isDone) int i; if (projInfo->pi_directMap) { - /* especially simple case where vars go to output in order */ + /* 简单的变量(Vars)按顺序映射到输出元组中 */ for (i = 0; i < numSimpleVars; i++) { char* slotptr = ((char*)econtext) + varSlotOffsets[i]; TupleTableSlot* varSlot = *((TupleTableSlot**)slotptr); @@ -6362,7 +7206,7 @@ TupleTableSlot* ExecProject(ProjectionInfo* projInfo, ExprDoneCond* isDone) isnull[i] = varSlot->tts_isnull[varNumber]; } } else { - /* we have to pay attention to varOutputCols[] */ + /* 必须要注意varOutputCols[] 变量(Vars)与输出元组的字段之间的映射关系。 */ int* varOutputCols = projInfo->pi_varOutputCols; for (i = 0; i < numSimpleVars; i++) { @@ -6384,20 +7228,25 @@ TupleTableSlot* ExecProject(ProjectionInfo* projInfo, ExprDoneCond* isDone) * that there are set-returning functions in such expressions; if so and * we have reached the end of the set, we return the result slot, which we * already marked empty. + * 评估通用表达式,并在可能包含返回集合的函数的情况下处理集合的末尾情况。 */ if (projInfo->pi_targetlist) { if (!ExecTargetList( projInfo->pi_targetlist, econtext, slot->tts_values, slot->tts_isnull, projInfo->pi_itemIsDone, isDone)) - return slot; /* no more result rows, return empty slot */ + return slot; /* no more result rows, return empty slot 没有更多的结果列,返回空槽 */ } /* * Successfully formed a result row. Mark the result slot as containing a * valid virtual tuple. + * 成功生成了一个结果行,并将结果槽标记为包含有效的虚拟元组。 */ return ExecStoreVirtualTuple(slot); } +/* + 计算分组标识的表达式值 + */ static Datum ExecEvalGroupingIdExpr( GroupingIdExprState* gstate, ExprContext* econtext, bool* isNull, ExprDoneCond* isDone) { @@ -6419,16 +7268,20 @@ static Datum ExecEvalGroupingIdExpr( /* * @Description: copy cursor data from estate->datums to target_cursor - * @in datums - estate->datums - * @in dno - varno in datums - * @in target_cursor - target cursor data * @return -void + * 参数说明: + * datums:输入参数,表示游标数据所在的数据结构,通常是 estate->datums。 + * dno:输入参数,表示在 datums 中的变量号(varno)。 + * target_cursor:输入参数,表示目标游标数据的数据结构。 + * 函数的目的是将游标数据从 estate->datums 复制到 target_cursor 中。 */ void ExecCopyDataFromDatum(PLpgSQL_datum** datums, int dno, Cursor_Data* target_cursor) { PLpgSQL_var *cursor_var = (PLpgSQL_var *)(datums[dno]); - /* only copy cursor option to refcursor */ + /* only copy cursor option to refcursor + 仅将游标选项(cursor option)复制到 refcursor + */ if (cursor_var->datatype->typoid != REFCURSOROID) { return; } @@ -6448,16 +7301,20 @@ void ExecCopyDataFromDatum(PLpgSQL_datum** datums, int dno, Cursor_Data* target_ /* * @Description: copy cursor data to estate->datums - * @in datums - estate->datums - * @in dno - varno in datums - * @in target_cursor - source cursor data * @return -void + * 将游标数据复制到 estate->datums 中的指定位置。 + * 参数说明: + * datums:输入参数,表示 estate->datums 数组,其中包含了PL/pgSQL程序的数据对象。 + * dno:输入参数,表示要存储游标数据的 datums 数组中的位置(varno)。 + * target_cursor:输入参数,表示源游标数据,其中包含了游标的各种属性和选项 */ void ExecCopyDataToDatum(PLpgSQL_datum** datums, int dno, Cursor_Data* source_cursor) { PLpgSQL_var *cursor_var = (PLpgSQL_var *)(datums[dno]); - /* only copy cursor option to refcursor */ + /* only copy cursor option to refcursor + 仅将游标选项(cursor option)复制到 refcursor + */ if (cursor_var->datatype->typoid != REFCURSOROID) { return; }