【OpenGauss源码学习 —— 执行算子(Result 算子)】

执行算子(Result 算子)

  • 控制算子
  • Result 算子
    • ExecInitResult 函数
      • ResultState 结构体
      • ExecInitResultTupleSlot 函数
      • ExecAllocTableSlot函数
    • ExecResult 函数
      • TupleTableSlot 结构体
      • ExecProcNode 函数
      • ExecProcNodeByType 函数
      • ExecProject 函数
    • ExecEndResult 函数
      • ExecFreeExprContext 函数
      • ExecClearTuple 函数
      • ExecEndNode函数
    • ExecResultMarkPos 函数
      • ExecMarkpos 函数
    • ExecResultRestrPos 函数
    • ExecReScanResult 函数
      • ExecReScan 函数

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

控制算子

  控制算子主要用于执行特殊流程,这类流程通常包含两个以上输入,如 Union 操作,需要把多个子结果(输入),合并成一个。控制算子有多种,如下所示。

算子名称说 明
Result算子处理只有一个结果或过滤条件是常量的流程
Append算子处理包含一个或多个子计划的链表
BitmapAnd算子对结果做And位图运算
BitmapOr算子对结果做Or位图运算
RecursionUnion算子递归处理UNION语句

Result 算子

  Result 算子对应的代码源文件是 “nodeResult.cpp”,用于处理只有一个结果(如通过SELECT调用可执行函数或表达式,或者INSERT语句只包含Values字句)或者WHERE表达式中的结果是常量(如“SELECT * FROM emp WHERE 2 > 1”,过滤条件“2 > 1”是常量只需要计算一次即可)的流程。由于openGauss没有提供单独的投影算子(Projection)和选择算子(Selection),Result算子也可以起到类似的作用。Result算子提供的主要函数如下所示。

主要函数说 明
ExecInitResult初始化状态机
ExecResult迭代执行算子
ExecEndResult结束清理
ExecResultMarkPos标记扫描位置
ExecResultRestrPos重置扫描位置
ExecReScanResult重置执行计划

  为了更好地理解和学习 Result 算子的相关操作,我们还是从一个实际案例来入手吧。首先执行以下 sql 语句:

-- 创建 employees 表
CREATE TABLE employees (id SERIAL PRIMARY KEY,name VARCHAR(50),age INT,salary DECIMAL(10, 2)
);-- 插入一些数据
INSERT INTO employees (name, age, salary) VALUES('Alice', 28, 60000.00),('Bob', 35, 75000.00),('Charlie', 22, 45000.00);-- 执行查询
SELECT * FROM employees;-- 查询结果id |  name   | age |  salary
----+---------+-----+----------1 | Alice   |  28 | 60000.002 | Bob     |  35 | 75000.003 | Charlie |  22 | 45000.00
(3 rows)

ExecInitResult 函数

  ExecInitResult 函数是用于初始化 Result 算子节点(结果节点)的运行时状态信息的。在 PostgreSQL 查询计划中,Result 节点用于产生查询的最终结果,并且通常是查询计划树的最后一个节点。该函数负责为 Result 节点创建一个运行时状态结构体(ResultState),初始化相关属性,并初始化其子节点的运行时状态。
  首先,执行以下SQL语句,并在 ExecInitResult 函数上打上断点:

INSERT INTO employees (name, age, salary) VALUES ('Tom', 23, 60000.00);

  函数的调用关系如下:

在这里插入图片描述
  ExecInitResult 函数源码如下:(路径:src/gausskernel/runtime/executor/nodeResult.cpp

/* ----------------------------------------------------------------*		ExecInitResult**		为由规划器生成的结果节点创建运行时状态信息,并初始化外部关系*		(子节点)。* ----------------------------------------------------------------*/
ResultState* ExecInitResult(BaseResult* node, EState* estate, int eflags)
{/* 检查是否有不支持的标志 */Assert(!(eflags & (EXEC_FLAG_MARK | EXEC_FLAG_BACKWARD)) || outerPlan(node) != NULL);/** 创建状态结构体*/ResultState* resstate = makeNode(ResultState);resstate->ps.plan = (Plan*)node;resstate->ps.state = estate;resstate->rs_done = false;resstate->rs_checkqual = (node->resconstantqual == NULL) ? false : true;/** 杂项初始化** 为节点创建表达式上下文*/ExecAssignExprContext(estate, &resstate->ps);resstate->ps.ps_TupFromTlist = false;/** 元组表初始化*/ExecInitResultTupleSlot(estate, &resstate->ps);/** 初始化子表达式*/resstate->ps.targetlist = (List*)ExecInitExpr((Expr*)node->plan.targetlist, (PlanState*)resstate);resstate->ps.qual = (List*)ExecInitExpr((Expr*)node->plan.qual, (PlanState*)resstate);resstate->resconstantqual = ExecInitExpr((Expr*)node->resconstantqual, (PlanState*)resstate);/** 初始化子节点*/outerPlanState(resstate) = ExecInitNode(outerPlan(node), estate, eflags);/** 我们不使用内部计划*/Assert(innerPlan(node) == NULL);/** 初始化元组类型和投影信息* 节点Result中没有涉及关系,将默认的表访问方法类型设置为HEAP*/ExecAssignResultTypeFromTL(&resstate->ps, TAM_HEAP);ExecAssignProjectionInfo(&resstate->ps, NULL);return resstate;
}

  ExecInitResult 函数的三个入参分别是:
  BaseResult* node: 这是指向查询计划树中的 Result 节点的指针BaseResultResult 节点的基本结构体,包含了 Plan 结构体的成员,以及与结果节点相关的属性。
在这里插入图片描述
  EState* estate: 这是查询的执行状态,包含了在查询执行过程中需要的上下文信息,例如内存管理、表达式计算上下文等。
在这里插入图片描述
  int eflags: 这是一个标志位,用于指示执行的参数。通常包含执行标志的组合,例如 EXEC_FLAG_MARKEXEC_FLAG_BACKWARD

ResultState 结构体

  ResultState 结构体是用于执行查询计划中的 Result 节点的状态信息。在查询执行引擎中,每个查询计划节点都有相应的状态结构体来跟踪执行过程中的状态和信息。Result 节点表示一个结果集,它可能包含了一些常量表达式过滤条件,需要根据这些信息生成最终的查询结果

/* ----------------*	 ResultState 信息* ----------------*/
typedef struct ResultState {PlanState ps;               /* 这个结构体的第一个字段是 NodeTag */ExprState* resconstantqual; /* 用于评估常量条件表达式的状态 */bool rs_done;               /* 是否完成了所有的输出 */bool rs_checkqual;          /* 是否需要检查过滤条件 */
} ResultState;

  Result 节点被定义成如图 6-22 所示的样子,除了继承Plan 节点的基本属性外,还扩展定义了 resconstantqual 字段。顾名思义,该字段保存只需计算一次的常量表达式。
在这里插入图片描述

ExecInitResultTupleSlot 函数

  ExecInitResultTupleSlot 函数的作用是为执行 Result 节点时准备一个用于存储结果元组的槽(slot)。在查询执行过程中,每个查询计划节点都会有一个或多个槽,用于存储不同阶段产生的元组数据。函数源码如下:(路径:src/gausskernel/runtime/executor/execTuples.cpp

/* ----------------*		ExecInitResultTupleSlot* ----------------*/
void ExecInitResultTupleSlot(EState* estate, PlanState* plan_state, TableAmType tam)
{plan_state->ps_ResultTupleSlot = ExecAllocTableSlot(&estate->es_tupleTable, tam);
}

  调试信息如下:
在这里插入图片描述

ExecAllocTableSlot函数

  ExecAllocTableSlot 函数由 ExecInitResultTupleSlot 函数调用。函数源码如下:(路径:src/gausskernel/runtime/executor/execTuples.cpp

/* --------------------------------*		ExecAllocTableSlot**		Create a tuple table slot within a tuple table (which is just a List).* --------------------------------*/
TupleTableSlot* ExecAllocTableSlot(List** tuple_table, TableAmType tupslotTableAm)
{TupleTableSlot* slot = MakeTupleTableSlot();*tuple_table = lappend(*tuple_table, slot);slot->tts_tupslotTableAm = tupslotTableAm;return slot;
}

  调式信息如下:
在这里插入图片描述

ExecResult 函数

   ExecResult 函数这实现了执行 Result 节点的逻辑。它首先检查常量条件,如果不满足常量条件,就会返回 NULL,表示没有满足条件的结果。然后它会处理从外部计划获取的元组,通过投影表达式进行计算,然后返回投影的结果。如果投影产生了多个结果,会逐个返回,直到没有更多结果。最后,如果没有外部计划,它会生成常量目标列表中的结果。这个函数在执行查询计划时负责处理 Result 节点的数据生成和处理。函数源码如下所示:(路径:src/gausskernel/runtime/executor/nodeResult.cpp

/* ----------------------------------------------------------------*		ExecResult(node)**		返回满足限制条件的外部计划产生的元组。由于带有右子树的结果节点*		不会被计划,我们完全忽略了右子树(暂时忽略)。-cim 10/7/89**		在进行任何处理之前,首先检查只包含常量子句的限制条件。*		如果常量限制条件不满足,则总是返回'nil'。* ----------------------------------------------------------------*/
TupleTableSlot* ExecResult(ResultState* node)
{TupleTableSlot* outer_tuple_slot = NULL;TupleTableSlot* result_slot = NULL;PlanState* outer_plan = NULL;ExprDoneCond is_done;ExprContext* econtext = node->ps.ps_ExprContext;/** 检查常量条件,如 (2 > 1),如果尚未进行过检查*/if (node->rs_checkqual) {bool qualResult = ExecQual((List*)node->resconstantqual, econtext, false);node->rs_checkqual = false;if (!qualResult) {node->rs_done = true;/** 标记这次常量限制条件检查失败,以备将来的特殊情况。当前仅在 GDS 外部扫描中使用。*/u_sess->exec_cxt.exec_result_checkqual_fail = true;return NULL;}}/** 检查是否仍在从前一个扫描元组中投影出元组(因为投影表达式中存在返回集合的函数)。* 如果是这样,尝试投影另一个元组。*/if (node->ps.ps_TupFromTlist) {result_slot = ExecProject(node->ps.ps_ProjInfo, &is_done);if (is_done == ExprMultipleResult) {return result_slot;}/* 完成这个源元组... */node->ps.ps_TupFromTlist = false;}/** 重置每个元组的内存上下文,以释放上一个元组循环中分配的表达式计算存储空间。* 注意,这只能在我们完成从扫描元组投影出元组后才能进行。*/ResetExprContext(econtext);/** 如果 rs_done 为 true,那么意味着我们被要求返回一个常量元组,* 并且我们在上次调用 ExecResult() 时已经执行了此操作,* 或者我们未通过常量限制条件检查。无论哪种方式,现在我们都完成了。*/while (!node->rs_done) {outer_plan = outerPlanState(node);if (outer_plan != NULL) {/** 检索来自外部计划的元组,直到没有更多元组为止。*/outer_tuple_slot = ExecProcNode(outer_plan);if (TupIsNull(outer_tuple_slot))return NULL;/** 准备计算投影表达式,这些表达式将期望以 OUTER 作为 varno 访问输入元组。*/econtext->ecxt_outertuple = outer_tuple_slot;if (node->ps.qual && !ExecQual(node->ps.qual, econtext, false))continue;} else {/** 如果我们没有外部计划,那么我们只是从一个常量目标列表中生成结果。只执行一次。*/node->rs_done = true;}/** 使用 ExecProject() 形成结果元组,并返回它 --- 除非投影产生了一个空集,* 在这种情况下,我们必须循环回来看是否还有更多的 outer_plan 元组。*/result_slot = ExecProject(node->ps.ps_ProjInfo, &is_done);if (is_done != ExprEndResult) {node->ps.ps_TupFromTlist = (is_done == ExprMultipleResult);return result_slot;}}return NULL;
}

  ExecResult 函数调用关系如下:
在这里插入图片描述

  ExecResult 函数迭代输出元组流程如下图所示:
在这里插入图片描述
在这里插入图片描述

TupleTableSlot 结构体

  TupleTableSlot 结构体表示一个元组槽,用于在数据库查询执行过程中存储元组数据。定义如下:(路径:src/include/executor/tuptable.h

typedef struct TupleTableSlot {NodeTag type;               /* 节点类型标记 */bool tts_isempty;           /* 槽是否为空 */bool tts_shouldFree;        /* 是否应该释放 tts_tuple 内存 */bool tts_shouldFreeMin;     /* 是否应该释放 tts_mintuple 内存 */bool tts_slow;              /* 用于 slot_deform_tuple 的保存状态 */Tuple tts_tuple;            /* 物理元组数据,若为虚拟元组则为 NULL */#ifdef PGXC/** PGXC 扩展,支持从远程 Datanode 发送的元组。*/char* tts_dataRow;          /* DataRow 格式的元组数据 */int tts_dataLen;            /* 数据行的实际长度 */bool tts_shouldFreeRow;     /* 是否应该释放 tts_dataRow 内存 */struct AttInMetadata* tts_attinmeta; /* 存储从 DataRow 提取值所需的信息 */Oid tts_xcnodeoid;          /* 获取数据行的节点的 Oid */MemoryContext tts_per_tuple_mcxt; /* 元组特定的内存上下文 */
#endifTupleDesc tts_tupleDescriptor;   /* 槽的元组描述符 */MemoryContext tts_mcxt;          /* 槽本身所在的内存上下文 */Buffer tts_buffer;               /* 元组的缓冲区,或者是 InvalidBuffer */int tts_nvalid;                  /* tts_values 中有效值的数量 */Datum* tts_values;               /* 当前每个属性的值 */bool* tts_isnull;                /* 当前每个属性的是否为 NULL 标志 */MinimalTuple tts_mintuple;       /* 最小元组数据,如果没有则为 NULL */HeapTupleData tts_minhdr;        /* 仅用于最小元组情况的工作空间 */long tts_off;                    /* 用于 slot_deform_tuple 的保存状态 */long tts_meta_off;               /* 用于 slot_deform_cmpr_tuple 的保存状态 */TableAmType tts_tupslotTableAm;  /* 槽的元组表类型 */
} TupleTableSlot;

ExecProcNode 函数

  ExecProcNode 函数作用是执行给定的计划节点,以返回一个(另一个)元组。源码如下:(路径:src/gausskernel/runtime/executor/execProcnode.cpp

/* ----------------------------------------------------------------*		ExecProcNode**		执行给定的节点以返回一个(另一个)元组。* ----------------------------------------------------------------*/
TupleTableSlot* ExecProcNode(PlanState* node)
{TupleTableSlot* result = NULL;// 检查是否发生中断CHECK_FOR_INTERRUPTS();MemoryContext old_context;/* 响应停止或取消信号。 */if (unlikely(executorEarlyStop())) {return NULL;}/* 切换到节点级内存上下文 */old_context = MemoryContextSwitchTo(node->nodeContext);// 如果有参数发生变化,执行重新扫描操作if (node->chgParam != NULL) { ExecReScan(node);       /* 交给 ReScan 处理 */}// 如果有仪器信息,开始节点执行计时if (node->instrument != NULL) {InstrStartNode(node->instrument);}// 如果需要存根执行,调用存根执行函数if (unlikely(planstate_need_stub(node))) {result = ExecProcNodeStub(node);} else {result = ExecProcNodeByType(node); // 否则,按节点类型执行}// 如果有仪器信息,记录节点执行信息if (node->instrument != NULL) {ExecProcNodeInstr(node, result);}// 切换回旧的内存上下文MemoryContextSwitchTo(old_context);// 增加行号计数node->ps_rownum++;return result;
}

ExecProcNodeByType 函数

  ExecProcNodeByType 函数,其作用是根据计划节点的类型执行相应类型的操作,以返回一个元组槽。源码如下:(路径:src/gausskernel/runtime/executor/execProcnode.cpp

TupleTableSlot* ExecProcNodeByType(PlanState* node)
{TupleTableSlot* result = NULL;switch (nodeTag(node)) {case T_ResultState:return ExecResult((ResultState*)node);case T_ModifyTableState:case T_DistInsertSelectState:return ExecModifyTable((ModifyTableState*)node);case T_AppendState:return ExecAppend((AppendState*)node);case T_MergeAppendState:return ExecMergeAppend((MergeAppendState*)node);case T_RecursiveUnionState:return ExecRecursiveUnion((RecursiveUnionState*)node);case T_SeqScanState:return ExecSeqScan((SeqScanState*)node);case T_IndexScanState:return ExecIndexScan((IndexScanState*)node);case T_IndexOnlyScanState:return ExecIndexOnlyScan((IndexOnlyScanState*)node);case T_BitmapHeapScanState:return ExecBitmapHeapScan((BitmapHeapScanState*)node);case T_TidScanState:return ExecTidScan((TidScanState*)node);case T_SubqueryScanState:return ExecSubqueryScan((SubqueryScanState*)node);case T_FunctionScanState:return ExecFunctionScan((FunctionScanState*)node);case T_ValuesScanState:return ExecValuesScan((ValuesScanState*)node);case T_CteScanState:return ExecCteScan((CteScanState*)node);case T_WorkTableScanState:return ExecWorkTableScan((WorkTableScanState*)node);case T_ForeignScanState:return ExecForeignScan((ForeignScanState*)node);case T_ExtensiblePlanState:return ExecExtensiblePlan((ExtensiblePlanState*)node);// ... 其他类型的执行操作default:ereport(ERROR,(errmodule(MOD_EXECUTOR),errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),errmsg("unrecognized node type: %d when executing executor node.", (int)nodeTag(node))));return NULL;}
}

ExecProject 函数

  ExecProject 函数的作用是执行一个投影操作,即根据表达式计算生成一个或多个结果。在查询执行计划中,投影操作用于生成新的元组,这些元组可能来自于底层的计划节点,并且可能涉及到表达式的计算。以下是 ExecProject 函数的基本作用:

  1. 执行投影操作:根据给定的表达式列表,在给定的上下文中计算这些表达式,并生成一个结果元组。
  2. 处理多个结果:表达式列表中的某些表达式可能返回集合,而不是单个值。在这种情况下,ExecProject 可能会返回多个结果。
  3. 返回结果槽ExecProject 函数返回一个 TupleTableSlot,其中包含生成的结果。如果投影操作产生多个结果,可以多次调用 ExecProject 来获取每个结果。
  4. 处理投影信息:函数需要一个 ProjectionInfo 结构,其中包含了投影表达式、目标列表等信息。这些信息用于进行表达式计算和结果生成。
  5. 表达式计算ExecProject 根据给定的表达式列表,在给定的上下文中计算每个表达式的值。这可能涉及到对关联的表进行访问、函数计算等操作。

  ExecProject 函数源码如下:(路径:src/gausskernel/runtime/executor/execQual.cpp

/** ExecProject**		projects a tuple based on projection info and stores*		it in the previously specified 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.*/
TupleTableSlot* ExecProject(ProjectionInfo* projInfo, ExprDoneCond* isDone)
{/** sanity checks*/Assert(projInfo != NULL);/** get the projection info we want*/TupleTableSlot *slot = projInfo->pi_slot;ExprContext *econtext = projInfo->pi_exprContext;/* Assume single result row until proven otherwise */if (isDone != NULL)*isDone = ExprSingleResult;/** 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);/** 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);}if (projInfo->pi_lastOuterVar > 0) {tableam_tslot_getsomeattrs(econtext->ecxt_outertuple, projInfo->pi_lastOuterVar);}if (projInfo->pi_lastScanVar > 0) {tableam_tslot_getsomeattrs(econtext->ecxt_scantuple, projInfo->pi_lastScanVar);}/** Assign simple Vars to result by direct extraction of fields from source* slots ... a mite ugly, but fast ...*/int numSimpleVars = projInfo->pi_numSimpleVars; // 获取投影信息中简单变量的数量if (numSimpleVars > 0) {Datum* values = slot->tts_values; // 获取当前槽中的数据值数组bool* isnull = slot->tts_isnull; // 获取当前槽中的空值标志数组int* varSlotOffsets = projInfo->pi_varSlotOffsets; // 获取投影信息中变量槽的偏移数组int* varNumbers = projInfo->pi_varNumbers; // 获取投影信息中变量编号数组int i;if (projInfo->pi_directMap) {// 特殊情况:直接映射,变量按顺序输出for (i = 0; i < numSimpleVars; i++) {char* slotptr = ((char*)econtext) + varSlotOffsets[i]; // 计算变量槽的指针位置TupleTableSlot* varSlot = *((TupleTableSlot**)slotptr); // 获取变量槽指针int varNumber = varNumbers[i] - 1; // 获取变量编号(从1开始)Assert (varNumber < varSlot->tts_tupleDescriptor->natts); // 断言:变量编号合法Assert (i < slot->tts_tupleDescriptor->natts); // 断言:投影元组的索引合法values[i] = varSlot->tts_values[varNumber]; // 将变量槽中的数据值赋给结果槽isnull[i] = varSlot->tts_isnull[varNumber]; // 将变量槽中的空值标志赋给结果槽}} else {// 需要考虑 varOutputCols[]int* varOutputCols = projInfo->pi_varOutputCols; // 获取投影信息中变量输出列的数组for (i = 0; i < numSimpleVars; i++) {char* slotptr = ((char*)econtext) + varSlotOffsets[i]; // 计算变量槽的指针位置TupleTableSlot* varSlot = *((TupleTableSlot**)slotptr); // 获取变量槽指针int varNumber = varNumbers[i] - 1; // 获取变量编号(从1开始)int varOutputCol = varOutputCols[i] - 1; // 获取变量输出列编号(从1开始)Assert (varNumber < varSlot->tts_tupleDescriptor->natts); // 断言:变量编号合法Assert (varOutputCol < slot->tts_tupleDescriptor->natts); // 断言:输出列编号合法values[varOutputCol] = varSlot->tts_values[varNumber]; // 将变量槽中的数据值赋给结果槽isnull[varOutputCol] = varSlot->tts_isnull[varNumber]; // 将变量槽中的空值标志赋给结果槽}}}/** If there are any generic expressions, evaluate them.  It's possible* 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 */}/** Successfully formed a result row.  Mark the result slot as containing a* valid virtual tuple.*/return ExecStoreVirtualTuple(slot);
}

ExecEndResult 函数

   ExecEndResult 函数的作用是在执行完一个操作节点(可能是查询的一部分)后,释放与该节点相关的资源,包括释放表达式计算上下文、清空结果元组槽,并完成当前节点的结束操作,包括关闭子计划。这有助于确保数据库系统能够高效地管理内存和资源,并保持正确的状态转换。函数源码如下:(路径:src/gausskernel/runtime/executor/nodeResult.cpp

/* ----------------------------------------------------------------*		ExecEndResult**		通过C例程分配的存储空间* ----------------------------------------------------------------*/
void ExecEndResult(ResultState* node)
{/** 释放表达式计算上下文*/ExecFreeExprContext(&node->ps);/** 清空元组表*/(void)ExecClearTuple(node->ps.ps_ResultTupleSlot);/** 关闭子计划*/ExecEndNode(outerPlanState(node));
}

ExecEndResult 函数包含三部分:

  1. ExecFreeExprContext(&node->ps);:在执行查询期间,数据库引擎可能会使用表达式上下文(exprcontext)来计算各种表达式,例如查询条件计算列等。这一行代码释放了与当前节点相关的表达式上下文,以释放与表达式计算相关的内存。
  2. (void)ExecClearTuple(node->ps.ps_ResultTupleSlot);:在查询中,结果元组槽用于存储当前节点计算出的结果元组。这一行代码清空了结果元组槽,以释放其中存储的元组数据。
  3. ExecEndNode(outerPlanState(node));OpenGauss 使用计划树plan tree)来表示查询计划,其中包含了一系列操作节点,包括顺序扫描聚合连接等。每个操作节点都可能有一个或多个子计划。这一行代码调用了 ExecEndNode 函数,来处理当前节点的结束操作,包括关闭相关的子计划。

ExecFreeExprContext 函数

  ExecFreeExprContext 函数并没有真正释放 ExprContext 所占用的内存,但它会断开计划节点和表达式上下文之间的关联。这意味着 ExprContext 对象仍然存在,但不再与特定的计划节点相关联,从而允许其他操作继续进行,或者在适当的时候由其他地方进行内存释放操作。这可能是为了确保在某些情况下不会意外释放正在使用的内存,而是将释放操作推迟到更合适的时机。ExecFreeExprContext 函数源码如下:(路径:src/gausskernel/runtime/executor/execUtils.cpp

/* ----------------*		ExecFreeExprContext** 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.* ----------------*/
void ExecFreeExprContext(PlanState* planstate)
{/** Per above discussion, don't actually delete the ExprContext. We do* unlink it from the plan node, though.*/planstate->ps_ExprContext = NULL;
}

  调试信息如下:
在这里插入图片描述

ExecClearTuple 函数

  函数 ExecClearTuple 清除槽位中存储的元组数据,并在需要的情况下释放相关资源,例如物理元组、最小元组等。函数还负责将槽位标记为空,并进行一些与内存管理相关的操作。这有助于确保查询执行期间的资源管理和正确性。函数源码如下所示:(路径:src/gausskernel/runtime/executor/execTuples.cpp

/* --------------------------------*		ExecClearTuple**		该函数用于清除元组表中的一个槽位。**		注意:仅清除元组,不清除元组描述符(如果有的话)。* --------------------------------*/
TupleTableSlot* ExecClearTuple(TupleTableSlot* slot) /* 返回:传入的槽位,用于存储元组 */
{/** 检查合法性*/Assert(slot != NULL);/** 通过 TableAm 清除物理元组或最小元组,如果存在的话。*/if (slot->tts_shouldFree || slot->tts_shouldFreeMin){Assert(slot->tts_tupleDescriptor != NULL);tableam_tslot_clear(slot);}/* * 如果 tts_shouldFree 为 false,tts_tuple 可能仍然有效,原始调用者不希望该槽位释放元组。*/slot->tts_tuple = NULL;slot->tts_mintuple = NULL;slot->tts_shouldFree = false;slot->tts_shouldFreeMin = false;#ifdef PGXCif (slot->tts_shouldFreeRow) {pfree_ext(slot->tts_dataRow);}slot->tts_shouldFreeRow = false;slot->tts_dataRow = NULL;slot->tts_dataLen = -1;slot->tts_xcnodeoid = 0;
#endif/** 如果存在引用的缓冲区,释放引用的缓冲区的引用。*/if (BufferIsValid(slot->tts_buffer)) {ReleaseBuffer(slot->tts_buffer);}slot->tts_buffer = InvalidBuffer;/** 标记为空。*/slot->tts_isempty = true;slot->tts_nvalid = 0;// 在某些情况下,行解压会使用 slot->tts_per_tuple_mcxt,因此我们需要重置内存上下文。// 该内存上下文是由 PGXC 引入的,仅在函数 'slot_deform_datarow' 中使用。// PGXC 还在函数 'FetchTuple' 中进行了重置。因此是安全的。ResetSlotPerTupleContext(slot);return slot;
}

ExecEndNode函数

   ExecEndNode 函数的作用是在查询执行的过程中,对一个执行计划节点(PlanState)进行结束操作。这个函数会执行一系列的清理工作释放资源,以及结束与该节点相关的一些操作。函数源码如下:(路径:src/gausskernel/runtime/executor/execProcnode.cpp

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);}}// 如果是协调节点(IS_PGXC_COORDINATOR)且处于流顶消费者(StreamTopConsumerAmI()),结束仪器信息的循环if (node->instrument != NULL && IS_PGXC_COORDINATOR && StreamTopConsumerAmI()) {InstrEndLoop(node->instrument);}// 如果需要在计划节点上应用存根操作,执行存根操作并返回if (planstate_need_stub(node)) {ExecEndNodeStub(node);return;}// 根据节点类型终止计划节点的执行ExecEndNodeByType(node);
}

ExecResultMarkPos 函数

  ExecResultMarkPos 函数是在查询执行过程中对 “Result” 节点(结果节点)进行标记位置(mark position)操作,以支持后续的恢复restore)操作。函数源码如下:(路径:src/gausskernel/runtime/executor/nodeResult.cpp

/* ----------------------------------------------------------------*		ExecResultMarkPos* ----------------------------------------------------------------*/
void ExecResultMarkPos(ResultState* node)
{// 获取外部计划节点状态PlanState* outer_plan = outerPlanState(node);// 如果存在外部计划节点if (outer_plan != NULL) {// 在外部计划节点上标记位置ExecMarkPos(outer_plan);} else {// 否则,输出调试信息,指示 "Result" 节点不支持标记/恢复操作elog(DEBUG2, "Result 节点不支持标记/恢复操作");}
}

ExecMarkpos 函数

  ExecMarkpos 函数主要目的是根据传入的节点类型,在执行计划中选择特定类型的操作进行标记扫描位置ExecMarkPos 函数标记扫描位置的作用在于支持在查询执行过程中执行位置的恢复操作。标记扫描位置实际上是记录当前扫描的状态,以便后续可以重新定位到这个状态,从而实现位置的恢复

/** ExecMarkPos** 标记当前扫描位置。*/
void ExecMarkPos(PlanState* node)
{// 根据节点类型选择不同的操作switch (nodeTag(node)) {case T_SeqScanState:ExecSeqMarkPos((SeqScanState*)node);break;case T_IndexScanState:ExecIndexMarkPos((IndexScanState*)node);break;case T_IndexOnlyScanState:ExecIndexOnlyMarkPos((IndexOnlyScanState*)node);break;case T_TidScanState:ExecTidMarkPos((TidScanState*)node);break;case T_ValuesScanState:ExecValuesMarkPos((ValuesScanState*)node);break;case T_MaterialState:ExecMaterialMarkPos((MaterialState*)node);break;case T_SortState:ExecSortMarkPos((SortState*)node);break;case T_ResultState:ExecResultMarkPos((ResultState*)node);break;default:/* 除非调用者要求还原,否则不会产生硬错误... */elog(DEBUG2, "无法识别的节点类型:%d", (int)nodeTag(node));break;}
}

ExecResultRestrPos 函数

  ExecResultRestrPos 函数作用是在查询执行中对 “Result” 节点(结果节点)进行位置恢复操作的函数。源码如下所示:(路径:src/gausskernel/runtime/executor/nodeResult.cpp

/* ----------------------------------------------------------------*		ExecResultRestrPos* ----------------------------------------------------------------*/
void ExecResultRestrPos(ResultState* node)
{// 获取外部计划节点状态PlanState* outer_plan = outerPlanState(node);// 如果存在外部计划节点if (outer_plan != NULL) {// 在外部计划节点上执行位置恢复操作ExecRestrPos(outer_plan);} else {// 否则,输出错误信息,指示 "Result" 节点不支持标记/恢复操作ereport(ERROR,(errmodule(MOD_EXECUTOR),(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Result nodes do not support mark/restore"))));}
}

  这个函数的主要目的是根据传入的 “Result” 节点状态,在执行计划中进行位置恢复操作。具体步骤如下:

  • 获取外部计划节点状态,这是连接到 “Result” 节点的外部计划节点的状态。
  • 如果存在外部计划节点,就在该外部计划节点上执行位置恢复操作,这是通过调用 ExecRestrPos 函数来实现的。
  • 如果不存在外部计划节点,就会产生一个错误报告,表明 “Result” 节点不支持标记/恢复操作。这是通过 ereport 函数生成一个错误消息,包括错误代码和错误信息来实现的。

ExecReScanResult 函数

  ExecReScanResult 函数作用是在查询执行中对 “Result” 节点(结果节点)进行重新扫描操作。函数源码如下:(路径:src/gausskernel/runtime/executor/nodeResult.cpp

void ExecReScanResult(ResultState* node)
{// 标记为未完成node->rs_done = false;// 将从目标列表获得的元组标记为未完成node->ps.ps_TupFromTlist = false;// 检查是否需要检查限定条件node->rs_checkqual = (node->resconstantqual == NULL) ? false : true;/** 如果子节点的 chgParam 不为 null,则计划将由第一个 ExecProcNode 进行重新扫描。*/if (node->ps.lefttree && node->ps.lefttree->chgParam == NULL) {// 对子节点进行重新扫描ExecReScan(node->ps.lefttree);}
}

  这个函数的主要目的是在查询重新扫描时,对 “Result” 节点进行重新扫描操作。具体步骤如下:

  • rs_done 标记设置为未完成,表示还未完成扫描。
  • ps_TupFromTlist 标记设置为未完成,表示还未从目标列表获得元组。
  • 根据是否存在 resconstantqual,确定是否需要检查限定条件,将 rs_checkqual 标记设置为对应的值。
  • 如果 “Result” 节点有左子节点(lefttree)且左子节点的 chgParam 为 NULL,则执行子节点的重新扫描,这是通过调用 ExecReScan 函数来实现的。

ExecReScan 函数

  ExecReScan 函数作用是对执行计划节点进行重新扫描,以便其输出可以重新被扫描。它执行了一系列的操作,包括更新参数重新计算表达式关闭函数等,以准备执行计划节点的重新扫描。其中一些操作可能会涉及到在重新扫描前重置状态和资源。

/** ExecReScan*		重置计划节点,使其输出可以重新扫描。** 注意,如果计划节点的参数值发生了变化,输出可能与上次不同。*/
void ExecReScan(PlanState* node)
{/* 如果收集时间统计信息,更新它们 */if (node->instrument) {InstrEndLoop(node->instrument);}/* 重置行号计数 */node->ps_rownum = 0;/** 如果参数值发生了变化,传播该信息。** 注意:ExecReScanSetParamPlan() 可以向 node->chgParam 添加位,* 对应于 InitPlan 将要更新的输出参数。由于我们只在列表上进行一次遍历,* 这意味着 InitPlan 只能依赖于列表中出现在其前面的 InitPlan 的输出参数。* 目前的限制足够应对一个 InitPlan 可能依赖于另一个 InitPlan 的有限方式,* 但最终我们可能需要更多的工作(或者使规划器扩大 extParam/allParam 集合,以包含所依赖的 InitPlan 参数)。*/if (node->chgParam != NULL) {ListCell* l = NULL;// 遍历 InitPlan 和子计划foreach (l, node->initPlan) {SubPlanState* sstate = (SubPlanState*)lfirst(l);PlanState* splan = sstate->planstate;if (splan->plan->extParam != NULL) {/* 忽略子计划的本地参数 */UpdateChangedParamSet(splan, node->chgParam);}if (splan->chgParam != NULL) {ExecReScanSetParamPlan(sstate, node);}}foreach (l, node->subPlan) {SubPlanState* sstate = (SubPlanState*)lfirst(l);PlanState* splan = sstate->planstate;if (splan->plan->extParam != NULL) {UpdateChangedParamSet(splan, node->chgParam);}}/* 接着,为左子树和右子树设置 chgParam。 */if (node->lefttree != NULL) {UpdateChangedParamSet(node->lefttree, node->chgParam);}if (node->righttree != NULL) {UpdateChangedParamSet(node->righttree, node->chgParam);}}/* 关闭计划节点目标列表中的任何 SRF 函数 */if (node->ps_ExprContext) {ReScanExprContext(node->ps_ExprContext);}/* 如果需要存根执行,在这里停止重新扫描 */if (!planstate_need_stub(node)) {if (IS_PGXC_DATANODE && EXEC_IN_RECURSIVE_MODE(node->plan) && IsA(node, StreamState)) {return;}ExecReScanByType(node);}/* 如果参数值发生了变化,释放相应资源 */if (node->chgParam != NULL) {bms_free_ext(node->chgParam);node->chgParam = NULL;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/48700.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MyBatis】动态SQL > 重点:${...}和#{...}与resultMap和resultType的区别

目录 一、MyBatis动态sql 1.1 动态sql的作用 1.2 动态sql作用论证 1.2.1 条件判断&#xff1a;<if> 1.2.2 循环迭代&#xff1a;<foreach> 1.2.3 SQL片段重用 1.2.4 动态条件组合&#xff1a;<choose><when><otherwise> 1.2.5 <where…

UE4/5Niagara粒子特效之拖尾渐变

目录 开始操作 发射器一的制作 添加新的模块 ​编辑 让粒子长久存在 添加颜色 发射器二的制作 第三人称模板添加Niagara 效果 添加颜色 效果 隐藏第一个发射器 开始操作 首先创建一个粒子系统&#xff0c;用Fountain这个模板&#xff1a; 发射器一的制作 将不需要的…

【大数据】Flink 详解(五):核心篇 Ⅳ

Flink 详解&#xff08;五&#xff09;&#xff1a;核心篇 Ⅳ 45、Flink 广播机制了解吗&#xff1f; 从图中可以理解 广播 就是一个公共的共享变量&#xff0c;广播变量存于 TaskManager 的内存中&#xff0c;所以广播变量不应该太大&#xff0c;将一个数据集广播后&#xff0…

代码随想录算法训练营(回溯总结篇)

回溯也可以说是暴力搜索&#xff08;最多剪枝一下&#xff09;。回溯是递归的副产品&#xff0c;只要有递归就会有回溯。 一.分类 1.组合问题 &#xff08;1&#xff09;按组合元素的个数 &#xff08;2&#xff09;按组合元素的总和 有重复元素 同一元素可以重复选&#x…

【LeetCode-中等题】3. 无重复字符的最长子串

题目 题解一&#xff1a;单指针&#xff0c;滑动窗口 思路&#xff1a; 设置一个左指针&#xff0c;来判断下一个元素是否在set集合中&#xff0c;如果不在&#xff0c;就加入集合&#xff0c;right继续&#xff0c;如果在&#xff0c;就剔除重复的元素&#xff0c;计算串的长度…

如何在Windows、Mac和Linux操作系统上安装Protocol Buffers(protobuf)编译器

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

【PHP】PHP常见语法

文章目录 PHP简介前置知识了解静态网站的特点动态网站特点 PHP基础语法代码标记注释语句分隔(结束)符变量变量的基本概念变量的使用变量命名规则预定义变量可变变量变量传值内存分区 常量基本概念常量定义形式命名规则使用形式系统常量魔术常量 数据类型简单&#xff08;基本&a…

RISC-V(1)——RISC-V是什么,有什么用

目录 1. RISC-V是什么 2. RISC-V指令集 3. RISC-V特权架构 4. RiscV的寄存器描述 5. 指令 5.1 算数运算—add/sub/addi/mul/div/rem 5.2 逻辑运算—and/andi/or/ori/xor/xori 5.3 位移运算—sll/slli/srl/srli/sra/srai 5.4 数据传输—lb/lh/lw/lbu/lhu/lwu/sb/sh/sw …

2023.8.8巨人网络数据开发工程师面试复盘

1 概述 问题一览 总体感觉良好&#xff0c;通过面试官的介绍可知这个岗位偏向离线数仓。 1.自我介绍 2.询问了其中一段实习经历 3.讲下你说用过的Linux命令 4.讲下HIVE的内部表和外部表有什么不同 *5.讲下你使用过的Hive函数&#xff08;好好在复习下多准备几个吧&#xff09…

算法随笔:强连通分量

概念和性质&#xff1a; 强连通&#xff1a;在有向图G中&#xff0c;如果两个点u和v是互相可达的&#xff0c;即从u出发可以到达v&#xff0c;从v出发也可以到达u&#xff0c;则成u和v是强连通的。 强连通分量&#xff1a;如果一个有向图G不是强连通图&#xff0c;那么可以把它…

第 7 章 排序算法(1)(介绍,分类,时间复杂度,空间复杂度)

7.1排序算法的介绍 排序也称排序算法(Sort Algorithm)&#xff0c;排序是将一组数据&#xff0c;依指定的顺序进行排列的过程。 7.2排序的分类&#xff1a; 内部排序: 指将需要处理的所有数据都加载到**内部存储器(内存)**中进行排序。外部排序法&#xff1a; 数据量过大&am…

基于CentOS搭建私有仓库harbor

环境&#xff1a; 操作系统&#xff1a;CentOS Linux 7 (Core) 内核&#xff1a; Linux 3.10.0-1160.el7.x86_64 目录 安装搭建harbor &#xff08;1&#xff09;安装docker编排工具docker compose &#xff08;2&#xff09;下载Harbor 安装包 &#xff08;3&…

chapter 3 Free electrons in solid - 3.1 自由电子模型

3.1 自由电子模型 Free electron model 研究晶体中的电子&#xff1a; 自由电子理论&#xff1a;不考虑离子实能带理论&#xff1a;考虑离子实&#xff08;周期性势场&#xff09;的作用 3.1.1 德鲁德模型 Drude Model - Classical Free Electron Model (1)德鲁德模型 德鲁…

【3Ds Max】可编辑多边形“边”层级的简单使用

目录 简介 示例 1. 编辑边 &#xff08;1&#xff09;插入顶点 &#xff08;2&#xff09;移除 &#xff08;3&#xff09;分割 &#xff08;4&#xff09;挤出 &#xff08;5&#xff09;切角 &#xff08;6&#xff09;焊接 &#xff08;7&#xff09;桥 &…

NPM 管理组织成员

目录 1、向组织添加成员 1.1 邀请成员加入您的组织 1.2 撤销组织邀请 2、接收或拒接组织邀请 2.1 接收组织邀请 2.2 拒绝组织邀请 3、组织角色和权限 4、管理组织权限 5、从组织中删除成员 1、向组织添加成员 作为组织所有者&#xff0c;您可以将其他npm用户添加到…

阿里云访问端口被限制解决方法记录

阿里云服务器&#xff0c;80端口可以访问&#xff0c;但是加入了安全组端口8080 通过公网访问改端口策略&#xff0c;发现不能被访问 问题出在防火墙&#xff0c;需要重置一下 解决方法&#xff1a; 在运行的服务器上执行如下命令&#xff1a; # iptables -A INPUT -j ACCEP…

手写 Mybatis-plus 基础架构(工厂模式+ Jdk 动态代理统一生成代理 Mapper)

这里写目录标题 前言温馨提示手把手带你解析 MapperScan 源码手把手带你解析 MapperScan 源码细节剖析工厂模式Jdk 代理手撕脚手架&#xff0c;复刻 BeanDefinitionRegistryPostProcessor手撕 FactoryBean代理 Mapper 在 Spring 源码中的生成流程手撕 MapperProxyFactory手撕增…

Android Studio调试出现错误时,无法定位错误信息解决办法

做项目时运行项目会出现问题&#xff0c;但是找不到具体位置&#xff0c;如下图所示&#xff1a;感觉是不是很懵逼~&#xff0c;Log也没有显示是哪里的问题 解决方案&#xff0c;在右侧导航栏中选择Gradle——app——build&#xff0c;然后点击运行 运行结果如下&#xff0c;很…

Stable Diffusion 系列教程 | 图生图基础

前段时间有一个风靡全网的真人转漫画风格&#xff0c;受到了大家的喜欢 而在SD里&#xff0c;就可以通过图生图来实现类似的效果 当然图生图还有更好玩的应用&#xff0c;我们一点一点来探索 首先我们来简单进行一下图生图的这一个实践---真人转动漫 1. 图生图基本界面 和…

iOS代码混淆

文章目录 一、混淆的原理二、实现混淆1. 创建文件2. 将文件拖导入目录中3. 将以下脚本拷贝到刚新建的confuse.sh文件中4. 修改文件权限5. 修改项目配置6. 添加需要混淆的方法名7. 配置PCH文件8. 运行效果 一、混淆的原理 这里使用的混淆的原理是&#xff0c;用一串随机生成的字…