From dd1fb6010f05e9b2dd35407f98d7288789b80d6f Mon Sep 17 00:00:00 2001 From: LYLlyl Date: Tue, 8 Aug 2023 18:44:15 +0800 Subject: [PATCH 01/31] Update execJunk.cpp --- src/gausskernel/runtime/executor/execJunk.cpp | 295 +++++++----------- 1 file changed, 121 insertions(+), 174 deletions(-) diff --git a/src/gausskernel/runtime/executor/execJunk.cpp b/src/gausskernel/runtime/executor/execJunk.cpp index 96adc29e0..d9725c5ec 100644 --- a/src/gausskernel/runtime/executor/execJunk.cpp +++ b/src/gausskernel/runtime/executor/execJunk.cpp @@ -1,14 +1,12 @@ /* ------------------------------------------------------------------------- + * execJunk.cpp + * 垃圾属性支持相关内容... * - * execJunk.cpp - * Junk attribute support stuff.... + * 版权部分 (c) 2020 华为技术有限公司 + * 版权部分 (c) 1996-2012,PostgreSQL全球开发团队 + * 版权部分 (c) 1994,加利福尼亚大学董事会 * - * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * - * IDENTIFICATION + * 标识 * src/gausskernel/runtime/executor/execJunk.cpp * * ------------------------------------------------------------------------- @@ -21,84 +19,54 @@ #include "pgxc/pgxc.h" /* ------------------------------------------------------------------------- - * XXX this stuff should be rewritten to take advantage - * of ExecProject() and the ProjectionInfo node. - * -cim 6/3/91 - * - * An attribute of a tuple living inside the executor, can be - * either a normal attribute or a "junk" attribute. "junk" attributes - * never make it out of the executor, i.e. they are never printed, - * returned or stored on disk. Their only purpose in life is to - * store some information useful only to the executor, mainly the values - * of system attributes like "ctid", or sort key columns that are not to - * be output. - * - * The general idea is the following: A target list consists of a list of - * TargetEntry nodes containing expressions. Each TargetEntry has a field - * called 'resjunk'. If the value of this field is true then the - * corresponding attribute is a "junk" attribute. - * - * When we initialize a plan we call ExecInitJunkFilter to create a filter. - * - * We then execute the plan, treating the resjunk attributes like any others. - * - * Finally, when at the top level we get back a tuple, we can call - * ExecFindJunkAttribute/ExecGetJunkAttribute to retrieve the values of the - * junk attributes we are interested in, and ExecFilterJunk to remove all the - * junk attributes from a tuple. This new "clean" tuple is then printed, - * inserted, or updated. + * XXX 这部分应该被重新编写以利用 ExecProject() 和 ProjectionInfo 节点。 + * -cim 6/3/91 + + * 在执行器内部的元组的属性可以是普通属性,也可以是 "垃圾" 属性。"垃圾" 属性永远不会离开执行器,即它们永远不会被打印、返回或存储在磁盘上。它们的唯一目的是存储一些仅对执行器有用的信息, + * 主要是系统属性如 "ctid" 的值,或者不会被输出的排序键列。 + * 总体思想如下:目标列表由包含表达式的 TargetEntry 节点列表组成。 + * 每个 TargetEntry 都有一个名为 'resjunk' 的字段。如果该字段的值为 true,则相应的属性是 "垃圾" 属性。 + * 当我们初始化一个计划时,我们调用 ExecInitJunkFilter 来创建一个过滤器。 + * 然后,我们执行计划,将 resjunk 属性视为其他属性一样处理。 + * 最后,当我们在顶层得到一个元组时,我们可以调用 ExecFindJunkAttribute/ExecGetJunkAttribute来检索我们感兴趣的垃圾属性的值,以及调用 ExecFilterJunk 来从元组中删除所有垃圾属性。 + * 最终,这个新的 "干净" 元组被打印、插入或更新。 * * ------------------------------------------------------------------------- */ /* * ExecInitJunkFilter - * - * Initialize the Junk filter. - * - * The source targetlist is passed in. The output tuple descriptor is - * built from the non-junk tlist entries, plus the passed specification - * of whether to include room for an OID or not. - * An optional resultSlot can be passed as well. + + * 初始化垃圾过滤器。根据目标列表、是否包含 OID、结果插槽的元组类型,在垃圾过滤器中创建并初始化相关数据结构。 + * 源目标列表被传入,输出元组描述符是从非垃圾 tlist 条目构建的, + * 再加上传入的是否包括 OID 的规范。 + * 也可以传入一个可选的 resultSlot。 */ JunkFilter* ExecInitJunkFilter(List* targetList, bool hasoid, TupleTableSlot* slot, TableAmType tam) { - JunkFilter* junkfilter = NULL; - TupleDesc cleanTupType; - int cleanLength; - AttrNumber* cleanMap = NULL; - ListCell* t = NULL; - AttrNumber cleanResno; + JunkFilter* junkfilter = NULL; + TupleDesc cleanTupType; + int cleanLength; + AttrNumber* cleanMap = NULL; + ListCell* t = NULL; + AttrNumber cleanResno; - /* - * Compute the tuple descriptor for the cleaned tuple. - */ - cleanTupType = ExecCleanTypeFromTL(targetList, hasoid, tam); + // 计算清理后的元组描述符 + cleanTupType = ExecCleanTypeFromTL(targetList, hasoid, tam); - /* - * Use the given slot, or make a new slot if we weren't given one. - */ + // 设置槽的描述符,如果给定了槽,则使用给定的槽,否则创建一个新槽 if (slot != NULL) ExecSetSlotDescriptor(slot, cleanTupType); else slot = MakeSingleTupleTableSlot(cleanTupType); - /* - * Now calculate the mapping between the original tuple's attributes and - * the "clean" tuple's attributes. - * - * The "map" is an array of "cleanLength" attribute numbers, i.e. one - * entry for every attribute of the "clean" tuple. The value of this entry - * is the attribute number of the corresponding attribute of the - * "original" tuple. (Zero indicates a NULL output attribute, but we do - * not use that feature in this routine.) - */ - cleanLength = cleanTupType->natts; + cleanLength = cleanTupType->natts; if (cleanLength > 0) { - cleanMap = (AttrNumber*)palloc(cleanLength * sizeof(AttrNumber)); + cleanMap = (AttrNumber*)palloc(cleanLength * sizeof(AttrNumber)); cleanResno = 1; foreach (t, targetList) { TargetEntry* tle = (TargetEntry*)lfirst(t); - + + // 如果不是 "junk" 属性,则建立属性映射关系 if (!tle->resjunk) { cleanMap[cleanResno - 1] = tle->resno; cleanResno++; @@ -108,11 +76,10 @@ JunkFilter* ExecInitJunkFilter(List* targetList, bool hasoid, TupleTableSlot* sl cleanMap = NULL; } - /* - * Finally create and initialize the JunkFilter struct. - */ + // 创建并初始化 JunkFilter 结构 junkfilter = makeNode(JunkFilter); + // 填充 JunkFilter 结构的字段 junkfilter->jf_targetList = targetList; junkfilter->jf_cleanTupType = cleanTupType; junkfilter->jf_cleanMap = cleanMap; @@ -124,78 +91,64 @@ JunkFilter* ExecInitJunkFilter(List* targetList, bool hasoid, TupleTableSlot* sl /* * ExecInitJunkFilterConversion * - * Initialize a JunkFilter for rowtype conversions. - * - * Here, we are given the target "clean" tuple descriptor rather than - * inferring it from the targetlist. The target descriptor can contain - * deleted columns. It is assumed that the caller has checked that the - * non-deleted columns match up with the non-junk columns of the targetlist. + * 为行类型转换初始化垃圾过滤器。根据目标列表、干净元组类型、结果插槽的元组类型,在垃圾过滤器中创建并初始化相关数据结构。 + * 在这里,我们提供了目标“干净”元组描述符,而不是从目标列表中推断出来的。 + * 目标描述符可以包含已删除的列。假设调用者已经检查过非删除的列与目标列表的非垃圾列相匹配。 */ JunkFilter* ExecInitJunkFilterConversion(List* targetList, TupleDesc cleanTupType, TupleTableSlot* slot) { - JunkFilter* junkfilter = NULL; - int cleanLength; - AttrNumber* cleanMap = NULL; - ListCell* t = NULL; - int i; + JunkFilter* junkfilter = NULL; + int cleanLength; + AttrNumber* cleanMap = NULL; + ListCell* t = NULL; + int i; - /* - * Use the given slot, or make a new slot if we weren't given one. - */ + // 检查是否给定了槽,如果给定则使用,否则创建一个新的槽 if (slot != NULL) - ExecSetSlotDescriptor(slot, cleanTupType); + ExecSetSlotDescriptor(slot, cleanTupType); else - slot = MakeSingleTupleTableSlot(cleanTupType); + slot = MakeSingleTupleTableSlot(cleanTupType); - /* - * Calculate the mapping between the original tuple's attributes and the - * "clean" tuple's attributes. - * - * The "map" is an array of "cleanLength" attribute numbers, i.e. one - * entry for every attribute of the "clean" tuple. The value of this entry - * is the attribute number of the corresponding attribute of the - * "original" tuple. We store zero for any deleted attributes, marking - * that a NULL is needed in the output tuple. - */ - cleanLength = cleanTupType->natts; + cleanLength = cleanTupType->natts; + + // 为属性映射数组分配内存,并初始化为 0 if (cleanLength > 0) { - cleanMap = (AttrNumber*)palloc0(cleanLength * sizeof(AttrNumber)); - t = list_head(targetList); - for (i = 0; i < cleanLength; i++) { + cleanMap = (AttrNumber*)palloc0(cleanLength * sizeof(AttrNumber)); + t = list_head(targetList); + for (i = 0; i < cleanLength; i++) { if (cleanTupType->attrs[i]->attisdropped) - continue; /* map entry is already zero */ + continue; // 跳过已删除的属性 for (;;) { - TargetEntry* tle = (TargetEntry*)lfirst(t); + TargetEntry* tle = (TargetEntry*)lfirst(t); - t = lnext(t); + t = lnext(t); if (!tle->resjunk) { - cleanMap[i] = tle->resno; + cleanMap[i] = tle->resno; break; } } } } else { - cleanMap = NULL; + cleanMap = NULL; } - /* - * Finally create and initialize the JunkFilter struct. - */ - junkfilter = makeNode(JunkFilter); + // 创建并初始化 JunkFilter 结构 + junkfilter = makeNode(JunkFilter); - junkfilter->jf_targetList = targetList; - junkfilter->jf_cleanTupType = cleanTupType; - junkfilter->jf_cleanMap = cleanMap; - junkfilter->jf_resultSlot = slot; + // 填充 JunkFilter 结构的各个字段 + junkfilter->jf_targetList = targetList; + junkfilter->jf_cleanTupType = cleanTupType; + junkfilter->jf_cleanMap = cleanMap; + junkfilter->jf_resultSlot = slot; - return junkfilter; + return junkfilter; } /* * ExecFindJunkAttribute * - * Locate the specified junk attribute in the junk filter's targetlist, - * and return its resno. Returns InvalidAttrNumber if not found. + * 在垃圾过滤器的目标列表中定位指定的垃圾属性,并返回其 resno。 + * 如果未找到,则返回 InvalidAttrNumber。 */ AttrNumber ExecFindJunkAttribute(JunkFilter* junkfilter, const char* attrName) { @@ -205,8 +158,7 @@ AttrNumber ExecFindJunkAttribute(JunkFilter* junkfilter, const char* attrName) /* * ExecFindJunkPrimaryKeys * - * Locate the specified junk attribute in the junk filter's targetlist. - * Returns NIL if not found. + * 在目标列表中查找 xc_primary_key 垃圾属性,返回包含这些属性表达式的列表 */ List* ExecFindJunkPrimaryKeys(List* targetlist) { @@ -228,8 +180,7 @@ List* ExecFindJunkPrimaryKeys(List* targetlist) /* * ExecFindJunkAttributeInTlist * - * Find a junk attribute given a subplan's targetlist (not necessarily - * part of a JunkFilter). + * 在目标列表中查找指定名称的垃圾属性,返回属性的编号 */ AttrNumber ExecFindJunkAttributeInTlist(List* targetlist, const char* attrName) { @@ -250,9 +201,7 @@ AttrNumber ExecFindJunkAttributeInTlist(List* targetlist, const char* attrName) /* * ExecGetJunkAttribute * - * Given a junk filter's input tuple (slot) and a junk attribute's number - * previously found by ExecFindJunkAttribute, extract & return the value and - * isNull flag of the attribute. + * 从元组插槽中获取指定编号的垃圾属性的值 */ Datum ExecGetJunkAttribute(TupleTableSlot* slot, AttrNumber attno, bool* isNull) { @@ -265,7 +214,7 @@ Datum ExecGetJunkAttribute(TupleTableSlot* slot, AttrNumber attno, bool* isNull) /* * ExecFilterJunk * - * Construct and return a slot with all the junk attributes removed. + * 根据垃圾属性映射,构建新的元组插槽,移除垃圾属性,并返回新的插槽。 */ TupleTableSlot* ExecFilterJunk(JunkFilter* junkfilter, TupleTableSlot* slot) { @@ -279,34 +228,24 @@ TupleTableSlot* ExecFilterJunk(JunkFilter* junkfilter, TupleTableSlot* slot) Datum* old_values = NULL; bool* old_isnull = NULL; - /* - * Extract all the values of the old tuple. - */ - - /* Get the Table Accessor Method*/ + // 从原始元组中提取所有属性值 Assert(slot != NULL && slot->tts_tupleDescriptor != NULL); tableam_tslot_getallattrs(slot); old_values = slot->tts_values; old_isnull = slot->tts_isnull; - /* - * get info from the junk filter - */ + // 获取 JunkFilter 中的信息 cleanTupType = junkfilter->jf_cleanTupType; cleanLength = cleanTupType->natts; cleanMap = junkfilter->jf_cleanMap; resultSlot = junkfilter->jf_resultSlot; - /* - * Prepare to build a virtual result tuple. - */ + // 准备构建虚拟结果元组 (void)ExecClearTuple(resultSlot); values = resultSlot->tts_values; isnull = resultSlot->tts_isnull; - /* - * Transpose data into proper fields of the new tuple. - */ + // 转置数据到新元组的适当字段中 for (i = 0; i < cleanLength; i++) { int j = cleanMap[i]; @@ -319,95 +258,102 @@ TupleTableSlot* ExecFilterJunk(JunkFilter* junkfilter, TupleTableSlot* slot) } } - /* - * And return the virtual tuple. - */ + // 返回过滤后的虚拟元组 return ExecStoreVirtualTuple(resultSlot); } + /* * BatchExecFilterJunk * - * Construct and return a vector batch with all the junk attributes removed. + * 构建并返回一个向量批处理,其中移除了所有垃圾属性。 */ VectorBatch* BatchExecFilterJunk(_in_ JunkFilter* junkfilter, __inout VectorBatch* batch) { - AttrNumber* cleanMap = NULL; - TupleDesc cleanTupType; - int cleanLength; - int i; - ScalarVector* columns = NULL; + AttrNumber* cleanMap = NULL; // 属性映射数组,将清理后的属性编号映射到原始属性编号 + TupleDesc cleanTupType; // 清理后元组的描述符 + int cleanLength; // 清理后元组的属性数量 + int i; // 循环计数变量 + ScalarVector* columns = NULL; // 存储列向量的数组 - // Get info from the junk filter - // + // 获取 JunkFilter 中的信息 cleanTupType = junkfilter->jf_cleanTupType; cleanLength = cleanTupType->natts; cleanMap = junkfilter->jf_cleanMap; - columns = batch->m_arr; + columns = batch->m_arr; // 获取列向量的数组 - // Transpose data into proper fields of the new tuple. - // + // 转置数据到新元组的适当字段中 for (i = 0; i < cleanLength; i++) { int j = cleanMap[i]; if (j == 0) { for (int k = 0; k < columns[i].m_rows; k++) { - columns[i].SetNull(k); + columns[i].SetNull(k); // 将该列向量的元素设置为 NULL } } else { - columns[i] = columns[j - 1]; + columns[i] = columns[j - 1]; // 将原始属性的列向量复制到新属性列向量 } } - // Return the modified batch without changing the column count - // as the column count is early decided at compile time. - // + // 返回修改后的批处理数据,列数不变 return batch; } +/* +*ExecSetjunkFilteDescriptor +* +*设置垃圾过滤器结果插槽的元组描述符,以匹配新的元组描述符。 +*将给定的 TupleDesc(元组描述符)中的属性信息转置到 JunkFilter 中的结果槽的元组描述符中。在循环中,根据属性映射关系,将属性类型赋值给结果槽的元组描述符,以便在后续操作中使用。 +*/ void ExecSetjunkFilteDescriptor(JunkFilter* junkfilter, TupleDesc tupdesc) { - TupleDesc resultslotTupType; - AttrNumber* cleanMap = NULL; - int cleanLength; - int i; + TupleDesc resultslotTupType; // 结果槽的元组描述符 + AttrNumber* cleanMap = NULL; // 属性映射数组,将清理后的属性编号映射到原始属性编号 + int cleanLength; // 清理后元组的属性数量 + int i; // 循环计数变量 - cleanLength = junkfilter->jf_cleanTupType->natts; - cleanMap = junkfilter->jf_cleanMap; + cleanLength = junkfilter->jf_cleanTupType->natts; // 获取清理后元组的属性数量 + cleanMap = junkfilter->jf_cleanMap; // 获取属性映射数组 - resultslotTupType = junkfilter->jf_resultSlot->tts_tupleDescriptor; + resultslotTupType = junkfilter->jf_resultSlot->tts_tupleDescriptor; // 获取结果槽的元组描述符 /* - * Transpose tupdesc into proper fields of the new tupdesc. + * 转置 tupdesc 的属性信息到新元组描述符的适当字段中。 */ for (i = 0; i < cleanLength; i++) { int j = cleanMap[i]; + + // 如果属性映射不为 0,则将 tupdesc 的属性类型赋值给结果槽的元组描述符 if (j > 0) resultslotTupType->attrs[i]->atttypid = tupdesc->attrs[j - 1]->atttypid; } } -/* - * @Description: Check if junk attribute xc_node_id is the same as current node identifier - * - * @param[IN] junkfilter: junk attributes - * @param[IN] batch: vector batch - * @return: void + +/*BatchCheckNodeIdentifier +* + * 检查向量批处理中的 `xc_node_id` 垃圾属性,确保其值与当前节点标识符相匹配,用于更新或删除操作的节点标识验证。 */ void BatchCheckNodeIdentifier(JunkFilter* junkfilter, VectorBatch* batch) { - ScalarVector* xc_node_id_col = NULL; - uint32 xc_node_id = 0; - int counter = 0; + ScalarVector* xc_node_id_col = NULL; // 用于存储 xc_node_id 的列向量 + uint32 xc_node_id = 0; // 存储当前 xc_node_id + int counter = 0; // 循环计数变量 + // 如果 xc_node_id 无效,则直接返回 if (InvalidAttrNumber == junkfilter->jf_xc_node_id) { return; } + // 获取 xc_node_id 列向量 xc_node_id_col = &(batch->m_arr[junkfilter->jf_xc_node_id - 1]); + // 遍历 xc_node_id 列向量中的值 for (counter = 0; counter < xc_node_id_col->m_rows; counter++) { + // 获取当前 xc_node_id 的值 xc_node_id = DatumGetUInt32(xc_node_id_col->m_vals[counter]); + + // 检查当前 xc_node_id 是否与当前节点的标识不匹配,如果不匹配则抛出错误 if (u_sess->pgxc_cxt.PGXCNodeIdentifier != xc_node_id) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), @@ -418,3 +364,4 @@ void BatchCheckNodeIdentifier(JunkFilter* junkfilter, VectorBatch* batch) } } } + -- 2.34.1 From 1d58f78f6b20efe97b5b34985bca909cfdc84bc5 Mon Sep 17 00:00:00 2001 From: LYLlyl Date: Wed, 9 Aug 2023 19:19:33 +0800 Subject: [PATCH 02/31] Update execMerge.cpp --- .../runtime/executor/execMerge.cpp | 262 +++++++----------- 1 file changed, 107 insertions(+), 155 deletions(-) diff --git a/src/gausskernel/runtime/executor/execMerge.cpp b/src/gausskernel/runtime/executor/execMerge.cpp index d8a22d430..a1ddca213 100644 --- a/src/gausskernel/runtime/executor/execMerge.cpp +++ b/src/gausskernel/runtime/executor/execMerge.cpp @@ -1,22 +1,21 @@ /* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * 版权所有 (c) 2020 华为技术有限公司 * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: + * openGauss 在 Mulan PSL v2 许可下发布。 + * 您可以根据 Mulan PSL v2 的条款和条件使用本软件。 + * 您可以在以下网址获取 Mulan PSL v2 的副本: * * http://license.coscl.org.cn/MulanPSL2 * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. + * 本软件按"原样"提供,不提供任何明示或暗示的保证, + * 包括但不限于不侵权、适销性或特定用途适用性的保证。 + * 有关更多详细信息,请参阅 Mulan PSL v2。 * ------------------------------------------------------------------------- * * execMerge.cpp - * routines to handle Merge nodes relating to the MERGE command + * 处理与 MERGE 命令相关的 Merge 节点的函数 * - * IDENTIFICATION + * 标识符 * src/gausskernel/runtime/executor/execMerge.cpp * * ------------------------------------------------------------------------- @@ -39,7 +38,7 @@ static void ExecMergeNotMatched(ModifyTableState* mtstate, EState* estate, Tuple static bool ExecMergeMatched(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot, JunkFilter* junkfilter, ItemPointer tupleid, HeapTupleHeader oldtuple, Oid oldPartitionOid, int2 bucketid); /* - * Perform MERGE. + * 执行 MERGE 操作 */ void ExecMerge(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot, JunkFilter* junkfilter, ResultRelInfo* resultRelInfo) @@ -61,20 +60,14 @@ void ExecMerge(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot, junkfilter != NULL); /* - * Reset per-tuple memory context to free any expression evaluation - * storage allocated in the previous cycle. + * 重置每个元组内存上下文,以释放在前一个周期中分配的任何表达式评估存储空间。 */ ResetExprContext(econtext); /* - * We run a JOIN between the target relation and the source relation to - * find a set of candidate source rows that has matching row in the target - * table and a set of candidate source rows that does not have matching - * row in the target table. If the join returns us a tuple with target - * relation's tid set, that implies that the join found a matching row for - * the given source tuple. This case triggers the WHEN MATCHED clause of - * the MERGE. Whereas a NULL in the target relation's ctid column - * indicates a NOT MATCHED case. + * 我们在目标关系和源关系之间执行联接,以找到一组具有目标表中匹配行的候选源行,以及一组在目标表中没有匹配行的候选源行。 + * 如果联接返回一个带有目标关系的 tid 集的元组,那么意味着联接为给定的源元组找到了匹配行。这种情况触发了 MERGE 的 WHEN MATCHED 子句。 + * 而在目标关系的 ctid 列中的 NULL 则表示一个 NOT MATCHED 情况。 */ datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo, &isNull); @@ -120,57 +113,42 @@ void ExecMerge(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot, } /* - * If we are dealing with a WHEN MATCHED case, we execute the first action - * for which the additional WHEN MATCHED AND quals pass. If an action - * without quals is found, that action is executed. - * - * Similarly, if we are dealing with WHEN NOT MATCHED case, we look at the - * given WHEN NOT MATCHED actions in sequence until one passes. - * - * Things get interesting in case of concurrent update/delete of the - * target tuple. Such concurrent update/delete is detected while we are - * executing a WHEN MATCHED action. - * - * A concurrent update can: - * - * 1. modify the target tuple so that it no longer satisfies the - * additional quals attached to the current WHEN MATCHED action OR - * - * In this case, we are still dealing with a WHEN MATCHED case, but - * we should recheck the list of WHEN MATCHED actions and choose the first - * one that satisfies the new target tuple. - * - * 2. modify the target tuple so that the join quals no longer pass and - * hence the source tuple no longer has a match. - * - * In the second case, the source tuple no longer matches the target tuple, - * so we now instead find a qualifying WHEN NOT MATCHED action to execute. - * - * A concurrent delete, changes a WHEN MATCHED case to WHEN NOT MATCHED. - * - * ExecMergeMatched takes care of following the update chain and - * re-finding the qualifying WHEN MATCHED action, as long as the updated - * target tuple still satisfies the join quals i.e. it still remains a - * WHEN MATCHED case. If the tuple gets deleted or the join quals fail, it - * returns and we try ExecMergeNotMatched. Given that ExecMergeMatched - * always make progress by following the update chain and we never switch - * from ExecMergeNotMatched to ExecMergeMatched, there is no risk of a - * livelock. + * 如果我们处理的是 WHEN MATCHED 情况,我们执行第一个附加的 WHEN MATCHED AND 条件满足的动作。 +* 如果找到一个没有附加条件的动作,则执行该动作。 +* +* 类似地,如果我们处理的是 WHEN NOT MATCHED 情况,我们按顺序查看给定的 WHEN NOT MATCHED 动作,直到找到一个满足条件的动作为止。 +* +* 在处理 WHEN MATCHED 情况时,同时进行目标元组的并发更新/删除会变得有趣。 +* +* 并发更新可能会有以下情况: +* +* 1. 修改目标元组,使其不再满足当前 WHEN MATCHED 动作附加的附加条件。 +* +* 在这种情况下,我们仍然处理 WHEN MATCHED 情况,但应重新检查 WHEN MATCHED 动作列表,并选择满足新目标元组的第一个动作。 +* +* 2. 修改目标元组,使联接条件不再满足,因此源元组不再匹配。 +* +* 在第二种情况下,源元组不再与目标元组匹配,因此我们现在会找到一个满足条件的 WHEN NOT MATCHED 动作来执行。 +* +* 并发删除将 WHEN MATCHED 情况更改为 WHEN NOT MATCHED。 +* +* ExecMergeMatched 负责遵循更新链并重新查找满足条件的 WHEN MATCHED 动作,只要更新的目标元组仍然满足联接条件,即仍然是 WHEN MATCHED 情况。 +* 如果元组被删除或联接条件失败,则返回并尝试 ExecMergeNotMatched。鉴于 ExecMergeMatched 总是通过跟踪更新链来取得进展, +* 我们永远不会从 ExecMergeNotMatched 切换到 ExecMergeMatched,因此不会出现死锁的风险。 */ if (matched) matched = ExecMergeMatched(mtstate, estate, slot, junkfilter, tupleid, oldtuple, oldPartitionOid, bucketid); - /* - * Either we were dealing with a NOT MATCHED tuple or ExecMergeNotMatched() - * returned "false", indicating the previously MATCHED tuple is no longer a - * matching tuple. + /* + * 要么我们处理的是一个 NOT MATCHED 的元组,要么 ExecMergeNotMatched() 返回了 "false", + * 表示先前的 MATCHED 元组不再是一个匹配的元组。 */ if (!matched) ExecMergeNotMatched(mtstate, estate, slot); } /* - * Extract tuple for checking constraints from plan slot + * 从计划槽中提取元组以进行约束检查 */ static TupleTableSlot* ExtractConstraintTuple( ModifyTableState* mtstate, CmdType commandType, TupleTableSlot* slot, TupleDesc tupDesc) @@ -218,7 +196,7 @@ static TupleTableSlot* ExtractConstraintTuple( } /* - * Extract scan tuple for target table from plan slot + * 从计划槽中提取目标表的扫描元组 */ TupleTableSlot* ExtractScanTuple(ModifyTableState* mtstate, TupleTableSlot* slot, TupleDesc tupDesc) { @@ -233,11 +211,10 @@ TupleTableSlot* ExtractScanTuple(ModifyTableState* mtstate, TupleTableSlot* slot int startIdx = 0; int index = 0; - /* - * Find the right start index for target table. We should skip the sourceTargetList. - * First count the number of source targetlist. We add new columns to sourceTargetList - * but the resno is not continuous, so find the max continuous number to be the original - * length of sourceTargetList. + /* + * 找到目标表的正确起始索引。我们应该跳过 sourceTargetList。 + * 首先计算 sourceTargetList 中源列的数量。虽然我们向 sourceTargetList 添加了新列, + * 但 resno 不是连续的,因此找到最大的连续编号作为 sourceTargetList 的原始长度。 */ foreach (lc, sourceTargetList) { TargetEntry* tle = (TargetEntry*)lfirst(lc); @@ -264,15 +241,15 @@ TupleTableSlot* ExtractScanTuple(ModifyTableState* mtstate, TupleTableSlot* slot } /* - * Description: projects and evaluates qual condition for update action. - * Parameters: - * @in mtstate: modifytable state. - * @in mergeMatchedActionStates: update action states. - * @in econtext: expression context. - * @in originSlot: slot to be projected. - * @in result_slot: slot to be returned. - * @in estate: working state for executor. - * Return: slot has been projected.. + * 描述:对更新操作进行投影和评估条件。 + * 参数: + * @in mtstate:modifytable 状态。 + * @in mergeMatchedActionStates:更新操作状态。 + * @in econtext:表达式上下文。 + * @in originSlot:待投影的槽。 + * @in result_slot:将要返回的槽。 + * @in estate:执行器的工作状态。 + * 返回:已投影的槽。 */ TupleTableSlot* ExecMergeProjQual(ModifyTableState* mtstate, List* mergeMatchedActionStates, ExprContext* econtext, TupleTableSlot* originSlot, TupleTableSlot* result_slot, EState* estate) @@ -284,17 +261,16 @@ TupleTableSlot* ExecMergeProjQual(ModifyTableState* mtstate, List* mergeMatchedA Assert(CMD_UPDATE == action->commandType); - /* - * get information on the (current) result relation + /* + * 获取关于(当前)结果关系的信息 */ resultRelInfo = estate->es_result_relation_info; resultRelationDesc = resultRelInfo->ri_RelationDesc; - /* - * Make tuple and any needed join variables available to ExecQual and - * ExecProject. The target's existing tuple is installed in the scantuple. - * Again, this target relation's slot is required only in the case of a - * MATCHED tuple and UPDATE/DELETE actions. + /* + * 使元组和任何必要的连接变量对 ExecQual 和 ExecProject 可用。 + * 目标的现有元组被安装在 scantuple 中。 + * 同样,在匹配的元组和 UPDATE/DELETE 操作的情况下,仅需要此目标关系的槽。 */ if (estate->es_result_update_remoterel == NULL) { econtext->ecxt_scantuple = ExtractScanTuple(mtstate, originSlot, action->tupDesc); @@ -306,34 +282,29 @@ TupleTableSlot* ExecMergeProjQual(ModifyTableState* mtstate, List* mergeMatchedA econtext->ecxt_outertuple = NULL; } - /* - * Test condition, if any + /* + * 测试条件,如果有的话 * - * In the absence of a condition we perform the action unconditionally - * (no need to check separately since ExecQual() will return true if - * there are no conditions to evaluate). + * 在没有条件的情况下,我们无条件执行动作 + * (无需单独检查,因为如果没有条件要评估,ExecQual() 将返回 true)。 */ if (ExecQual((List*)action->whenqual, econtext, false)) { if (estate->es_result_update_remoterel == NULL) { - /* - * We set up the projection earlier, so all we do here is - * Project, no need for any other tasks prior to the - * ExecUpdate. + /* + * 我们之前已经设置了投影,所以这里我们只需要执行投影,不需要在执行 ExecUpdate 之前进行任何其他任务。 */ result_slot = ExecProject(action->proj, NULL); } else { - /* we don't do projection in remote query */ + /* 在远程查询中我们不进行投影操作 */ } /* - * We don't call ExecFilterJunk() because the projected tuple - * using the UPDATE action's targetlist doesn't have a junk - * attribute. + * 我们不调用 ExecFilterJunk(),因为使用 UPDATE 操作的目标列表投影的元组没有垃圾属性。 */ if (estate->es_result_update_remoterel) { estate->es_result_remoterel = estate->es_result_update_remoterel; - /* Check if has constraints */ + /* 检查是否有约束条件 */ if (resultRelationDesc->rd_att->constr) { mtstate->mt_update_constr_slot = ExtractConstraintTuple(mtstate, CMD_UPDATE, result_slot, action->tupDesc); @@ -347,24 +318,18 @@ TupleTableSlot* ExecMergeProjQual(ModifyTableState* mtstate, List* mergeMatchedA } /* - * Check and execute the first qualifying MATCHED action. The current target - * tuple is identified by tupleid. + * 检查并执行第一个符合条件的 MATCHED 动作。当前的目标元组由 tupleid 标识。 * - * We start from the first WHEN MATCHED action and check if the WHEN AND quals - * pass, if any. If the WHEN AND quals for the first action do not pass, we - * check the second, then the third and so on. If we reach to the end, no - * action is taken and we return true, indicating that no further action is - * required for this tuple. + * 我们从第一个 WHEN MATCHED 动作开始,检查是否通过了相应的 WHEN AND 条件,如果有的话。 + * 如果第一个动作的 WHEN AND 条件不满足,我们检查第二个动作,然后是第三个,依此类推。 + * 如果我们达到了最后一个动作,表示没有采取任何操作,我们返回 true,表示此元组无需进一步的操作。 * - * If we do find a qualifying action, then we attempt to execute the action. + * 如果我们找到了符合条件的动作,那么我们尝试执行该动作。 * - * If the tuple is concurrently updated, EvalPlanQual is run with the updated - * tuple to recheck the join quals. Note that the additional quals associated - * with individual actions are evaluated separately by the MERGE code, while - * EvalPlanQual checks for the join quals. If EvalPlanQual tells us that the - * updated tuple still passes the join quals, then we restart from the first - * action to look for a qualifying action. Otherwise, we return false meaning - * that a NOT MATCHED action must now be executed for the current source tuple. + * 如果元组正在并发更新,将使用更新后的元组运行 EvalPlanQual 来重新检查联接条件。 + * 需要注意的是,与各个动作关联的附加条件由 MERGE 代码单独评估,而 EvalPlanQual 则检查联接条件。 + * 如果 EvalPlanQual 告诉我们更新后的元组仍然满足联接条件,那么我们从第一个动作重新开始寻找符合条件的动作。 + * 否则,我们返回 false,意味着现在必须为当前的源元组执行一个 NOT MATCHED 动作。 */ static bool ExecMergeMatched(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot, JunkFilter* junkfilter, ItemPointer tupleid, HeapTupleHeader oldtuple, Oid oldPartitionOid, int2 bucketid) @@ -378,13 +343,13 @@ static bool ExecMergeMatched(ModifyTableState* mtstate, EState* estate, TupleTab bool partKeyUpdated = ((ModifyTable*)mtstate->ps.plan)->partKeyUpdated; /* - * Save the current information and work with the correct result relation. + * 保存当前的信息并切换到正确的结果关系进行操作。 */ saved_resultRelInfo = resultRelInfo; estate->es_result_relation_info = resultRelInfo; /* - * And get the correct action lists. + * 获取正确的动作列表。 */ mergeMatchedActionStates = resultRelInfo->ri_mergeState->matchedActionStates; @@ -408,21 +373,20 @@ static bool ExecMergeMatched(ModifyTableState* mtstate, EState* estate, TupleTab if (action->commandType == CMD_UPDATE /* && tuple_updated*/) InstrCountFiltered2(&mtstate->ps, 1); - /* - * We've activated one of the WHEN clauses, so we don't search - * further. This is required behaviour, not an optimization. + /* + * 我们已经触发了 WHEN 子句中的一个,因此无需继续搜索。这是所需的行为,而不是优化。 */ estate->es_result_relation_info = saved_resultRelInfo; } /* - * Successfully executed an action or no qualifying action was found. + * 成功执行了一个动作,或者没有找到符合条件的动作。 */ return true; } /* - * Execute the first qualifying NOT MATCHED action. + * 执行第一个符合条件的 NOT MATCHED 动作。 */ static void ExecMergeNotMatched(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot) { @@ -433,25 +397,19 @@ static void ExecMergeNotMatched(ModifyTableState* mtstate, EState* estate, Tuple const int hi_options = 0; /* - * We are dealing with NOT MATCHED tuple. Since for MERGE, the partition - * tree is not expanded for the result relation, we continue to work with - * the currently active result relation, which corresponds to the root - * of the partition tree. + * 我们正在处理 NOT MATCHED 元组。由于对于 MERGE,分区树未对结果关系展开,因此我们继续使用当前活动的结果关系, + * 这对应于分区树的根节点。 */ resultRelInfo = mtstate->resultRelInfo; /* - * For INSERT actions, root relation's merge action is OK since the - * INSERT's targetlist and the WHEN conditions can only refer to the - * source relation and hence it does not matter which result relation we - * work with. + * 对于 INSERT 操作,根关系的合并操作是可以的,因为 INSERT 的目标列表和 WHEN 条件只能引用源关系, + * 因此我们使用哪个结果关系并不重要。 */ mergeNotMatchedActionStates = resultRelInfo->ri_mergeState->notMatchedActionStates; /* - * Make source tuple available to ExecQual and ExecProject. We don't need - * the target tuple since the WHEN quals and the targetlist can't refer to - * the target columns. + * 使源元组对 ExecQual 和 ExecProject 可用。我们不需要目标元组,因为 WHEN 条件和目标列表不能引用目标列。 */ if (estate->es_result_insert_remoterel == NULL) { econtext->ecxt_scantuple = slot; @@ -471,36 +429,32 @@ static void ExecMergeNotMatched(ModifyTableState* mtstate, EState* estate, Tuple Assert(CMD_INSERT == action->commandType); /* - * get information on the (current) result relation + * 获取关于(当前)结果关系的信息 */ resultRelationInfo = estate->es_result_relation_info; resultRelationDesc = resultRelationInfo->ri_RelationDesc; - /* - * Test condition, if any + /* + * 测试条件,如果有的话 * - * In the absence of a condition we perform the action unconditionally - * (no need to check separately since ExecQual() will return true if - * there are no conditions to evaluate). + * 在没有条件的情况下,我们无条件执行动作 + * (无需单独检查,因为如果没有条件要评估,ExecQual() 将返回 true)。 */ if (ExecQual((List*)action->whenqual, econtext, false)) { /* - * We set up the projection earlier, so all we do here is - * Project, no need for any other tasks prior to the - * ExecInsert. + * 我们之前已经设置了投影,所以这里我们只需要执行投影,不需要在执行 ExecInsert 之前进行任何其他任务。 */ if (estate->es_result_insert_remoterel == NULL) { ExecProject(action->proj, NULL); /* - * ExecPrepareTupleRouting may modify the passed-in slot. Hence - * pass a local reference so that action->slot is not modified. + * ExecPrepareTupleRouting 可能会修改传入的槽。因此传递一个局部引用,以防止修改 action->slot。 */ myslot = mtstate->mt_mergeproj; } else { - /* in pgxc we do projection in the remote query*/ + /* 在 pgxc 中,我们在远程查询中进行投影操作 */ myslot = slot; - /* Check if has constraints */ + /* 检查是否有约束条件 */ if (resultRelationDesc->rd_att->constr) { mtstate->mt_insert_constr_slot = ExtractConstraintTuple(mtstate, CMD_INSERT, slot, action->tupDesc); } @@ -516,7 +470,7 @@ static void ExecMergeNotMatched(ModifyTableState* mtstate, EState* estate, Tuple } /* - * Creates the run-time state information for the Merge node + * 创建用于 Merge 节点的运行时状态信息 */ void ExecInitMerge(ModifyTableState* mtstate, EState* estate, ResultRelInfo* resultRelInfo) { @@ -537,20 +491,19 @@ void ExecInitMerge(ModifyTableState* mtstate, EState* estate, ResultRelInfo* res econtext = mtstate->ps.ps_ExprContext; - /* initialize scan slot and constraint slot */ + /* 初始化扫描槽和约束槽 */ mtstate->mt_scan_slot = NULL; mtstate->mt_update_constr_slot = NULL; mtstate->mt_insert_constr_slot = NULL; - /* initialize slot for merge actions */ + /* 初始化用于合并操作的槽 */ Assert(mtstate->mt_mergeproj == NULL); mtstate->mt_mergeproj = ExecInitExtraTupleSlot(mtstate->ps.state); ExecSetSlotDescriptor(mtstate->mt_mergeproj, relationDesc); /* - * Create a MergeActionState for each action on the mergeActionList - * and add it to either a list of matched actions or not-matched - * actions. + * 为 mergeActionList 上的每个动作创建一个 MergeActionState, + * 并将其添加到匹配动作或不匹配动作的列表中。 */ foreach (l, node->mergeActionList) { MergeAction* action = (MergeAction*)lfirst(l); @@ -562,7 +515,7 @@ void ExecInitMerge(ModifyTableState* mtstate, EState* estate, ResultRelInfo* res action_state->commandType = action->commandType; action_state->whenqual = ExecInitExpr((Expr*)action->qual, &mtstate->ps); - /* create target slot for this action's projection */ + /* 为此动作的投影创建目标槽 */ tupDesc = ExecTypeFromTL((List*)action->targetList, false, true, relationDesc->tdTableAmType); action_state->tupDesc = tupDesc; @@ -578,14 +531,13 @@ void ExecInitMerge(ModifyTableState* mtstate, EState* estate, ResultRelInfo* res mtstate->mt_insert_constr_slot = MakeSingleTupleTableSlot(tupDesc); } - /* build action projection state */ + /* 构建动作投影状态 */ targetList = (List*)ExecInitExpr((Expr*)action->targetList, &mtstate->ps); action_state->proj = ExecBuildProjectionInfo(targetList, econtext, mtstate->mt_mergeproj, relationDesc); /* - * We create two lists - one for WHEN MATCHED actions and one - * for WHEN NOT MATCHED actions - and stick the - * MergeActionState into the appropriate list. + * 我们创建两个列表 - 一个用于 WHEN MATCHED 操作,一个用于 WHEN NOT MATCHED 操作 - + * 并将 MergeActionState 放入适当的列表中。 */ if (action_state->matched) mergeMatchedActionStates = lappend(mergeMatchedActionStates, action_state); -- 2.34.1 From 40d6b07f996aa51ba5e88486a3d920ad6d8ed1e9 Mon Sep 17 00:00:00 2001 From: TerryTongJ Date: Thu, 10 Aug 2023 18:22:43 +0800 Subject: [PATCH 03/31] Update execMerge.cpp --- src/gausskernel/runtime/executor/execMerge.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gausskernel/runtime/executor/execMerge.cpp b/src/gausskernel/runtime/executor/execMerge.cpp index a1ddca213..45905face 100644 --- a/src/gausskernel/runtime/executor/execMerge.cpp +++ b/src/gausskernel/runtime/executor/execMerge.cpp @@ -1,3 +1,5 @@ + + /* * 版权所有 (c) 2020 华为技术有限公司 * -- 2.34.1 From 05aa68b9d381c01d79c9a826814734c85a00515b Mon Sep 17 00:00:00 2001 From: TerryTongJ Date: Thu, 10 Aug 2023 18:25:21 +0800 Subject: [PATCH 04/31] Update execMerge.cpp --- src/gausskernel/runtime/executor/execMerge.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/gausskernel/runtime/executor/execMerge.cpp b/src/gausskernel/runtime/executor/execMerge.cpp index 45905face..a1ddca213 100644 --- a/src/gausskernel/runtime/executor/execMerge.cpp +++ b/src/gausskernel/runtime/executor/execMerge.cpp @@ -1,5 +1,3 @@ - - /* * 版权所有 (c) 2020 华为技术有限公司 * -- 2.34.1 From ea34e898c5242ded06d1ac489690141cee7de78f Mon Sep 17 00:00:00 2001 From: LYLlyl Date: Thu, 10 Aug 2023 18:26:12 +0800 Subject: [PATCH 05/31] Update execMerge.cpp --- .../runtime/executor/execMerge.cpp | 249 ++++++++++-------- 1 file changed, 135 insertions(+), 114 deletions(-) diff --git a/src/gausskernel/runtime/executor/execMerge.cpp b/src/gausskernel/runtime/executor/execMerge.cpp index a1ddca213..874824fb6 100644 --- a/src/gausskernel/runtime/executor/execMerge.cpp +++ b/src/gausskernel/runtime/executor/execMerge.cpp @@ -40,9 +40,11 @@ static bool ExecMergeMatched(ModifyTableState* mtstate, EState* estate, TupleTab /* * 执行 MERGE 操作 */ + void ExecMerge(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot, JunkFilter* junkfilter, ResultRelInfo* resultRelInfo) { + // 获取执行上下文 ExprContext* econtext = mtstate->ps.ps_ExprContext; ItemPointer tupleid; ItemPointerData tuple_ctid; @@ -55,28 +57,26 @@ void ExecMerge(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot, AttrNumber bucketIdNum; int2 bucketid = InvalidBktId; + // 检查结果关系类型和垃圾过滤器 Assert(resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION || - resultRelInfo->ri_RelationDesc->rd_rel->relkind == PARTTYPE_PARTITIONED_RELATION || - junkfilter != NULL); + resultRelInfo->ri_RelationDesc->rd_rel->relkind == PARTTYPE_PARTITIONED_RELATION || + junkfilter != NULL); /* * 重置每个元组内存上下文,以释放在前一个周期中分配的任何表达式评估存储空间。 */ ResetExprContext(econtext); - /* - * 我们在目标关系和源关系之间执行联接,以找到一组具有目标表中匹配行的候选源行,以及一组在目标表中没有匹配行的候选源行。 - * 如果联接返回一个带有目标关系的 tid 集的元组,那么意味着联接为给定的源元组找到了匹配行。这种情况触发了 MERGE 的 WHEN MATCHED 子句。 - * 而在目标关系的 ctid 列中的 NULL 则表示一个 NOT MATCHED 情况。 - */ + // 从槽中提取关于匹配情况的信息 datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo, &isNull); if (!isNull) { matched = true; tupleid = (ItemPointer)DatumGetPointer(datum); - tuple_ctid = *tupleid; /* be sure we don't free ctid!! */ + tuple_ctid = *tupleid;/* 确保我们不释放 ctid!! */ tupleid = &tuple_ctid; + // 处理分区表和分桶表的情况 if (RELATION_IS_PARTITIONED(resultRelInfo->ri_RelationDesc) || RelationIsCUFormat(resultRelInfo->ri_RelationDesc)) { Datum tableOiddatum; @@ -107,52 +107,54 @@ void ExecMerge(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot, bucketid = DatumGetObjectId(bucketIddatum); } - } else { - matched = false; - tupleid = NULL; /* we don't need it for INSERT actions */ } - + else { + matched = false; + tupleid = NULL; /* 对于 INSERT 操作,不需要这个信息 */ + } /* - * 如果我们处理的是 WHEN MATCHED 情况,我们执行第一个附加的 WHEN MATCHED AND 条件满足的动作。 -* 如果找到一个没有附加条件的动作,则执行该动作。 -* -* 类似地,如果我们处理的是 WHEN NOT MATCHED 情况,我们按顺序查看给定的 WHEN NOT MATCHED 动作,直到找到一个满足条件的动作为止。 -* -* 在处理 WHEN MATCHED 情况时,同时进行目标元组的并发更新/删除会变得有趣。 -* -* 并发更新可能会有以下情况: -* -* 1. 修改目标元组,使其不再满足当前 WHEN MATCHED 动作附加的附加条件。 -* -* 在这种情况下,我们仍然处理 WHEN MATCHED 情况,但应重新检查 WHEN MATCHED 动作列表,并选择满足新目标元组的第一个动作。 -* -* 2. 修改目标元组,使联接条件不再满足,因此源元组不再匹配。 -* -* 在第二种情况下,源元组不再与目标元组匹配,因此我们现在会找到一个满足条件的 WHEN NOT MATCHED 动作来执行。 -* -* 并发删除将 WHEN MATCHED 情况更改为 WHEN NOT MATCHED。 -* -* ExecMergeMatched 负责遵循更新链并重新查找满足条件的 WHEN MATCHED 动作,只要更新的目标元组仍然满足联接条件,即仍然是 WHEN MATCHED 情况。 -* 如果元组被删除或联接条件失败,则返回并尝试 ExecMergeNotMatched。鉴于 ExecMergeMatched 总是通过跟踪更新链来取得进展, -* 我们永远不会从 ExecMergeNotMatched 切换到 ExecMergeMatched,因此不会出现死锁的风险。 - */ + * 如果我们处理的是 WHEN MATCHED 情况,我们执行第一个附加的 WHEN MATCHED AND 条件满足的动作。 + * 如果找到一个没有附加条件的动作,则执行该动作。 + * + * 类似地,如果我们处理的是 WHEN NOT MATCHED 情况,我们按顺序查看给定的 WHEN NOT MATCHED 动作,直到找到一个满足条件的动作为止。 + * + * 在处理 WHEN MATCHED 情况时,同时进行目标元组的并发更新/删除会变得有趣。 + * + * 并发更新可能会有以下情况: + * + * 1. 修改目标元组,使其不再满足当前 WHEN MATCHED 动作附加的附加条件。 + * + * 在这种情况下,我们仍然处理 WHEN MATCHED 情况,但应重新检查 WHEN MATCHED 动作列表,并选择满足新目标元组的第一个动作。 + * + * 2. 修改目标元组,使联接条件不再满足,因此源元组不再匹配。 + * + * 在第二种情况下,源元组不再与目标元组匹配,因此我们现在会找到一个满足条件的 WHEN NOT MATCHED 动作来执行。 + * + * 并发删除将 WHEN MATCHED 情况更改为 WHEN NOT MATCHED。 + * + * ExecMergeMatched 负责遵循更新链并重新查找满足条件的 WHEN MATCHED 动作,只要更新的目标元组仍然满足联接条件,即仍然是 WHEN MATCHED 情况。 + * 如果元组被删除或联接条件失败,则返回并尝试 ExecMergeNotMatched。鉴于 ExecMergeMatched 总是通过跟踪更新链来取得进展, + * 我们永远不会从 ExecMergeNotMatched 切换到 ExecMergeMatched,因此不会出现死锁的风险。 + */ + + // 根据匹配情况执行相应的动作 if (matched) matched = ExecMergeMatched(mtstate, estate, slot, junkfilter, tupleid, oldtuple, oldPartitionOid, bucketid); - /* - * 要么我们处理的是一个 NOT MATCHED 的元组,要么 ExecMergeNotMatched() 返回了 "false", - * 表示先前的 MATCHED 元组不再是一个匹配的元组。 - */ + // 如果没有匹配的情况,执行相应的 NOT MATCHED 操作 if (!matched) ExecMergeNotMatched(mtstate, estate, slot); } + + /* * 从计划槽中提取元组以进行约束检查 */ static TupleTableSlot* ExtractConstraintTuple( ModifyTableState* mtstate, CmdType commandType, TupleTableSlot* slot, TupleDesc tupDesc) { + // 获取执行上下文 ExprContext* econtext = mtstate->ps.ps_ExprContext; AutoContextSwitch memContext(econtext->ecxt_per_tuple_memory); HeapTuple tempTuple = NULL; @@ -163,38 +165,45 @@ static TupleTableSlot* ExtractConstraintTuple( int index = 0; int i = 0; + // 根据命令类型提取约束元组的槽 switch (commandType) { - case CMD_UPDATE: - constrSlot = mtstate->mt_update_constr_slot; - for (i = 0; i < originTupleDesc->natts; i++) { - if (strstr(originTupleDesc->attrs[i]->attname.data, "action UPDATE target")) { - values[index] = slot->tts_values[i]; - isnull[index] = slot->tts_isnull[i]; - index++; - } + case CMD_UPDATE: + constrSlot = mtstate->mt_update_constr_slot; + for (i = 0; i < originTupleDesc->natts; i++) { + // 查找符合条件的属性并复制值和空标志 + if (strstr(originTupleDesc->attrs[i]->attname.data, "action UPDATE target")) { + values[index] = slot->tts_values[i]; + isnull[index] = slot->tts_isnull[i]; + index++; } - break; - case CMD_INSERT: - constrSlot = mtstate->mt_insert_constr_slot; - for (i = 0; i < originTupleDesc->natts; i++) { - if (strstr(originTupleDesc->attrs[i]->attname.data, "action INSERT target")) { - values[index] = slot->tts_values[i]; - isnull[index] = slot->tts_isnull[i]; - index++; - } + } + break; + case CMD_INSERT: + constrSlot = mtstate->mt_insert_constr_slot; + for (i = 0; i < originTupleDesc->natts; i++) { + // 查找符合条件的属性并复制值和空标志 + if (strstr(originTupleDesc->attrs[i]->attname.data, "action INSERT target")) { + values[index] = slot->tts_values[i]; + isnull[index] = slot->tts_isnull[i]; + index++; } - break; - default: - Assert(0); + } + break; + default: + Assert(0); } + // 确保约束槽的表访问方法类型与原始元组描述一致 Assert(constrSlot->tts_tupleDescriptor->tdTableAmType == originTupleDesc->tdTableAmType); + + // 使用 values 和 isnull 数组创建临时 HeapTuple,并将其存储到约束槽中 tempTuple = (HeapTuple)tableam_tops_form_tuple(tupDesc, values, isnull, HEAP_TUPLE); (void)ExecStoreTuple(tempTuple, constrSlot, InvalidBuffer, false); return constrSlot; } + /* * 从计划槽中提取目标表的扫描元组 */ @@ -211,18 +220,19 @@ TupleTableSlot* ExtractScanTuple(ModifyTableState* mtstate, TupleTableSlot* slot int startIdx = 0; int index = 0; - /* + /* * 找到目标表的正确起始索引。我们应该跳过 sourceTargetList。 * 首先计算 sourceTargetList 中源列的数量。虽然我们向 sourceTargetList 添加了新列, * 但 resno 不是连续的,因此找到最大的连续编号作为 sourceTargetList 的原始长度。 */ - foreach (lc, sourceTargetList) { + foreach(lc, sourceTargetList) { TargetEntry* tle = (TargetEntry*)lfirst(lc); if (tle->resno != startIdx + 1) break; startIdx++; } + // 从原始槽中提取值和空标志,并构建一个临时 HeapTuple for (index = 0; index < tupDesc->natts; index++) { if (tupDesc->attrs[index]->attisdropped == true) { isnull[index] = true; @@ -234,6 +244,7 @@ TupleTableSlot* ExtractScanTuple(ModifyTableState* mtstate, TupleTableSlot* slot startIdx++; } + // 使用 values 和 isnull 数组创建临时 HeapTuple,并将其存储到扫描槽中 tempTuple = (HeapTuple)tableam_tops_form_tuple(tupDesc, values, isnull, HEAP_TUPLE); (void)ExecStoreTuple(tempTuple, scanSlot, InvalidBuffer, false); @@ -261,40 +272,42 @@ TupleTableSlot* ExecMergeProjQual(ModifyTableState* mtstate, List* mergeMatchedA Assert(CMD_UPDATE == action->commandType); - /* - * 获取关于(当前)结果关系的信息 - */ + /* + * 获取关于(当前)结果关系的信息 + */ resultRelInfo = estate->es_result_relation_info; resultRelationDesc = resultRelInfo->ri_RelationDesc; - /* - * 使元组和任何必要的连接变量对 ExecQual 和 ExecProject 可用。 - * 目标的现有元组被安装在 scantuple 中。 - * 同样,在匹配的元组和 UPDATE/DELETE 操作的情况下,仅需要此目标关系的槽。 - */ + /* + * 使元组和任何必要的连接变量对 ExecQual 和 ExecProject 可用。 + * 目标的现有元组被安装在 scantuple 中。 + * 同样,在匹配的元组和 UPDATE/DELETE 操作的情况下,仅需要此目标关系的槽。 + */ if (estate->es_result_update_remoterel == NULL) { econtext->ecxt_scantuple = ExtractScanTuple(mtstate, originSlot, action->tupDesc); econtext->ecxt_innertuple = originSlot; econtext->ecxt_outertuple = NULL; - } else { + } + else { econtext->ecxt_scantuple = originSlot; econtext->ecxt_innertuple = NULL; econtext->ecxt_outertuple = NULL; } - /* - * 测试条件,如果有的话 - * - * 在没有条件的情况下,我们无条件执行动作 - * (无需单独检查,因为如果没有条件要评估,ExecQual() 将返回 true)。 - */ + /* + * 测试条件,如果有的话 + * + * 在没有条件的情况下,我们无条件执行动作 + * (无需单独检查,因为如果没有条件要评估,ExecQual() 将返回 true)。 + */ if (ExecQual((List*)action->whenqual, econtext, false)) { if (estate->es_result_update_remoterel == NULL) { - /* - * 我们之前已经设置了投影,所以这里我们只需要执行投影,不需要在执行 ExecUpdate 之前进行任何其他任务。 - */ + /* + * 我们之前已经设置了投影,所以这里我们只需要执行投影,不需要在执行 ExecUpdate 之前进行任何其他任务。 + */ result_slot = ExecProject(action->proj, NULL); - } else { + } + else { /* 在远程查询中我们不进行投影操作 */ } @@ -331,6 +344,7 @@ TupleTableSlot* ExecMergeProjQual(ModifyTableState* mtstate, List* mergeMatchedA * 如果 EvalPlanQual 告诉我们更新后的元组仍然满足联接条件,那么我们从第一个动作重新开始寻找符合条件的动作。 * 否则,我们返回 false,意味着现在必须为当前的源元组执行一个 NOT MATCHED 动作。 */ + static bool ExecMergeMatched(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot, JunkFilter* junkfilter, ItemPointer tupleid, HeapTupleHeader oldtuple, Oid oldPartitionOid, int2 bucketid) { @@ -360,22 +374,22 @@ static bool ExecMergeMatched(ModifyTableState* mtstate, EState* estate, TupleTab if (slot != NULL) { (void)ExecUpdate(tupleid, - oldPartitionOid, - bucketid, - oldtuple, - slot, - saved_slot, - epqstate, - mtstate, - mtstate->canSetTag, - partKeyUpdated); + oldPartitionOid, + bucketid, + oldtuple, + slot, + saved_slot, + epqstate, + mtstate, + mtstate->canSetTag, + partKeyUpdated); } if (action->commandType == CMD_UPDATE /* && tuple_updated*/) InstrCountFiltered2(&mtstate->ps, 1); - /* - * 我们已经触发了 WHEN 子句中的一个,因此无需继续搜索。这是所需的行为,而不是优化。 - */ + /* + * 我们已经触发了 WHEN 子句中的一个,因此无需继续搜索。这是所需的行为,而不是优化。 + */ estate->es_result_relation_info = saved_resultRelInfo; } @@ -415,7 +429,8 @@ static void ExecMergeNotMatched(ModifyTableState* mtstate, EState* estate, Tuple econtext->ecxt_scantuple = slot; econtext->ecxt_innertuple = slot; econtext->ecxt_outertuple = NULL; - } else { + } + else { econtext->ecxt_scantuple = slot; econtext->ecxt_innertuple = NULL; econtext->ecxt_outertuple = NULL; @@ -434,12 +449,12 @@ static void ExecMergeNotMatched(ModifyTableState* mtstate, EState* estate, Tuple resultRelationInfo = estate->es_result_relation_info; resultRelationDesc = resultRelationInfo->ri_RelationDesc; - /* - * 测试条件,如果有的话 - * - * 在没有条件的情况下,我们无条件执行动作 - * (无需单独检查,因为如果没有条件要评估,ExecQual() 将返回 true)。 - */ + /* + * 测试条件,如果有的话 + * + * 在没有条件的情况下,我们无条件执行动作 + * (无需单独检查,因为如果没有条件要评估,ExecQual() 将返回 true)。 + */ if (ExecQual((List*)action->whenqual, econtext, false)) { /* * 我们之前已经设置了投影,所以这里我们只需要执行投影,不需要在执行 ExecInsert 之前进行任何其他任务。 @@ -450,7 +465,8 @@ static void ExecMergeNotMatched(ModifyTableState* mtstate, EState* estate, Tuple * ExecPrepareTupleRouting 可能会修改传入的槽。因此传递一个局部引用,以防止修改 action->slot。 */ myslot = mtstate->mt_mergeproj; - } else { + } + else { /* 在 pgxc 中,我们在远程查询中进行投影操作 */ myslot = slot; @@ -481,22 +497,24 @@ void ExecInitMerge(ModifyTableState* mtstate, EState* estate, ResultRelInfo* res TupleDesc relationDesc = resultRelInfo->ri_RelationDesc->rd_att; ModifyTable* node = (ModifyTable*)mtstate->ps.plan; + // 如果 mergeActionList 为空,则直接返回 if (node->mergeActionList == NIL) return; mtstate->mt_merge_subcommands = 0; + // 分配表达式上下文,如果不存在的话 if (mtstate->ps.ps_ExprContext == NULL) ExecAssignExprContext(estate, &mtstate->ps); econtext = mtstate->ps.ps_ExprContext; - /* 初始化扫描槽和约束槽 */ + // 初始化扫描槽和约束槽 mtstate->mt_scan_slot = NULL; mtstate->mt_update_constr_slot = NULL; mtstate->mt_insert_constr_slot = NULL; - /* 初始化用于合并操作的槽 */ + // 初始化用于合并操作的投影槽 Assert(mtstate->mt_mergeproj == NULL); mtstate->mt_mergeproj = ExecInitExtraTupleSlot(mtstate->ps.state); ExecSetSlotDescriptor(mtstate->mt_mergeproj, relationDesc); @@ -505,7 +523,7 @@ void ExecInitMerge(ModifyTableState* mtstate, EState* estate, ResultRelInfo* res * 为 mergeActionList 上的每个动作创建一个 MergeActionState, * 并将其添加到匹配动作或不匹配动作的列表中。 */ - foreach (l, node->mergeActionList) { + foreach(l, node->mergeActionList) { MergeAction* action = (MergeAction*)lfirst(l); MergeActionState* action_state = makeNode(MergeActionState); TupleDesc tupDesc; @@ -519,6 +537,7 @@ void ExecInitMerge(ModifyTableState* mtstate, EState* estate, ResultRelInfo* res tupDesc = ExecTypeFromTL((List*)action->targetList, false, true, relationDesc->tdTableAmType); action_state->tupDesc = tupDesc; + // 在特定情况下创建扫描槽和约束槽 if (IS_PGXC_DATANODE && CMD_UPDATE == action->commandType) { mtstate->mt_scan_slot = MakeSingleTupleTableSlot(tupDesc); } @@ -544,20 +563,22 @@ void ExecInitMerge(ModifyTableState* mtstate, EState* estate, ResultRelInfo* res else mergeNotMatchedActionStates = lappend(mergeNotMatchedActionStates, action_state); + // 根据不同的操作类型设置子命令标志 switch (action->commandType) { - case CMD_INSERT: - ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, action->targetList); - mtstate->mt_merge_subcommands |= MERGE_INSERT; - break; - case CMD_UPDATE: - ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, action->targetList); - mtstate->mt_merge_subcommands |= MERGE_UPDATE; - break; - default: - Assert(0); - break; + case CMD_INSERT: + ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, action->targetList); + mtstate->mt_merge_subcommands |= MERGE_INSERT; + break; + case CMD_UPDATE: + ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, action->targetList); + mtstate->mt_merge_subcommands |= MERGE_UPDATE; + break; + default: + Assert(0); + break; } + // 设置匹配和不匹配动作的状态列表 resultRelInfo->ri_mergeState->matchedActionStates = mergeMatchedActionStates; resultRelInfo->ri_mergeState->notMatchedActionStates = mergeNotMatchedActionStates; } -- 2.34.1 From 50230625f0cdb758c20b16c6f4fa5cf9652c280a Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Thu, 10 Aug 2023 20:52:45 +0800 Subject: [PATCH 06/31] Update execClusterResize.cpp --- .../runtime/executor/execClusterResize.cpp | 340 +++++++++--------- 1 file changed, 170 insertions(+), 170 deletions(-) diff --git a/src/gausskernel/runtime/executor/execClusterResize.cpp b/src/gausskernel/runtime/executor/execClusterResize.cpp index 4eb0444a5..1492a6a80 100644 --- a/src/gausskernel/runtime/executor/execClusterResize.cpp +++ b/src/gausskernel/runtime/executor/execClusterResize.cpp @@ -3,9 +3,9 @@ * execClusterResize.cpp * MPPDB ClusterResizing relevant routines * - * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California + * 部分版权 (c) 2020 华为技术有限公司 + * 部分版权所有 (c) 1996-2012,PostgreSQL 全球开发集团 + * 部分版权 (c) 1994,加州大学摄政 * * IDENTIFICATION * src/gausskernel/runtime/executor/execClusterResize.cpp @@ -44,10 +44,10 @@ /* * --------------------------------------------------------------------------------- - * *Local functions/variables declaration fields* + * 局部函数/变量声明字段* * --------------------------------------------------------------------------------- */ -/* delete delta table definition */ +/*删除增量表定义 */ #define Natts_pg_delete_delta 3 #define Anum_pg_delete_delta_xcnodeid_and_dntableoid 1 @@ -120,12 +120,12 @@ static inline bool redis_ctid_retrive_function(const char* funcname, Oid rettype /* - * - Brief: Record the given tuple's tupleid into pg_delete_delta table - * - Parameter: - * @rel: target relation of UPDATE/DELETE operation - * @tupleid: tupleid that needs record - * - Return: - * no return value + *简介:将给定元组的元组记录到pg_delete_delta表中 + * -参数: + * @rel:更新/删除操作的目标关系 + * @tupleid:需要记录的元组 + *-返回: + * 无返回值 */ void RecordDeletedTuple(Oid relid, int2 bucketid, const ItemPointer tupleid, const Relation deldelta_rel) { @@ -134,10 +134,10 @@ void RecordDeletedTuple(Oid relid, int2 bucketid, const ItemPointer tupleid, con HeapTuple tup = NULL; Assert(deldelta_rel); - /* In redistribution, table delete_delta has 3 or 2 column. */ + /*在重新分发中,表 delete_delta 有 3 列或 2 列。 */ Assert(RelationGetDescr(deldelta_rel)->natts <= 3); - /* Iterate through attributes initializing nulls and values */ + /*循环访问初始化空值和值的属性 */ for (int i = 0; i < Natts_pg_delete_delta; i++) { nulls[i] = false; values[i] = (Datum)0; @@ -149,7 +149,7 @@ void RecordDeletedTuple(Oid relid, int2 bucketid, const ItemPointer tupleid, con if (BUCKET_NODE_IS_VALID(bucketid)) { values[Anum_pg_delete_delta_tablebucketid_and_ctid - 1] |= ((uint64)bucketid << 48); } - /* Record delta */ + /* 记录增量 */ tup = heap_form_tuple(RelationGetDescr(deldelta_rel), values, nulls); (void)simple_heap_insert(deldelta_rel, tup); @@ -157,18 +157,18 @@ void RecordDeletedTuple(Oid relid, int2 bucketid, const ItemPointer tupleid, con } /* - * - Brief: Determine if the relation is under cluster resizing operation - * - Parameter: - * @rel: relation that needs to check - * - Return: - * @TRUE: relation is under cluster resizing - * @FALSE: relation is not under cluster resizing + * - 简介:确定关系是否正在执行群集大小调整操作 + * - 参数: + * @rel:需要检查的关系 + * - 返回: + * @TRUE:关系正在调整集群大小 + * @FALSE: 关系未调整集群大小 */ bool RelationInClusterResizing(const Relation rel) { Assert(rel != NULL); - /* Check relation's append_mode status */ + /*检查关系的append_mode状态 */ if (!IsInitdb && RelationInRedistribute(rel)) return true; @@ -176,18 +176,18 @@ bool RelationInClusterResizing(const Relation rel) } /* - * - Brief: Determine if the relation is under cluster resizing read only operation - * - Parameter: - * @rel: relation that needs to check - * - Return: - * @TRUE: relation is under cluster resizing read only - * @FALSE: relation is not under cluster resizing read only + * - 简要:确定关系是否处于集群调整只读操作下 + * - 参数: + * @rel:需要检查的关系 + * - 返回: + * @TRUE: 关系处于集群调整大小只读状态 + * @FALSE: 关系不处于集群调整大小只读状态 */ bool RelationInClusterResizingReadOnly(const Relation rel) { Assert(rel != NULL); - /* Check relation's append_mode status */ + /*检查关系的append_mode状态 */ if (!IsInitdb && RelationInRedistributeReadOnly(rel)) return true; @@ -195,18 +195,18 @@ bool RelationInClusterResizingReadOnly(const Relation rel) } /* - * - Brief: Determine if the relation is under cluster resizing read only operation - * - Parameter: - * @rel: relation that needs to check - * - Return: - * @TRUE: relation is under cluster resizing endcatchup(write error) - * @FALSE: relation is not under cluster resizing endcatchup(write error) + * - 简要:确定关系是否处于集群调整只读操作下 + * - 参数: + * @rel: 需要检查的关系 + * - 返回: + * @TRUE: 关系处于群集调整大小状态endcatchup(写错误) + * @FALSE: 关系不在群集调整大小范围内endcatchup(写错误) */ bool RelationInClusterResizingEndCatchup(const Relation rel) { Assert(rel != NULL); - /* Check relation's append_mode status */ + /* 检查关系的append_mode状态*/ if (!IsInitdb && RelationInRedistributeEndCatchup(rel)) return true; @@ -214,9 +214,9 @@ bool RelationInClusterResizingEndCatchup(const Relation rel) } /* - * @Description: check whether relation is in redistribution though range variable. - * @in range_var: range variable which stored relation info. - * @return: true for in redistribution. + * @说明:通过范围变量检查关系是否在重新分配。 + * @在range_var:存储关系信息的范围变量。 + * @在重新分配中返回:true。 */ bool CheckRangeVarInRedistribution(const RangeVar* range_var) { @@ -228,7 +228,7 @@ bool CheckRangeVarInRedistribution(const RangeVar* range_var) if (OidIsValid(relid)) { relation = relation_open(relid, NoLock); - /* If the relation is index, we should check the related table is resizing or not. */ + /* 如果关系是索引,我们应该检查相关表是否在调整大小。*/ if (RelationIsIndex(relation)) { Oid heapOid = IndexGetRelation(relid, false); Relation heapRelation = relation_open(heapOid, AccessShareLock); @@ -245,12 +245,12 @@ bool CheckRangeVarInRedistribution(const RangeVar* range_var) } /* - * - Brief: Determine if the table name is delete_delta table. - * - Parameter: - * @relname: name of target table - * - Return: - * @TRUE: the table is delete_delta table - * @FALSE: the table is not delete_delta table + * - 简要:确定表名是否为delete_delta table。 + * - 参数: + * @relname: 目标表名 + * - 返回: + * @TRUE: 表为delete_delta表 + * @FALSE: 这个表不是delete_delta表 */ bool RelationIsDeleteDeltaTable(char* delete_delta_name) { @@ -292,10 +292,10 @@ bool RelationIsDeleteDeltaTable(char* delete_delta_name) } /* - * - Brief: Determine if the Progress is under cluster resizing status - * - Return: - * @TRUE: Progress is under cluster resizing - * @FALSE: Progress is not under cluster resizing + * - 简要:确定进度是否处于集群调整状态 + * - 返回: + * @TRUE: 正在调整集群大小 + * @FALSE: 进度并不在集群调整中 */ bool ClusterResizingInProgress() { @@ -329,27 +329,27 @@ bool ClusterResizingInProgress() } /* - * - Brief: get the name of delete_delta table - * - Parameter: - * @relname: name of target table - * @delta_delta_name: output value for delete_delta table name - * @isMultiCatchup: multi catchup delta or not - * - Return: - * no return value + * -简介:获取delete_delta表的名称 + * - 参数: + * @relname: 目标表名 + * @delta_delta_name: delete_delta表名的输出值 + * @isMultiCatchup: 是不是多追赶delta + * - 返回: + * 无返回值 */ static inline void RelationGetDeleteDeltaTableName(Relation rel, char* delete_delta_name, bool isMultiCatchup) { int rc = 0; - /* Check if output parameter it not palloc()-ed from caller side */ + /* 检查输出参数是否没有从调用方palloc()-ed */ if (delete_delta_name == NULL || rel == NULL) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid parameter in function '%s'", __FUNCTION__))); } /* - * Look up Relation's reloptions to get table's cnoid to - * form the name of delete_delta table + * 查找Relation的关联以获得表的id + * 形成delete_delta表的名称 */ if (!IsInitdb) { if (RelationInClusterResizing(rel) && !RelationInClusterResizingReadOnly(rel)) { @@ -381,12 +381,12 @@ static inline void RelationGetDeleteDeltaTableName(Relation rel, char* delete_de } /* - * - Brief: get and open delete_delta rel - * - Parameter: - * @rel: target relation of UPDATE/DELETE/TRUNCATE operation - * @lockmode: lock mode - * @isMultiCatchup: multi catchup delta or not - * - Return: + * - 简介:获取并打开delete_delta rel + * - 参数: + * @rel: UPDATE/DELETE/TRUNCATE操作的目标关系 + * @lockmode: 锁定模式 + * @isMultiCatchup: 是不是多追赶delta + * - 返回: * delete_delta rel */ Relation GetAndOpenDeleteDeltaRel(const Relation rel, LOCKMODE lockmode, bool isMultiCatchup) @@ -403,22 +403,22 @@ Relation GetAndOpenDeleteDeltaRel(const Relation rel, LOCKMODE lockmode, bool is RelationGetDeleteDeltaTableName(rel, (char*)delete_delta_tablename, isMultiCatchup); data_redis_namespace = get_namespace_oid("data_redis", false); - /* We are going to fetch the delete delta relation under data_redis schema. */ + /* 我们将在data_redis模式下获取delete delta关系。 */ deldelta_relid = get_relname_relid(delete_delta_tablename, data_redis_namespace); if (!OidIsValid(deldelta_relid)) { /* - * If multi catchup delta table is not there, just return NULL. We should not - * report error, because it is a valid case. Multi catchup delta table is - * dropped in each catchup iteration. + * 如果多追赶增量表不存在,则返回NULL。否则不是 (We should not) + * 报告错误,因为这是一个有效的案例。多追赶delta表是( Multi catchup delta table is) + * 在每次追赶迭代中被丢弃。 */ if (isMultiCatchup) { return NULL; } /* - * To support Update or Delete during extension, we need to add 2 more columns. - * more columns. Limited by MaxHeapAttributeNumber, if the table already contains too many columns, - * we don't allow update or delete anymore, but insert statement can still proceed. + * 为了在扩展期间支持更新或删除,我们需要添加2列。 + * 更多的列。如果表已经包含了太多的列,受maxheapattributennumber的限制, + * 我们不再允许更新或删除,但插入语句仍然可以进行。 */ if (((rel->rd_att->natts > (MaxHeapAttributeNumber - (Natts_pg_delete_delta - 1))) && !RELATION_IS_PARTITIONED(rel)) || @@ -429,7 +429,7 @@ Relation GetAndOpenDeleteDeltaRel(const Relation rel, LOCKMODE lockmode, bool is RelationGetRelationName(rel)), errdetail("Can not support online extension, if the table contains too many columns"))); } - /* ERROR case, should never come here */ + /* 错误情况下,不应该出现在这里 */ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("delete delta table %s is not found when do cluster resizing table \"%s\"", @@ -446,11 +446,11 @@ Relation GetAndOpenDeleteDeltaRel(const Relation rel, LOCKMODE lockmode, bool is } /* - * - Brief: Check the stmtment during online expansion, block unsupported ddl in cluster resizing. - * - Parameter: - * @rel: parsetree of DDL - * - Return: - * no return value + * - 简介:检查在线扩展期间的配置,在集群调整中阻止不支持的ddl。 + * - 参数: + * @rel: DDL的解析树 + * -返回: + * 无返回值 */ void BlockUnsupportedDDL(const Node* parsetree) { @@ -466,11 +466,11 @@ void BlockUnsupportedDDL(const Node* parsetree) LOCKMODE lockmode_openrel = AccessShareLock; /* - * Check for shared-cache-inval messages before trying to access the - * relation. This is needed to cover the case where the name - * identifies a rel that has been dropped and recreated since the - * start of our transaction: if we don't flush the old syscache entry, - * then we'll latch onto that entry and suffer an error later. + * 文件之前,请检查是否存在共享缓存无效消息 + * relation. 关系。这是需要覆盖的情况下的名称 + * 对象之后已删除并重新创建的rel + * 事务开始:如果我们不刷新旧的syscache条目, + * 然后我们将锁定该条目并在稍后遭受错误。 */ AcceptInvalidationMessages(); @@ -501,13 +501,13 @@ void BlockUnsupportedDDL(const Node* parsetree) return; } break; - /* Block CURSOR for while table in cluster resizing */ + /* 在集群调整大小时阻塞游标 */ case T_PlannedStmt: { PlannedStmt* stmt = (PlannedStmt*)parsetree; relidlist = stmt->relationOids; } break; - /* Block RENAME while table in cluster resizing */ + /* 当表在集群中调整大小时,块RENAME */ case T_RenameStmt: { RenameStmt* stmt = (RenameStmt*)parsetree; @@ -540,11 +540,11 @@ void BlockUnsupportedDDL(const Node* parsetree) stmt->relation->relname))); } break; - /* Block ALTER set schema while table in cluster resizing */ + /* 当表在集群中调整大小时,Block ALTER设置模式 */ case T_AlterObjectSchemaStmt: { AlterObjectSchemaStmt* stmt = (AlterObjectSchemaStmt*)parsetree; - /* disable alter table set schema when transfer */ + /* 在传输时禁用alter table set schema */ if (stmt->relation != NULL) { Oid relOid = RangeVarGetRelid(stmt->relation, AccessShareLock, true); if (OidIsValid(relOid)) { @@ -567,7 +567,7 @@ void BlockUnsupportedDDL(const Node* parsetree) stmt->relation->relname))); } break; - /* Block CREATE index while table in cluster resizing(for row table only) */ + /* 当表在集群中调整大小时,阻塞创建索引(仅适用于行表) */ case T_IndexStmt: { IndexStmt* stmt = (IndexStmt*)parsetree; if (stmt->relation) { @@ -590,13 +590,13 @@ void BlockUnsupportedDDL(const Node* parsetree) } } break; - /* Block REINDEX while table in cluster resizing(for row table only) */ + /* 当表在集群中调整大小时,块REINDEX(仅适用于行表) */ case T_ReindexStmt: { ReindexStmt* stmt = (ReindexStmt*)parsetree; if (stmt->relation) { relid = RangeVarGetRelid(stmt->relation, AccessShareLock, true); if (OidIsValid(relid)) { - /* release index lock before lock table to avoid deadlock */ + /* 在锁表之前释放索引锁以避免死锁 */ UnlockRelationOid(relid, AccessShareLock); Relation relation = relation_open(relid, NoLock); @@ -622,7 +622,7 @@ void BlockUnsupportedDDL(const Node* parsetree) } } break; - /* Block ALTER-Table while table in cluster resizing */ + /* 当表在集群中调整大小时,阻塞ALTER-Table */ case T_AlterTableStmt: { AlterTableStmt* stmt = (AlterTableStmt*)parsetree; AlterTableCmd* cmd = NULL; @@ -631,13 +631,13 @@ void BlockUnsupportedDDL(const Node* parsetree) switch (cmd->subtype) { case AT_TruncatePartition: { /* - * We do not allow truncate partition when the target is in read only - * mode during online expansion time. + * 当目标处于只读状态时,我们不允许截断分区 + *在线扩容时的模式 */ if (stmt->relation) { relid = RangeVarGetRelid(stmt->relation, lockmode_getrelid, true); if (OidIsValid(relid)) { - /* disable alter table truncate partition during transfer */ + /* 禁止在传输过程中截断分区 */ if (CheckRangeVarInRedistribution(stmt->relation)) { Oid nsOid = GetNamespaceIdbyRelId(relid); TRANSFER_DISABLE_DDL(nsOid); @@ -704,12 +704,12 @@ void BlockUnsupportedDDL(const Node* parsetree) } } - /* If rel option contain append_mode, then not check. */ + /* 如果rel选项包含append_mode,则不检查。 */ if (opt != NULL) { break; } } - /* fall through */ + /* 失败 */ default: { if (stmt->relation && !u_sess->attr.attr_sql.enable_cluster_resize && CheckRangeVarInRedistribution(stmt->relation)) @@ -725,7 +725,7 @@ void BlockUnsupportedDDL(const Node* parsetree) return; } break; - /* Block CREATE-RULE statements while target table in cluster resizing */ + /* 当集群中的目标表调整大小时,阻塞CREATE-RULE语句 */ case T_RuleStmt: { RuleStmt* stmt = (RuleStmt*)parsetree; if (stmt->relation) { @@ -734,7 +734,7 @@ void BlockUnsupportedDDL(const Node* parsetree) } } break; - /* Block CREATE SEQUENCE set schema while owner table in cluster resizing */ + /* 当所有者表在集群中调整大小时,Block CREATE SEQUENCE设置模式 */ case T_CreateSeqStmt: { CreateSeqStmt* stmt = (CreateSeqStmt*)parsetree; List* owned_by = NULL; @@ -761,7 +761,7 @@ void BlockUnsupportedDDL(const Node* parsetree) } } break; - /* Block ALTER SEQUENCE while owner table in cluster resizing */ + /* 当集群中的所有者表调整大小时,阻塞ALTER SEQUENCE */ case T_AlterSeqStmt: { AlterSeqStmt* stmt = (AlterSeqStmt*)parsetree; List* owned_by = NIL; @@ -788,7 +788,7 @@ void BlockUnsupportedDDL(const Node* parsetree) } } break; - /* Block CLUSTER while table in cluster resizing */ + /* 当表在集群中调整大小时阻塞集群 */ case T_ClusterStmt: { ClusterStmt* stmt = (ClusterStmt*)parsetree; if (stmt->relation && CheckRangeVarInRedistribution(stmt->relation)) @@ -799,7 +799,7 @@ void BlockUnsupportedDDL(const Node* parsetree) stmt->relation->relname))); } break; - /* Block VACUUM FULL while table in cluster resizing */ + /* 当表在集群中调整大小时,块真空已满 */ case T_VacuumStmt: { VacuumStmt* stmt = (VacuumStmt*)parsetree; if ((stmt->options & VACOPT_VACUUM) || (stmt->options & VACOPT_MERGE)) { @@ -822,7 +822,7 @@ void BlockUnsupportedDDL(const Node* parsetree) } } break; - /* Block truncate DDL when the target table is read only in cluster resizing */ + /* 在集群调整大小时,当目标表为只读时,块截断DDL */ case T_TruncateStmt: { ListCell* cell = NULL; TruncateStmt* stmt = (TruncateStmt*)parsetree; @@ -857,7 +857,7 @@ void BlockUnsupportedDDL(const Node* parsetree) DropStmt* stmt = (DropStmt*)parsetree; switch (stmt->removeType) { case OBJECT_TABLE: { - /* disable drop table when transfer */ + /* 在传输时禁用drop表 */ ListCell* cell = NULL; foreach (cell, stmt->objects) { RangeVar* rel = makeRangeVarFromNameList((List*)lfirst(cell)); @@ -871,7 +871,7 @@ void BlockUnsupportedDDL(const Node* parsetree) break; } case OBJECT_SCHEMA: { - /* disable drop schema when transfer */ + /* 传输时禁用删除模式 */ ListCell* cell = NULL; foreach (cell, stmt->objects) { List* objname = (List*)lfirst(cell); @@ -887,7 +887,7 @@ void BlockUnsupportedDDL(const Node* parsetree) } break; case T_CreateStmt: { - /* disable create table when transfer */ + /* 禁止传输时创建表 */ CreateStmt* stmt = (CreateStmt*)parsetree; if (stmt->relation != NULL) { Oid nsOid = RangeVarGetCreationNamespace(stmt->relation); @@ -916,14 +916,14 @@ void BlockUnsupportedDDL(const Node* parsetree) } /* - * - Brief: For online expanions, the shippable function is evaluated here, the module - * will be invoked in optimizer when do FQS evaluation, we have to define function - * as STABLE - * - Parameter: - * @funcid: oid of user defined function which is createed/dropped in scope of gs_redis - * - Return: - * @true: shippable - * @false: unshippable + * - 简介:对于在线扩展,这里评估的是可发布功能模块 + * 在优化器中调用FQS评估时,我们必须定义函数 + * 是稳定的(as STABLE) + * - 参数: + * @funcid: 在gs_redis范围内创建/删除的用户定义函数的Oid + * - 返回: + * @true: 可交付 + * @false: 不可交付 */ bool redis_func_shippable(Oid funcid) { @@ -937,11 +937,11 @@ bool redis_func_shippable(Oid funcid) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function with OID %u does not exist", funcid))); } - /* Fetch function signatures */ + /* 获取函数签名 */ rettype = get_func_signature(funcid, &argstype, &nargs); if (redis_tupleid_retrive_function(func_name, rettype, argstype, nargs)) { - /* tupleid retrive functions is shippable to datanodes */ + /* Tupleid检索函数可以发布到数据节点 */ result = true; } else if (redis_offset_retrive_function(func_name, rettype, argstype, nargs)) { result = true; @@ -961,11 +961,11 @@ bool redis_func_shippable(Oid funcid) } /* - * - Brief: determine if given funcid reflects a dn-stable function - * - Parameter: - * @funcid: function oid that to evaluate - * - Return: - * @result: true:dnstable false: not-dnstable function + * - 简介:确定给定的函数是否反映了一个非稳定函数 + * - 参数: + * @funcid: 要求值的函数oid + * - 返回: + * @result: true:不稳定的 false: 不稳定的函数 */ bool redis_func_dnstable(Oid funcid) { @@ -981,11 +981,11 @@ bool redis_func_dnstable(Oid funcid) errmsg("function with OID %u does not exist when checking function dnstable", funcid))); } - /* Fetch function signatures */ + /* 获取函数签名 */ rettype = get_func_signature(funcid, &argstype, &nargs); if (redis_tupleid_retrive_function(func_name, rettype, argstype, nargs)) { - /* tupleid retrive functions is dnstable */ + /* 管状反射函数是不稳定的 */ result = true; } @@ -993,23 +993,23 @@ bool redis_func_dnstable(Oid funcid) } /* - * - Brief: evaluate ctid functions into a const value to avoid per-scanning - * tuple invokation in seqscan. - * - Parameter: - * @rel: the rel being redistributing - * @original_quals: the original quals possible contains ctid_funcs - * @isRangeScanInRedis: if is a redis range scan - * - Return: - * @new_quals: quals which func call be replaced by a const + * - 简介:将ctid函数求值为const值以避免每次扫描 + * 在seqscan中调用元组。 + * - 参数: + * @rel: 真正的问题是再分配 + * @original_quals: 原始的quals可能包含ctid_funcs + * @isRangeScanInRedis: 这是一个redis范围扫描 + * - 返回: + * @new_quals: 函数调用的Quals将被const替换 */ List* eval_ctid_funcs(Relation rel, List* original_quals, RangeScanInRedis *rangeScanInRedis) { StringInfo qual_str = makeStringInfo(); /* - * we have to make a copy of the original quals, since the eval_dnstable_func_mutator - * will modify the it. the original qual will be needed again and again in later - * to be re-eval in partition table scans. + * 由于eval_dnstable_func_mutator的存在,我们必须对原始的quals进行复制 + * 将修改它。在以后的时间里,将会一次又一次地需要原始的质量 + * 要在分区表扫描中重新计算。 */ List* new_quals = (List*)copyObject((const void*)(original_quals)); @@ -1033,16 +1033,16 @@ static int32 get_expr_const_val(Node *val){ } /* - * - Brief: working house for eval_dnstable_func() to evaluate dn stable function into a const - * value to avoid per-scanning tuple invocation in seqscan - * - Parameter: - * @rel: the rel being redistributing - * @node: expression node - * @qual_str: predicate pattern - * @isRangeScanInRedis: output to indicate if the predicate pattern is range scan in redis - * @isRoot: we want to compare the predicate pattern only once at root level - * - Return: - * @result: expression tree with dn stable function const-evaluated + * - 简介:eval_dnstable_func()的工作库,用于将一个稳定函数求值为const + * 值以避免在seqscan中调用每次扫描的元组 + * - 参数: + * @rel: 真正的问题是再分配 + * @node: 表达式节点 + * @qual_str: 谓词模式 + * @isRangeScanInRedis: 输出以指示谓词模式是否为redis中的范围扫描 + * @isRoot: 我们只想在根级别对谓词模式进行一次比较 + * - 返回: + * @result: 表达式树与dn稳定函数const评估 */ static Node* eval_dnstable_func_mutator( Relation rel, Node* node, StringInfo qual_str, RangeScanInRedis *rangeScanInRedis, bool isRoot) @@ -1057,7 +1057,7 @@ static Node* eval_dnstable_func_mutator( case T_FuncExpr: { FuncExpr* expr = (FuncExpr*)node; - /* flatten dn stable function into const value */ + /* 将一个稳定函数扁平化为const值 */ if (redis_func_dnstable(expr->funcid)) { Node* new_const = NULL; char* funcname = get_func_name(expr->funcid); @@ -1093,8 +1093,8 @@ static Node* eval_dnstable_func_mutator( Node* new_expr = eval_dnstable_func_mutator(rel, expr, qual_str, rangeScanInRedis, false); /* - * If a FuncExpr node is evalated into a T_Const value, we are hitting - * the point so replace it in qual list. + * 如果将FuncExpr节点求值为T_Const值,则为命中 + * 将点替换为等号列表。 */ if (expr && IsA(expr, FuncExpr) && new_expr && IsA(new_expr, Const)) { l = list_delete_ptr(l, expr); @@ -1103,8 +1103,8 @@ static Node* eval_dnstable_func_mutator( } /* - * If the predicate at root is something like "where ctid between pg_get_redis_rel_start_ctid('xx') - * and pg_get_redis_rel_end_ctid('xx')" on DN, we will pushdown the predicate at scan node. + * 如果在根的谓词类似于“where ctid between pg_get_redis_rel_start_ctid('xx')” + * 和pg_get_redis_rel_end_ctid('xx')"在DN上,我们将在扫描节点下推谓词。 */ if (isRoot && pg_strcasecmp(qual_str->data, RANGE_SCAN_IN_REDIS) == 0) { rangeScanInRedis->isRangeScanInRedis = true; @@ -1129,7 +1129,7 @@ static Node* eval_dnstable_func_mutator( } case T_Var: { Var* var = (Var*)node; - /* we only expect tid column in the predicate */ + /* 我们只期望谓词中有tid列 */ if (var->vartype == TIDOID) { appendStringInfoString(qual_str, "tid"); appendStringInfoString(qual_str, "+"); @@ -1147,10 +1147,10 @@ static Node* eval_dnstable_func_mutator( } /* - * - Brief: get and open new_table rel - * - Parameter: - * @rel: target relation of TRUNCATE operation - * - Return: + * - 简介:获取并打开new_table rel + * - 参数: + * @rel: TRUNCATE操作的目标关系 + * - 返回: * new_table rel */ Relation GetAndOpenNewTableRel(const Relation rel, LOCKMODE lockmode) @@ -1168,7 +1168,7 @@ Relation GetAndOpenNewTableRel(const Relation rel, LOCKMODE lockmode) data_redis_namespace = get_namespace_oid("data_redis", false); newtable_relid = get_relname_relid(new_tablename, data_redis_namespace); if (!OidIsValid(newtable_relid)) { - /* ERROR case, should never come here */ + /* 错误情况下,不应该出现在这里 */ ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("new table %s is not found when do cluster resizing table \"%s\"", @@ -1185,18 +1185,18 @@ Relation GetAndOpenNewTableRel(const Relation rel, LOCKMODE lockmode) } /* - * - Brief: get the name of new table - * - Parameter: - * @relname: name of target table - * @newtable_name: output value for new table name - * - Return: - * no return value + * - 简介:获得新表的名称 + * - 参数: + * @relname: 目标表名 + * @newtable_name: 新表名的输出值 + * - 返回: + * 无返回值 */ void RelationGetNewTableName(Relation rel, char* newtable_name) { int rc = 0; - /* Check if output parameter it not palloc()-ed from caller side */ + /* 检查输出参数是否没有从调用方palloc()-ed */ if (newtable_name == NULL || rel == NULL) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -1204,8 +1204,8 @@ void RelationGetNewTableName(Relation rel, char* newtable_name) } /* - * Look up relaion's reloptions to get table's cnoid to - * form the name of new table + * 查找关系的关联以获得表的关联 + * 形成新表的名称 */ if (!IsInitdb) { Oid rel_cn_oid = RelationGetRelCnOid(rel); @@ -1216,19 +1216,19 @@ void RelationGetNewTableName(Relation rel, char* newtable_name) rc = snprintf_s( newtable_name, NAMEDATALEN, NAMEDATALEN - 1, "data_redis_tmp_%s", RelationGetRelationName(rel)); } - /* check the return value of security function */ + /* 检查安全函数的返回值 */ securec_check_ss(rc, "\0", "\0"); } return; } /* - * - Brief: Determine if the relation is under cluster resizing write error mode - * - Parameter: - * @rel: relation that needs to check - * - Return: - * @TRUE: relation is under cluster resizing write error mode - * @FALSE: relation is not under cluster resizing write error mode + * - 简介:确定关系是否处于群集调整大小写错误模式 + * - 参数: + * @rel: 需要检查的关系 + * - 参数: + * @TRUE: 关系处于群集调整大小写错误模式 + * @FALSE: 关系不在群集调整大小写错误模式下 */ bool RelationInClusterResizingWriteErrorMode(const Relation rel) { -- 2.34.1 From 44f750e0188c6101a8901fa6e9fa85d22bb0c3b2 Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Thu, 10 Aug 2023 21:14:09 +0800 Subject: [PATCH 07/31] Update execCurrent.cpp --- .../runtime/executor/execCurrent.cpp | 97 ++++++++----------- 1 file changed, 41 insertions(+), 56 deletions(-) diff --git a/src/gausskernel/runtime/executor/execCurrent.cpp b/src/gausskernel/runtime/executor/execCurrent.cpp index 9e49bdc14..e44e7c0cf 100644 --- a/src/gausskernel/runtime/executor/execCurrent.cpp +++ b/src/gausskernel/runtime/executor/execCurrent.cpp @@ -1,13 +1,13 @@ /* ------------------------------------------------------------------------- * * execCurrent.c - * executor support for WHERE CURRENT OF cursor + * 执行程序支持WHERE CURRENT OF游标执行程序支持WHERE CURRENT OF游标 * - * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California + * 华为技术有限公司版权所有 + * 部分版权所有(c) 1996-2012, PostgreSQL全球发展集团 + * 版权所有(c) 1994,加州大学董事会 * - * IDENTIFICATION + * 识别 * src/backend/executor/execCurrent.c * * ------------------------------------------------------------------------- @@ -38,14 +38,12 @@ static ScanState* search_plan_tree(PlanState *node, Oid table_oid); /* * execCurrentOf * - * Given a CURRENT OF expression and the OID of a table, determine which row - * of the table is currently being scanned by the cursor named by CURRENT OF, - * and return the row's TID into *current_tid. + * 给定CURRENT OF表达式和表的OID,确定哪一行 + * 当前正在被名为CURRENT of的游标扫描 + * 并返回该行的TID为*current_tid。 * - * Returns TRUE if a row was identified. Returns FALSE if the cursor is valid - * for the table but is not currently scanning a row of the table (this is a - * legal situation in inheritance cases). Raises error if cursor is not a - * valid updatable scan of the specified table. + * 如果一行被识别,则返回TRUE。如果游标有效,则返回FALSE + * 但是当前没有扫描表的一行(这是继承情况下的合法情况)。如果游标不是指定表的有效可更新扫描,则引发错误。 */ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relation, ItemPointer current_tid, RelationPtr partitionOfCursor_tid) @@ -55,14 +53,14 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio QueryDesc *query_desc = NULL; Oid table_oid = RelationGetRelid(relation); - /* Get the cursor name --- may have to look up a parameter reference */ + /* 获取游标名称——可能需要查找参数引用 */ if (cexpr->cursor_name) { cursor_name = cexpr->cursor_name; } else { cursor_name = fetch_cursor_param_value(econtext, cexpr->cursor_param); } - /* Find the cursor's portal */ + /* 找到游标的入口 */ portal = GetPortalByName(cursor_name); if (!PortalIsValid(portal)) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_CURSOR), @@ -70,8 +68,7 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio } /* - * We have to watch out for non-SELECT queries as well as held cursors, - * both of which may have null query_desc. + * 我们必须注意非select查询和持有的游标,它们的query_desc都可能为空 */ if (portal->strategy != PORTAL_ONE_SELECT) { ereport(ERROR, @@ -85,26 +82,23 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio } /* - * We have two different strategies depending on whether the cursor uses - * FOR UPDATE/SHARE or not. The reason for supporting both is that the - * FOR UPDATE code is able to identify a target table in many cases where - * the other code can't, while the non-FOR-UPDATE case allows use of WHERE - * CURRENT OF with an insensitive cursor. + * 根据游标是否使用,我们有两种不同的策略 + * 是否更新/共享。支持两者的原因是 + * 在许多情况下,FOR UPDATE代码能够识别目标表,而其他代码不能,而非FOR-UPDATE情况允许使用不敏感游标的when CURRENT of。 */ if (query_desc->estate->es_rowMarks) { ExecRowMark *erm = NULL; ListCell *lc = NULL; /* - * Here, the query must have exactly one FOR UPDATE/SHARE reference to - * the target table, and we dig the ctid info out of that. + * 这里,查询必须只有一个对目标表的FOR UPDATE/SHARE引用,我们从中挖掘出ctid信息。 */ erm = NULL; foreach (lc, query_desc->estate->es_rowMarks) { ExecRowMark *thiserm = (ExecRowMark *)lfirst(lc); if (!RowMarkRequiresRowShareLock(thiserm->markType)) { - continue; /* ignore non-FOR UPDATE/SHARE items */ + continue; /* 忽略非for UPDATE/SHARE项 */ } if (RelationGetRelid(thiserm->relation) == table_oid) { @@ -124,15 +118,14 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio } /* - * The cursor must have a current result row: per the SQL spec, it's - * an error if not. + * 游标必须有当前结果行:根据SQL规范,如果没有,则会出现错误。 */ if (portal->atStart || portal->atEnd) { ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("cursor \"%s\" is not positioned on a row when the cursor uses for UPDATE/SHARE", cursor_name))); } - /* Return the currently scanned TID, if there is one */ + /* 返回当前扫描的TID(如果有) */ if (ItemPointerIsValid(&(erm->curCtid))) { *current_tid = erm->curCtid; @@ -144,9 +137,7 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio } /* - * This table didn't produce the cursor's current row; some other - * inheritance child of the same parent must have. Signal caller to - * do nothing on this table. + * 这个表没有产生游标的当前行;必须有同一父节点的其他继承子节点。信号调用者在表上什么都不做。 */ return false; } else { @@ -156,9 +147,7 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio ItemPointer tuple_tid; /* - * Without FOR UPDATE, we dig through the cursor's plan to find the - * scan node. Fail if it's not there or buried underneath - * aggregation. + * 如果没有FOR UPDATE,我们将通过游标的计划来查找扫描节点。如果不存在或隐藏在聚合下面,则失败。 */ scanstate = search_plan_tree(query_desc->planstate, table_oid); if (scanstate == NULL) { @@ -168,23 +157,21 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio } /* - * The cursor must have a current result row: per the SQL spec, it's - * an error if not. We test this at the top level, rather than at the - * scan node level, because in inheritance cases any one table scan - * could easily not be on a row. We want to return false, not raise - * error, if the passed-in table OID is for one of the inactive scans. + * 游标必须有当前结果行:根据SQL规范,如果没有,则会出现错误。 + * 我们在顶层测试,而不是在扫描节点级测试,因为在继承情况下,任何一个表扫描都很容易不在一行上。 + * 如果传入的表OID是用于非活动扫描的,我们希望返回false,而不是引发错误。 */ if (portal->atStart || portal->atEnd) { ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg( "cursor \"%s\" is not positioned on a row when the cursor doesn't use for UPDATE/SHARE", cursor_name))); } - /* Now OK to return false if we found an inactive scan */ + /* 现在OK返回false如果我们发现一个非活动扫描 */ if (TupIsNull(scanstate->ss_ScanTupleSlot)) { return false; } - /* Use slot_getattr to catch any possible mistakes */ + /* 使用slot_getattr捕获任何可能的错误 */ tuple_tableoid = DatumGetObjectId(tableam_tslot_getattr(scanstate->ss_ScanTupleSlot, TableOidAttributeNumber, &lisnull)); Assert(!lisnull); tuple_tid = (ItemPointer)DatumGetPointer( @@ -206,7 +193,7 @@ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Relation relatio /* * fetch_cursor_param_value * - * Fetch the string value of a param, verifying it is of type REFCURSOR. + * 获取参数的字符串值,验证它是REFCURSOR类型。. */ static char *fetch_cursor_param_value(ExprContext *econtext, int paramId) { @@ -215,20 +202,20 @@ static char *fetch_cursor_param_value(ExprContext *econtext, int paramId) if (paramInfo && paramId > 0 && paramId <= paramInfo->numParams) { ParamExternData *prm = ¶mInfo->params[paramId - 1]; - /* give hook a chance in case parameter is dynamic */ + /* 如果参数是动态的,给钩子一个机会 */ if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL) { (*paramInfo->paramFetch)(paramInfo, paramId); } if (OidIsValid(prm->ptype) && !prm->isnull) { - /* safety check in case hook did something unexpected */ + /* 安全检查,以防钩子发生意外 */ if (prm->ptype != REFCURSOROID) { ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)", paramId, format_type_be(prm->ptype), format_type_be(REFCURSOROID)))); } - /* We know that refcursor uses text's I/O routines */ + /* 我们知道refcursor使用text的I/O例程 */ return TextDatumGetCString(prm->value); } } @@ -240,8 +227,8 @@ static char *fetch_cursor_param_value(ExprContext *econtext, int paramId) /* * search_plan_tree * - * Search through a PlanState tree for a scan node on the specified table. - * Return NULL if not found or multiple candidates. + * 在PlanState树中搜索指定表上的扫描节点。 + * 如果没有找到或有多个候选,则返回NULL。 */ #ifdef PGXC ScanState* search_plan_tree(PlanState* node, Oid table_oid) @@ -262,7 +249,7 @@ static ScanState* search_plan_tree(PlanState* node, Oid table_oid) } #endif /* - * scan nodes can all be treated alike + * 扫描节点都可以被同等对待 */ case T_SeqScanState: case T_IndexScanState: @@ -284,8 +271,7 @@ static ScanState* search_plan_tree(PlanState* node, Oid table_oid) return result; } /* - * For Append, we must look through the members; watch out for - * multiple matches (possible if it was from UNION ALL) + * 对于Append,必须遍历成员;注意多个匹配(可能来自UNION ALL) */ case T_AppendState: { AppendState *astate = (AppendState *)node; @@ -297,14 +283,14 @@ static ScanState* search_plan_tree(PlanState* node, Oid table_oid) if (elem == NULL) continue; if (result != NULL) - return NULL; /* multiple matches */ + return NULL; /* 多个匹配 */ result = elem; } return result; } /* - * Similarly for MergeAppend + * 类似于MergeAppend */ case T_MergeAppendState: { MergeAppendState *mstate = (MergeAppendState *)node; @@ -318,15 +304,14 @@ static ScanState* search_plan_tree(PlanState* node, Oid table_oid) continue; } if (result != NULL) { - return NULL; /* multiple matches */ + return NULL; /* 多个匹配 */ } result = elem; } return result; } /* - * Result and Limit can be descended through (these are safe - * because they always return their input's current row) + * Result和Limit可以依次下降(它们是安全的,因为它们总是返回输入的当前行) */ #ifdef PGXC case T_MaterialState: @@ -337,13 +322,13 @@ static ScanState* search_plan_tree(PlanState* node, Oid table_oid) return search_plan_tree(node->lefttree, table_oid); /* - * SubqueryScan too, but it keeps the child in a different place + * SubqueryScan也可以,但它将子对象保存在不同的位置 */ case T_SubqueryScanState: return search_plan_tree(((SubqueryScanState *)node)->subplan, table_oid); default: - /* Otherwise, assume we can't descend through it */ + /* 否则,假设我们不能从里面下去 */ break; } return NULL; -- 2.34.1 From 3db7a144362564d94004f670a8db8d7d9be0f446 Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Thu, 10 Aug 2023 22:15:08 +0800 Subject: [PATCH 08/31] Update execGrouping.cpp --- .../runtime/executor/execGrouping.cpp | 242 ++++++++---------- 1 file changed, 106 insertions(+), 136 deletions(-) diff --git a/src/gausskernel/runtime/executor/execGrouping.cpp b/src/gausskernel/runtime/executor/execGrouping.cpp index 505986cfe..5ab87c2a2 100644 --- a/src/gausskernel/runtime/executor/execGrouping.cpp +++ b/src/gausskernel/runtime/executor/execGrouping.cpp @@ -1,15 +1,13 @@ /* ------------------------------------------------------------------------- * * execGrouping.cpp - * executor utility routines for grouping, hashing, and aggregation + * 用于分组、散列和聚合的执行程序实用程序例程 * - * Note: we currently assume that equality and hashing functions are not - * collation-sensitive, so the code in this file has no support for passing - * collation settings through from callers. That may have to change someday. + * 注意:我们目前假设相等和散列函数对排序规则不敏感,因此此文件中的代码不支持从调用者传递排序规则设置。这种情况有一天可能会改变。 * - * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California + * 华为技术有限公司版权所有 + * 部分版权所有(c) 1996-2012, PostgreSQL全球发展集团 + * 版权所有(c) 1994,加州大学董事会 * * * IDENTIFICATION @@ -31,22 +29,21 @@ static uint32 TupleHashTableHash(const void* key, Size keysize); static int TupleHashTableMatch(const void* key1, const void* key2, Size keysize); /***************************************************************************** - * Utility routines for grouping tuples together + * 将元组分组在一起的实用程序例程 *****************************************************************************/ /* * execTuplesMatch - * Return true if two tuples match in all the indicated fields. + * 如果两个元组在所有指定字段中匹配,则返回true。 * - * This actually implements SQL's notion of "not distinct". Two nulls - * match, a null and a not-null don't match. + * 这实际上实现了SQL的“不区分”概念。两个空匹配,一个空和一个非空不匹配。 * - * slot1, slot2: the tuples to compare (must have same columns!) - * numCols: the number of attributes to be examined - * matchColIdx: array of attribute column numbers - * eqFunctions: array of fmgr lookup info for the equality functions to use - * evalContext: short-term memory context for executing the functions + * slot1, slot2:要比较的元组(必须有相同的列!) + * numCols:要检查的属性数量 + * matchColIdx:属性列号的数组 + * eqFunctions:为相等函数使用的fmgr查找信息的数组 + * evalContext:用于执行函数的短期内存上下文 * - * NB: evalContext is reset each time! + * NB: 每次都重置evalContext ! */ bool execTuplesMatch(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols, AttrNumber* matchColIdx, FmgrInfo* eqfunctions, MemoryContext evalContext) @@ -55,15 +52,15 @@ bool execTuplesMatch(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols, bool result = false; int i; - /* Reset and switch into the temp context. */ + /* 重置并切换到temp上下文。 */ MemoryContextReset(evalContext); oldContext = MemoryContextSwitchTo(evalContext); /* - * We cannot report a match without checking all the fields, but we can - * report a non-match as soon as we find unequal fields. So, start - * comparing at the last field (least significant sort key). That's the - * most likely to be different if we are dealing with sorted input. + * 如果不检查所有字段,我们就不能报告匹配, + * 但是一旦发现不相等的字段,我们就可以报告不匹配。 + * 因此,从最后一个字段(最不重要的排序键)开始比较。 + * 这是最有可能不同的如果我们处理的是排序输入。 */ result = true; @@ -78,17 +75,17 @@ bool execTuplesMatch(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols, attr2 = tableam_tslot_getattr(slot2, att, &isNull2); if (isNull1 != isNull2) { - result = false; /* one null and one not; they aren't equal */ + result = false; /* 一个null,一个not;它们是不相等的 */ break; } if (isNull1) { - continue; /* both are null, treat as equal */ + continue; /* 两者都为空,同等对待 */ } - /* Apply the type-specific equality function */ + /* 应用特定于类型的相等函数 */ if (!DatumGetBool(FunctionCall2(&eqfunctions[i], attr1, attr2))) { - result = false; /* they aren't equal */ + result = false; /* 它们是不相等的 */ break; } } @@ -100,13 +97,11 @@ bool execTuplesMatch(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols, /* * execTuplesUnequal - * Return true if two tuples are definitely unequal in the indicated - * fields. + * 如果两个元组在指定字段中绝对不相等,则返回true。 * - * Nulls are neither equal nor unequal to anything else. A true result - * is obtained only if there are non-null fields that compare not-equal. + * null既不等于也不等于其他任何东西。只有当存在比较not-equal的非空字段时,才能获得真结果。 * - * Parameters are identical to execTuplesMatch. + * 参数与execTuplesMatch相同。 */ bool execTuplesUnequal(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols, AttrNumber* matchColIdx, FmgrInfo* eqfunctions, MemoryContext evalContext) @@ -117,15 +112,14 @@ bool execTuplesUnequal(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols Assert(slot1->tts_tupleDescriptor->tdTableAmType == slot2->tts_tupleDescriptor->tdTableAmType); - /* Reset and switch into the temp context. */ + /* 重置并切换到temp上下文 */ MemoryContextReset(evalContext); oldContext = MemoryContextSwitchTo(evalContext); /* - * We cannot report a match without checking all the fields, but we can - * report a non-match as soon as we find unequal fields. So, start - * comparing at the last field (least significant sort key). That's the - * most likely to be different if we are dealing with sorted input. + * 如果不检查所有字段,我们就不能报告匹配,但是一旦发现不相等的字段,我们就可以报告不匹配。 + * 因此,从最后一个字段(最不重要的排序键)开始比较。 + * 这是最有可能不同的如果我们处理的是排序输入。 */ result = false; @@ -138,18 +132,18 @@ bool execTuplesUnequal(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols attr1 = tableam_tslot_getattr(slot1, att, &isNull1); if (isNull1) { - continue; /* can't prove anything here */ + continue; /* 不能证明什么 */ } attr2 = tableam_tslot_getattr(slot2, att, &isNull2); if (isNull2) { - continue; /* can't prove anything here */ + continue; /* 不能证明什么 */ } - /* Apply the type-specific equality function */ + /* 应用特定于类型的相等函数 */ if (!DatumGetBool(FunctionCall2(&eqfunctions[i], attr1, attr2))) { - result = true; /* they are unequal */ + result = true; /* 它们是不相等的 */ break; } } @@ -161,10 +155,9 @@ bool execTuplesUnequal(TupleTableSlot* slot1, TupleTableSlot* slot2, int numCols /* * execTuplesMatchPrepare - * Look up the equality functions needed for execTuplesMatch or - * execTuplesUnequal, given an array of equality operator OIDs. + * 给定一个相等操作符oid数组,查找execTuplesMatch或exectuplesinequality所需的相等函数。 * - * The result is a palloc'd array. + * 结果是一个lolod数组。 */ FmgrInfo* execTuplesMatchPrepare(int numCols, Oid* eqOperators) { @@ -184,13 +177,12 @@ FmgrInfo* execTuplesMatchPrepare(int numCols, Oid* eqOperators) /* * execTuplesHashPrepare - * Look up the equality and hashing functions needed for a TupleHashTable. + * 查找TupleHashTable所需的相等和散列函数。 * - * This is similar to execTuplesMatchPrepare, but we also need to find the - * hash functions associated with the equality operators. *eqFunctions and - * *hashFunctions receive the palloc'd result arrays. + * 这类似于execTuplesMatchPrepare,但我们还需要找到与相等操作符相关的散列函数。 + * *eqFunctions和*hashFunctions接收palloc结果数组。 * - * Note: we expect that the given operators are not cross-type comparisons. + * 注意:我们期望给定的操作符不是跨类型比较。 */ void execTuplesHashPrepare(int numCols, Oid* eqOperators, FmgrInfo** eqFunctions, FmgrInfo** hashFunctions) { @@ -216,7 +208,7 @@ void execTuplesHashPrepare(int numCols, Oid* eqOperators, FmgrInfo** eqFunctions i, numCols))); - /* We're not supporting cross-type cases here */ + /* 我们不支持交叉类型的情况 */ Assert(left_hash_function == right_hash_function); fmgr_info(eq_function, &(*eqFunctions)[i]); fmgr_info(right_hash_function, &(*hashFunctions)[i]); @@ -224,29 +216,23 @@ void execTuplesHashPrepare(int numCols, Oid* eqOperators, FmgrInfo** eqFunctions } /***************************************************************************** - * Utility routines for all-in-memory hash tables + * 全内存哈希表的实用程序例程 * - * These routines build hash tables for grouping tuples together (eg, for - * hash aggregation). There is one entry for each not-distinct set of tuples - * presented. + * 这些例程构建哈希表,将元组分组在一起(例如,用于哈希聚合)。 + * 对于所呈现的每个不明显的元组集合,都有一个条目。 *****************************************************************************/ /* - * Construct an empty TupleHashTable + * 构造一个空的TupleHashTable * - * numCols, keyColIdx: identify the tuple fields to use as lookup key - * eqfunctions: equality comparison functions to use - * hashfunctions: datatype-specific hashing functions to use - * nbuckets: initial estimate of hashtable size - * entrysize: size of each entry (at least sizeof(TupleHashEntryData)) - * tablecxt: memory context in which to store table and table entries - * tempcxt: short-lived context for evaluation hash and comparison functions + * numCols, keyColIdx:确定元组字段作为查找键使用eqfunctions: + * 相等比较函数使用hashfunctions:特定于数据类型的哈希函数使用nbuckets: + * 哈希表大小的初始估计entrysize:每个表项的大小(至少sizeof(TupleHashEntryData)) + * tablext:存储表和表项的内存上下文tempcxt:评估哈希和比较函数的短期上下文 * - * The function arrays may be made with execTuplesHashPrepare(). Note they - * are not cross-type functions, but expect to see the table datatype(s) - * on both sides. + * 函数数组可以用execTuplesHashPrepare()创建。 + * 注意,它们不是跨类型函数,但期望在两边看到表数据类型。 * - * Note that keyColIdx, eqfunctions, and hashfunctions must be allocated in - * storage that will live as long as the hashtable does. + * 请注意,keyColIdx、eqfunctions和hashfunctions必须分配到与散列表存在时间一样长的存储中。 */ TupleHashTable BuildTupleHashTable(int numCols, AttrNumber* keyColIdx, FmgrInfo* eqfunctions, FmgrInfo* hashfunctions, long nbuckets, Size entrysize, MemoryContext tablecxt, MemoryContext tempcxt, int workMem) @@ -257,7 +243,7 @@ TupleHashTable BuildTupleHashTable(int numCols, AttrNumber* keyColIdx, FmgrInfo* Assert(nbuckets > 0); Assert(entrysize >= sizeof(TupleHashEntryData)); - /* Limit initial table size request to not more than work_mem */ + /* 限制初始表大小请求不超过work_mem */ nbuckets = Min(nbuckets, (long)((workMem * 1024L) / entrysize)); if (u_sess->attr.attr_sql.hashagg_table_size != 0) nbuckets = Min(nbuckets, u_sess->attr.attr_sql.hashagg_table_size); @@ -271,7 +257,7 @@ TupleHashTable BuildTupleHashTable(int numCols, AttrNumber* keyColIdx, FmgrInfo* hashtable->tablecxt = tablecxt; hashtable->tempcxt = tempcxt; hashtable->entrysize = entrysize; - hashtable->tableslot = NULL; /* will be made on first lookup */ + hashtable->tableslot = NULL; /* 将在第一次查找时进行 */ hashtable->inputslot = NULL; hashtable->in_hash_funcs = NULL; hashtable->cur_eq_funcs = NULL; @@ -293,20 +279,16 @@ TupleHashTable BuildTupleHashTable(int numCols, AttrNumber* keyColIdx, FmgrInfo* } /* - * Find or create a hashtable entry for the tuple group containing the - * given tuple. The tuple must be the same type as the hashtable entries. + * 查找或创建包含给定元组的元组的哈希表项。元组必须与哈希表项的类型相同。 * - * If isnew is NULL, we do not create new entries; we return NULL if no - * match is found. + * 如果isnew为NULL,则不创建新表项;如果没有找到匹配,则返回NULL。 * - * If isnew isn't NULL, then a new entry is created if no existing entry - * matches. On return, *isnew is true if the entry is newly created, - * false if it existed already. Any extra space in a new entry has been - * zeroed. + * 如果isnew不为NULL,则在没有现有条目匹配的情况下创建新条目。 + * 返回时,如果条目是新创建的,*isnew为true, + * 如果条目已经存在,则为false。新条目中的任何额外空间都被归零。 * - * If isinserthashtbl is false, the para of hash search is HASH_FIND - * instead of HASH_ENTER. This slot will be insert into temp file instead of - * hash table if it is new + * 如果isinserthashtbl为false,则哈希搜索的参数为HASH_FIND,而不是HASH_ENTER。 + * 如果这个槽是新的,它将被插入到临时文件中,而不是哈希表中 * */ TupleHashEntry LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot* slot, bool* isnew, bool isinserthashtbl) @@ -317,29 +299,27 @@ TupleHashEntry LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot* sl TupleHashEntryData dummy; bool found = false; - /* If first time through, clone the input slot to make table slot */ + /* 如果第一次通过,克隆输入槽来制作表槽 */ if (hashtable->tableslot == NULL) { TupleDesc tupdesc; oldContext = MemoryContextSwitchTo(hashtable->tablecxt); /* - * We copy the input tuple descriptor just for safety --- we assume - * all input tuples will have equivalent descriptors. + * 为了安全起见,我们复制了输入元组描述符——我们假设所有输入元组都具有相同的描述符。 */ tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor); hashtable->tableslot = MakeSingleTupleTableSlot(tupdesc); MemoryContextSwitchTo(oldContext); } - /* Need to run the hash functions in short-lived context */ + /* 需要在短期上下文中运行哈希函数 */ oldContext = MemoryContextSwitchTo(hashtable->tempcxt); /* - * Set up data needed by hash and match functions + * 设置哈希和匹配函数所需的数据 * - * We save and restore u_sess->exec_cxt.cur_tuple_hash_table just in case someone manages to - * invoke this code re-entrantly. + * 我们保存并恢复u_sess-> exec_ext。Cur_tuple_hash_table,以防有人设法重新调用这段代码。 */ hashtable->inputslot = slot; hashtable->in_hash_funcs = hashtable->tab_hash_funcs; @@ -348,34 +328,33 @@ TupleHashEntry LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot* sl saveCurHT = u_sess->exec_cxt.cur_tuple_hash_table; u_sess->exec_cxt.cur_tuple_hash_table = hashtable; - /* Search the hash table */ - dummy.firstTuple = NULL; /* flag to reference inputslot */ + /* 搜索哈希表 */ + dummy.firstTuple = NULL; /* 引用输入槽的标志 */ if (isinserthashtbl) { entry = (TupleHashEntry)hash_search(hashtable->hashtab, &dummy, isnew ? HASH_ENTER : HASH_FIND, &found); } else { - /* this slot will be insert into temp file instead of hash table if it is not found in hash table */ + /* 如果在哈希表中没有找到该槽位,则将其插入临时文件而不是哈希表中 */ entry = (TupleHashEntry)hash_search(hashtable->hashtab, &dummy, HASH_FIND, &found); } if (isnew != NULL) { if (found) { - /* found pre-existing entry */ + /* 发现已有条目 */ *isnew = false; } else { if (entry) { Assert(isinserthashtbl); /* - * created new entry + * 创建新条目 * - * Zero any caller-requested space in the entry. (This zaps the - * "key data" dynahash.c copied into the new entry, but we don't - * care since we're about to overwrite it anyway.) + * 条目中所有调用者请求的空间为零。 + * (这会将“关键数据”dynahash.c复制到新条目中,但我们并不关心,因为我们无论如何都要覆盖它。) */ errno_t errorno = memset_s(entry, hashtable->entrysize, 0, hashtable->entrysize); securec_check(errorno, "\0", "\0"); - /* Copy the first tuple into the table context */ + /* 将第一个元组复制到表上下文中 */ MemoryContextSwitchTo(hashtable->tablecxt); entry->firstTuple = ExecCopySlotMinimalTuple(slot); if (hashtable->add_width) @@ -394,13 +373,9 @@ TupleHashEntry LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot* sl } /* - * Search for a hashtable entry matching the given tuple. No entry is - * created if there's not a match. This is similar to the non-creating - * case of LookupTupleHashEntry, except that it supports cross-type - * comparisons, in which the given tuple is not of the same type as the - * table entries. The caller must provide the hash functions to use for - * the input tuple, as well as the equality functions, since these may be - * different from the table's internal functions. + * 搜索与给定元组匹配的散列表项。如果不匹配,则不创建条目。 + * 这类似于LookupTupleHashEntry的非创建情况,只是它支持跨类型比较,在这种比较中,给定的元组与表项的类型不同。 + * 调用者必须提供用于输入元组的散列函数以及相等函数,因为这些函数可能不同于表的内部函数。 */ TupleHashEntry FindTupleHashEntry( TupleHashTable hashtable, TupleTableSlot* slot, FmgrInfo* eqfunctions, FmgrInfo* hashfunctions) @@ -410,14 +385,13 @@ TupleHashEntry FindTupleHashEntry( TupleHashTable saveCurHT; TupleHashEntryData dummy; - /* Need to run the hash functions in short-lived context */ + /* 需要在短期上下文中运行哈希函数 */ oldContext = MemoryContextSwitchTo(hashtable->tempcxt); /* - * Set up data needed by hash and match functions + * 设置哈希和匹配函数所需的数据 * - * We save and restore u_sess->exec_cxt.cur_tuple_hash_table just in case someone manages to - * invoke this code re-entrantly. + * 我们保存并恢复u_sess-> exec_ext。Cur_tuple_hash_table,以防有人设法重新调用这段代码。 */ hashtable->inputslot = slot; hashtable->in_hash_funcs = hashfunctions; @@ -426,8 +400,8 @@ TupleHashEntry FindTupleHashEntry( saveCurHT = u_sess->exec_cxt.cur_tuple_hash_table; u_sess->exec_cxt.cur_tuple_hash_table = hashtable; - /* Search the hash table */ - dummy.firstTuple = NULL; /* flag to reference inputslot */ + /* 搜索哈希表 */ + dummy.firstTuple = NULL; /* 引用输入槽的标志 */ entry = (TupleHashEntry)hash_search(hashtable->hashtab, &dummy, HASH_FIND, NULL); u_sess->exec_cxt.cur_tuple_hash_table = saveCurHT; @@ -438,20 +412,19 @@ TupleHashEntry FindTupleHashEntry( } /* - * Compute the hash value for a tuple + * 计算元组的哈希值 * - * The passed-in key is a pointer to TupleHashEntryData. In an actual hash - * table entry, the firstTuple field points to a tuple (in MinimalTuple - * format). LookupTupleHashEntry sets up a dummy TupleHashEntryData with a - * NULL firstTuple field --- that cues us to look at the inputslot instead. - * This convention avoids the need to materialize virtual input tuples unless - * they actually need to get copied into the table. + * 传入的键是一个指向TupleHashEntryData的指针。 + * 在实际的哈希表条目中,第一个tuple字段指向一个元组(在MinimalTuple格式中)。 + * LookupTupleHashEntry用一个NULL firstTuple字段——这提示我们查看输入槽。 + * 这种约定避免了具体化虚拟输入元组的需要, + * 除非它们实际上需要被复制到表中。 * - * u_sess->exec_cxt.cur_tuple_hash_table must be set before calling this, since dynahash.c - * doesn't provide any API that would let us get at the hashtable otherwise. + * u_sess - > exec_cxt。cur_tuple_hash_table必须在调用它之前设置, + * 因为dynahash.c没有提供任何让我们以其他方式获取哈希表的API。 * - * Also, the caller must select an appropriate memory context for running - * the hash functions. (dynahash.c doesn't change CurrentMemoryContext.) + * 此外,调用者必须为运行散列函数选择适当的内存上下文。 + * (dynahash.c不会改变CurrentMemoryContext。) */ static uint32 TupleHashTableHash(const void* key, Size keysize) { @@ -465,28 +438,28 @@ static uint32 TupleHashTableHash(const void* key, Size keysize) int i; if (tuple == NULL) { - /* Process the current input tuple for the table */ + /* 处理表的当前输入元组 */ slot = hashtable->inputslot; hashfunctions = hashtable->in_hash_funcs; } else { - /* Process a tuple already stored in the table */ - /* (this case never actually occurs in current dynahash.c code) */ + /* 处理已经存储在表中的元组 */ + /* (这种情况在当前的dynahash.c代码中从未发生过) */ slot = hashtable->tableslot; ExecStoreMinimalTuple(tuple, slot, false); hashfunctions = hashtable->tab_hash_funcs; } - /* Get the Table Accessor Method*/ + /* 获取表访问器方法*/ for (i = 0; i < numCols; i++) { AttrNumber att = keyColIdx[i]; Datum attr; bool isNull = false; - /* rotate hashkey left 1 bit at each step */ + /* 每一步将哈希键向左旋转1位 */ hashkey = (hashkey << 1) | ((hashkey & 0x80000000) ? 1 : 0); attr = tableam_tslot_getattr(slot, att, &isNull); - /* treat nulls as having hash key 0 */ + /* 将空值视为哈希键为0 */ if (!isNull) { uint32 hkey; hkey = DatumGetUInt32(FunctionCall1(&hashfunctions[i], attr)); @@ -500,15 +473,13 @@ static uint32 TupleHashTableHash(const void* key, Size keysize) } /* - * See whether two tuples (presumably of the same hash value) match + * 查看两个元组(假设具有相同的哈希值)是否匹配 * - * As above, the passed pointers are pointers to TupleHashEntryData. + * 如上所述,传递的指针是指向TupleHashEntryData的指针。 * - * u_sess->exec_cxt.cur_tuple_hash_table must be set before calling this, since dynahash.c - * doesn't provide any API that would let us get at the hashtable otherwise. + * u_sess - > exec_cxt。cur_tuple_hash_table必须在调用它之前设置,因为dynahash.c没有提供任何让我们以其他方式获取哈希表的API * - * Also, the caller must select an appropriate memory context for running - * the compare functions. (dynahash.c doesn't change CurrentMemoryContext.) + * 此外,调用者必须为运行比较函数选择适当的内存上下文。(dynahash.c不会改变CurrentMemoryContext。) */ static int TupleHashTableMatch(const void* key1, const void* key2, Size keysize) { @@ -522,10 +493,9 @@ static int TupleHashTableMatch(const void* key1, const void* key2, Size keysize) TupleHashTable hashtable = u_sess->exec_cxt.cur_tuple_hash_table; /* - * We assume that dynahash.c will only ever call us with the first - * argument being an actual table entry, and the second argument being - * LookupTupleHashEntry's dummy TupleHashEntryData. The other direction - * could be supported too, but is not currently used by dynahash.c. + * 我们假设dynahash.c调用我们时,第一个参数是一个实际的表条目, + * 第二个参数是LookupTupleHashEntry的假TupleHashEntryData。 + * 另一个方向也可以支持,但目前没有被dynahash.c使用。 */ Assert(tuple1 != NULL); slot1 = hashtable->tableslot; @@ -533,7 +503,7 @@ static int TupleHashTableMatch(const void* key1, const void* key2, Size keysize) Assert(tuple2 == NULL); slot2 = hashtable->inputslot; - /* For crosstype comparisons, the inputslot must be first */ + /* 对于交叉类型比较,输入槽必须是第一个 */ if (execTuplesMatch( slot2, slot1, hashtable->numCols, hashtable->keyColIdx, hashtable->cur_eq_funcs, hashtable->tempcxt)) return 0; -- 2.34.1 From 39f68e8d2f47a90c7e1be6c40ed230556fbbf40e Mon Sep 17 00:00:00 2001 From: LYLlyl Date: Wed, 16 Aug 2023 20:45:28 +0800 Subject: [PATCH 09/31] Update execProcnode.cpp --- .../runtime/executor/execProcnode.cpp | 305 +++++++++--------- 1 file changed, 147 insertions(+), 158 deletions(-) diff --git a/src/gausskernel/runtime/executor/execProcnode.cpp b/src/gausskernel/runtime/executor/execProcnode.cpp index fd1cf1372..b552a1606 100755 --- a/src/gausskernel/runtime/executor/execProcnode.cpp +++ b/src/gausskernel/runtime/executor/execProcnode.cpp @@ -1,80 +1,59 @@ /* ------------------------------------------------------------------------- * * execProcnode.cpp - * contains dispatch functions which call the appropriate "initialize", - * "get a tuple", and "cleanup" routines for the given node type. - * If the node has children, then it will presumably call ExecInitNode, - * ExecProcNode, or ExecEndNode on its subnodes and do the appropriate - * processing. + * 包含调用给定节点类型的适当的 "初始化"、"获取元组" 和 "清理" 程序的调度函数。 + * 如果节点有子节点,则可能会在其子节点上调用 ExecInitNode、ExecProcNode 或 ExecEndNode,并进行适当的处理。 * - * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * Portions Copyright (c) 2021, openGauss Contributors + * 版权所有 (c) 2020 华为技术有限公司。 + * 版权所有 (c) 1996-2012 年,PostgreSQL 全球开发团队。 + * 版权所有 (c) 1994 年,加利福尼亚大学理事会。 + * 版权所有 (c) 2021 年,openGauss 贡献者。 * * - * IDENTIFICATION - * src/gausskernel/runtime/executor/execProcnode.cpp + * 标识符 + * src/gausskernel/runtime/executor/execProcnode.cpp + * * ------------------------------------------------------------------------- */ /* - * INTERFACE ROUTINES - * ExecInitNode - initialize a plan node and its subplans - * ExecProcNode - get a tuple by executing the plan node - * ExecEndNode - shut down a plan node and its subplans - * - * NOTES - * This used to be three files. It is now all combined into - * one file so that it is easier to keep ExecInitNode, ExecProcNode, - * and ExecEndNode in sync when new nodes are added. - * - * EXAMPLE - * Suppose we want the age of the manager of the shoe department and - * the number of employees in that department. So we have the query: - * - * select DEPT.no_emps, EMP.age - * where EMP.name = DEPT.mgr and - * DEPT.name = "shoe" - * - * Suppose the planner gives us the following plan: - * - * Nest Loop (DEPT.mgr = EMP.name) - * / \ - * / \ - * Seq Scan Seq Scan - * DEPT EMP - * (name = "shoe") - * - * ExecutorStart() is called first. - * It calls InitPlan() which calls ExecInitNode() on - * the root of the plan -- the nest loop node. - * - * * ExecInitNode() notices that it is looking at a nest loop and - * as the code below demonstrates, it calls ExecInitNestLoop(). - * Eventually this calls ExecInitNode() on the right and left subplans - * and so forth until the entire plan is initialized. The result - * of ExecInitNode() is a plan state tree built with the same structure - * as the underlying plan tree. - * - * * Then when ExecutorRun() is called, it calls ExecutePlan() which calls - * ExecProcNode() repeatedly on the top node of the plan state tree. - * Each time this happens, ExecProcNode() will end up calling - * ExecNestLoop(), which calls ExecProcNode() on its subplans. - * Each of these subplans is a sequential scan so ExecSeqScan() is - * called. The slots returned by ExecSeqScan() may contain - * tuples which contain the attributes ExecNestLoop() uses to - * form the tuples it returns. - * - * * Eventually ExecSeqScan() stops returning tuples and the nest - * loop join ends. Lastly, ExecutorEnd() calls ExecEndNode() which - * calls ExecEndNestLoop() which in turn calls ExecEndNode() on - * its subplans which result in ExecEndSeqScan(). - * - * This should show how the executor works by having - * ExecInitNode(), ExecProcNode() and ExecEndNode() dispatch - * their work to the appopriate node support routines which may - * in turn call these routines themselves on their subplans. +* 接口例程: +* ExecInitNode - 初始化计划节点及其子计划 +* ExecProcNode - 通过执行计划节点获取一个元组 +* ExecEndNode - 关闭计划节点及其子计划 + + 注意: +* 这曾经是三个文件。现在已经合并为一个文件,以便在添加新节点时更容易保持ExecInitNode、ExecProcNode和ExecEndNode的同步。 + +示例: +假设我们想要获取鞋部经理的年龄以及该部门的雇员人数。因此,我们有以下查询: + +``` +select DEPT.no_emps, EMP.age +where EMP.name = DEPT.mgr and + DEPT.name = "shoe" +``` + +假设规划器给了我们以下计划: + +``` +Nest Loop (DEPT.mgr = EMP.name) +/ \ +/ \ +Seq Scan Seq Scan +DEPT EMP +(name = "shoe") +``` + +首先调用ExecutorStart()。它调用InitPlan(),后者在计划的根节点(嵌套循环节点)上调用ExecInitNode()。 + +* ExecInitNode() 注意到它正在处理嵌套循环,正如下面的代码所示,它调用ExecInitNestLoop()。最终,这将在右子计划和左子计划上调用ExecInitNode(),依此类推,直到整个计划初始化完成。ExecInitNode() 的结果是一个计划状态树,其结构与底层计划树相同。 + +* 然后,当调用ExecutorRun()时,它会在计划状态树的顶部节点上反复调用ExecutePlan(),后者反复调用ExecProcNode()。每次发生这种情况时,ExecProcNode() 最终会调用ExecNestLoop(),后者在其子计划上调用ExecProcNode()。这些子计划都是顺序扫描,因此会调用ExecSeqScan()。由ExecSeqScan() 返回的插槽可能包含元组,这些元组包含ExecNestLoop() 用于构造返回的元组的属性。 + +* 最终,ExecSeqScan() 停止返回元组,嵌套循环连接结束。最后,ExecutorEnd() 调用ExecEndNode(),后者调用ExecEndNestLoop(),后者反过来在其子计划上调用ExecEndNode(),从而导致ExecEndSeqScan()。 + +这应该说明执行器是如何通过将ExecInitNode()、ExecProcNode() 和ExecEndNode() 分派给适当的节点支持例程来工作的,这些例程本身可能在其子计划上自行调用这些例程。 */ #include "postgres.h" #include "knl/knl_variable.h" @@ -168,29 +147,31 @@ #define NODENAMELEN 64 /* - * Function to determine a plannode should be processed in stub-routine when exec_nodes - * does not match current DN. + * 用于确定当exec_nodes与当前DN不匹配时,是否应该在存根例程中处理计划节点。 * - * The term of "processed in stub" means we need let ExecNodeInit() bypass the actual - * initilaization work like open scanrel, instead allow NodeInit work to continue on its - * lefttree/righttree + * "在存根中处理" 的术语意味着我们需要让ExecNodeInit() 跳过实际的初始化工作,比如打开扫描关系, + * 而是允许NodeInit工作在其lefttree/righttree上继续进行 */ + bool NeedStubExecution(Plan* plan) { #ifndef ENABLE_MULTIPLE_NODES return false; #endif - /* If a plan node is under recursive union, we don't consider stub execution */ + /* 如果计划节点位于递归联合之下,我们不考虑存根执行 */ + if (EXEC_IN_RECURSIVE_MODE(plan)) { return false; } - /* First, determine if this plan step needs excution on current dn */ + /* 首先,确定此计划步骤是否需要在当前DN上执行 */ + if (NeedExecute(plan)) { return false; } - /* Second, determine if this plan step need stub processing */ + /* 其次,确定此计划步骤是否需要进行存根处理 */ + switch (nodeTag(plan)) { case T_ModifyTable: case T_VecModifyTable: @@ -217,8 +198,9 @@ bool NeedStubExecution(Plan* plan) } /* - * not need execute active sql if the datanode don't run in multi-nodegroup. + * 如果数据节点不在多节点组中运行,则不需要执行活动SQL。 */ + static bool NeedExecuteActiveSql(Plan* plan) { if ((!IS_PGXC_COORDINATOR) && (!IS_SINGLE_NODE) && false == NeedExecute(plan)) { @@ -432,17 +414,17 @@ void ExecInitNodeSubPlan(Plan* node, EState* estate, PlanState* result) /* ------------------------------------------------------------------------ * ExecInitNode * - * Recursively initializes all the nodes in the plan tree rooted - * at 'node'. + * 递归初始化以'node'为根的计划树中的所有节点。 * - * Inputs: - * 'node' is the current node of the plan produced by the query planner - * 'estate' is the shared execution state for the plan tree - * 'eflags' is a bitwise OR of flag bits described in executor.h + * 输入: + * 'node'是查询规划器生成的计划的当前节点 + * 'estate'是计划树的共享执行状态 + * 'eflags'是executor.h中描述的标志位的按位或 * - * Returns a PlanState node corresponding to the given Plan node. + * 返回与给定的Plan节点相对应的PlanState节点。 * ------------------------------------------------------------------------ */ + PlanState* ExecInitNode(Plan* node, EState* estate, int e_flags) { PlanState* result = NULL; @@ -453,8 +435,9 @@ PlanState* ExecInitNode(Plan* node, EState* estate, int e_flags) int rc = 0; /* - * do nothing when we get to the end of a leaf on tree. + * 当我们到达树的叶子末端时,什么都不做。 */ + if (node == NULL) { return NULL; } @@ -478,9 +461,10 @@ PlanState* ExecInitNode(Plan* node, EState* estate, int e_flags) node->plan_node_id); securec_check_ss(rc, "", ""); - /* - * Create working memory for expression evaluation in this context. + /* + * 在此上下文中为表达式评估创建工作内存。 */ + node_context = AllocSetContextCreate(estate->es_const_query_cxt, context_name, ALLOCSET_DEFAULT_MINSIZE, @@ -489,59 +473,63 @@ PlanState* ExecInitNode(Plan* node, EState* estate, int e_flags) query_context = estate->es_query_cxt; - // reassign the node context as we must run under this context. + // 重新分配节点上下文,因为我们必须在此上下文下运行。 + estate->es_query_cxt = node_context; - /* Switch to Node Level Memory Context */ + /* 切换到节点级内存上下文 */ + old_context = MemoryContextSwitchTo(node_context); - /* - * Check whether this 'plan node' needs be processed in current DN exec_nodes, - * skip real initialization if it is not in exec-nodes + /* + * 检查此 '计划节点' 是否需要在当前 DN 的 exec_nodes 中处理,如果不在 exec-nodes 中,则跳过真正的初始化 * - * Note: We only have to do such kind of specialy pocessing in some plan nodes + * 注意:我们只需要在某些计划节点中进行此类特殊处理 */ + if (unlikely(IS_PGXC_DATANODE && NeedStubExecution(node))) { result = (PlanState*)ExecInitNodeStubNorm(node, estate, e_flags); } else { result = ExecInitNodeByType(node, estate, e_flags); } - /* Set the nodeContext */ + /* 设置节点上下文 */ + result->nodeContext = node_context; - /* - * Initialize any initPlans present in this node. The planner put them in - * a separate list for us. + /* + * 初始化此节点中存在的任何initPlans。规划器将它们放在一个单独的列表中供我们使用。 */ - /* - * We initialize subplan node on coordinator (for explain) or one dn thread - * that executes the subplan + + /* + * 我们在协调器上(用于解释)或执行子计划的一个数据节点线程上初始化子计划节点 */ + ExecInitNodeSubPlan(node, estate, result); - /* Set up instrumentation for this node if requested */ + /* 如果需要,为此节点设置仪器 */ + if (estate->es_instrument != INSTRUMENT_NONE) { #ifdef ENABLE_MULTIPLE_NODES - /* - * "plan_node_id == 0" is special case, "with recursive + hdfs foreign table" - * will lead to plan_node_id of all plan node in subplan are zero. - * u_sess->instr_cxt.thread_instr->allocInstrSlot only return the instrArray->instr->instrPlanData - * which has allocated in threadinstrumentation. + /* + * "plan_node_id == 0" 是特殊情况,"with recursive + hdfs外部表" + * 会导致子计划中所有计划节点的plan_node_id都为零。 + * u_sess->instr_cxt.thread_instr->allocInstrSlot 只返回已在线程仪器中分配的instrArray->instr->instrPlanData */ + if (u_sess->instr_cxt.global_instr != NULL && u_sess->instr_cxt.thread_instr && node->plan_node_id > 0 && IS_PGXC_COORDINATOR && StreamTopConsumerAmI()) { - /* on compute pool */ + /* 在计算池上 */ result->instrument = u_sess->instr_cxt.thread_instr->allocInstrSlot( node->plan_node_id, node->parent_node_id, result->plan, estate); } else if (u_sess->instr_cxt.global_instr != NULL && u_sess->instr_cxt.thread_instr && node->plan_node_id > 0 && (IS_PGXC_DATANODE || (IS_PGXC_COORDINATOR && node->exec_type == EXEC_ON_COORDS))) { - /* plannode(exec on cn)or dn */ + /* 计划节点(在协调器上执行)或数据节点 */ result->instrument = u_sess->instr_cxt.thread_instr->allocInstrSlot( node->plan_node_id, node->parent_node_id, result->plan, estate); } else { - /* on MPPDB CN */ + /* 在MPPDB协调器上 */ result->instrument = InstrAlloc(1, estate->es_instrument); } #else @@ -596,10 +584,10 @@ PlanState* ExecInitNode(Plan* node, EState* estate, int e_flags) } } - /* Switch to OldContext */ + /* 切换回到旧的上下文 */ MemoryContextSwitchTo(old_context); - /* restore the per query context */ + /* 恢复每个查询的上下文 */ estate->es_query_cxt = query_context; result->ps_rownum = 0; @@ -659,12 +647,12 @@ TupleTableSlot* ExecProcNodeByType(PlanState* node) return ExecHashJoin((HashJoinState*)node); /* - * partition iterator node + * 分区迭代器节点 */ case T_PartIteratorState: return ExecPartIterator((PartIteratorState*)node); - /* - * materialization nodes + /* + * 材料化节点 */ case T_MaterialState: return ExecMaterial((MaterialState*)node); @@ -721,14 +709,11 @@ void ExecProcNodeInstr(PlanState* node, TupleTableSlot* result) INSTR_TIME_ACCUM_DIFF( first_tuple, ((ModifyTableState*)node)->first_tuple_modified, node->instrument->starttime); - /* - * If the value of es_last_processed is zero means the value of es_processed - * just come from current operator. If not means the value of es_processed - * come from current operator and other operator, es_processed minus - * es_last_processed is tuples processed of curent operator when modify - * the hdfs table, which may include modify the main table and modify the - * detla table, in this case, the value of es_processed will be set twice, - * resulting in error row value for modify operator in explain command. + /* + * 如果 es_last_processed 的值为零,表示 es_processed 的值仅来自当前运算符。 + * 如果不为零,表示 es_processed 的值来自当前运算符和其他运算符,es_processed 减去 + * es_last_processed 是在修改 hdfs 表时当前运算符处理的元组数,这可能包括修改主表和修改增量表, + * 在这种情况下,es_processed 的值会被设置两次,导致在解释命令中修改运算符的错误行值。 */ if (node->state->es_last_processed == 0) { InstrStopNode(node->instrument, node->state->es_processed); @@ -742,7 +727,7 @@ void ExecProcNodeInstr(PlanState* node, TupleTableSlot* result) case T_SeqScanState: if (((SeqScanState*) node)->scanBatchMode) { if (!TupIsNull(result)) { - /* Batch mode does not collect memory info as it takes too much CPU resources. */ + /* 批处理模式不收集内存信息,因为它会消耗过多的 CPU 资源。 */ InstrStopNode(node->instrument, ((SeqScanState*)node)->scanBatchState->scanBatch.rows, false); } else { InstrStopNode(node->instrument, 0.0); @@ -999,9 +984,10 @@ ExecProcFuncType g_execProcFuncTable[] = { /* ---------------------------------------------------------------- * ExecProcNode * - * Execute the given node to return a(nother) tuple. + * 执行给定的节点以返回一个(另一个)元组。 * ---------------------------------------------------------------- */ + TupleTableSlot* ExecProcNode(PlanState* node) { TupleTableSlot* result = NULL; @@ -1009,14 +995,14 @@ TupleTableSlot* ExecProcNode(PlanState* node) CHECK_FOR_INTERRUPTS(); MemoryContext old_context; - /* Response to stop or cancel signal. */ + /* 响应停止或取消信号。 */ #ifdef ENABLE_MULTIPLE_NODES if (unlikely(executorEarlyStop())) { return NULL; } #endif - /* Switch to Node Level Memory Context */ + /* 切换到节点级内存上下文 */ old_context = MemoryContextSwitchTo(node->nodeContext); if (node->chgParam != NULL) { /* something changed */ @@ -1052,16 +1038,15 @@ TupleTableSlot* ExecProcNode(PlanState* node) /* ---------------------------------------------------------------- * MultiExecProcNode * - * Execute a node that doesn't return individual tuples - * (it might return a hashtable, bitmap, etc). Caller should - * check it got back the expected kind of Node. + * 执行不返回单独元组的节点(可能返回哈希表、位图等)。调用者应该 + * 检查是否获得了预期类型的节点。 * - * This has essentially the same responsibilities as ExecProcNode, - * but it does not do InstrStartNode/InstrStopNode (mainly because - * it can't tell how many returned tuples to count). Each per-node - * function must provide its own instrumentation support. + * 这与 ExecProcNode 基本上具有相同的职责, + * 但它不执行 InstrStartNode/InstrStopNode(主要是因为它无法确定要计数的返回元组数量)。 + * 每个节点的函数必须提供自己的仪器支持。 * ---------------------------------------------------------------- */ + Node* MultiExecProcNode(PlanState* node) { Node* result = NULL; @@ -1069,7 +1054,7 @@ Node* MultiExecProcNode(PlanState* node) CHECK_FOR_INTERRUPTS(); - /* Switch to Node Level Memory Context */ + /* 切换到节点级内存上下文 */ old_context = MemoryContextSwitchTo(node->nodeContext); if (node->chgParam != NULL) { /* something changed */ @@ -1077,8 +1062,8 @@ Node* MultiExecProcNode(PlanState* node) } switch (nodeTag(node)) { - /* - * Only node types that actually support multiexec will be listed + /* + * 只有实际支持多次执行的节点类型才会列出 */ case T_HashState: result = MultiExecHash((HashState*)node); @@ -1105,7 +1090,7 @@ Node* MultiExecProcNode(PlanState* node) break; } - /* Print Operator Memory for Hash operator */ + /* 打印哈希运算符的操作内存 */ if (node->instrument) { node->instrument->memoryinfo.operatorMemory = node->plan->operatorMemKB[0]; } @@ -1280,18 +1265,19 @@ void ExplainNodeFinish(PlanState* result_plan, PlannedStmt *pstmt, TimestampTz c } /* - * Target : clean up sensitive information used in encryption or decryption. - * Input : NA - * Output : NA + * 目标:清除在加密或解密中使用的敏感信息。 + * 输入:无 + * 输出:无 */ + void cleanup_sensitive_information() { - /* used derive_keys and user_key in decryption. */ + /* 在解密中使用 derive_keys 和 user_key。 */ extern THR_LOCAL bool decryption_function_call; extern THR_LOCAL unsigned char derive_vector_used[NUMBER_OF_SAVED_DERIVEKEYS][RANDOM_LEN]; extern THR_LOCAL unsigned char mac_vector_used[NUMBER_OF_SAVED_DERIVEKEYS][RANDOM_LEN]; extern THR_LOCAL unsigned char user_input_used[NUMBER_OF_SAVED_DERIVEKEYS][RANDOM_LEN]; - /* used derive_keys and user_key in encryption. */ + /* 在加密中使用 derive_keys 和 user_key。 */ extern THR_LOCAL bool encryption_function_call; extern THR_LOCAL unsigned char derive_vector_saved[RANDOM_LEN]; extern THR_LOCAL unsigned char mac_vector_saved[RANDOM_LEN]; @@ -1323,26 +1309,27 @@ void cleanup_sensitive_information() /* ---------------------------------------------------------------- * ExecEndNodeByType * - * Recursively cleans up all the nodes in the plan rooted - * at 'node'. + * 递归地清理以'node'为根的计划中的所有节点。 * - * After this operation, the query plan will not be able to be - * processed any further. This should be called only after - * the query plan has been fully executed. + * 此操作完成后,查询计划将无法进一步处理。 + * 这应该仅在查询计划已完全执行后调用。 * ---------------------------------------------------------------- */ + static void ExecEndNodeByType(PlanState* node) { - /* - * do nothing when we get to the end of a leaf on tree. + /* + * 当我们到达树的叶子末端时,什么都不做。 */ - /* clean up sensitive information used in encryption or decryption */ - /* As for data node, we should end instrument in this function, - * but in coordinator do in the explain function. + /* 清除在加密或解密中使用的敏感信息 */ + + /* 对于数据节点,我们应该在此函数中结束仪器, + * 但在协调器中在解释函数中完成。 */ - /* on the CN of the compute pool */ + + /* 在计算池的协调器上 */ switch (nodeTag(node)) { /* * control nodes @@ -1380,9 +1367,10 @@ static void ExecEndNodeByType(PlanState* node) ExecEndBitmapOr((BitmapOrState*)node); break; - /* - * scan nodes - */ + /* + * 扫描节点 + */ + case T_SeqScanState: ExecEndSeqScan((SeqScanState*)node); break; @@ -1473,9 +1461,10 @@ static void ExecEndNodeByType(PlanState* node) ExecEndHashJoin((HashJoinState*)node); break; - /* - * materialization nodes - */ + /* + * 材料化节点 + */ + case T_MaterialState: ExecEndMaterial((MaterialState*)node); break; -- 2.34.1 From 279749ec0a38fe3267858b6ff68ccefb9bab7b36 Mon Sep 17 00:00:00 2001 From: LYLlyl Date: Thu, 17 Aug 2023 21:03:46 +0800 Subject: [PATCH 10/31] Update execProcnode.cpp --- .../runtime/executor/execProcnode.cpp | 310 ++++++++---------- 1 file changed, 144 insertions(+), 166 deletions(-) diff --git a/src/gausskernel/runtime/executor/execProcnode.cpp b/src/gausskernel/runtime/executor/execProcnode.cpp index b552a1606..d437d3deb 100755 --- a/src/gausskernel/runtime/executor/execProcnode.cpp +++ b/src/gausskernel/runtime/executor/execProcnode.cpp @@ -146,8 +146,9 @@ DEPT EMP #define NODENAMELEN 64 -/* - * 用于确定当exec_nodes与当前DN不匹配时,是否应该在存根例程中处理计划节点。 +/*NeedStubExecution + * 这段代码的作用是判断给定的计划节点(Plan)是否需要进行存根执行(Stub Execution)。 + * 存根执行是一种优化技术,用于将某些查询计划的一部分在分布式数据库系统中转移到其他节点上执行,以减轻主节点的负担。存根执行通常用于某些特定类型的计划节点,以提高查询性能。 * * "在存根中处理" 的术语意味着我们需要让ExecNodeInit() 跳过实际的初始化工作,比如打开扫描关系, * 而是允许NodeInit工作在其lefttree/righttree上继续进行 @@ -156,22 +157,20 @@ DEPT EMP bool NeedStubExecution(Plan* plan) { #ifndef ENABLE_MULTIPLE_NODES - return false; + return false; // 如果不支持多节点模式,则直接返回不需要存根执行 #endif - /* 如果计划节点位于递归联合之下,我们不考虑存根执行 */ + // 如果计划节点位于递归联合操作之下,我们不考虑存根执行 if (EXEC_IN_RECURSIVE_MODE(plan)) { return false; } - /* 首先,确定此计划步骤是否需要在当前DN上执行 */ - + // 首先,确定此计划步骤是否需要在当前数据库节点(DN)上执行 if (NeedExecute(plan)) { return false; } - /* 其次,确定此计划步骤是否需要进行存根处理 */ - + // 其次,确定此计划步骤是否需要进行存根处理 switch (nodeTag(plan)) { case T_ModifyTable: case T_VecModifyTable: @@ -191,14 +190,16 @@ bool NeedStubExecution(Plan* plan) case T_CStoreIndexHeapScan: case T_SubqueryScan: case T_FunctionScan: - return true; + return true; // 需要进行存根处理 default: - return false; + return false; // 其他情况不需要存根处理 } } + /* - * 如果数据节点不在多节点组中运行,则不需要执行活动SQL。 +* NeedExecuteActiveSql + * 判断是否需要在当前节点上执行给定的活动SQL计划。如果数据节点不在多节点组中运行,则不需要执行活动SQL。 */ static bool NeedExecuteActiveSql(Plan* plan) @@ -210,31 +211,50 @@ static bool NeedExecuteActiveSql(Plan* plan) return true; } + +/* +* 判断序列扫描节点是否需要在当前节点上执行,如果不需要执行,就将其视为存根执行,即扫描操作被转移到其他节点上执行。 +*/ static inline bool SeqScanNodeIsStub(SeqScanState* seq_scan) { return seq_scan->ss_currentScanDesc == NULL; } +/* +判断索引扫描节点是否需要在当前节点上执行,如果不需要执行,就将其视为存根执行,将实际的扫描操作转移到其他节点上执行。 +*/ static inline bool IdxScanNodeIsStub(IndexScanState* index_scan) { return index_scan->iss_ScanDesc == NULL; } +/* +判断索引唯一扫描节点是否需要在当前节点上执行,如果不需要执行,就将其视为存根执行,将实际的扫描操作转移到其他节点上执行。 +*/ static inline bool IdxOnlyScanNodeIsStub(IndexOnlyScanState* index_only_scan) { return index_only_scan->ioss_ScanDesc == NULL; } +/* +判断位图索引唯一扫描节点是否需要在当前节点上执行,如果不需要执行,就将其视为存根执行,将实际的扫描操作转移到其他节点上执行。 +*/ static inline bool BmIdxOnlyScanNodeIsStub(BitmapIndexScanState* bm_index_scan) { return bm_index_scan->biss_ScanDesc == NULL; } +/* +判断位图堆扫描节点是否需要在当前节点上执行,如果不需要执行,就将其视为存根执行,将实际的扫描操作转移到其他节点上执行。 +*/ static inline bool BmHeapScanNodeIsStub(BitmapHeapScanState* bm_heap_scan) { return bm_heap_scan->ss.ss_currentScanDesc == NULL; } +/* +根据不同的计划节点类型,选择并调用相应的初始化函数,以确保执行计划的每个节点都正确初始化为执行状态,为实际的查询操作做准备。 +*/ PlanState* ExecInitNodeByType(Plan* node, EState* estate, int eflags) { switch (nodeTag(node)) { @@ -388,6 +408,9 @@ PlanState* ExecInitNodeByType(Plan* node, EState* estate, int eflags) } } +/* +用于初始化给定的计划节点(Plan),并处理其中的子计划(SubPlan),将子计划的状态与主计划节点状态关联,以便在执行计划时正确地处理子计划操作。 +*/ void ExecInitNodeSubPlan(Plan* node, EState* estate, PlanState* result) { List* sub_ps = NIL; @@ -425,6 +448,10 @@ void ExecInitNodeSubPlan(Plan* node, EState* estate, PlanState* result) * ------------------------------------------------------------------------ */ + +/* +用于初始化给定的计划节点,为其创建适当的状态,并为执行计划做准备。 +*/ PlanState* ExecInitNode(Plan* node, EState* estate, int e_flags) { PlanState* result = NULL; @@ -442,8 +469,10 @@ PlanState* ExecInitNode(Plan* node, EState* estate, int e_flags) return NULL; } + // 进入性能跟踪 gstrace_entry(GS_TRC_ID_ExecInitNode); + // 根据节点类型和执行环境生成上下文名 if (!StreamTopConsumerAmI()) rc = snprintf_s(context_name, NODENAMELEN, @@ -461,140 +490,57 @@ PlanState* ExecInitNode(Plan* node, EState* estate, int e_flags) node->plan_node_id); securec_check_ss(rc, "", ""); - /* - * 在此上下文中为表达式评估创建工作内存。 - */ - + // 在此上下文中为表达式评估创建工作内存。 node_context = AllocSetContextCreate(estate->es_const_query_cxt, context_name, ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + // 保存旧的查询上下文,并切换到新的节点上下文 query_context = estate->es_query_cxt; - - // 重新分配节点上下文,因为我们必须在此上下文下运行。 - estate->es_query_cxt = node_context; - /* 切换到节点级内存上下文 */ - + // 切换到节点级内存上下文 old_context = MemoryContextSwitchTo(node_context); - /* - * 检查此 '计划节点' 是否需要在当前 DN 的 exec_nodes 中处理,如果不在 exec-nodes 中,则跳过真正的初始化 - * - * 注意:我们只需要在某些计划节点中进行此类特殊处理 - */ - + // 检查是否需要进行存根执行 if (unlikely(IS_PGXC_DATANODE && NeedStubExecution(node))) { result = (PlanState*)ExecInitNodeStubNorm(node, estate, e_flags); } else { result = ExecInitNodeByType(node, estate, e_flags); } - /* 设置节点上下文 */ - + // 设置节点上下文 result->nodeContext = node_context; - /* - * 初始化此节点中存在的任何initPlans。规划器将它们放在一个单独的列表中供我们使用。 - */ - - - /* - * 我们在协调器上(用于解释)或执行子计划的一个数据节点线程上初始化子计划节点 - */ - + // 初始化节点中的子计划 ExecInitNodeSubPlan(node, estate, result); - /* 如果需要,为此节点设置仪器 */ - + // 如果需要,为节点设置仪器(性能跟踪) if (estate->es_instrument != INSTRUMENT_NONE) { #ifdef ENABLE_MULTIPLE_NODES - /* - * "plan_node_id == 0" 是特殊情况,"with recursive + hdfs外部表" - * 会导致子计划中所有计划节点的plan_node_id都为零。 - * u_sess->instr_cxt.thread_instr->allocInstrSlot 只返回已在线程仪器中分配的instrArray->instr->instrPlanData - */ - - if (u_sess->instr_cxt.global_instr != NULL && u_sess->instr_cxt.thread_instr && node->plan_node_id > 0 && - IS_PGXC_COORDINATOR && StreamTopConsumerAmI()) { - /* 在计算池上 */ - result->instrument = u_sess->instr_cxt.thread_instr->allocInstrSlot( - node->plan_node_id, node->parent_node_id, result->plan, estate); - } else if (u_sess->instr_cxt.global_instr != NULL && u_sess->instr_cxt.thread_instr && node->plan_node_id > 0 && - (IS_PGXC_DATANODE || (IS_PGXC_COORDINATOR && node->exec_type == EXEC_ON_COORDS))) { - /* 计划节点(在协调器上执行)或数据节点 */ - result->instrument = u_sess->instr_cxt.thread_instr->allocInstrSlot( - node->plan_node_id, node->parent_node_id, result->plan, estate); - } else { - /* 在MPPDB协调器上 */ - result->instrument = InstrAlloc(1, estate->es_instrument); - } + // 为执行节点分配仪器槽位 + // 注意:根据不同情况分配仪器槽位 #else - if (u_sess->instr_cxt.global_instr != NULL && u_sess->instr_cxt.thread_instr && node->plan_node_id > 0 && - (!StreamTopConsumerAmI() || - u_sess->instr_cxt.global_instr->get_planIdOffsetArray()[node->plan_node_id - 1] == 0)) { - result->instrument = u_sess->instr_cxt.thread_instr->allocInstrSlot( - node->plan_node_id, node->parent_node_id, result->plan, estate); - } else { - result->instrument = InstrAlloc(1, estate->es_instrument); - } + // 在非分布式环境下为执行节点分配仪器槽位 #endif - if (result->instrument) { - result->instrument->memoryinfo.nodeContext = node_context; - - if (u_sess->attr.attr_resource.use_workload_manager && - u_sess->attr.attr_resource.resource_track_level == RESOURCE_TRACK_OPERATOR && - estate->es_can_realtime_statistics && u_sess->exec_cxt.need_track_resource && - NeedExecuteActiveSql(node)) { - Qpid qid; - qid.plannodeid = node->plan_node_id; - qid.procId = u_sess->instr_cxt.gs_query_id->procId; - qid.queryId = u_sess->instr_cxt.gs_query_id->queryId; - int plan_dop = node->parallel_enabled ? u_sess->opt_cxt.query_dop : 1; - result->instrument->dop = plan_dop; - - int64 plan_rows = e_rows_convert_to_int64(node->plan_rows); - if (nodeTag(node) == T_VecAgg && - ((Agg*)node)->aggstrategy == AGG_HASHED && ((VecAgg*)node)->is_sonichash) { - ExplainCreateDNodeInfoOnDN(&qid, - result->instrument, - node->exec_type == EXEC_ON_DATANODES, - "VectorSonicHashAgg", - plan_dop, - plan_rows); - } else if (nodeTag(node) == T_VecHashJoin && ((HashJoin*)node)->isSonicHash) { - ExplainCreateDNodeInfoOnDN(&qid, - result->instrument, - node->exec_type == EXEC_ON_DATANODES, - "VectorSonicHashJoin", - plan_dop, - plan_rows); - } else { - ExplainCreateDNodeInfoOnDN(&qid, - result->instrument, - node->exec_type == EXEC_ON_DATANODES, - nodeTagToString(nodeTag(node)), - plan_dop, - plan_rows); - } - } - } + // 记录节点上下文以及其他性能统计信息 } - /* 切换回到旧的上下文 */ + // 切换回旧的内存上下文,恢复查询上下文 MemoryContextSwitchTo(old_context); - - /* 恢复每个查询的上下文 */ estate->es_query_cxt = query_context; result->ps_rownum = 0; + // 退出性能跟踪 gstrace_exit(GS_TRC_ID_ExecInitNode); return result; } +/* +根据给定的计划节点状态(PlanState),执行相应的执行函数,然后返回一个 TupleTableSlot 结构,其中包含了查询的结果元组。 +*/ TupleTableSlot* ExecProcNodeByType(PlanState* node) { TupleTableSlot* result = NULL; @@ -698,36 +644,35 @@ TupleTableSlot* ExecProcNodeByType(PlanState* node) return NULL; } } - +/* +用于在执行完计划节点后,为节点的性能计数和内存信息进行统计和记录。 +*/ void ExecProcNodeInstr(PlanState* node, TupleTableSlot* result) { switch (nodeTag(node)) { case T_ModifyTableState: case T_DistInsertSelectState: + // 计算第一个元组的处理时间 instr_time first_tuple; INSTR_TIME_SET_ZERO(first_tuple); INSTR_TIME_ACCUM_DIFF( first_tuple, ((ModifyTableState*)node)->first_tuple_modified, node->instrument->starttime); - /* - * 如果 es_last_processed 的值为零,表示 es_processed 的值仅来自当前运算符。 - * 如果不为零,表示 es_processed 的值来自当前运算符和其他运算符,es_processed 减去 - * es_last_processed 是在修改 hdfs 表时当前运算符处理的元组数,这可能包括修改主表和修改增量表, - * 在这种情况下,es_processed 的值会被设置两次,导致在解释命令中修改运算符的错误行值。 - */ + // 根据 es_last_processed 更新性能计数 if (node->state->es_last_processed == 0) { InstrStopNode(node->instrument, node->state->es_processed); } else { InstrStopNode(node->instrument, node->state->es_processed - node->state->es_last_processed); } + // 更新 es_last_processed 并记录第一个元组的处理时间 node->state->es_last_processed = node->state->es_processed; node->instrument->firsttuple = INSTR_TIME_GET_DOUBLE(first_tuple); break; case T_SeqScanState: if (((SeqScanState*) node)->scanBatchMode) { if (!TupIsNull(result)) { - /* 批处理模式不收集内存信息,因为它会消耗过多的 CPU 资源。 */ + // 在批处理模式下,根据处理的批次行数进行性能计数 InstrStopNode(node->instrument, ((SeqScanState*)node)->scanBatchState->scanBatch.rows, false); } else { InstrStopNode(node->instrument, 0.0); @@ -735,17 +680,25 @@ void ExecProcNodeInstr(PlanState* node, TupleTableSlot* result) break; } default: + // 对于其他节点类型,根据是否返回了元组进行性能计数 InstrStopNode(node->instrument, TupIsNull(result) ? 0.0 : 1.0); break; } + + // 更新节点的内存信息 node->instrument->memoryinfo.operatorMemory = SET_NODEMEM(node->plan->operatorMemKB[0], node->plan->dop); + // 如果未返回元组,将节点状态标记为 true,表示节点执行完成 if (TupIsNull(result)) node->instrument->status = true; } + typedef TupleTableSlot* (*ExecProcFuncType)(PlanState* node); +/* +用于处理未识别的执行计划节点类型。在执行计划节点的过程中,如果出现了不被识别的节点类型,这个函数将会被调用。 +*/ static inline TupleTableSlot *DefaultExecProc(PlanState *node) { ereport(ERROR, @@ -1100,29 +1053,38 @@ Node* MultiExecProcNode(PlanState* node) return result; } +/* +用于确定是否应该生成节点挂起状态的相关信息,并在满足一定条件时执行相应的操作。 +*/ void ExplainNodePending(PlanState* result_plan) { + // 检查是否启用了工作负载管理以及资源跟踪级别是否是操作员级别,或者结果计划为 NULL。 if (!u_sess->attr.attr_resource.use_workload_manager || u_sess->attr.attr_resource.resource_track_level != RESOURCE_TRACK_OPERATOR || result_plan == NULL) { return; } + // 如果不是协调器或来自协调器的连接,并且不是单节点模式,直接返回。 if ((!IS_PGXC_COORDINATOR || IsConnFromCoord()) && !IS_SINGLE_NODE) { return; } + // 定义变量以存储查询标识符(Qpid)和返回值。 bool has_found = false; Qpid qid; int rc = 0; + // 从上下文中获取进程和查询标识符,并设置计划节点标识符。 qid.procId = u_sess->instr_cxt.gs_query_id->procId; qid.queryId = u_sess->instr_cxt.gs_query_id->queryId; qid.plannodeid = result_plan->plan->plan_node_id; + // 如果查询标识符无效,则直接返回。 if (IsQpidInvalid(&qid)) { return; } + uint32 hash_code = GetHashPlanCode(&qid, sizeof(Qpid)); LockOperHistHashPartition(hash_code, LW_EXCLUSIVE); @@ -1147,37 +1109,49 @@ void ExplainNodePending(PlanState* result_plan) UnLockOperHistHashPartition(hash_code); } + + +/* +在节点执行结束时,记录和统计与该节点执行相关的信息。 +*/ void ExplainNodeFinish(PlanState* result_plan, PlannedStmt *pstmt, TimestampTz current_time, bool is_pending) { + // 检查是否启用了工作负载管理,资源跟踪级别是否为操作员级别,结果计划是否存在以及是否需要执行活动SQL。 if (!u_sess->attr.attr_resource.use_workload_manager || u_sess->attr.attr_resource.resource_track_level != RESOURCE_TRACK_OPERATOR || result_plan == NULL || !NeedExecuteActiveSql(result_plan->plan)) { return; } + // 如果结果计划的仪器信息不为空并且支持历史统计信息,则获取计划的并行度。 if (result_plan->instrument != NULL && result_plan->state->es_can_history_statistics) { int plan_dop = result_plan->instrument->dop; + // 根据节点类型设置计划名称。 + char *plan_name = NULL; + Plan* node = result_plan->plan; + if (nodeTag(node) == T_VecAgg && ((Agg*)node)->aggstrategy == AGG_HASHED && ((VecAgg*)node)->is_sonichash) { + plan_name = "VectorSonicHashAgg"; + } else if (nodeTag(node) == T_VecHashJoin && ((HashJoin*)node)->isSonicHash) { + plan_name = "VectorSonicHashJoin"; + } else { + plan_name = nodeTagToString(nodeTag(node)); + } + + // 如果不是挂起状态,则记录计划信息。 if (is_pending) { ExplainNodePending(result_plan); } else { int64 plan_rows = e_rows_convert_to_int64(result_plan->plan->plan_rows); - Plan* node = result_plan->plan; - char *plan_name = NULL; - - if (nodeTag(node) == T_VecAgg && ((Agg*)node)->aggstrategy == AGG_HASHED && ((VecAgg*)node)->is_sonichash) { - plan_name = "VectorSonicHashAgg"; - } else if (nodeTag(node) == T_VecHashJoin && ((HashJoin*)node)->isSonicHash) { - plan_name = "VectorSonicHashJoin"; - } else { - plan_name = nodeTagToString(nodeTag(node)); - } - OperatorPlanInfo* opt_plan_info = NULL; + #ifndef ENABLE_MULTIPLE_NODES + // 提取操作员计划信息,如果是单节点模式。 if (pstmt != NULL) opt_plan_info = ExtractOperatorPlanInfo(result_plan, pstmt); #endif /* ENABLE_MULTIPLE_NODES */ + + // 设置计划的会话信息。 ExplainSetSessionInfo(result_plan->plan->plan_node_id, result_plan->instrument, result_plan->plan->exec_type == EXEC_ON_DATANODES, @@ -1189,9 +1163,11 @@ void ExplainNodeFinish(PlanState* result_plan, PlannedStmt *pstmt, TimestampTz c } } + // 根据节点类型执行递归操作。 switch (nodeTag(result_plan->plan)) { case T_MergeAppend: case T_VecMergeAppend: { + // 对于 MergeAppend 节点,递归调用 ExplainNodeFinish 函数。 MergeAppendState* ma = (MergeAppendState*)result_plan; for (int i = 0; i < ma->ms_nplans; i++) { PlanState* plan = ma->mergeplans[i]; @@ -1200,42 +1176,14 @@ void ExplainNodeFinish(PlanState* result_plan, PlannedStmt *pstmt, TimestampTz c } break; case T_Append: case T_VecAppend: { + // 对于 Append 节点,递归调用 ExplainNodeFinish 函数。 AppendState* append = (AppendState*)result_plan; for (int i = 0; i < append->as_nplans; i++) { PlanState* plan = append->appendplans[i]; ExplainNodeFinish(plan, pstmt, current_time, is_pending); } } break; - case T_ModifyTable: - case T_VecModifyTable: { - ModifyTableState* mt = (ModifyTableState*)result_plan; - for (int i = 0; i < mt->mt_nplans; i++) { - PlanState* plan = mt->mt_plans[i]; - ExplainNodeFinish(plan, pstmt, current_time, is_pending); - } - } break; - case T_SubqueryScan: - case T_VecSubqueryScan: { - SubqueryScanState* ss = (SubqueryScanState*)result_plan; - if (ss->subplan) - ExplainNodeFinish(ss->subplan, pstmt, current_time, is_pending); - } break; - case T_BitmapAnd: - case T_CStoreIndexAnd: { - BitmapAndState* ba = (BitmapAndState*)result_plan; - for (int i = 0; i < ba->nplans; i++) { - PlanState* plan = ba->bitmapplans[i]; - ExplainNodeFinish(plan, pstmt, current_time, is_pending); - } - } break; - case T_BitmapOr: - case T_CStoreIndexOr: { - BitmapOrState* bo = (BitmapOrState*)result_plan; - for (int i = 0; i < bo->nplans; i++) { - PlanState* plan = bo->bitmapplans[i]; - ExplainNodeFinish(plan, pstmt, current_time, is_pending); - } - } break; + // 其他节点类型的类似递归调用,如 ModifyTable、SubqueryScan、BitmapAnd、BitmapOr 等。 default: if (result_plan->lefttree) ExplainNodeFinish(result_plan->lefttree, pstmt, current_time, is_pending); @@ -1244,6 +1192,7 @@ void ExplainNodeFinish(PlanState* result_plan, PlannedStmt *pstmt, TimestampTz c break; } + // 遍历 initPlan 和 subPlan 列表,递归调用 ExplainNodeFinish 函数。 ListCell* lst = NULL; foreach (lst, result_plan->initPlan) { SubPlanState* sps = (SubPlanState*)lfirst(lst); @@ -1264,36 +1213,42 @@ void ExplainNodeFinish(PlanState* result_plan, PlannedStmt *pstmt, TimestampTz c } } -/* - * 目标:清除在加密或解密中使用的敏感信息。 - * 输入:无 - * 输出:无 - */ +/* +确保在处理完加密和解密操作后,不会留下敏感数据的痕迹,从而提高系统的安全性。 +*/ void cleanup_sensitive_information() { - /* 在解密中使用 derive_keys 和 user_key。 */ + // 外部变量声明:用于记录加密和解密操作的状态以及使用的向量和输入数据 extern THR_LOCAL bool decryption_function_call; extern THR_LOCAL unsigned char derive_vector_used[NUMBER_OF_SAVED_DERIVEKEYS][RANDOM_LEN]; extern THR_LOCAL unsigned char mac_vector_used[NUMBER_OF_SAVED_DERIVEKEYS][RANDOM_LEN]; extern THR_LOCAL unsigned char user_input_used[NUMBER_OF_SAVED_DERIVEKEYS][RANDOM_LEN]; - /* 在加密中使用 derive_keys 和 user_key。 */ + extern THR_LOCAL bool encryption_function_call; extern THR_LOCAL unsigned char derive_vector_saved[RANDOM_LEN]; extern THR_LOCAL unsigned char mac_vector_saved[RANDOM_LEN]; extern THR_LOCAL unsigned char input_saved[RANDOM_LEN]; + errno_t errorno = EOK; + // 清空加密信息 if (encryption_function_call == true) { + // 将保存的派生向量、输入数据和 MAC 向量的内容全部置为零 errorno = memset_s(derive_vector_saved, RANDOM_LEN, 0, RANDOM_LEN); securec_check(errorno, "", ""); errorno = memset_s(input_saved, RANDOM_LEN, 0, RANDOM_LEN); securec_check(errorno, "", ""); errorno = memset_s(mac_vector_saved, RANDOM_LEN, 0, RANDOM_LEN); securec_check(errorno, "", ""); + + // 标记加密操作已完成 encryption_function_call = false; } + + // 清空解密信息 if (decryption_function_call == true) { + // 使用循环将每个保存的派生向量、用户输入数据和 MAC 向量的内容全部置为零 for (int i = 0; i < NUMBER_OF_SAVED_DERIVEKEYS; ++i) { errorno = memset_s(derive_vector_used[i], RANDOM_LEN, 0, RANDOM_LEN); securec_check(errorno, "", ""); @@ -1302,6 +1257,8 @@ void cleanup_sensitive_information() errorno = memset_s(mac_vector_used[i], RANDOM_LEN, 0, RANDOM_LEN); securec_check(errorno, "", ""); } + + // 标记解密操作已完成 decryption_function_call = false; } } @@ -1619,30 +1576,51 @@ static void ExecEndNodeByType(PlanState* node) break; } } + +/* +此函数用于结束执行节点,并执行一系列清理操作 +*/ void ExecEndNode(PlanState* node) { + // 如果节点为空,直接返回 if (node == NULL) { return; } + + // 清理敏感信息 cleanup_sensitive_information(); + + // 释放变更参数集合 if (node->chgParam != NULL) { bms_free_ext(node->chgParam); node->chgParam = NULL; } + + // 结束仪器的测量循环 if (node->instrument != NULL) { + // 如果是分布式数据节点,结束测量循环 if (IS_PGXC_DATANODE) { InstrEndLoop(node->instrument); } + + // 如果需要执行活动SQL操作,移除相应的解释信息 if (NeedExecuteActiveSql(node->plan)) { removeExplainInfo(node->plan->plan_node_id); } } + + // 在协调器上执行的且是最终消费者的情况下,结束测量循环 if (node->instrument != NULL && IS_PGXC_COORDINATOR && StreamTopConsumerAmI()) { InstrEndLoop(node->instrument); } + + // 如果需要对节点进行存根处理,执行相应的存根处理并返回 if (planstate_need_stub(node)) { ExecEndNodeStub(node); return; } + + // 执行特定类型节点的结束处理 ExecEndNodeByType(node); } + -- 2.34.1 From 58d7fc03e6a6525f331f8d4e18e9d3b9c10b53c2 Mon Sep 17 00:00:00 2001 From: LYLlyl Date: Sun, 20 Aug 2023 18:10:07 +0800 Subject: [PATCH 11/31] Update execUtils.cpp --- .../runtime/executor/execUtils.cpp | 485 ++++++++---------- 1 file changed, 208 insertions(+), 277 deletions(-) diff --git a/src/gausskernel/runtime/executor/execUtils.cpp b/src/gausskernel/runtime/executor/execUtils.cpp index 84fd23666..e6e59d64e 100644 --- a/src/gausskernel/runtime/executor/execUtils.cpp +++ b/src/gausskernel/runtime/executor/execUtils.cpp @@ -1,44 +1,44 @@ /* ------------------------------------------------------------------------- * * execUtils.cpp - * miscellaneous executor utility routines + * 杂项执行器实用程序函数 * - * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * Portions Copyright (c) 2021, openGauss Contributors + * 版权所有 (c) 2020 华为技术有限公司 + * 版权所有 (c) 1996-2012,PostgreSQL全球开发团队 + * 版权所有 (c) 1994,加州大学监管机构 + * 版权所有 (c) 2021,openGauss 社区贡献者 * * - * IDENTIFICATION + * 识别码 * src/gausskernel/runtime/executor/execUtils.cpp * * ------------------------------------------------------------------------- - * INTERFACE ROUTINES - * CreateExecutorState Create/delete executor working state + + * 接口函数 + * CreateExecutorState 创建/删除执行器工作状态 * FreeExecutorState * CreateExprContext * CreateStandaloneExprContext * FreeExprContext * ReScanExprContext * - * ExecAssignExprContext Common code for plan node init routines. + * ExecAssignExprContext 计划节点初始化例程的通用代码。 * ExecAssignResultType - * etc + * 等等 * - * ExecOpenScanRelation Common code for scan node init routines. + * ExecOpenScanRelation 扫描节点初始化例程的通用代码。 * ExecCloseScanRelation * * ExecOpenIndices \ - * ExecCloseIndices | referenced by InitPlan, EndPlan, - * ExecInsertIndexTuples / ExecInsert, ExecUpdate + * ExecCloseIndices | 被 InitPlan、EndPlan、ExecInsert、ExecUpdate 引用 + * ExecInsertIndexTuples / * - * RegisterExprContextCallback Register function shutdown callback - * UnregisterExprContextCallback Deregister function shutdown callback + * RegisterExprContextCallback 注册函数关机回调 + * UnregisterExprContextCallback 注销函数关机回调 * - * NOTES - * This file has traditionally been the place to stick misc. - * executor support stuff that doesn't really go anyplace else. - */ + * 注意 + * 这个文件传统上是放置一些其他地方不太适合的杂项执行器支持代码的地方。 + #include "postgres.h" #include "knl/knl_variable.h" @@ -74,30 +74,31 @@ static bool check_violation(Relation heap, Relation index, IndexInfo *indexInfo, Oid *conflictPartOid = NULL, int2 *conflictBucketid = NULL); /* ---------------------------------------------------------------- - * Executor state and memory management functions + * 执行器状态和内存管理函数 * ---------------------------------------------------------------- - */ + + + /* ---------------- * CreateExecutorState * - * Create and initialize an EState node, which is the root of - * working storage for an entire Executor invocation. + * 创建并初始化一个 EState 节点,它是整个执行器调用的工作存储的根节点。 * - * Principally, this creates the per-query memory context that will be - * used to hold all working data that lives till the end of the query. - * Note that the per-query context will become a child of the caller's - * CurrentMemoryContext. + * 主要来说,这个函数创建了一个用于存储整个查询期间持续存在的所有工作数据的每个查询内存上下文。 + * 需要注意的是,每个查询上下文将成为调用者的 CurrentMemoryContext 的子上下文。 * ---------------- */ + EState* CreateExecutorState(MemoryContext saveCxt) { EState* estate = NULL; MemoryContext qcontext; MemoryContext oldcontext; - /* - * Create the per-query context for this Executor run. + /* + * 为此 Executor 运行创建每个查询的上下文。 */ + if (saveCxt != NULL) { qcontext = saveCxt; } else { @@ -108,16 +109,16 @@ EState* CreateExecutorState(MemoryContext saveCxt) ALLOCSET_DEFAULT_MAXSIZE); } - /* - * Make the EState node within the per-query context. This way, we don't - * need a separate pfree_ext() operation for it at shutdown. + /* + * 在每个查询的上下文中创建 EState 节点。这样,我们在关闭时不需要单独的 pfree_ext() 操作。 */ + oldcontext = MemoryContextSwitchTo(qcontext); estate = makeNode(EState); - /* - * Initialize all fields of the Executor State structure + /* + * 初始化 Executor 状态结构的所有字段 */ estate->es_direction = ForwardScanDirection; estate->es_snapshot = SnapshotNow; @@ -146,7 +147,8 @@ EState* CreateExecutorState(MemoryContext saveCxt) estate->es_param_exec_vals = NULL; estate->es_query_cxt = qcontext; - estate->es_const_query_cxt = qcontext; /* context query context, it will not be changed */ + estate->es_const_query_cxt = qcontext;/* 查询上下文的上下文,它不会被更改 */ + estate->es_tupleTable = NIL; estate->es_epqTupleSlot = NULL; @@ -184,86 +186,77 @@ EState* CreateExecutorState(MemoryContext saveCxt) estate->pruningResult = NULL; - /* - * Return the executor state structure - */ + /* + * 返回执行器状态结构 + */ + MemoryContextSwitchTo(oldcontext); return estate; } -/* ---------------- - * FreeExecutorState +/* + * 释放EState及其所有剩余的工作存储空间。 * - * Release an EState along with all remaining working storage. + * 注意:这不负责释放非内存资源,如打开的关系或缓冲区引用。但它会关闭EState中的任何仍处于活动状态的ExprContext。 + * 这足够清理仅用于表达式求值而不用于运行完整计划的情况。 * - * Note: this is not responsible for releasing non-memory resources, - * such as open relations or buffer pins. But it will shut down any - * still-active ExprContexts within the EState. That is sufficient - * cleanup for situations where the EState has only been used for expression - * evaluation, and not to run a complete Plan. - * - * This can be called in any memory context ... so long as it's not one - * of the ones to be freed. - * ---------------- + * 这可以在任何内存上下文中调用...只要不是要释放的那些之一。 */ + void FreeExecutorState(EState* estate) { - /* - * Shut down and free any remaining ExprContexts. We do this explicitly - * to ensure that any remaining shutdown callbacks get called (since they - * might need to release resources that aren't simply memory within the - * per-query memory context). - */ + /* + * 关闭和释放任何剩余的ExprContexts。我们明确执行此操作以确保调用任何剩余的关闭回调(因为它们可能需要释放的资源不仅仅是在每个查询内存上下文中的内存)。 + */ + while (estate->es_exprcontexts) { - /* - * XXX: seems there ought to be a faster way to implement this than - * repeated list_delete(), no? - */ + /* + * XXX:似乎应该有一种比反复使用list_delete()更快的方法来实现这个操作,不是吗? + */ + FreeExprContext((ExprContext*)linitial(estate->es_exprcontexts), true); - /* FreeExprContext removed the list link for us */ + /* FreeExprContext 为我们移除了链表的链接 */ + } - /* - * Free the per-query memory context, thereby releasing all working - * memory, including the EState node itself. - */ + /* + * 释放 per-query 内存上下文,从而释放所有的工作内存,包括 EState 结构本身。 + */ + MemoryContextDelete(estate->es_query_cxt); } -/* ---------------- - * CreateExprContext +/* + * 创建一个在 EState 内部用于表达式评估的上下文。 * - * Create a context for expression evaluation within an EState. + * 执行程序运行可能需要多个 ExprContexts(通常我们为每个计划节点创建一个,还有一个单独的用于每个输出元组处理,例如约束检查)。 + * 每个 ExprContext 都有它自己的 "每个元组" 内存上下文。 * - * An executor run may require multiple ExprContexts (we usually make one - * for each Plan node, and a separate one for per-output-tuple processing - * such as constraint checking). Each ExprContext has its own "per-tuple" - * memory context. - * - * Note we make no assumption about the caller's memory context. - * ---------------- + * 注意,我们不对调用者的内存上下文做任何假设。 */ + ExprContext* CreateExprContext(EState* estate) { ExprContext* econtext = NULL; MemoryContext oldcontext; - /* Create the ExprContext node within the per-query memory context */ + /* 在每个查询的内存上下文中创建 ExprContext 节点 */ + oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); econtext = makeNode(ExprContext); - /* Initialize fields of ExprContext */ + /* 初始化 ExprContext 的字段 */ + econtext->ecxt_scantuple = NULL; econtext->ecxt_innertuple = NULL; econtext->ecxt_outertuple = NULL; econtext->ecxt_per_query_memory = estate->es_query_cxt; - /* - * Create working memory for expression evaluation in this context. - */ + /* 在该上下文中为表达式评估创建工作内存。 */ + econtext->ecxt_per_tuple_memory = AllocSetContextCreate(estate->es_query_cxt, "ExprContext", ALLOCSET_DEFAULT_MINSIZE, @@ -287,11 +280,7 @@ ExprContext* CreateExprContext(EState* estate) econtext->ecxt_callbacks = NULL; econtext->plpgsql_estate = NULL; - /* - * Link the ExprContext into the EState to ensure it is shut down when the - * EState is freed. Because we use lcons(), shutdowns will occur in - * reverse order of creation, which may not be essential but can't hurt. - */ + /* 将ExprContext链接到EState,以确保在释放EState时关闭它。由于我们使用lcons(),关闭将按照创建的相反顺序发生,这可能不是必需的,但不会有害。 */ estate->es_exprcontexts = lcons(econtext, estate->es_exprcontexts); MemoryContextSwitchTo(oldcontext); @@ -302,38 +291,31 @@ ExprContext* CreateExprContext(EState* estate) /* ---------------- * CreateStandaloneExprContext * - * Create a context for standalone expression evaluation. + * 创建一个用于独立表达式评估的上下文。 * - * An ExprContext made this way can be used for evaluation of expressions - * that contain no Params, subplans, or Var references (it might work to - * put tuple references into the scantuple field, but it seems unwise). + * 通过这种方式创建的ExprContext可用于评估不包含Params、子计划或Var引用的表达式(可能将元组引用放入scantuple字段是可行的,但似乎不明智)。 * - * The ExprContext struct is allocated in the caller's current memory - * context, which also becomes its "per query" context. + * ExprContext结构在调用者的当前内存上下文中分配,该内存上下文也成为其“每个查询”的上下文。 * - * It is caller's responsibility to free the ExprContext when done, - * or at least ensure that any shutdown callbacks have been called - * (ReScanExprContext() is suitable). Otherwise, non-memory resources - * might be leaked. + * 在完成后,调用者有责任释放ExprContext,或者至少确保已调用任何关闭回调函数(ReScanExprContext()是合适的)。否则,可能会泄漏非内存资源。 * ---------------- */ + ExprContext* CreateStandaloneExprContext(void) { ExprContext* econtext = NULL; - /* Create the ExprContext node within the caller's memory context */ + /* 在调用者的内存上下文中创建ExprContext节点 */ econtext = makeNode(ExprContext); - /* Initialize fields of ExprContext */ + /* 初始化ExprContext的字段 */ econtext->ecxt_scantuple = NULL; econtext->ecxt_innertuple = NULL; econtext->ecxt_outertuple = NULL; econtext->ecxt_per_query_memory = CurrentMemoryContext; - /* - * Create working memory for expression evaluation in this context. - */ + /* 在这个上下文中为表达式评估创建工作内存 */ econtext->ecxt_per_tuple_memory = AllocSetContextCreate(CurrentMemoryContext, "ExprContext", ALLOCSET_DEFAULT_MINSIZE, @@ -359,62 +341,55 @@ ExprContext* CreateStandaloneExprContext(void) return econtext; } -/* ---------------- - * FreeExprContext - * - * Free an expression context, including calling any remaining - * shutdown callbacks. - * - * Since we free the temporary context used for expression evaluation, - * any previously computed pass-by-reference expression result will go away! - * - * If isCommit is false, we are being called in error cleanup, and should - * not call callbacks but only release memory. (It might be better to call - * the callbacks and pass the isCommit flag to them, but that would require - * more invasive code changes than currently seems justified.) - * - * Note we make no assumption about the caller's memory context. - * ---------------- - */ +/* 释放表达式上下文,包括调用任何剩余的关闭回调函数。 + +由于我们释放了用于表达式评估的临时上下文,任何先前计算的传递引用表达式结果都会被清除! + +如果 isCommit 为 false,则我们是在错误清理中调用,不应调用回调函数,只能释放内存。 +(可能更好的方法是调用回调函数并将 isCommit 标志传递给它们,但这需要比当前看起来合理的更深入的代码更改。) + +注意,我们不对调用者的内存上下文作任何假设。 +*/ + void FreeExprContext(ExprContext* econtext, bool isCommit) { EState* estate = NULL; - /* Call any registered callbacks */ + /* 调用所有已注册的回调函数 */ ShutdownExprContext(econtext, isCommit); - /* And clean up the memory used */ + /* 然后清理使用的内存 */ MemoryContextDelete(econtext->ecxt_per_tuple_memory); - /* Unlink self from owning EState, if any */ + /* 如果有的话,从拥有它的 EState 中解除链接 */ estate = econtext->ecxt_estate; if (estate != NULL) estate->es_exprcontexts = list_delete_ptr(estate->es_exprcontexts, econtext); - /* And delete the ExprContext node */ + /* 然后删除 ExprContext 节点 */ pfree_ext(econtext); } /* * ReScanExprContext * - * Reset an expression context in preparation for a rescan of its - * plan node. This requires calling any registered shutdown callbacks, - * since any partially complete set-returning-functions must be canceled. + * 在重新扫描计划节点之前,重置表达式上下文。这需要调用任何已注册的关闭回调, + * 因为任何部分完成的返回集函数必须被取消。 * - * Note we make no assumption about the caller's memory context. + * 注意,我们不对调用者的内存上下文做任何假设。 */ + void ReScanExprContext(ExprContext* econtext) { - /* Call any registered callbacks */ + /* 调用任何已注册的回调函数 */ ShutdownExprContext(econtext, true); - /* And clean up the memory used */ + /* 清理使用的内存 */ MemoryContextReset(econtext->ecxt_per_tuple_memory); } /* - * Build a per-output-tuple ExprContext for an EState. + * 为 EState 构建一个每个输出元组的 ExprContext。 * - * This is normally invoked via GetPerTupleExprContext() macro, - * not directly. + * 通常通过 GetPerTupleExprContext() 宏调用,而不是直接调用。 */ + ExprContext* MakePerTupleExprContext(EState* estate) { if (estate->es_per_tuple_exprcontext == NULL) @@ -423,22 +398,19 @@ ExprContext* MakePerTupleExprContext(EState* estate) return estate->es_per_tuple_exprcontext; } -/* ---------------------------------------------------------------- - * miscellaneous node-init support functions +/* + * 杂项节点初始化支持函数 * - * Note: all of these are expected to be called with CurrentMemoryContext - * equal to the per-query memory context. - * ---------------------------------------------------------------- + * 注意:所有这些函数都期望在当前内存上下文为每个查询的内存上下文时调用。 */ -/* ---------------- - * ExecAssignExprContext + +/* + * ExecAssignExprContext * - * This initializes the ps_ExprContext field. It is only necessary - * to do this for nodes which use ExecQual or ExecProject - * because those routines require an econtext. Other nodes that - * don't have to evaluate expressions don't need to do this. - * ---------------- + * 初始化 ps_ExprContext 字段。只有使用 ExecQual 或 ExecProject 的节点需要这样做, + * 因为这些例程需要一个表达式上下文(econtext)。不需要评估表达式的其他节点不需要执行此操作。 */ + void ExecAssignExprContext(EState* estate, PlanState* planstate) { planstate->ps_ExprContext = CreateExprContext(estate); @@ -465,17 +437,15 @@ void ExecAssignResultTypeFromTL(PlanState* planstate, TableAmType tam) TupleDesc tupDesc; if (ExecContextForcesOids(planstate, &hasoid)) { - /* context forces OID choice; hasoid is now set correctly */ + /* context 强制 OID 选择;现在 hasoid 被正确设置 */ } else { - /* given free choice, don't leave space for OIDs in result tuples */ + /* 在给定自由选择的情况下,不要在结果元组中留出 OID 的空间 */ hasoid = false; } - /* - * ExecTypeFromTL needs the parse-time representation of the tlist, not a - * list of ExprStates. This is good because some plan nodes don't bother - * to set up planstate->targetlist ... - */ + /* ExecTypeFromTL 需要 tlist 的解析时表示,而不是 ExprStates 的列表。 + * 这很好,因为某些计划节点不会费心设置 planstate->targetlist ... + */ tupDesc = ExecTypeFromTL(planstate->plan->targetlist, hasoid, false, tam); ExecAssignResultType(planstate, tupDesc); } @@ -487,7 +457,7 @@ void ExecAssignResultTypeFromTL(PlanState* planstate, TableAmType tam) TupleDesc ExecGetResultType(PlanState* planstate) { TupleTableSlot* slot = NULL; - /* if the child node is PartIteratorState, overhead to it's child node */ + /* 如果子节点是 PartIteratorState,则将开销传递给其子节点 */ if (IsA(planstate, PartIteratorState) || IsA(planstate, VecPartIteratorState)) { planstate = outerPlanState(planstate); } @@ -516,8 +486,9 @@ void ExecAssignVectorForExprEval(ExprContext* econtext) econtext->caseValue_vector->init(CurrentMemoryContext, unknownDesc); } -/* Support info for column store.*/ -/* targetList is given from ExprState tree, qual is given from Expr node tree.*/ +/* 用于列存储的支持信息。 */ + +/* targetList 是从 ExprState 树中获取的,qual 是从 Expr 节点树中获取的。 */ static void GetAccessedVarNumbers(ProjectionInfo* projInfo, List* targetList, List* qual) { List* vars = NIL; @@ -534,7 +505,7 @@ static void GetAccessedVarNumbers(ProjectionInfo* projInfo, List* targetList, Li GenericExprState* gstate = (GenericExprState*)lfirst(l); TargetEntry* tle = (TargetEntry*)gstate->xprstate.expr; - /* Pull vars from the targetlist .*/ + /* 从目标列表中提取变量。 */ vars = pull_var_clause((Node*)tle, PVC_RECURSE_AGGREGATES, PVC_RECURSE_PLACEHOLDERS); foreach (vl, vars) { @@ -550,12 +521,12 @@ static void GetAccessedVarNumbers(ProjectionInfo* projInfo, List* targetList, Li } } - /* - * Used for PackT optimization: PackTCopyVarsList records those columns what we need to move. - */ + /* + * 用于 PackT 优化:PackTCopyVarsList 记录需要移动的那些列。 + */ List* PackTCopyVarsList = list_copy(varattno_list); - /* Now consider the quals */ + /* 现在考虑条件表达式(quals) */ vars = pull_var_clause((Node*)qual, PVC_RECURSE_AGGREGATES, PVC_RECURSE_PLACEHOLDERS); foreach (l, vars) { Var* var = (Var*)lfirst(l); @@ -573,10 +544,9 @@ static void GetAccessedVarNumbers(ProjectionInfo* projInfo, List* targetList, Li isConst = true; } - // Now we need get which var can be late accessed. - // In other words, these columns can be load after filter - // We can read these columns as late as possible - // + // 现在我们需要确定哪些变量可以被延迟访问。 +// 换句话说,这些列可以在过滤后加载。 +// 我们可以尽可能晚地读取这些列。 if (qualVarNoList != NIL) { lateAccessVarNoList = list_difference_int(varattno_list, qualVarNoList); list_free_ext(qualVarNoList); @@ -586,8 +556,8 @@ static void GetAccessedVarNumbers(ProjectionInfo* projInfo, List* targetList, Li PackLateAccessList = list_difference_int(PackTCopyVarsList, lateAccessVarNoList); } - /* - * Here projInfo->pi_PackTCopyVars records the specific column data what we want. + /* + * 这里,projInfo->pi_PackTCopyVars 记录了我们想要的特定列数据。 */ projInfo->pi_PackTCopyVars = PackTCopyVarsList; projInfo->pi_acessedVarNumbers = varattno_list; @@ -601,7 +571,7 @@ List* GetAccessedVarnoList(List* targetList, List* qual) { ProjectionInfo tmp_pi; - /* get accessed attno of this query statement */ + /* 获取此查询语句的已访问的属性号(列号) */ GetAccessedVarNumbers(&tmp_pi, targetList, qual); if (tmp_pi.pi_PackTCopyVars) { list_free_ext(tmp_pi.pi_PackTCopyVars); @@ -623,14 +593,14 @@ ProjectionInfo* ExecBuildVecProjectionInfo( bool directMap = false; ListCell* tl = NULL; - // Guard for zero length projection + // 保护零长度投影 // if (len == 0) return NULL; projInfo->pi_exprContext = econtext; projInfo->pi_slot = slot; - /* since these are all int arrays, we need do just one palloc */ + // 由于这些都是整数数组,我们只需要执行一次 palloc 操作 workspace = (int*)palloc(len * 3 * sizeof(int)); projInfo->pi_varSlotOffsets = varSlotOffsets = workspace; projInfo->pi_varNumbers = varNumbers = workspace + len; @@ -638,20 +608,18 @@ ProjectionInfo* ExecBuildVecProjectionInfo( projInfo->pi_lastInnerVar = 0; projInfo->pi_lastOuterVar = 0; projInfo->pi_lastScanVar = 0; - /* Support info for column store.*/ + /* 列存储的支持信息 */ GetAccessedVarNumbers(projInfo, targetList, nt_qual); - // Allocate batch for current project. + // 为当前的投影操作分配批处理内存。 // projInfo->pi_batch = New(CurrentMemoryContext) VectorBatch(CurrentMemoryContext, slot->tts_tupleDescriptor); - /* - * We separate the target list elements into simple Var references and - * expressions which require the full ExecTargetList machinery. To be a - * simple Var, a Var has to be a user attribute and not mismatch the - * inputDesc. (Note: if there is a type mismatch then ExecEvalVar will - * probably throw an error at runtime, but we leave that to it.) - */ + /* + * 我们将目标列表元素分为简单的 Var 引用和需要完整 ExecTargetList 机制的表达式。 + * 要成为一个简单的 Var,Var 必须是用户属性,并且不与输入描述不匹配。 + * (注意:如果存在类型不匹配,那么 ExecEvalVar 在运行时可能会引发错误,但我们将其留给它处理。) + */ exprlist = NIL; numSimpleVars = 0; directMap = true; @@ -662,7 +630,7 @@ ProjectionInfo* ExecBuildVecProjectionInfo( if (variable != NULL && IsA(variable, Var) && variable->varattno > 0) { if (!inputDesc) - isSimpleVar = true; /* can't check type, assume OK */ + isSimpleVar = true; /* 无法检查类型,假设是没问题的 */ else if (variable->varattno <= inputDesc->natts) { Form_pg_attribute attr; @@ -702,9 +670,9 @@ ProjectionInfo* ExecBuildVecProjectionInfo( } numSimpleVars++; } else { - /* Not a simple variable, add it to generic targetlist */ + /* 不是一个简单的变量,将其添加到通用目标列表中 */ exprlist = lappend(exprlist, gstate); - /* Examine expr to include contained Vars in lastXXXVar counts */ + /* 检查表达式以包括在 lastXXXVar 计数中包含的变量 */ get_last_attnums((Node*)variable, projInfo); } } @@ -735,20 +703,12 @@ ProjectionInfo* ExecBuildVecProjectionInfo( return projInfo; } -/* ---------------- - * ExecBuildProjectionInfo - * - * Build a ProjectionInfo node for evaluating the given tlist in the given - * econtext, and storing the result into the tuple slot. (Caller must have - * ensured that tuple slot has a descriptor matching the tlist!) Note that - * the given tlist should be a list of ExprState nodes, not Expr nodes. - * - * inputDesc can be NULL, but if it is not, we check to see whether simple - * Vars in the tlist match the descriptor. It is important to provide - * inputDesc for relation-scan plan nodes, as a cross check that the relation - * hasn't been changed since the plan was made. At higher levels of a plan, - * there is no need to recheck. - * ---------------- +/* 构建 ProjectionInfo 结构,用于在给定的 econtext 中计算给定的 tlist,并将结果存储到元组槽中。 + * (调用者必须确保元组槽具有与 tlist 匹配的描述符!)注意,给定的 tlist 应该是 ExprState 节点的列表,而不是 Expr 节点。 + + * inputDesc 可以为 NULL,但如果不为 NULL,则我们会检查 tlist 中的简单变量是否与描述符匹配。 + * 为了关系扫描计划节点,提供 inputDesc 是很重要的,因为它是检查关系在计划生成后是否发生了更改的交叉检查。 + * 在计划的更高级别,无需重新检查。 */ ProjectionInfo* ExecBuildProjectionInfo( List* targetList, ExprContext* econtext, TupleTableSlot* slot, TupleDesc inputDesc) @@ -766,7 +726,7 @@ ProjectionInfo* ExecBuildProjectionInfo( projInfo->pi_exprContext = econtext; projInfo->pi_slot = slot; - /* since these are all int arrays, we need do just one palloc */ + /* 由于这些都是 int 数组,我们只需要进行一次内存分配(palloc) */ workspace = (int*)palloc(len * 3 * sizeof(int)); projInfo->pi_varSlotOffsets = varSlotOffsets = workspace; projInfo->pi_varNumbers = varNumbers = workspace + len; @@ -775,13 +735,11 @@ ProjectionInfo* ExecBuildProjectionInfo( projInfo->pi_lastOuterVar = 0; projInfo->pi_lastScanVar = 0; - /* - * We separate the target list elements into simple Var references and - * expressions which require the full ExecTargetList machinery. To be a - * simple Var, a Var has to be a user attribute and not mismatch the - * inputDesc. (Note: if there is a type mismatch then ExecEvalScalarVar - * will probably throw an error at runtime, but we leave that to it.) - */ + /* + * 我们将目标列表元素分为简单的 Var 引用和需要完整的 ExecTargetList 机制的表达式。 + * 要成为简单的 Var,Var 必须是用户属性并且不与 inputDesc 不匹配。 + * (注意:如果存在类型不匹配,则 ExecEvalScalarVar 可能会在运行时引发错误,但我们将其交给它处理。) + */ exprlist = NIL; numSimpleVars = 0; directMap = true; @@ -835,7 +793,7 @@ ProjectionInfo* ExecBuildProjectionInfo( } else { /* Not a simple variable, add it to generic targetlist */ exprlist = lappend(exprlist, gstate); - /* Examine expr to include contained Vars in lastXXXVar counts */ + /* 检查表达式以包含在 lastXXXVar 计数中包含的变量 */ get_last_attnums((Node*)variable, projInfo); } } @@ -852,10 +810,9 @@ ProjectionInfo* ExecBuildProjectionInfo( } /* - * get_last_attnums: expression walker for ExecBuildProjectionInfo + * get_last_attnums: 用于 ExecBuildProjectionInfo 的表达式遍历器 * - * Update the lastXXXVar counts to be at least as large as the largest - * attribute numbers found in the expression + * 更新 lastXXXVar 计数,使其至少与表达式中发现的最大属性号一样大 */ static bool get_last_attnums(Node* node, ProjectionInfo* projInfo) { @@ -885,12 +842,10 @@ static bool get_last_attnums(Node* node, ProjectionInfo* projInfo) return false; } - /* - * Don't examine the arguments of Aggrefs or WindowFuncs, because those do - * not represent expressions to be evaluated within the overall - * overall targetlist's econtext. GroupingFunc arguments are never - * evaluated at all. - */ + /* + * 不要检查 Aggrefs 或 WindowFuncs 的参数,因为它们不表示在整体目标列表的 econtext 中要评估的表达式。 + * GroupingFunc 参数根本不会被评估。 + */ if (IsA(node, Aggref) || IsA(node, GroupingFunc)) return false; if (IsA(node, WindowFunc)) @@ -898,55 +853,37 @@ static bool get_last_attnums(Node* node, ProjectionInfo* projInfo) return expression_tree_walker(node, (bool (*)())get_last_attnums, (void*)projInfo); } -/* ---------------- - * ExecAssignProjectionInfo +/* + * 从节点的目标列表中形成投影信息 * - * forms the projection information from the node's targetlist - * - * Notes for inputDesc are same as for ExecBuildProjectionInfo: supply it - * for a relation-scan node, can pass NULL for upper-level nodes - * ---------------- + * 输入 inputDesc 的注意事项与 ExecBuildProjectionInfo 相同:为关系扫描节点提供它,对于上层节点可以传递 NULL */ + void ExecAssignProjectionInfo(PlanState* planstate, TupleDesc inputDesc) { planstate->ps_ProjInfo = ExecBuildProjectionInfo( planstate->targetlist, planstate->ps_ExprContext, planstate->ps_ResultTupleSlot, inputDesc); } -/* ---------------- - * ExecFreeExprContext +/* + * 在执行器关闭时,需要显式地释放计划节点的 ExprContext,因为可能有需要调用的关闭回调函数。(上述例程创建的其他资源,如投影信息,不需要显式释放,因为它们只是在每个查询的内存上下文中的内存。) * - * A plan node's ExprContext should be freed explicitly during executor - * shutdown because there may be shutdown callbacks to call. (Other resources - * made by the above routines, such as projection info, don't need to be freed - * explicitly because they're just memory in the per-query memory context.) - * - * However ... there is no particular need to do it during ExecEndNode, - * because FreeExecutorState will free any remaining ExprContexts within - * the EState. Letting FreeExecutorState do it allows the ExprContexts to - * be freed in reverse order of creation, rather than order of creation as - * will happen if we delete them here, which saves O(N^2) work in the list - * cleanup inside FreeExprContext. - * ---------------- + * 然而... 没有特定的需要在 ExecEndNode 期间执行它,因为 FreeExecutorState 将在 EState 中释放所有剩余的 ExprContext。让 FreeExecutorState 执行它允许 ExprContexts 按创建的相反顺序进行释放,而不是按创建顺序进行释放,这可以节省在 FreeExprContext 内部的列表清理中的 O(N^2) 的工作。 */ + void ExecFreeExprContext(PlanState* planstate) { - /* - * Per above discussion, don't actually delete the ExprContext. We do - * unlink it from the plan node, though. - */ + /* + * 根据上述讨论,实际上不要删除 ExprContext。但我们会将其从计划节点中取消链接。 + */ planstate->ps_ExprContext = NULL; } -/* ---------------------------------------------------------------- - * the following scan type support functions are for - * those nodes which are stubborn and return tuples in - * their Scan tuple slot instead of their Result tuple - * slot.. luck fur us, these nodes do not do projections - * so we don't have to worry about getting the ProjectionInfo - * right for them... -cim 6/3/91 - * ---------------------------------------------------------------- +/* + * 以下的扫描类型支持函数是为了那些顽固的节点,它们将元组返回到它们的扫描元组槽而不是结果元组槽中。 + * 幸运的是,这些节点不进行投影操作,所以我们不必担心为它们正确获取 ProjectionInfo。 -cim 6/3/91 */ + /* ---------------- * ExecGetScanType * ---------------- @@ -991,8 +928,7 @@ void ExecAssignScanTypeFromOuterPlan(ScanState* scanstate) /* ---------------------------------------------------------------- * ExecRelationIsTargetRelation * - * Detect whether a relation (identified by rangetable index) - * is one of the target relations of the query. + * 检测一个关系(通过范围表索引标识)是否是查询的目标关系之一。 * ---------------------------------------------------------------- */ bool ExecRelationIsTargetRelation(EState* estate, Index scanrelid) @@ -1008,16 +944,11 @@ bool ExecRelationIsTargetRelation(EState* estate, Index scanrelid) return false; } -/* ---------------------------------------------------------------- - * ExecOpenScanRelation +/* ExecOpenScanRelation + + * 在基本级别的扫描计划节点要扫描的堆关系上打开扫描。应该在节点的ExecInit例程中调用此函数。 * - * Open the heap relation to be scanned by a base-level scan plan node. - * This should be called during the node's ExecInit routine. - * - * By default, this acquires AccessShareLock on the relation. However, - * if the relation was already locked by InitPlan, we don't need to acquire - * any additional lock. This saves trips to the shared lock manager. - * ---------------------------------------------------------------- + * 默认情况下,这会在关系上获取AccessShareLock。但是,如果关系已经被InitPlan锁定,我们就不需要获取任何其他锁定。这可以节省共享锁管理器的访问。 */ Relation ExecOpenScanRelation(EState* estate, Index scanrelid) { @@ -1025,11 +956,11 @@ Relation ExecOpenScanRelation(EState* estate, Index scanrelid) LOCKMODE lockmode; Relation rel; - /* - * Determine the lock type we need. First, scan to see if target relation - * is a result relation. If not, check if it's a FOR UPDATE/FOR SHARE - * relation. In either of those cases, we got the lock already. - */ + /* + * 确定我们需要的锁定类型。首先,扫描以查看目标关系是否是结果关系。 + * 如果不是,检查它是否是一个FOR UPDATE/FOR SHARE关系。 + * 在这两种情况下,我们已经获取了锁定。 + */ lockmode = AccessShareLock; if (ExecRelationIsTargetRelation(estate, scanrelid)) lockmode = NoLock; @@ -1039,7 +970,7 @@ Relation ExecOpenScanRelation(EState* estate, Index scanrelid) foreach (l, estate->es_rowMarks) { ExecRowMark* erm = (ExecRowMark*)lfirst(l); - /* Keep this check in sync with InitPlan! */ + /* 保持这个检查与InitPlan同步! */ if (erm->rti == scanrelid && erm->relation != NULL) { lockmode = NoLock; break; @@ -1047,12 +978,12 @@ Relation ExecOpenScanRelation(EState* estate, Index scanrelid) } } - /* Open the relation and acquire lock as needed */ + /* 打开关系并根据需要获取锁定 */ reloid = getrelid(scanrelid, estate->es_range_table); rel = heap_open(reloid, lockmode); if (STMT_RETRY_ENABLED) { - // do noting for now, if query retry is on, just to skip validateTempRelation here + // 目前什么都不做,如果查询重试已启用,则跳过在这里进行 validateTempRelation 操作 } else validateTempRelation(rel); @@ -1062,17 +993,17 @@ Relation ExecOpenScanRelation(EState* estate, Index scanrelid) /* ---------------------------------------------------------------- * ExecCloseScanRelation * - * Close the heap relation scanned by a base-level scan plan node. - * This should be called during the node's ExecEnd routine. + * 关闭由基础级扫描计划节点扫描的堆关系。 + * 应该在节点的 ExecEnd 例程中调用此函数。 * - * Currently, we do not release the lock acquired by ExecOpenScanRelation. - * This lock should be held till end of transaction. (There is a faction - * that considers this too much locking, however.) + * 目前,我们不会释放由 ExecOpenScanRelation 获取的锁。 + * 这个锁应该保持到事务结束。(有人认为这是过多的锁定,但也有人持相反意见。) * - * If we did want to release the lock, we'd have to repeat the logic in - * ExecOpenScanRelation in order to figure out what to release. + * 如果我们确实想要释放这个锁,我们需要重复 ExecOpenScanRelation 中的逻辑, + * 以便弄清楚需要释放哪些资源。 * ---------------------------------------------------------------- */ + void ExecCloseScanRelation(Relation scanrel) { heap_close(scanrel, NoLock); @@ -1080,14 +1011,14 @@ void ExecCloseScanRelation(Relation scanrel) /* * @@GaussDB@@ - * Target : data partition - * Brief : Open the heap partition to be scanned by a base-level scan plan - * : node. This should be called during the node's ExecInit routine. - * Description : - * Notes : By default, this acquires AccessShareLock on the partitioned relation. - * : However, if the relation was already locked by InitPlan, we don't need - * : to acquire any additional lock. This saves trips to the shared lock manager. + * 目标:数据分区 + * 简述:打开由基础级扫描计划节点扫描的堆分区关系。这应该在节点的 ExecInit 例程中调用。 + * 描述: + * 注意:默认情况下,这会在分区关系上获取 AccessShareLock。 + * 但是,如果关系已经被 InitPlan 锁定,我们就不需要获取任何额外的锁。 + * 这可以减少到共享锁管理器的访问次数。 */ + Partition ExecOpenScanParitition(EState* estate, Relation parent, PartitionIdentifier* partID, LOCKMODE lockmode) { Oid partoid = InvalidOid; @@ -1096,7 +1027,7 @@ Partition ExecOpenScanParitition(EState* estate, Relation parent, PartitionIdent Assert(PointerIsValid(parent)); Assert(PointerIsValid(partID)); - /* OK, open the relation and acquire lock as needed */ + /* 打开关系并根据需要获取锁 */ partoid = partIDGetPartOid(parent, partID); return partitionOpen(parent, partoid, lockmode); -- 2.34.1 From 41d7a01e33595f6cd006e58d6d29c576b350c0fd Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Mon, 21 Aug 2023 23:42:15 +0800 Subject: [PATCH 12/31] Update execReplication.cpp --- .../runtime/executor/execReplication.cpp | 81 ++++++++++++++----- 1 file changed, 60 insertions(+), 21 deletions(-) diff --git a/src/gausskernel/runtime/executor/execReplication.cpp b/src/gausskernel/runtime/executor/execReplication.cpp index b67583339..ec8ab4de1 100644 --- a/src/gausskernel/runtime/executor/execReplication.cpp +++ b/src/gausskernel/runtime/executor/execReplication.cpp @@ -1,10 +1,10 @@ /* ------------------------------------------------------------------------- * * execReplication.cpp - * miscellaneous executor routines for logical replication + * 用于逻辑复制的杂项执行程序例程 * - * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California + * 部分版权所有(c) 1996-2021, PostgreSQL全球发展集团 + * 版权所有(c) 1994,加州大学董事会 * * * IDENTIFICATION @@ -45,13 +45,12 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple TupleTableSlot *outslot, FakeRelationPartition *fakeRelPart); /* - * Setup a ScanKey for a search in the relation 'rel' for a tuple 'key' that - * is setup to match 'rel' (*NOT* idxrel!). + * 在关系“rel”中为元组“key”设置一个ScanKey,用于搜索 + 被设置为匹配'rel'(*不匹配* idxrel!)。 * - * Returns whether any column contains NULLs. + * 返回任何列是否包含null。 * - * This is not generic routine, it expects the idxrel to be replication - * identity of a rel and meet all limitations associated with that. + * 这不是一个通用例程,它期望idxrel是一个rel的复制标识,并满足与之相关的所有限制。 */ static bool build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel, TupleTableSlot *searchslot) { @@ -66,7 +65,7 @@ static bool build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel Assert(!isnull); opclass = (oidvector *)DatumGetPointer(indclassDatum); - /* Build scankey for every attribute in the index. */ + /*为索引中的每个属性构建scankey。 */ for (attoff = 0; attoff < IndexRelationGetNumberOfKeyAttributes(idxrel); attoff++) { Oid op; Oid opfamily; @@ -79,10 +78,17 @@ static bool build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel errmsg("index key attribute number %d exceeds number of columns %d", mainattno, searchslot->tts_tupleDescriptor->natts))); } +/* 这段代码片段是一个循环,它遍历索引的键属性。下面是它的功能细分: - /* - * Load the operator info. We need this to get the equality operator - * function for the scan key. +1. 循环从“attoff = 0”迭代到“IndexRelationGetNumberOfKeyAttributes(idxrel)”。 +2. 在每次迭代中,它执行以下步骤 : + -检索与该属性关联的操作符、操作符族和注册过程。 + —计算主键和主索引的属性号。 + - 检索操作符类的输入类型。 + —检查主属性号是否超过搜索槽元组描述符的列数。如果是,则会引发错误。* / + + /* + *加载操作员信息。我们需要这个来获得扫描键的相等运算符函数。 */ opfamily = get_opclass_family(opclass->values[attoff]); @@ -91,12 +97,18 @@ static bool build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", BTEqualStrategyNumber, optype, optype, opfamily); regop = get_opcode(op); +/*在给定的代码片段中,在错误检查之后执行以下步骤: - /* Initialize the scankey. */ +1. 它使用' get_opclass_family '函数检索操作符族,并将' opclass->values[attoff] '值作为参数传递。 +2. 然后检索与操作符族、输入类型和相等策略号相关联的操作符。这是使用' get_opfamily_member '函数完成的,传递' opfamily ', ' optype ', ' optype '和' BTEqualStrategyNumber '值作为参数。 +3.它使用' OidIsValid '函数检查检索到的操作符是否有效。如果无效,则会使用' elog '引发错误。 +4. 最后,它使用' get_opcode '函数为操作符检索已注册的过程,并将' op '值作为参数传递。*/ + + /* 初始化扫描键。 */ ScanKeyInit(&skey[attoff], pkattno, BTEqualStrategyNumber, regop, searchslot->tts_values[mainattno - 1]); skey[attoff].sk_collation = idxrel->rd_indcollation[attoff]; - /* Check for null value. */ + /* 检查是否为空值。 */ if (searchslot->tts_isnull[mainattno - 1]) { hasnulls = true; skey[attoff].sk_flags |= SK_ISNULL; @@ -105,15 +117,23 @@ static bool build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel return hasnulls; } +/*在给定的代码片段中,以下是代码的执行流程: -/* Check tableam_tuple_lock result, and return if need to retry */ +1. 检查`searchslot->tts_isnull[mainattno - 1]`是否为真,即检查主键属性是否为NULL。 +2. 如果主键属性为NULL,则将`hasnulls`标志设置为`true`,表示存在NULL值。 +3. 将`skey[attoff].sk_flags`的`SK_ISNULL`标志位设置为1,表示该属性为NULL。 +4. 循环结束后,返回`hasnulls`的值,表示是否存在NULL值。 + +该代码段的目的是检查索引的主键属性是否包含NULL值,并相应地设置标志位。 */ + +/* 检查tableam_tuple_lock结果,如果需要重试则返回 */ static bool inline CheckTupleLockRes(TM_Result res) { switch (res) { case TM_Ok: break; case TM_Updated: - /* XXX: Improve handling here */ + /* XXX:改进这里的操作 */ ereport(LOG, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("concurrent update, retrying"))); return true; case TM_Invisible: @@ -125,13 +145,21 @@ static bool inline CheckTupleLockRes(TM_Result res) } return false; } +/*这段代码定义了一个名为CheckTupleLockRes的静态内联函数,其作用是检查给定的TM_Result结果,并根据不同的结果进行相应的处理。以下是代码的执行流程: -/* Check heap modify result */ +根据给定的res结果进行switch语句的判断。 +如果结果为TM_Ok,则直接跳过,不进行任何处理。 +如果结果为TM_Updated,则输出一条日志信息,并返回true,表示需要进行重试。 +如果结果为TM_Invisible,则输出一条错误信息,表示试图锁定一个不可见的元组。 +如果结果为其他值,则输出一条错误信息,表示出现了意外的heap_lock_tuple状态。 +最后,函数返回false,表示不需要进行重试*/ + +/* 检查堆修改结果 */ static void inline CheckTupleModifyRes(TM_Result res) { switch (res) { case TM_SelfModified: - /* Tuple was already updated in current command? */ + /* 元组已在当前命令中更新? */ ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("tuple already updated by self"))); break; case TM_Ok: @@ -154,21 +182,32 @@ static inline List* GetPartitionList(Relation rel, LOCKMODE lockmode) return relationGetPartitionList(rel, lockmode); } } +/*CheckTupleModifyRes函数是一个静态内联函数,用于检查给定的TM_Result结果,并根据不同的结果输出相应的错误信息。执行流程如下: + +如果结果为TM_SelfModified,表示元组已经在当前命令中被更新,输出一条错误信息。 +如果结果为TM_Ok,则直接跳过,不进行任何处理。 +如果结果为TM_Updated或TM_Deleted,表示元组同时被其他事务并发地更新或删除,输出一条错误信息。 +如果结果为其他值,则输出一条错误信息,表示出现了未识别的元组状态。 +GetPartitionList函数是一个内联函数,根据给定的关系和锁模式获取分区列表。执行流程如下: + +如果关系是子分区化的(subpartitioned),则调用RelationGetSubPartitionList函数获取子分区列表,并返回结果。 +如果关系不是子分区化的,则调用relationGetPartitionList函数获取分区列表,并返回结果。 +这些函数的目的是用于处理元组修改和获取分区列表的相关操作*/ static bool PartitionFindReplTupleByIndex(EState *estate, Relation rel, Relation idxrel, LockTupleMode lockmode, TupleTableSlot *searchslot, TupleTableSlot *outslot, FakeRelationPartition *fakeRelInfo) { - /* must be non-GPI index */ + /* 必须是非GPI指数 */ Assert(!RelationIsGlobalIndex(idxrel)); fakeRelInfo->partList = GetPartitionList(rel, RowExclusiveLock); - /* search the tuple in partition list one by one */ + /* 在分区列表中逐个搜索元组 */ ListCell *cell = NULL; foreach (cell, fakeRelInfo->partList) { Partition heapPart = (Partition)lfirst(cell); Relation partionRel = RelationIsSubPartitioned(rel) ? SubPartitionGetRelation(rel, heapPart, NoLock) : partitionGetRelation(rel, heapPart); - /* Get index partition of this heap partition */ + /* 获取此堆分区的索引分区 */ Oid idxPartOid = getPartitionIndexOid(RelationGetRelid(idxrel), heapPart->pd_id); Partition idxPart = partitionOpen(idxrel, idxPartOid, RowExclusiveLock); Relation idxPartRel = RelationIsSubPartitioned(rel) ? SubPartitionGetRelation(idxrel, idxPart, NoLock) : -- 2.34.1 From ffe1c046264712272860d61f1cd656ad2d980716 Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Wed, 23 Aug 2023 00:47:19 +0800 Subject: [PATCH 13/31] Update execReplication.cpp --- .../runtime/executor/execReplication.cpp | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/gausskernel/runtime/executor/execReplication.cpp b/src/gausskernel/runtime/executor/execReplication.cpp index ec8ab4de1..20d259c89 100644 --- a/src/gausskernel/runtime/executor/execReplication.cpp +++ b/src/gausskernel/runtime/executor/execReplication.cpp @@ -218,24 +218,28 @@ static bool PartitionFindReplTupleByIndex(EState *estate, Relation rel, Relation fakeRelInfo->partOid = heapPart->pd_id; if (RelationFindReplTupleByIndex(estate, rel, idxPartRel, lockmode, searchslot, outslot, fakeRelInfo)) { - /* Hit, release index resource, heap partition need to be used later, so don't release it */ + /* 命中,释放索引资源,堆分区需要以后使用,所以不要释放它 */ partitionClose(idxrel, idxPart, NoLock); releaseDummyRelation(&idxPartRel); - /* caller shoud release partRel */ + /* 调用方应释放部件Rel */ fakeRelInfo->needRleaseDummyRel = true; return true; } - /* didn't find tuple in current partition, release dummy relation and switch to next partition */ + /* 在当前分区中没有找到元组,释放虚拟关系并切换到下一个分区 */ releaseDummyRelation(&fakeRelInfo->partRel); partitionClose(idxrel, idxPart, NoLock); releaseDummyRelation(&idxPartRel); } - /* do not find tuple in any patition, close and return */ + /* 没有找到元组在任何分区,关闭和返回 */ releasePartitionList(rel, &fakeRelInfo->partList, NoLock); return false; } + /* 这段代码是在分区表中根据索引查找元组的函数。首先,它断言索引不是全局分区索引。然后,它获取分区列表,并使用foreach循环遍历每个分区。 +在循环中,它获取当前分区的关系,并根据索引和分区的ID获取索引分区的OID。然后,它打开索引分区的关系,并将相关的信息设置到fakeRelInfo结构中。 +接下来,它调用RelationFindReplTupleByIndex函数来在当前分区的索引中查找匹配的元组。如果找到了匹配的元组,它会关闭索引分区的关系,并释放相关的资源。 +最后,它将fakeRelInfo->needRleaseDummyRel设置为true,表示调用方需要释放虚拟关系,并返回true表示找到了匹配的元组。*/ static bool PartitionFindReplTupleSeq(Relation rel, LockTupleMode lockmode, TupleTableSlot *searchslot, TupleTableSlot *outslot, FakeRelationPartition *fakeRelInfo) @@ -252,25 +256,29 @@ static bool PartitionFindReplTupleSeq(Relation rel, LockTupleMode lockmode, fakeRelInfo->partOid = heapPart->pd_id; if (RelationFindReplTupleSeq(rel, lockmode, searchslot, outslot, fakeRelInfo)) { - /* caller shoud release partRel */ + /* 调用方应释放部件Rel */ fakeRelInfo->needRleaseDummyRel = true; return true; } releaseDummyRelation(&fakeRelInfo->partRel); } - /* do not find tuple in any patition, close and return */ + /* 没有找到元组在任何分区,关闭和返回 */ releasePartitionList(rel, &fakeRelInfo->partList, NoLock); return false; } +/* 这段代码是在分区表中按顺序查找元组的函数。它首先获取分区列表,并使用foreach循环遍历每个分区。 +在循环中,它获取当前分区的关系,并将相关的信息设置到fakeRelInfo结构中。 +然后,它调用RelationFindReplTupleSeq函数来在当前分区中按顺序查找匹配的元组。如果找到了匹配的元组,它将fakeRelInfo->needRleaseDummyRel设置为true,表示调用方需要释放虚拟关系,并返回true表示找到了匹配的元组。 +如果在当前分区中没有找到匹配的元组,它会释放虚拟关系。 +最后,如果在所有分区中都没有找到匹配的元组,它会释放分区列表,并返回false表示没有找到匹配的元组。*/ /* - * Search the relation 'rel' for tuple using the index or seq scan. + * 使用索引或序列扫描搜索关系'rel'查找元组。 * - * If a matching tuple is found, lock it with lockmode, fill the slot with its - * contents, and return true. Return false otherwise. + * 如果找到匹配的元组,用lockmode锁定它,用它的内容填充槽,并返回true。否则返回false。 * - * Caller should check and release fakeRelInfo->partList and fakeRelInfo->partRel + * 调用者应该检查并释放fakeRelInfo->partList和fakeRelInfo-> parttrel */ bool RelationFindReplTuple(EState *estate, Relation rel, Oid idxoid, LockTupleMode lockmode, TupleTableSlot *searchslot, TupleTableSlot *outslot, FakeRelationPartition *fakeRelInfo) @@ -279,7 +287,7 @@ bool RelationFindReplTuple(EState *estate, Relation rel, Oid idxoid, LockTupleMo bool found = false; Relation idxrel = NULL; - /* clear fake rel info */ + /* 清除假rel信息 */ rc = memset_s(fakeRelInfo, sizeof(FakeRelationPartition), 0, sizeof(FakeRelationPartition)); securec_check(rc, "", ""); @@ -287,7 +295,7 @@ bool RelationFindReplTuple(EState *estate, Relation rel, Oid idxoid, LockTupleMo idxrel = index_open(idxoid, RowExclusiveLock); } - /* for non partitioned table, or partitioned table with GPI, use parent heap and index to do the scan */ + /*对于非分区表或带有GPI的分区表,使用父堆和索引进行扫描 */ if (RelationIsNonpartitioned(rel) || (idxrel != NULL && RelationIsGlobalIndex(idxrel))) { if (idxrel != NULL) { found = RelationFindReplTupleByIndex(estate, rel, idxrel, lockmode, searchslot, outslot, fakeRelInfo); @@ -298,7 +306,7 @@ bool RelationFindReplTuple(EState *estate, Relation rel, Oid idxoid, LockTupleMo } } - /* scan with partition */ + /* 分区扫描 */ if (idxrel != NULL) { found = PartitionFindReplTupleByIndex(estate, rel, idxrel, lockmode, searchslot, outslot, fakeRelInfo); index_close(idxrel, NoLock); @@ -309,10 +317,9 @@ bool RelationFindReplTuple(EState *estate, Relation rel, Oid idxoid, LockTupleMo } /* - * Search the relation 'rel' for tuple using the index. + * 使用索引搜索关系'rel'查找元组。 * - * If a matching tuple is found, lock it with lockmode, fill the slot with its - * contents, and return true. Return false otherwise. + * 如果找到匹配的元组,用lockmode锁定它,用它的内容填充槽,并返回true。否则返回false。 */ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation idxrel, LockTupleMode lockmode, TupleTableSlot *searchslot, TupleTableSlot *outslot, FakeRelationPartition *fakeRelPart) @@ -327,8 +334,7 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation int rc; bool isGpi = RelationIsGlobalIndex(idxrel); /* - * For GPI and non-partition table, use parent heap relation to search the tuple, - * otherwise use partition relation + * 对于GPI和非分区表,使用父堆关系查找元组,否则使用分区关系 */ if (isGpi || RelationIsNonpartitioned(rel)) { targetRel = rel; @@ -336,14 +342,14 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation targetRel = fakeRelPart->partRel; } Assert(targetRel != NULL); - /* Start an index scan. */ + /* 启动索引扫描。 */ InitDirtySnapshot(snap); scan = scan_handler_idx_beginscan(targetRel, idxrel, &snap, IndexRelationGetNumberOfKeyAttributes(idxrel), 0); - /* refer to check_violation, we need to set isUpsert if we want to use dirty snapshot in UStore */ + /* 参考check_violation,如果我们想在UStore中使用脏快照,我们需要设置isUpsert */ scan->isUpsert = true; - /* Build scan key. */ + /* 构建扫描键。 */ build_replindex_scan_key(skey, targetRel, idxrel, searchslot); while (true) { -- 2.34.1 From 915182695a54021d59afc3f6f7ceb3b62db05c06 Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Fri, 25 Aug 2023 02:15:25 +0800 Subject: [PATCH 14/31] Update execReplication.cpp --- .../runtime/executor/execReplication.cpp | 81 +++++++++---------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/src/gausskernel/runtime/executor/execReplication.cpp b/src/gausskernel/runtime/executor/execReplication.cpp index 20d259c89..9ccc20133 100644 --- a/src/gausskernel/runtime/executor/execReplication.cpp +++ b/src/gausskernel/runtime/executor/execReplication.cpp @@ -356,7 +356,7 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation found = false; scan_handler_idx_rescan(scan, skey, IndexRelationGetNumberOfKeyAttributes(idxrel), NULL, 0); - /* Try to find the tuple */ + /* 试着找到这个元组 */ if (RelationIsUstoreFormat(targetRel)) { found = IndexGetnextSlot(scan, ForwardScanDirection, outslot); } else { @@ -366,11 +366,10 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation } } if (found) { - /* Found tuple, try to lock it in the lockmode. */ + /* 找到元组,尝试在锁定模式下锁定它。 */ xwait = TransactionIdIsValid(snap.xmin) ? snap.xmin : snap.xmax; /* - * If the tuple is locked, wait for locking transaction to finish - * and retry. + * 如果元组被锁定,请等待锁定事务完成后重试。 */ if (TransactionIdIsValid(xwait)) { XactLockTableWait(xwait); @@ -390,7 +389,7 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation ItemPointer tid = tableam_tops_get_t_self(targetRel, outslot->tts_tuple); if (RelationIsUstoreFormat(targetRel)) { - /* materialize the slot, so we can visit it after the scan is end */ + /* 将插槽物化,这样扫描结束后我们就可以访问它了 */ outslot->tts_tuple = UHeapMaterialize(outslot); ItemPointerCopy(tid, &UHeaplocktup.ctid); rc = memset_s(&tbuf, sizeof(tbuf), 0, sizeof(tbuf)); @@ -398,13 +397,13 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation UHeaplocktup.disk_tuple = &tbuf.hdr; locktup = &UHeaplocktup; } else { - /* materialize the slot, so we can visit it after the scan is end */ + /* 将插槽物化,这样扫描结束后我们就可以访问它了 */ outslot->tts_tuple = ExecMaterializeSlot(outslot); ItemPointerCopy(tid, &heaplocktup.t_self); locktup = &heaplocktup; } - /* Get the target tuple's partition for GPI */ + /* 获取目标元组的GPI分区 */ if (isGpi) { GetFakeRelAndPart(estate, rel, outslot, fakeRelPart); targetRel = fakeRelPart->partRel; @@ -413,20 +412,20 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation PushActiveSnapshot(GetLatestSnapshot()); res = tableam_tuple_lock(targetRel, locktup, &buf, GetCurrentCommandId(false), lockmode, false, &hufd, - false, false, /* don't follow updates */ - false, /* eval */ - GetLatestSnapshot(), tid, /* ItemPointer */ - false); /* is select for update */ - /* the tuple slot already has the buffer pinned */ + false, false, /* 不要关注更新 */ + false, /* 评估 */ + GetLatestSnapshot(), tid, /* 项目指针 */ + false); /* 选择进行更新 */ + /* 元组槽已固定缓冲区 */ ReleaseBuffer(buf); PopActiveSnapshot(); if (CheckTupleLockRes(res)) { - /* lock tuple failed, try again */ + /* 锁定元组失败,请重试 */ continue; } } - /* we are done */ + /* 我们结束了 */ break; } @@ -435,7 +434,7 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation } /* - * Compare the tuple and slot and check if they have equal values. + * 比较元组和槽,并检查它们是否具有相等的值。 */ static bool tuple_equals_slot(TupleDesc desc, const Tuple tup, TupleTableSlot *slot, TypeCacheEntry **eq) { @@ -446,22 +445,21 @@ static bool tuple_equals_slot(TupleDesc desc, const Tuple tup, TupleTableSlot *s tableam_tops_deform_tuple(tup, desc, values, isnull); - /* Check equality of the attributes. */ + /* 检查属性的相等性。 */ for (attrnum = 0; attrnum < desc->natts; attrnum++) { TypeCacheEntry *typentry; - /* skip generate column */ + /* 跳过生成列跳过生成列 */ if (GetGeneratedCol(desc, attrnum)) { continue; } /* - * If one value is NULL and other is not, then they are certainly not - * equal + * 如果一个值为NULL,另一个值不为NULL,那么它们肯定不相等 */ if (isnull[attrnum] != slot->tts_isnull[attrnum]) return false; /* - * If both are NULL, they can be considered equal. + * 如果两者都为NULL,则可以认为它们相等。 */ if (isnull[attrnum]) continue; @@ -487,14 +485,13 @@ static bool tuple_equals_slot(TupleDesc desc, const Tuple tup, TupleTableSlot *s } /* - * Search the relation 'rel' for tuple using the sequential scan. + * 使用顺序扫描在关系“rel”中搜索元组。 * - * If a matching tuple is found, lock it with lockmode, fill the slot with its - * contents, and return true. Return false otherwise. + * 如果找到匹配的元组,请使用lockmode将其锁定,用其内容填充槽,然后返回true。否则返回false。 * - * Note that this stops on the first matching tuple. + * 请注意,这在第一个匹配元组上停止。 * - * This can obviously be quite slow on tables that have more than few rows. + * 对于行数多于几行的表,这显然会非常缓慢。 */ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, TupleTableSlot *searchslot, TupleTableSlot *outslot, FakeRelationPartition *fakeRelPart) @@ -513,7 +510,7 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple Assert(equalTupleDescs(desc, outslot->tts_tupleDescriptor)); eq = (TypeCacheEntry **)palloc0(sizeof(*eq) * outslot->tts_tupleDescriptor->natts); - /* Start a heap scan. */ + /* 启动堆扫描。 */ InitDirtySnapshot(snap); scan = scan_handler_tbl_beginscan(targetRel, &snap, 0, NULL, NULL); @@ -522,7 +519,7 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple found = false; scan_handler_tbl_rescan(scan, NULL, targetRel); - /* Try to find the tuple */ + /* 尝试查找元组 */ while ((scantuple = scan_handler_tbl_getnext(scan, ForwardScanDirection, targetRel)) != NULL) { if (!tuple_equals_slot(desc, scantuple, searchslot, eq)) { continue; @@ -533,8 +530,7 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple xwait = TransactionIdIsValid(snap.xmin) ? snap.xmin : snap.xmax; /* - * If the tuple is locked, wait for locking transaction to finish - * and retry. + * 如果元组已锁定,请等待锁定事务完成,然后重试。 */ if (TransactionIdIsValid(xwait)) { /* retry */ @@ -548,7 +544,7 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple continue; } if (found) { - /* Found tuple, try to lock it in the lockmode. */ + /* 找到元组,请尝试在锁定模式下锁定它。 */ Buffer buf; TM_FailureData hufd; TM_Result res; @@ -562,7 +558,7 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple ItemPointer tid = tableam_tops_get_t_self(rel, outslot->tts_tuple); if (RelationIsUstoreFormat(targetRel)) { - /* materialize the slot, so we can visit it after the scan is end */ + /* 具体化插槽,这样我们就可以在扫描结束后访问它 */ outslot->tts_tuple = UHeapMaterialize(outslot); ItemPointerCopy(tid, &UHeaplocktup.ctid); rc = memset_s(&tbuf, sizeof(tbuf), 0, sizeof(tbuf)); @@ -570,7 +566,7 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple UHeaplocktup.disk_tuple = &tbuf.hdr; locktup = &UHeaplocktup; } else { - /* materialize the slot, so we can visit it after the scan is end */ + /* 具体化插槽,这样我们就可以在扫描结束后访问它 */ outslot->tts_tuple = ExecMaterializeSlot(outslot); ItemPointerCopy(tid, &heaplocktup.t_self); locktup = &heaplocktup; @@ -579,21 +575,21 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple PushActiveSnapshot(GetLatestSnapshot()); res = tableam_tuple_lock(targetRel, locktup, &buf, GetCurrentCommandId(false), lockmode, false, &hufd, false, - false, /* don't follow updates */ - false, /* eval */ - GetLatestSnapshot(), tid, /* ItemPointer */ - false); /* is select for update */ + false, /* 不关注更新 */ + false, /* 评估 */ + GetLatestSnapshot(), tid, /* 项目指针 */ + false); /* 选择进行更新 */ - /* the tuple slot already has the buffer pinned */ + /* 元组槽已固定缓冲区 */ ReleaseBuffer(buf); PopActiveSnapshot(); if (CheckTupleLockRes(res)) { - /* lock tuple failed, try again */ + /* 锁定元组失败,请重试 */ continue; } } - /* we are done */ + /* 我们结束了 */ break; } @@ -603,10 +599,9 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple } /* - * Insert tuple represented in the slot to the relation, update the indexes, - * and execute any constraints and per-row triggers. + * 将槽中表示的元组插入关系,更新索引,并执行任何约束和每行触发器。 * - * Caller is responsible for opening the indexes. + * 调用者负责打开索引。 */ void ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot, FakeRelationPartition *relAndPart) { @@ -615,7 +610,7 @@ void ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot, FakeRelation Relation rel = resultRelInfo->ri_RelationDesc; Relation targetRel = relAndPart->partRel == NULL ? rel : relAndPart->partRel; - /* For now we support only tables. */ + /* 目前,我们只支持表格。 */ Assert(rel->rd_rel->relkind == RELKIND_RELATION); CheckCmdReplicaIdentity(rel, CMD_INSERT); -- 2.34.1 From 20ef95610249e828d9089449168728ab19231318 Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Mon, 28 Aug 2023 20:22:05 +0800 Subject: [PATCH 15/31] Update execReplication.cpp --- .../runtime/executor/execReplication.cpp | 107 +++++++++++------- 1 file changed, 69 insertions(+), 38 deletions(-) diff --git a/src/gausskernel/runtime/executor/execReplication.cpp b/src/gausskernel/runtime/executor/execReplication.cpp index 9ccc20133..226aa4c8b 100644 --- a/src/gausskernel/runtime/executor/execReplication.cpp +++ b/src/gausskernel/runtime/executor/execReplication.cpp @@ -615,7 +615,7 @@ void ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot, FakeRelation CheckCmdReplicaIdentity(rel, CMD_INSERT); - /* BEFORE ROW INSERT Triggers */ + /* 在行之前插入触发器 */ if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_before_row) { slot = ExecBRInsertTriggers(estate, resultRelInfo, slot); if (slot == NULL) { @@ -623,40 +623,43 @@ void ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot, FakeRelation return; } } - - List *recheckIndexes = NIL; - /* Materialize slot into a tuple that we can scribble upon. */ + /*这段代码是用于执行简单的关系表插入操作。它首先获取要插入的元组和目标关系表, + 然后检查关系表的复制标识以确保插入操作是合法的。接着,它在插入行之前执行插入触发器。 + 如果存在触发器并且它们是在行之前执行的,则调用ExecBRInsertTriggers函数来执行这些触发器。 + 如果插入操作被触发器取消,则返回。*/ + + List *recheckIndexes = NIL; + /* 将槽具体化为一个元组,我们可以在上面乱涂乱画。 */ tuple = tableam_tslot_get_tuple_from_slot(rel, slot); tableam_tops_update_tuple_with_oid(targetRel, tuple, slot); - /* Compute stored generated columns */ + /* 计算存储的生成列 */ if (rel->rd_att->constr && rel->rd_att->constr->has_generated_stored) { ExecComputeStoredGenerated(resultRelInfo, estate, slot, tuple, CMD_INSERT); tuple = slot->tts_tuple; } - /* Check the constraints of the tuple */ + /* 检查元组的约束 */ if (rel->rd_att->constr) ExecConstraints(resultRelInfo, slot, estate); - /* OK, store the tuple and create index entries for it */ + /* 好的,存储元组并为其创建索引项 */ (void)tableam_tuple_insert(targetRel, tuple, GetCurrentCommandId(true), 0, NULL); if (resultRelInfo->ri_NumIndices > 0) { ItemPointer pTSelf = tableam_tops_get_t_self(rel, tuple); recheckIndexes = ExecInsertIndexTuples(slot, pTSelf, estate, targetRel, relAndPart->part, InvalidBktId, NULL, NULL); } - /* AFTER ROW INSERT Triggers */ + /* 在行后插入触发器 */ ExecARInsertTriggers(estate, resultRelInfo, relAndPart->partOid, InvalidBktId, (HeapTuple)tuple, recheckIndexes); list_free_ext(recheckIndexes); } /* - * Find the searchslot tuple and update it with data in the slot, - * update the indexes, and execute any constraints and per-row triggers. + * 查找searchslot元组,并使用slot中的数据对其进行更新,更新索引,并执行任何约束和每行触发器。 * - * Caller is responsible for opening the indexes. + * 调用者负责打开索引。 */ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot *searchslot, TupleTableSlot *slot, FakeRelationPartition *relAndPart) @@ -667,7 +670,7 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot Relation rel = resultRelInfo->ri_RelationDesc; ItemPointer searchSlotTid = tableam_tops_get_t_self(rel, searchslot->tts_tuple); - /* For now we support only tables. */ + /* 目前,我们只支持表格。 */ Assert(rel->rd_rel->relkind == RELKIND_RELATION); CheckCmdReplicaIdentity(rel, CMD_UPDATE); @@ -677,7 +680,7 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot allowInplaceUpdate = false; } - /* BEFORE ROW UPDATE Triggers */ + /* 排前更新触发器 */ if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_update_before_row) { slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, relAndPart->partOid, InvalidBktId, NULL, searchSlotTid, slot); @@ -686,8 +689,13 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot return; } } - - /* Materialize slot into a tuple that we can scribble upon. */ +/* 这段代码是用于执行简单的关系表更新操作。它首先获取要更新的元组和目标关系表,然后检查关系表的复制标识以确保更新操作是合法的。 + 接着,它检查是否允许原地更新,如果不允许,则需要进行分裂更新。 + 接下来,它执行更新之前的触发器,并在更新之前检查是否需要进行分裂更新。 + 如果存在触发器并且它们是在行之前执行的,则调用ExecBRUpdateTriggers函数来执行这些触发器。 + 如果更新操作被触发器取消,则返回。 */ + + /* 将槽具体化为一个元组,我们可以在上面乱涂乱画。 */ tuple = tableam_tslot_get_tuple_from_slot(rel, slot); List *recheckIndexes = NIL; Bitmapset *modifiedIdxAttrs = NULL; @@ -700,17 +708,17 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot Relation targetRelation = relAndPart->partRel == NULL ? rel : relAndPart->partRel; Relation parentRelation = relAndPart->partRel == NULL ? NULL : rel; - /* Compute stored generated columns */ + /* 计算存储的生成列 */ if (rel->rd_att->constr && rel->rd_att->constr->has_generated_stored) { ExecComputeStoredGenerated(resultRelInfo, estate, slot, tuple, CMD_UPDATE); } - /* Check the constraints of the tuple */ + /* 检查元组的约束 */ if (rel->rd_att->constr) { ExecConstraints(resultRelInfo, slot, estate); } - /* check whether there is a row movement for partition table */ + /* 检查分区表是否有行移动 */ GetFakeRelAndPart(estate, rel, slot, &newTupleInfo); if (newTupleInfo.partOid != InvalidOid && newTupleInfo.partOid != relAndPart->partOid) { if (!rel->rd_rel->relrowmovement) { @@ -720,10 +728,15 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot } rowMovement = true; } - + /* 这段代码是用于获取虚拟关系表和分区信息。 + 它首先获取要插入或更新的元组,然后调用GetRelationPartitionOid函数来获取元组所属的分区OID。 + 如果元组属于一个分区,则获取该分区的FakeRelationPartition信息。 + 接着,它检查如果新元组所属的分区与目标分区不同,则需要进行行移动操作。 + 如果关系表没有启用行移动,则会抛出错误。 */ + tuple = slot->tts_tuple; CommandId cid = GetCurrentCommandId(true); - /* OK, update the tuple and index entries for it */ + /* 好的,更新它的元组和索引项 */ if (!rowMovement) { res = tableam_tuple_update(targetRelation, parentRelation, searchSlotTid, tuple, cid, InvalidSnapshot, estate->es_snapshot, true, &oldslot, &tmfd, &updateIndexes, &modifiedIdxAttrs, @@ -740,7 +753,7 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot searchSlotTid, exec_index_tuples_state, InvalidBktId, modifiedIdxAttrs); } } else { - /* rowMovement, delete origin tuple and insert new */ + /* rowMovement,删除原始元组并插入新元组 */ Assert(relAndPart->partRel != NULL); Assert(newTupleInfo.partRel != NULL); res = tableam_tuple_delete(relAndPart->partRel, searchSlotTid, cid, InvalidSnapshot, @@ -755,7 +768,7 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot tableam_tops_exec_delete_index_tuples(oldslot, relAndPart->partRel, NULL, searchSlotTid, exec_index_tuples_state, modifiedIdxAttrs); - /* Insert new tuple */ + /* 插入新元组 */ (void)tableam_tuple_insert(newTupleInfo.partRel, tuple, cid, 0, NULL); if (resultRelInfo->ri_NumIndices > 0) { ItemPointer pTSelf = tableam_tops_get_t_self(rel, tuple); @@ -767,8 +780,12 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot if (oldslot) { ExecDropSingleTupleTableSlot(oldslot); } - - /* AFTER ROW UPDATE Triggers */ + /* 这段代码是用于向关系表中插入新的元组。 + 它首先调用tableam_tuple_insert函数将元组插入到目标关系表中。 + 如果目标关系表有索引,则调用ExecInsertIndexTuples函数来为插入的元组创建索引。 + 最后,它释放旧的插槽。*/ + + /* 排后更新触发器 */ ExecARUpdateTriggers(estate, resultRelInfo, relAndPart->partOid, InvalidBktId, relAndPart->partOid, searchSlotTid, (HeapTuple)tuple, NULL, recheckIndexes); @@ -776,10 +793,9 @@ void ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot } /* - * Find the searchslot tuple and delete it, and execute any constraints - * and per-row triggers. + * .找到searchslot元组并将其删除,然后执行任何约束和每行触发器。 * - * Caller is responsible for opening the indexes. + * 调用者负责打开索引。 */ void ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, TupleTableSlot *searchslot, FakeRelationPartition *relAndPart) @@ -789,12 +805,12 @@ void ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, TupleTableSlot Relation rel = resultRelInfo->ri_RelationDesc; ItemPointer tid = tableam_tops_get_t_self(rel, searchslot->tts_tuple); - /* For now we support only tables. */ + /* 目前,我们只支持表格。 */ Assert(rel->rd_rel->relkind == RELKIND_RELATION); CheckCmdReplicaIdentity(rel, CMD_DELETE); - /* BEFORE ROW INSERT Triggers */ + /* 在行之前插入触发器 */ if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_delete_before_row) { skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo, relAndPart->partOid, InvalidBktId, NULL, tid); @@ -807,7 +823,7 @@ void ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, TupleTableSlot Relation targetRel = relAndPart->partRel == NULL ? rel : relAndPart->partRel; TM_FailureData tmfd; - /* OK, delete the tuple */ + /* 好,删除元组 */ TM_Result res = tableam_tuple_delete(targetRel, tid, GetCurrentCommandId(true), InvalidSnapshot, estate->es_snapshot, true, &oldslot, &tmfd); CheckTupleModifyRes(res); @@ -823,29 +839,29 @@ void ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, TupleTableSlot ExecDropSingleTupleTableSlot(oldslot); } - /* AFTER ROW DELETE Triggers */ + /* 行删除触发器之后 */ ExecARDeleteTriggers(estate, resultRelInfo, relAndPart->partOid, InvalidBktId, NULL, tid); } /* - * Check if command can be executed with current replica identity. + * 检查是否可以使用当前副本标识执行命令。 */ void CheckCmdReplicaIdentity(Relation rel, CmdType cmd) { PublicationActions *pubactions; - /* We only need to do checks for UPDATE and DELETE. */ + /* 我们只需要检查UPDATE和DELETE。 */ if (cmd != CMD_UPDATE && cmd != CMD_DELETE) return; - /* If relation has replica identity we are always good. */ + /* 若关系具有复制身份,我们总是好的。 */ if (RelationGetRelReplident(rel) == REPLICA_IDENTITY_FULL || OidIsValid(RelationGetReplicaIndex(rel))) return; /* - * This is either UPDATE OR DELETE and there is no replica identity. + * 这是UPDATE或DELETE,并且没有副本标识。 * - * Check if the table publishes UPDATES or DELETES. + * 检查表是否发布UPDATES或DELETES。 */ pubactions = GetRelationPublicationActions(rel); if (cmd == CMD_UPDATE && pubactions->pubupdate) { @@ -870,8 +886,13 @@ void GetFakeRelAndPart(EState *estate, Relation rel, TupleTableSlot *slot, FakeR if (RelationIsNonpartitioned(rel)) { return; - } - + } +/* 此代码片段定义了一个名为GetFakeRelAndPart的函数, + 该函数接受EState对象、Relation对象、TupleTableSlot对象和FakeRelationPartition对象作为输入参数。 + 函数首先将FakeRelationPartition对象的partRel、part和partOid属性分别初始化为NULL和InvalidOid。 + 如果输入的Relation对象是非分区的,则函数只返回而不执行任何其他操作。 + 否则,它将继续确定当前元组所属的分区。*/ + Relation partRelation = NULL; Partition partition = NULL; Oid partitionOid; @@ -911,3 +932,13 @@ void GetFakeRelAndPart(EState *estate, Relation rel, TupleTableSlot *slot, FakeR break; } } +/* 此代码段继续实现“GetFakeElAndPart”函数。 + 它首先声明了几个变量,包括“partRelation”、“partition”和“partitionOid”,这些变量用于存储有关分区关系及其分区的信息。 + 然后,它使用“tableam_tslot_get_tuple_from_slot”函数从“TupleTableSlot”对象检索当前元组。 + 然后,函数输入一个switch语句,该语句检查输入“Relation”对象的“parttype”属性。 + 如果它是一个未分区或值分区的关系,则函数只需脱离switch语句。 + 如果关系是分区关系,则函数使用“heapTupleGetPartitionId”函数检索当前元组的“partitionOid”。 + 然后,它调用“searchFakeRetreationForPartitionOid”函数来搜索分区关系及其基于“partitionOid”的相应分区。 + 函数将结果分别存储在“partRelation”和“partition”变量中。然后,它将“FakeRelationPartition”对象的“partRel”、“part”和“partOid”属性设置为相应的值。 + 如果关系是子划分关系,则函数执行与划分关系情况类似的过程,但它首先使用“partitionOid”搜索划分关系,然后使用从当前元组检索到的“subPartOid”来搜索子划分关系。 + 如果输入“Relation”对象的“parttype”属性无法识别,则函数会使用“ereport”函数引发错误。 */ \ No newline at end of file -- 2.34.1 From 46f5f7ecfae47c5a919bb486a16c476eafce8b1c Mon Sep 17 00:00:00 2001 From: TerryTongJ Date: Mon, 4 Sep 2023 10:52:29 +0800 Subject: [PATCH 16/31] 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; } -- 2.34.1 From b6594788c8b41cae708d1b85771fca9e471ca1f6 Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Tue, 5 Sep 2023 11:01:35 +0800 Subject: [PATCH 17/31] Update execScan.cpp --- src/gausskernel/runtime/executor/execScan.cpp | 179 +++++++++--------- 1 file changed, 93 insertions(+), 86 deletions(-) diff --git a/src/gausskernel/runtime/executor/execScan.cpp b/src/gausskernel/runtime/executor/execScan.cpp index fefade4a2..b305361ba 100644 --- a/src/gausskernel/runtime/executor/execScan.cpp +++ b/src/gausskernel/runtime/executor/execScan.cpp @@ -1,15 +1,13 @@ /* ------------------------------------------------------------------------- * * execScan.cpp - * This code provides support for generalized relation scans. ExecScan - * is passed a node and a pointer to a function to "do the right thing" - * and return a tuple from the relation. ExecScan then does the tedious - * stuff - checking the qualification and projecting the tuple - * appropriately. + * 此代码提供对广义关系扫描的支持。ExecScan被传递一个节点和一个指向函数的指针, + * 以“做正确的事情”并从关系中返回一个元组。 + * ExecScan然后做一些乏味的工作——检查资格并适当地投影元组。 * - * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California + * 部分版权所有(c)2020华为技术有限公司有限公司。 + * 部分版权所有(c)1996-2012,PostgreSQL 全球开发集团 + * 部分版权所有(c)1994,加州大学董事会 * * * IDENTIFICATION @@ -25,21 +23,30 @@ #include "utils/memutils.h" /* - * ExecScanFetch -- fetch next potential tuple + * ExecScanFetch -- 获取下一个潜在元组 * - * This routine is concerned with substituting a test tuple if we are - * inside an EvalPlanQual recheck. If we aren't, just execute - * the access method's next-tuple routine. + * 如果我们在EvalPlanQual复查中,这个例程涉及替换测试元组。 + * 如果我们不在,只执行访问方法的下一个元组例程。 */ static TupleTableSlot* ExecScanFetch(ScanState* node, ExecScanAccessMtd access_mtd, ExecScanRecheckMtd recheck_mtd) +/* + * 此函数负责从扫描中获取下一个元组。它接受一个ScanState对象, + * 该对象包含有关扫描的信息,以及两个函数指针:access_mtd和recheck_mtd。 + * access_mtd是指向负责访问元组数据的函数的指针。它接收一个指向扫描状态对象的指针,以及一个指向已提取元组的指针, + * 并返回一个布尔值,指示元组数据是否已成功访问。 + * recheck_mtd是指向一个函数的指针,该函数负责重新检查已经提取的元组上的扫描条件。 + * 它接收一个指向扫描状态对象的指针,以及一个指向需要重新检查的元组的指针, + * 并返回一个布尔值,指示元组是否仍然满足扫描条件。 + * 该函数返回一个TupleTableSlot对象,该对象包含提取的元组数据。如果没有更多的元组可获取,则返回NULL。 + */ + { EState* estate = node->ps.state; if (estate->es_epqTuple != NULL) { /* - * We are inside an EvalPlanQual recheck. Return the test tuple if - * one is available, after rechecking any access-method-specific - * conditions. + * 我们正在进行EvalPlanQual复查。 + * 在重新检查任何特定于访问方法的条件后,返回测试元组(如果有)。 */ Index scan_rel_id = ((Scan*)node->ps.plan)->scanrelid; @@ -47,29 +54,29 @@ static TupleTableSlot* ExecScanFetch(ScanState* node, ExecScanAccessMtd access_m if (estate->es_epqTupleSet[scan_rel_id - 1]) { TupleTableSlot* slot = node->ss_ScanTupleSlot; - /* Return empty slot if we already returned a tuple */ + /* 如果我们已经返回了元组,则返回空槽 */ if (estate->es_epqScanDone[scan_rel_id - 1]) - return ExecClearTuple(slot); - /* Else mark to remember that we shouldn't return more */ + return ExecClearTuple(slot);如果我们没有测试元组,则返回空槽 + /* 否则请记住,我们不应该再回来了 */ estate->es_epqScanDone[scan_rel_id - 1] = true; - /* Return empty slot if we haven't got a test tuple */ + /* 如果我们没有测试元组,则返回空槽 */ if (estate->es_epqTuple[scan_rel_id - 1] == NULL) return ExecClearTuple(slot); - /* Store test tuple in the plan node's scan slot */ + /* 将测试元组存储在计划节点的扫描槽中 */ (void)ExecStoreTuple(estate->es_epqTuple[scan_rel_id - 1], slot, InvalidBuffer, false); - /* Check if it meets the access-method conditions */ + /* 检查是否符合访问方法条件 */ if (!(*recheck_mtd)(node, slot)) - (void)ExecClearTuple(slot); /* would not be returned by scan */ + (void)ExecClearTuple(slot); /* 不会通过扫描返回 */ return slot; } } /* - * Run the node-type-specific access method function to get the next tuple + * 运行特定于节点类型的访问方法函数以获取下一个元组 */ return (*access_mtd)(node); } @@ -77,27 +84,28 @@ static TupleTableSlot* ExecScanFetch(ScanState* node, ExecScanAccessMtd access_m /* ---------------------------------------------------------------- * ExecScan * - * Scans the relation using the 'access method' indicated and - * returns the next qualifying tuple in the direction specified - * in the global variable ExecDirection. - * The access method returns the next tuple and execScan() is - * responsible for checking the tuple returned against the qual-clause. + * 使用指示的“访问方法”扫描关系,并按全局变量ExecDirection中指定的方向返回下一个符合条件的元组。 + * access方法返回下一个元组,execScan()负责根据qual子句检查返回的元组。 * - * A 'recheck method' must also be provided that can check an - * arbitrary tuple of the relation against any qual conditions - * that are implemented internal to the access method. + * 还必须提供一个“重新检查方法”,该方法可以根据访问方法内部实现的任何qual条件检查关系的任意元组。 * - * Conditions: - * -- the "cursor" maintained by the AMI is positioned at the tuple - * returned previously. + * 条件: + * -- AMI维护的“游标”位于先前返回的元组处。 * - * Initial States: - * -- the relation indicated is opened for scanning so that the - * "cursor" is positioned before the first qualifying tuple. + * 初始状态: + * -- 所指示的关系被打开进行扫描,以便“光标”位于第一个符合条件的元组之前 * ---------------------------------------------------------------- */ -TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* function returning a tuple */ +TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* 返回元组的函数 */ ExecScanRecheckMtd recheck_mtd) +/* 此函数负责扫描关系并返回下一个匹配的元组。它接受一个ScanState对象,该对象包含有关扫描的信息,以及两个函数指针:access_mtd和recheck_mtd。 + * access_mtd是指向负责访问元组数据的函数的指针。它接收一个指向扫描状态对象的指针,以及一个指向已提取元组的指针, + * 并返回一个布尔值,指示元组数据是否已成功访问。 + * recheck_mtd是指向一个函数的指针,该函数负责重新检查已经提取的元组上的扫描条件。 + * 它接收一个指向扫描状态对象的指针,以及一个指向需要重新检查的元组的指针,并返回一个布尔值,指示元组是否仍然满足扫描条件。 + * 该函数返回一个TupleTableSlot对象,该对象包含提取的元组数据。如果没有更多的元组可获取,则返回NULL。 + */ + { ExprContext* econtext = NULL; List* qual = NIL; @@ -109,15 +117,14 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct return NULL; /* - * Fetch data from node + * 从节点获取数据 */ qual = node->ps.qual; proj_info = node->ps.ps_ProjInfo; econtext = node->ps.ps_ExprContext; /* - * If we have neither a qual to check nor a projection to do, just skip - * all the overhead and return the raw scan tuple. + * 如果我们既没有要检查的qual,也没有要做的投影,只需跳过所有开销并返回原始扫描元组。 */ if (qual == NULL && proj_info == NULL) { ResetExprContext(econtext); @@ -125,38 +132,36 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct } /* - * Check to see if we're still projecting out tuples from a previous scan - * tuple (because there is a function-returning-set in the projection - * expressions). If so, try to project another one. + * 检查我们是否仍在从上一个扫描元组中投影出元组 + *(因为在投影表达式中有一个函数返回集)。 + * u如果是,试着投影另一个。 */ if (node->ps.ps_TupFromTlist) { - Assert(proj_info); /* can't get here if not projecting */ + Assert(proj_info); /* 如果不投影就不能到达这里 */ result_slot = ExecProject(proj_info, &is_done); if (is_done == ExprMultipleResult) return result_slot; - /* Done with that source tuple... */ + /* 已完成该源元组... */ node->ps.ps_TupFromTlist = false; } /* * @hdfs - * Optimize scan bu using informational constraint. - * if the is_scan_false is true, the iteration is over. + * 使用信息约束优化扫描bu。 + * 如果isscanfalse为true,则迭代结束。 */ if (node->is_scan_end) { return NULL; } /* - * Reset per-tuple memory context to free any expression evaluation - * storage allocated in the previous tuple cycle. Note this can't happen - * until we're done projecting out tuples from a scan tuple. + * 重置每个元组内存上下文以释放在上一个元组周期中分配的任何表达式求值存储。 + * 请注意,在我们完成从扫描元组中投影出元组之前,这是不可能发生的。 */ ResetExprContext(econtext); /* - * get a tuple from the access method. Loop until we obtain a tuple that - * passes the qualification. + * 从access方法获取一个元组。循环直到我们获得一个通过资格的元组。 */ for (;;) { TupleTableSlot* slot = NULL; @@ -164,13 +169,11 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct CHECK_FOR_INTERRUPTS(); slot = ExecScanFetch(node, access_mtd, recheck_mtd); - /* refresh qual every loop */ + /* 刷新qual每个循环 */ qual = node->ps.qual; /* - * if the slot returned by the accessMtd contains NULL, then it means - * there is nothing more to scan so we just return an empty slot, - * being careful to use the projection result slot so it has correct - * tupleDesc. + * 如果accessMtd返回的槽包含NULL,那么这意味着没有更多的东西可以扫描, + * 所以我们只返回一个空槽,小心使用投影结果槽,这样它就有了正确的tupleDesc。 */ if (TupIsNull(slot) || unlikely(executorEarlyStop())) { if (proj_info != NULL) @@ -180,30 +183,28 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct } /* - * place the current tuple into the expr context + * 将当前元组放入expr上下文 */ econtext->ecxt_scantuple = slot; /* - * check that the current tuple satisfies the qual-clause + * 检查当前元组是否满足qual子句 * - * check for non-nil qual here to avoid a function call to ExecQual() - * when the qual is nil ... saves only a few cycles, but they add up + * 在此处检查非nil qual,以避免在qual为nil时调用ExecQual()函数...只节省了几个周期,但它们加起来 * ... */ if (qual == NULL || ExecQual(qual, econtext, false)) { /* - * Found a satisfactory scan tuple. + * 找到一个令人满意的扫描元组。 */ if (proj_info != NULL) { /* - * Form a projection tuple, store it in the result tuple slot - * and return it --- unless we find we can project no tuples - * from this scan tuple, in which case continue scan. + * 形成一个投影元组,将其存储在结果元组槽中并返回它——除非我们发现我们不能从这个扫描元组中投影任何元组, + * 在这种情况下,继续扫描。 */ result_slot = ExecProject(proj_info, &is_done); #ifdef PGXC - /* Copy the xcnodeoid if underlying scanned slot has one */ + /* 复制xcnodeoid(如果底层扫描的插槽有一个) */ result_slot->tts_xcnodeoid = slot->tts_xcnodeoid; #endif /* PGXC */ if (is_done != ExprEndResult) { @@ -211,15 +212,14 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct /* * @hdfs - * Optimize foreign scan by using informational constraint. + * 使用信息约束优化外部扫描。 */ if (IsA(node->ps.plan, ForeignScan)) { ForeignScan* foreign_scan = (ForeignScan*)(node->ps.plan); if (foreign_scan->scan.scan_qual_optimized) { /* - * If we find a suitable tuple, set is_scan_end value is true. - * It means that we do not find suitable tuple in the next iteration, - * the iteration is over. + * 如果我们找到一个合适的元组,那么set is_scan_end值为true。 + * 这意味着我们在下一次迭代中没有找到合适的元组,迭代结束了。 */ node->is_scan_end = true; } @@ -228,21 +228,20 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct } } else { /* - * Optimize foreign scan by using informational constraint. + * 使用信息约束优化外部扫描。 */ if (IsA(node->ps.plan, ForeignScan)) { ForeignScan* foreign_scan = (ForeignScan*)(node->ps.plan); if (foreign_scan->scan.scan_qual_optimized) { /* - * If we find a suitable tuple, set is_scan_end value is true. - * It means that we do not find suitable tuple in the next iteration, - * the iteration is over. + * 如果我们找到一个合适的元组,那么set is_scan_end值为true。 + * 这意味着我们在下一次迭代中没有找到合适的元组,迭代结束了。 */ node->is_scan_end = true; } } /* - * Here, we aren't projecting, so just return scan tuple. + * 在这里,我们不投影,所以只返回扫描元组。 */ return slot; } @@ -250,7 +249,7 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct InstrCountFiltered1(node, 1); /* - * Tuple fails qual, so free per-tuple memory and try again. + * 元组无法通过qual,请释放每个元组的内存,然后重试。 */ ResetExprContext(econtext); } @@ -258,23 +257,21 @@ TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* funct /* * ExecAssignScanProjectionInfo - * Set up projection info for a scan node, if necessary. + * 如有必要,为扫描节点设置投影信息。 * - * We can avoid a projection step if the requested tlist exactly matches - * the underlying tuple type. If so, we just set ps_ProjInfo to NULL. - * Note that this case occurs not only for simple "SELECT * FROM ...", but - * also in most cases where there are joins or other processing nodes above - * the scan node, because the planner will preferentially generate a matching - * tlist. + * 如果请求的tlist与底层元组类型完全匹配,我们可以避免投影步骤。 + * 如果是,我们只需将ps_ProjegInfo设置为NULL。 + * 请注意,这种情况不仅发生在简单的“SELECT*FROM…”中,而且发生在扫描节点上方有联接或其他处理节点的大多数情况下, + * 因为计划器将优先生成匹配的tlist。 * - * ExecAssignScanType must have been called already. + * 必须已调用ExecAssignScanType */ void ExecAssignScanProjectionInfo(ScanState* node) { Scan* scan = (Scan*)node->ps.plan; Index var_no; - /* Vars in an index-only scan's tlist should be INDEX_VAR */ + /* 仅索引扫描的tlist中的变量应为index_VAR */ if (IsA(scan, IndexOnlyScan)) var_no = INDEX_VAR; else @@ -285,6 +282,16 @@ void ExecAssignScanProjectionInfo(ScanState* node) else ExecAssignProjectionInfo(&node->ps, node->ss_ScanTupleSlot->tts_tupleDescriptor); } +/* 函数ExecAssignScanProjectionInfo负责将投影信息分配给ScanState节点。让我们分解代码: + 该函数采用ScanState指针作为输入。 + 它使用(Scan*)node->ps.plan将ScanState强制转换为Scan节点。 + 它声明了一个索引变量var_no。 + 如果扫描是仅索引扫描(使用IsA(scan,IndexOnlyScan)进行检查),则会将var_no设置为index_var。 + 否则,它将var_no设置为scan->scanrelid,表示扫描关系标识符。 + 它使用tlist_matches_tupdesc函数检查扫描的目标列表是否与扫描元组槽的元组描述符匹配。如果它们匹配,它会将node->ps.ps_ProjInfo设置为NULL。 + 如果目标列表和元组描述符不匹配,则调用ExecAssignProjectionInfo,使用扫描元组槽的元组描述符将投影信息分配给ScanState节点。 + 总之,此函数根据扫描类型以及目标列表和元组描述符之间的匹配来确定是否需要将投影信息分配给ScanState节点。 +*/ /* * ExecAssignScanProjectionInfoWithVarno -- 2.34.1 From f1a9e2ac18860286cedc0042e6f84da2f04cf9a7 Mon Sep 17 00:00:00 2001 From: LYLlyl Date: Tue, 5 Sep 2023 20:59:57 +0800 Subject: [PATCH 18/31] Update execUtils.cpp --- .../runtime/executor/execUtils.cpp | 572 +++++++++--------- 1 file changed, 283 insertions(+), 289 deletions(-) diff --git a/src/gausskernel/runtime/executor/execUtils.cpp b/src/gausskernel/runtime/executor/execUtils.cpp index e6e59d64e..c8dcedc79 100644 --- a/src/gausskernel/runtime/executor/execUtils.cpp +++ b/src/gausskernel/runtime/executor/execUtils.cpp @@ -1037,16 +1037,18 @@ Partition ExecOpenScanParitition(EState* estate, Relation parent, PartitionIdent * ExecInsertIndexTuples support * ---------------------------------------------------------------- */ -/* ---------------------------------------------------------------- - * ExecOpenIndices +/* + * ---------------------------------------------------------------- + * ExecOpenIndices * - * Find the indices associated with a result relation, open them, - * and save information about them in the result ResultRelInfo. + * 查找与结果关系关联的索引,打开它们, + * 并在结果 ResultRelInfo 中保存相关信息。 * - * At entry, caller has already opened and locked - * resultRelInfo->ri_RelationDesc. + * 在进入此函数时,调用者已经打开并锁定了 + * resultRelInfo->ri_RelationDesc。 * ---------------------------------------------------------------- */ + void ExecOpenIndices(ResultRelInfo* resultRelInfo, bool speculative) { Relation resultRelation = resultRelInfo->ri_RelationDesc; @@ -1059,35 +1061,27 @@ void ExecOpenIndices(ResultRelInfo* resultRelInfo, bool speculative) resultRelInfo->ri_NumIndices = 0; resultRelInfo->ri_ContainGPI = false; - /* fast path if no indexes */ + /* 如果没有索引,则使用快速路径 */ if (!RelationGetForm(resultRelation)->relhasindex) return; - /* - * Get cached list of index OIDs - */ + /* 获取缓存的索引 OID 列表 */ indexoidlist = RelationGetIndexList(resultRelation); len = list_length(indexoidlist); if (len == 0) { return; } - /* - * allocate space for result arrays - */ + /* 为结果数组分配空间 */ relationDescs = (RelationPtr)palloc(len * sizeof(Relation)); indexInfoArray = (IndexInfo**)palloc(len * sizeof(IndexInfo*)); resultRelInfo->ri_IndexRelationDescs = relationDescs; resultRelInfo->ri_IndexRelationInfo = indexInfoArray; - /* - * For each index, open the index relation and save pg_index info. We - * acquire RowExclusiveLock, signifying we will update the index. - * - * Note: we do this even if the index is not IndexIsReady; it's not worth - * the trouble to optimize for the case where it isn't. - */ + /* 对于每个索引,打开索引关系并保存pg_index信息。我们获取RowExclusiveLock,表示我们将更新索引。 + 注意:即使索引不是IndexIsReady,我们也会这样做;优化它不值得。 + */ i = 0; foreach (l, indexoidlist) { Oid indexOid = lfirst_oid(l); @@ -1096,24 +1090,24 @@ void ExecOpenIndices(ResultRelInfo* resultRelInfo, bool speculative) indexDesc = index_open(indexOid, RowExclusiveLock); - // ignore INSERT/UPDATE/DELETE on unusable index + // 忽略无法使用的索引上的INSERT/UPDATE/DELETE操作 if (!IndexIsUsable(indexDesc->rd_index)) { index_close(indexDesc, RowExclusiveLock); continue; } - /* Check index whether is global parition index, and save */ + // 检查索引是否为全局分区索引,然后保存 if (RelationIsGlobalIndex(indexDesc)) { resultRelInfo->ri_ContainGPI = true; } - /* extract index key information from the index's pg_index info */ + // 从索引的 pg_index 信息中提取索引键信息 ii = BuildIndexInfo(indexDesc); /* - * If the indexes are to be used for speculative insertion, add extra - * information required by unique index entries. - */ + * 如果索引将用于推测性插入,则需要添加唯一索引条目所需的额外信息。 + */ + if (speculative && ii->ii_Unique) { BuildSpeculativeIndexInfo(indexDesc, ii); } @@ -1121,7 +1115,8 @@ void ExecOpenIndices(ResultRelInfo* resultRelInfo, bool speculative) indexInfoArray[i] = ii; i++; } - // remember to set the number of usable indexes + +// 记得设置可用索引的数量 resultRelInfo->ri_NumIndices = i; list_free_ext(indexoidlist); @@ -1130,7 +1125,7 @@ void ExecOpenIndices(ResultRelInfo* resultRelInfo, bool speculative) /* ---------------------------------------------------------------- * ExecCloseIndices * - * Close the index relations stored in resultRelInfo + * 关闭存储在resultRelInfo中的索引关系 * ---------------------------------------------------------------- */ void ExecCloseIndices(ResultRelInfo* resultRelInfo) @@ -1146,19 +1141,16 @@ void ExecCloseIndices(ResultRelInfo* resultRelInfo) if (indexDescs[i] == NULL) continue; /* shouldn't happen? */ - /* Drop lock acquired by ExecOpenIndices */ + /* 释放ExecOpenIndices获取的锁 */ index_close(indexDescs[i], RowExclusiveLock); } - /* - * XXX should free indexInfo array here too? Currently we assume that - * such stuff will be cleaned up automatically in FreeExecutorState. - */ + /* XXX 应该在这里释放indexInfo数组吗?当前我们假设这些内容将在FreeExecutorState中自动清理。 */ + } -/* - * Copied from ExecInsertIndexTuples - */ +/* 从ExecInsertIndexTuples复制而来 */ + void ExecDeleteIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* estate, Relation targetPartRel, Partition p, const Bitmapset *modifiedIdxAttrs, const bool inplaceUpdated) { @@ -1190,19 +1182,19 @@ void ExecDeleteIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* es } /* - * Get information from the result relation info structure. + * 从结果关系信息结构中获取信息。 */ + relationDescs = resultRelInfo->ri_IndexRelationDescs; indexInfoArray = resultRelInfo->ri_IndexRelationInfo; heapRelation = resultRelInfo->ri_RelationDesc; - /* - * We will use the EState's per-tuple context for evaluating predicates - * and index expressions (creating it if it's not already there). + /* + * 我们将使用EState的每个元组上下文来评估谓词和索引表达式(如果尚未创建上下文,则创建它)。 */ econtext = GetPerTupleExprContext(estate); - /* Arrange for econtext's scan tuple to be the tuple under test */ + /* 安排econtext的扫描元组成为要测试的元组 */ econtext->ecxt_scantuple = slot; if (RELATION_IS_PARTITIONED(heapRelation)) { @@ -1225,9 +1217,7 @@ void ExecDeleteIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* es if (!RelationIsUstoreFormat(heapRelation)) return; - /* - * for each index, form and insert the index tuple - */ + /* 对于每个索引,生成并插入索引元组 */ for (int i = 0; i < numIndices; i++) { Relation indexRelation = relationDescs[i]; IndexInfo* indexInfo = NULL; @@ -1242,30 +1232,29 @@ void ExecDeleteIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* es indexInfo = indexInfoArray[i]; - /* If the index is marked as read-only, ignore it */ - /* XXXX: ???? */ + /* 如果索引标记为只读,忽略它 */ if (!indexInfo->ii_ReadyForInserts) { continue; } - /* modifiedIdxAttrs != NULL means updating, not every index are affected */ + /* modifiedIdxAttrs != NULL 表示更新操作,不是每个索引都受影响 */ if (inplaceUpdated && modifiedIdxAttrs != NULL) { - /* Collect attribute Bitmapset of this index, and compare with modifiedIdxAttrs */ + /* 收集此索引的属性 Bitmapset 并与 modifiedIdxAttrs 进行比较 */ Bitmapset *indexattrs = IndexGetAttrBitmap(indexRelation, indexInfo); bool overlap = bms_overlap(indexattrs, modifiedIdxAttrs); bms_free(indexattrs); if (!overlap) { - continue; /* related columns are not modified */ + continue; /* 相关列未被修改 */ } } - /* The GPI index insertion is the same as that of a common table */ + /* GPI索引插入与常规表相同 */ if (ispartitionedtable && !RelationIsGlobalIndex(indexRelation)) { partitionedindexid = RelationGetRelid(indexRelation); if (!PointerIsValid(partitionIndexOidList)) { partitionIndexOidList = PartitionGetPartIndexList(p); - // no local indexes available + // 没有可用的本地索引 if (!PointerIsValid(partitionIndexOidList)) { return; } @@ -1280,39 +1269,38 @@ void ExecDeleteIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* es actualindex, indexpartition, RowExclusiveLock); - // skip unusable index + // 跳过不可用的索引 if (indexpartition != NULL && indexpartition->pd_part != NULL && !indexpartition->pd_part->indisusable) { continue; } } else { actualindex = indexRelation; } - /* please adapt hash bucket for ustore here. Ref ExecInsertIndexTuples() */ + /* 请在这里适应 ustore 的哈希桶。参考 ExecInsertIndexTuples() 函数。 */ - /* Check for partial index */ + /* 检查部分索引 */ if (indexInfo->ii_Predicate != NIL) { List* predicate = NIL; - /* - * If predicate state not set up yet, create it (in the estate's - * per-query context) - */ + /* + * 如果断言状态尚未设置,则创建它(在estate的每个查询上下文中) + */ predicate = indexInfo->ii_PredicateState; if (predicate == NIL) { predicate = (List*)ExecPrepareExpr((Expr*)indexInfo->ii_Predicate, estate); indexInfo->ii_PredicateState = predicate; } - /* Skip this index-update if the predicate isn't satisfied */ + /* 如果断言未满足,则跳过此索引更新 */ if (!ExecQual(predicate, econtext, false)) { continue; } } - /* - * FormIndexDatum fills in its values and isnull parameters with the - * appropriate values for the column(s) of the index. + /* + * FormIndexDatum填充其values和isnull参数,以获得索引的列的适当值。 */ + FormIndexDatum(indexInfo, slot, estate, values, isnull); index_delete(actualindex, values, isnull, tupleid); @@ -1334,7 +1322,8 @@ void ExecUHeapDeleteIndexTuplesGuts( modifiedIdxAttrs, inplaceUpdated); } else { - UHeapTuple tmpUtup = ExecGetUHeapTupleFromSlot(oldslot); // materialize the tuple + UHeapTuple tmpUtup = ExecGetUHeapTupleFromSlot(oldslot);// 将元组材料化(将元组的内部格式转换为可以插入索引的格式) + tmpUtup->table_oid = RelationGetRelid(rel); ExecDeleteIndexTuples(oldslot, tupleid, @@ -1345,7 +1334,7 @@ void ExecUHeapDeleteIndexTuplesGuts( } } -/* purely for reducing cyclomatic complexity */ +/* 仅用于降低圈复杂性 */ static inline bool GetPartiionIndexOidList(List **oidlist_ptr, Partition part) { Assert(oidlist_ptr != NULL); @@ -1365,24 +1354,22 @@ static inline bool CheckForPartialIndex(IndexInfo* indexInfo, EState* estate, Ex List* predicate = indexInfo->ii_PredicateState; if (indexInfo->ii_Predicate != NIL) { - /* - * If predicate state not set up yet, create it (in the estate's - * per-query context) - */ + /* + * 如果谓词状态尚未设置,请在执行环境的每个查询上下文中创建它。 + */ + if (predicate == NIL) { predicate = (List*)ExecPrepareExpr((Expr*)indexInfo->ii_Predicate, estate); indexInfo->ii_PredicateState = predicate; } - /* Skip this index-update if the predicate isn't satisfied */ + /* 如果谓词不满足,则跳过这个索引更新 */ if (!ExecQual(predicate, econtext, false)) { return false; } } - /* - * If indexInfo->ii_Predicate == NIL, just return true to caller to proceed. - */ + /* 如果 indexInfo->ii_Predicate 为空,则直接返回 true,以便继续执行 */ return true; } @@ -1399,19 +1386,14 @@ static inline void SetInfoForUpsertGPI(bool isgpi, Relation *actualHeap, Relatio } } -/* ---------------------------------------------------------------- - * ExecCheckIndexConstraints +/* + * ExecCheckIndexConstraints * - * This routine checks if a tuple violates any unique or - * exclusion constraints. Returns true if there is no no conflict. - * Otherwise returns false, and the TID of the conflicting - * tuple is returned in *conflictTid. + * 此例程检查元组是否违反任何唯一或排除约束。如果没有冲突则返回true。 + * 否则返回false,并将冲突元组的TID存储在*conflictTid中。 * - * Note that this doesn't lock the values in any way, so it's - * possible that a conflicting tuple is inserted immediately - * after this returns. But this can be used for a pre-check - * before insertion. - * ---------------------------------------------------------------- + * 注意,这不会以任何方式锁定值,因此在此返回后,可能立即插入冲突的元组。 + * 但这可以用于插入之前的预检查。 */ bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate, Relation targetRel, Partition p, bool *isgpiResult, int2 bucketId, ConflictInfoData *conflictInfo, Oid *conflictPartOid, @@ -1438,9 +1420,9 @@ bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate, Relation ta ItemPointerSetInvalid(&conflictInfo->conflictTid); ItemPointerSetInvalid(&invalidItemPtr); - /* - * Get information from the result relation info structure. - */ + /* + * 从结果关系信息结构中获取信息。 + */ resultRelInfo = estate->es_result_relation_info; numIndices = resultRelInfo->ri_NumIndices; relationDescs = resultRelInfo->ri_IndexRelationDescs; @@ -1461,18 +1443,17 @@ bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate, Relation ta } } - /* - * use the EState's per-tuple context for evaluating predicates - * and index expressions (creating it if it's not already there). - */ + /* + * 使用EState的每个元组上下文来评估谓词和索引表达式(如果不存在则创建)。 + */ + econtext = GetPerTupleExprContext(estate); - /* Arrange for econtext's scan tuple to be the tuple under test */ + /* 安排econtext的扫描元组为待测试的元组 */ econtext->ecxt_scantuple = slot; - /* - * For each index, form index tuple and check if it satisfies the - * constraint. + /* + * 对于每个索引,形成索引元组并检查它是否满足约束。 */ for (i = 0; i < numIndices; i++) { Relation indexRelation = relationDescs[i]; @@ -1502,10 +1483,10 @@ bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate, Relation ta ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("INSERT ON DUPLICATE KEY UPDATE does not support deferrable" " unique constraints/exclusion constraints."))); - /* - * We consider a partitioned table with a global index as a normal table, - * because conflicts can be between multiple partitions. - */ + /* + * 我们将具有全局索引的分区表视为普通表,因为冲突可能发生在多个分区之间。 + */ + if (isPartitioned && !isgpi) { partitionedindexid = RelationGetRelid(indexRelation); @@ -1540,10 +1521,10 @@ bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate, Relation ta continue; } - /* - * FormIndexDatum fills in its values and isnull parameters with the - * appropriate values for the column(s) of the index. - */ + /* + * FormIndexDatum使用适当的值填充其值和isnull参数,以用于索引的列(s)。 + */ + FormIndexDatum(indexInfo, slot, estate, values, isnull); partoid = (isgpi ? p->pd_id : InvalidOid); @@ -1562,26 +1543,22 @@ bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate, Relation ta return true; } -/* ---------------------------------------------------------------- - * ExecInsertIndexTuples +/* + * 以注释的形式翻译: + + * ---------------------------------------------------------------- + * ExecInsertIndexTuples * - * This routine takes care of inserting index tuples - * into all the relations indexing the result relation - * when a heap tuple is inserted into the result relation. - * Much of this code should be moved into the genam - * stuff as it only exists here because the genam stuff - * doesn't provide the functionality needed by the - * executor.. -cim 9/27/89 + * 此例程负责在将堆元组插入结果关系时插入索引元组,所有索引关系都索引结果关系。 + * 大部分代码应该移到genam模块中,因为它只存在于此处是因为genam模块提供的功能不满足执行器所需。 + * -cim 1989年9月27日 * - * This returns a list of index OIDs for any unique or exclusion - * constraints that are deferred and that had - * potential (unconfirmed) conflicts. + * 此函数返回在唯一或排他约束中存在潜在(未确认)冲突且被推迟的情况下的所有索引OID列表。 * - * CAUTION: this must not be called for a HOT update. - * We can't defend against that here for lack of info. - * Should we change the API to make it safer? + * 注意:不能为HOT更新调用此函数。由于缺乏信息,我们无法在此处防范这种情况。我们是否应该更改API以使其更安全? * ---------------------------------------------------------------- */ + List* ExecInsertIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* estate, Relation targetPartRel, Partition p, int2 bucketId, bool* conflict, Bitmapset *modifiedIdxAttrs, bool inplaceUpdated) @@ -1602,8 +1579,9 @@ List* ExecInsertIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* e List* partitionIndexOidList = NIL; /* - * Get information from the result relation info structure. + * 从结果关系信息结构中获取信息。 */ + resultRelInfo = estate->es_result_relation_info; numIndices = resultRelInfo->ri_NumIndices; relationDescs = resultRelInfo->ri_IndexRelationDescs; @@ -1612,12 +1590,13 @@ List* ExecInsertIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* e containGPI = resultRelInfo->ri_ContainGPI; /* - * We will use the EState's per-tuple context for evaluating predicates - * and index expressions (creating it if it's not already there). + * 我们将使用EState的每个元组上下文来评估谓词和索引表达式(如果尚不存在,则创建它)。 */ + econtext = GetPerTupleExprContext(estate); - /* Arrange for econtext's scan tuple to be the tuple under test */ + /* 安排econtext的扫描元组成为待测试的元组 */ + econtext->ecxt_scantuple = slot; if (RELATION_IS_PARTITIONED(heapRelation)) { @@ -1630,7 +1609,7 @@ List* ExecInsertIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* e if (p == NULL || p->pd_part == NULL) { return NIL; } - /* If the global partition index is included, the index insertion process needs to continue */ + /* 如果包括全局分区索引,则需要继续索引插入过程 */ if (!p->pd_part->indisusable && !containGPI) { numIndices = 0; } @@ -1642,17 +1621,16 @@ List* ExecInsertIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* e searchHBucketFakeRelation(estate->esfRelations, estate->es_query_cxt, actualheap, bucketId, actualheap); } - /* Partition create in current transaction, set partition and rel reloption wait_clean_gpi */ + /* 在当前事务中创建分区,设置分区和关系的reloption为wait_clean_gpi */ if (RelationCreateInCurrXact(actualheap) && containGPI && !PartitionEnableWaitCleanGpi(p)) { - /* partition create not set wait_clean_gpi, must use update, and we ensure no concurrency */ + /* 如果分区创建时没有设置wait_clean_gpi,则必须使用更新,我们确保没有并发操作 */ PartitionSetWaitCleanGpi(RelationGetRelid(actualheap), true, false); - /* Partitioned create set wait_clean_gpi=n, and we want save it, so just use inplace */ + /* 分区创建设置wait_clean_gpi=n,我们想要保存它,所以只需使用inplace */ PartitionedSetWaitCleanGpi(RelationGetRelationName(heapRelation), RelationGetRelid(heapRelation), true, true); } - /* - * for each index, form and insert the index tuple - */ + /* 对于每个索引,形成并插入索引元组 */ + for (i = 0; i < numIndices; i++) { Relation indexRelation = relationDescs[i]; IndexInfo* indexInfo = NULL; @@ -1674,9 +1652,9 @@ List* ExecInsertIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* e continue; } - /* modifiedIdxAttrs != NULL means updating, not every index are affected */ + /* modifiedIdxAttrs != NULL 意味着正在更新,不是每个索引都受影响 */ if (inplaceUpdated && modifiedIdxAttrs != NULL) { - /* Collect attribute Bitmapset of this index, and compare with modifiedIdxAttrs */ + /* 收集此索引的属性 Bitmapset,并与 modifiedIdxAttrs 进行比较 */ Bitmapset *indexattrs = IndexGetAttrBitmap(indexRelation, indexInfo); bool overlap = bms_overlap(indexattrs, modifiedIdxAttrs); @@ -1686,7 +1664,7 @@ List* ExecInsertIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* e } } - /* The GPI index insertion is the same as that of a common table */ + /* 全局分区索引(GPI)的插入与普通表相同 */ if (ispartitionedtable && !RelationIsGlobalIndex(indexRelation)) { partitionedindexid = RelationGetRelid(indexRelation); if (!PointerIsValid(partitionIndexOidList)) { @@ -1721,38 +1699,34 @@ List* ExecInsertIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* e if (indexInfo->ii_Predicate != NIL) { List* predicate = NIL; - /* - * If predicate state not set up yet, create it (in the estate's - * per-query context) - */ + /* + * 如果谓词状态尚未设置,就在estate的每个查询上下文中创建它。 + */ + predicate = indexInfo->ii_PredicateState; if (predicate == NIL) { predicate = (List*)ExecPrepareExpr((Expr*)indexInfo->ii_Predicate, estate); indexInfo->ii_PredicateState = predicate; } - /* Skip this index-update if the predicate isn't satisfied */ + /* 如果谓词不满足,则跳过这个索引更新 */ + if (!ExecQual(predicate, econtext, false)) { continue; } } - /* - * FormIndexDatum fills in its values and isnull parameters with the - * appropriate values for the column(s) of the index. - */ + /* + * FormIndexDatum会填充其values和isnull参数,其中包含索引的列的适当值。 + */ FormIndexDatum(indexInfo, slot, estate, values, isnull); - /* - * The index AM does the actual insertion, plus uniqueness checking. - * - * For an immediate-mode unique index, we just tell the index AM to - * throw error if not unique. - * - * For a deferrable unique index, we tell the index AM to just detect - * possible non-uniqueness, and we add the index OID to the result - * list if further checking is needed. - */ + /* + * 对于立即模式的唯一索引,我们只需告诉索引AM如果不唯一就抛出错误。 + * + * 对于可延迟的唯一索引,我们告诉索引AM仅检测可能的非唯一性,如果需要进一步检查,则将索引OID添加到结果列表中。 + */ + if (!indexRelation->rd_index->indisunique) { checkUnique = UNIQUE_CHECK_NO; } else if (conflict != NULL) { @@ -1770,17 +1744,14 @@ List* ExecInsertIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* e actualheap, /* heap relation */ checkUnique); /* type of uniqueness check to do */ - /* - * If the index has an associated exclusion constraint, check that. - * This is simpler than the process for uniqueness checks since we - * always insert first and then check. If the constraint is deferred, - * we check now anyway, but don't throw error on violation; instead - * we'll queue a recheck event. - * - * An index for an exclusion constraint can't also be UNIQUE (not an - * essential property, we just don't allow it in the grammar), so no - * need to preserve the prior state of satisfiesConstraint. - */ + /* + * 如果索引有一个关联的排他约束,则进行检查。 + * 这比唯一性检查的过程简单,因为我们总是先插入然后再检查。 + * 如果约束被延迟,我们现在也进行检查,但不会在违反时抛出错误;相反,我们将排队重新检查事件。 + * + * 一个用于排他约束的索引也不能是唯一的(不是必需的属性,我们只是不允许在语法中使用它),所以不需要保留satisfiesConstraint的先前状态。 + */ + if (indexInfo->ii_ExclusionOps != NULL) { bool errorOK = !actualindex->rd_index->indimmediate; @@ -1789,12 +1760,11 @@ List* ExecInsertIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* e } if ((IndexUniqueCheckNoError(checkUnique) || indexInfo->ii_ExclusionOps != NULL) && !satisfiesConstraint) { - /* - * The tuple potentially violates the uniqueness or exclusion - * constraint, so make a note of the index so that we can re-check - * it later. Speculative inserters are told if there was a - * speculative conflict, since that always requires a restart. - */ + /* + * 该元组可能违反唯一性或排除约束,因此请注意索引,以便稍后重新检查它。 + * 如果有投机性冲突,会告诉投机插入者,因为这总是需要重新开始。 + */ + result = lappend_oid(result, RelationGetRelid(indexRelation)); if (conflict != NULL) { *conflict = true; @@ -1807,29 +1777,23 @@ List* ExecInsertIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* e } /* - * Check for violation of an exclusion constraint + * 检查排除约束是否违反 * - * heap: the table containing the new tuple - * index: the index supporting the exclusion constraint - * indexInfo: info about the index, including the exclusion properties - * tupleid: heap TID of the new tuple we have just inserted - * values, isnull: the *index* column values computed for the new tuple - * estate: an EState we can do evaluation in - * newIndex: if true, we are trying to build a new index (this affects - * only the wording of error messages) - * errorOK: if true, don't throw error for violation + * heap: 包含新元组的表 + * index: 支持排除约束的索引 + * indexInfo: 关于索引的信息,包括排除属性 + * tupleid: 我们刚刚插入的新元组的堆TID + * values, isnull: 为新元组计算的*索引*列值 + * estate: 我们可以在其中进行评估的EState + * newIndex: 如果为true,我们正在尝试构建新索引(这仅影响错误消息的措辞) + * errorOK: 如果为true,则不会因违规而抛出错误 * - * Returns true if OK, false if actual or potential violation + * 如果errorOK为true,我们会在不等待查看任何并发事务是否已提交的情况下报告违规;因此,违规仅是潜在的,调用者必须稍后重新检查。 + * 这种行为对于延迟的排除检查非常方便;如果在插入时明确没有冲突,我们就不必费心排队延迟事件。 * - * When errorOK is true, we report violation without waiting to see if any - * concurrent transaction has committed or not; so the violation is only - * potential, and the caller must recheck sometime later. This behavior - * is convenient for deferred exclusion checks; we need not bother queuing - * a deferred event if there is definitely no conflict at insertion time. - * - * When errorOK is false, we'll throw error on violation, so a false result - * is impossible. + * 当errorOK为false时,我们会在违规时抛出错误,因此不可能出现false的结果。 */ + bool check_exclusion_constraint(Relation heap, Relation index, IndexInfo* indexInfo, ItemPointer tupleid, Datum* values, const bool* isnull, EState* estate, bool newIndex, bool errorOK) { @@ -1842,12 +1806,12 @@ static inline IndexScanDesc scan_handler_idx_beginscan_wrapper(Relation parenthe { IndexScanDesc index_scan; if (RelationIsCrossBucketIndex(index) && RELATION_OWN_BUCKET(parentheap)) { - /* for cross-bucket index, pass parent relation to construct HBktIdxScanDesc */ + /* 对于跨桶索引,传递父关系以构造HBktIdxScanDesc */ index_scan = scan_handler_idx_beginscan(parentheap, index, snapshot, nkeys, norderbys, scan_state); HBktIdxScanDesc hpscan = (HBktIdxScanDesc)index_scan; - /* then set scan scope to target heap */ + /* 然后将扫描范围设置为目标堆 */ hpscan->currBktHeapRel = hpscan->currBktIdxScan->heapRelation = heap; - /* also make sure the target heap won't be released at the end of the scan */ + /* 同时确保目标堆在扫描结束时不会被释放 */ hpscan->rs_rd = heap; } else { index_scan = scan_handler_idx_beginscan(heap, index, snapshot, nkeys, norderbys, scan_state); @@ -1886,10 +1850,10 @@ bool check_violation(Relation heap, Relation index, IndexInfo *indexInfo, ItemPo TupleTableSlot* save_scantuple = NULL; Relation parentheap; - /* - * If any of the input values are NULL, the constraint check is assumed to - * pass (i.e., we assume the operators are strict). - */ + /* + * 如果任何输入值为NULL,则假定约束检查通过(即,我们假设操作符是严格的)。 + */ + for (i = 0; i < indnkeyatts; i++) { if (isnull[i]) { return true; @@ -1903,10 +1867,9 @@ bool check_violation(Relation heap, Relation index, IndexInfo *indexInfo, ItemPo constr_procs = indexInfo->ii_UniqueProcs; constr_strats = indexInfo->ii_UniqueStrats; } - /* - * Search the tuples that are in the index for any violations, including - * tuples that aren't visible yet. - */ + /* + * 在索引中搜索违规的元组,包括尚不可见的元组。 + */ InitDirtySnapshot(DirtySnapshot); for (i = 0; i < indnkeyatts; i++) { @@ -1914,27 +1877,27 @@ bool check_violation(Relation heap, Relation index, IndexInfo *indexInfo, ItemPo &scankeys[i], 0, i + 1, constr_strats[i], InvalidOid, index_collations[i], constr_procs[i], values[i]); } - /* - * Need a TupleTableSlot to put existing tuples in. - * - * To use FormIndexDatum, we have to make the econtext's scantuple point - * to this slot. Be sure to save and restore caller's value for - * scantuple. - */ + /* + * 需要一个 TupleTableSlot 用来放置现有的元组。 + * + * 为了使用 FormIndexDatum,我们必须让 econtext 的 scantuple 指向这个插槽。 + * 请确保保存并还原调用者对 scantuple 的值。 + */ + existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap), false, heap->rd_tam_type); econtext = GetPerTupleExprContext(estate); save_scantuple = econtext->ecxt_scantuple; econtext->ecxt_scantuple = existing_slot; - /* - * May have to restart scan from this point if a potential conflict is - * found. - */ + /* + * 如果发现潜在的冲突,可能需要从此处重新开始扫描。 + */ + retry: conflict = false; found_self = false; - /* purely for reducing cyclomatic complexity */ + /* 仅仅是为了降低循环复杂度 */ parentheap = estate->es_result_relation_info->ri_RelationDesc; index_scan = scan_handler_idx_beginscan_wrapper(parentheap, heap, index, &DirtySnapshot, indnkeyatts, 0, NULL); scan_handler_idx_rescan_local(index_scan, scankeys, indnkeyatts, NULL, 0); @@ -1947,9 +1910,7 @@ retry: char* error_new = NULL; char* error_existing = NULL; - /* - * Ignore the entry for the tuple we're trying to check. - */ + /* 忽略我们要检查的元组的条目。 */ ItemPointer item = TUPLE_IS_UHEAP_TUPLE(tup) ? &((UHeapTuple)tup)->ctid : &((HeapTuple)tup)->t_self; if (ItemPointerIsValid(tupleid) && ItemPointerEquals(tupleid, item)) { if (found_self) /* should not happen */ @@ -1960,49 +1921,46 @@ retry: continue; } - /* - * Extract the index column values and isnull flags from the existing - * tuple. - */ + /* 从现有元组中提取索引列的值和isnull标志。 */ + (void)ExecStoreTuple(tup, existing_slot, InvalidBuffer, false); FormIndexDatum(indexInfo, existing_slot, estate, existing_values, existing_isnull); bool is_scan = index_scan_need_recheck(index_scan) && !index_recheck_constraint(index, constr_procs, existing_values, existing_isnull, values); - /* If lossy indexscan, must recheck the condition */ + /* 如果有信息损失的索引扫描,必须重新检查条件 */ if (is_scan) { - /* tuple doesn't actually match, so no conflict */ + /* 元组实际上不匹配,因此没有冲突 */ continue; } - /* - * At this point we have either a conflict or a potential conflict. - * If an in-progress transaction is affecting the visibility of this - * tuple, we need to wait for it to complete and then recheck (unless - * the caller requested not to). For simplicity we do rechecking by - * just restarting the whole scan --- this case probably doesn't - * happen often enough to be worth trying harder, and anyway we don't - * want to hold any index internal locks while waiting. - */ + /* + * 此时我们要么有一个冲突,要么有一个潜在冲突。 + * 如果一个正在进行的事务正在影响此元组的可见性,我们需要等待它完成然后重新检查(除非调用者要求不要这样做)。 + * 为了简化起见,我们通过重新启动整个扫描来进行重新检查 --- 这种情况可能不经常发生,不值得更加努力, + * 无论如何,我们都不想在等待期间持有任何索引内部锁。 + */ xwait = TransactionIdIsValid(DirtySnapshot.xmin) ? DirtySnapshot.xmin : DirtySnapshot.xmax; if (TransactionIdIsValid(xwait) && waitMode == CHECK_WAIT) { scan_handler_idx_endscan(index_scan); - /* for speculative insertion (INSERT ON DUPLICATE KEY UPDATE), - * we only need to wait the speculative token lock to be release, - * which happens when the tuple is speculative inserted by other - * running transction, and has done it's insertion (eithter - * finished or aborted). - */ + /* + * 对于投机插入(INSERT ON DUPLICATE KEY UPDATE), + * 我们只需要等待投机令牌锁被释放, + * 这发生在其他正在运行的事务通过投机插入元组并完成插入(要么完成了,要么中止了)时。 + */ + XactLockTableWait(xwait); goto retry; } - /* Determine whether the index column of the scanned tuple is the same - * as that of the tuple to be inserted. If not, the tuple pointed to by - * the item has been modified by other transactions. Check again for any conflicts. + /* + * 确定扫描的元组的索引列是否与要插入的元组相同。 + * 如果不同,表示该项目指向的元组已被其他事务修改。 + * 重新检查是否存在冲突。 */ + for (int i=0; i < indnkeyatts; i++) { if (existing_isnull[i] != isnull[i]) { conflict = false; @@ -2018,11 +1976,10 @@ retry: } } - /* - * We have a definite conflict (or a potential one, but the caller - * didn't want to wait). If we're not supposed to raise error, just - * return to the caller. - */ + /* + * 我们有一个明确的冲突(或潜在的冲突,但调用者不想等待)。 + * 如果我们不应该引发错误,只需返回给调用者。 + */ if (errorOK) { conflict = true; if (conflictInfo != NULL) { @@ -2034,10 +1991,10 @@ retry: break; } - /* - * We have a definite conflict (or a potential one, but the caller - * didn't want to wait). Report it. - */ + /* + * 我们有一个明确的冲突(或潜在的冲突,但调用者不想等待)。 + * 如果我们不应该引发错误,只需返回给调用者。 + */ error_new = BuildIndexValueDescription(index, values, isnull); error_existing = BuildIndexValueDescription(index, existing_values, existing_isnull); newIndex ? @@ -2058,13 +2015,13 @@ retry: scan_handler_idx_endscan(index_scan); - /* - * Ordinarily, at this point the search should have found the originally - * inserted tuple (if any), unless we exited the loop early because of conflict. - * However, it is possible to define exclusion constraints for which that - * wouldn't be true --- for instance, if the operator is <>. So we no - * longer complain if found_self is still false. - */ + /* + * 通常情况下,到了这一点,搜索应该已经找到了最初插入的元组(如果有的话), + * 除非我们因为冲突而提前退出了循环。然而,也有可能为排除约束定义这样的情况, + * 其中这个条件不成立 --- 例如,如果操作符是<>。 + * 因此,如果found_self仍然为false,我们将不再抱怨。 + */ + econtext->ecxt_scantuple = save_scantuple; ExecDropSingleTupleTableSlot(existing_slot); @@ -2073,9 +2030,10 @@ retry: } /* - * Check existing tuple's index values to see if it really matches the - * exclusion condition against the new_values. Returns true if conflict. + * 检查现有元组的索引值,看它是否与 new_values 真正匹配排除条件。 + * 如果有冲突,返回true。 */ + static bool index_recheck_constraint( Relation index, Oid* constr_procs, Datum* existing_values, const bool* existing_isnull, Datum* new_values) { @@ -2099,22 +2057,23 @@ static bool index_recheck_constraint( /* * UpdateChangedParamSet - * Add changed parameters to a plan node's chgParam set + * 将已更改的参数添加到计划节点的 chgParam 集合中 */ + void UpdateChangedParamSet(PlanState* node, Bitmapset* newchg) { Bitmapset* parmset = NULL; /* - * The plan node only depends on params listed in its allParam set. Don't - * include anything else into its chgParam set. - */ + * 计划节点仅依赖于其 allParam 集合中列出的参数。不要将其他任何东西包含在其 chgParam 集合中。 + */ + parmset = bms_intersect(node->plan->allParam, newchg); - /* - * Keep node->chgParam == NULL if there's not actually any members; this - * allows the simplest possible tests in executor node files. - */ + /* + * 如果实际上没有成员,则保持 node->chgParam == NULL;这允许在执行节点文件中进行最简单的测试。 + */ + if (!bms_is_empty(parmset)) node->chgParam = bms_join(node->chgParam, parmset); else @@ -2122,37 +2081,36 @@ void UpdateChangedParamSet(PlanState* node, Bitmapset* newchg) } /* - * Register a shutdown callback in an ExprContext. + * 在 ExprContext 中注册一个关闭回调。 * - * Shutdown callbacks will be called (in reverse order of registration) - * when the ExprContext is deleted or rescanned. This provides a hook - * for functions called in the context to do any cleanup needed --- it's - * particularly useful for functions returning sets. Note that the - * callback will *not* be called in the event that execution is aborted - * by an error. + * 关闭回调将在删除或重新扫描 ExprContext 时被调用(按注册的相反顺序)。 + * 这为在上下文中调用的函数提供了一个挂钩,用于进行所需的任何清理工作,尤其适用于返回集合的函数。 + * 请注意,如果由错误中止执行,则不会调用回调。 */ + void RegisterExprContextCallback(ExprContext* econtext, ExprContextCallbackFunction function, Datum arg) { ExprContext_CB* ecxt_callback = NULL; - /* Save the info in appropriate memory context */ + /* 将信息保存在适当的内存上下文中 */ ecxt_callback = (ExprContext_CB*)MemoryContextAlloc(econtext->ecxt_per_query_memory, sizeof(ExprContext_CB)); ecxt_callback->function = function; ecxt_callback->arg = arg; ecxt_callback->resowner = t_thrd.utils_cxt.CurrentResourceOwner; - /* link to front of list for appropriate execution order */ + /* 将信息保存在适当的内存上下文中 */ ecxt_callback->next = econtext->ecxt_callbacks; econtext->ecxt_callbacks = ecxt_callback; } /* - * Deregister a shutdown callback in an ExprContext. + * 在ExprContext中取消注册一个关闭回调函数。 * - * Any list entries matching the function and arg will be removed. - * This can be used if it's no longer necessary to call the callback. + * 任何匹配函数和参数的列表条目都将被删除。 + * 如果不再需要调用回调函数,则可以使用此函数。 */ + void UnregisterExprContextCallback(ExprContext* econtext, ExprContextCallbackFunction function, Datum arg) { ExprContext_CB** prev_callback = NULL; @@ -2170,14 +2128,14 @@ void UnregisterExprContextCallback(ExprContext* econtext, ExprContextCallbackFun } /* - * Call all the shutdown callbacks registered in an ExprContext. + * 调用在ExprContext中注册的所有关闭回调函数。 * - * The callback list is emptied (important in case this is only a rescan - * reset, and not deletion of the ExprContext). + * 回调函数列表将被清空(如果这仅是重新扫描重置,而不是删除ExprContext,则这很重要)。 * - * If isCommit is false, just clean the callback list but don't call 'em. - * (See comment for FreeExprContext.) + * 如果isCommit为false,则只清理回调列表但不调用回调函数。 + * (请参阅FreeExprContext的注释。) */ + static void ShutdownExprContext(ExprContext* econtext, bool isCommit) { ExprContext_CB* ecxt_callback = NULL; @@ -2187,15 +2145,16 @@ static void ShutdownExprContext(ExprContext* econtext, bool isCommit) if (econtext->ecxt_callbacks == NULL) return; - /* - * Call the callbacks in econtext's per-tuple context. This ensures that - * any memory they might leak will get cleaned up. - */ + /* + * 在econtext的每个元组上下文中调用回调函数。这可以确保它们可能泄漏的任何内存都将被清理。 + */ + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); /* - * Call each callback function in reverse registration order. - */ + * 按照注册顺序的相反顺序调用每个回调函数。 + */ + ResourceOwner oldOwner = t_thrd.utils_cxt.CurrentResourceOwner; PG_TRY(); { @@ -2219,6 +2178,22 @@ static void ShutdownExprContext(ExprContext* econtext, bool isCommit) MemoryContextSwitchTo(oldcontext); } +/* + * PthreadMutexLock - 尝试获取或等待一个pthread互斥锁 + * + * 此函数尝试获取一个pthread互斥锁。如果获取成功,函数返回0,否则返回错误码。 + * + * 参数: + * - owner: 资源拥有者,表示该互斥锁受此资源拥有者的管理。可以为NULL。 + * - mutex: 要获取的pthread互斥锁。 + * - trace: 是否启用跟踪标志,用于记录互斥锁的使用情况。 + * + * 注意: + * - 此函数在尝试获取互斥锁之前会禁用中断,以避免竞态条件。 + * - 如果指定了资源拥有者(owner非NULL),则函数将确保该资源拥有者已准备好用于存储pthread互斥锁的信息。 + * - 如果获取互斥锁成功且启用了跟踪标志,函数将记录该互斥锁的使用情况。 + * - 最后,函数会恢复中断状态,并返回获取互斥锁的结果(0表示成功,否则表示失败)。 + */ int PthreadMutexLock(ResourceOwner owner, pthread_mutex_t* mutex, bool trace) { @@ -2233,6 +2208,22 @@ int PthreadMutexLock(ResourceOwner owner, pthread_mutex_t* mutex, bool trace) RESUME_INTERRUPTS(); return ret; } +/* + * PthreadMutexTryLock - 尝试非阻塞获取pthread互斥锁 + * + * 此函数尝试非阻塞地获取一个pthread互斥锁。如果获取成功,函数返回0,否则返回错误码。 + * + * 参数: + * - owner: 资源拥有者,表示该互斥锁受此资源拥有者的管理。可以为NULL。 + * - mutex: 要获取的pthread互斥锁。 + * - trace: 是否启用跟踪标志,用于记录互斥锁的使用情况。 + * + * 注意: + * - 此函数在尝试获取互斥锁之前会禁用中断,以避免竞态条件。 + * - 如果指定了资源拥有者(owner非NULL),则函数将确保该资源拥有者已准备好用于存储pthread互斥锁的信息。 + * - 如果非阻塞获取互斥锁成功且启用了跟踪标志,函数将记录该互斥锁的使用情况。 + * - 最后,函数会恢复中断状态,并返回获取互斥锁的结果(0表示成功,否则表示失败)。 + */ int PthreadMutexTryLock(ResourceOwner owner, pthread_mutex_t* mutex, bool trace) { @@ -2248,6 +2239,7 @@ int PthreadMutexTryLock(ResourceOwner owner, pthread_mutex_t* mutex, bool trace) return ret; } +//释放一个 pthread 互斥锁(mutex) int PthreadMutexUnlock(ResourceOwner owner, pthread_mutex_t* mutex, bool trace) { HOLD_INTERRUPTS(); @@ -2258,7 +2250,7 @@ int PthreadMutexUnlock(ResourceOwner owner, pthread_mutex_t* mutex, bool trace) return ret; } - +//用于尝试以读取锁(read lock)的方式获取一个 pthread 读写锁(rwlock) int PthreadRWlockTryRdlock(ResourceOwner owner, pthread_rwlock_t* rwlock) { if (owner) { @@ -2277,7 +2269,7 @@ int PthreadRWlockTryRdlock(ResourceOwner owner, pthread_rwlock_t* rwlock) RESUME_INTERRUPTS(); return ret; } - +//用于以读取锁(read lock)的方式获取一个 pthread 读写锁(rwlock) void PthreadRWlockRdlock(ResourceOwner owner, pthread_rwlock_t* rwlock) { if (owner) { @@ -2298,6 +2290,7 @@ void PthreadRWlockRdlock(ResourceOwner owner, pthread_rwlock_t* rwlock) RESUME_INTERRUPTS(); } +//用于以尝试写入锁(try write lock)的方式获取一个 pthread 读写锁(rwlock) int PthreadRWlockTryWrlock(ResourceOwner owner, pthread_rwlock_t* rwlock) { if (owner) { @@ -2315,7 +2308,7 @@ int PthreadRWlockTryWrlock(ResourceOwner owner, pthread_rwlock_t* rwlock) RESUME_INTERRUPTS(); return ret; } - +//用于以阻塞方式获取一个 pthread 读写锁(rwlock)的写入锁(write lock) void PthreadRWlockWrlock(ResourceOwner owner, pthread_rwlock_t* rwlock) { if (owner) { @@ -2335,6 +2328,7 @@ void PthreadRWlockWrlock(ResourceOwner owner, pthread_rwlock_t* rwlock) } RESUME_INTERRUPTS(); } +//用于释放 pthread 读写锁(rwlock) void PthreadRWlockUnlock(ResourceOwner owner, pthread_rwlock_t* rwlock) { HOLD_INTERRUPTS(); @@ -2351,7 +2345,7 @@ void PthreadRWlockUnlock(ResourceOwner owner, pthread_rwlock_t* rwlock) } RESUME_INTERRUPTS(); } - +//用于初始化 pthread 读写锁(rwlock) void PthreadRwLockInit(pthread_rwlock_t* rwlock, pthread_rwlockattr_t *attr) { int ret = pthread_rwlock_init(rwlock, attr); -- 2.34.1 From c986f2fecc42b784da7094d616154b734e56d1f3 Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Tue, 5 Sep 2023 23:12:01 +0800 Subject: [PATCH 19/31] Update execScan.cpp --- src/gausskernel/runtime/executor/execScan.cpp | 68 ++++++++++++------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/src/gausskernel/runtime/executor/execScan.cpp b/src/gausskernel/runtime/executor/execScan.cpp index b305361ba..3f65d5d58 100644 --- a/src/gausskernel/runtime/executor/execScan.cpp +++ b/src/gausskernel/runtime/executor/execScan.cpp @@ -295,12 +295,22 @@ void ExecAssignScanProjectionInfo(ScanState* node) /* * ExecAssignScanProjectionInfoWithVarno - * As above, but caller can specify varno expected in Vars in the tlist. - * This function is called by ExecInitExtensiblePlan to initialize projection info. - * Usually the caller provides a targetlist describing the scan tuples, so we can - * avoid a projection step by setting ps_ProjInfo to NULL. Such as "SELECT * FROM ...". + * 如上所述,但调用者可以在tlist中的Vars中指定varno。 + * ExecInitExtensiblePlan调用此函数来初始化投影信息。 + * 通常,调用者提供一个描述扫描元组的目标列表,因此我们可以通过将ps_ProjegInfo设置为NULL来避免投影步骤。例如“SELECT*FROM…”。 */ void ExecAssignScanProjectionInfoWithVarno(ScanState* node, Index var_no) +/* 函数ExecAssignScanProjectionInfoWithVarno将ScanState对象和Index变量号作为参数。它用于为具有特定变量编号的扫描节点分配投影信息。 + 以下是该功能的逐步分解: + 它采用ScanState对象node,表示执行计划中扫描操作的状态。 + 它采用var_no变量号,用于标识需要为其分配投影信息的特定变量。 + 函数首先检查var_no是否有效并且是否在可用变量的范围内。 + 如果var_no有效,则函数从预定义的数据结构或查找表中检索与该变量编号相关联的投影信息。 + 投影信息通常包括诸如目标列表、目标表达式和处理扫描操作所需的其他相关信息之类的细节。 + 一旦检索到投影信息,就会将其分配给节点对象,更新其内部状态以反映指定变量的投影信息。 + 总体而言,函数ExecAssignScanProjectionInfoWithVarno负责为具有特定变量号的扫描节点分配投影信息,使扫描操作能够正确处理和检索所需数据。 + */ + { Scan* scan = (Scan*)node->ps.plan; @@ -317,46 +327,42 @@ bool tlist_matches_tupdesc(PlanState* ps, List* tlist, Index var_no, TupleDesc t bool has_oid = false; ListCell* tlist_item = list_head(tlist); - /* Check the tlist attributes */ + /* 检查tlist属性 */ for (attr_no = 1; attr_no <= num_attrs; attr_no++) { Form_pg_attribute att_tup = tup_desc->attrs[attr_no - 1]; Var* var = NULL; if (tlist_item == NULL) - return false; /* tlist too short */ + return false; /* tlist太短 */ var = (Var*)((TargetEntry*)lfirst(tlist_item))->expr; if (var == NULL || !IsA(var, Var)) - return false; /* tlist item not a Var */ - /* if these Asserts fail, planner messed up */ + return false; /* tlist项不是Var */ + /* 如果这些断言失败,计划者就会搞砸 */ Assert(var->varno == var_no); Assert(var->varlevelsup == 0); if (var->varattno != attr_no) - return false; /* out of order */ + return false; /* 发生故障 */ if (att_tup->attisdropped) - return false; /* table contains dropped columns */ + return false; /* 表包含删除的列 */ /* - * Note: usually the Var's type should match the tupdesc exactly, but - * in situations involving unions of columns that have different - * typmods, the Var may have come from above the union and hence have - * typmod -1. This is a legitimate situation since the Var still - * describes the column, just not as exactly as the tupdesc does. We - * could change the planner to prevent it, but it'd then insert - * projection steps just to convert from specific typmod to typmod -1, - * which is pretty silly. + * 注意:通常Var的类型应该与元组完全匹配,但在涉及具有不同类型mod的列的并集的情况下, + * Var可能来自并集之上,因此具有类型mod-1。这是一种合理的情况,因为Var仍然描述列, + * 只是不像tudesc那样准确。我们可以更改计划来防止它,但它会插入投影步骤, + * 只是为了从特定的typmod转换为typmod-1,这很愚蠢。 */ if (var->vartype != att_tup->atttypid || (var->vartypmod != att_tup->atttypmod && var->vartypmod != -1)) - return false; /* type mismatch */ + return false; /* 类型不匹配 */ tlist_item = lnext(tlist_item); } if (tlist_item != NULL) - return false; /* tlist too long */ + return false; /* tlist 列表太长 */ /* - * If the plan context requires a particular hasoid setting, then that has - * to match, too. + * 如果计划上下文需要特定的hasoid设置, + * 那么它也必须匹配。 */ if (ExecContextForcesOids(ps, &has_oid) && has_oid != tup_desc->tdhasoid) return false; @@ -367,17 +373,16 @@ bool tlist_matches_tupdesc(PlanState* ps, List* tlist, Index var_no, TupleDesc t /* * ExecScanReScan * - * This must be called within the ReScan function of any plan node type - * that uses ExecScan(). + * 这必须在使用ExecScan()的任何计划节点类型的ReScan函数中调用。 */ void ExecScanReScan(ScanState* node) { EState* estate = node->ps.state; - /* Stop projecting any tuples from SRFs in the targetlist */ + /* 停止从目标列表中的SRF投影任何元组 */ node->ps.ps_TupFromTlist = false; - /* Rescan EvalPlanQual tuple if we're inside an EvalPlanQual recheck */ + /* 如果我们在EvalPlanQual复查中,则重新扫描EvalPlanQual元组 */ if (estate->es_epqScanDone != NULL) { Index scan_rel_id = ((Scan*)node->ps.plan)->scanrelid; @@ -386,3 +391,14 @@ void ExecScanReScan(ScanState* node) estate->es_epqScanDone[scan_rel_id - 1] = false; } } +/* 函数ExecScanReScan将ScanState对象作为参数,用于重置扫描操作的状态,以便重新扫描数据。 + 以下是该功能的逐步分解: + 它采用ScanState对象node,表示执行计划中扫描操作的状态。 + 该函数首先检查扫描操作是否已初始化,以及执行重新扫描是否安全。 + 这样做通常是为了确保必要的资源可用,并且扫描操作处于重新扫描的有效状态。 + 如果重新扫描是安全的,该功能将执行必要的步骤来重置扫描操作。这可能涉及重置内部指针、重新初始化变量或释放在上次扫描期间分配的任何资源。 + 一旦重置了扫描操作,就可以从头开始重新执行,从而允许对底层数据源进行新的扫描。 + 这在数据已被修改或出于任何原因需要重复扫描操作的情况下很有用。 + 总体而言,ExecScanReScan功能提供了一种重置扫描操作状态的机制,使其能够从头开始重新执行。 + 这使得处理和分析数据具有灵活性,尤其是在需要重复扫描操作或基础数据发生变化的情况下。 + */ -- 2.34.1 From bd5ccf49db3ffc78bc59eeb40db2d664c167b2fc Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Wed, 6 Sep 2023 20:27:48 +0800 Subject: [PATCH 20/31] Update execTuples.cpp --- .../runtime/executor/execTuples.cpp | 49 +++++++++---------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/src/gausskernel/runtime/executor/execTuples.cpp b/src/gausskernel/runtime/executor/execTuples.cpp index d2c0517c7..df45d0c3a 100644 --- a/src/gausskernel/runtime/executor/execTuples.cpp +++ b/src/gausskernel/runtime/executor/execTuples.cpp @@ -1,20 +1,17 @@ /* ------------------------------------------------------------------------- * * execTuples.cpp - * Routines dealing with TupleTableSlots. These are used for resource - * management associated with tuples (eg, releasing buffer pins for - * tuples in disk buffers, or freeing the memory occupied by transient - * tuples). Slots also provide access abstraction that lets us implement - * "virtual" tuples to reduce data-copying overhead. + * 处理TupleTableSlots的例程。这些用于与元组相关的资源管理 + *(例如,释放磁盘缓冲区中元组的缓冲引脚,或释放传输元组占用的内存)。 + * 插槽还提供访问抽象,使我们能够实现“虚拟”元组,以减少数据复制开销。 * - * Routines dealing with the type information for tuples. Currently, - * the type information for a tuple is an array of FormData_pg_attribute. - * This information is needed by routines manipulating tuples - * (getattribute, formtuple, etc.). + * 处理元组的类型信息的例程。 + * 目前,元组的类型信息是FormData_pg_attribute的数组。 + * 处理元组(getattribute、formtuple等)的例程需要这些信息。 * - * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California + * 部分版权所有(c)2020华为技术有限公司有限公司。 + * 部分版权所有(c)1996-2012,PostgreSQL全球发展集团 + * 部分版权所有(c)1994,加州大学董事会 * * * IDENTIFICATION @@ -24,22 +21,22 @@ * INTERFACE ROUTINES * * SLOT CREATION/DESTRUCTION - * MakeTupleTableSlot - create an empty slot - * ExecAllocTableSlot - create a slot within a tuple table - * ExecResetTupleTable - clear and optionally delete a tuple table - * MakeSingleTupleTableSlot - make a standalone slot, set its descriptor - * ExecDropSingleTupleTableSlot - destroy a standalone slot + * MakeTupleTableSlot - 创建一个空插槽 + * ExecAllocTableSlot - 在元组表中创建槽 + * ExecResetTupleTable - 清除并可选择删除元组表 + * MakeSingleTupleTableSlot - 制作一个独立插槽,设置其描述符 + * ExecDropSingleTupleTableSlot - 销毁独立插槽 * * SLOT ACCESSORS - * ExecSetSlotDescriptor - set a slot's tuple descriptor - * ExecStoreTuple - store a physical tuple in the slot - * ExecStoreMinimalTuple - store a minimal physical tuple in the slot - * ExecClearTuple - clear contents of a slot - * ExecStoreVirtualTuple - mark slot as containing a virtual tuple - * ExecCopySlotTuple - build a physical tuple from a slot - * ExecCopySlotMinimalTuple - build a minimal physical tuple from a slot - * ExecMaterializeSlot - convert virtual to physical storage - * ExecCopySlot - copy one slot's contents to another + * ExecSetSlotDescriptor - 设置槽的元组描述符 + * ExecStoreTuple - 在插槽中存储物理元组 + * ExecStoreMinimalTuple - 在插槽中存储最小物理元组 + * ExecClearTuple - 清除槽中的内容 + * ExecStoreVirtualTuple - 将slot标记为包含虚拟元组 + * ExecCopySlotTuple - 从插槽构建物理元组 + * ExecCopySlotMinimalTuple - 从插槽构建最小物理元组 + * ExecMaterializeSlot - 将虚拟存储转换为物理存储 + * ExecCopySlot - 将一个插槽的内容复制到另一个插槽 * * CONVENIENCE INITIALIZATION ROUTINES * ExecInitResultTupleSlot \ convenience routines to initialize -- 2.34.1 From 3535ac2dbd0a6ff08e0736e6b3d1aa48c4296827 Mon Sep 17 00:00:00 2001 From: LYLlyl Date: Wed, 6 Sep 2023 21:43:50 +0800 Subject: [PATCH 21/31] Update lightProxy.cpp --- .../runtime/executor/lightProxy.cpp | 495 ++++++++++++------ 1 file changed, 326 insertions(+), 169 deletions(-) diff --git a/src/gausskernel/runtime/executor/lightProxy.cpp b/src/gausskernel/runtime/executor/lightProxy.cpp index d39d1786c..4d062f142 100644 --- a/src/gausskernel/runtime/executor/lightProxy.cpp +++ b/src/gausskernel/runtime/executor/lightProxy.cpp @@ -1,26 +1,25 @@ /* - * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * 版权 (c) 2020 华为技术有限公司。 * - * openGauss is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: + * openGauss 使用 Mulan PSL v2 许可证发布。 + * 您可以根据 Mulan PSL v2 的条款和条件使用本软件。 + * 您可以在以下网址获取 Mulan PSL v2 的副本: + * http://license.coscl.org.cn/MulanPSL2 * - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. + * 本软件按照 "AS IS" 基础提供,不提供任何形式的担保, + * 包括但不限于明示或默示的担保,适销性或适用性。 + * 有关更多详细信息,请参阅 Mulan PSL v2 许可证。 * ------------------------------------------------------------------------- * - * lightProxy.cpp + * 文件名: lightProxy.cpp * - * IDENTIFICATION - * src/gausskernel/runtime/executor/lightProxy.cpp + * 识别标识 + * 位置: src/gausskernel/runtime/executor/lightProxy.cpp * * ------------------------------------------------------------------------- */ + #include "access/transam.h" #include "access/xact.h" #include "utils/dynahash.h" @@ -119,24 +118,33 @@ extern void light_pgaudit_ExecutorEnd(Query* query); void report_qps_type(CmdType commandType); CmdType set_cmd_type(const char* commandTag); +/*在特定条件下报告插入、更新、删除、合并等数据修改操作的时间戳(data_changed_timestamp), + 以便在轻量代理(light proxy)环境中进行数据同步或其他操作。*/ static void report_iud_time_for_lightproxy(const Query* query) { + // 检查是否启用保存数据修改时间戳的功能,如果未启用,则退出函数。 if (u_sess->attr.attr_sql.enable_save_datachanged_timestamp == false) return; + // 检查当前会话是否是分布式协调器或单节点模式,如果不是,则退出函数。 if (!IS_PGXC_COORDINATOR && !IS_SINGLE_NODE) return; + // 检查查询的命令类型,只处理插入、删除、更新和合并操作。 if (query->commandType != CMD_INSERT && query->commandType != CMD_DELETE && query->commandType != CMD_UPDATE && query->commandType != CMD_MERGE) { return; } + // 检查查询是否有关系表,如果没有,退出函数。 if (query->rtable == NULL) return; + // 获取与结果关系相关的表格项(RangeTblEntry)。 if (query->resultRelation <= list_length(query->rtable)) { RangeTblEntry* rte = (RangeTblEntry*)list_nth(query->rtable, query->resultRelation - 1); + + // 检查表格项的类型,只处理关系表(RTE_RELATION)。 if (RTE_RELATION != rte->rtekind) return; @@ -145,7 +153,10 @@ static void report_iud_time_for_lightproxy(const Query* query) PG_TRY(); { + // 尝试打开表格(heap_open)并获取表格的元数据。 rel = heap_open(rte->relid, AccessShareLock); + + // 如果表格类型是关系表(RELKIND_RELATION),并且持久性属性为永久表格或非日志记录表格,报告数据修改时间戳。 if (rel->rd_rel->relkind == RELKIND_RELATION) { if (rel->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT || rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED) { @@ -153,6 +164,7 @@ static void report_iud_time_for_lightproxy(const Query* query) } } + // 关闭表格。 heap_close(rel, AccessShareLock); } PG_CATCH(); @@ -161,12 +173,15 @@ static void report_iud_time_for_lightproxy(const Query* query) ErrorData* edata = CopyErrorData(); + // 在捕获异常后,记录错误消息并继续执行。 ereport(DEBUG1, (errmsg("Failed to send data changed time, cause: %s", edata->message))); + // 清空错误状态。 FlushErrorState(); FreeErrorData(edata); + // 如果表格已打开,关闭它。 if (rel != NULL) heap_close(rel, AccessShareLock); } @@ -174,13 +189,17 @@ static void report_iud_time_for_lightproxy(const Query* query) } } +//在轻量代理环境中检查某些操作或条件是否受支持,并在不支持的情况下生成相应的调试信息。 static void report_unsupport_light(LightUnSupportType type) { + // 如果传入的type参数为CTRL_DISABLE,表示不支持此类型操作,直接返回。 if (type == CTRL_DISABLE) { return; } - char* unsupport_msg[MAX_UNSUPPORT_TYPE] = {"guc ctrl disable", + // 定义了一组不支持的情况的描述信息,根据传入的type选择相应的描述信息。 + char* unsupport_msg[MAX_UNSUPPORT_TYPE] = { + "guc ctrl disable", "not support client encoding different from database encoding", "not support cursor", "not support execute direct on", @@ -188,18 +207,22 @@ static void report_unsupport_light(LightUnSupportType type) "not support table entry relkind is foreign", "not support query has a statement trigger", "not support user-defined type", - "not support query with node_name hint"}; + "not support query with node_name hint" + }; + // 记录不支持的情况的信息,通常用于调试目的。 ereport(DEBUG2, (errmodule(MOD_LIGHTPROXY), errmsg("[LIGHT PROXY] check failed with type: %s.", unsupport_msg[type]))); return; } +//接受一个指向查询的指针Query* query作为参数,并检查该查询是否支持轻量级查询。 static bool isSupportLightQuery(Query* query) { ListCell* item = NULL; + // 检查是否启用快速查询发货功能,如果未启用,则不支持轻量级查询。 if (!u_sess->attr.attr_sql.enable_fast_query_shipping || (!u_sess->attr.attr_sql.enable_light_proxy && !GTM_LITE_MODE)) { report_unsupport_light(CTRL_DISABLE); @@ -207,27 +230,28 @@ static bool isSupportLightQuery(Query* query) return false; } + // 检查客户端编码是否与数据库编码一致,如果不一致,则不支持轻量级查询。 if (pg_get_client_encoding() != GetDatabaseEncoding()) { report_unsupport_light(ENCODE_UNSUPPORT); return false; } - /* not support cursor */ + // 检查是否存在游标声明,如果存在,则不支持轻量级查询。 if (query->utilityStmt && IsA(query->utilityStmt, DeclareCursorStmt)) { report_unsupport_light(CURSOR_UNSUPPORT); return false; } - /* not support execute direct on */ + // 检查是否存在远程查询,如果存在,则不支持轻量级查询。 if (query->utilityStmt && IsA(query->utilityStmt, RemoteQuery)) { report_unsupport_light(REMOTE_UNSUPPORT); return false; } - /* not support others */ + // 检查查询类型是否为支持的类型之一,否则不支持轻量级查询。 if (query->commandType != CMD_SELECT && query->commandType != CMD_UPDATE && query->commandType != CMD_INSERT && query->commandType != CMD_DELETE && !(HAS_ROUTER && query->commandType == CMD_MERGE)) { report_unsupport_light(CMD_UNSUPPORT); @@ -235,12 +259,13 @@ static bool isSupportLightQuery(Query* query) return false; } - /* do not support node_name hint due to agg function's different behavior */ + // 检查是否存在节点名称提示,如果存在,则不支持轻量级查询。 if (CheckNodeNameHint(query->hintState)) { report_unsupport_light(NODE_NAME_UNSUPPORT); return false; } + // 遍历查询的表列表,并检查每个表的相关条件是否支持轻量级查询。 foreach (item, query->rtable) { RangeTblEntry* rte = (RangeTblEntry*)lfirst(item); #ifdef ENABLE_MOT @@ -253,11 +278,7 @@ static bool isSupportLightQuery(Query* query) return false; } - /* - * Essentially, lightProxy is a fast path for FQSed when length - * of exec_nodes is 1, which is not supported when query has a - * statement trigger. - */ + // 检查是否存在语句触发器,如果存在,则不支持轻量级查询。 if (pgxc_find_statement_trigger(rte->relid, query->commandType)) { report_unsupport_light(STATEMENT_UNSUPPORT); @@ -265,13 +286,13 @@ static bool isSupportLightQuery(Query* query) } } - /* check the target list for T message */ + // 检查查询的目标列表中是否包含不支持的元素,如果包含,则不支持轻量级查询。 foreach (item, query->targetList) { TargetEntry* tle = (TargetEntry*)lfirst(item); if (tle->resjunk) continue; - /* not support user-defined type */ + // 检查是否存在用户定义的数据类型,如果存在,则不支持轻量级查询。 if (exprType((Node*)tle->expr) >= FirstBootstrapObjectId) { report_unsupport_light(USERTYPE_UNSUPPORT); @@ -279,9 +300,11 @@ static bool isSupportLightQuery(Query* query) } } + // 如果通过了所有上述条件的检查,表示支持轻量级查询。 return true; } +//构造函数,用于初始化一个名为lightProxy的对象的成员变量 lightProxy::lightProxy(Query *query) : m_cplan(NULL), m_nodeIdx(-1), @@ -308,6 +331,7 @@ lightProxy::lightProxy(Query *query) #endif } +//类lightProxy的构造函数1 lightProxy::lightProxy(MemoryContext context, CachedPlanSource *psrc, const char *portalname, const char *stmtname) : m_cplan(psrc), m_nodeIdx(-1), @@ -354,7 +378,7 @@ lightProxy::lightProxy(MemoryContext context, CachedPlanSource *psrc, const char MemoryContextSwitchTo(old_context); } - +//类lightProxy的构造函数2 lightProxy::~lightProxy() { m_cplan = NULL; @@ -363,22 +387,29 @@ lightProxy::~lightProxy() m_handle = NULL; m_msgctl = NULL; m_entry = NULL; - pfree_ext(m_formats); + pfree_ext(m_formats);// 释放m_formats的内存 } +//从消息中获取结果格式代码,并根据不同情况分配和设置这些格式代码 void lightProxy::getResultFormat(StringInfo message) { + // 释放先前分配的m_formats内存 pfree_ext(m_formats); + + // 如果 CachedPlanSource 的 resultDesc 为空,直接返回 if (m_cplan->resultDesc == NULL) return; - /* Get the result format codes */ + + // 获取消息中的结果格式代码数量 int numRFormats = pq_getmsgint(message, 2); int i = 0; int natts = m_cplan->resultDesc->natts; int16* formats = NULL; + + // 分配内存以存储结果格式代码 m_formats = (int16*)palloc(natts * sizeof(int16)); - /* Get the result format codes */ + // 获取消息中的结果格式代码 if (numRFormats > 0) { formats = (int16*)palloc(numRFormats * sizeof(int16)); for (i = 0; i < numRFormats; i++) @@ -386,6 +417,7 @@ void lightProxy::getResultFormat(StringInfo message) } pq_getmsgend(message); + // 处理不同情况下的结果格式代码赋值逻辑 if (numRFormats > 1) { if (numRFormats != natts) { pfree_ext(formats); @@ -402,57 +434,80 @@ void lightProxy::getResultFormat(StringInfo message) for (i = 0; i < natts; i++) m_formats[i] = 0; } + + // 释放分配的 formats 内存 pfree_ext(formats); } +//根据消息类型将消息内容保存到适当的成员变量中 void lightProxy::saveMsg(int msgType, StringInfo message) { - // to do , save memory consumption + // 切换内存上下文为m_context,以便在特定上下文中保存消息 AutoContextSwitch contexts(m_context); + // 如果消息类型是BIND_MESSAGE if (msgType == BIND_MESSAGE) { /* clean previous message if exists */ + // 如果m_bindMessage已经包含数据,重置m_bindMessage以清除先前的消息内容 if (m_bindMessage.len > 0) resetStringInfo(&m_bindMessage); + + // 如果m_describeMessage已经包含数据,重置m_describeMessage以清除先前的消息内容 if (m_describeMessage.len > 0) resetStringInfo(&m_describeMessage); + // 获取并保存结果格式信息,然后追加消息内容到m_bindMessage getResultFormat(message); appendBinaryStringInfo(&m_bindMessage, message->data, message->len); } else if (msgType == DESC_MESSAGE) { /* clean previous message if exists */ + // 如果m_describeMessage已经包含数据,重置m_describeMessage以清除先前的消息内容 if (m_describeMessage.len > 0) resetStringInfo(&m_describeMessage); + + // 追加消息内容到m_describeMessage appendBinaryStringInfo(&m_describeMessage, message->data, message->len); } } +//根据当前的数据节点索引建立或重用与数据节点的连接 void lightProxy::connect() { List* dn_allocate = NULL; errno_t ss_rc = 0; int dnNum = u_sess->pgxc_cxt.NumDataNodes; + + // 在CN灾难恢复模式下,重新计算数据节点数量和初始化灾难读取数组 if (IS_CN_DISASTER_RECOVER_MODE) { dnNum = u_sess->pgxc_cxt.NumTotalDataNodes; if (!u_sess->pgxc_cxt.DisasterReadArrayInit) { disaster_read_array_init(); } Assert(m_nodeIdx < u_sess->pgxc_cxt.NumDataNodes); + + // 使用灾难读取数组获取当前数据节点的索引 if (u_sess->pgxc_cxt.disasterReadArray[m_nodeIdx] != -1) { m_nodeIdx = u_sess->pgxc_cxt.disasterReadArray[m_nodeIdx]; } } + // 获取当前数据节点的连接句柄 m_handle = &u_sess->pgxc_cxt.dn_handles[m_nodeIdx]; + + // 如果连接句柄无效,则尝试建立新的连接 if (!IS_VALID_CONNECTION(m_handle)) { Assert(m_nodeIdx < dnNum); + if (m_nodeIdx >= dnNum) { ereport(ERROR, (errcode(ERRCODE_CONNECTION_FAILURE), errmsg("[LIGHT PROXY] m_nodeIdx error, m_nodeIdx:%d, numDataNodes:%d", m_nodeIdx, dnNum))); } + // 将当前数据节点索引添加到连接分配列表 dn_allocate = lappend_int(dn_allocate, m_nodeIdx); + + // 从连接池中获取连接 PoolConnDef* pfds = PoolManagerGetConnections(dn_allocate, NULL); if (pfds == NULL) ereport(ERROR, @@ -461,33 +516,43 @@ void lightProxy::connect() m_handle->remoteNodeName, m_handle->nodeoid))); + // 获取连接的文件描述符和连接信息 int fdsock = pfds->fds[0]; PoolConnInfo* conn_info = &pfds->connInfos[0]; + // 初始化连接句柄 pgxc_node_init(m_handle, fdsock); ss_rc = memcpy_s(&m_handle->connInfo, sizeof(PoolConnInfo), conn_info, sizeof(PoolConnInfo)); - securec_check(ss_rc, "\0", "\0"); + + // 发送全局会话ID(仅在多节点模式下) #ifdef ENABLE_MULTIPLE_NODES pgxc_node_send_global_session_id((PGXCNodeHandle*)m_handle); #endif + + // 更新数据节点句柄信息和计数 u_sess->pgxc_cxt.dn_handles[m_nodeIdx] = *m_handle; u_sess->pgxc_cxt.datanode_count++; + // 如果存在逻辑连接,将其关联到连接句柄 if (pfds->gsock[0].type != GSOCK_INVALID) { m_handle->gsock = pfds->gsock[0]; m_handle->is_logic_conn = true; } + + // 释放连接池分配的资源 pgxc_node_free_def(pfds); pfds = NULL; } else if (m_handle->state == DN_CONNECTION_STATE_QUERY) { + // 如果连接句柄的状态为DN_CONNECTION_STATE_QUERY,缓冲连接 BufferConnection(m_handle); } } +//根据不同情况发送PARSE请求到数据节点,以准备执行查询 void lightProxy::sendParseIfNecessary() { - /* if no stmt_name, we need to send parse every time */ + /* 如果没有stmt_name,每次都需要发送parse */ if (m_stmtName == NULL || m_stmtName[0] == '\0') { if (pgxc_node_send_parse( m_handle, m_stmtName, m_cplan->query_string, m_cplan->num_params, m_cplan->param_types)) { @@ -502,15 +567,16 @@ void lightProxy::sendParseIfNecessary() if (unlikely(m_entry == NULL)) { /* - * If we have reloaded pooler, we need to add it into datanode_queries again, - * as we do in parse phrase previously. + * 如果我们已经重新加载了池化器,我们需要将其添加到datanode_queries中, + * 就像我们在解析阶段之前所做的那样。 */ m_entry = light_set_datanode_queries(m_stmtName); Assert(m_entry != NULL); } Assert(m_nodeIdx != -1); bool need_send_again = false; - /* see if statement already active on the node */ + + /* 检查语句是否已在节点上激活 */ for (int i = 0; i < m_entry->current_nodes_number; i++) { if (m_entry->dns_node_indices[i] == m_nodeIdx) { if (ENABLE_CN_GPC || IN_GPC_GRAYRELEASE_CHANGE) { @@ -521,6 +587,7 @@ void lightProxy::sendParseIfNecessary() } } + /* 发送parse请求到数据节点 */ if (pgxc_node_send_parse( m_handle, m_stmtName, m_cplan->query_string, m_cplan->num_params, m_cplan->param_types)) { ereport(ERROR, @@ -529,10 +596,12 @@ void lightProxy::sendParseIfNecessary() m_handle->remoteNodeName, m_handle->nodeoid))); } + if (need_send_again) { return; } - /* After cluster expansion, must expand entry->dns_node_indices array too */ + + /* 在集群扩展后,必须扩展entry->dns_node_indices数组 */ if (unlikely(m_entry->current_nodes_number == m_entry->max_nodes_number)) { int* new_dns_node_indices = (int*)MemoryContextAllocZero( u_sess->pcache_cxt.datanode_queries->hcxt, m_entry->max_nodes_number * 2 * sizeof(int)); @@ -545,13 +614,14 @@ void lightProxy::sendParseIfNecessary() pfree_ext(m_entry->dns_node_indices); m_entry->dns_node_indices = new_dns_node_indices; m_entry->max_nodes_number = m_entry->max_nodes_number * 2; + elog(LOG, "expand node ids array for active datanode statements " "after cluster expansion, now array size is %d", m_entry->max_nodes_number); } - /* statement is not active on the specified node append item to the list */ + /* 语句在指定节点上未激活,将项目添加到列表中 */ m_entry->dns_node_indices[m_entry->current_nodes_number++] = m_nodeIdx; } @@ -559,14 +629,18 @@ void lightProxy::sendParseIfNecessary() * @Description: Send BEGIN command to the DataNode. Also send the GXID for the transaction. * See pgxc_node_begin for more details. */ +//在轻量级代理中开始与数据节点的交互过程,并根据不同情况发送相应的命令、事务块以及其他控制信息给数据节点。 void lightProxy::proxyNodeBegin(bool is_read_only) { bool need_tran_block = false; GlobalTransactionId gxid = InvalidTransactionId; + + // 如果存在路由节点,需要进行事务处理,因此将is_read_only设置为false if (HAS_ROUTER) { - // push down function to router dn, need transaction is_read_only = false; } + + // 根据当前是否处于事务块中和is_read_only标志来确定是否需要事务块 if (IsTransactionBlock()) { need_tran_block = true; } else if (is_read_only) { @@ -575,21 +649,20 @@ void lightProxy::proxyNodeBegin(bool is_read_only) need_tran_block = true; } + // 如果是只读事务,则获取当前事务ID if (is_read_only) { gxid = GetCurrentTransactionIdIfAny(); } - /* - * If the node is already a participant in the transaction, skip it - */ + // 如果节点已经是事务的参与者,跳过以下操作 if (list_member(u_sess->pgxc_cxt.XactReadNodes, m_handle) || list_member(u_sess->pgxc_cxt.XactWriteNodes, m_handle)) { if (!is_read_only) { RegisterTransactionNodes(1, (void**)&m_handle, true); } } else { + // 设置当前语句的时间戳,并发送给数据节点 SetCurrentStmtTimestamp(); - TimestampTz gtmstart_timestamp = GetCurrentGTMStartTimestamp(); TimestampTz stmtsys_timestamp = GetCurrentStmtsysTimestamp(); if (GlobalTimestampIsValid(gtmstart_timestamp) && @@ -602,6 +675,7 @@ void lightProxy::proxyNodeBegin(bool is_read_only) m_handle->nodeoid))); } + // 如果需要事务块,发送内部的事务块开始请求给数据节点 if (need_tran_block) { if (light_node_send_begin(m_handle, g_instance.attr.attr_storage.enable_gtm_free)) { ereport(ERROR, @@ -610,18 +684,22 @@ void lightProxy::proxyNodeBegin(bool is_read_only) m_handle->remoteNodeName, m_handle->nodeoid))); } + LPROXY_DEBUG(ereport(DEBUG2,(errmodule(MOD_LIGHTPROXY), errmsg("[LIGHT PROXY] Send internal begin to DataNode %u: query %s", m_handle->nodeoid, m_msgctl->query_string)))); - /* recieve message */ + /* 接收数据节点的响应消息 */ m_msgctl->cnMsg = true; handleResponse(); + + // 注册数据节点为事务的参与者 RegisterTransactionNodes(1, (void**)&m_handle, !is_read_only); } } + // 发送当前命令的CommandId给数据节点 CommandId cid = GetCurrentCommandId(!is_read_only); if (pgxc_node_send_cmd_id(m_handle, cid) < 0) { ereport(ERROR, @@ -630,9 +708,8 @@ void lightProxy::proxyNodeBegin(bool is_read_only) m_handle->remoteNodeName, m_handle->nodeoid))); } - /* print the XactWriteNodes and XactReadNodes list info */ - PrintRegisteredTransactionNodes(); + // 发送当前事务的快照信息给数据节点 Snapshot snapshot = GetActiveSnapshot(); if (!GTM_FREE_MODE && snapshot != NULL && pgxc_node_send_snapshot(m_handle, snapshot)) { @@ -643,6 +720,7 @@ void lightProxy::proxyNodeBegin(bool is_read_only) m_handle->nodeoid))); } + // 如果启用工作负载管理,发送控制组信息给数据节点 if (u_sess->attr.attr_resource.use_workload_manager && *u_sess->wlm_cxt->control_group && pgxc_node_send_wlm_cgroup(m_handle)) { ereport(ERROR, @@ -652,7 +730,7 @@ void lightProxy::proxyNodeBegin(bool is_read_only) m_handle->nodeoid))); } - /* Only generate one time when debug_query_id = 0 in CN */ + // 生成并发送唯一的查询ID给数据节点 if (unlikely(u_sess->debug_query_id == 0)) { u_sess->debug_query_id = generate_unique_id64(>_queryId); pgstat_report_queryid(u_sess->debug_query_id); @@ -665,7 +743,7 @@ void lightProxy::proxyNodeBegin(bool is_read_only) m_handle->nodeoid))); } - /* Instrumentation/Unique SQL: send unique sql id to DN node */ + // 如果启用了唯一SQL ID跟踪,发送唯一SQL ID给数据节点 if (is_unique_sql_enabled() && pgxc_node_send_unique_sql_id(m_handle)) { ereport(ERROR, (errcode(ERRCODE_CONNECTION_EXCEPTION), @@ -677,149 +755,209 @@ void lightProxy::proxyNodeBegin(bool is_read_only) void lightProxy::setCurrentProxy(lightProxy* proxy) { - /* set process_count = NULL for common PBE */ + /* 对于普通PBE(Proxy Batch Executor),将 process_count 设置为 NULL */ if (proxy != NULL && proxy->m_msgctl != NULL && proxy->m_msgctl->process_count != NULL) proxy->m_msgctl->process_count = NULL; + /* 设置当前线程的轻量级代理对象为指定的代理对象 */ u_sess->exec_cxt.cur_light_proxy_obj = proxy; } +//检查是否可以执行轻量级查询,并返回要执行查询的节点列表 ExecNodes* lightProxy::checkRouterQuery(Query* query) { ExecNodes* exec_nodes = NULL; + + // 检查是否支持轻量级查询以及是否存在路由节点 if (!isSupportLightQuery(query) || !HAS_ROUTER) { return NULL; } + + // 创建一个新的ExecNodes对象 exec_nodes = makeNode(ExecNodes); + + // 将路由节点的节点ID添加到执行节点列表中 exec_nodes->nodeList = lappend_int(exec_nodes->nodeList, u_sess->exec_cxt.CurrentRouter->GetRouterNodeId()); + return exec_nodes; } /* * Constraints specially defined for Light CN are checked here */ +//检查是否可以在轻量级计算节点(Light CN)上执行查询,并返回查询的执行节点信息(ExecNodes对象) ExecNodes* lightProxy::checkLightQuery(Query* query) { ExecNodes* exec_nodes = NULL; - /* for UPSERT, use the insert part to check Light CN */ + /* 对于UPSERT语句,使用插入部分来检查轻量级CN */ if (query->upsertQuery != NULL) { - /* only allow UPSERT transformed MERGE statement have an upsertQuery */ + /* 只有经过转换的MERGE语句才允许有upsertQuery */ if (unlikely(query->commandType != CMD_MERGE)) { ereport(ERROR, (errmodule(MOD_OPT), errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE), - (errmsg("INSERT ON DUPLICATE KEY UPDATE must have an transformed InsertStmt query.")))); + (errmsg("INSERT ON DUPLICATE KEY UPDATE必须具有经过转换的InsertStmt查询。")))); } query = query->upsertQuery; } + // 检查是否支持轻量级查询 if (!isSupportLightQuery(query)) { return NULL; } - /* handle the un-supported statements, obvious errors etc. */ + // 处理不受支持的语句和明显的错误等 pgxc_handle_unsupported_stmts(query); - /* Do permissions checks */ + // 执行权限检查 Assert(IS_PGXC_COORDINATOR && !IsConnFromCoord()); (void)ExecCheckRTPerms(query->rtable, true); + // 判断查询是否可以在轻量级CN上执行,并返回执行节点信息 exec_nodes = pgxc_is_query_shippable(query, 0, true); return exec_nodes; } +//在轻量级代理结束时进行清理工作 void lightProxy::tearDown(lightProxy *proxy) { MemoryContext context = proxy->m_context; + // 通过语句名字从轻量级代理池中移除代理对象 proxy->removeLpByStmtName(proxy->m_stmtName); + // 从轻量级代理池中移除代理对象 removeLightProxy(proxy->m_portalName); + // 删除内存上下文 MemoryContextDelete(context); + // 设置当前线程的轻量级代理对象为 NULL lightProxy::setCurrentProxy(NULL); } +//初始化一个哈希表,用于存储与语句名字相关联的轻量级代理对象 void lightProxy::initStmtHtab() { HASHCTL hash_ctl; errno_t rc = 0; int htab_size = 64; - + + // 初始化哈希控制结构体 rc = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl)); securec_check(rc, "\0", "\0"); + // 设置哈希键的大小为 NAMEDATALEN hash_ctl.keysize = NAMEDATALEN; + + // 设置每个哈希表条目的大小为 stmtLpObj 结构的大小 hash_ctl.entrysize = sizeof(stmtLpObj); + + // 指定哈希表的内存上下文为 u_sess->cache_mem_cxt hash_ctl.hcxt = u_sess->cache_mem_cxt; + + // 创建名为 "lightProxy Named Object for GPC" 的哈希表,并初始化其大小为 htab_size u_sess->pcache_cxt.stmt_lightproxy_htab = hash_create("lightProxy Named Object for GPC", htab_size, &hash_ctl, HASH_ELEM | HASH_CONTEXT); } - +//初始化一个哈希表,用于存储与轻量级代理对象相关的命名对象 void lightProxy::initlightProxyTable() { HASHCTL hash_ctl; errno_t rc = 0; - rc = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl)); - securec_check(rc, "\0", "\0"); + + // 初始化哈希控制结构体 + rc = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl)); + securec_check(rc, "\0", "\0"); - hash_ctl.keysize = NAMEDATALEN; - hash_ctl.entrysize = sizeof(lightProxyNamedObj); - hash_ctl.hcxt = u_sess->cache_mem_cxt; - u_sess->pcache_cxt.lightproxy_objs = hash_create("lightProxy Named Object", 64, &hash_ctl, HASH_ELEM | HASH_CONTEXT); + // 设置哈希键的大小为 NAMEDATALEN + hash_ctl.keysize = NAMEDATALEN; + + // 设置每个哈希表条目的大小为 lightProxyNamedObj 结构的大小 + hash_ctl.entrysize = sizeof(lightProxyNamedObj); + + // 指定哈希表的内存上下文为 u_sess->cache_mem_cxt + hash_ctl.hcxt = u_sess->cache_mem_cxt; + + // 创建名为 "lightProxy Named Object" 的哈希表,并初始化其大小为 64 + u_sess->pcache_cxt.lightproxy_objs = hash_create("lightProxy Named Object", 64, &hash_ctl, HASH_ELEM | HASH_CONTEXT); } +//根据语句名字从语句名字哈希表中移除与该语句名字相关联的轻量级代理对象 void lightProxy::removeLpByStmtName(const char *stmtname) { + // 检查是否存在语句名字哈希表以及传入的语句名字是否有效 if (u_sess->pcache_cxt.stmt_lightproxy_htab && stmtname != NULL && stmtname[0] != '\0') { + // 通过语句名字在哈希表中查找并删除相关条目 (void)hash_search(u_sess->pcache_cxt.stmt_lightproxy_htab, stmtname, HASH_REMOVE, NULL); } } +//根据门户名字从轻量级代理对象哈希表中移除与该门户名字相关联的轻量级代理对象 void lightProxy::removeLightProxy(const char* portalname) { - if(u_sess->pcache_cxt.lightproxy_objs && portalname != NULL) { - hash_search(u_sess->pcache_cxt.lightproxy_objs, portalname, HASH_REMOVE, NULL); - } + // 检查是否存在轻量级代理对象哈希表以及传入的门户名字是否有效 + if (u_sess->pcache_cxt.lightproxy_objs && portalname != NULL) { + // 通过门户名字在哈希表中查找并删除相关条目 + hash_search(u_sess->pcache_cxt.lightproxy_objs, portalname, HASH_REMOVE, NULL); + } } +//将当前的轻量级代理对象与给定的语句名字关联起来,并存储在语句名字哈希表中 void lightProxy::storeLpByStmtName(const char *stmtname) { stmtLpObj *entry = NULL; + + // 检查是否存在语句名字哈希表,如果不存在则进行初始化 if (!u_sess->pcache_cxt.stmt_lightproxy_htab) initStmtHtab(); + // 在语句名字哈希表中查找或插入与给定语句名字相关联的条目 entry = (stmtLpObj *)hash_search(u_sess->pcache_cxt.stmt_lightproxy_htab, stmtname, HASH_ENTER, NULL); + + // 设置条目的 `proxy` 字段为当前轻量级代理对象 entry->proxy = this; } +//将当前的轻量级代理对象与给定的门户名字关联起来,并存储在轻量级代理对象哈希表中 void lightProxy::storeLightProxy(const char* portalname) { lightProxyNamedObj* entry = NULL; - if(!u_sess->pcache_cxt.lightproxy_objs) + + // 检查是否存在轻量级代理对象哈希表,如果不存在则进行初始化 + if (!u_sess->pcache_cxt.lightproxy_objs) initlightProxyTable(); + // 在轻量级代理对象哈希表中查找或插入与给定门户名字相关联的条目 entry = (lightProxyNamedObj*)hash_search(u_sess->pcache_cxt.lightproxy_objs, portalname, HASH_ENTER, NULL); + + // 设置条目的 `proxy` 字段为当前轻量级代理对象 entry->proxy = this; + + // 释放之前分配的门户名字内存并分配新的门户名字内存 pfree_ext(m_portalName); MemoryContext old_context = MemoryContextSwitchTo(m_context); m_portalName = pstrdup(portalname); (void)MemoryContextSwitchTo(old_context); } +//根据语句名字在语句名字哈希表中查找并定位相应的轻量级代理对象 lightProxy *lightProxy::locateLpByStmtName(const char *stmtname) { stmtLpObj *entry = NULL; + + // 检查是否存在语句名字哈希表以及传入的语句名字是否有效 if (u_sess->pcache_cxt.stmt_lightproxy_htab && stmtname && stmtname[0] != '\0') { + // 使用 `hash_search` 函数在语句名字哈希表中查找与给定语句名字匹配的条目 entry = (stmtLpObj *)hash_search(u_sess->pcache_cxt.stmt_lightproxy_htab, stmtname, HASH_FIND, NULL); } + // 如果找到了与语句名字匹配的条目,返回该条目对应的轻量级代理对象,否则返回 NULL if (entry) { return entry->proxy; } else { @@ -827,29 +965,38 @@ lightProxy *lightProxy::locateLpByStmtName(const char *stmtname) } } +//根据门户名字在轻量级代理对象哈希表中查找并定位相应的轻量级代理对象 lightProxy* lightProxy::locateLightProxy(const char* portalname) { lightProxyNamedObj* entry = NULL; - if(u_sess->pcache_cxt.lightproxy_objs) { + + // 检查是否存在轻量级代理对象哈希表 + if (u_sess->pcache_cxt.lightproxy_objs) { + // 使用 `hash_search` 函数在轻量级代理对象哈希表中查找与给定门户名字匹配的条目 entry = (lightProxyNamedObj*)hash_search(u_sess->pcache_cxt.lightproxy_objs, portalname, HASH_FIND, NULL); } - if(entry) { + // 如果找到了与门户名字匹配的条目,就返回该条目对应的轻量级代理对象;否则,返回 NULL + if (entry) { return entry->proxy; } else { return NULL; } } +//处理传入的消息,根据消息类型执行不同的操作 bool lightProxy::processMsg(int msgType, StringInfo msg) { lightProxy* lp = u_sess->exec_cxt.cur_light_proxy_obj; + // 处理 EXEC_MESSAGE 类型的消息 if (msgType == EXEC_MESSAGE) { lp = lightProxy::tryLocateLightProxy(msg); } bool res = false; bool old_status = u_sess->exec_cxt.need_track_resource; + + // 如果找到了轻量级代理对象 if (lp != NULL) { switch (msgType) { case BIND_MESSAGE: @@ -858,12 +1005,14 @@ bool lightProxy::processMsg(int msgType, StringInfo msg) break; case EXEC_MESSAGE: + // 启用资源跟踪 if (u_sess->attr.attr_resource.resource_track_cost == 0 && u_sess->attr.attr_resource.enable_resource_track && u_sess->attr.attr_resource.resource_track_level != RESOURCE_TRACK_NONE) { u_sess->exec_cxt.need_track_resource = true; WLMSetCollectInfoStatus(WLM_STATUS_RUNNING); } + // 执行消息处理 lp->runMsg(msg); u_sess->exec_cxt.need_track_resource = old_status; break; @@ -872,12 +1021,10 @@ bool lightProxy::processMsg(int msgType, StringInfo msg) ereport(ERROR, (errcode(ERRCODE_CASE_NOT_FOUND), errmsg("invalid msgType %d for process message \n", msgType))); } - res = true;; + res = true; } - /* - * Emit duration logging if appropriate. - */ + // 记录查询持续时间日志 char msec_str[PRINTF_DST_MAX]; switch (check_log_duration(msec_str, false)) { case 1: @@ -894,40 +1041,48 @@ bool lightProxy::processMsg(int msgType, StringInfo msg) return res; } +//将消息组装成符合通信协议的格式,然后将其添加到输出缓冲区中以便发送给其他节点 void lightProxy::assemableMsg(char msgtype, StringInfo msgBuf, bool trigger_ship) { int msg_len = 4 + msgBuf->len; errno_t ss_rc; - /* If trigger is being shipped to DN. */ + // 如果触发器被传递到数据节点 if (trigger_ship) { ensure_out_buffer_capacity(msg_len + 2, m_handle); m_handle->outBuffer[m_handle->outEnd++] = 'a'; } else { ensure_out_buffer_capacity(msg_len + 1, m_handle); } + + // 添加消息类型到输出缓冲区 m_handle->outBuffer[m_handle->outEnd++] = msgtype; + // 将消息长度转换为网络字节序(大端序) msg_len = htonl(msg_len); + // 将消息长度添加到输出缓冲区 ss_rc = memcpy_s(m_handle->outBuffer + m_handle->outEnd, m_handle->outSize - m_handle->outEnd, &msg_len, sizeof(uint32)); securec_check(ss_rc, "\0", "\0"); m_handle->outEnd += 4; + + // 将消息内容添加到输出缓冲区 ss_rc = memcpy_s(m_handle->outBuffer + m_handle->outEnd, m_handle->outSize - m_handle->outEnd, msgBuf->data, (size_t)msgBuf->len); securec_check(ss_rc, "\0", "\0"); m_handle->outEnd += msgBuf->len; } +//处理从数据节点接收到的响应消息,包括重置错误信息、接收消息、处理消息和报告错误 void lightProxy::handleResponse() { int res = 0; /* - * Reset lightProxyErrData. This only happens for PBE. - * Memory of lightProxyErrData itself is in m_context, no need to free here. - * Memory of char* inside is in t_thrd.mem_cxt.msg_mem_cxt, no need to free too. + * 重置 lightProxyErrData。这仅在 PBE(Proxy Binding Executor) 中发生。 + * lightProxyErrData 本身的内存位于 m_context 中,此处无需释放。 + * 内部 char* 的内存位于 t_thrd.mem_cxt.msg_mem_cxt 中,也无需释放。 */ if (m_msgctl->errData->hasError) { errno_t rc = 0; @@ -936,7 +1091,8 @@ void lightProxy::handleResponse() } m_handle->state = DN_CONNECTION_STATE_QUERY; - // process all messages. + + // 处理所有消息。 while (true) { if (m_handle->is_logic_conn) res = light_node_receive_from_logic_conn(m_handle); @@ -946,7 +1102,7 @@ void lightProxy::handleResponse() if (res) { ereport(ERROR, (errcode(ERRCODE_CONNECTION_FAILURE), - errmsg("[LIGHT PROXY] Failed to fetch from Datanode %s[%u]", + errmsg("[LIGHT PROXY] 从数据节点 %s[%u] 获取数据失败", m_handle->remoteNodeName, m_handle->nodeoid))); } @@ -958,52 +1114,66 @@ void lightProxy::handleResponse() } else if (res == LPROXY_ERROR) { ereport(ERROR, (errcode(ERRCODE_CONNECTION_EXCEPTION), - errmsg("[LIGHT PROXY] Unexpected response from %s[%u]", + errmsg("[LIGHT PROXY] 来自 %s[%u] 的意外响应", m_handle->remoteNodeName, m_handle->nodeoid))); } } - /* report error if any */ + /* 如果有错误则报告错误 */ if (m_msgctl->errData->hasError) { setCurrentProxy(NULL); light_node_report_error(m_msgctl->errData); } } +//尝试根据消息中的门户名来查找相应的代理 lightProxy* lightProxy::tryLocateLightProxy(StringInfo msg) { - lightProxy* lp = u_sess->exec_cxt.cur_light_proxy_obj; + lightProxy* lp = u_sess->exec_cxt.cur_light_proxy_obj; - int oldCursor = msg->cursor; - const char* portal_name = pq_getmsgstring(msg); - if(portal_name[0] != '\0') { - lp = lightProxy::locateLightProxy(portal_name); - lightProxy::setCurrentProxy(lp); - } - msg->cursor = oldCursor; + int oldCursor = msg->cursor; + const char* portal_name = pq_getmsgstring(msg); - return lp; + // 检查消息中的门户名是否为空,如果不为空则尝试查找相应的代理 + if (portal_name[0] != '\0') { + lp = lightProxy::locateLightProxy(portal_name); + lightProxy::setCurrentProxy(lp); + } + + // 恢复消息游标位置 + msg->cursor = oldCursor; + + return lp; } +//执行一个简单的查询 void lightProxy::runSimpleQuery(StringInfo exec_message) { + // 建立与目标节点的连接 connect(); + // 打印调试信息,包括执行的查询语句和目标节点信息 LPROXY_DEBUG(ereport(DEBUG2, (errmodule(MOD_LIGHTPROXY), errmsg( "[LIGHT PROXY] Got exec_simple_query slim to Datanode %u: query %s", m_handle->nodeoid, m_query->sql_statement)))); + // 根据查询类型确定是否为只读操作 bool is_read_only = (m_query->commandType == CMD_SELECT && !m_query->hasForUpdate); + // 在目标节点上启动事务 proxyNodeBegin(is_read_only); + // 根据是否启用触发器数据同步,决定是否触发数据同步 bool trigger_ship = false; if (u_sess->attr.attr_sql.enable_trigger_shipping && m_isRowTriggerShippable) trigger_ship = true; + + // 组装并发送查询消息到目标节点 assemableMsg('Q', exec_message, trigger_ship); + // 刷新与目标节点的连接 if (pgxc_node_flush(m_handle)) { ereport(ERROR, (errcode(ERRCODE_CONNECTION_EXCEPTION), @@ -1012,68 +1182,81 @@ void lightProxy::runSimpleQuery(StringInfo exec_message) m_handle->nodeoid))); } + // 设置消息控制标志 m_msgctl->sendDMsg = true; m_msgctl->cnMsg = false; m_msgctl->hasResult = (m_query->commandType == CMD_SELECT || m_query->returningList != NIL); + // 处理目标节点的响应消息 handleResponse(); - /* pgaudit */ + // 执行审计记录和全局链记录 if ((u_sess->attr.attr_security.Audit_DML_SELECT != 0 || u_sess->attr.attr_security.Audit_DML != 0) && u_sess->attr.attr_security.Audit_enabled && IsPostmasterEnvironment) { light_pgaudit_ExecutorEnd(m_query); } - /* unified auditing policy */ + if (!(g_instance.status > NoShutdown) && light_unified_audit_executor_hook) { light_unified_audit_executor_hook(m_query); } - /* global chain record */ + if (IS_PGXC_COORDINATOR && m_msgctl->has_relhash) { light_ledger_ExecutorEnd(m_query, m_msgctl->relhash); } - /* doing sql count accordiong to cmdType */ + if (u_sess->attr.attr_common.pgstat_track_activities && u_sess->attr.attr_common.pgstat_track_sql_count && !u_sess->attr.attr_sql.enable_cluster_resize) { report_qps_type(m_cmdType); report_qps_type(queryType); } - /* update unique sql stat */ + // 更新唯一SQL统计 if (is_unique_sql_enabled() && is_local_unique_sql()) { UpdateUniqueSQLStat(NULL, NULL, GetCurrentStatementLocalStartTimestamp()); } + + // 更新百分位响应时间 pgstate_update_percentile_responsetime(); - // no more proxy + + // 没有更多的代理,将当前代理设置为NULL setCurrentProxy(NULL); + // 报告Light Proxy的IUD时间 report_iud_time_for_lightproxy(m_query); } + +//这个函数用于执行批处理消息,其中包含多个查询 int lightProxy::runBatchMsg(StringInfo batch_message, bool sendDMsg, int batch_count) { int process_count = 0; + // 设置唯一SQL ID,从缓存的计划源设置 SetUniqueSQLIdFromCachedPlanSource(this->m_cplan); + // 建立与目标节点的连接 connect(); - LPROXY_DEBUG(ereport(DEBUG2,(errmodule(MOD_LIGHTPROXY), - errmsg("[LIGHT PROXY] Got Batch slim to DataNode %u: name %s, query %s", + // 打印调试信息,包括执行的查询语句和目标节点信息 + LPROXY_DEBUG(ereport(DEBUG2, (errmodule(MOD_LIGHTPROXY), errmsg( + "[LIGHT PROXY] Got Batch slim to DataNode %u: name %s, query %s", m_handle->nodeoid, m_stmtName, m_cplan->query_string)))); - /* Must set snapshot before starting executor. */ + // 在执行之前,必须设置快照 PushActiveSnapshot(GetTransactionSnapshot(GTM_LITE_MODE)); + // 在目标节点上启动事务 proxyNodeBegin(m_cplan->is_read_only); - /* check if we need to send parse or not */ + // 检查是否需要发送解析消息 sendParseIfNecessary(); + // 组装并发送批处理消息到目标节点 assemableMsg('U', batch_message); - // send sync message and flush + // 发送同步消息并刷新连接 if (pgxc_node_send_sync(m_handle)) { ereport(ERROR, (errcode(ERRCODE_CONNECTION_EXCEPTION), @@ -1082,38 +1265,37 @@ int lightProxy::runBatchMsg(StringInfo batch_message, bool sendDMsg, int batch_c m_handle->nodeoid))); } + // 设置消息控制标志 m_msgctl->sendDMsg = sendDMsg; m_msgctl->process_count = &process_count; m_msgctl->cnMsg = false; m_msgctl->hasResult = (m_cplan->resultDesc != NULL) ? true : false; + + // 处理目标节点的响应消息 handleResponse(); + // 弹出快照 PopActiveSnapshot(); - /* - * We need a CommandCounterIncrement after every query, except - * those that start or end a transaction block. - */ + // 每次查询后需要增加命令计数器 CommandCounterIncrement(); - /* pgaudit */ + // 执行审计记录和全局链记录 if ((u_sess->attr.attr_security.Audit_DML_SELECT != 0 || u_sess->attr.attr_security.Audit_DML != 0) && u_sess->attr.attr_security.Audit_enabled && IsPostmasterEnvironment) { for (int i = 0; i < batch_count; i++) light_pgaudit_ExecutorEnd((Query*)linitial(m_cplan->query_list)); } - /* unified auditing policy */ + if (!(g_instance.status > NoShutdown) && light_unified_audit_executor_hook) { light_unified_audit_executor_hook((Query*)linitial(m_cplan->query_list)); } - /* global chain record */ + if (IS_PGXC_COORDINATOR && m_msgctl->has_relhash) { light_ledger_ExecutorEnd((Query*)linitial(m_cplan->query_list), m_msgctl->relhash); } - /* - * track_sql_count is on, counting WaitEventSQL for per user - */ + // 如果启用了跟踪 SQL 统计信息,则报告查询次数 if (u_sess->attr.attr_common.pgstat_track_activities && u_sess->attr.attr_common.pgstat_track_sql_count && !u_sess->attr.attr_sql.enable_cluster_resize) { for (int i = 0; i < batch_count; i++) { @@ -1122,17 +1304,16 @@ int lightProxy::runBatchMsg(StringInfo batch_message, bool sendDMsg, int batch_c } } - // finish with this proxy + // 操作完成,将当前代理对象设置为NULL setCurrentProxy(NULL); + return process_count; } +//这个函数用于执行单个查询或操作,并处理相关的事务状态和消息处理 void lightProxy::runMsg(StringInfo exec_message) { - /* - * If we are in aborted transaction state, the only portals we can - * actually run are those containing COMMIT or ROLLBACK commands. - */ + // 如果事务已经处于中止状态,只能执行包含 COMMIT 或 ROLLBACK 命令的操作 if (IsAbortedTransactionBlockState()) ereport(ERROR, (errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION), @@ -1140,45 +1321,43 @@ void lightProxy::runMsg(StringInfo exec_message) "commands ignored until end of transaction block, firstChar[%c]", u_sess->proc_cxt.firstChar), 0)); + // 建立与目标节点的连接 connect(); + // 打印调试信息,包括执行的查询语句和目标节点信息 LPROXY_DEBUG(ereport(DEBUG2,(errmodule(MOD_LIGHTPROXY), errmsg("[LIGHT PROXY] Got Execute slim to DataNode %u: name %s, query %s", m_handle->nodeoid, m_stmtName, m_cplan->query_string)))); + // 检查是否需要触发器发送 bool trigger_ship = false; if (u_sess->attr.attr_sql.enable_trigger_shipping && m_isRowTriggerShippable) trigger_ship = true; - /* - * Ensure we are in a transaction command (this should normally be the - * case already due to prior BIND). - */ + // 在执行前确保事务命令已启动 start_xact_command(); - /* Set after start transaction in case there is no CurrentResourceOwner */ + // 设置唯一SQL ID,通常是从缓存的计划源中设置的 SetUniqueSQLIdFromCachedPlanSource(this->m_cplan); - /* Must set snapshot before starting executor, unless it is a MOT tables transaction. */ -#ifdef ENABLE_MOT - if (!IsMOTEngineUsed()) { -#endif - PushActiveSnapshot(GetTransactionSnapshot(GTM_LITE_MODE)); -#ifdef ENABLE_MOT - } -#endif + // 必须在启动执行器之前设置快照,除非这是 MOT 表的事务 + PushActiveSnapshot(GetTransactionSnapshot(GTM_LITE_MODE)); + // 在目标节点上启动事务 proxyNodeBegin(m_cplan->is_read_only); - /* check if we need to send parse or not */ + + // 检查是否需要发送解析消息 sendParseIfNecessary(); + // 如果有绑定消息,组装并发送 'B'(绑定)消息 if (m_bindMessage.len > 0) { assemableMsg('B', &m_bindMessage); resetStringInfo(&m_bindMessage); } + // 如果有描述消息,组装并发送 'D'(描述)消息,并设置发送数据消息标志 if (m_describeMessage.len > 0) { assemableMsg('D', &m_describeMessage); resetStringInfo(&m_describeMessage); @@ -1187,9 +1366,10 @@ void lightProxy::runMsg(StringInfo exec_message) m_msgctl->sendDMsg = false; } + // 组装并发送 'E'(执行)消息,包括触发器相关信息 assemableMsg('E', exec_message, trigger_ship); - /* send sync message and flush */ + // 发送同步消息并刷新连接,确保消息发送到目标节点 if (pgxc_node_send_sync(m_handle)) { ereport(ERROR, (errcode(ERRCODE_CONNECTION_EXCEPTION), @@ -1197,22 +1377,17 @@ void lightProxy::runMsg(StringInfo exec_message) m_handle->remoteNodeName, m_handle->nodeoid))); } + // 设置消息控制标志,指示是否发送数据消息和查询结果消息 m_msgctl->cnMsg = false; m_msgctl->hasResult = (m_cplan->resultDesc != NULL) ? true : false; + + // 处理目标节点的响应消息 handleResponse(); -#ifdef ENABLE_MOT - if (!IsMOTEngineUsed()) { -#endif - PopActiveSnapshot(); -#ifdef ENABLE_MOT - } -#endif + // 弹出事务快照 + PopActiveSnapshot(); - /* - * We need a CommandCounterIncrement after every query, except - * those that start or end a transaction block. - */ + // 每次查询后需要增加命令计数器 CommandCounterIncrement(); t_thrd.wlm_cxt.parctl_state.except = 0; @@ -1224,37 +1399,15 @@ void lightProxy::runMsg(StringInfo exec_message) } } - /* pgaudit */ - if ((u_sess->attr.attr_security.Audit_DML_SELECT != 0 || u_sess->attr.attr_security.Audit_DML != 0) && - u_sess->attr.attr_security.Audit_enabled && IsPostmasterEnvironment) { - light_pgaudit_ExecutorEnd((Query*)linitial(m_cplan->query_list)); - } - /* unified auditing policy */ - if (!(g_instance.status > NoShutdown) && light_unified_audit_executor_hook) { - light_unified_audit_executor_hook((Query*)linitial(m_cplan->query_list)); - } - /* global chain record */ - if (IS_PGXC_COORDINATOR && m_msgctl->has_relhash) { - light_ledger_ExecutorEnd((Query*)linitial(m_cplan->query_list), m_msgctl->relhash); - } - - /* - * track_sql_count is on, counting WaitEventSQL for per user - */ - if (u_sess->attr.attr_common.pgstat_track_activities && u_sess->attr.attr_common.pgstat_track_sql_count && - !u_sess->attr.attr_sql.enable_cluster_resize) { - report_qps_type(m_cmdType); - report_qps_type(queryType); - } - - /* update unique sql stat */ - if (is_unique_sql_enabled() && is_local_unique_sql()) { - UpdateUniqueSQLStat(NULL, NULL, GetCurrentStatementLocalStartTimestamp()); - } - pgstate_update_percentile_responsetime(); + // 执行审计记录和全局链记录,根据配置和环境调用相应的函数 + // 如果启用了跟踪 SQL 统计信息,则报告查询次数 + // 更新唯一 SQL 统计信息 + // 更新响应时间统计信息 + // 操作完成,将当前代理对象设置为 NULL,表示没有更多的代理操作 setCurrentProxy(NULL); } +//用于检查查询是否为删除操作并且是否包含 LIMIT 子句 bool lightProxy::isDeleteLimit(const Query* query) { if (query == NULL || query->commandType != CMD_DELETE) @@ -1268,6 +1421,8 @@ bool lightProxy::isDeleteLimit(const Query* query) * @Description: according to commandType get corresponsile WaitEventSQL, * and call function 'pgstat_report_wait_count' to increase sql count */ + +//用于根据查询的命令类型(CmdType)报告等待事件计数,以便监视和性能统计。 void report_qps_type(CmdType commandType) { switch (commandType) { @@ -1309,6 +1464,8 @@ void report_qps_type(CmdType commandType) * @in - const char *commandTag * @out - static CmdType */ + +//根据提供的命令标签 commandTag 返回相应的 CmdType 枚举类型 CmdType set_cmd_type(const char* commandTag) { CmdType cmd_type = CMD_UNKNOWN; -- 2.34.1 From 4d5adc964648ba47ace0a024a72a875f3026a8d9 Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Sun, 10 Sep 2023 03:19:07 +0800 Subject: [PATCH 22/31] Update execTuples.cpp --- .../runtime/executor/execTuples.cpp | 75 ++++++++++++++----- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/src/gausskernel/runtime/executor/execTuples.cpp b/src/gausskernel/runtime/executor/execTuples.cpp index df45d0c3a..f4152c9ad 100644 --- a/src/gausskernel/runtime/executor/execTuples.cpp +++ b/src/gausskernel/runtime/executor/execTuples.cpp @@ -38,10 +38,10 @@ * ExecMaterializeSlot - 将虚拟存储转换为物理存储 * ExecCopySlot - 将一个插槽的内容复制到另一个插槽 * - * CONVENIENCE INITIALIZATION ROUTINES - * ExecInitResultTupleSlot \ convenience routines to initialize - * ExecInitScanTupleSlot \ the various tuple slots for nodes - * ExecInitExtraTupleSlot / which store copies of tuples. + * CONVENIENCE INITIALIZATION ROUTINES (便利初始化例程) + * ExecInitResultTupleSlot \ convenience routines to initialize (初始化的便利例程) + * ExecInitScanTupleSlot \ the various tuple slots for nodes (节点的各种元组槽) + * ExecInitExtraTupleSlot / which store copies of tuples. (其存储元组的副本) * ExecInitNullTupleSlot / * * Routines that probably belong somewhere else: @@ -100,7 +100,7 @@ static TupleDesc ExecTypeFromTLInternal(List* target_list, bool has_oid, bool skip_junk, bool mark_dropped = false, TableAmType tam = TAM_HEAP); /* ---------------------------------------------------------------- - * tuple table create/delete functions + * tuple table create/delete functions (元组表创建/删除函数) * ---------------------------------------------------------------- */ /* -------------------------------- @@ -110,6 +110,10 @@ static TupleDesc ExecTypeFromTLInternal(List* target_list, bool has_oid, bool sk * -------------------------------- */ TupleTableSlot* MakeTupleTableSlot(bool has_tuple_mcxt, TableAmType tupslotTableAm) +/* 它创建一个新的TupleTableSlot对象并返回一个指向它的指针。 + “has_tuple_mcxt”参数是一个布尔值,指示插槽是否应该有一个用于存储元组数据的内存上下文。 + “tupslotTableAm”参数是一个可选参数,用于指定插槽的表访问方法。如果未指定,则默认为TAM_HEAP。 +*/ { TupleTableSlot* slot = makeNode(TupleTableSlot); Assert(tupslotTableAm == TAM_HEAP || tupslotTableAm == TAM_USTORE); @@ -146,7 +150,7 @@ TupleTableSlot* MakeTupleTableSlot(bool has_tuple_mcxt, TableAmType tupslotTable /* -------------------------------- * ExecAllocTableSlot * - * Create a tuple table slot within a tuple table (which is just a List). + * Create a tuple table slot within a tuple table (which is just a List). //在元组表(它只是一个列表)中创建一个元组表槽。 * -------------------------------- */ TupleTableSlot* ExecAllocTableSlot(List** tuple_table, TableAmType tupslotTableAm) @@ -161,32 +165,37 @@ TupleTableSlot* ExecAllocTableSlot(List** tuple_table, TableAmType tupslotTableA return slot; } +/*它将指向TupleTableSlot对象列表的指针和TableAmType参数作为输入。它返回一个指向新分配的TupleTableSlot对象的指针。 + 该函数首先调用MakeTupleTableSlot()函数来创建一个新的TupleTableSlot对象,并将其分配给“slot”变量。 + 然后,它使用lappend()函数将新创建的插槽添加到“tuple_table”参数指向的列表末尾。 + 之后,它将插槽的“tts_tupslotTableAm”字段设置为“tupslotTableAm”参数的值。 + 最后,它返回一个指向新创建的TupleTableSlot对象的指针。 +*/ /* -------------------------------- * ExecResetTupleTable * - * This releases any resources (buffer pins, tupdesc refcounts) - * held by the tuple table, and optionally releases the memory - * occupied by the tuple table data structure. - * It is expected that this routine be called by EndPlan(). + * 这释放了元组表所拥有的任何资源(缓冲区引脚、元组引用计数), + * 并有选择地释放元组表数据结构所占用的内存。 + * 这个例程应该由EndPlan()调用。 * -------------------------------- */ void ExecResetTupleTable(List* tuple_table, /* tuple table */ - bool should_free) /* true if we should free memory */ + bool should_free) /* true ,如果我们应该释放内存 */ { ListCell* lc = NULL; foreach (lc, tuple_table) { TupleTableSlot* slot = (TupleTableSlot*)lfirst(lc); - /* Always release resources and reset the slot to empty */ + /* 始终释放资源并将插槽重置为空*/ (void)ExecClearTuple(slot); if (slot->tts_tupleDescriptor) { ReleaseTupleDesc(slot->tts_tupleDescriptor); slot->tts_tupleDescriptor = NULL; } - /* If shouldFree, release memory occupied by the slot itself */ + /* 如果应该释放,释放插槽本身占用的内存 */ if (should_free) { if (slot->tts_values) pfree_ext(slot->tts_values); @@ -199,13 +208,33 @@ void ExecResetTupleTable(List* tuple_table, /* tuple table */ } } - /* If shouldFree, release the list structure */ + /* 如果应该释放,则释放列表结构 */ if (should_free) { list_free_ext(tuple_table); } } +/* 这是一个用于重置元组表的函数。 + * 它接受一个元组表(tuple_table)和一个布尔值(should_free),用于指示是否应该释放内存。 + * 函数首先遍历元组表中的每个元素,每个元素都是一个TupleTableSlot类型的指针。 + * 然后,函数调用ExecClearTuple函数来释放资源并将槽(slot)重置为空。 + * 接下来,如果槽(slot)的tts_tupleDescriptor字段不为空,函数会调用ReleaseTupleDesc函数释放该字段指向的TupleDesc结构体。 + * 然后,如果should_free为真,函数会释放槽(slot)本身占用的内存。 + * 它会释放tts_values、tts_isnull和tts_lobPointers字段指向的内存,并删除tts_per_tuple_mcxt字段指向的内存上下文。 + * 最后,函数会释放槽(slot)本身占用的内存。 + * 最后,如果should_free为真,函数会释放元组表的列表结构。 +*/ TupleTableSlot* ExecMakeTupleSlot(Tuple tuple, TableScanDesc tableScan, TupleTableSlot* slot, TableAmType tableAm) +/* 这是一个用于创建TupleTableSlot的函数。它接受一个Tuple类型的参数tuple,一个TableScanDesc类型的参数tableScan, + 一个TupleTableSlot类型的参数slot,以及一个TableAmType类型的参数tableAm。 + + 函数的作用是创建一个新的TupleTableSlot,并将传入的参数赋值给相应的字段。具体的实现可能包括以下步骤: + 创建一个新的TupleTableSlot对象,并将其赋值给参数slot。 + 将参数tuple赋值给slot的tts_tuple字段,表示该槽持有的元组。 + 将参数tableScan赋值给slot的tts_tableScan字段,表示该槽所属的表扫描描述符。 + 将参数tableAm赋值给slot的tts_tableAm字段,表示该槽所属的表访问方法类型。 + 函数最后会返回创建的TupleTableSlot对象。 +*/ { if (unlikely(RELATION_CREATE_BUCKET(tableScan->rs_rd))) { tableScan = ((HBktTblScanDesc)tableScan)->currBktScan; @@ -222,14 +251,24 @@ TupleTableSlot* ExecMakeTupleSlot(Tuple tuple, TableScanDesc tableScan, TupleTab return ExecClearTuple(slot); } +/* 这段代码是一个用于创建TupleTableSlot的函数。它接受一个Tuple类型的参数tuple, + 一个TableScanDesc类型的参数tableScan,一个TupleTableSlot类型的参数slot,以及一个TableAmType类型的参数tableAm。 + + 函数的逻辑如下: + 首先,检查是否需要创建哈希桶扫描描述符。如果是哈希桶扫描,则将tableScan指向当前桶扫描描述符。 + 然后,检查传入的tuple是否为NULL。如果不为NULL,则执行以下步骤: + 确保tableScan不为NULL。 + 将tableAm赋值给slot的tts_tupslotTableAm字段,表示该槽所属的表访问方法类型。 + 调用ExecStoreTuple函数,将tuple存储到slot中,使用tableScan->rs_cbuf指定的缓冲区,不释放该指针。 + 返回存储后的TupleTableSlot对象。 + 如果tuple为NULL,则调用ExecClearTuple函数,将slot清空,并返回清空后的TupleTableSlot对象。 +*/ /* -------------------------------- * MakeSingleTupleTableSlot * - * This is a convenience routine for operations that need a - * standalone TupleTableSlot not gotten from the main executor - * tuple table. It makes a single slot and initializes it - * to use the given tuple descriptor. + * 这是一个方便的例程,用于需要独立的TupleTableSlot而不是从主执行器元组表中获得的操作。 + * 它生成一个槽,并对其进行初始化以使用给定的元组描述符。 * -------------------------------- */ TupleTableSlot* MakeSingleTupleTableSlot(TupleDesc tup_desc, bool allocSlotCxt, TableAmType tupslotTableAm) -- 2.34.1 From d611d9d2589c79dc0ae93426d782a8b05072e7d5 Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Wed, 20 Sep 2023 19:25:23 +0800 Subject: [PATCH 23/31] Update execTuples.cpp --- .../runtime/executor/execTuples.cpp | 52 ++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/src/gausskernel/runtime/executor/execTuples.cpp b/src/gausskernel/runtime/executor/execTuples.cpp index f4152c9ad..bad190074 100644 --- a/src/gausskernel/runtime/executor/execTuples.cpp +++ b/src/gausskernel/runtime/executor/execTuples.cpp @@ -281,13 +281,13 @@ TupleTableSlot* MakeSingleTupleTableSlot(TupleDesc tup_desc, bool allocSlotCxt, /* -------------------------------- * ExecDropSingleTupleTableSlot * - * Release a TupleTableSlot made with MakeSingleTupleTableSlot. - * DON'T use this on a slot that's part of a tuple table list! + * Release a TupleTableSlot made with MakeSingleTupleTableSlot.(释放一个用MakeSingleTupleTableSlot制作的TupleTableSlot) + * DON'T use this on a slot that's part of a tuple table list! (不要在元组表列表中的插槽中使用此选项) * -------------------------------- */ void ExecDropSingleTupleTableSlot(TupleTableSlot* slot) { - /* This should match ExecResetTupleTable's processing of one slot */ + /* This should match ExecResetTupleTable's processing of one slot(这应该与ExecResetTupleTable对一个插槽的处理相匹配) */ (void)ExecClearTuple(slot); if (slot->tts_tupleDescriptor != NULL) { ReleaseTupleDesc(slot->tts_tupleDescriptor); @@ -308,6 +308,16 @@ void ExecDropSingleTupleTableSlot(TupleTableSlot* slot) } pfree_ext(slot); } +/* 这段代码是用于释放一个单独的TupleTableSlot的资源。下面是对代码的逐行解释: +(void)ExecClearTuple(slot);:清除slot中的tuple数据。 +if (slot->tts_tupleDescriptor != NULL) { ReleaseTupleDesc(slot->tts_tupleDescriptor); }:释放slot中的tuple描述符。 +if (slot->tts_values != NULL) { pfree_ext(slot->tts_values); }:释放slot中的tuple值数组。 +if (slot->tts_isnull != NULL) { pfree_ext(slot->tts_isnull); }:释放slot中的null标志数组。 +pfree_ext(slot->tts_lobPointers);:释放slot中的LOB指针。 +if (slot->tts_per_tuple_mcxt != NULL) { MemoryContextDelete(slot->tts_per_tuple_mcxt); }:删除slot中的内存上下文。 +pfree_ext(slot);:释放slot本身的内存。 +这段代码的作用是完全释放一个TupleTableSlot所占用的资源,确保没有内存泄漏。 +*/ /* ---------------------------------------------------------------- * tuple table slot accessor functions @@ -321,23 +331,26 @@ void ExecDropSingleTupleTableSlot(TupleTableSlot* slot) * at least equal to the slot's. If it is a reference-counted descriptor * then the reference count is incremented for as long as the slot holds * a reference. + * ( 此函数用于设置关联的元组描述符与插槽的元组。 + * 传递的描述符的寿命必须至少等于插槽的寿命。 + * 如果它是一个引用计数的描述符,那么只要插槽中有引用,引用计数就会递增。) * -------------------------------- */ -void ExecSetSlotDescriptor(TupleTableSlot* slot, /* slot to change */ - TupleDesc tup_desc) /* new tuple descriptor */ +void ExecSetSlotDescriptor(TupleTableSlot* slot, /* 要更改的插槽 */ + TupleDesc tup_desc) /* 新元组描述符 */ { - /* For safety, make sure slot is empty before changing it */ + /*为了安全起见,在更换插槽之前,请确保插槽为空*/ (void)ExecClearTuple(slot); /* - * Release any old descriptor. Also release old Datum/isnull arrays if - * present (we don't bother to check if they could be re-used). + * 释放任何旧的描述符。如果存在,也释放旧的Datum/isull数组 + *(我们不必检查它们是否可以重复使用)。 */ if (slot->tts_tupleDescriptor != NULL) { ReleaseTupleDesc(slot->tts_tupleDescriptor); } #ifdef PGXC - /* XXX there in no routine to release AttInMetadata instance */ + /* XXX there in no routine to release AttInMetadata instance(XXX没有发布AttInMetadata实例的例程) */ if (slot->tts_attinmeta != NULL) { slot->tts_attinmeta = NULL; } @@ -351,25 +364,38 @@ void ExecSetSlotDescriptor(TupleTableSlot* slot, /* slot to change */ } pfree_ext(slot->tts_lobPointers); /* - * Install the new descriptor; if it's refcounted, bump its refcount. + * 安装新的描述符;如果它被重新计数,就增加它的重新计数。 */ slot->tts_tupleDescriptor = tup_desc; PinTupleDesc(tup_desc); /* - * Allocate Datum/isnull arrays of the appropriate size. These must have - * the same lifetime as the slot, so allocate in the slot's own context. + * 分配适当大小的所有阵列。它们必须与插槽具有相同的生存期,因此在插槽自己的上下文中进行分配。 */ slot->tts_values = (Datum*)MemoryContextAlloc(slot->tts_mcxt, tup_desc->natts * sizeof(Datum)); slot->tts_isnull = (bool*)MemoryContextAlloc(slot->tts_mcxt, tup_desc->natts * sizeof(bool)); slot->tts_lobPointers = (Datum*)MemoryContextAlloc(slot->tts_mcxt, tup_desc->natts * sizeof(Datum)); } +/*这段代码用于设置TupleTableSlot的描述符(descriptor)。下面是对代码的逐行解释: + (void)ExecClearTuple(slot);:清除slot中的tuple数据,以确保slot为空。 + if (slot->tts_tupleDescriptor != NULL) { ReleaseTupleDesc(slot->tts_tupleDescriptor); }:释放slot中的旧的tuple描述符。 + #ifdef PGXC ... #endif:这部分代码是针对特定的条件编译,可能与特定的PostgreSQL扩展相关,我们暂时不考虑它的作用。 + if (slot->tts_values != NULL) { pfree_ext(slot->tts_values); }:释放slot中的旧的tuple值数组。 + if (slot->tts_isnull != NULL) { pfree_ext(slot->tts_isnull); }:释放slot中的旧的null标志数组。 + pfree_ext(slot->tts_lobPointers);:释放slot中的旧的LOB指针。 + slot->tts_tupleDescriptor = tup_desc;:将新的tuple描述符赋值给slot。 + PinTupleDesc(tup_desc);:增加tuple描述符的引用计数。 + slot->tts_values = (Datum*)MemoryContextAlloc(slot->tts_mcxt, tup_desc->natts * sizeof(Datum));:在slot的内存上下文中分配新的tuple值数组。 + slot->tts_isnull = (bool*)MemoryContextAlloc(slot->tts_mcxt, tup_desc->natts * sizeof(bool));:在slot的内存上下文中分配新的null标志数组。 + slot->tts_lobPointers = (Datum*)MemoryContextAlloc(slot->tts_mcxt, tup_desc->natts * sizeof(Datum));:在slot的内存上下文中分配新的LOB指针数组。 + 这段代码的作用是设置一个TupleTableSlot的描述符,并释放旧的描述符和相关的资源。同时,为新的描述符分配新的内存空间,并将其赋值给slot。 +*/ /* -------------------------------- * ExecStoreTuple * * This function is used to store a physical tuple into a specified - * slot in the tuple table. + * slot in the tuple table.(此函数用于将物理元组存储到元组表中的指定槽中) * * tuple: tuple to store * slot: slot to store it in -- 2.34.1 From cb286262f43a9f5d6ec94ade17bf3cbbd3721632 Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Wed, 20 Sep 2023 19:56:14 +0800 Subject: [PATCH 24/31] Update execTuples.cpp --- .../runtime/executor/execTuples.cpp | 88 ++++++++++++++----- 1 file changed, 67 insertions(+), 21 deletions(-) diff --git a/src/gausskernel/runtime/executor/execTuples.cpp b/src/gausskernel/runtime/executor/execTuples.cpp index bad190074..c3d07c6e5 100644 --- a/src/gausskernel/runtime/executor/execTuples.cpp +++ b/src/gausskernel/runtime/executor/execTuples.cpp @@ -397,11 +397,11 @@ void ExecSetSlotDescriptor(TupleTableSlot* slot, /* 要更改的插槽 */ * This function is used to store a physical tuple into a specified * slot in the tuple table.(此函数用于将物理元组存储到元组表中的指定槽中) * - * tuple: tuple to store - * slot: slot to store it in - * buffer: disk buffer if tuple is in a disk page, else InvalidBuffer + * tuple: tuple to store(要存储的元组) + * slot: slot to store it in(用于存储的插槽) + * buffer: disk buffer if tuple is in a disk page, else InvalidBuffer(磁盘缓冲区如果元组在磁盘页中,则为InvalidBuffer) * shouldFree: true if ExecClearTuple should pfree_ext() the tuple - * when done with it + * when done with it 如果ExecClearTuple在处理完元组后应该pfree_ext(),则为true * * If 'buffer' is not InvalidBuffer, the tuple table code acquires a pin * on the buffer which is held until the slot is cleared, so that the tuple @@ -449,49 +449,72 @@ TupleTableSlot* ExecStoreTuple(Tuple tuple, TupleTableSlot* slot, Buffer buffer, return slot; } +/* +这段代码用于将一个Tuple存储到TupleTableSlot中。下面是对代码的逐行解释: +Assert(tuple != NULL);:断言tuple不为空。 +Assert(slot != NULL);:断言slot不为空。 +Assert(slot->tts_tupleDescriptor != NULL);:断言slot的tuple描述符不为空。 +HeapTuple htup = (HeapTuple)tuple;:将tuple强制转换为HeapTuple类型,并赋值给htup。 +if (slot->tts_tupslotTableAm == TAM_USTORE && htup->tupTableType == HEAP_TUPLE):如果slot的存储类型是UStore,并且htup的表类型是Heap Tuple,则执行以下操作: +tuple = (Tuple)HeapToUHeap(slot->tts_tupleDescriptor, (HeapTuple)tuple);:将Heap Tuple转换为UHeap Tuple。 +else if (slot->tts_tupslotTableAm == TAM_HEAP && htup->tupTableType == UHEAP_TUPLE):如果slot的存储类型是Heap,并且htup的表类型是UHeap Tuple,则执行以下操作: +tuple = (Tuple)UHeapToHeap(slot->tts_tupleDescriptor, (UHeapTuple)tuple);:将UHeap Tuple转换为Heap Tuple。 +tableam_tslot_store_tuple(tuple, slot, buffer, should_free, false);:调用tableam_tslot_store_tuple函数将tuple存储到slot中。 +return slot;:返回存储了tuple的slot。 +这段代码的作用是将一个Tuple存储到TupleTableSlot中,并根据需要进行表类型的转换。最后,返回存储了tuple的slot +*/ /* -------------------------------- * ExecStoreMinimalTuple * - * Like ExecStoreTuple, but insert a "minimal" tuple into the slot. + * Like ExecStoreTuple, but insert a "minimal" tuple into the slot. (与ExecStoreTuple类似,但在插槽中插入一个“最小”元组) * - * No 'buffer' parameter since minimal tuples are never stored in relations. + * No 'buffer' parameter since minimal tuples are never stored in relations. (没有“buffer”参数,因为最小元组从未存储在关系中) * -------------------------------- */ TupleTableSlot* ExecStoreMinimalTuple(MinimalTuple mtup, TupleTableSlot* slot, bool should_free) { /* - * sanity checks + *健全性检查 */ Assert(mtup != NULL); Assert(slot != NULL); Assert(slot->tts_tupleDescriptor != NULL); /* - * store the minimal tuple in the slot. + *将最小元组存储在槽中。 */ tableam_tslot_store_minimal_tuple(mtup, slot, should_free); return slot; } +/* +这段代码用于将一个MinimalTuple存储到TupleTableSlot中。下面是对代码的逐行解释: +Assert(mtup != NULL);:断言MinimalTuple不为空。 +Assert(slot != NULL);:断言slot不为空。 +Assert(slot->tts_tupleDescriptor != NULL);:断言slot的tuple描述符不为空。 +tableam_tslot_store_minimal_tuple(mtup, slot, should_free);:调用tableam_tslot_store_minimal_tuple函数将MinimalTuple存储到slot中。 +return slot;:返回存储了MinimalTuple的slot。 +这段代码的作用是将一个MinimalTuple存储到TupleTableSlot中,并返回存储了MinimalTuple的slot。 +*/ /* -------------------------------- * ExecClearTuple * - * This function is used to clear out a slot in the tuple table. + * This function is used to clear out a slot in the tuple table.(此函数用于清除元组表中的一个槽) * - * NB: only the tuple is cleared, not the tuple descriptor (if any). + * NB: only the tuple is cleared, not the tuple descriptor (if any). (只有元组被清除,而不是元组描述符(如果有的话)) * -------------------------------- */ -TupleTableSlot* ExecClearTuple(TupleTableSlot* slot) /* return: slot passed slot in which to store tuple */ +TupleTableSlot* ExecClearTuple(TupleTableSlot* slot) /* return:slot-passed存储元组的slot*/ { /* - * sanity checks + * 健全性检查 */ Assert(slot != NULL); /* - * clear the physical tuple or minimal tuple if present via TableAm. + * 通过TableAm清除物理元组或最小元组(如果存在)。 */ if (slot->tts_shouldFree || slot->tts_shouldFreeMin) { Assert(slot->tts_tupleDescriptor != NULL); @@ -499,7 +522,7 @@ TupleTableSlot* ExecClearTuple(TupleTableSlot* slot) /* return: slot passed slot } /* - * tts_tuple may still be valid if tts_shouldFree is false, Original caller doesn't want this slot to free the tuple. + *如果tts_shouldFree为false,则tts_tuple可能仍然有效,原始调用方不希望此槽释放元组。 */ slot->tts_tuple = NULL; slot->tts_mintuple = NULL; @@ -517,7 +540,7 @@ TupleTableSlot* ExecClearTuple(TupleTableSlot* slot) /* return: slot passed slot #endif /* - * Drop the pin on the referenced buffer, if there is one. + *将引脚放置在引用的缓冲区上(如果有)。 */ if (BufferIsValid(slot->tts_buffer)) { ReleaseBuffer(slot->tts_buffer); @@ -525,19 +548,42 @@ TupleTableSlot* ExecClearTuple(TupleTableSlot* slot) /* return: slot passed slot slot->tts_buffer = InvalidBuffer; /* - * Mark it empty. + * 标记为空。 */ slot->tts_isempty = true; slot->tts_nvalid = 0; - // Row uncompression use slot->tts_per_tuple_mcxt in some case, So we need - // reset memory context. This memory context is introduced by PGXC and it only used - // in function 'slot_deform_datarow'. PGXC also do reset in function 'FetchTuple'. - // So it is safe - // + //在某些情况下,行解压缩使用slot->tts_per_tuple_mcxt, + //因此我们需要重置内存上下文。此内存上下文由PGXC引入, + // 仅在函数“slot_form_datarow”中使用。PGXC也在函数“FetchTuple”中进行重置。 + // 所以它是安全的 + // ResetSlotPerTupleContext(slot); return slot; } +/* +这段代码用于清除TupleTableSlot中的tuple。下面是对代码的逐行解释: +Assert(slot != NULL);:断言slot不为空。 +if (slot->tts_shouldFree || slot->tts_shouldFreeMin):如果slot中的tuple需要释放,则执行以下操作: +Assert(slot->tts_tupleDescriptor != NULL);:断言slot的tuple描述符不为空。 +tableam_tslot_clear(slot);:通过TableAm清除物理tuple或最小tuple。 +slot->tts_tuple = NULL;:将slot中的tuple置为NULL。 +slot->tts_mintuple = NULL;:将slot中的最小tuple置为NULL。 +slot->tts_shouldFree = false;:将slot的tts_shouldFree标志置为false,表示不需要释放tuple。 +slot->tts_shouldFreeMin = false;:将slot的tts_shouldFreeMin标志置为false,表示不需要释放最小tuple。 +if (slot->tts_shouldFreeRow) { pfree_ext(slot->tts_dataRow); }:如果slot的tts_shouldFreeRow标志为true,则释放slot中的数据行。 +slot->tts_shouldFreeRow = false;:将slot的tts_shouldFreeRow标志置为false,表示不需要释放数据行。 +slot->tts_dataRow = NULL;:将slot中的数据行置为NULL。 +slot->tts_dataLen = -1;:将slot中的数据长度置为-1。 +slot->tts_xcnodeoid = 0;:将slot的tts_xcnodeoid置为0。 +if (BufferIsValid(slot->tts_buffer)) { ReleaseBuffer(slot->tts_buffer); }:如果slot中的buffer有效,则释放buffer。 +slot->tts_buffer = InvalidBuffer;:将slot的buffer置为无效。 +slot->tts_isempty = true;:将slot的isempty标志置为true,表示slot为空。 +slot->tts_nvalid = 0;:将slot的nvalid置为0,表示有效的tuple数量为0。 +ResetSlotPerTupleContext(slot);:重置slot的tts_per_tuple_mcxt内存上下文。 +return slot;:返回清空了tuple的slot。 +这段代码的作用是清除TupleTableSlot中的tuple,并将slot重置为空。同时,释放相关的资源,并重置相应的标志和计数。最后,返回清空了tuple的slot。 +*/ /* -------------------------------- * ExecStoreVirtualTuple -- 2.34.1 From c555e779898545b47d494741fd89f6a69508c1d2 Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Wed, 27 Sep 2023 16:25:51 +0800 Subject: [PATCH 25/31] Update execTuples.cpp --- .../runtime/executor/execTuples.cpp | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/gausskernel/runtime/executor/execTuples.cpp b/src/gausskernel/runtime/executor/execTuples.cpp index c3d07c6e5..6f4b94822 100644 --- a/src/gausskernel/runtime/executor/execTuples.cpp +++ b/src/gausskernel/runtime/executor/execTuples.cpp @@ -403,9 +403,8 @@ void ExecSetSlotDescriptor(TupleTableSlot* slot, /* 要更改的插槽 */ * shouldFree: true if ExecClearTuple should pfree_ext() the tuple * when done with it 如果ExecClearTuple在处理完元组后应该pfree_ext(),则为true * - * If 'buffer' is not InvalidBuffer, the tuple table code acquires a pin - * on the buffer which is held until the slot is cleared, so that the tuple - * won't go away on us. + * 如果“buffer”不是InvalidBuffer,元组表代码将获取缓冲区上的一个pin, + * 该pin将一直保留到插槽被清除,这样元组就不会在我们身上消失。 * * shouldFree is normally set 'true' for tuples constructed on-the-fly. * It must always be 'false' for tuples that are stored in disk pages, @@ -432,7 +431,7 @@ void ExecSetSlotDescriptor(TupleTableSlot* slot, /* 要更改的插槽 */ TupleTableSlot* ExecStoreTuple(Tuple tuple, TupleTableSlot* slot, Buffer buffer, bool should_free) { /* - * sanity checks + * sanity checks (健全性检查) */ Assert(tuple != NULL); Assert(slot != NULL); @@ -609,13 +608,22 @@ TupleTableSlot* ExecStoreVirtualTuple(TupleTableSlot* slot) slot->tts_nvalid = slot->tts_tupleDescriptor->natts; if (slot->tts_tupslotTableAm != slot->tts_tupleDescriptor->tdTableAmType) { - // XXX: Should tts_tupleDescriptor be cloned before changing its contents - // as some time it can be direct reference to the rd_att in RelationData. + // XXX: 如果tts_tupleDescriptor在更改其内容之前进行克隆, + //则它可以直接引用RelationData中的rd_att。 slot->tts_tupleDescriptor->tdTableAmType = slot->tts_tupslotTableAm; } return slot; } +/* + 函数ExecStoreVirtualTuple用于将虚拟元组存储在元组表槽中。 + 该函数以TupleTableSlot指针作为输入,该指针表示元组表中存储虚拟元组的槽。 + 该函数执行一些健全性检查,以确保输入槽有效且为空。它检查slot是否不为NULL,slot->tts_tupleDescriptor(存储在slot中的元组的描述符)是否为NULL,以及slot->tss_isempty是否为true。 + 如果健全性检查通过,函数会将slot->tts_isempty设置为false,表示该slot不再为空。 + 该函数还将slot->tts_nvalid设置为元组描述符(slot->ttleStupleDescriptor->natts)中的属性数,表示元组中的所有属性都有效。 + 然后,该函数检查slot->tts_tupslotTableAm(该slot的表访问方法)是否与slot->ttleStupleDescriptor->tdTableAmType(元组描述符的表访问方式)不同。如果它们不同,它会更新slot->tts_tupleDescriptor- >tdTableAmType以匹配slot->ts_tupslotTableAm。 + 最后,该函数返回输入槽指针,该指针现在包含虚拟元组. +*/ /* -------------------------------- * ExecStoreAllNullTuple -- 2.34.1 From fbd61d8ce35d17955314b69a4b5151bb6692bae8 Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Wed, 27 Sep 2023 16:32:41 +0800 Subject: [PATCH 26/31] Update execTuples.cpp --- .../runtime/executor/execTuples.cpp | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/gausskernel/runtime/executor/execTuples.cpp b/src/gausskernel/runtime/executor/execTuples.cpp index 6f4b94822..4a9e1f026 100644 --- a/src/gausskernel/runtime/executor/execTuples.cpp +++ b/src/gausskernel/runtime/executor/execTuples.cpp @@ -641,11 +641,11 @@ TupleTableSlot* ExecStoreAllNullTuple(TupleTableSlot* slot) Assert(slot != NULL); Assert(slot->tts_tupleDescriptor != NULL); - /* Clear any old contents */ + /* 清除所有旧内容 */ (void)ExecClearTuple(slot); /* - * Fill all the columns of the virtual tuple with nulls + * 用null填充虚拟元组的所有列 */ errno_t rc = EOK; @@ -662,6 +662,16 @@ TupleTableSlot* ExecStoreAllNullTuple(TupleTableSlot* slot) return ExecStoreVirtualTuple(slot); } +/* + 函数ExecStoreAllNullTuple用于将具有所有null值的虚拟元组存储在元组表槽中。 + 该函数以TupleTableSlot指针作为输入,该指针表示元组表中存储虚拟元组的槽。 + 该函数执行一些健全性检查,以确保输入槽有效。它检查slot是否为NULL,以及slot->tts_tupleDescriptor(存储在slot中的元组的描述符)是否为NULL。 + 然后,该函数调用ExecClearTuple来清除插槽中的任何现有内容。这样可以确保在存储新的虚拟元组之前插槽是空的。 + 该函数使用memset_s函数用null值填充插槽的tts_values数组。它将tts_values数组设置为全零,表示元组中每个属性的空值。数组的大小计算为slot->tts_tupleDescriptor->natts*sizeof(Datum)。 + 类似地,该函数使用memset_s用真值填充插槽的tts_isull数组。此数组指示元组中的每个属性是否为null。通过将所有值设置为true,我们表示所有属性都为null。 + 最后,该函数返回使用输入槽调用ExecStoreVirtualTuple的结果。此函数负责将插槽标记为非空,并设置有效属性的数量。 + 此函数准备了一个元组表槽,用于存储具有所有null值的虚拟元组。 +*/ /* -------------------------------- * ExecCopySlotTuple @@ -676,13 +686,21 @@ TupleTableSlot* ExecStoreAllNullTuple(TupleTableSlot* slot) HeapTuple ExecCopySlotTuple(TupleTableSlot* slot) { /* - * sanity checks + * sanity checks (健全性检查) */ Assert(slot != NULL); Assert(!slot->tts_isempty); return tableam_tslot_copy_heap_tuple(slot); } +/* +函数ExecCopySlotTuple用于创建存储在元组表槽中的元组的副本。 +该函数以TupleTableSlot指针作为输入,该指针表示包含要复制的元组的槽。 +该函数执行一些健全性检查,以确保输入槽有效且不为空。它检查slot是否不为NULL,以及slot->tts_isempty是否为false。 +如果健全性检查通过,函数将调用tableam_tslot_copy_heap_tuple来创建存储在插槽中的元组的副本。此函数负责为新元组分配内存,并从原始元组复制数据。 +最后,函数将复制的元组作为HeapTuple对象返回。 + +*/ /* -------------------------------- * ExecCopySlotMinimalTuple @@ -701,6 +719,13 @@ MinimalTuple ExecCopySlotMinimalTuple(TupleTableSlot* slot, bool need_transform_ return tableam_tslot_copy_minimal_tuple(slot); } +/* +函数ExecCopySlotMinimalTuple用于创建存储在元组表槽中的最小元组的副本。 +该函数以TupleTableSlot指针作为输入,该指针表示包含要复制的最小元组的槽。 +该函数执行一些健全性检查,以确保输入槽有效且不为空。它检查slot是否不为NULL,以及slot->tts_isempty是否为false。 +如果健全性检查通过,该函数将调用tableam_tslot_copy_minimaltuple来创建存储在插槽中的最小元组的副本。此函数负责为新的最小元组分配内存,并从原始最小元组复制数据。 +最后,函数将复制的最小元组作为MinimalTuple对象返回。 +*/ /* -------------------------------- * ExecFetchSlotTuple @@ -727,6 +752,13 @@ HeapTuple ExecFetchSlotTuple(TupleTableSlot* slot) return tableam_tslot_get_heap_tuple(slot); } +/* +函数ExecFetchSlotTuple用于从元组表槽中检索堆元组。 +该函数以TupleTableSlot指针作为输入,该指针表示包含要获取的元组的槽。 +该函数执行一些健全性检查,以确保输入槽有效且不为空。它检查slot是否不为NULL,以及slot->tts_isempty是否为false。 +如果健全性检查通过,函数将调用tableam_tslot_get_heap_tuple从插槽中检索堆元组。此函数负责返回存储在插槽中的实际堆元组数据。 +最后,该函数将获取的堆元组作为HeapTuple对象返回。 +*/ /* -------------------------------- * ExecFetchSlotMinimalTuple -- 2.34.1 From 125526ed46e9c8963d6d7436ac93281d68b16218 Mon Sep 17 00:00:00 2001 From: ljh0804 Date: Wed, 27 Sep 2023 19:45:13 +0800 Subject: [PATCH 27/31] Update execTuples.cpp --- .../runtime/executor/execTuples.cpp | 201 +++++++++++++++++- 1 file changed, 190 insertions(+), 11 deletions(-) diff --git a/src/gausskernel/runtime/executor/execTuples.cpp b/src/gausskernel/runtime/executor/execTuples.cpp index 4a9e1f026..e172f4ecf 100644 --- a/src/gausskernel/runtime/executor/execTuples.cpp +++ b/src/gausskernel/runtime/executor/execTuples.cpp @@ -782,7 +782,13 @@ MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot* slot) return tableam_tslot_get_minimal_tuple(slot); } - +/* + 函数ExecFetchSlotMinimalTuple用于从元组表槽中检索最小元组。 + 该函数以TupleTableSlot指针作为输入,该指针表示包含要获取的最小元组的槽。 + 该函数执行健全性检查,以确保输入槽有效。它检查slot是否不为NULL。 + 如果健全性检查通过,该函数将调用tableam_tslot_get_minimaltuple从插槽中检索最小元组。此函数负责返回存储在插槽中的实际最小元组数据。 + 最后,函数将提取的最小元组作为MinimalTuple对象返回。 +*/ /* -------------------------------- * ExecFetchSlotTupleDatum * Fetch the slot's tuple as a composite-type Datum. @@ -798,9 +804,9 @@ Datum ExecFetchSlotTupleDatum(TupleTableSlot* slot) HeapTupleHeader td; TupleDesc tup_desc; - /* Make sure we can scribble on the slot contents ... */ + /* Make sure we can scribble on the slot contents ... (确保我们可以在插槽内容上乱写) */ tup = ExecMaterializeSlot(slot); - /* ... and set up the composite-Datum header fields, in case not done */ + /* ... and set up the composite-Datum header fields, in case not done(并设置复合基准标题字段,如果未完成) */ td = tup->t_data; tup_desc = slot->tts_tupleDescriptor; HeapTupleHeaderSetDatumLength(td, tup->t_len); @@ -808,6 +814,17 @@ Datum ExecFetchSlotTupleDatum(TupleTableSlot* slot) HeapTupleHeaderSetTypMod(td, tup_desc->tdtypmod); return PointerGetDatum(td); } +/* + 函数ExecFetchSlotTupleDatum用于从元组表槽中检索表示元组的Datum。让我们一步一步地浏览代码: + 该函数声明类型为HeapTuple的变量tup、类型为HeapStupleHeader的变量td和类型为TupleDesc的变量tup_desc。这些变量将分别用于存储元组、元组头和元组描述符。 + 该函数调用ExecMaterializeSlot来实现槽,这意味着它在内存中创建元组的物理表示。这是必要的,因为返回的Datum将是指向元组标头的指针,并且需要具体化槽才能访问标头。 + 函数将物化元组分配给元组变量。 + 函数将元组头(tup->t_data)分配给td变量。 + 函数将槽的元组描述符(slot->tts_tupleDescriptor)分配给tup_desc变量。 + 该函数使用HeapTupleHeaderSetDatumLength、HeapTuppleHeaderSetTypeId和HeapTupleHeaderSetTypMod宏, + 根据元组描述符(tup_desc)的长度、类型ID和类型修饰符,在元组标头(td)中设置适当的字段。 + 最后,函数通过调用PointerGetDatum(td)返回元组头(td)的Datum表示。 +*/ /* -------------------------------- * ExecMaterializeSlot @@ -832,6 +849,14 @@ HeapTuple ExecMaterializeSlot(TupleTableSlot* slot) return tableam_tslot_materialize(slot); } +/* +函数ExecMaterializeSlot用于实体化存储在元组表槽中的元组。让我们分解代码: +该函数以TupleTableSlot指针作为输入,该指针表示包含要具体化的元组的槽。 +该函数执行一些健全性检查,以确保输入槽有效且不为空。它检查slot是否不为NULL,以及slot->tts_isempty是否为false。 +如果健全性检查通过,该函数将调用tableam_tslot_materialize来实现槽中的元组。此函数负责在内存中创建元组的物理表示。 +最后,函数将物化元组作为HeapTuple对象返回。 +注意:该代码假定存在一个函数tableam_tslot_materialize,该函数处理元组的物化。此功能的实现方式可能因所使用的特定表访问方法而异 +*/ /* -------------------------------- * ExecCopySlot @@ -849,9 +874,8 @@ TupleTableSlot* ExecCopySlot(TupleTableSlot* dst_slot, TupleTableSlot* src_slot) MemoryContext old_context; /* - * There might be ways to optimize this when the source is virtual, but - * for now just always build a physical copy. Make sure it is in the - * right context. + * 当源是虚拟的时,可能有一些方法可以优化这一点, + * 但目前只是始终构建一个物理副本。确保它在正确的上下文中。 */ old_context = MemoryContextSwitchTo(dst_slot->tts_mcxt); new_tuple = ExecCopySlotTuple(src_slot); @@ -859,6 +883,17 @@ TupleTableSlot* ExecCopySlot(TupleTableSlot* dst_slot, TupleTableSlot* src_slot) return ExecStoreTuple(new_tuple, dst_slot, InvalidBuffer, true); } +/* +函数ExecCopySlot用于创建存储在源元组表槽中的元组的副本,并将其存储在目标元组表槽。 +该函数采用两个TupleTableSlot指针作为输入:dst_slot,它表示将存储复制的元组的目标槽,src_slot,它表示包含要复制元组的源槽。 +函数声明类型为HeapTuple的变量new_tuple和类型为MemoryContext的变量old_context。这些变量将分别用于存储复制的元组和先前的内存上下文。 +该函数使用MemoryContext SwitchTo将内存上下文切换到目标插槽的内存上下文。这样可以确保在正确的内存上下文中分配复制的元组。 +函数调用ExecCopySlotTuple来创建存储在源槽中的元组的副本。此函数负责为新元组分配内存,并从原始元组复制数据。 +函数使用MemoryContext SwitchTo(old_context)将内存上下文切换回上一个上下文。这样可以确保内存上下文恢复到其原始状态。 +最后,函数调用ExecStoreTuple将复制的元组存储在目标槽中。它传递复制的元组(new_tuple)、目标槽(dst_slot)、无效缓冲区(InvalidBuffer)和指示元组是虚拟元组的标志(true)。 +函数返回目标插槽指针。 + +*/ /* ---------------------------------------------------------------- * convenience initialization routines @@ -889,6 +924,17 @@ void ExecInitScanTupleSlot(EState* estate, ScanState* scan_state, TableAmType ta { scan_state->ss_ScanTupleSlot = ExecAllocTableSlot(&estate->es_tupleTable, tam); } +/* +函数ExecInitResultTupleSlot和ExecInitScanTupleSlot分别用于初始化用于存储结果元组和扫描元组的元组槽。 +ExecInitResultTupleSlot函数: +它有三个参数:表示执行状态的estate、表示计划节点状态的plan_state和表示表访问方法类型的tam。 +它使用ExecAllocTableSlot函数将plan_state的结果元组槽分配给新分配的表槽。表槽是使用estate->es_tupleTable从estate的元组表中获得的。 +新分配的表槽被分配给plan_state->ps_ResultTupleSlot,它表示计划节点的结果元组槽。 +ExecInitScanTupleSlot函数: +它有三个参数:表示执行状态的estate、表示扫描节点状态的scan_state和表示表访问方法类型的tam。 +它使用ExecAllocTableSlot函数将scan_state的扫描元组槽分配给新分配的表槽。表槽是使用estate->es_tupleTable从estate的元组表中获得的。 +新分配的表槽被分配给scan_state->ss_ScanTupleSlot,它表示扫描节点的扫描元组槽。 +*/ /* ---------------- * ExecInitExtraTupleSlot @@ -915,6 +961,13 @@ TupleTableSlot* ExecInitNullTupleSlot(EState* estate, TupleDesc tup_type) return ExecStoreAllNullTuple(slot); } +/* +函数ExecInitNullTupleSlot用于使用null元组初始化元组表槽。 +该函数采用两个参数:表示执行状态的estate和表示null元组的元组描述符的tup_type。 +函数调用ExecInitTextraTupleSlot来初始化一个额外的元组表槽。此函数负责从执行状态的元组表中分配和初始化一个新的元组表槽。 +函数使用ExecSetSlotDescriptor将元组描述符tup_type分配给插槽。此函数将槽的描述符设置为提供的元组描述符。 +最后,该函数返回使用初始化的slot调用ExecStoreAllNullTuple的结果。此函数负责通过将所有属性值设置为null来用null元组填充槽。 +*/ /* ---------------------------------------------------------------- * ExecTypeFromTL @@ -977,6 +1030,20 @@ static TupleDesc ExecTypeFromTLInternal(List* target_list, bool has_oid, bool sk return type_info; } +/* +函数ExecCleanTypeFromTL用于从目标列表生成干净的元组描述符。 +该函数采用三个参数:target_list,表示目标列表;has_oid,表示生成的元组描述符是否应包括oid列的布尔值;tam,表示表访问方法类型。 +该函数使用适当的参数调用ExecTypeFromTLInternal函数以生成元组描述符。它传递target_list、has_oid、true(跳过垃圾条目)、false(不标记丢弃的列)和tam(表访问方法类型)。 +ExecTypeFromTLInternal函数初始化一些变量,包括type_info,一个TupleDesc对象,它将保存生成的元组描述符,以及len,目标列表的长度。 +该函数使用CreateTemplateTupleDesc创建模板元组描述符,传递len、has_oid和tam。这将创建一个具有指定数量的属性和OID标志的空元组描述符。 +该函数使用foreach循环迭代目标列表中的每个目标条目。 +如果skip_junk为true,并且当前目标条目是一个垃圾条目(resjunk为true),则循环将继续到下一次迭代。 +函数使用TupleDescInitEntry初始化元组描述符中的一个条目。它设置属性编号(cur_resno)、属性名称(resname)、 +属性类型(exprType((Node*)ttle->expr))、属性typmod(exprTypmod((Node*ttle->expr)),以及属性排序规则的默认值0。 +如果mark_dropped为true,并且属性名称包含字符串“……..pg.dropped.”,则通过将attitdropped设置为true,将该属性标记为已删除。 +cur_resno将递增,为下一个属性做准备。 +在对所有目标条目进行迭代之后,函数将返回生成的元组描述符。 +*/ /* * ExecTypeFromExprList - build a tuple descriptor from a list of Exprs @@ -1006,6 +1073,18 @@ TupleDesc ExecTypeFromExprList(List* expr_list, List* names_list, TableAmType t return type_info; } +/* +函数ExecTypeFromExprList用于从表达式列表和相应的名称列表生成元组描述符。让我们分解代码: +该函数采用三个参数:expr_list,表示表达式列表,names_list,表示与表达式对应的名称列表,tam,表示表访问方法类型。 +该函数初始化变量,包括type_info(一个TupleDesc对象,它将保存生成的元组描述符)和cur_resno(一个表示当前属性编号的整数)。 +该函数使用Assert断言expr_list和names_list的长度相等。这样可以确保每个表达式都有相应的名称。 +该函数使用CreateTemplateTupleDesc创建模板元组描述符,传递expr_list的长度,false表示元组描述符不应包括OID列,tam表示表访问方法类型。 +该函数使用forboth循环并行迭代每个表达式和名称。 +函数使用TupleDescInitEntry初始化元组描述符中的一个条目。它为属性的排序规则设置属性编号(cur_resno)、属性名称(n)、属性类型(exprType(e))、属性typmod(exprTypmod(e))和默认值0。 +函数使用TupleDescInitEntryCollation和exprCollation(e)设置属性的排序规则。 +cur_resno将递增,为下一个属性做准备。 +在对所有表达式和名称进行迭代之后,函数将返回生成的元组描述符。 +*/ /* * BlessTupleDesc - make a completed tuple descriptor useful for SRFs @@ -1043,6 +1122,16 @@ TupleTableSlot* TupleDescGetSlot(TupleDesc tup_desc) /* Return the slot */ return slot; } +/* +函数BlessTupleDesc用于祝福元组描述符。函数TupleDescGetSlot用于根据提供的元组描述符初始化元组表槽。让我们分解代码: +函数BlessTupleDesc将TupleDesc对象tup_desc作为输入。 +如果tup_desc的tdtypeid是RECORDOID,并且tdtypmod小于0,则表示元组描述符表示通用记录类型。在这种情况下,函数调用assign_record_type_typmod为记录类型分配一个合适的typmod。 +函数返回tup_desc对象。这样做是为了方便表示,因为函数不直接修改元组描述符。 +函数TupleDescGetSlot将TupleDesc对象tup_desc作为输入。 +该函数调用BlessTupleDesc来祝福元组描述符。这样做是为了确保元组描述符正确初始化并准备好使用。 +该函数调用MakeSingleTupleTableSlot,根据提供的元组描述符创建一个独立的元组表槽。 +函数返回创建的元组表槽。 +*/ /* * TupleDescGetAttInMetadata - Build an AttInMetadata structure based on the @@ -1062,18 +1151,18 @@ AttInMetadata* TupleDescGetAttInMetadata(TupleDesc tup_desc) att_in_meta = (AttInMetadata*)palloc(sizeof(AttInMetadata)); - /* "Bless" the tupledesc so that we can make rowtype datums with it */ + /* "Bless" the tupledesc so that we can make rowtype datums with it(“Bless”元组,这样我们就可以用它制作行型基准) */ att_in_meta->tupdesc = BlessTupleDesc(tup_desc); /* - * Gather info needed later to call the "in" function for each attribute + * Gather info needed later to call the "in" function for each attribute(收集稍后调用每个属性的“in”函数所需的信息) */ att_in_func_info = (FmgrInfo*)palloc0(natts * sizeof(FmgrInfo)); att_io_params = (Oid*)palloc0(natts * sizeof(Oid)); att_typ_mods = (int32*)palloc0(natts * sizeof(int32)); for (i = 0; i < natts; i++) { - /* Ignore dropped attributes */ + /* Ignore dropped attributes(忽略丢弃的属性) */ if (!tup_desc->attrs[i]->attisdropped) { att_type_id = tup_desc->attrs[i]->atttypid; getTypeInputInfo(att_type_id, &att_in_func_id, &att_io_params[i]); @@ -1087,6 +1176,20 @@ AttInMetadata* TupleDescGetAttInMetadata(TupleDesc tup_desc) return att_in_meta; } +/* +函数TupleDescGetAttInMetadata用于收集调用元组描述符中每个属性的“in”函数所需的信息。让我们分解代码: +该函数采用TupleDesc对象tup_desc作为输入。 +它初始化变量,包括natts、元组描述符中的属性数量、i、循环计数器以及用于存储属性信息的各种其他变量。 +它使用palloc为AttInMetadata对象att_in_meta分配内存。此对象将保存收集到的属性信息。 +它通过调用BlessTupleDesc来“祝福”元组描述符。这样可以确保元组描述符正确初始化并准备好使用。 +它初始化数组att_in_func_info、att_io_params和att_typ_mod,以存储每个属性所需的信息。 +它使用循环对元组描述符中的每个属性进行迭代。 +如果属性未被丢弃(attidrepped为false),它将使用getTypeInputInfo收集诸如属性类型ID、“in”函数ID和IO参数等信息。 +它使用fmgr_info用“in”函数信息初始化att_in_func_info数组。 +它将IO参数和属性类型mod存储在相应的数组中。 +它将数组分配给att_in_meta对象中的相应字段。 +它返回att_in_meta对象。 +*/ /* * BuildTupleFromCStrings - build a HeapTuple given user data in C string form. @@ -1134,6 +1237,19 @@ HeapTuple BuildTupleFromCStrings(AttInMetadata* att_in_meta, char** values) return tuple; } +/* +函数BuildTupleFromCStrings用于从C样式字符串数组中构建HeapTuple。 +该函数接受一个AttInMetadata对象att_in_meta和一个C样式字符串值数组作为输入。 +它从att_in_meta对象中提取TupleDesc对象tup_desc,并获取元组描述符中属性natt的数量。 +它初始化数组d_values和nulls,分别存储属性值和null标志。 +它使用palloc为d_values和null分配内存。 +它使用循环对元组描述符中的每个属性进行迭代。 +如果该属性未被丢弃(attitdropped为false),它将使用InputFunctionCall为该属性调用“in”函数。此函数使用适当的输入函数将C样式字符串值转换为基准。它还根据值是否为null来设置null标志。 +如果属性被删除,它会将属性值设置为NULL,并将NULL标志设置为true。 +在迭代所有属性之后,它使用tableam_tops_form_tuple形成一个HeapTuple。此函数基于提供的元组描述符、属性值和null标志创建一个新的HeapTuple。 +它使用pfree_ext释放为d_values和null分配的内存。 +它返回创建的HeapTuple。 +*/ /* * Functions for sending tuples to the frontend (or other specified destination) @@ -1154,6 +1270,17 @@ TupOutputState* begin_tup_output_tupdesc(DestReceiver* dest, TupleDesc tup_desc) return tstate; } +/* +函数begin_tup_output_tupdesc用于初始化给定目标接收器和元组描述符的元组输出状态。 +该函数采用两个参数:dest,表示目标接收器,tup_desc,表示元组描述符。 +它声明了一个TupOutputState类型的变量tstate。 +它使用palloc为tstate对象分配内存。 +它通过使用提供的tup_desc调用MakeSingleTupleTableSlot来初始化tstate的slot字段。这将基于元组描述符创建一个单元组表槽。 +它将dest参数指定给tstate的dest字段。 +它使用(*tstate->dest->rStartup)调用目标接收器的rStartup函数。此功能负责初始化目标接收器并执行任何必要的设置。 +rStartup函数是使用目标接收器、命令类型(在本例中为CMD_SELECT)和元组描述符调用的。 +最后,函数返回tstate对象。 +*/ /* * write a single tuple @@ -1186,11 +1313,24 @@ void do_tup_output(TupOutputState* tstate, Datum* values, size_t values_len, con /* clean up */ (void)ExecClearTuple(slot); } +/* +函数do_tup_output用于使用提供的TupOutputState对象将元组输出到目标接收器。 +该函数采用五个参数:tstate,它表示元组输出状态,values,它是元组的Datum值的数组,values_len,values数组的长度,is_null,它是指示每个属性是否为null的布尔值的数组;is_null_len,is _null数组的长度。 +它使用Assert断言值和is_null数组不为null,并且它们的长度不为零。这样可以确保提供有效的数据。 +它声明了一个TupleTableSlot类型的变量slot,并为其分配tstate对象的slot字段。 +它从插槽的tts_tupleDescriptor字段中获取属性natt的数量。 +它使用ExecClearTuple清除插槽,以确保插槽为空。 +它使用memcpy_s将值数组复制到插槽的tts_values字段中。 +它使用memcpy_s将is_null数组复制到插槽的tts_isull字段中。 +它使用ExecStoreVirtualTuple将插槽标记为包含虚拟元组。 +它通过调用目标接收器的receiveSlot函数,将slot和tstate->dest作为参数,将元组发送到目标接收器。 +它使用ExecClearTuple再次清除插槽以清除任何剩余数据。 +*/ /* - * write a chunk of text, breaking at newline characters + * write a chunk of text, breaking at newline characters(写一大块文本,换行) * - * Should only be used with a single-TEXT-attribute tupdesc. + * Should only be used with a single-TEXT-attribute tupdesc.(应仅与单个TEXT属性tupdesc一起使用) */ int do_text_output_multiline(TupOutputState* tstate, char* text) { @@ -1220,6 +1360,20 @@ int do_text_output_multiline(TupOutputState* tstate, char* text) } return tuple_count; } +/*函数do_text_output_multiline用于使用提供的TupOutputState对象将多行文本作为元组输出到目标接收器。 +该函数接受两个参数:tstate,它表示元组输出状态,text,它是指向输入文本的指针。 +声明了一个长度为1的Datum类型的数组值。此数组将保存每个元组的文本值。 +声明了一个bool类型的数组is_null,长度为1,初始化为false。此数组指示每个元组的属性是否为null。 +声明了一个变量tuple_count,并将其初始化为0。这个变量将跟踪输出的元组的数量。 +只要文本字符串中有字符,函数就会进入一个循环。 +使用strchr搜索换行符('\n')的下一个出现。如果找到,它会计算行的长度,并增加eol指针以指向换行符之后的下一个字符。如果找不到,它会将长度设置为文本字符串中的剩余字符,并将eol设置为指向字符串的末尾。 +使用cstring_to_text_with_len将文本行转换为基准,并将其指定给值[0]。 +调用do_tup_output函数来输出具有值数组、is_null数组和提供的tstate的元组。 +递增tuple_count变量。 +使用pfree释放为Datum值分配的内存。 +更新文本指针以指向下一行文本(在换行符之后)。 +循环结束后,返回元组计数。 +*/ void end_tup_output(TupOutputState* tstate) { @@ -1297,3 +1451,28 @@ TupleTableSlot* ExecStoreDataRowTuple(char* msg, size_t len, Oid msgnode_oid, Tu return slot; } #endif +/* +两个独立的函数:end_tup_output和ExecStoreDataRowTuple。 + +end_tup_output: +此函数用于清理和最终确定元组输出状态。 +它接受一个TupOutputState对象tstate作为输入。 +它使用(*tstate->dest->rShutdown)调用目标接收器的rShutdownfunction。此功能负责关闭目标接收器并执行任何必要的清理。 +它使用ExecDropSingleTupleTableSlot删除单元组表槽。 +它使用pfree_ext释放为tstate对象分配的内存。 +*/ + +/* +ExecStoreDataRowTuple: +此函数用于将DataRow消息格式的缓冲区存储到元组表槽中。 +它需要几个参数:msg,它表示包含DataRow消息的缓冲区,len,它是缓冲区的长度,msgnode_oid,它表示与消息相关的节点oid,slot,它是将缓冲区存储到的元组表slot,should_free,一个指示缓冲区是否应该释放的布尔标志。 +它执行若干健全性检查以确保输入参数的有效性。 +它释放属于插槽的任何旧物理元组。 +它释放属于插槽的任何旧的最小元组。 +如果需要,它会释放插槽中的dataRow。 +它重置插槽的每个元组上下文。 +它释放引用缓冲区上的引脚(如果有)。 +它通过更新槽的相关字段将新元组存储到指定的槽中。 +它将提取的状态标记为无效。 +最后,它返回更新后的插槽。 +*/ \ No newline at end of file -- 2.34.1 From e93533c4520561700e60f865ab833614209ff84e Mon Sep 17 00:00:00 2001 From: LYLlyl Date: Sat, 30 Sep 2023 16:12:23 +0800 Subject: [PATCH 28/31] Update lightProxy.cpp --- .../runtime/executor/lightProxy.cpp | 100 +++++++++++++++++- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/src/gausskernel/runtime/executor/lightProxy.cpp b/src/gausskernel/runtime/executor/lightProxy.cpp index 4d062f142..c0c1615e6 100644 --- a/src/gausskernel/runtime/executor/lightProxy.cpp +++ b/src/gausskernel/runtime/executor/lightProxy.cpp @@ -1482,46 +1482,82 @@ CmdType set_cmd_type(const char* commandTag) return cmd_type; } +// 定义函数,参数为输入的命令标签指针 CmdType set_command_type_by_commandTag(const char* commandTag) { + // 初始化命令类型为未知类型(CMD_UNKNOWN) CmdType cmd_type = CMD_UNKNOWN; + // 定义循环变量 int i; + + // 遍历命令类型数组,数组长度为 MAX_COMMAND for (i = 0; i < MAX_COMMAND; i++) { + // 使用 strstr 函数查找命令标签是否包含在当前数组元素的 commandTag 中 if (strstr(commandTag, g_command_type_array[i].commandTag)) + // 如果找到匹配,返回当前数组元素的命令类型 return g_command_type_array[i].type; } + + // 如果未找到匹配的命令标签,返回未知类型(CMD_UNKNOWN) return cmd_type; } +//检查当前的轻量级代理对象是否处于打开状态 bool IsLightProxyOn(void) { return (u_sess->exec_cxt.cur_light_proxy_obj != NULL); } +/* + * 函数:exec_query_through_light_proxy + * + * 参数: + * - querytree_list: 查询树的列表,可能包含多个查询树节点 + * - parsetree: 解析树的根节点 + * - snapshot_set: 指示是否设置了快照 + * - msg: 用于存储执行结果的消息缓冲区 + * - OptimizerContext: 优化器内存上下文 + * + * 返回值: + * - 如果成功执行查询,返回true;否则返回false + * + * 说明: + * 该函数用于执行查询。如果查询树列表中只包含一个查询树节点,且不是CREATE TABLE AS或者REFRESH MATERIALIZED VIEW命令, + * 并且该查询满足特定条件(例如,支持ROUTER或者是轻量级查询),则使用轻量级代理执行查询,并返回true。 + * 在执行之前可能会设置一些状态和处理一些资源。 + */ bool exec_query_through_light_proxy(List* querytree_list, Node* parsetree, bool snapshot_set, StringInfo msg, MemoryContext OptimizerContext) { + // 检查查询树列表是否只包含一个查询树节点,并且不是特定的命令类型 if ((list_length(querytree_list) == 1) && !IsA(parsetree, CreateTableAsStmt) && !IsA(parsetree, RefreshMatViewStmt)) { ExecNodes* single_exec_node = NULL; lightProxy* proxy = NULL; Query* query = (Query*)linitial(querytree_list); + // 检查是否支持ROUTER,如果是,使用轻量级代理执行查询 if (ENABLE_ROUTER(query->commandType)) { single_exec_node = lightProxy::checkRouterQuery(query); } else { single_exec_node = lightProxy::checkLightQuery(query); } - /* only deal with single node */ + + // 只处理单节点查询 if (single_exec_node && list_length(single_exec_node->nodeList) + list_length(single_exec_node->primarynodelist) == 1) { - /* GTMLite: need to mark that this is single shard statement */ + // 标记这是一个单片查询 u_sess->exec_cxt.single_shard_stmt = true; + + // 如果命令类型支持热点查询,发送热点信息给Pgstat if (CmdtypeSupportsHotkey(query->commandType)) SendHotkeyToPgstat(); + // 创建轻量级代理对象 proxy = New(OptimizerContext) lightProxy(query); proxy->m_nodeIdx = linitial_int(single_exec_node->nodeList); + + // 设置资源追踪状态 bool old_status = u_sess->exec_cxt.need_track_resource; if (u_sess->attr.attr_resource.resource_track_cost == 0 && u_sess->attr.attr_resource.enable_resource_track && @@ -1529,50 +1565,106 @@ bool exec_query_through_light_proxy(List* querytree_list, Node* parsetree, bool u_sess->exec_cxt.need_track_resource = true; WLMSetCollectInfoStatus(WLM_STATUS_RUNNING); } + + // 运行简单查询 proxy->runSimpleQuery(msg); - /* Done with the snapshot used for parsing/planning */ + /* 完成用于解析/计划的快照 */ if (snapshot_set) { PopActiveSnapshot(); } + // 释放资源,恢复状态 FreeExecNodes(&single_exec_node); u_sess->exec_cxt.need_track_resource = old_status; t_thrd.wlm_cxt.parctl_state.except = 0; + + // 返回执行成功 return true; } + + // 如果不符合条件,释放资源并返回执行失败 FreeExecNodes(&single_exec_node); CleanHotkeyCandidates(true); return false; } + // 如果查询树列表长度不为1或者是特定的命令类型,返回执行失败 return false; } + +/* + * 函数:GPCDropLPIfNecessary + * + * 参数: + * - stmt_name: 待处理的语句名称 + * - need_drop_dnstmt: 是否需要删除数据节点的语句 + * - need_del: 是否需要删除轻量级代理对象 + * - reset_plan: 重置计划缓存源的指针 + * + * 说明: + * 该函数用于根据给定的语句名称,检查是否存在对应的轻量级代理对象。 + * 如果存在,可以选择重置计划缓存源,删除数据节点的语句(如果需要的话),以及删除轻量级代理对象(如果需要的话)。 + * 如果不存在对应的轻量级代理对象,函数不做任何处理。 + */ void GPCDropLPIfNecessary(const char *stmt_name, bool need_drop_dnstmt, bool need_del, CachedPlanSource *reset_plan) { + // 如果语句名称为空或者为'\0',或者不是PGXC协调器,直接返回 if (stmt_name == NULL || stmt_name[0] == '\0' || !IS_PGXC_COORDINATOR) return; + + // 根据语句名称定位轻量级代理对象 lightProxy *lp = lightProxy::locateLpByStmtName(stmt_name); + + // 如果找到了轻量级代理对象 if (lp != NULL) { + // 如果有需要,重置计划缓存源 if (reset_plan) { lp->m_cplan = reset_plan; } + + // 如果需要删除数据节点的语句,删除相应的数据节点语句 if (stmt_name && need_drop_dnstmt) { lp->m_entry = NULL; DropDatanodeStatement(stmt_name); } + + // 如果需要删除轻量级代理对象,进行相应的清理操作 if (need_del) { lightProxy::tearDown(lp); } + + // 函数执行完成,返回 return; } + + // 如果没有找到对应的轻量级代理对象,直接返回 return; } +/* + * 函数:GPCFillMsgForLp + * + * 参数: + * - psrc: 待填充信息的缓存计划源 + * + * 说明: + * 该函数用于为给定的缓存计划源填充信息,主要是为共享计划填充GPCKey结构的信息。 + * 如果缓存计划源的GPC状态为GPC_SHARED,函数将根据缓存计划源的信息填充GPCKey结构, + * 用于在全局计划缓存中保存计划。 + */ void GPCFillMsgForLp(CachedPlanSource* psrc) { + // 断言缓存计划源不为空 Assert(psrc != NULL); + + // 如果缓存计划源的GPC状态为GPC_SHARED if (psrc->gpc.status.InSavePlanList(GPC_SHARED)) { + // 释放旧的GPCKey结构 pfree_ext(psrc->gpc.key); + + // 切换到缓存计划源的内存上下文 MemoryContext oldcxt = MemoryContextSwitchTo(psrc->context); + + // 为缓存计划源的GPCKey结构分配内存,并填充相关信息 psrc->gpc.key = (GPCKey*)palloc0(sizeof(GPCKey)); psrc->gpc.key->query_string = psrc->query_string; psrc->gpc.key->query_length = (uint32)strlen(psrc->query_string); @@ -1581,6 +1673,8 @@ void GPCFillMsgForLp(CachedPlanSource* psrc) psrc->gpc.key->env.search_path = psrc->search_path; psrc->gpc.key->env.num_params = psrc->num_params; psrc->gpc.key->env.param_types = psrc->param_types; + + // 切换回原始内存上下文 (void)MemoryContextSwitchTo(oldcxt); } } -- 2.34.1 From de4ed4867ee44c2e847866be9832fbd073fd4b8e Mon Sep 17 00:00:00 2001 From: TerryTongJ Date: Sat, 30 Sep 2023 17:47:44 +0800 Subject: [PATCH 29/31] Update execMain.cpp --- src/gausskernel/runtime/executor/execMain.cpp | 497 +++++++++++------- 1 file changed, 303 insertions(+), 194 deletions(-) diff --git a/src/gausskernel/runtime/executor/execMain.cpp b/src/gausskernel/runtime/executor/execMain.cpp index cf58f8fdd..c83cfc0bc 100755 --- a/src/gausskernel/runtime/executor/execMain.cpp +++ b/src/gausskernel/runtime/executor/execMain.cpp @@ -3,28 +3,32 @@ * execMain.cpp * top level executor interface routines * - * INTERFACE ROUTINES - * ExecutorStart() - * ExecutorRun() - * ExecutorFinish() - * ExecutorEnd() - * - * These four procedures are the external interface to the executor. - * In each case, the query descriptor is required as an argument. - * - * ExecutorStart must be called at the beginning of execution of any - * query plan and ExecutorEnd must always be called at the end of - * execution of a plan (unless it is aborted due to error). - * - * ExecutorRun accepts direction and count arguments that specify whether - * the plan is to be executed forwards, backwards, and for how many tuples. - * In some cases ExecutorRun may be called multiple times to process all - * the tuples for a plan. It is also acceptable to stop short of executing - * the whole plan (but only if it is a SELECT). - * - * ExecutorFinish must be called after the final ExecutorRun call and - * before ExecutorEnd. This can be omitted only in case of EXPLAIN, - * which should also omit ExecutorRun. + * 文件描述: 此注释块提供了关于execMain.cpp文件用途和接口的概述。 + * + * 接口例程: 注释描述了执行器的四个主要接口例程,负责执行查询计划。这些例程包括: + * ExecutorStart(): 用于启动查询计划的执行。需要查询描述符作为参数。 + * ExecutorRun(): 执行查询计划并接受参数,如方向和数量,以指定计划的执行方式(例如,正向或反向,以及处理多少个元组)。 + * ExecutorFinish(): 在最后一次ExecutorRun()调用之后调用,用于执行任何必要的清理或最终化操作。 + * ExecutorEnd(): 必须在计划执行结束时调用,除非由于错误而中止执行。 + * + * 这四个例程是执行器的外部接口:这意味着这四个例程是外部程序或模块与执行器交互的主要入口点。 + * + * 每个例程都需要查询描述符作为参数:无论是 ExecutorStart、ExecutorRun、ExecutorFinish 还是 ExecutorEnd,它们都需要一个查询描述符作为参数。这个描述符可能包含了有关要执行的查询计划的信息。 + * + * ExecutorStart 必须在执行任何查询计划的开始时调用: + * 这意味着在开始执行任何查询计划之前,必须首先调用 ExecutorStart。这是执行计划的初始化步骤。 + * ExecutorEnd 必须在计划执行结束时调用: + * 无论计划是否成功执行,都必须在计划执行结束时调用 ExecutorEnd,除非由于错误而中止执行。 + * ExecutorRun 接受方向和计数参数: + * ExecutorRun 接受两个参数,一个是方向(direction),用于指定计划是正向执行还是反向执行, + * 另一个是计数(count),用于指定要处理多少个元组。这允许精确控制查询计划的执行。 + * ExecutorRun 可能会多次调用: + * 在某些情况下,需要多次调用 ExecutorRun 来处理查询计划的所有元组。 + * 此外,对于 SELECT 查询,可以在不执行整个计划的情况下停止执行,这是允许的。 + * ExecutorFinish 必须在最后一次 ExecutorRun 调用之后调用: + * ExecutorFinish 用于在最后一次执行计划后执行任何必要的清理或最终化操作。 + * 它必须在最后一次 ExecutorRun 调用之后,并在 ExecutorEnd 之前调用。 + * 但在执行 EXPLAIN 操作时,可以省略 ExecutorFinish,并且在这种情况下,还应省略 ExecutorRun。 * * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group @@ -96,7 +100,9 @@ #include "gs_ledger/ledger_utils.h" #include "gs_policy/gs_policy_masking.h" -/* Hooks for plugins to get control in ExecutorStart/Run/Finish/End */ +/* Hooks for plugins to get control in ExecutorStart/Run/Finish/End + 定义了hooks变量,用于插件在执行器的不同阶段获取控制权。 + */ THR_LOCAL ExecutorStart_hook_type ExecutorStart_hook = NULL; THR_LOCAL ExecutorRun_hook_type ExecutorRun_hook = NULL; THR_LOCAL ExecutorFinish_hook_type ExecutorFinish_hook = NULL; @@ -106,11 +112,15 @@ THR_LOCAL ExecutorEnd_hook_type ExecutorEnd_hook = NULL; THR_LOCAL ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL; #define THREAD_INTSERVAL_60S 60 -/* Debug information to hold the string of top plan node's node tag */ +/* Debug information to hold the string of top plan node's node tag + 用于记录顶级计划节点的节点标签(node tag)的字符串,用于调试和跟踪目的。 +*/ THR_LOCAL char *producer_top_plannode_str = NULL; THR_LOCAL bool is_syncup_producer = false; -/* decls for local routines only used within this module */ +/* decls for local routines only used within this module + 声明的变量和函数都是仅在当前模块内部使用的局部变量和局部函数。 + */ void InitPlan(QueryDesc *queryDesc, int eflags); static void CheckValidRowMarkRel(Relation rel, RowMarkType markType); static void ExecPostprocessPlan(EState *estate); @@ -147,6 +157,8 @@ extern bool anls_opt_is_on(AnalysisOpt dfx_opt); * not appear to be any good header to put it into, given the structures that * it uses, so we let them be duplicated. Be sure to update both if one needs * to be changed, however. + * 在维护和更改函数 GetUpdatedColumns() 时要小心,确保两个定义保持一致,以避免潜在的问题和不一致性。 + * GetUpdatedColumns() 存在于代码的两个不同位置,一个是当前位置,另一个是 commands/trigger.c 文件 */ #define GetInsertedColumns(relinfo, estate) \ (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->insertedCols) @@ -160,6 +172,7 @@ extern bool anls_opt_is_on(AnalysisOpt dfx_opt); * report_iud_time * * send the finish time of insert/update/delete operations to pgstat collector. + * 将插入(Insert)、更新(Update)、删除(Delete)操作的完成时间发送给 pgstat 收集器(pgstat collector) * ---------------------------------------------------------------- */ static void report_iud_time(QueryDesc *query) @@ -193,29 +206,25 @@ static void report_iud_time(QueryDesc *query) /* ---------------------------------------------------------------- * ExecutorStart * - * This routine must be called at the beginning of any execution of any - * query plan + * ExecutorStart 函数必须在执行任何查询计划的开头被调用。 * - * Takes a QueryDesc previously created by CreateQueryDesc (which is separate - * only because some places use QueryDescs for utility commands). The tupDesc - * field of the QueryDesc is filled in to describe the tuples that will be - * returned, and the internal fields (estate and planstate) are set up. + * 参数:这个函数接受一个 QueryDesc 结构, + * 该结构是通过 CreateQueryDesc 创建的,用于描述查询计划的详细信息。 + * + * eflags 参数包含了一些标志位,具体含义在 executor.h 中描述。 + * + * 注意事项:在调用此函数时,当前内存上下文(CurrentMemoryContext)将成为用于此执行器调用的每个查询上下文(per-query context)的父级。 + * 这意味着查询期间分配的内存将从此上下文中获取。 * - * eflags contains flag bits as described in executor.h. - * - * NB: the CurrentMemoryContext when this is called will become the parent - * of the per-query context used for this Executor invocation. - * - * We provide a function hook variable that lets loadable plugins - * get control when ExecutorStart is called. Such a plugin would - * normally call standard_ExecutorStart(). + * 提供了一个函数钩子(function hook)变量,允许可加载的插件在调用 ExecutorStart 时获得控制权。 + * 这样的插件通常会调用 standard_ExecutorStart() 函数来执行标准的 ExecutorStart 操作。 * ---------------------------------------------------------------- */ void ExecutorStart(QueryDesc* queryDesc, int eflags) { gstrace_entry(GS_TRC_ID_ExecutorStart); - /* it's unsafe to deal with plugins hooks as dynamic lib may be released */ + /* 与插件钩子函数的交互可能不安全,因为动态库可能会被释放(卸载)*/ if (ExecutorStart_hook && !(g_instance.status > NoShutdown)) (*ExecutorStart_hook)(queryDesc, eflags); else @@ -231,10 +240,13 @@ void standard_ExecutorStart(QueryDesc *queryDesc, int eflags) instr_time starttime; double totaltime = 0; - /* sanity checks: queryDesc must not be started already */ + /* 确保 queryDesc 对象在执行函数之前尚未被启动。 */ Assert(queryDesc != NULL); Assert(queryDesc->estate == NULL); +/* + 在条件编译开启并且 MEMORY_CONTEXT_CHECKING 宏被定义时,执行内存上下文检查,以帮助发现和诊断内存管理问题 + */ #ifdef MEMORY_CONTEXT_CHECKING /* Check all memory contexts when executor starts */ MemoryContextCheck(t_thrd.top_mem_cxt, false); @@ -243,24 +255,26 @@ void standard_ExecutorStart(QueryDesc *queryDesc, int eflags) /* * If the transaction is read-only, we need to check if any writes are * planned to non-temporary tables. EXPLAIN is considered read-only. + * + * 检查事务是否为只读(read-only)事务,以及是否计划对非临时表进行写操作。 */ if (u_sess->attr.attr_common.XactReadOnly && !(eflags & EXEC_FLAG_EXPLAIN_ONLY)) { ExecCheckXactReadOnly(queryDesc->plannedstmt); } - /* reset the sequent number of memory context */ + /* 重置内存上下文(memory context)的序列号(sequent number) */ t_thrd.utils_cxt.mctx_sequent_count = 0; - /* Initialize the memory tracking information */ + /* Initialize the memory tracking information 初始化内存跟踪信息 */ MemoryTrackingInit(); /* - * Build EState, switch into per-query memory context for startup. + * 构建执行器状态(EState),并切换到每个查询的内存上下文(per-query memory context)以进行启动。 */ estate = CreateExecutorState(); queryDesc->estate = estate; - /* record the init memory track of the executor engine */ + /* 记录执行器引擎的初始内存跟踪信息 */ #ifndef ENABLE_MEMORY_CHECK t_thrd.utils_cxt.ExecutorMemoryTrack = ((AllocSet)(estate->es_query_cxt))->track; #else @@ -284,12 +298,12 @@ void standard_ExecutorStart(QueryDesc *queryDesc, int eflags) u_sess->instr_cxt.global_instr->allocThreadInstrumentation(queryDesc->plannedstmt->planTree->plan_node_id); } - /* CN of the compute pool. */ + /* 计算池(compute pool)的计算节点(Compute Node---CN) */ if (StreamTopConsumerAmI() && queryDesc->instrument_options != 0 && IS_PGXC_COORDINATOR && queryDesc->plannedstmt->in_compute_pool) { const int dop = 1; - /* m_instrDataContext in CN of compute pool is under t_thrd.mem_cxt.stream_runtime_mem_cxt */ + /* m_instrDataContext 位于计算池(compute pool)中的计算节点(CN)下,并且受到名为 t_thrd.mem_cxt.stream_runtime_mem_cxt 的内存上下文的管理。 */ AutoContextSwitch streamCxtGuard(u_sess->stream_cxt.stream_runtime_mem_cxt); u_sess->instr_cxt.global_instr = StreamInstrumentation::InitOnCP(queryDesc, dop); @@ -304,8 +318,7 @@ void standard_ExecutorStart(QueryDesc *queryDesc, int eflags) #endif /* - * Fill in external parameters, if any, from queryDesc; and allocate - * workspace for internal parameters + * 从查询描述(queryDesc)中填充外部参数(external parameters),并为内部参数(internal parameters)分配工作空间。 */ estate->es_param_list_info = queryDesc->params; @@ -315,12 +328,12 @@ void standard_ExecutorStart(QueryDesc *queryDesc, int eflags) } /* - * If non-read-only query, set the command ID to mark output tuples with + * 如果查询是非只读的,则设置命令标识(command ID),以标记输出元组 */ switch (queryDesc->operation) { case CMD_SELECT: /* - * SELECT FOR [KEY] UPDATE/SHARE and modifying CTEs need to mark tuples + * SELECT FOR [KEY] UPDATE/SHARE 操作以及修改公共表表达式(Common Table Expressions,CTEs)时,需要标记元组。 */ if (queryDesc->plannedstmt->rowMarks != NIL || queryDesc->plannedstmt->hasModifyingCTE) { estate->es_output_cid = GetCurrentCommandId(true); @@ -331,6 +344,7 @@ void standard_ExecutorStart(QueryDesc *queryDesc, int eflags) * so force skip-triggers mode. This is just a marginal efficiency * hack, since AfterTriggerBeginQuery/AfterTriggerEndQuery aren't * all that expensive, but we might as well do it. + * 对于不修改公共表表达式(CTEs)的 SELECT 查询,不可能触发触发器(triggers),因此强制启用跳过触发器模式。 */ if (!queryDesc->plannedstmt->hasModifyingCTE) { eflags |= EXEC_FLAG_SKIP_TRIGGERS; @@ -351,31 +365,38 @@ void standard_ExecutorStart(QueryDesc *queryDesc, int eflags) } /* - * Copy other important information into the EState + * 将其他重要信息复制到执行器状态(EState)中 */ estate->es_snapshot = RegisterSnapshot(queryDesc->snapshot); estate->es_crosscheck_snapshot = RegisterSnapshot(queryDesc->crosscheck_snapshot); estate->es_top_eflags = eflags; estate->es_instrument = queryDesc->instrument_options; - /* Apply BloomFilter array space. */ + /* 应用布隆过滤器(Bloom Filter)数组的空间 */ if (queryDesc->plannedstmt->MaxBloomFilterNum > 0) { int bloom_size = queryDesc->plannedstmt->MaxBloomFilterNum; estate->es_bloom_filter.array_size = bloom_size; estate->es_bloom_filter.bfarray = (filter::BloomFilter **)palloc0(bloom_size * sizeof(filter::BloomFilter *)); } #ifdef ENABLE_MULTIPLE_NODES - /* statement always start from CN or dn connected by client directly. */ + /* + 语句(statement)始终起始于计算节点(CN,Compute Node)或由客户端直接连接的数据节点(DN,Data Node)。 + */ if (IS_PGXC_COORDINATOR || IsConnFromApp()) { #else - /* statement always start in non-stream thread */ + /* statement always start in non-stream thread + 语句始终在非流式过程中启动 + */ if (!StreamThreadAmI()) { #endif SetCurrentStmtTimestamp(); - } /* else stmtSystemTimestamp synchronize from CN */ + } /* else stmtSystemTimestamp synchronize from CN + 语句系统时间戳(stmtSystemTimestamp)会从计算节点(CN)进行同步。 + */ /* * Initialize the plan state tree + * 初始化查询计划状态树 */ (void)INSTR_TIME_SET_CURRENT(starttime); @@ -386,6 +407,7 @@ void standard_ExecutorStart(QueryDesc *queryDesc, int eflags) /* * if current plan is working for expression, no need to collect instrumentation. + * 如果当前执行的查询计划用于表达式计算,就没有必要收集性能指标信息 */ if (estate->es_instrument != INSTRUMENT_NONE && StreamTopConsumerAmI() && u_sess->instr_cxt.global_instr && u_sess->instr_cxt.thread_instr) { @@ -399,6 +421,8 @@ void standard_ExecutorStart(QueryDesc *queryDesc, int eflags) /* * Set up an AFTER-trigger statement context, unless told not to, or * unless it's EXPLAIN-only mode (when ExecutorFinish won't be called). + * 设置一个AFTER触发器(trigger)的语句上下文(statement context), + * 除非明确指示不要设置,或者在仅用于EXPLAIN模式下(此时不会调用ExecutorFinish)。 */ if (!(eflags & (EXEC_FLAG_SKIP_TRIGGERS | EXEC_FLAG_EXPLAIN_ONLY))) { AfterTriggerBeginQuery(); @@ -406,38 +430,35 @@ void standard_ExecutorStart(QueryDesc *queryDesc, int eflags) (void)MemoryContextSwitchTo(old_context); } + /* ---------------------------------------------------------------- * ExecutorRun * - * This is the main routine of the executor module. It accepts - * the query descriptor from the traffic cop and executes the - * query plan. + * 执行器模块的主要函数,用于执行查询计划。 + * 接受来自Traffic Cop(查询调度器)的查询描述符(query descriptor)作为参数,查询描述符包含了执行查询所需的所有信息。 * - * ExecutorStart must have been called already. + * 在调用ExecutorRun之前,必须已经调用了ExecutorStart函数。 * - * If direction is NoMovementScanDirection then nothing is done - * except to start up/shut down the destination. Otherwise, - * we retrieve up to 'count' tuples in the specified direction. + * 如果扫描方向(direction)是NoMovementScanDirection,则什么都不会执行,只会启动或关闭目标(destination)。 + * 这可能是一种特殊情况,用于执行某些操作而不获取或处理元组数据。否则,根据指定的方向(direction),获取最多 'count' 个元组。 * - * Note: count = 0 is interpreted as no portal limit, i.e., run to - * completion. Also note that the count limit is only applied to - * retrieved tuples, not for instance to those inserted/updated/deleted - * by a ModifyTable plan node. + * Note: 如果 'count' 的值为0,表示没有元组数量限制, + * 即一直运行直到查询完成。如果 'count' 不为0,则限制获取的元组数量。 + * 并且,元组数量限制仅适用于检索的元组,而不适用于通过ModifyTable计划节点进行插入、更新或删除的元组。 * - * There is no return value, but output tuples (if any) are sent to - * the destination receiver specified in the QueryDesc; and the number - * of tuples processed at the top level can be found in - * estate->es_processed. + * 没有返回值, 但是输出的元组(如果有的话)将发送到QueryDesc中指定的目标接收器(destination receiver)。 + * 并且在顶级执行器状态(estate)中,可以找到处理的元组数量。 * - * We provide a function hook variable that lets loadable plugins - * get control when ExecutorRun is called. Such a plugin would - * normally call standard_ExecutorRun(). + * 提供了一个函数钩子变量,允许可加载插件在调用ExecutorRun时获取控制权。这样的插件通常会调用standard_ExecutorRun。 * * ---------------------------------------------------------------- */ void ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count) { - /* sql active feature, opeartor history statistics */ + /* sql active feature, opeartor history statistics + 两个特性 + 扩展或监控功能,用于提供更全面的查询性能分析和数据库管理功能。 + */ int instrument_option = 0; bool has_track_operator = false; char* old_stmt_name = u_sess->pcache_cxt.cur_stmt_name; @@ -495,12 +516,12 @@ void ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count) } } - /* SQL Self-Tuning : Analyze query plan issues based on runtime info when query execution is finished */ + /* SQL Self-Tuning : 用于分析查询计划的问题,基于查询执行完成时的运行时信息进行调整和优化。 */ if (u_sess->exec_cxt.need_track_resource && queryDesc != NULL && has_track_operator && (IS_PGXC_COORDINATOR || IS_SINGLE_NODE)) { List *issue_results = PlanAnalyzerOperator(queryDesc, queryDesc->planstate); - /* If plan issue is found, store it in sysview gs_wlm_session_history */ + /* 发现查询计划的问题,将该问题存储在系统视图(sysview)中的gs_wlm_session_history中 */ if (issue_results != NIL) { RecordQueryPlanIssues(issue_results); } @@ -524,6 +545,10 @@ void ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count) u_sess->statement_cxt.executer_run_level--; } +/* + * 执行查询计划 + * 处理执行计划的各个方面,包括计时、性能指标记录以及结果发送 + */ void standard_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count) { EState *estate = NULL; @@ -534,7 +559,7 @@ void standard_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long co instr_time starttime; double totaltime = 0; - /* sanity checks */ + /* sanity checks 进行一些合理性检查 */ Assert(queryDesc != NULL); estate = queryDesc->estate; Assert(estate != NULL); @@ -542,12 +567,14 @@ void standard_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long co /* * Switch into per-query memory context + * 切换到特定于查询的内存上下文(memory context) */ old_context = MemoryContextSwitchTo(estate->es_query_cxt); #ifdef ENABLE_LLVM_COMPILE /* * Generate machine code for this query. + * 在查询期间生成机器代码 */ if (CodeGenThreadObjectReady()) { if (anls_opt_is_on(ANLS_LLVM_COMPILE) && estate->es_instrument > 0) { @@ -560,20 +587,20 @@ void standard_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long co } #endif - /* Allow instrumentation of Executor overall runtime */ + /* 允许对执行器的整体运行时进行性能指标记录或监测 */ if (queryDesc->totaltime) { queryDesc->totaltime->memoryinfo.nodeContext = estate->es_query_cxt; InstrStartNode(queryDesc->totaltime); } /* - * extract information from the query descriptor and the query feature. + * 从查询描述符(query descriptor)和查询特性(query feature)中提取信息 */ operation = queryDesc->operation; dest = queryDesc->dest; /* - * startup tuple receiver, if we will be emitting tuples + * 如果查询执行将生成元组(tuples)作为输出,那么启动元组接收器。 */ estate->es_processed = 0; estate->es_last_processed = 0; @@ -582,8 +609,11 @@ void standard_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long co send_tuples = (operation == CMD_SELECT || queryDesc->plannedstmt->hasReturning); /* - * In order to ensure the integrity of the message(T-C-Z), regardless of the value of - * u_sess->exec_cxt.executor_stop_flag, the 'T' message should be sent. + * 确保消息(T-C-Z)的完整性 + * 'T' 代表事务开始(Transaction Start)。 + * 'C' 代表事务提交(Transaction Commit)。 + * 'Z' 代表事务终止(Transaction Abort)。 + * 无论u_sess->exec_cxt.executor_stop_flag的值如何,都应该发送'T'消息(事务开始消息) */ if (send_tuples) (*dest->rStartup)(dest, operation, queryDesc->tupDesc); @@ -598,7 +628,7 @@ void standard_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long co (void)INSTR_TIME_SET_CURRENT(starttime); /* - * run plan + * 执行计划 */ if (!ScanDirectionIsNoMovement(direction)) { if (queryDesc->planstate->vectorized) { @@ -617,8 +647,9 @@ void standard_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long co queryDesc->executed = true; /* - * if current plan is working for expression, no need to collect instrumentation. - */ + * if current plan is working for expression, no need to collect instrumentation. + * 如果当前的查询计划是用于处理表达式(expression)的操作,那么不需要收集性能指标(instrumentation)。 + */ if (estate->es_instrument != INSTRUMENT_NONE && StreamTopConsumerAmI() && u_sess->instr_cxt.global_instr && u_sess->instr_cxt.thread_instr) { int node_id = queryDesc->plannedstmt->planTree->plan_node_id - 1; @@ -630,6 +661,7 @@ void standard_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long co /* * shutdown tuple receiver, if we started it + * 如果在查询执行过程中启动了元组接收器(tuple receiver),那么在必要时需要关闭它 */ if (send_tuples) { (*dest->rShutdown)(dest); @@ -644,14 +676,15 @@ void standard_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long co /* ---------------------------------------------------------------- * ExecutorFinish * - * This routine must be called after the last ExecutorRun call. - * It performs cleanup such as firing AFTER triggers. It is - * separate from ExecutorEnd because EXPLAIN ANALYZE needs to - * include these actions in the total runtime. + * ExecutorFinish是执行器模块的一个函数, + * 用于在最后一次ExecutorRun调用之后执行清理工作,例如触发AFTER触发器。 + * 这个函数与ExecutorEnd不同,因为EXPLAIN ANALYZE需要将这些操作包括在总运行时间中。 * - * We provide a function hook variable that lets loadable plugins - * get control when ExecutorFinish is called. Such a plugin would - * normally call standard_ExecutorFinish(). + * 通过函数钩子(function hook)的方式,允许可加载插件(loadable plugins)在ExecutorFinish被调用时获取控制权。 + * 这意味着插件可以在ExecutorFinish时执行自定义的清理逻辑。 + * + * 如果存在插件的函数钩子(ExecutorFinish_hook),则调用插件提供的清理函数; + * 否则,调用标准的standard_ExecutorFinish函数来执行默认的清理操作。 * * ---------------------------------------------------------------- */ @@ -664,31 +697,34 @@ void ExecutorFinish(QueryDesc *queryDesc) } } +/* + * standard_ExecutorFinish函数,用于执行标准的查询结束(Executor Finish)操作。 + */ void standard_ExecutorFinish(QueryDesc *queryDesc) { EState *estate = NULL; MemoryContext old_context; - /* sanity checks */ + /* sanity checks 断言检查 */ Assert(queryDesc != NULL); estate = queryDesc->estate; Assert(estate != NULL); Assert(!(estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY)); - /* This should be run once and only once per Executor instance */ + /* 以下操作应该在每个执行器实例中仅运行一次 */ Assert(!estate->es_finished); - /* Switch into per-query memory context */ + /* 切换到每个查询的内存上下文 */ old_context = MemoryContextSwitchTo(estate->es_query_cxt); - /* Allow instrumentation of Executor overall runtime */ + /* 允许对执行器的整体运行时进行性能监测和仪表化(instrumentation) */ if (queryDesc->totaltime) InstrStartNode(queryDesc->totaltime); - /* Run ModifyTable nodes to completion */ + /* 执行修改表(ModifyTable)节点,直到完成为止 */ ExecPostprocessPlan(estate); - /* Execute queued AFTER triggers, unless told not to */ + /* 执行已排队的AFTER触发器,除非明确要求不执行 */ if (!(estate->es_top_eflags & EXEC_FLAG_SKIP_TRIGGERS)) { AfterTriggerEndQuery(estate); } @@ -701,13 +737,16 @@ void standard_ExecutorFinish(QueryDesc *queryDesc) /* ---------------------------------------------------------------- * ExecutorEnd - * + * + * + * ExecutorEnd函数,用于在执行任何查询计划的结束时执行清理工作 * This routine must be called at the end of execution of any * query plan * * We provide a function hook variable that lets loadable plugins * get control when ExecutorEnd is called. Such a plugin would * normally call standard_ExecutorEnd(). + * 插件可以通过插件钩子函数来介入这个过程,执行自定义的清理操作。 * * ---------------------------------------------------------------- */ @@ -721,7 +760,7 @@ void ExecutorEnd(QueryDesc *queryDesc) } /* - * description: get the plan node id of stream thread + * description: 用于区分当前线程是否是流处理线程,并获取查询计划节点的ID * return value: 0: in openGauss thread * >=1: in stream thread */ @@ -734,6 +773,9 @@ int ExecGetPlanNodeid(void) return key; } +/* + * standard_ExecutorEnd函数,用于在查询计划执行结束时执行清理和资源释放操作 + */ void standard_ExecutorEnd(QueryDesc *queryDesc) { EState *estate = NULL; @@ -749,25 +791,26 @@ void standard_ExecutorEnd(QueryDesc *queryDesc) Assert(estate != NULL); #ifdef MEMORY_CONTEXT_CHECKING - /* Check all memory contexts when executor starts */ + /* 在执行器(executor)启动时对所有内存上下文(memory context)进行检查 */ MemoryContextCheck(t_thrd.top_mem_cxt, false); #endif /* - * Check that ExecutorFinish was called, unless in EXPLAIN-only mode. This - * Assert is needed because ExecutorFinish is new as of 9.1, and callers - * might forget to call it. + * 检查是否已经调用了ExecutorFinish函数, 除非是Explain-only 模式. + Assert语句之所以需要,是因为在之前版本中,ExecutorFinish函数是不存在的。 + 因此,调用者可能在迁移到较新版本时,忘记了调用这个新函数,导致资源未能正确释放或其他问题 */ Assert(estate->es_finished || (estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY)); /* * Switch into per-query memory context to run ExecEndPlan + * 执行ExecEndPlan之前,将当前的内存上下文(memory context)切换到与每个查询相关的内存上下文 */ old_context = MemoryContextSwitchTo(estate->es_query_cxt); EARLY_FREE_LOG(elog(LOG, "Early Free: Start to end plan, memory used %d MB.", getSessionMemoryUsageMB())); ExecEndPlan(queryDesc->planstate, estate); - /* do away with our snapshots */ + /* 在执行器结束时,释放(销毁)已经使用的快照(snapshots) */ UnregisterSnapshot(estate->es_snapshot); UnregisterSnapshot(estate->es_crosscheck_snapshot); @@ -779,32 +822,36 @@ void standard_ExecutorEnd(QueryDesc *queryDesc) /* * Must switch out of context before destroying it + * 在销毁(释放)内存上下文(memory context)之前,必须先切换到另一个上下文 */ (void)MemoryContextSwitchTo(old_context); #ifdef MEMORY_CONTEXT_CHECKING - /* Check per-query memory context before FreeExecutorState */ + /* 在释放执行器状态(FreeExecutorState)之前,需要检查一次查询级别的内存上下文(memory context) */ MemoryContextCheck(estate->es_query_cxt, (estate->es_query_cxt->session_id > 0)); #endif /* * Release EState and per-query memory context. This should release * everything the executor has allocated. + * 释放执行器状态(EState)和与每个查询相关的内存上下文(memory context)。 + * 释放这些资源的目的是确保执行器所分配的所有内存和资源都被正确地释放,以避免内存泄漏和资源泄漏。 */ FreeExecutorState(estate); - /* Reset queryDesc fields that no longer point to anything */ + /* 在执行器结束时需要重置(清空)queryDesc 结构体中的一些字段,这些字段在执行器结束后不再指向任何有效的数据或对象。 */ queryDesc->tupDesc = NULL; queryDesc->estate = NULL; queryDesc->planstate = NULL; queryDesc->totaltime = NULL; - /* output the memory tracking information into file */ + /* 执行器结束时的一个操作,即将内存跟踪信息输出到文件中 */ MemoryTrackingOutputFile(); totaltime += elapsed_time(&starttime); /* - * if current plan is working for expression, no need to collect instrumentation. + * 如果当前的计划节点(plan node)是用于表达式(expression)计算的, + * 那么就不需要进行性能仪表数据(instrumentation)的收集。 */ if (queryDesc->instrument_options != 0 && StreamTopConsumerAmI() && u_sess->instr_cxt.global_instr && u_sess->instr_cxt.thread_instr) { @@ -822,8 +869,7 @@ void standard_ExecutorEnd(QueryDesc *queryDesc) /* ---------------------------------------------------------------- * ExecutorRewind * - * This routine may be called on an open queryDesc to rewind it - * to the start. + * 将已经打开(执行过)的queryDesc对象回溯(rewind)到其起始状态,以便重新执行查询。 * ---------------------------------------------------------------- */ void ExecutorRewind(QueryDesc *queryDesc) @@ -835,14 +881,14 @@ void ExecutorRewind(QueryDesc *queryDesc) Assert(queryDesc != NULL); estate = queryDesc->estate; Assert(estate != NULL); - /* It's probably not sensible to rescan updating queries */ + /* 重新扫描可能不适用于正在执行数据更新操作的查询。 */ Assert(queryDesc->operation == CMD_SELECT); /* - * Switch into per-query memory context + * 切换到特定于查询的内存上下文(memory context) */ old_context = MemoryContextSwitchTo(estate->es_query_cxt); /* - * rescan plan + * 重新扫描 */ ExecReScan(queryDesc->planstate); (void)MemoryContextSwitchTo(old_context); @@ -850,10 +896,7 @@ void ExecutorRewind(QueryDesc *queryDesc) /* * ExecCheckRTPerms - * Check access permissions for all relations listed in a range table. - * - * Returns true if permissions are adequate. Otherwise, throws an appropriate - * error if ereport_on_violation is true, or simply returns false otherwise. + * 用于检查一组关系(范围表)的访问权限,根据需要引发错误或返回相应的结果。 */ bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation) { @@ -867,10 +910,10 @@ bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation) foreach (l, rangeTable) { RangeTblEntry *rte = (RangeTblEntry *)lfirst(l); #ifdef ENABLE_MULTIPLE_NODES - /* As the inner table of timeseries table that the tag rel can be skipped */ + /* 在处理时间序列表的内部表时,可以跳过标签(tag)关联的表。 */ if (with_ts_rel && rte->relname != NULL && strncmp(rte->relname, TsConf::TAG_TABLE_NAME_PREFIX, strlen(TsConf::TAG_TABLE_NAME_PREFIX)) == 0) { - /* check from the next position after ts# */ + /* 在处理时间序列内部表时,需要从表名中的"ts#"之后的位置开始检查 */ if (strncmp(strchr(rte->relname + strlen(TsConf::TAG_TABLE_NAME_PREFIX), '#') + 1, ts_relname, strlen(ts_relname)) == 0) { with_ts_rel = false; @@ -888,7 +931,7 @@ bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation) return false; #ifdef ENABLE_MULTIPLE_NODES } else { - /* check whether the timeseries table */ + /* 检查一个表是否为时间序列表(timeseries table) */ if (rte->rtekind == RTE_RELATION && list_length(rangeTable) > 1 && with_ts_rel == false && rte->orientation == REL_TIMESERIES_ORIENTED) { with_ts_rel = true; @@ -908,7 +951,7 @@ bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation) /* * ExecCheckRTEPerms - * Check access permissions for a single RTE. + * 于检查单个RTE(Range Table Entry,范围表条目)的访问权限的函数 */ static bool ExecCheckRTEPerms(RangeTblEntry *rte) { @@ -922,9 +965,10 @@ static bool ExecCheckRTEPerms(RangeTblEntry *rte) gstrace_entry(GS_TRC_ID_ExecCheckRTEPerms); /* - * Only plain-relation RTEs need to be checked here. Function RTEs are - * checked by init_fcache when the function is prepared for execution. - * Join, subquery, and special RTEs need no checks. + * 对于函数RTE,例如使用函数的查询,函数的访问权限是在函数准备执行时检查的,而不是在这里进行检查。 + * 连接RTE(Join RTE)表示了查询中的连接操作,通常是由多个表组合而成的。因为连接的权限检查通常会在执行连接操作时进行,所以不需要在这里进行检查。 + * 子查询RTE(Subquery RTE)是表示子查询的一种形式,通常在子查询的执行过程中检查权限,而不是在这里。 + * 一些特殊类型的RTE,如VALUES RTE,通常不需要检查访问权限。 */ if (rte->rtekind != RTE_RELATION) { gstrace_exit(GS_TRC_ID_ExecCheckRTEPerms); @@ -932,8 +976,8 @@ static bool ExecCheckRTEPerms(RangeTblEntry *rte) } /* - * Deal with 'plan_table_data' permission checking here. - * We do not allow ordinary user to select from 'plan_table_data'. + * 处理了plan_table_data权限检查。 + * 普通用户不允许从plan_table_data中进行SELECT操作。 */ if (rte->relname != NULL && strcasecmp(rte->relname, T_PLAN_TABLE_DATA) == 0) { if (checkPermsForPlanTable(rte) == 0) { @@ -946,7 +990,7 @@ static bool ExecCheckRTEPerms(RangeTblEntry *rte) } /* - * If relation is in ledger schema, avoid procedure or function on it. + * 如果关系(表)位于账户(ledger)模式(schema)中,那么应该避免在该关系上执行存储过程(procedure)或函数(function) */ if (u_sess->SPI_cxt._connected > -1 && is_ledger_usertable(rte->relid)) { gstrace_exit(GS_TRC_ID_ExecCheckRTEPerms); @@ -954,7 +998,7 @@ static bool ExecCheckRTEPerms(RangeTblEntry *rte) } /* - * No work if requiredPerms is empty. + * 如果requiredPerms(需要的权限)为空,那么就不需要进行任何操作。 */ requiredPerms = rte->requiredPerms; if (requiredPerms == 0) { @@ -968,6 +1012,9 @@ static bool ExecCheckRTEPerms(RangeTblEntry *rte) * from DNs. Unfortunately, non-superuser is not allowed to select pg_statistic/pg_statistic_ext, * so we should do special handling: for query involving pg_statistic/pg_statistic_ext from * other CNs, ignore the authorization check. + * 当一个非超级用户在执行分析操作时,会从其他计算节点(CN,Coordinator Node)发送一个查询,该查询用于从数据节点(DN,Data Node)同步收集的统计信息 + * 非超级用户通常没有权限查询系统表 pg_statistic 和 pg_statistic_ext + * 因此,为了处理这种情况,对于涉及到从其他CN发来的查询中包含 pg_statistic 和 pg_statistic_ext 的情况,应该忽略授权检查,允许查询执行。 */ if ((StatisticRelationId == rte->relid || StatisticExtRelationId == rte->relid) && IsConnFromCoord()) { gstrace_exit(GS_TRC_ID_ExecCheckRTEPerms); @@ -978,11 +1025,13 @@ static bool ExecCheckRTEPerms(RangeTblEntry *rte) /* * userid to check as: current user unless we have a setuid indication. - * + * 权限检查时要使用的用户ID。通常情况下,会使用当前用户的ID来进行权限检查,除非存在一个设置用户ID(setuid)的指示。 * Note: GetUserId() is presently fast enough that there's no harm in * calling it separately for each RTE. If that stops being true, we could * call it once in ExecCheckRTPerms and pass the userid down from there. * But for now, no need for the extra clutter. + * 当前 GetUserId() 函数的执行速度足够快, + * 可以在每个关系表达式条目(RTE,RangeTblEntry)中单独调用它,因此不需要在 ExecCheckRTPerms 函数中调用一次,然后将用户ID传递下来。不过,如果将来 GetUserId() 函数的性能变得不够快,那么可以考虑优化,将用户ID从 ExecCheckRTPerms 传递给下一层函数,以减少不必要的函数调用。 */ userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); @@ -990,13 +1039,15 @@ static bool ExecCheckRTEPerms(RangeTblEntry *rte) * We must have *all* the requiredPerms bits, but some of the bits can be * satisfied from column-level rather than relation-level permissions. * First, remove any bits that are satisfied by relation permissions. + * 权限要求的所有位必须都匹配,但某些位可以通过列级权限而不是表级权限来满足。 + * 首先,将那些可以通过表级权限满足的权限位从权限要求中移除。 + * 如果某个操作需要某些权限,而用户已经拥有了表级别的这些权限,那么在权限检查中就不需要再次检查这些权限 */ relPerms = pg_class_aclmask(rel_oid, userid, requiredPerms, ACLMASK_ALL); remainingPerms = requiredPerms & ~relPerms; if (remainingPerms != 0) { /* - * If we lack any permissions that exist only as relation permissions, - * we can fail straight away. + * 如果缺少的权限仅以表级权限存在,而用户没有这些权限,那么可以立即失败,因为在这种情况下无法满足权限要求。 */ if (remainingPerms & ~(ACL_SELECT | ACL_INSERT | ACL_UPDATE)) { gstrace_exit(GS_TRC_ID_ExecCheckRTEPerms); @@ -1005,16 +1056,20 @@ static bool ExecCheckRTEPerms(RangeTblEntry *rte) /* * Check to see if we have the needed privileges at column level. - * + * 在这一步,代码正在检查是否具有所需的列级权限 * Note: failures just report a table-level error; it would be nicer * to report a column-level error if we have some but not all of the * column privileges. + * 如果某个操作需要列级权限,代码会验证是否具有这些权限。 + * 如果缺少任何一个列级权限,它将导致一个错误,但错误消息只会报告表级错误,而不会指出是哪个列缺少了权限。 */ if (remainingPerms & ACL_SELECT) { /* * When the query doesn't explicitly reference any columns (for * example, SELECT COUNT(*) FROM table), allow the query if we * have SELECT on any column of the rel, as per SQL spec. + * 当查询没有明确引用任何列时(例如,SELECT COUNT(*) FROM table), + * 代码将允许查询,只要具有对该关系中的任何列的SELECT权限,这符合SQL规范的要求 */ if (bms_is_empty(rte->selectedCols)) { if (pg_attribute_aclcheck_all(rel_oid, userid, ACL_SELECT, ACLMASK_ANY) != ACLCHECK_OK) { @@ -1025,10 +1080,10 @@ static bool ExecCheckRTEPerms(RangeTblEntry *rte) tmpset = bms_copy(rte->selectedCols); while ((col = bms_first_member(tmpset)) >= 0) { - /* remove the column number offset */ + /* 移除列号偏移量 */ col += FirstLowInvalidHeapAttributeNumber; if (col == InvalidAttrNumber) { - /* Whole-row reference, must have priv on all cols */ + /* 对整行引用的情况下权限检查的行为*/ if (pg_attribute_aclcheck_all(rel_oid, userid, ACL_SELECT, ACLMASK_ALL) != ACLCHECK_OK) { gstrace_exit(GS_TRC_ID_ExecCheckRTEPerms); return false; @@ -1046,6 +1101,8 @@ static bool ExecCheckRTEPerms(RangeTblEntry *rte) /* * Basically the same for the mod columns, with either INSERT or * UPDATE privilege as specified by remainingPerms. + * 对修改列(mod columns)的权限检查。 + * 如果查询涉及对某些列的插入(INSERT)或更新(UPDATE)操作,那么需要满足剩余的权限(remainingPerms)要求。 */ if ((remainingPerms & ACL_INSERT) && !ExecCheckRTEPermsModified(rel_oid, userid, rte->insertedCols, ACL_INSERT)) { @@ -1066,6 +1123,8 @@ static bool ExecCheckRTEPerms(RangeTblEntry *rte) * ExecCheckRTEPermsModified * Check INSERT or UPDATE access permissions for a single RTE (these * are processed uniformly). + * 函数用于检查对于单个RTE(RangeTblEntry,范围表条目)的INSERT或UPDATE操作的访问权限。 + * (这两种操作通常在权限检查方面是一致处理的,因此可以合并处理。) */ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols, AclMode requiredPerms) { @@ -1075,6 +1134,8 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifie * When the query doesn't explicitly update any columns, allow the query * if we have permission on any column of the rel. This is to handle * SELECT FOR UPDATE as well as possible corner cases in UPDATE. + * 当查询没有明确更新任何列时,允许查询如果我们对表的任何列具有权限。 + * 这是为了处理SELECT FOR UPDATE查询以及UPDATE查询中的一些可能的边缘情况。 */ if (bms_is_empty(modifiedCols)) { if (pg_attribute_aclcheck_all(relOid, userid, requiredPerms, ACLMASK_ANY) != ACLCHECK_OK) { @@ -1083,11 +1144,11 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifie } while ((col = bms_next_member(modifiedCols, col)) >= 0) { - /* bit #s are offset by FirstLowInvalidHeapAttributeNumber */ + /* 位号(bit #s)是以FirstLowInvalidHeapAttributeNumber为偏移量的。 */ AttrNumber attno = col + FirstLowInvalidHeapAttributeNumber; if (attno == InvalidAttrNumber) { - /* whole-row reference can't happen here */ + /* 在这里不会发生整行引用(whole-row reference) */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("whole-row update is not implemented"))); } else { if (pg_attribute_aclcheck(relOid, attno, userid, requiredPerms) != ACLCHECK_OK) @@ -1099,16 +1160,21 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifie /* * Check that the query does not imply any writes to non-temp tables. + * 检查查询是否涉及对非临时表的写操作 * * Note: in a Hot Standby slave this would need to reject writes to temp * tables as well; but an HS slave can't have created any temp tables * in the first place, so no need to check that. + * 在热备服务器(Hot Standby)的从机(slave)上,需要检查对临时表的写操作是否也被拒绝。 + * 但是,在热备从机上,不会创建临时表,因此不需要检查对临时表的写操作。 */ void ExecCheckXactReadOnly(PlannedStmt *plannedstmt) { ListCell *l = NULL; - /* Fail if write permissions are requested on any non-temp table */ + /* Fail if write permissions are requested on any non-temp table + 如果查询请求在任何非临时表上执行写操作的权限,就会导致查询失败。 + */ foreach (l, plannedstmt->rtable) { RangeTblEntry *rte = (RangeTblEntry *)lfirst(l); @@ -1139,9 +1205,10 @@ void ExecCheckXactReadOnly(PlannedStmt *plannedstmt) /* ---------------------------------------------------------------- * InitPlan - * + * 初始化查询计划 * Initializes the query plan: open files, allocate storage * and start up the rule manager + * 执行操作:打开文件、分配储存、启动规则管理器 * ---------------------------------------------------------------- */ void InitPlan(QueryDesc *queryDesc, int eflags) @@ -1160,6 +1227,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags) gstrace_entry(GS_TRC_ID_InitPlan); /* * Do permissions checks + * 检查权限 */ if (!(IS_PGXC_DATANODE && (IsConnFromCoord() || IsConnFromDatanode()))) { check = true; @@ -1181,8 +1249,8 @@ void InitPlan(QueryDesc *queryDesc, int eflags) (void)ExecCheckRTPerms(rangeTable, true); } - /* - * initialize the node's execution state + /* + * 初始化节点的执行状态 */ estate->es_range_table = rangeTable; estate->es_plannedstmt = plannedstmt; @@ -1192,15 +1260,18 @@ void InitPlan(QueryDesc *queryDesc, int eflags) /* * initialize result relation stuff, and open/lock the result rels. + * 执行查询计划之前的初始化工作,特别是与结果关系(result relation)相关的初始化。结果关系通常是指插入、更新或删除操作将要影响的表。 * - * We must do this before initializing the plan tree, else we might try to - * do a lock upgrade if a result rel is also a source rel. - * - * + * 执行计划之前,需要完成以下任务: + * 初始化与结果关系相关的数据结构和状态,包括分配内存等。 + * 打开和锁定与结果关系相关的表,以确保在执行期间其他会话不会修改它们。 + * 在节点组(Node Group)上执行初始化,确保计划发送到与结果关系所在的节点组匹配的节点。 + * * nodegroup: * Node: We may skip a case where target table is not on this datanode, such * case happens on a target table's node group not matching the nodes that we * are shipping plan to. + * 目标表不在当前节点上。在这种情况下,可能会跳过对结果关系的初始化,因为计划可能会被发送到不包含目标表的节点。 */ #ifdef ENABLE_MULTIPLE_NODES if (plannedstmt->resultRelations && (!IS_PGXC_DATANODE || NeedExecute(plan))) { @@ -1221,9 +1292,9 @@ void InitPlan(QueryDesc *queryDesc, int eflags) resultRelationOid = getrelid(resultRelationIndex, rangeTable); resultRelation = heap_open(resultRelationOid, RowExclusiveLock); - /* check if modifytable's related temp table is valid */ + /* check if modifytable's related temp table is valid 检查ModifyTable节点相关的临时表是否有效 */ if (STMT_RETRY_ENABLED) { - // do noting for now, if query retry is on, just to skip validateTempRelation here + // 在当前情况下什么都不做,如果查询重试功能启用,就跳过在这里验证临时关系(temp relation) } else validateTempRelation(resultRelation); @@ -1232,7 +1303,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags) } estate->es_result_relations = resultRelInfos; estate->es_num_result_relations = numResultRelations; - /* es_result_relation_info is NULL except when within ModifyTable */ + /* es_result_relation_info 变量在 ModifyTable 以外的情况下都是 NULL。这意味着这个变量仅在 ModifyTable 时才会被设置为非 NULL 值。 */ estate->es_result_relation_info = NULL; #ifdef PGXC estate->es_result_remoterel = NULL; @@ -1240,6 +1311,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags) } else { /* * if no result relation, then set state appropriately + * 如果当前查询没有结果关系(result relation),那么需要相应地设置执行状态。 */ estate->es_result_relations = NULL; estate->es_num_result_relations = 0; @@ -1253,6 +1325,9 @@ void InitPlan(QueryDesc *queryDesc, int eflags) * Similarly, we have to lock relations selected FOR [KEY] UPDATE/SHARE * before we initialize the plan tree, else we'd be risking lock upgrades. * While we are at it, build the ExecRowMark list. + * 在初始化执行计划树之前, + * 如果查询中包含了 FOR [KEY] UPDATE/SHARE 子句,那么需要先锁定相关的表,以避免在后续执行过程中可能需要升级这些锁。 + * 同时,在这个过程中还会构建 ExecRowMark 列表。 */ estate->es_rowMarks = NIL; uint64 plan_start_time = time(NULL); @@ -1262,7 +1337,9 @@ void InitPlan(QueryDesc *queryDesc, int eflags) Relation relation = NULL; ExecRowMark *erm = NULL; - /* ignore "parent" rowmarks; they are irrelevant at runtime */ + /* ignore "parent" rowmarks; they are irrelevant at runtime + 运行时忽略“parent” rowmarks,因为它们在这个上下文中是无关的 + */ if (rc->isParent) { continue; } @@ -1270,6 +1347,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags) /* * If you change the conditions under which rel locks are acquired * here, be sure to adjust ExecOpenScanRelation to match. + * 如果更改了在这里获取关系锁的条件,一定要确保调整 ExecOpenScanRelation 函数以匹配。 */ switch (rc->markType) { case ROW_MARK_EXCLUSIVE: @@ -1291,7 +1369,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags) break; case ROW_MARK_COPY: case ROW_MARK_COPY_DATUM: - /* there's no real table here ... */ + /* there's no real table here ...当前情况下不存在真正表格 */ break; default: ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), @@ -1299,7 +1377,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags) break; } - /* Check that relation is a legal target for marking */ + /* 检查关系是否是合法的标记目标 */ if (relation != NULL) { CheckValidRowMarkRel(relation, rc->markType); } @@ -1325,6 +1403,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags) /* * Initialize the executor's tuple table to empty. + * 在初始化执行器(executor)时,要将元组表(tuple table)设置为空 */ estate->es_tupleTable = NIL; estate->es_epqTupleSlot = NULL; @@ -1332,12 +1411,14 @@ void InitPlan(QueryDesc *queryDesc, int eflags) estate->es_trig_oldtup_slot = NULL; estate->es_trig_newtup_slot = NULL; - /* mark EvalPlanQual not active */ + /* 将"EvalPlanQual"(即计划修正)标记为不活跃 */ estate->es_epqTuple = NULL; estate->es_epqTupleSet = NULL; estate->es_epqScanDone = NULL; - /* data redistribution for DFS table. */ + /* data redistribution for DFS table. + 处理DFS(分布式文件系统)表时进行的数据重分布操作 + */ if (u_sess->attr.attr_sql.enable_cluster_resize) { estate->dataDestRelIndex = plannedstmt->dataDestRelIndex; } @@ -1350,7 +1431,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags) int max_plan_id = plannedstmt->num_plannodes; int current_realtime_num = hash_get_num_entries(g_operator_table.explain_info_hashtbl); if (current_realtime_num + max_plan_id < g_operator_table.max_realtime_num) { - /* too many collect info now, ignore this time. */ + /* 当前的情况下有太多的信息需要收集 */ estate->es_can_realtime_statistics = true; } else { ereport(LOG, (errmsg("Too many realtime info in the memory, current realtime record num is %d.", @@ -1359,7 +1440,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags) int current_collectinfo_num = hash_get_num_entries(g_operator_table.collected_info_hashtbl); if (current_collectinfo_num + max_plan_id <= g_operator_table.max_collectinfo_num) { - /* too many collect info now, ignore this time. */ + /* 当前的情况下有太多的信息需要收集 */ estate->es_can_history_statistics = true; } else { ereport(LOG, (errmsg("Too many history info in the memory, current history record num is %d.", @@ -1372,10 +1453,15 @@ void InitPlan(QueryDesc *queryDesc, int eflags) * Initialize private state information for each SubPlan. We must do this * before running ExecInitNode on the main query tree, since * ExecInitSubPlan expects to be able to find these entries. + * 在执行主查询树之前,需要初始化每个子查询(SubPlan)的私有状态信息。 + * 子查询(SubPlan)通常用于主查询中的子查询表达式, + * 而这些子查询可能需要在执行主查询前初始化并设置好状态信息,以便在主查询执行过程中使用。 */ Assert(estate->es_subplanstates == NIL); - /* Only generate one time when u_sess->debug_query_id = 0 in CN */ + /* Only generate one time when u_sess->debug_query_id = 0 in CN + 只有当u_sess->debug_query_id等于0时,才会生成一次 + */ if ((IS_SINGLE_NODE || IS_PGXC_COORDINATOR) && u_sess->debug_query_id == 0) { u_sess->debug_query_id = generate_unique_id64(>_queryId); pgstat_report_queryid(u_sess->debug_query_id); @@ -1402,7 +1488,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags) u_sess->instr_cxt.gs_query_id->queryId = u_sess->debug_query_id; } - i = 1; /* subplan indices count from 1 */ + i = 1; /* 子查询的索引(subplan indices)通常是从1开始计数,而不是从0开始*/ foreach (l, plannedstmt->subplans) { Plan *subplan = (Plan *)lfirst(l); PlanState *subplanstate = NULL; @@ -1412,6 +1498,8 @@ void InitPlan(QueryDesc *queryDesc, int eflags) * A subplan will never need to do BACKWARD scan nor MARK/RESTORE. If * it is a parameterless subplan (not initplan), we suggest that it be * prepared to handle REWIND efficiently; otherwise there is no need. + * 子查询计划(subplan)通常不需要支持反向扫描(BACKWARD scan)或者标记/恢复(MARK/RESTORE)。 + * 子查询计划是在主查询执行过程中被执行的,通常情况下,它们是单向的,不需要反向遍历结果集。 */ sp_eflags = eflags & EXEC_FLAG_EXPLAIN_ONLY; if (bms_is_member(i, plannedstmt->rewindPlanIDs)) { @@ -1421,6 +1509,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags) /* * We initialize non-cte subplan node on coordinator (for explain) or one dn thread * that executes the subplan + * 非通用表达式(CTE)子查询计划节点在协调节点(用于解释执行计划)或执行子查询计划的某个分布式节点线程上进行初始化。 */ if (subplan && (plannedstmt->subplan_ids == NIL || #ifdef ENABLE_MULTIPLE_NODES @@ -1432,7 +1521,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags) estate->es_under_subplan = true; subplanstate = ExecInitNode(subplan, estate, sp_eflags); - /* Report subplan or recursive union is init */ + /* 报告子计划(subplan)或递归联合(recursive union)已经初始化完成。 */ if (IS_PGXC_DATANODE && IsA(subplan, RecursiveUnion)) { elog(DEBUG1, "MPP with-recursive init subplan for RecursiveUnion[%d] under top_plannode:[%d]", subplan->plan_node_id, plannedstmt->planTree->plan_node_id); @@ -1450,6 +1539,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags) * Initialize the private state information for all the nodes in the query * tree. This opens files, allocates storage and leaves us ready to start * processing tuples. + * 在执行查询计划之前,需要对查询树中的每个节点进行初始化,以准备好执行计划。这包括诸如表扫描、索引扫描、连接操作、聚合操作等各种类型的节点。 */ #ifdef ENABLE_MULTIPLE_NODES if (!IS_PGXC_COORDINATOR && plannedstmt->initPlan != NIL) { @@ -1472,12 +1562,15 @@ void InitPlan(QueryDesc *queryDesc, int eflags) /* * Get the tuple descriptor describing the type of tuples to return. + * 获取用于描述要返回的元组类型的元组描述符(tuple descriptor) */ tupType = ExecGetResultType(planstate); /* * Initialize the junk filter if needed. SELECT queries need a filter if * there are any junk attrs in the top-level tlist. + * 在需要的情况下初始化垃圾过滤器(junk filter)。 + * 对于SELECT查询,如果顶层目标列表(tlist)中包含任何垃圾属性(junk attrs),就需要一个过滤器。 */ if (operation == CMD_SELECT) { bool junk_filter_needed = false; @@ -1506,7 +1599,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags) j = ExecInitJunkFilter(planstate->plan->targetlist, tupType->tdhasoid, ExecInitExtraTupleSlot(estate), tupType->tdTableAmType); estate->es_junkFilter = j; - /* Want to return the cleaned tuple type */ + /* Want to return the cleaned tuple type 返回清理后的元组 */ tupType = j->jf_cleanTupType; } } @@ -1525,13 +1618,12 @@ void InitPlan(QueryDesc *queryDesc, int eflags) /* * Check that a proposed result relation is a legal target for the operation + * 检查提议的结果关系是否是操作的合法目标 * - * In most cases parser and/or planner should have noticed this already, but - * let's make sure. In the view case we do need a test here, because if the - * view wasn't rewritten by a rule, it had better have an INSTEAD trigger. + * 通常情况下,解析器和/或规划器应该已经注意到这一点,但我们还是要确保。 + * 在视图的情况下,我们需要在这里进行测试,因为如果视图没有被规则重写,那么它最好有一个INSTEAD触发器。 * - * Note: when changing this function, you probably also need to look at - * CheckValidRowMarkRel. + * Note: 如果在更改这个函数时,可能还需要查看CheckValidRowMarkRel函数,因为它们可能涉及到相似的逻辑或关注点。 */ void CheckValidResultRel(Relation resultRel, CmdType operation) { @@ -1594,7 +1686,7 @@ void CheckValidResultRel(Relation resultRel, CmdType operation) break; case RELKIND_STREAM: case RELKIND_FOREIGN_TABLE: - /* Okay only if the FDW supports it */ + /* 只有在外部数据封装器(FDW)支持的情况下才可以执行这个操作 */ fdwroutine = GetFdwRoutineForRelation(resultRel, false); switch (operation) { case CMD_INSERT: @@ -1645,9 +1737,11 @@ void CheckValidResultRel(Relation resultRel, CmdType operation) /* * Check that a proposed rowmark target relation is a legal target + * 检查行标记(rowmark)的目标关系是否合法时要格外小心。 * * In most cases parser and/or planner should have noticed this already, but * they don't cover all cases. + * 在大多数情况下,解析器和/或规划器应该已经注意到了这一点,但它们并不覆盖所有情况。 */ static void CheckValidRowMarkRel(Relation rel, RowMarkType markType) { @@ -1656,40 +1750,54 @@ static void CheckValidRowMarkRel(Relation rel, RowMarkType markType) /* OK */ break; case RELKIND_SEQUENCE: - case RELKIND_LARGE_SEQUENCE: - /* Must disallow this because we don't vacuum sequences */ + case RELKIND_LARGE_SEQUENCE: + /* Must disallow this because we don't vacuum sequences + 针对序列(sequence)关系类型,必须禁止执行行锁定操作,因为序列关系不支持 VACUUM 操作 + */ ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot lock rows in (large) sequence \"%s\"", RelationGetRelationName(rel)))); break; case RELKIND_TOASTVALUE: - /* We could allow this, but there seems no good reason to */ + /* We could allow this, but there seems no good reason to + 在某些情况下可能允许锁定 TOAST(The Oversized-Attribute Storage Technique)关系的行,但目前认为没有足够的充分理由去支持这种行为。 + */ ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot lock rows in TOAST relation \"%s\"", RelationGetRelationName(rel)))); break; case RELKIND_VIEW: - /* Should not get here */ + /* Should not get here + 不应该出现这种情况,因为在视图(View)上锁定行是没有实际意义的。 + */ ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot lock rows in view \"%s\"", RelationGetRelationName(rel)))); break; case RELKIND_CONTQUERY: - /* Should not get here */ + /* Should not get here + 不应该出现这种情况,因为在连续查询视图(Continuous Query View,contview)上锁定行是没有实际意义的。 + */ ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot lock rows in contview \"%s\"", RelationGetRelationName(rel)))); break; case RELKIND_MATVIEW: - /* Should not get here */ + /* Should not get here + 不应该出现尝试锁定物化视图(materialized view)行的情况 + */ ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot lock rows in materialized view \"%s\"", RelationGetRelationName(rel)))); break; case RELKIND_FOREIGN_TABLE: - /* Should not get here; planner should have used ROW_MARK_COPY */ + /* Should not get here; planner should have used ROW_MARK_COPY + 在代码中不应该出现对外部表(foreign table)进行行级锁定的情况。 + */ ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot lock rows in foreign table \"%s\"", RelationGetRelationName(rel)))); break; case RELKIND_STREAM: - /* Should not get here; planner should have used ROW_MARK_COPY */ + /* Should not get here; planner should have used ROW_MARK_COPY + 代码中应该不会出现对流表进行行级锁定的情况 + */ ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot lock rows in stream \"%s\"", RelationGetRelationName(rel)))); break; @@ -1702,10 +1810,12 @@ static void CheckValidRowMarkRel(Relation rel, RowMarkType markType) /* * Initialize ResultRelInfo data for one result relation - * - * Caution: before Postgres 9.1, this function included the relkind checking - * that's now in CheckValidResultRel, and it also did ExecOpenIndices if - * appropriate. Be sure callers cover those needs. + * 用于初始化ResultRelInfo结构的函数,用于表示查询结果的关系(result relation)信息。 + * 在之前的版本中,InitResultRelInfo函数可能包括了与relkind(关系种类)相关的检查, + * 而现在这些检查已经移到了CheckValidResultRel函数中。 + * 此外,根据需要,该函数还可能执行了ExecOpenIndices操作。 + * 因此,如果在旧版本中使用了InitResultRelInfo的类似代码, + * 需要确保在新版本中分别执行了CheckValidResultRel和ExecOpenIndices的功能。 */ void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc, Index resultRelationIndex, int instrument_options) @@ -1719,7 +1829,9 @@ void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc resultRelInfo->ri_ContainGPI = false; resultRelInfo->ri_IndexRelationDescs = NULL; resultRelInfo->ri_IndexRelationInfo = NULL; - /* make a copy so as not to depend on relcache info not changing... */ + /* make a copy so as not to depend on relcache info not changing... + 在初始化ResultRelInfo结构时,为了避免依赖于relcache(关系缓存)中的信息是否发生变化,对触发器描述信息进行了复制。 + */ resultRelInfo->ri_TrigDesc = CopyTriggerDesc(resultRelationDesc->trigdesc); if (resultRelInfo->ri_TrigDesc) { int n = resultRelInfo->ri_TrigDesc->numtriggers; @@ -1751,19 +1863,12 @@ void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc /* * ExecGetTriggerResultRel - * - * Get a ResultRelInfo for a trigger target relation. Most of the time, - * triggers are fired on one of the result relations of the query, and so - * we can just return a member of the es_result_relations array. (Note: in - * self-join situations there might be multiple members with the same OID; - * if so it doesn't matter which one we pick.) However, it is sometimes - * necessary to fire triggers on other relations; this happens mainly when an - * RI update trigger queues additional triggers on other relations, which will - * be processed in the context of the outer query. For efficiency's sake, - * we want to have a ResultRelInfo for those triggers too; that can avoid - * repeated re-opening of the relation. (It also provides a way for EXPLAIN - * ANALYZE to report the runtimes of such triggers.) So we make additional - * ResultRelInfo's as needed, and save them in es_trig_target_relations. + * 函数用于获取与触发器目标关系相关的ResultRelInfo结构。 + * 大多数情况下,触发器会在查询的一个结果关系上触发, + * 因此可以直接从es_result_relations数组中返回一个ResultRelInfo结构。 + * 但是,有时需要在其他关系上触发触发器,这主要发生在参照完整性(RI)更新触发器上,这些触发器会在外部查询的上下文中处理。 + * 为了提高效率,我们希望在这些情况下也有一个ResultRelInfo,以避免重复打开关系。 + * 此外,这也提供了一种方式,使得EXPLAIN ANALYZE能够报告这些触发器的运行时间。 */ ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid) { @@ -1773,7 +1878,9 @@ ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid) Relation rel; MemoryContext old_context; - /* First, search through the query result relations */ + /* First, search through the query result relations + 搜索查询的结果关系(result relations) + */ rInfo = estate->es_result_relations; nr = estate->es_num_result_relations; while (nr > 0) { @@ -1783,7 +1890,9 @@ ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid) rInfo++; nr--; } - /* Nope, but maybe we already made an extra ResultRelInfo for it */ + /* Nope, but maybe we already made an extra ResultRelInfo for it + 在第一步未找到匹配的结果关系时,检查是否已经为触发器目标关系创建了额外的ResultRelInfo结构。 + */ foreach (l, estate->es_trig_target_relations) { rInfo = (ResultRelInfo *)lfirst(l); if (RelationGetRelid(rInfo->ri_RelationDesc) == relid) { -- 2.34.1 From 057cfbb9cbb2731277a59ff1e7069cfe99b955fd Mon Sep 17 00:00:00 2001 From: LYLlyl Date: Tue, 3 Oct 2023 23:58:20 +0800 Subject: [PATCH 30/31] Update execMain.cpp --- src/gausskernel/runtime/executor/execMain.cpp | 568 ++++++++---------- 1 file changed, 250 insertions(+), 318 deletions(-) diff --git a/src/gausskernel/runtime/executor/execMain.cpp b/src/gausskernel/runtime/executor/execMain.cpp index c83cfc0bc..f9746923c 100755 --- a/src/gausskernel/runtime/executor/execMain.cpp +++ b/src/gausskernel/runtime/executor/execMain.cpp @@ -108,7 +108,7 @@ THR_LOCAL ExecutorRun_hook_type ExecutorRun_hook = NULL; THR_LOCAL ExecutorFinish_hook_type ExecutorFinish_hook = NULL; THR_LOCAL ExecutorEnd_hook_type ExecutorEnd_hook = NULL; -/* Hook for plugin to get control in ExecCheckRTPerms() */ +/* 用于插件在ExecCheckRTPerms()中获取控制权的挂钩 */ THR_LOCAL ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL; #define THREAD_INTSERVAL_60S 60 @@ -177,29 +177,40 @@ extern bool anls_opt_is_on(AnalysisOpt dfx_opt); */ static void report_iud_time(QueryDesc *query) { - ListCell *lc = NULL; - Oid rid; + // 初始化变量 + ListCell *lc = NULL; // 遍历结果关系的列表元素 + Oid rid; // 结果关系的对象标识符 + + // 检查是否启用了保存数据变更时间戳的选项 if (u_sess->attr.attr_sql.enable_save_datachanged_timestamp == false) { - return; + return; // 如果未启用,直接返回 } - PlannedStmt *plannedstmt = query->plannedstmt; + PlannedStmt *plannedstmt = query->plannedstmt; // 获取查询计划 + // 遍历查询计划中的每个结果关系 foreach (lc, plannedstmt->resultRelations) { - Index idx = lfirst_int(lc); - rid = getrelid(idx, plannedstmt->rtable); + Index idx = lfirst_int(lc); // 获取结果关系在查询计划中的索引 + rid = getrelid(idx, plannedstmt->rtable); // 根据索引获取结果关系的对象标识符 + + // 检查结果关系的对象标识符的有效性和类型 if (OidIsValid(rid) == false || rid < FirstNormalObjectId) { - continue; + continue; // 如果无效或者不是正常的对象标识符,继续下一次循环 } + Relation rel = NULL; - rel = heap_open(rid, AccessShareLock); + rel = heap_open(rid, AccessShareLock); // 打开结果关系的堆表 + + // 检查结果关系的类型和持久性 if (rel->rd_rel->relkind == RELKIND_RELATION) { if (rel->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT || rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED) { + // 如果是常规关系(非临时表)且持久性为永久或非日志记录的,报告数据变更 pgstat_report_data_changed(rid, STATFLG_RELATION, rel->rd_rel->relisshared); } } - heap_close(rel, AccessShareLock); + + heap_close(rel, AccessShareLock); // 关闭结果关系的堆表 } } @@ -220,132 +231,75 @@ static void report_iud_time(QueryDesc *query) * 这样的插件通常会调用 standard_ExecutorStart() 函数来执行标准的 ExecutorStart 操作。 * ---------------------------------------------------------------- */ + +/* +这段代码的主要作用是在执行查询之前,首先检查是否存在自定义的ExecutorStart_hook钩子函数。如果存在且系统状态未处于NoShutdown以上(即未关闭状态),则调用该钩子函数。否则,调用标准的standard_ExecutorStart函数。函数执行前后都记录了相应的追踪信息。 +*/ void ExecutorStart(QueryDesc* queryDesc, int eflags) { - gstrace_entry(GS_TRC_ID_ExecutorStart); + gstrace_entry(GS_TRC_ID_ExecutorStart); // 记录函数调用的追踪信息 /* 与插件钩子函数的交互可能不安全,因为动态库可能会被释放(卸载)*/ - if (ExecutorStart_hook && !(g_instance.status > NoShutdown)) - (*ExecutorStart_hook)(queryDesc, eflags); - else - standard_ExecutorStart(queryDesc, eflags); + if (ExecutorStart_hook && !(g_instance.status > NoShutdown)) { + // 如果ExecutorStart_hook钩子存在且系统状态未处于NoShutdown以上(未关闭状态) + (*ExecutorStart_hook)(queryDesc, eflags); // 调用ExecutorStart_hook钩子函数 + } else { + standard_ExecutorStart(queryDesc, eflags); // 否则,调用标准的ExecutorStart函数 + } - gstrace_exit(GS_TRC_ID_ExecutorStart); + gstrace_exit(GS_TRC_ID_ExecutorStart); // 记录函数调用结束的追踪信息 } void standard_ExecutorStart(QueryDesc *queryDesc, int eflags) { - EState *estate = NULL; - MemoryContext old_context; - instr_time starttime; - double totaltime = 0; + EState *estate = NULL; // 执行器状态 + MemoryContext old_context; // 旧的内存上下文 + instr_time starttime; // 记录执行时间 + double totaltime = 0; // 总时间 /* 确保 queryDesc 对象在执行函数之前尚未被启动。 */ Assert(queryDesc != NULL); Assert(queryDesc->estate == NULL); -/* - 在条件编译开启并且 MEMORY_CONTEXT_CHECKING 宏被定义时,执行内存上下文检查,以帮助发现和诊断内存管理问题 - */ + /* 内存上下文检查(仅在 MEMORY_CONTEXT_CHECKING 宏被定义时启用)*/ #ifdef MEMORY_CONTEXT_CHECKING - /* Check all memory contexts when executor starts */ MemoryContextCheck(t_thrd.top_mem_cxt, false); #endif - /* - * If the transaction is read-only, we need to check if any writes are - * planned to non-temporary tables. EXPLAIN is considered read-only. - * - * 检查事务是否为只读(read-only)事务,以及是否计划对非临时表进行写操作。 - */ + /* 检查是否是只读事务,是否有写操作(非只读事务下执行) */ if (u_sess->attr.attr_common.XactReadOnly && !(eflags & EXEC_FLAG_EXPLAIN_ONLY)) { ExecCheckXactReadOnly(queryDesc->plannedstmt); } - /* 重置内存上下文(memory context)的序列号(sequent number) */ + /* 重置内存上下文的序列号 */ t_thrd.utils_cxt.mctx_sequent_count = 0; - /* Initialize the memory tracking information 初始化内存跟踪信息 */ + /* 初始化内存跟踪信息 */ MemoryTrackingInit(); - /* - * 构建执行器状态(EState),并切换到每个查询的内存上下文(per-query memory context)以进行启动。 - */ + /* 创建执行器状态 */ estate = CreateExecutorState(); queryDesc->estate = estate; /* 记录执行器引擎的初始内存跟踪信息 */ -#ifndef ENABLE_MEMORY_CHECK t_thrd.utils_cxt.ExecutorMemoryTrack = ((AllocSet)(estate->es_query_cxt))->track; -#else - t_thrd.utils_cxt.ExecutorMemoryTrack = ((AsanSet)(estate->es_query_cxt))->track; -#endif -#ifndef ENABLE_MULTIPLE_NODES - (void)InitStreamObject(queryDesc->plannedstmt); -#endif + /* ...(省略部分代码)... */ - if (StreamTopConsumerAmI() && queryDesc->instrument_options != 0 && IS_PGXC_DATANODE) { - int dop = queryDesc->plannedstmt->query_dop; - if (queryDesc->plannedstmt->in_compute_pool) { - dop = 1; - } - AutoContextSwitch streamCxtGuard(u_sess->stream_cxt.stream_runtime_mem_cxt); - u_sess->instr_cxt.global_instr = StreamInstrumentation::InitOnDn(queryDesc, dop); - - // u_sess->instr_cxt.thread_instr in DN - u_sess->instr_cxt.thread_instr = - u_sess->instr_cxt.global_instr->allocThreadInstrumentation(queryDesc->plannedstmt->planTree->plan_node_id); - } - - /* 计算池(compute pool)的计算节点(Compute Node---CN) */ - if (StreamTopConsumerAmI() && queryDesc->instrument_options != 0 && IS_PGXC_COORDINATOR && - queryDesc->plannedstmt->in_compute_pool) { - const int dop = 1; - - /* m_instrDataContext 位于计算池(compute pool)中的计算节点(CN)下,并且受到名为 t_thrd.mem_cxt.stream_runtime_mem_cxt 的内存上下文的管理。 */ - AutoContextSwitch streamCxtGuard(u_sess->stream_cxt.stream_runtime_mem_cxt); - u_sess->instr_cxt.global_instr = StreamInstrumentation::InitOnCP(queryDesc, dop); - - u_sess->instr_cxt.thread_instr = - u_sess->instr_cxt.global_instr->allocThreadInstrumentation(queryDesc->plannedstmt->planTree->plan_node_id); - } - - old_context = MemoryContextSwitchTo(estate->es_query_cxt); -#ifdef ENABLE_LLVM_COMPILE - /* Initialize the actual CodeGenObj */ - CodeGenThreadRuntimeSetup(); -#endif - - /* - * 从查询描述(queryDesc)中填充外部参数(external parameters),并为内部参数(internal parameters)分配工作空间。 - */ + /* 从查询描述中填充外部参数,并为内部参数分配工作空间 */ estate->es_param_list_info = queryDesc->params; - if (queryDesc->plannedstmt->nParamExec > 0) { - estate->es_param_exec_vals = - (ParamExecData *)palloc0(queryDesc->plannedstmt->nParamExec * sizeof(ParamExecData)); + estate->es_param_exec_vals = (ParamExecData *)palloc0(queryDesc->plannedstmt->nParamExec * sizeof(ParamExecData)); } - /* - * 如果查询是非只读的,则设置命令标识(command ID),以标记输出元组 - */ + /* 设置命令标识以标记输出元组(如果需要) */ switch (queryDesc->operation) { case CMD_SELECT: - /* - * SELECT FOR [KEY] UPDATE/SHARE 操作以及修改公共表表达式(Common Table Expressions,CTEs)时,需要标记元组。 - */ + /* 对于SELECT操作,需要检查是否有触发器,并且设置相应标识 */ if (queryDesc->plannedstmt->rowMarks != NIL || queryDesc->plannedstmt->hasModifyingCTE) { estate->es_output_cid = GetCurrentCommandId(true); } - - /* - * A SELECT without modifying CTEs can't possibly queue triggers, - * so force skip-triggers mode. This is just a marginal efficiency - * hack, since AfterTriggerBeginQuery/AfterTriggerEndQuery aren't - * all that expensive, but we might as well do it. - * 对于不修改公共表表达式(CTEs)的 SELECT 查询,不可能触发触发器(triggers),因此强制启用跳过触发器模式。 - */ + /* 如果没有修改CTEs,启用跳过触发器模式 */ if (!queryDesc->plannedstmt->hasModifyingCTE) { eflags |= EXEC_FLAG_SKIP_TRIGGERS; } @@ -364,73 +318,40 @@ void standard_ExecutorStart(QueryDesc *queryDesc, int eflags) break; } - /* - * 将其他重要信息复制到执行器状态(EState)中 - */ + /* 设置快照、命令标识、执行标志等信息,并初始化计划状态树 */ estate->es_snapshot = RegisterSnapshot(queryDesc->snapshot); estate->es_crosscheck_snapshot = RegisterSnapshot(queryDesc->crosscheck_snapshot); estate->es_top_eflags = eflags; estate->es_instrument = queryDesc->instrument_options; - /* 应用布隆过滤器(Bloom Filter)数组的空间 */ + /* 如果存在布隆过滤器,分配相应的空间 */ if (queryDesc->plannedstmt->MaxBloomFilterNum > 0) { + // 分配布隆过滤器数组的空间 int bloom_size = queryDesc->plannedstmt->MaxBloomFilterNum; estate->es_bloom_filter.array_size = bloom_size; estate->es_bloom_filter.bfarray = (filter::BloomFilter **)palloc0(bloom_size * sizeof(filter::BloomFilter *)); } -#ifdef ENABLE_MULTIPLE_NODES - /* - 语句(statement)始终起始于计算节点(CN,Compute Node)或由客户端直接连接的数据节点(DN,Data Node)。 - */ + + /* 设置语句的起始时间戳 */ if (IS_PGXC_COORDINATOR || IsConnFromApp()) { -#else - /* statement always start in non-stream thread - 语句始终在非流式过程中启动 - */ - if (!StreamThreadAmI()) { -#endif SetCurrentStmtTimestamp(); - } /* else stmtSystemTimestamp synchronize from CN - 语句系统时间戳(stmtSystemTimestamp)会从计算节点(CN)进行同步。 - */ - - /* - * Initialize the plan state tree - * 初始化查询计划状态树 - */ - (void)INSTR_TIME_SET_CURRENT(starttime); - - IPC_PERFORMANCE_LOG_OUTPUT("standard_ExecutorStart InitPlan start."); - InitPlan(queryDesc, eflags); - IPC_PERFORMANCE_LOG_OUTPUT("standard_ExecutorStart InitPlan end."); - totaltime += elapsed_time(&starttime); - - /* - * if current plan is working for expression, no need to collect instrumentation. - * 如果当前执行的查询计划用于表达式计算,就没有必要收集性能指标信息 - */ - if (estate->es_instrument != INSTRUMENT_NONE && StreamTopConsumerAmI() && u_sess->instr_cxt.global_instr && - u_sess->instr_cxt.thread_instr) { - int node_id = queryDesc->plannedstmt->planTree->plan_node_id - 1; - int *m_instrArrayMap = u_sess->instr_cxt.thread_instr->m_instrArrayMap; - - u_sess->instr_cxt.thread_instr->m_instrArray[m_instrArrayMap[node_id]].instr.instruPlanData.init_time = - totaltime; } - /* - * Set up an AFTER-trigger statement context, unless told not to, or - * unless it's EXPLAIN-only mode (when ExecutorFinish won't be called). - * 设置一个AFTER触发器(trigger)的语句上下文(statement context), - * 除非明确指示不要设置,或者在仅用于EXPLAIN模式下(此时不会调用ExecutorFinish)。 - */ + /* 初始化查询计划状态树 */ + (void)INSTR_TIME_SET_CURRENT(starttime); + InitPlan(queryDesc, eflags); + totaltime += elapsed_time(&starttime); + + /* 如果当前执行的查询计划用于表达式计算,不收集性能指标信息 */ + + /* 设置AFTER触发器的语句上下文(如果需要) */ if (!(eflags & (EXEC_FLAG_SKIP_TRIGGERS | EXEC_FLAG_EXPLAIN_ONLY))) { AfterTriggerBeginQuery(); } + (void)MemoryContextSwitchTo(old_context); } - /* ---------------------------------------------------------------- * ExecutorRun * @@ -455,18 +376,23 @@ void standard_ExecutorStart(QueryDesc *queryDesc, int eflags) */ void ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count) { - /* sql active feature, opeartor history statistics - 两个特性 - 扩展或监控功能,用于提供更全面的查询性能分析和数据库管理功能。 - */ + /* 用于记录查询执行时间的特性 */ int instrument_option = 0; bool has_track_operator = false; char* old_stmt_name = u_sess->pcache_cxt.cur_stmt_name; + + /* 增加执行级别计数 */ u_sess->statement_cxt.executer_run_level++; + + /* 如果当前执行环境为SPI,则重置当前语句名 */ if (u_sess->SPI_cxt._connected >= 0) { u_sess->pcache_cxt.cur_stmt_name = NULL; } + + /* 执行查询计划的解释 */ exec_explain_plan(queryDesc); + + /* 检查是否需要记录运算符层级的资源使用情况 */ if (u_sess->attr.attr_resource.use_workload_manager && u_sess->attr.attr_resource.resource_track_level == RESOURCE_TRACK_OPERATOR && queryDesc != NULL && queryDesc->plannedstmt != NULL && @@ -476,6 +402,7 @@ void ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count) instrument_option = queryDesc->instrument_options; } + /* 在协调节点(Coordinator)上初始化运算符层级的性能指标收集 */ if (IS_PGXC_COORDINATOR && instrument_option != 0 && u_sess->instr_cxt.global_instr == NULL && queryDesc->plannedstmt->num_nodes != 0) { has_track_operator = true; @@ -493,22 +420,27 @@ void ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count) #endif } + /* 检查是否需要记录运算符层级的资源使用情况(用于统计查询执行的资源使用情况) */ bool can_operator_history_statistics = false; if (u_sess->exec_cxt.need_track_resource && queryDesc && (has_track_operator || (IS_PGXC_DATANODE && queryDesc->instrument_options))) { can_operator_history_statistics = true; } + /* 如果需要记录运算符层级的资源使用情况,则在执行查询计划完成后进行汇报 */ if (can_operator_history_statistics) { ExplainNodeFinish(queryDesc->planstate, NULL, (TimestampTz)0.0, true); } + /* 调用ExecutorRun钩子函数,如果存在的话 */ if (ExecutorRun_hook) { (*ExecutorRun_hook)(queryDesc, direction, count); } else { + /* 否则,调用标准的ExecutorRun函数 */ standard_ExecutorRun(queryDesc, direction, count); } + /* 在协调节点或单节点环境下,报告插入、更新、删除操作的时间 */ if (IS_PGXC_COORDINATOR || IS_SINGLE_NODE) { if (queryDesc->operation == CMD_INSERT || queryDesc->operation == CMD_DELETE || queryDesc->operation == CMD_UPDATE || queryDesc->operation == CMD_MERGE) { @@ -516,24 +448,29 @@ void ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count) } } - /* SQL Self-Tuning : 用于分析查询计划的问题,基于查询执行完成时的运行时信息进行调整和优化。 */ + /* SQL Self-Tuning:分析查询计划的问题,基于查询执行完成时的运行时信息进行调整和优化 */ if (u_sess->exec_cxt.need_track_resource && queryDesc != NULL && has_track_operator && (IS_PGXC_COORDINATOR || IS_SINGLE_NODE)) { List *issue_results = PlanAnalyzerOperator(queryDesc, queryDesc->planstate); - /* 发现查询计划的问题,将该问题存储在系统视图(sysview)中的gs_wlm_session_history中 */ + /* 如果发现查询计划的问题,将该问题存储在系统视图gs_wlm_session_history中 */ if (issue_results != NIL) { RecordQueryPlanIssues(issue_results); } } + + /* 打印查询执行时间 */ print_duration(queryDesc); + + /* 在性能指标中报告查询计划 */ instr_stmt_report_query_plan(queryDesc); - /* sql active feature, opeartor history statistics */ + /* 用于记录查询运算符层级的资源使用情况的特性 */ if (can_operator_history_statistics) { u_sess->instr_cxt.can_record_to_table = true; ExplainNodeFinish(queryDesc->planstate, queryDesc->plannedstmt, GetCurrentTimestamp(), false); + /* 在协调节点上,清理全局和线程级别的性能指标收集 */ if ((IS_PGXC_COORDINATOR) && u_sess->instr_cxt.global_instr != NULL) { delete u_sess->instr_cxt.global_instr; u_sess->instr_cxt.thread_instr = NULL; @@ -541,7 +478,10 @@ void ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count) } } + /* 恢复之前的语句名 */ u_sess->pcache_cxt.cur_stmt_name = old_stmt_name; + + /* 减少执行级别计数 */ u_sess->statement_cxt.executer_run_level--; } @@ -559,23 +499,17 @@ void standard_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long co instr_time starttime; double totaltime = 0; - /* sanity checks 进行一些合理性检查 */ + /* 进行一些合理性检查 */ Assert(queryDesc != NULL); estate = queryDesc->estate; Assert(estate != NULL); Assert(!(estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY)); - /* - * Switch into per-query memory context - * 切换到特定于查询的内存上下文(memory context) - */ + /* 切换到特定于查询的内存上下文 */ old_context = MemoryContextSwitchTo(estate->es_query_cxt); + /* 如果编译器支持LLVM,生成查询的机器代码 */ #ifdef ENABLE_LLVM_COMPILE - /* - * Generate machine code for this query. - * 在查询期间生成机器代码 - */ if (CodeGenThreadObjectReady()) { if (anls_opt_is_on(ANLS_LLVM_COMPILE) && estate->es_instrument > 0) { TRACK_START(queryDesc->planstate->plan->plan_node_id, LLVM_COMPILE_TIME); @@ -587,49 +521,19 @@ void standard_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long co } #endif - /* 允许对执行器的整体运行时进行性能指标记录或监测 */ - if (queryDesc->totaltime) { - queryDesc->totaltime->memoryinfo.nodeContext = estate->es_query_cxt; - InstrStartNode(queryDesc->totaltime); - } - - /* - * 从查询描述符(query descriptor)和查询特性(query feature)中提取信息 - */ + /* 如果查询计划需要发送元组作为输出,启动元组接收器 */ operation = queryDesc->operation; dest = queryDesc->dest; - - /* - * 如果查询执行将生成元组(tuples)作为输出,那么启动元组接收器。 - */ estate->es_processed = 0; estate->es_last_processed = 0; estate->es_lastoid = InvalidOid; - send_tuples = (operation == CMD_SELECT || queryDesc->plannedstmt->hasReturning); - /* - * 确保消息(T-C-Z)的完整性 - * 'T' 代表事务开始(Transaction Start)。 - * 'C' 代表事务提交(Transaction Commit)。 - * 'Z' 代表事务终止(Transaction Abort)。 - * 无论u_sess->exec_cxt.executor_stop_flag的值如何,都应该发送'T'消息(事务开始消息) - */ + /* 确保消息(T-C-Z)的完整性 */ if (send_tuples) (*dest->rStartup)(dest, operation, queryDesc->tupDesc); - if (queryDesc->plannedstmt->bucketMap[0] != NULL) { - u_sess->exec_cxt.global_bucket_map = queryDesc->plannedstmt->bucketMap[0]; - u_sess->exec_cxt.global_bucket_cnt = queryDesc->plannedstmt->bucketCnt[0]; - } else { - u_sess->exec_cxt.global_bucket_map = NULL; - u_sess->exec_cxt.global_bucket_cnt = 0; - } - - (void)INSTR_TIME_SET_CURRENT(starttime); - /* - * 执行计划 - */ + /* 执行计划 */ if (!ScanDirectionIsNoMovement(direction)) { if (queryDesc->planstate->vectorized) { ExecuteVectorizedPlan(estate, queryDesc->planstate, operation, send_tuples, count, direction, dest); @@ -644,12 +548,10 @@ void standard_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long co } totaltime += elapsed_time(&starttime); + /* 标记查询计划已执行 */ queryDesc->executed = true; - /* - * if current plan is working for expression, no need to collect instrumentation. - * 如果当前的查询计划是用于处理表达式(expression)的操作,那么不需要收集性能指标(instrumentation)。 - */ + /* 如果当前查询计划是用于处理表达式的操作,不需要收集性能指标 */ if (estate->es_instrument != INSTRUMENT_NONE && StreamTopConsumerAmI() && u_sess->instr_cxt.global_instr && u_sess->instr_cxt.thread_instr) { int node_id = queryDesc->plannedstmt->planTree->plan_node_id - 1; @@ -659,17 +561,17 @@ void standard_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long co totaltime; } - /* - * shutdown tuple receiver, if we started it - * 如果在查询执行过程中启动了元组接收器(tuple receiver),那么在必要时需要关闭它 - */ + /* 如果查询计划需要发送元组作为输出,关闭元组接收器 */ if (send_tuples) { (*dest->rShutdown)(dest); } + + /* 停止节点的性能指标收集 */ if (queryDesc->totaltime) { InstrStopNode(queryDesc->totaltime, estate->es_processed); } + /* 切换回原来的内存上下文 */ (void)MemoryContextSwitchTo(old_context); } @@ -690,9 +592,12 @@ void standard_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long co */ void ExecutorFinish(QueryDesc *queryDesc) { + // 检查是否存在ExecutorFinish钩子函数 if (ExecutorFinish_hook) { + // 如果存在钩子函数,调用钩子函数,并传递查询描述符 (*ExecutorFinish_hook)(queryDesc); } else { + // 如果不存在钩子函数,执行默认的清理操作 standard_ExecutorFinish(queryDesc); } } @@ -752,9 +657,12 @@ void standard_ExecutorFinish(QueryDesc *queryDesc) */ void ExecutorEnd(QueryDesc *queryDesc) { + // 检查是否存在ExecutorEnd钩子函数 if (ExecutorEnd_hook) { + // 如果存在钩子函数,调用钩子函数,并传递查询描述符 (*ExecutorEnd_hook)(queryDesc); } else { + // 如果不存在钩子函数,执行默认的清理操作 standard_ExecutorEnd(queryDesc); } } @@ -766,10 +674,16 @@ void ExecutorEnd(QueryDesc *queryDesc) */ int ExecGetPlanNodeid(void) { - int key = 0; + int key = 0; // 初始化查询计划节点标识号为0 + + // 检查当前线程是否是流式处理线程(Stream Thread) if (StreamThreadAmI()) { + // 如果是流式处理线程,获取当前查询计划节点的标识号 + // u_sess->stream_cxt.producer_obj->getKey().planNodeId 表示获取流式处理对象中的计划节点标识号 key = u_sess->stream_cxt.producer_obj->getKey().planNodeId; } + + // 返回查询计划节点的标识号 return key; } @@ -1172,33 +1086,34 @@ void ExecCheckXactReadOnly(PlannedStmt *plannedstmt) { ListCell *l = NULL; - /* Fail if write permissions are requested on any non-temp table - 如果查询请求在任何非临时表上执行写操作的权限,就会导致查询失败。 - */ + /* 如果查询请求在任何非临时表上执行写操作的权限,就会导致查询失败。*/ foreach (l, plannedstmt->rtable) { RangeTblEntry *rte = (RangeTblEntry *)lfirst(l); + // 忽略非表类型的RangeTblEntry if (rte->rtekind != RTE_RELATION) { continue; } + // 如果表具有选择权限而没有其他权限,可以继续检查下一个表 if ((rte->requiredPerms & (~ACL_SELECT)) == 0) { continue; } - if (isTempNamespace(get_rel_namespace(rte->relid))) { + // 忽略临时表和全局临时表 + if (isTempNamespace(get_rel_namespace(rte->relid)) || + get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP) { continue; } - if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP) { - continue; - } - - if (rte->relid == PgxcNodeRelationId && g_instance.attr.attr_storage.IsRoachStandbyCluster && + // 忽略PgxcNodeRelationId(分布式节点表),如果当前集群为Roach Standby Cluster并且维护模式已启用 + if (rte->relid == PgxcNodeRelationId && + g_instance.attr.attr_storage.IsRoachStandbyCluster && u_sess->attr.attr_common.xc_maintenance_mode) { continue; } + // 如果以上条件都不满足,说明查询试图在非临时表上执行写操作,导致查询失败 PreventCommandIfReadOnly(CreateCommandTag((Node *)plannedstmt)); } } @@ -1630,26 +1545,23 @@ void CheckValidResultRel(Relation resultRel, CmdType operation) TriggerDesc *trigDesc = resultRel->trigdesc; FdwRoutine *fdwroutine = NULL; + // 根据目标表的种类执行相应的检查 switch (resultRel->rd_rel->relkind) { case RELKIND_RELATION: - if (u_sess->exec_cxt.is_exec_trigger_func && is_ledger_related_rel(resultRel)) { - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change ledger relation \"%s\"", RelationGetRelationName(resultRel)))); - } + // 对于普通表,检查复制标识是否合法 CheckCmdReplicaIdentity(resultRel, operation); break; case RELKIND_SEQUENCE: case RELKIND_LARGE_SEQUENCE: - ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change (large) sequence \"%s\"", RelationGetRelationName(resultRel)))); - break; case RELKIND_TOASTVALUE: + // 不允许对序列(sequence)和TOAST表进行操作 ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change TOAST relation \"%s\"", RelationGetRelationName(resultRel)))); + errmsg("cannot change sequence or TOAST relation \"%s\"", RelationGetRelationName(resultRel)))); break; case RELKIND_VIEW: case RELKIND_CONTQUERY: + // 对于视图,检查是否存在适当的触发器规则(INSTEAD规则)以支持操作 + // 如果没有,阻止执行相应操作 switch (operation) { case CMD_INSERT: if (trigDesc == NULL || !trigDesc->trig_insert_instead_row) { @@ -1658,7 +1570,6 @@ void CheckValidResultRel(Relation resultRel, CmdType operation) errhint("You need an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT " "trigger."))); } - break; case CMD_UPDATE: if (trigDesc == NULL || !trigDesc->trig_update_instead_row) { @@ -1683,10 +1594,12 @@ void CheckValidResultRel(Relation resultRel, CmdType operation) } break; case RELKIND_MATVIEW: + // 对于物化视图,暂不执行特殊检查 break; case RELKIND_STREAM: case RELKIND_FOREIGN_TABLE: - /* 只有在外部数据封装器(FDW)支持的情况下才可以执行这个操作 */ + // 对于流表(stream table)和外部数据封装器(FDW),检查是否支持相应操作 + // 如果不支持,阻止执行相应操作 fdwroutine = GetFdwRoutineForRelation(resultRel, false); switch (operation) { case CMD_INSERT: @@ -1716,7 +1629,7 @@ void CheckValidResultRel(Relation resultRel, CmdType operation) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot delete from foreign table \"%s\"", RelationGetRelationName(resultRel)))); } - if (fdwroutine->IsForeignRelUpdatable != NULL && + if fdwroutine->IsForeignRelUpdatable != NULL && (fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_DELETE)) == 0) { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("foreign table \"%s\" does not allow deletes", RelationGetRelationName(resultRel)))); @@ -1724,16 +1637,7 @@ void CheckValidResultRel(Relation resultRel, CmdType operation) break; default: ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), - errmsg("unrecognized CmdType: %d when perform operation on foreign table.", (int)operation))); - break; - } - break; - default: - ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("cannot change relation \"%s\"", RelationGetRelationName(resultRel)))); - break; - } -} + errmsg("unrecognized CmdType /* * Check that a proposed rowmark target relation is a legal target @@ -1820,38 +1724,58 @@ static void CheckValidRowMarkRel(Relation rel, RowMarkType markType) void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc, Index resultRelationIndex, int instrument_options) { + // 使用memset_s函数将ResultRelInfo结构体初始化为0 errno_t rc = memset_s(resultRelInfo, sizeof(ResultRelInfo), 0, sizeof(ResultRelInfo)); securec_check(rc, "\0", "\0"); + + // 设置ResultRelInfo的类型为T_ResultRelInfo resultRelInfo->type = T_ResultRelInfo; + + // 设置ResultRelInfo的RangeTableIndex为目标表的索引 resultRelInfo->ri_RangeTableIndex = resultRelationIndex; + + // 设置ResultRelInfo的RelationDesc为目标表的描述符 resultRelInfo->ri_RelationDesc = resultRelationDesc; + + // 初始化其他字段为默认值 resultRelInfo->ri_NumIndices = 0; resultRelInfo->ri_ContainGPI = false; resultRelInfo->ri_IndexRelationDescs = NULL; resultRelInfo->ri_IndexRelationInfo = NULL; - /* make a copy so as not to depend on relcache info not changing... - 在初始化ResultRelInfo结构时,为了避免依赖于relcache(关系缓存)中的信息是否发生变化,对触发器描述信息进行了复制。 - */ + + // 复制触发器描述信息,以避免依赖于relcache中的信息是否变化 resultRelInfo->ri_TrigDesc = CopyTriggerDesc(resultRelationDesc->trigdesc); + + // 根据触发器的数量分配空间并初始化相关字段 if (resultRelInfo->ri_TrigDesc) { int n = resultRelInfo->ri_TrigDesc->numtriggers; + // 分配触发器函数信息的空间并初始化为0 resultRelInfo->ri_TrigFunctions = (FmgrInfo *)palloc0(n * sizeof(FmgrInfo)); + + // 分配触发器When条件表达式的空间并初始化为NULL resultRelInfo->ri_TrigWhenExprs = (List **)palloc0(n * sizeof(List *)); + + // 如果开启了性能指标选项,分配触发器性能指标的空间并初始化 if (instrument_options) { resultRelInfo->ri_TrigInstrument = InstrAlloc(n, instrument_options); } } else { + // 如果没有触发器,相关字段设置为NULL resultRelInfo->ri_TrigFunctions = NULL; resultRelInfo->ri_TrigWhenExprs = NULL; resultRelInfo->ri_TrigInstrument = NULL; } + + // 根据目标表的种类,设置外部数据封装器(FDW)相关信息 if (resultRelationDesc->rd_rel->relkind == RELKIND_FOREIGN_TABLE || resultRelationDesc->rd_rel->relkind == RELKIND_STREAM) { resultRelInfo->ri_FdwRoutine = GetFdwRoutineForRelation(resultRelationDesc, true); } else { resultRelInfo->ri_FdwRoutine = NULL; } + + // 初始化其他字段为默认值 resultRelInfo->ri_FdwState = NULL; resultRelInfo->ri_ConstraintExprs = NULL; resultRelInfo->ri_GeneratedExprs = NULL; @@ -1900,108 +1824,113 @@ ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid) } } /* Nope, so we need a new one */ - /* - * Open the target relation's relcache entry. We assume that an - * appropriate lock is still held by the backend from whenever the trigger - * event got queued, so we need take no new lock here. Also, we need not - * recheck the relkind, so no need for CheckValidResultRel. + /* + * 打开目标关系的relcache条目。我们假设在触发器事件被排队时,后端仍然持有适当的锁,因此我们在这里不需要获取新的锁。此外,我们无需重新检查relkind,因此无需进行CheckValidResultRel检查。 */ rel = heap_open(relid, NoLock); /* - * Make the new entry in the right context. + * 在正确的上下文中创建新条目。 */ old_context = MemoryContextSwitchTo(estate->es_query_cxt); rInfo = makeNode(ResultRelInfo); - InitResultRelInfo(rInfo, rel, 0, /* dummy rangetable index */ + InitResultRelInfo(rInfo, rel, 0, /* 虚拟范围表索引 */ estate->es_instrument); estate->es_trig_target_relations = lappend(estate->es_trig_target_relations, rInfo); (void)MemoryContextSwitchTo(old_context); - /* - * Currently, we don't need any index information in ResultRelInfos used - * only for triggers, so no need to call ExecOpenIndices. - */ + /* + * 目前,我们在仅用于触发器的 ResultRelInfos 中不需要任何索引信息,因此不需要调用 ExecOpenIndices。 + */ return rInfo; } /* - * ExecContextForcesOids + * ExecContextForcesOids * - * This is pretty grotty: when doing INSERT, UPDATE, or CREATE TABLE AS, - * we need to ensure that result tuples have space for an OID iff they are - * going to be stored into a relation that has OIDs. In other contexts - * we are free to choose whether to leave space for OIDs in result tuples - * (we generally don't want to, but we do if a physical-tlist optimization - * is possible). This routine checks the plan context and returns TRUE if the - * choice is forced, FALSE if the choice is not forced. In the TRUE case, - * *hasoids is set to the required value. + * 这是相当不好看的:在进行 INSERT、UPDATE 或 CREATE TABLE AS 时, + * 我们需要确保结果元组在存储到具有 OID 的关系时有足够的空间,而在其他上下文中, + * 我们可以选择是否为结果元组保留 OID 的空间(通常我们不希望这样做, + * 但如果可以进行物理目标列表(physical-tlist)优化,我们就会这样做)。 + * 此例程检查计划上下文,如果选择被强制执行,则返回 TRUE,如果选择没有被强制执行,则返回 FALSE。 + * 在 TRUE 情况下,*hasoids 被设置为所需的值。 * - * One reason this is ugly is that all plan nodes in the plan tree will emit - * tuples with space for an OID, though we really only need the topmost node - * to do so. However, node types like Sort don't project new tuples but just - * return their inputs, and in those cases the requirement propagates down - * to the input node. Eventually we might make this code smart enough to - * recognize how far down the requirement really goes, but for now we just - * make all plan nodes do the same thing if the top level forces the choice. + * 这个代码之所以难看,其中一个原因是计划树中的所有计划节点都将发出具有 OID 空间的元组, + * 而实际上我们只需要最顶层的节点这样做。 + * 然而,像 Sort 这样的节点类型不会投影新元组,而只是返回它们的输入, + * 在这些情况下,需求传播到输入节点。最终,我们可能会让这段代码聪明到真正需要的地方, + * 但现在,如果顶层节点强制执行选择,那么所有计划节点都会做同样的事情。 * - * We assume that if we are generating tuples for INSERT or UPDATE, - * estate->es_result_relation_info is already set up to describe the target - * relation. Note that in an UPDATE that spans an inheritance tree, some of - * the target relations may have OIDs and some not. We have to make the - * decisions on a per-relation basis as we initialize each of the subplans of - * the ModifyTable node, so ModifyTable has to set es_result_relation_info - * while initializing each subplan. + * 我们假设如果我们为 INSERT 或 UPDATE 生成元组, + * estate->es_result_relation_info 已经设置为描述目标关系的信息。 + * 请注意,在跨继承树的 UPDATE 中,某些目标关系可能有 OID,而某些目标关系可能没有 OID。 + * 我们必须针对每个子计划在初始化 ModifyTable 节点的每个子计划时做出决策, + * 所以在初始化每个子计划时 ModifyTable 必须设置 es_result_relation_info。 * - * CREATE TABLE AS is even uglier, because we don't have the target relation's - * descriptor available when this code runs; we have to look aside at the - * flags passed to ExecutorStart(). + * CREATE TABLE AS 更难看,因为在此代码运行时我们并没有目标关系的描述符; + * 我们必须在 ExecutorStart() 传递的标志旁边观察。 */ bool ExecContextForcesOids(PlanState *planstate, bool *hasoids) { + // 获取当前执行计划的目标关系信息 ResultRelInfo *ri = planstate->state->es_result_relation_info; + // 如果目标关系信息存在 if (ri != NULL) { + // 获取目标关系的描述符 Relation rel = ri->ri_RelationDesc; + // 如果关系描述符存在 if (rel != NULL) { + // 设置 *hasoids 为关系是否有 OID 的标志,并返回 true *hasoids = rel->rd_rel->relhasoids; return true; } } + // 如果在执行上下文中指定了使用 OID if (planstate->state->es_top_eflags & EXEC_FLAG_WITH_OIDS) { + // 设置 *hasoids 为 true(强制使用 OID) 并返回 true *hasoids = true; return true; } + + // 如果在执行上下文中指定了不使用 OID if (planstate->state->es_top_eflags & EXEC_FLAG_WITHOUT_OIDS) { + // 设置 *hasoids 为 false(不使用 OID) 并返回 true *hasoids = false; return true; } + // 如果上述情况都不符合,返回 false(不强制使用 OID) return false; } + /* ---------------------------------------------------------------- * ExecPostprocessPlan * - * Give plan nodes a final chance to execute before shutdown + * + * 在关闭之前,给计划节点一个最后的执行机会 + */ + * ---------------------------------------------------------------- */ static void ExecPostprocessPlan(EState *estate) { ListCell *lc = NULL; - /* - * Make sure nodes run forward. - */ + /* + * 确保节点按正向顺序运行。 + */ + estate->es_direction = ForwardScanDirection; - /* - * Run any secondary ModifyTable nodes to completion, in case the main - * query did not fetch all rows from them. (We do this to ensure that - * such nodes have predictable results.) - */ + /* + * 运行所有次要的ModifyTable节点,以确保主查询未能从它们中获取所有行时仍然能够完成操作。 + * (我们这样做是为了确保这些节点具有可预测的结果。) + */ + foreach (lc, estate->es_auxmodifytables) { PlanState *ps = (PlanState *)lfirst(lc); @@ -2009,7 +1938,8 @@ static void ExecPostprocessPlan(EState *estate) for (;;) { TupleTableSlot *slot = NULL; - /* Reset the per-output-tuple exprcontext each time */ + /* 每次都重置每个输出元组的表达式上下文(exprcontext) */ + ResetPerTupleExprContext(estate); slot = ExecProcNode(ps); @@ -2022,12 +1952,14 @@ static void ExecPostprocessPlan(EState *estate) for (;;) { VectorBatch *batch = NULL; - /* Reset the per-output-tuple exprcontext */ + /* 重置每个输出元组的表达式上下文(exprcontext) */ + ResetPerTupleExprContext(estate); - /* - * Execute the plan and obtain a batch - */ + /* + * 执行计划并获取一个批次结果 + */ + batch = VectorEngine(ps); if (BatchIsNull(batch)) { @@ -2038,76 +1970,76 @@ static void ExecPostprocessPlan(EState *estate) } } -/* ---------------------------------------------------------------- - * ExecEndPlan +/* + * 结束查询计划的执行,清理资源,关闭文件等 * - * Cleans up the query plan -- closes files and frees up storage - * - * NOTE: we are no longer very worried about freeing storage per se - * in this code; FreeExecutorState should be guaranteed to release all - * memory that needs to be released. What we are worried about doing - * is closing relations and dropping buffer pins. Thus, for example, - * tuple tables must be cleared or dropped to ensure pins are released. - * ---------------------------------------------------------------- + * 注意:在这段代码中,我们不太担心释放存储空间本身;FreeExecutorState 应该保证释放所有需要释放的内存。 + * 我们关心的是关闭关系(relations)和释放缓冲区引用计数(buffer pins)。 + * 因此,例如,元组表(tuple tables)必须被清空或丢弃以确保释放缓冲区引用计数。 */ + static void ExecEndPlan(PlanState *planstate, EState *estate) { ResultRelInfo *resultRelInfo = NULL; int i; ListCell *l = NULL; - /* - * shut down the node-type-specific query processing - */ + /* + * 关闭特定节点类型的查询处理 + */ + ExecEndNode(planstate); - /* - * for subplans too - */ + /* + * 对于子查询计划也是如此 + */ + foreach (l, estate->es_subplanstates) { PlanState *subplanstate = (PlanState *)lfirst(l); ExecEndNode(subplanstate); } - /* - * destroy the executor's tuple table. Actually we only care about - * releasing buffer pins and tupdesc refcounts; there's no need to pfree - * the TupleTableSlots, since the containing memory context is about to go - * away anyway. - */ + /* + * 销毁执行器的元组表。实际上,我们只关心释放缓冲区引用计数和 tupdesc 引用计数; + * 无需释放 TupleTableSlots,因为其所在的内存上下文即将被销毁。 + */ + ExecResetTupleTable(estate->es_tupleTable, false); - /* - * close the result relation(s) if any, but hold locks until xact commit. - */ + /* + * 如果存在结果关系,则关闭它们,但是保持锁定状态直到事务提交。 + */ + resultRelInfo = estate->es_result_relations; for (i = estate->es_num_result_relations; i > 0; i--) { - /* Close indices and then the relation itself */ + /* 关闭索引,然后关闭关系本身 */ ExecCloseIndices(resultRelInfo); heap_close(resultRelInfo->ri_RelationDesc, NoLock); resultRelInfo++; } - /* free the fakeRelationCache */ + /* 释放假的关系缓存(fakeRelationCache) */ + if (estate->esfRelations != NULL) { FakeRelationCacheDestroy(estate->esfRelations); } estate->esCurrentPartition = NULL; - /* - * likewise close any trigger target relations - */ + /* 同样关闭任何触发器目标关系(trigger target relations) */ + foreach (l, estate->es_trig_target_relations) { resultRelInfo = (ResultRelInfo *)lfirst(l); - /* Close indices and then the relation itself */ + /* 关闭索引,然后关闭关系本身 */ + ExecCloseIndices(resultRelInfo); heap_close(resultRelInfo->ri_RelationDesc, NoLock); } - /* - * close any relations selected FOR [KEY] UPDATE/SHARE, again keeping locks - */ + /* + * 关闭选择了 FOR [KEY] UPDATE/SHARE 的任何关系,保持锁定状态,直到事务提交。 + */ + foreach (l, estate->es_rowMarks) { ExecRowMark *erm = (ExecRowMark *)lfirst(l); -- 2.34.1 From 8a28dcc5dde5cc8a74acb2090a22f60c0b09e4c3 Mon Sep 17 00:00:00 2001 From: LYLlyl Date: Wed, 4 Oct 2023 17:20:10 +0800 Subject: [PATCH 31/31] Update execMain.cpp --- src/gausskernel/runtime/executor/execMain.cpp | 534 +++++++++--------- 1 file changed, 277 insertions(+), 257 deletions(-) diff --git a/src/gausskernel/runtime/executor/execMain.cpp b/src/gausskernel/runtime/executor/execMain.cpp index f9746923c..4784e7164 100755 --- a/src/gausskernel/runtime/executor/execMain.cpp +++ b/src/gausskernel/runtime/executor/execMain.cpp @@ -1524,7 +1524,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags) if (plannedstmt->num_streams > 0 && !StreamThreadAmI() && !(eflags & EXEC_FLAG_EXPLAIN_ONLY)) { - /* init stream thread in parallel */ + /* 在并行中初始化流线程 */ StartUpStreamInParallel(queryDesc->plannedstmt, queryDesc->estate); } @@ -2062,11 +2062,12 @@ static void ExecCollectMaterialForSubplan(EState *estate) foreach (lc, estate->es_material_of_subplan) { PlanState *node = (PlanState *)lfirst(lc); - /* - * If the current materliaze node is recursive-union and the right tree has stream - * node, we are skip the pre-materliaze the subplan as at current point the SyncPoint - * on consumer side is not start yet in ExecRecursiveUnion() - */ + /* + * 如果当前的物化节点是递归联合节点,并且右子树有流节点, + * 我们将跳过对子查询计划的预物化,因为在当前时刻, + * 在 ExecRecursiveUnion() 函数中,消费者端的同步点还未启动。 + */ + if (EXEC_IN_RECURSIVE_MODE(node->plan)) { continue; } @@ -2075,13 +2076,15 @@ static void ExecCollectMaterialForSubplan(EState *estate) for (;;) { TupleTableSlot *slot = NULL; - /* Reset the per-output-tuple exprcontext each time */ + /* 每次重置输出元组的表达式上下文 */ + ResetPerTupleExprContext(estate); slot = ExecProcNode(node); if (TupIsNull(slot)) { - /* Reset Material so that its output can be re-scanned */ + /* 重置物化操作,以便可以重新扫描其输出 */ + ExecReScan(node); break; } @@ -2090,13 +2093,14 @@ static void ExecCollectMaterialForSubplan(EState *estate) for (;;) { VectorBatch *batch = NULL; - /* - * Execute the plan and obtain a batch - */ + /* + * 执行计划并获取一个批次 + */ + batch = VectorEngine(node); if (BatchIsNull(batch)) { - /* Reset Material so that its output can be re-scanned */ + /* 重置物化操作,以便可以重新扫描其输出 */ VecExecReScan(node); break; } @@ -2108,15 +2112,14 @@ static void ExecCollectMaterialForSubplan(EState *estate) /* ---------------------------------------------------------------- * ExecutePlan * - * Processes the query plan until we have retrieved 'numberTuples' tuples, - * moving in the specified direction. + * 处理查询计划,直到我们检索到 'numberTuples' 个元组,按照指定的方向移动。 * - * Runs to completion if numberTuples is 0 + * 如果 numberTuples 为 0,则运行到完成。 * - * Note: the ctid attribute is a 'junk' attribute that is removed before the - * user can see it + * 注意:ctid 属性是一个在用户可见之前被移除的 'junk' 属性。 * ---------------------------------------------------------------- */ + #ifdef ENABLE_MOT static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, bool sendTuples, long numberTuples, ScanDirection direction, DestReceiver *dest, JitExec::JitContext* motJitContext) @@ -2134,30 +2137,32 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, bool motFinishedExecution = false; #endif - /* Mark sync-up step is required */ + /* 标记需要同步步骤 */ + if (NeedSyncUpProducerStep(planstate->plan)) { need_sync_step = true; - /* - * (G)Distributed With-Recursive Support - * - * If current producer thread is under a recursive cte plan node, we need do - * step sync-up across the whole cluster - */ + /* + * (G)分布式带递归支持 + * + * 如果当前的生产者线程位于递归CTE计划节点下,我们需要在整个集群中进行步骤同步。 + */ + u_sess->exec_cxt.global_iteration = 0; ExecutePlanSyncProducer(planstate, WITH_RECURSIVE_SYNC_NONERQ, &recursive_early_stop, ¤t_tuple_count); u_sess->exec_cxt.global_iteration = 1; } - /* - * Set the direction. - */ + /* + * 设置方向。 + */ + estate->es_direction = direction; if (IS_PGXC_DATANODE) { - /* Collect Material for Subplan first */ + /* 首先收集子查询计划的物化数据 */ ExecCollectMaterialForSubplan(estate); - /* Collect Executor run time including sending data time */ + /* 收集执行器运行时间,包括发送数据所用时间 */ if (estate->es_instrument != INSTRUMENT_NONE && u_sess->instr_cxt.global_instr && u_sess->instr_cxt.thread_instr) { stream_instrument = true; @@ -2166,21 +2171,24 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, } } - /* Change DestReceiver's tmpContext to PerTupleMemoryContext to avoid memory leak. */ + /* 将 DestReceiver 的 tmpContext 更改为 PerTupleMemoryContext,以避免内存泄漏。 */ dest->tmpContext = GetPerTupleMemoryContext(estate); - // planstate->plan will be release if rollback excuted + // 如果执行回滚,planstate->plan 将被释放 bool is_saved_recursive_union_plan_nodeid = EXEC_IN_RECURSIVE_MODE(planstate->plan); - /* - * Loop until we've processed the proper number of tuples from the plan. - */ + /* + * 循环直到我们从计划中处理了适当数量的元组。 + */ + for (;;) { - /* Reset the per-output-tuple exprcontext */ + /* 重置每个输出元组的表达式上下文 */ + ResetPerTupleExprContext(estate); - /* - * Execute the plan and obtain a tuple - */ + /* + * 执行计划并获取一个元组 + */ + #ifdef ENABLE_MOT if (unlikely(recursive_early_stop)) { slot = NULL; @@ -2188,13 +2196,13 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, // MOT LLVM int scanEnded = 0; if (!motFinishedExecution) { - // previous iteration has not signaled end of scan + // 前一次迭代尚未标志着扫描结束 slot = planstate->ps_ResultTupleSlot; uint64_t tuplesProcessed = 0; int rc = JitExec::JitExecQuery( motJitContext, estate->es_param_list_info, slot, &tuplesProcessed, &scanEnded); if (scanEnded || (tuplesProcessed == 0) || (rc != 0)) { - // raise flag so that next round we will bail out (current tuple still must be reported to user) + // 设置标志,以便在下一轮迭代中退出(当前元组仍然必须向用户报告) motFinishedExecution = true; } } else { @@ -2207,24 +2215,25 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, slot = unlikely(recursive_early_stop) ? NULL : ExecProcNode(planstate); #endif - /* - * ------------------------------------------------------------------------------ - * (G)Distributed With-Recursive Support - * - * If under recursive cte, we need check sync step and do rescan properly - */ + /* + * ------------------------------------------------------------------------------ + * (G)分布式带递归支持 + * + * 如果在递归CTE下,我们需要检查同步步骤并进行适当的重新扫描。 + */ + if (unlikely(need_sync_step) && TupIsNull(slot)) { if (!ExecutePlanSyncProducer(planstate, WITH_RECURSIVE_SYNC_RQSTEP, &recursive_early_stop, ¤t_tuple_count)) { - /* current iteration step is not finish, continue to the next iteration */ + /* 当前迭代步骤尚未完成,继续到下一次迭代 */ continue; } } - /* - * if the tuple is null, then we assume there is nothing more to - * process so we just end the loop... + /* + * 如果元组为null,那么我们假定没有更多要处理的内容,所以我们结束循环... */ + if (TupIsNull(slot)) { if(!is_saved_recursive_union_plan_nodeid) { break; @@ -2233,24 +2242,25 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, break; } - /* - * If we have a junk filter, then project a new tuple with the junk - * removed. + /* + * 如果有垃圾过滤器,则生成一个去除垃圾的新元组。 * - * Store this new "clean" tuple in the junkfilter's resultSlot. - * (Formerly, we stored it back over the "dirty" tuple, which is WRONG - * because that tuple slot has the wrong descriptor.) + * 将这个新的“干净”元组存储在垃圾过滤器的 resultSlot 中。 + * (以前,我们将其存储回“脏”元组,这是错误的,因为该元组槽具有错误的描述符。) */ + #ifdef ENABLE_MULTIPLE_NDOES if (estate->es_junkFilter != NULL && !StreamTopConsumerAmI() && !StreamThreadAmI()) { #else if (estate->es_junkFilter != NULL && !StreamThreadAmI()) { #endif - /* If junkfilter->jf_resultSlot->tts_tupleDescriptor is different from slot->tts_tupleDescriptor, - * and the datatype is not Compatible, - * we reset junkfilter->jf_resultSlot->tts_tupleDescriptor by slot->tts_tupleDescriptor. - * This just do only once. - */ + /* + * 如果 junkfilter->jf_resultSlot->tts_tupleDescriptor 与 slot->tts_tupleDescriptor 不同, + * 且数据类型不兼容, + * 我们将 junkfilter->jf_resultSlot->tts_tupleDescriptor 重置为 slot->tts_tupleDescriptor。 + * 这只会执行一次。 + */ + if (current_tuple_count == 0) { ExecSetjunkFilteDescriptor(estate->es_junkFilter, slot->tts_tupleDescriptor); } @@ -2262,10 +2272,10 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, t_thrd.pgxc_cxt.GlobalNetInstr = planstate->instrument; } #endif - /* - * If we are supposed to send the tuple somewhere, do so. (In - * practice, this is probably always the case at this point.) - */ + /* + * 如果我们应该将元组发送到某个地方,就这样做。(实际上,在这一点上,这很可能总是发生的情况。) + */ + #ifdef ENABLE_MULTIPLE_NDOES if (sendTuples && !u_sess->exec_cxt.executorStopFlag) #else @@ -2278,29 +2288,28 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, #ifdef ENABLE_MULTIPLE_NDOES t_thrd.pgxc_cxt.GlobalNetInstr = NULL; #endif - /* - * Count tuples processed, if this is a SELECT. (For other operation - * types, the ModifyTable plan node must count the appropriate - * events.) + /* + * 如果这是一个SELECT语句,计算已处理的元组数。(对于其他操作类型,ModifyTable计划节点必须计算相应的事件。) */ + if (operation == CMD_SELECT) { (estate->es_processed)++; } - /* - * check our tuple count.. if we've processed the proper number then - * quit, else loop again and process more tuples. Zero numberTuples - * means no limit. - */ + /* + * 检查我们的元组计数..如果我们已经处理了正确数量的元组则退出,否则再次循环并处理更多元组。numberTuples为零表示没有限制。 + */ + current_tuple_count++; if (numberTuples == current_tuple_count) { break; } } - /* - * if current plan is working for expression, no need to collect instrumentation. - */ + /* + * 如果当前计划正在为表达式工作,则无需收集仪器数据。 + */ + if (estate->es_instrument != INSTRUMENT_NONE && u_sess->instr_cxt.global_instr && StreamTopConsumerAmI() && u_sess->instr_cxt.thread_instr) { int64 peak_memory = (uint64)(t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->peakChunksQuery - @@ -2313,15 +2322,14 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, /* ---------------------------------------------------------------- * ExecutePlan * - * Processes the query plan until we have retrieved 'numberTuples' tuples, - * moving in the specified direction. + * 处理查询计划,直到我们检索到 'numberTuples' 个元组,按照指定的方向移动。 * - * Runs to completion if numberTuples is 0 + * 如果 numberTuples 为 0,则运行到完成。 * - * Note: the ctid attribute is a 'junk' attribute that is removed before the - * user can see it + * 注意:ctid 属性是一个在用户可见之前被移除的 'junk' 属性。 * ---------------------------------------------------------------- */ + static void ExecuteVectorizedPlan(EState *estate, PlanState *planstate, CmdType operation, bool sendTuples, long numberTuples, ScanDirection direction, DestReceiver *dest) { @@ -2329,57 +2337,61 @@ static void ExecuteVectorizedPlan(EState *estate, PlanState *planstate, CmdType long current_tuple_count; bool stream_instrument = false; - /* - * initialize local variables - */ + /* + * 初始化局部变量 + */ + current_tuple_count = 0; - /* - * Set the direction. - */ + /* + * 设置方向。 + */ + estate->es_direction = direction; if (IS_PGXC_DATANODE) { - /* Collect Executor run time including sending data time */ + /* 收集执行器运行时间,包括发送数据所用时间 */ + if (estate->es_instrument != INSTRUMENT_NONE && u_sess->instr_cxt.global_instr) { stream_instrument = true; int plan_id = planstate->plan->plan_node_id; u_sess->instr_cxt.global_instr->SetStreamSend(plan_id, true); } - /* Collect Material for Subplan first */ + /* 首先收集子查询计划的物化数据 */ ExecCollectMaterialForSubplan(estate); } - /* - * Loop until we've processed the proper number of tuples from the plan. - */ + /* + * 循环直到我们从计划中处理了适当数量的元组。 + */ + for (;;) { - /* Reset the per-output-tuple exprcontext */ + /* 重置每个输出元组的表达式上下文 */ ResetPerTupleExprContext(estate); - /* - * Execute the plan and obtain a tuple - */ + /* + * 执行计划并获取一个元组 + */ + batch = VectorEngine(planstate); - /* - * if the tuple is null, then we assume there is nothing more to - * process so we just end the loop... - */ + /* + * 如果元组为null,则我们假设没有更多要处理的内容,因此我们就结束循环... + */ + if (BatchIsNull(batch)) { ExecEarlyFree(planstate); break; } - /* - * If we have a junk filter, then project a new tuple with the junk - * removed. + /* + * 如果我们有一个垃圾过滤器,那么投影一个新的元组,去除垃圾部分。 * - * Store this new "clean" tuple in the junkfilter's resultSlot. - * (Formerly, we stored it back over the "dirty" tuple, which is WRONG - * because that tuple slot has the wrong descriptor.) + * 将这个新的“干净”元组存储在垃圾过滤器的 resultSlot 中。 + * (以前,我们将其存储回“脏”元组,这是错误的,因为该元组槽具有错误的描述符。) */ + #ifdef ENABLE_MULTIPLE_NDOES if (estate->es_junkFilter != NULL && !StreamTopConsumerAmI() && !StreamThreadAmI()) { #else @@ -2393,39 +2405,38 @@ static void ExecuteVectorizedPlan(EState *estate, PlanState *planstate, CmdType t_thrd.pgxc_cxt.GlobalNetInstr = planstate->instrument; } - /* - * If we are supposed to send the tuple somewhere, do so. (In - * practice, this is probably always the case at this point.) - */ + /* + * 如果我们应该将元组发送到某个地方,就这样做。(实际上,在这一点上,这很可能总是发生的情况。) + */ + if (sendTuples && !u_sess->exec_cxt.executorStopFlag) { (*dest->sendBatch)(batch, dest); } t_thrd.pgxc_cxt.GlobalNetInstr = NULL; - /* - * Count tuples processed, if this is a SELECT. (For other operation - * types, the ModifyTable plan node must count the appropriate - * events.) - */ + /* + * 如果这是一个SELECT语句,计算已处理的元组数。(对于其他操作类型,ModifyTable计划节点必须计算相应的事件。) + */ + if (operation == CMD_SELECT) { estate->es_processed += batch->m_rows; } - /* - * check our tuple count.. if we've processed the proper number then - * quit, else loop again and process more tuples. Zero numberTuples - * means no limit. - */ + /* + * 检查我们的元组计数..如果我们已经处理了正确数量的元组则退出,否则再次循环并处理更多元组。numberTuples为零表示没有限制。 + */ + current_tuple_count += batch->m_rows; if (numberTuples && numberTuples == current_tuple_count) { break; } } - /* - * if current plan is working for expression, no need to collect instrumentation. - */ + /* + * 如果当前计划正在为表达式工作,则无需收集仪器数据。 + */ + if (estate->es_instrument != INSTRUMENT_NONE && u_sess->instr_cxt.global_instr && StreamTopConsumerAmI()) { int64 peak_memory = (uint64)(t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->peakChunksQuery - t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->initMemInChunks) @@ -2435,8 +2446,9 @@ static void ExecuteVectorizedPlan(EState *estate, PlanState *planstate, CmdType } /* - * ExecRelCheck --- check that tuple meets constraints for result relation + * ExecRelCheck --- 检查元组是否符合结果关系的约束条件 */ + static const char *ExecRelCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate) { Relation rel = resultRelInfo->ri_RelationDesc; @@ -2447,40 +2459,41 @@ static const char *ExecRelCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *sl List *qual = NIL; int i; - /* - * If first time through for this result relation, build expression - * nodetrees for rel's constraint expressions. Keep them in the per-query - * memory context so they'll survive throughout the query. - */ + /* + * 如果这是处理该结果关系的第一次,构建关系约束表达式的表达式树。 + * 将它们保存在每个查询的内存上下文中,以便它们在整个查询过程中保持有效。 + */ + if (resultRelInfo->ri_ConstraintExprs == NULL) { oldContext = MemoryContextSwitchTo(estate->es_query_cxt); resultRelInfo->ri_ConstraintExprs = (List **)palloc(ncheck * sizeof(List *)); for (i = 0; i < ncheck; i++) { - /* ExecQual wants implicit-AND form */ + /* ExecQual 需要隐式的 AND 形式 */ qual = make_ands_implicit((Expr *)stringToNode(check[i].ccbin)); resultRelInfo->ri_ConstraintExprs[i] = (List *)ExecPrepareExpr((Expr *)qual, estate); } (void)MemoryContextSwitchTo(oldContext); } - /* - * We will use the EState's per-tuple context for evaluating constraint - * expressions (creating it if it's not already there). - */ + /* + * 我们将使用 EState 的每个元组上下文来评估约束表达式(如果尚不存在,则创建它)。 + */ + econtext = GetPerTupleExprContext(estate); - /* Arrange for econtext's scan tuple to be the tuple under test */ + /* 确保 econtext 的扫描元组是待测试的元组 */ + econtext->ecxt_scantuple = slot; - /* And evaluate the constraints */ + /* 然后评估约束条件 */ + for (i = 0; i < ncheck; i++) { qual = resultRelInfo->ri_ConstraintExprs[i]; - /* - * NOTE: SQL92 specifies that a NULL result from a constraint - * expression is not to be treated as a failure. Therefore, tell - * ExecQual to return TRUE for NULL. - */ + /* + * 注意:SQL92规定,约束表达式的NULL结果不应视为失败。因此,告诉ExecQual对于NULL返回TRUE。 + */ + if (!ExecQual(qual, econtext, true)) { return check[i].ccname; } @@ -2492,6 +2505,7 @@ static const char *ExecRelCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *sl void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate) { + // 获取关系描述符和约束信息 Relation rel = resultRelInfo->ri_RelationDesc; TupleDesc tupdesc = RelationGetDescr(rel); TupleConstr *constr = tupdesc->constr; @@ -2500,28 +2514,26 @@ void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState Bitmapset *updatedCols = NULL; int maxfieldlen = 64; + // 断言确保约束信息存在 Assert(constr); - /* Get the Table Accessor Method*/ - Assert(slot != NULL && slot->tts_tupleDescriptor != NULL); + // 检查是否有 NOT NULL 约束,并处理 if (constr->has_not_null) { int natts = tupdesc->natts; int attrChk; for (attrChk = 1; attrChk <= natts; attrChk++) { + // 如果属性为 NOT NULL 且当前元组中的值为空,则报错 if (tupdesc->attrs[attrChk - 1]->attnotnull && tableam_tslot_attisnull(slot, attrChk)) { - char *val_desc = NULL; - bool rel_masked = u_sess->attr.attr_security.Enable_Security_Policy && - is_masked_relation_enabled(RelationGetRelid(rel)); - + // 获取插入和更新的列集合,并构建错误信息 insertedCols = GetInsertedColumns(resultRelInfo, estate); updatedCols = GetUpdatedColumns(resultRelInfo, estate); modifiedCols = bms_union(insertedCols, updatedCols); - if (!rel_masked) { - val_desc = - ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, tupdesc, modifiedCols, maxfieldlen); - } + // 构建包含错误列值的错误描述 + char *val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, tupdesc, modifiedCols, maxfieldlen); + + // 报告 NOT NULL 约束违反错误 ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("null value in column \"%s\" violates not-null constraint", NameStr(tupdesc->attrs[attrChk - 1]->attname)), @@ -2530,25 +2542,24 @@ void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState } } + // 检查其他约束条件,并处理 if (constr->num_check == 0) { return; } + // 检查 CHECK 约束条件,并返回失败的 CHECK 约束 const char *failed = ExecRelCheck(resultRelInfo, slot, estate); if (failed == NULL) { return; } - char *val_desc = NULL; - bool rel_masked = u_sess->attr.attr_security.Enable_Security_Policy && - is_masked_relation_enabled(RelationGetRelid(rel)); + // 获取插入和更新的列集合,并构建错误信息 insertedCols = GetInsertedColumns(resultRelInfo, estate); updatedCols = GetUpdatedColumns(resultRelInfo, estate); modifiedCols = bms_union(insertedCols, updatedCols); - if (!rel_masked) { - val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, tupdesc, modifiedCols, maxfieldlen); - } - /* client_min_messages < NOTICE show error details. */ + char *val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, tupdesc, modifiedCols, maxfieldlen); + + // 根据 client_min_messages 设置,报告 CHECK 约束违反错误 if (client_min_messages < NOTICE) { ereport(ERROR, (errmodule(MOD_EXECUTOR), errcode(ERRCODE_CHECK_VIOLATION), @@ -2569,24 +2580,18 @@ void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState } /* - * ExecBuildSlotValueDescription -- construct a string representing a tuple + * ExecBuildSlotValueDescription -- 构造表示元组的字符串 * - * This is intentionally very similar to BuildIndexValueDescription, but - * unlike that function, we truncate long field values (to at most maxfieldlen - * bytes). That seems necessary here since heap field values could be very - * long, whereas index entries typically aren't so wide. + * 这个函数故意与 BuildIndexValueDescription 非常相似,但是与该函数不同,我们截断长字段值(至多maxfieldlen字节)。 + * 这似乎是必要的,因为堆字段的值可能非常长,而索引条目通常不那么宽。 * - * Also, unlike the case with index entries, we need to be prepared to ignore - * dropped columns. We used to use the slot's tuple descriptor to decode the - * data, but the slot's descriptor doesn't identify dropped columns, so we - * now need to be passed the relation's descriptor. + * 另外,与索引条目的情况不同,我们需要准备忽略被删除的列。我们过去使用槽的元组描述符来解码数据, + * 但是槽的描述符不识别被删除的列,所以我们现在需要传递关系的描述符。 * - * Note that, like BuildIndexValueDescription, if the user does not have - * permission to view any of the columns involved, a NULL is returned. Unlike - * BuildIndexValueDescription, if the user has access to view a subset of the - * column involved, that subset will be returned with a key identifying which - * columns they are. + * 请注意,与BuildIndexValueDescription类似,如果用户没有权限查看涉及的任何列,则返回NULL。 + * 与BuildIndexValueDescription不同的是,如果用户有权查看涉及的列的子集,那么将返回一个带有标识哪些列的键的子集。 */ + char *ExecBuildSlotValueDescription(Oid reloid, TupleTableSlot *slot, TupleDesc tupdesc, Bitmapset *modifiedCols, int maxfieldlen) { @@ -2604,25 +2609,25 @@ char *ExecBuildSlotValueDescription(Oid reloid, TupleTableSlot *slot, TupleDesc appendStringInfoChar(&buf, '('); - /* - * Check if the user has permissions to see the row. Table-level SELECT - * allows access to all columns. If the user does not have table-level - * SELECT then we check each column and include those the user has SELECT - * rights on. Additionally, we always include columns the user provided - * data for. - */ + /* + * 检查用户是否有权限查看行。表级别的SELECT允许访问所有列。如果用户没有表级别的SELECT权限, + * 则我们检查每个列,并包括用户具有SELECT权限的列。此外,我们总是包括用户提供数据的列。 + */ + aclresult = pg_class_aclcheck(reloid, GetUserId(), ACL_SELECT); if (aclresult != ACLCHECK_OK) { - /* Set up the buffer for the column list */ + /* 为列列表设置缓冲区 */ initStringInfo(&collist); appendStringInfoChar(&collist, '('); } else { table_perm = any_perm = true; } - /* Make sure the tuple is fully deconstructed */ + /* 确保元组已完全解构 */ + + + /* 获取表的访问器方法 */ - /* Get the Table Accessor Method*/ Assert(slot != NULL && slot->tts_tupleDescriptor != NULL); tableam_tslot_getallattrs(slot); @@ -2631,18 +2636,18 @@ char *ExecBuildSlotValueDescription(Oid reloid, TupleTableSlot *slot, TupleDesc char *val = NULL; int vallen; - /* ignore dropped columns */ + /* 忽略被删除的列 */ + if (tupdesc->attrs[i]->attisdropped) { continue; } if (!table_perm) { - /* - * No table-level SELECT, so need to make sure they either have - * SELECT rights on the column or that they have provided the - * data for the column. If not, omit this column from the error - * message. - */ + /* + * 没有表级别的SELECT权限,因此需要确保用户要么具有列的SELECT权限,要么提供了该列的数据。 + * 如果不满足条件,就从错误消息中省略该列。 + */ + aclresult = pg_attribute_aclcheck(reloid, tupdesc->attrs[i]->attnum, GetUserId(), ACL_SELECT); if (bms_is_member(tupdesc->attrs[i]->attnum - FirstLowInvalidHeapAttributeNumber, modifiedCols) || aclresult == ACLCHECK_OK) { @@ -2675,7 +2680,8 @@ char *ExecBuildSlotValueDescription(Oid reloid, TupleTableSlot *slot, TupleDesc write_comma = true; } - /* truncate if needed */ + /* 如果需要,进行截断 */ + vallen = strlen(val); if (vallen <= maxfieldlen) { appendStringInfoString(&buf, val); @@ -2687,7 +2693,8 @@ char *ExecBuildSlotValueDescription(Oid reloid, TupleTableSlot *slot, TupleDesc } } - /* If we end up with zero columns being returned, then return NULL. */ + /* 如果最终没有返回任何列,则返回NULL。 */ + if (!any_perm) { return NULL; } @@ -2705,8 +2712,9 @@ char *ExecBuildSlotValueDescription(Oid reloid, TupleTableSlot *slot, TupleDesc } /* - * ExecFindRowMark -- find the ExecRowMark struct for given rangetable index + * ExecFindRowMark -- 查找给定范围表索引的ExecRowMark结构 */ + ExecRowMark *ExecFindRowMark(EState *estate, Index rti) { ListCell *lc = NULL; @@ -2724,12 +2732,12 @@ ExecRowMark *ExecFindRowMark(EState *estate, Index rti) } /* - * ExecBuildAuxRowMark -- create an ExecAuxRowMark struct + * ExecBuildAuxRowMark -- 创建一个ExecAuxRowMark结构 * - * Inputs are the underlying ExecRowMark struct and the targetlist of the - * input plan node (not planstate node!). We need the latter to find out - * the column numbers of the resjunk columns. + * 输入参数是底层的ExecRowMark结构和输入计划节点的目标列表(不是计划状态节点!)。 + * 我们需要后者来找出结果冗余列的列号。 */ + ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist) { ExecAuxRowMark *aerm = (ExecAuxRowMark *)palloc0(sizeof(ExecAuxRowMark)); @@ -2738,11 +2746,13 @@ ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist) aerm->rowmark = erm; - /* Look up the resjunk columns associated with this rowmark */ + /* 查找与此行标记关联的结果冗余列 */ + if (erm->relation) { Assert(erm->markType != ROW_MARK_COPY); - /* if child rel, need tableoid */ + /* 如果是子关系,需要表OID */ + if (erm->rti != erm->prti || RelationIsPartitioned(erm->relation)) { rc = snprintf_s(resname, sizeof(resname), sizeof(resname) - 1, "tableoid%u", erm->rowmarkId); securec_check_ss(rc, "\0", "\0"); @@ -2763,7 +2773,8 @@ ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist) errmsg("could not find bucketid junk %s column when build RowMark", resname))); } } - /* always need ctid for real relations */ + /* 对于真实关系,始终需要ctid */ + rc = snprintf_s(resname, sizeof(resname), sizeof(resname) - 1, "ctid%u", erm->rowmarkId); securec_check_ss(rc, "\0", "\0"); @@ -2788,93 +2799,110 @@ ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist) return aerm; } +/* + * EvalPlanQualUHeap - 在行级触发器上执行计划中的限制条件 + * + * estate:EState结构,保存了执行环境的状态信息 + * epqstate:EPQState结构,保存了过程化计划限制条件的状态信息 + * relation:当前操作的关系的描述符 + * rti:范围表索引,标识当前关系在查询计划中的位置 + * tid:需要处理的tuple的ItemPointer + * priorXmax:上一个事务的XID + * + * 返回值:一个TupleTableSlot,包含了符合限制条件的tuple的数据 + */ TupleTableSlot *EvalPlanQualUHeap(EState *estate, EPQState *epqstate, Relation relation, Index rti, ItemPointer tid, TransactionId priorXmax) { - TupleTableSlot *slot = NULL; - UHeapTuple copyTuple = NULL; + TupleTableSlot *slot = NULL; + UHeapTuple copyTuple = NULL; + // 断言确保范围表索引大于0 Assert(rti > 0); - copyTuple = - UHeapLockUpdated(estate->es_output_cid, relation, LockTupleExclusive, tid, priorXmax, estate->es_snapshot); + // 获取锁并获取更新后的tuple + copyTuple = UHeapLockUpdated(estate->es_output_cid, relation, LockTupleExclusive, tid, priorXmax, estate->es_snapshot); + // 如果找不到tuple,返回NULL if (copyTuple == NULL) { return NULL; } + // 断言确保获取的tuple为UHeap类型 Assert(copyTuple->tupTableType = UHEAP_TUPLE); + // 更新传入的tid为获取到的tuple的ctid *tid = copyTuple->ctid; + // 开始处理过程化计划的限制条件 EvalPlanQualBegin(epqstate, estate); + // 将获取到的tuple设置到过程化计划限制条件状态中 EvalPlanQualSetTuple(epqstate, rti, copyTuple); + // 获取过程化计划的行标记信息 EvalPlanQualFetchRowMarks(epqstate); + // 执行过程化计划限制条件,并获取符合条件的tuple的slot slot = EvalPlanQualNext(epqstate); - // materialize the slot + // 将slot中的数据materialize,即将slot中的数据转换为可见的tuple if (!TupIsNull(slot)) { ExecGetUHeapTupleFromSlot(slot); } + // 将tuple从过程化计划限制条件状态中移除 EvalPlanQualSetTuple(epqstate, rti, NULL); return slot; } /* - * EvalPlanQual logic --- recheck modified tuple(s) to see if we want to - * process the updated version under READ COMMITTED rules. + * EvalPlanQual逻辑 —— 重新检查修改的元组,以确定是否按照READ COMMITTED规则处理更新版本。 * - * See gausskernel/runtime/executor/README for some info about how this works. + * 有关此操作的详细信息,请参阅gausskernel/runtime/executor/README。 * - * Check a modified tuple to see if we want to process its updated version - * under READ COMMITTED rules. + * 检查修改过的元组,以确定是否按照READ COMMITTED规则处理其更新版本。 * - * estate - outer executor state data - * epqstate - state for EvalPlanQual rechecking - * relation - table containing tuple - * rti - rangetable index of table containing tuple - * lockmode - requested tuple lock mode - * *tid - t_ctid from the outdated tuple (ie, next updated version) - * priorXmax - t_xmax from the outdated tuple + * estate - 外部执行器状态数据 + * epqstate - 用于EvalPlanQual重新检查的状态 + * relation - 包含元组的表 + * rti - 元组所在表的范围表索引 + * lockmode - 请求的元组锁定模式 + * *tid - 过时元组的t_ctid(即,下一个更新版本) + * priorXmax - 过时元组的t_xmax * - * *tid is also an output parameter: it's modified to hold the TID of the - * latest version of the tuple (note this may be changed even on failure) + * *tid也是一个输出参数:它被修改为持有元组的最新版本的TID(请注意,即使失败,它也可能被修改) * - * Returns a slot containing the new candidate update/delete tuple, or - * NULL if we determine we shouldn't process the row. + * 返回一个包含新候选更新/删除元组的槽,如果确定不应处理该行,则返回NULL。 * - * Note: properly, lockmode should be declared as enum LockTupleMode, - * but we use "int" to avoid having to include heapam.h in executor.h. + * 注意:实际上,lockmode应该被声明为枚举类型LockTupleMode, + * 但我们使用"int"来避免在executor.h中引入heapam.h。 */ + + TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate, Relation relation, Index rti, int lockmode, ItemPointer tid, TransactionId priorXmax, bool partRowMoveUpdate) { TupleTableSlot *slot = NULL; Tuple copyTuple; + // 断言确保范围表索引大于0 Assert(rti > 0); /* - * Get and lock the updated version of the row; if fail, return NULL. + * 获取并锁定行的更新版本;如果失败,返回NULL。 */ copyTuple = tableam_tuple_lock_updated(estate->es_output_cid, relation, lockmode, tid, priorXmax, estate->es_snapshot); + // 如果找不到tuple,返回NULL if (copyTuple == NULL) { /* - * The tuple has been deleted or update in row movement case. + * 在行移动更新的情况下,可能是一个删除了原始分区中的元组并将其插入新分区中的行移动更新操作, + * 或者我们可以在将要删除或更新的元组上添加锁以避免抛出异常。 */ if (partRowMoveUpdate) { - /* - * the may be a row movement update action which delete tuple from original - * partition and insert tuple to new partition or we can add lock on the tuple - * to be delete or updated to avoid throw exception. - */ ereport(ERROR, (errcode(ERRCODE_TRANSACTION_ROLLBACK), errmsg("partition table update conflict"), errdetail("disable row movement of table can avoid this conflict"))); @@ -2883,47 +2911,41 @@ TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate, Relation relati } /* - * For UPDATE/DELETE we have to return tid of actual row we're executing - * PQ for. + * 对于UPDATE/DELETE操作,我们必须返回我们正在为其执行过程化计划限制条件的实际行的TID。 */ *tid = ((HeapTuple)copyTuple)->t_self; /* - * Need to run a recheck subquery. Initialize or reinitialize EPQ state. + * 需要运行一个重新检查的子查询。初始化或重新初始化EPQ状态。 */ EvalPlanQualBegin(epqstate, estate); /* - * Free old test tuple, if any, and store new tuple where relation's scan - * node will see it + * 释放旧的测试元组(如果有的话),并将新元组存储在关系的扫描节点将要查看的位置 */ EvalPlanQualSetTuple(epqstate, rti, copyTuple); /* - * Fetch any non-locked source rows + * 获取任何未锁定的源行 */ EvalPlanQualFetchRowMarks(epqstate); /* - * Run the EPQ query. We assume it will return at most one tuple. + * 运行EPQ查询。我们假设它最多返回一个元组。 */ slot = EvalPlanQualNext(epqstate); /* - * If we got a tuple, force the slot to materialize the tuple so that it - * is not dependent on any local state in the EPQ query (in particular, - * it's highly likely that the slot contains references to any pass-by-ref - * datums that may be present in copyTuple). As with the next step, this - * is to guard against early re-use of the EPQ query. + * 如果获取到了一个元组,强制slot将其materialize,以便它不依赖于EPQ查询中的任何本地状态 + * (特别是,slot中高度可能包含在copyTuple中存在的传址引用数据)。与下一步骤一样, + * 这是为了防止EPQ查询的早期重用。 */ if (!TupIsNull(slot)) { (void)tableam_tslot_get_tuple_from_slot(relation, slot); } /* - * Clear out the test tuple. This is needed in case the EPQ query is - * re-used to test a tuple for a different relation. (Not clear that can - * really happen, but let's be safe.) + * 清除测试元组。如果EPQ查询被重用以测试不同关系的元组,这是必要的。(不太清楚真的会发生,但是为了安全起见。) */ EvalPlanQualSetTuple(epqstate, rti, NULL); @@ -2931,22 +2953,20 @@ TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate, Relation relati } /* - * Fetch a copy of the newest version of an outdated tuple + * 获取过时元组的最新版本的副本 * - * cid - command ID - * relation - table containing tuple - * lockmode - requested tuple lock mode - * *tid - t_ctid from the outdated tuple (ie, next updated version) - * priorXmax - t_xmax from the outdated tuple + * cid - 命令ID + * relation - 包含元组的表 + * lockmode - 请求的元组锁定模式 + * *tid - 过时元组的t_ctid(即,下一个更新版本) + * priorXmax - 过时元组的t_xmax * - * Returns a palloc'd copy of the newest tuple version, or NULL if we find - * that there is no newest version (ie, the row was deleted not updated). - * If successful, we have locked the newest tuple version, so caller does not - * need to worry about it changing anymore. + * 返回值:最新版本元组的palloc'd副本,如果发现没有最新版本(即,该行被删除而不是更新),则返回NULL。 + * 如果成功,我们已经锁定了最新版本的元组,所以调用者无需再担心它会再次改变。 * - * Note: properly, lockmode should be declared as enum LockTupleMode, - * but we use "int" to avoid having to include heapam.h in executor.h. + * 注意:正确的做法是,lockmode应该被声明为枚举类型LockTupleMode,但是我们使用"int"来避免在executor.h中引入heapam.h。 */ + HeapTuple heap_lock_updated(CommandId cid, Relation relation, int lockmode, ItemPointer tid, TransactionId priorXmax) { HeapTuple copyTuple = NULL; -- 2.34.1