当前位置: 首页 > news >正文

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

执行算子(Append算子)

  • Append 算子
    • ExecInitAppend 函数
    • exec_append_initialize_next 函数
    • ExecAppend 函数
    • ExecEndAppend 函数
    • ExecReScanAppend 函数

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

Append 算子

  Append 算子对应的代码源文件是 “nodeAppend.cpp”,用于处理包含一个或多个子计划的链表Append 算子遍历子计划链表逐个执行子计划,当子计划返回全部结果后,迭代执行下一个子计划Append 算子通常用于 SQL 中的集合操作中,例如多个 Union All 操作,可以对多个子查询的结果取并集;另外 Append 算子还可以用来实现继承表的查询功能

  以下是对 Append 算子的一些详细描述:

  1. 多子计划处理:Append 算子通过遍历包含一个或多个子计划的链表,逐个执行这些子计划每个子计划对应一个子查询,这些子查询的结果将按照它们在链表中的顺序合并输出
  2. 顺序合并:子计划的执行是按照它们在链表中的顺序进行的。当一个子计划返回全部结果后Append 算子迭代执行下一个子计划。这确保了最终输出结果的顺序与子计划在链表中的顺序一致
  3. 用途:主要用于处理 SQL 查询中的集合操作,例如多个 UNION ALL 操作。在这种情况下,每个子查询可能对应于一个分支条件,而 Append 算子将这些分支的结果合并为一个整体结果
  4. 继承表查询: Append 算子还常用于实现继承表的查询功能。在面向对象的数据库设计中,可能存在一个包含所有子类的继承表,而每个子类对应一个子查询Append 算子可以将这些子查询的结果按顺序组合,形成包含所有子类数据的查询结果
  5. 执行计划灵活性: Append 算子的存在使得查询优化器能够以更灵活的方式处理复杂的查询结构。通过将多个子计划组合成一个逻辑单元优化器可以更好地进行成本估算执行计划选择

  Append算子对应的主要函数如下表所示:

主要函数说 明
ExecInitAppend初始化 Append 节点
ExecAppend迭代获取元组
ExecEndAppend关闭 Append 节点
ExecReScanAppend重新扫描 Append 节点
exec_append_initialize_next为下一个扫描节点设置状态

  按照传统,下面我们还是以一个案例来调试一下代码吧,首先执行以下 sql 语句:

-- 创建表
CREATE TABLE employees (emp_id SERIAL PRIMARY KEY,emp_name VARCHAR(100),emp_department VARCHAR(50)
);-- 插入数据
INSERT INTO employees (emp_name, emp_department) VALUES('John Doe', 'HR'),('Alice Smith', 'IT'),('Bob Johnson', 'Finance');-- 第一个子查询
SELECT emp_id, emp_name, emp_department FROM employees WHERE emp_department = 'HR'
UNION ALL
-- 第二个子查询
SELECT emp_id, emp_name, emp_department FROM employees WHERE emp_department = 'IT'
UNION ALL
-- 第三个子查询
SELECT emp_id, emp_name, emp_department FROM employees WHERE emp_department = 'Finance';

  在这个例子中,我们通过 UNION ALL 连接了三个子查询,每个子查询选择了属于不同部门的雇员。这样的查询可以模拟 Append 算子的效果将来自不同子查询的结果合并为一个整体结果。请注意,这只是一种模拟,实际的 Append 算子是由数据库系统内部生成的。

ExecInitAppend 函数

  ExecInitAppend 函数是为 Append 节点执行计划初始化阶段而设计的。在初始化过程中,它创建了 AppendState 结构体,用于存储 Append 节点的执行状态信息,包括子计划的执行状态数组当前执行的子计划编号等。接着,它遍历 Append 节点的子计划列表,对每个子计划调用 ExecInitNode 进行初始化,并将结果保存到数组中。此外,还初始化了执行结果存储槽输出元组类型。最终,函数返回初始化完成的 AppendState 结构体,为执行 Append 节点提供了必要的状态信息。在执行阶段,该结构体将被用于跟踪和管理 Append 节点的执行过程。函数源码如下所示:(路径:src/gausskernel/runtime/executor/nodeAppend.cpp

/* ----------------------------------------------------------------*		ExecInitAppend**		开始执行所有的追加节点子计划。**	   (这可能是不够高效的,因为追加节点的整个结果可能不会被扫描,*		但这样所有的结构都会被分配在执行器的顶级内存块中,而不是*		在对 ExecAppend 的调用的内存块中。)* ----------------------------------------------------------------*/
AppendState* ExecInitAppend(Append* node, EState* estate, int eflags)
{// 创建 AppendState 结构体,用于存储追加节点的执行状态信息AppendState* appendstate = makeNode(AppendState);// 子计划状态数组PlanState** appendplanstates;// 子计划数量int nplans;// 循环变量int i;ListCell* lc = NULL;// 检查是否存在不支持的标志Assert(!(eflags & EXEC_FLAG_MARK));/** 设置子计划状态的空向量*/// 获取子计划的数量nplans = list_length(node->appendplans);// 分配存储子计划状态的数组appendplanstates = (PlanState**)palloc0(nplans * sizeof(PlanState*));/** 为追加节点创建新的 AppendState*/// 初始化 AppendState 结构体的基本信息appendstate->ps.plan = (Plan*)node;appendstate->ps.state = estate;appendstate->appendplans = appendplanstates;appendstate->as_nplans = nplans;/** 杂项初始化** 追加计划没有表达式上下文,因为它们从不调用 ExecQual 或 ExecProject。*/// 初始化执行上下文,因为追加节点不涉及 ExecQual 或 ExecProject/** 追加节点仍然具有 Result 存储槽,其中保存了指向元组的指针,因此* 我们必须对它们进行初始化。*/// 初始化 Result 存储槽,用于存储指向元组的指针ExecInitResultTupleSlot(estate, &appendstate->ps);/** 对要执行的每个子计划调用 ExecInitNode,并将结果保存到数组“appendplans”中。*/// 遍历子计划列表,对每个子计划调用 ExecInitNode 进行初始化i = 0;foreach (lc, node->appendplans) {Plan* initNode = (Plan*)lfirst(lc);// 执行初始化并保存结果到数组中appendplanstates[i] = ExecInitNode(initNode, estate, eflags);i++;}/** 初始化输出元组类型* 追加的 Result 元组槽始终包含一个虚拟元组,* 该槽的默认表AM类型为 Heap。*/// 初始化输出元组类型ExecAssignResultTypeFromTL(&appendstate->ps, TAM_HEAP);appendstate->ps.ps_ProjInfo = NULL;/** 初始化以扫描第一个子计划*/// 初始化当前执行的子计划编号appendstate->as_whichplan = 0;// 执行初始化下一个子计划的操作(void)exec_append_initialize_next(appendstate);// 返回初始化完成的 AppendState 结构体,为执行追加节点提供必要的状态和信息return appendstate;
}

  函数的调用关系如下所示:

#0  ExecInitAppend (node=0x7f15adf87f18, estate=0x7f15ae6aa060, eflags=16) at nodeAppend.cpp:112
#1  0x000000000159910a in ExecInitNodeByType (node=0x7f15adf87f18, estate=0x7f15ae6aa060, eflags=16) at execProcnode.cpp:262
#2  0x0000000001599bf0 in ExecInitNode (node=0x7f15adf87f18, estate=0x7f15ae6aa060, e_flags=16) at execProcnode.cpp:497
#3  0x0000000001607b33 in ExecInitResult (node=0x7f15adf87868, estate=0x7f15ae6aa060, eflags=16) at nodeResult.cpp:239
#4  0x00000000015990d4 in ExecInitNodeByType (node=0x7f15adf87868, estate=0x7f15ae6aa060, eflags=16) at execProcnode.cpp:258
#5  0x0000000001599bf0 in ExecInitNode (node=0x7f15adf87868, estate=0x7f15ae6aa060, e_flags=16) at execProcnode.cpp:497
#6  0x00000000015939ba in InitPlan (queryDesc=0x7f15ae47f860, eflags=16) at execMain.cpp:1437
#7  0x0000000001591404 in standard_ExecutorStart (queryDesc=0x7f15ae47f860, eflags=16) at execMain.cpp:382
#8  0x00007f160055e78a in gs_audit_executor_start_hook (queryDesc=0x7f15ae47f860, eflags=0) at gs_policy_plugin.cpp:1907
#9  0x000000000139a43d in explain_ExecutorStart (queryDesc=0x7f15ae47f860, eflags=0) at auto_explain.cpp:83
#10 0x0000000001590e1b in ExecutorStart (queryDesc=0x7f15ae47f860, eflags=0) at execMain.cpp:228
#11 0x0000000001470c03 in PortalStart (portal=0x7f15ae4ce060, params=0x0, eflags=0, snapshot=0x0) at pquery.cpp:784
#12 0x000000000145d184 in exec_simple_query (
---Type <return> to continue, or q <return> to quit---

  函数的调式信息如下所示:

(gdb) p nplans
$1 = 3
(gdb) p *appendstate
$2 = {ps = {type = T_AppendState, plan = 0x7f15adf87f18, state = 0x7f15ae6aa060, instrument = 0x0, targetlist = 0x0, qual = 0x0, lefttree = 0x0,righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0, hbktScanSlot = {currSlot = 0}, ps_ResultTupleSlot = 0x0, ps_ExprContext = 0x0,ps_ProjInfo = 0x0, ps_TupFromTlist = false, vectorized = false, nodeContext = 0x0, earlyFreed = false, stubType = 0 '\000', jitted_vectarget = 0x0,plan_issues = 0x0, recursive_reset = false, qual_is_inited = false, ps_rownum = 0}, appendplans = 0x7f15ae738188, as_nplans = 3, as_whichplan = 0}
(gdb) p *appendstate->ps.plan
$3 = {type = T_Append, plan_node_id = 2, parent_node_id = 1, exec_type = EXEC_ON_DATANODES, startup_cost = 0, total_cost = 38.317499999999995, plan_rows = 3,multiple = 0, plan_width = 340, dop = 1, pred_rows = -1, pred_startup_time = -1, pred_total_time = -1, pred_max_memory = -1, recursive_union_plan_nodeid = 0,recursive_union_controller = false, control_plan_nodeid = 0, is_sync_plannode = false, targetlist = 0x7f15adf88140, qual = 0x0, lefttree = 0x0,righttree = 0x0, ispwj = false, paramno = -1, initPlan = 0x0, distributed_keys = 0x0, exec_nodes = 0x7f15ae56dd40, extParam = 0x0, allParam = 0x0,vec_output = false, hasUniqueResults = false, isDeltaTable = false, operatorMemKB = {0, 0}, operatorMaxMem = 0, parallel_enabled = false,hasHashFilter = false, var_list = 0x0, filterIndexList = 0x0, ng_operatorMemKBArray = 0x0, ng_num = 0, innerdistinct = 1, outerdistinct = 1}
(gdb) p *appendstate->ps.state
$4 = {type = T_EState, es_direction = ForwardScanDirection, es_snapshot = 0x7f15ae46c4f8, es_crosscheck_snapshot = 0x0, es_range_table = 0x7f15adf8a6a0,es_plannedstmt = 0x7f15adf8d728, es_junkFilter = 0x0, es_output_cid = 0, es_result_relations = 0x0, es_num_result_relations = 0,es_result_relation_info = 0x0, esCurrentPartition = 0x0, esfRelations = 0x0, es_result_remoterel = 0x0, es_result_insert_remoterel = 0x0,es_result_update_remoterel = 0x0, es_result_delete_remoterel = 0x0, es_trig_target_relations = 0x0, es_trig_tuple_slot = 0x0, es_trig_oldtup_slot = 0x0,es_trig_newtup_slot = 0x0, es_param_list_info = 0x0, es_param_exec_vals = 0x0, es_query_cxt = 0x7f15ae542b28, es_const_query_cxt = 0x7f15ae543a40,es_tupleTable = 0x7f15ae5445d0, es_rowMarks = 0x0, es_processed = 0, es_last_processed = 0, es_lastoid = 0, es_top_eflags = 16, es_instrument = 0,es_finished = false, es_exprcontexts = 0x7f15ae5443f8, es_subplanstates = 0x0, es_auxmodifytables = 0x0, es_remotequerystates = 0x0,es_per_tuple_exprcontext = 0x0, es_epqTuple = 0x0, es_epqTupleSet = 0x0, es_epqScanDone = 0x0, es_subplan_ids = 0x0, es_skip_early_free = false,es_skip_early_deinit_consumer = false, es_under_subplan = false, es_material_of_subplan = 0x0, es_recursive_next_iteration = false, dataDestRelIndex = 0,es_bloom_filter = {bfarray = 0x0, array_size = 0}, es_can_realtime_statistics = false, es_can_history_statistics = false, isRowTriggerShippable = false}
(gdb) p appendstate->as_nplans
$5 = 3
(gdb) p **appendplanstates@2
$14 = {{type = T_SeqScanState, plan = 0x7f15adf885a0, state = 0x7f15ae6aa060, instrument = 0x0, targetlist = 0x7f15ae54c710, qual = 0x7f15ae54d948,lefttree = 0x0, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0, hbktScanSlot = {currSlot = 0}, ps_ResultTupleSlot = 0x7f15ae54ce88,ps_ExprContext = 0x7f15ae54c288, ps_ProjInfo = 0x0, ps_TupFromTlist = false, vectorized = false, nodeContext = 0x7f15ae543c68, earlyFreed = false,stubType = 0 '\000', jitted_vectarget = 0x0, plan_issues = 0x0, recursive_reset = false, qual_is_inited = true, ps_rownum = 0}, {type = 2927208672,plan = 0x7f15ade6c860, state = 0x7f15ae54cff8, instrument = 0x0, targetlist = 0x0, qual = 0x0, lefttree = 0x0, righttree = 0x0, initPlan = 0x0,subPlan = 0x0, chgParam = 0x0, hbktScanSlot = {currSlot = 0}, ps_ResultTupleSlot = 0x0, ps_ExprContext = 0x0, ps_ProjInfo = 0x0, ps_TupFromTlist = false,vectorized = false, nodeContext = 0x0, earlyFreed = false, stubType = 0 '\000', jitted_vectarget = 0x160acad <SeqNext(SeqScanState*)>,plan_issues = 0x7f15ae543c68, recursive_reset = false, qual_is_inited = 2, ps_rownum = -6510615555426900571}}

exec_append_initialize_next 函数

  exec_append_initialize_next 函数用于设置追加节点状态以准备进行下一个子计划的扫描。该函数检查当前迭代的子计划索引是否在有效范围内,如果,则返回 true,表示有下一个子计划需要处理;如果超出索引范围,则根据扫描的方向(正向或逆向)进行调整,然后返回 false,通知 ExecAppend 函数已经到达了子计划列表的末尾。这个函数在追加节点执行过程中被调用,用于初始化追加节点状态以准备处理下一个子计划。函数源码如下所示:(路径:src/gausskernel/runtime/executor/nodeAppend.cpp

/* ----------------------------------------------------------------*		exec_append_initialize_next**		设置追加状态节点以进行“下一个”扫描。**		如果有“下一个”扫描要处理,则返回 true。* ----------------------------------------------------------------*/
bool exec_append_initialize_next(AppendState* appendstate)
{int whichplan;/** 从追加节点获取信息*/whichplan = appendstate->as_whichplan;if (whichplan < 0) {/** 如果是逆向扫描,我们从列表中的最后一个扫描开始,* 然后向前进行到第一个.. 在任何情况下,通过返回 FALSE,* 通知 ExecAppend 我们已经到达了行的末尾*/appendstate->as_whichplan = 0;return FALSE;} else if (whichplan >= appendstate->as_nplans) {/** 如上,如果超出了列表中的最后一个扫描,结束扫描..*/appendstate->as_whichplan = appendstate->as_nplans - 1;return FALSE;} else {// 如果存在下一个扫描,则返回 truereturn TRUE;}
}

  函数的调式信息如下所示:

(gdb) p whichplan
$1 = 0
(gdb) p appendstate->as_nplans
$2 = 3

  因此,whichplan < appendstate->as_nplans 条件成立,返回 TURE

ExecAppend 函数

  ExecAppend 函数负责处理对多个子计划进行迭代的逻辑。它循环遍历子计划从当前子计划获取元组,如果获取到元组则直接返回;否则,释放当前子计划的资源切换到下一个子计划,继续循环。在正向逆向扫描中,如果没有更多的子计划可供处理,则返回一个由 ExecInitAppend 设置的空槽。这样,ExecAppend 实现了对多个子计划的迭代执行,用于支持像多个 Union All 操作这样的集合操作,以及继承表的查询等功能。函数源码如下所示:(路径:src/gausskernel/runtime/executor/nodeAppend.cpp

/* ----------------------------------------------------------------*	   ExecAppend**		处理多个子计划的迭代。* ----------------------------------------------------------------*/
TupleTableSlot* ExecAppend(AppendState* node) {for (;;) {PlanState* subnode = NULL;      // 当前处理的子计划节点TupleTableSlot* result = NULL;  // 子计划的执行结果/** 确定当前正在处理的子计划*/subnode = node->appendplans[node->as_whichplan];/** 从子计划获取一个元组*/result = ExecProcNode(subnode);if (!TupIsNull(result)) {/** 如果子计划返回了结果,则直接返回。我们不使用在* ExecInitAppend 中设置的结果槽;没有必要使用它。*/return result;}/* 提前释放每个子计划的资源 */ExecEarlyFree(subnode);/** 转到适当方向上的“下一个”子计划。如果没有更多的子计划,则返回* 由 ExecInitAppend 为我们设置的空槽。*/if (ScanDirectionIsForward(node->ps.state->es_direction))node->as_whichplan++;elsenode->as_whichplan--;if (!exec_append_initialize_next(node))return ExecClearTuple(node->ps.ps_ResultTupleSlot);/* 否则回到循环顶部,尝试从新的子计划获取元组 */}
}

  函数的调用关系如下所示:

#0  ExecAppend (node=0x7f15ae738060) at nodeAppend.cpp:185
#1  0x000000000159a253 in ExecProcNodeByType (node=0x7f15ae738060) at execProcnode.cpp:609
#2  0x000000000159a8dd in ExecProcNode (node=0x7f15ae738060) at execProcnode.cpp:769
#3  0x0000000001607807 in ExecResult (node=0x7f15ae544060) at nodeResult.cpp:124
#4  0x000000000159a231 in ExecProcNodeByType (node=0x7f15ae544060) at execProcnode.cpp:604
#5  0x000000000159a8dd in ExecProcNode (node=0x7f15ae544060) at execProcnode.cpp:769
#6  0x0000000001595232 in ExecutePlan (estate=0x7f15ae6aa060, planstate=0x7f15ae544060, operation=CMD_SELECT, sendTuples=true, numberTuples=0,direction=ForwardScanDirection, dest=0x7f15adf30318) at execMain.cpp:2124
#7  0x0000000001591d6a in standard_ExecutorRun (queryDesc=0x7f15ae47f860, direction=ForwardScanDirection, count=0) at execMain.cpp:608
#8  0x000000000139a5d4 in explain_ExecutorRun (queryDesc=0x7f15ae47f860, direction=ForwardScanDirection, count=0) at auto_explain.cpp:116
#9  0x000000000159188f in ExecutorRun (queryDesc=0x7f15ae47f860, direction=ForwardScanDirection, count=0) at execMain.cpp:484
#10 0x000000000147298f in PortalRunSelect (portal=0x7f15ae4ce060, forward=true, count=0, dest=0x7f15adf30318) at pquery.cpp:1396
#11 0x0000000001471b5c in PortalRun (portal=0x7f15ae4ce060, count=9223372036854775807, isTopLevel=true, dest=0x7f15adf30318, altdest=0x7f15adf30318,
---Type <return> to continue, or q <return> to quit---

  函数的调式信息如下所示:

(gdb) p *subnode
$1 = {type = T_SeqScanState, plan = 0x7f15adf885a0, state = 0x7f15ae6aa060, instrument = 0x0, targetlist = 0x7f15ae54c710, qual = 0x7f15ae54d948,lefttree = 0x0, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0, hbktScanSlot = {currSlot = 0}, ps_ResultTupleSlot = 0x7f15ae54ce88,ps_ExprContext = 0x7f15ae54c288, ps_ProjInfo = 0x0, ps_TupFromTlist = false, vectorized = false, nodeContext = 0x7f15ae543c68, earlyFreed = false,stubType = 0 '\000', jitted_vectarget = 0x0, plan_issues = 0x0, recursive_reset = false, qual_is_inited = true, ps_rownum = 0}
(gdb) p result
$2 = (TupleTableSlot *) 0x7f15ae54cff8
(gdb) p *result
$3 = {type = T_TupleTableSlot, tts_isempty = false, tts_shouldFree = false, tts_shouldFreeMin = false, tts_slow = true, tts_tuple = 0x7f15ade6cb28,tts_dataRow = 0x0, tts_dataLen = -1, tts_shouldFreeRow = false, tts_attinmeta = 0x0, tts_xcnodeoid = 0, tts_per_tuple_mcxt = 0x7f15ae6a0f78,tts_tupleDescriptor = 0x7f15ae793348, tts_mcxt = 0x7f15ae543c68, tts_buffer = 151, tts_nvalid = 3, tts_values = 0x7f15ae54d9b0, tts_isnull = 0x7f15ae54da18,tts_mintuple = 0x0, tts_minhdr = {tupTableType = 0 '\000', t_bucketId = 0, t_len = 0, t_self = {ip_blkid = {bi_hi = 0, bi_lo = 0}, ip_posid = 0},t_tableOid = 0, t_xid_base = 0, t_multi_base = 0, t_xc_node_id = 0, t_data = 0x0}, tts_off = 16, tts_meta_off = 0, tts_tupslotTableAm = TAM_HEAP}
(gdb) p *result.tts_values
$4 = 1
(gdb) p *result.tts_isnull
$5 = false

ExecEndAppend 函数

  ExecEndAppend 函数用于关闭 Append 节点的子扫描。它首先获取 AppendState 结构中的子计划数组计划数量信息,然后遍历关闭每个子计划,通过调用 ExecEndNode 函数完成关闭操作。这样,ExecEndAppend 负责释放 Append 节点相关的资源,确保在执行结束时进行清理。函数源码如下所示:(路径:src/gausskernel/runtime/executor/nodeAppend.cpp

/* ----------------------------------------------------------------*		ExecEndAppend**		Shuts down the subscans of the append node.**		Returns nothing of interest.* ----------------------------------------------------------------*/
void ExecEndAppend(AppendState* node)
{// 获取子计划数组和计划数量信息PlanState** appendplans = node->appendplans;int nplans = node->as_nplans;int i;/** shut down each of the subscans* 逐个关闭每个子计划*/for (i = 0; i < nplans; i++)ExecEndNode(appendplans[i]);  // 调用ExecEndNode函数完成关闭操作
}

  函数的调用关系如下所示:

#0  ExecEndAppend (node=0x7f15ae738060) at nodeAppend.cpp:242
#1  0x000000000159c01e in ExecEndNodeByType (node=0x7f15ae738060) at execProcnode.cpp:1084
#2  0x000000000159c5d6 in ExecEndNode (node=0x7f15ae738060) at execProcnode.cpp:1374
#3  0x0000000001607bd4 in ExecEndResult (node=0x7f15ae544060) at nodeResult.cpp:279
#4  0x000000000159bffc in ExecEndNodeByType (node=0x7f15ae544060) at execProcnode.cpp:1075
#5  0x000000000159c5d6 in ExecEndNode (node=0x7f15ae544060) at execProcnode.cpp:1374
#6  0x0000000001594cfa in ExecEndPlan (planstate=0x7f15ae544060, estate=0x7f15ae6aa060) at execMain.cpp:1910
#7  0x0000000001592232 in standard_ExecutorEnd (queryDesc=0x7f15ae47f860) at execMain.cpp:766
#8  0x00000000014954f2 in pgaudit_ExecutorEnd (queryDesc=0x7f15ae47f860) at auditfuncs.cpp:1600
#9  0x00000000014d98be in hypo_executorEnd_hook (queryDesc=0x7f15ae47f860) at hypopg_index.cpp:216
#10 0x00000000015920bb in ExecutorEnd (queryDesc=0x7f15ae47f860) at execMain.cpp:714
#11 0x00000000012bc705 in PortalCleanup (portal=0x7f15ae4ce060) at portalcmds.cpp:280
#12 0x0000000000e68985 in PortalDrop (portal=0x7f15ae4ce060, isTopCommit=false) at portalmem.cpp:498
---Type <return> to continue, or q <return> to quit---

  函数的调式信息如下所示:

(gdb) p **appendplans
$1 = {type = T_SeqScanState, plan = 0x7f15adf885a0, state = 0x7f15ae6aa060, instrument = 0x0, targetlist = 0x7f15ae54c710, qual = 0x7f15ae54d948,lefttree = 0x0, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0, hbktScanSlot = {currSlot = 0}, ps_ResultTupleSlot = 0x7f15ae54ce88,ps_ExprContext = 0x7f15ae54c288, ps_ProjInfo = 0x0, ps_TupFromTlist = false, vectorized = false, nodeContext = 0x7f15ae543c68, earlyFreed = true,stubType = 0 '\000', jitted_vectarget = 0x0, plan_issues = 0x0, recursive_reset = false, qual_is_inited = true, ps_rownum = 2}
执行 ExecEndNode 函数后结果如下:
(gdb) p **appendplans
$33 = {type = T_SeqScanState, plan = 0x7f15adf885a0, state = 0x7f15ae6aa060, instrument = 0x0, targetlist = 0x7f15ae54c710, qual = 0x7f15ae54d948,lefttree = 0x0, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0, hbktScanSlot = {currSlot = 0}, ps_ResultTupleSlot = 0x7f15ae54ce88,ps_ExprContext = 0x0, ps_ProjInfo = 0x0, ps_TupFromTlist = false, vectorized = false, nodeContext = 0x7f15ae543c68, earlyFreed = true, stubType = 0 '\000',jitted_vectarget = 0x0, plan_issues = 0x0, recursive_reset = false, qual_is_inited = true, ps_rownum = 2}

ExecReScanAppend 函数

  ExecReScanAppend 函数用于重新启动 Append 节点的扫描过程。它遍历 Append 节点的所有子计划,对每个子计划执行重新扫描操作。在重新扫描前,函数检查是否有参数变更,如果有,则通过 UpdateChangedParamSet 函数进行变更参数的信号处理。如果子计划的 chgParam 不为 null,表示需要在后续的 ExecProcNode重新扫描该子计划。最后,函数重置 Append 节点的当前子计划索引,并调用exec_append_initialize_next 函数重新初始化 Append 节点的状态,以准备开始新的扫描过程。函数源码如下所示:(路径:src/gausskernel/runtime/executor/nodeAppend.cpp

void ExecReScanAppend(AppendState* node)
{int i;for (i = 0; i < node->as_nplans; i++) {PlanState* subnode = node->appendplans[i];/** ExecReScan doesn't know about my subplans, so I have to do* changed-parameter signaling myself.* ExecReScan不了解我的子计划,因此我必须自己进行参数变更信号处理。*/if (node->ps.chgParam != NULL)UpdateChangedParamSet(subnode, node->ps.chgParam);/** If chgParam of subnode is not null then plan will be re-scanned by* first ExecProcNode.* 如果subnode的chgParam不为null,则计划将在第一个ExecProcNode中重新扫描。*/if (subnode->chgParam == NULL)ExecReScan(subnode);  // 调用ExecReScan函数重新扫描子计划}node->as_whichplan = 0;(void)exec_append_initialize_next(node);  // 重新初始化Append节点的状态
}

  ExecReScanAppend 函数通常在执行计划需要重新扫描的时候被调用。这可能是由于外部参数的变化,需要重新执行整个计划。例如,考虑以下情况:
  假设有一个包含多个子查询的 Append 节点,每个子查询对应于不同的分区。如果在执行计划期间,某个影响所有子查询结果的外部参数发生变化,为了保证计划的正确性,就需要调用 ExecReScanAppend。例如:

-- 创建一个包含多个子查询的Append计划
EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM table1 WHERE column = 1
UNION ALL
SELECT * FROM table2 WHERE column = 2
UNION ALL
SELECT * FROM table3 WHERE column = 3;

  假设在上述计划执行过程中,外部条件 column 的值发生变化,为了反映这个变化,就需要调用 ExecReScanAppend 重新扫描所有子查询确保计划的执行结果是最新

相关文章:

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

执行算子&#xff08;Append算子&#xff09; Append 算子ExecInitAppend 函数exec_append_initialize_next 函数ExecAppend 函数ExecEndAppend 函数ExecReScanAppend 函数 声明&#xff1a;本文的部分内容参考了他人的文章。在编写过程中&#xff0c;我们尊重他人的知识产权和…...

Java(一)(引用类型的参数在传递,方法重载,面向对象编程基础)

基本类型和引用类型的参数在传递的时候有什么不同? 基本类型的值传递:参数传输存储的数据值 引用类型的值传递:参数传输存储的地址值 传递数组名字的时候,传递的是数组的地址,change方法可以通过地址直接访问我们在堆内存中开辟的数组,然后改变数组,数组中的元素发生变化 方…...

Vue第1天:特性概览

文章目录 Vue.js 简介 Vue的特性 如何使用Vue 安装Vue 通过CDN引入 使用npm 创建Vue实例 结语 Vue.js 简介 Vue.js&#xff08;通常简称为Vue&#xff09;是一款流行的JavaScript框架&#xff0c;专注于构建用户界面。它的设计灵感来自于现代的JavaScript框架&#xf…...

C++语法基础知识面经汇总

背景&#xff1a;汇总了网上C常考的基础知识&#xff0c;方便复习 1&#xff0c;static关键字 static可以用于成员变量&#xff0c;或者成员函数。存储空间在静态存储区&#xff08;编译器会将其初始化为0&#xff0c;对应的存储空间直到程序执行结束才会释放&#xff09;&…...

AM@幂级数性质@幂级数和函数求解

文章目录 幂级数性质四则运算性质分析性质求解和函数例例 幂级数性质 和多项式有相似的性质本文介绍用幂级数的性质求解幂级数和函数的两个例子 四则运算性质 若幂级数 ∑ n 0 ∞ a n x n \sum_{n0}^{\infin}a_{n}x^{n} ∑n0∞​an​xn(1)的收敛半径为 R 1 R_1 R1​,和函数为…...

PHP低版本安全问题

目录 1、PHP弱类型问题 1.1 MD5、 SHA1 弱比较问题 1.2 数组 0 1&#xff09;函数无法处理数组&#xff0c;返回0 2&#xff09;strcmp 2、特殊字符串导致的问题 2.1 "ffifdyop" 与 md5(string,raw) 2.2 ereg函数漏洞&#xff1a;00 截断 3、正则匹配问…...

结构体——C语言初阶

一.结构体的声明&#xff1a; &#xff08;1&#xff09;结构的基础知识&#xff1a; 结构体是一种构造数据类型把不同类型的数据组合成一个整体结构体是一些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量需要注意的是&#xff0c;结构体是一种…...

基于django电影推荐系统

基于django电影推荐系统 摘要 该Django电影推荐系统是一个简单而基础的框架&#xff0c;旨在展示系统的基本组件。系统包括两个主要模型&#xff0c;即Movie和Rating&#xff0c;用于存储电影信息和用户评分。视图层包括展示电影列表和电影详情的功能&#xff0c;使用模板进行页…...

【问题处理】WPS提示不能启动此对象的源应用程序如何处理?

哈喽&#xff0c;大家好&#xff0c;我是雷工&#xff01; 最近在用WPS打开word文件中&#xff0c;插入的Excel附件时&#xff0c;无法打开&#xff0c;提示&#xff1a;“不能启动此对象的源应用程序”。 经过上网查找处理办法&#xff0c;尝试解决&#xff0c;现将解决过程记…...

UE 程序化网格 计算横截面

首先在构造函数内加上程序化网格&#xff0c;然后复制网格体到程序化网格组件上&#xff0c;将Static Mesh&#xff08;类型StaticMeshActor&#xff09;的静态网格体组件给到程序化网格体上 然后把StaticMesh&#xff08;类型为StaticMeshActor&#xff09;Instance暴漏出去 …...

【Spring】IoC容器的一些总结与补充

文章目录 1. 创建容器的两种方式相对路径导入绝对路径导入 2. 获取Bean的三种方式getBean后强转类型getBean内写明类别根据类别获取bean 3. 容器层次结构4. BeanFactory5. bean的总结6. 注入的总结 1. 创建容器的两种方式 相对路径导入 ApplicationContext ctx new ClassPat…...

Java GUI实现五子棋游戏

五子棋是一种双人对弈的棋类游戏&#xff0c;通常在棋盘上进行。棋盘为 1515 的方格&#xff0c;黑白双方各执棋子&#xff0c;轮流在棋盘的格点上落子&#xff0c;先在横、竖、斜线上形成五个相连的同色棋子者获胜。五子棋规则简单&#xff0c;易学难精&#xff0c;兼具攻防和…...

Python 集成 Nacos 配置中心

Python 集成 Nacos 配置中心 下载 Nacos 官方 pyhton 库 pip install nacos-sdk-python # 指定国内阿里云镜像源 pip3 install nacos-sdk-python -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com配置 Nacos 相关信息 Global:nacos:port: 8848…...

Debian 11 更新 Node.js 版本

发布于 2023-07-14 在 https://chenhaotian.top/debian/d-upd-nodejs/ 步骤 从 NodeSource 服务下载需要的 Node.js 安装脚本。注意更换版本号。当前的 LTS 版本是 18.x curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -现在可以直接从 apt 安装&#xff0…...

python 对图像进行聚类分析

import cv2 import numpy as np from sklearn.cluster import KMeans import time# 中文路径读取 def cv_imread(filePath, cv2_falgcv2.COLOR_BGR2RGB): cv_img cv2.imdecode(np.fromfile(filePath, dtypenp.uint8), cv2_falg) return cv_img# 自定义装饰器计算时间 def…...

程序员导航站

探路者 hello.alluniverse.vip 开发者导航 - Pro Developer网站导航 探路者是一款极简导航工具&#xff0c;致力于收录的每个站点都有其独特的作用。同时支持自定义导航&#xff0c;让用户快速实现个性化的导航站点。 特性概述 免费ChatGPT 装机必备 开发工具 Git精选项目 …...

BIO、NIO、AIO三者的区别及其应用场景(结合生活例子,简单易懂)

再解释三者之前我们需要先了解几个概念&#xff1a; 阻塞、非阻塞&#xff1a;是相较于线程来说的&#xff0c;如果是阻塞则线程无法往下执行&#xff0c;不阻塞&#xff0c;则线程可以继续往下 执行。同步、异步&#xff1a;是相较于IO来说的&#xff0c;同步需要等待IO操作完…...

深度学习YOLO图像视频足球和人体检测 - python opencv 计算机竞赛

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络4 Yolov5算法5 数据集6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习YOLO图像视频足球和人体检测 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非…...

系列七、JVM的内存结构【堆(Heap)】

一、概述 一个JVM实例只存在一个堆内存&#xff0c;堆内存的大小是可以手动调节的。类加载器读取了类文件后&#xff0c;需要把类、方法、常变量放到堆内存中&#xff0c;保存所有引用类型的真实信息&#xff0c;以方便执行器执行&#xff0c;堆内存分为三个部分&#xff0c;即…...

什么是Selenium?如何使用Selenium进行自动化测试?

什么是 Selenium&#xff1f; Selenium 是一种开源工具&#xff0c;用于在 Web 浏览器上执行自动化测试&#xff08;使用任何 Web 浏览器进行 Web 应用程序测试&#xff09;。   等等&#xff0c;先别激动&#xff0c;让我再次重申一下&#xff0c;Selenium 仅可以测试Web应用…...

【蓝桥杯 第十五届模拟赛 Java B组】训练题(A - I)

目录 A、求全是字母的最小十六进制数 B、Excel表格组合 C、求满足条件的日期 D、 取数字 - 二分 &#xff08;1&#xff09;暴力 &#xff08;2&#xff09;二分 E、最大连通块 - bfs F、哪一天&#xff1f; G、信号覆盖 - bfs &#xff08;1&#xff09;bfs&#xf…...

【数据结构】手撕双向链表

目录 前言 1. 双向链表 带头双向循环链表的结构 2. 链表的实现 2.1 初始化 2.2 尾插 2.3 尾删 2.4 头插 2.5 头删 2.6 在pos位置之前插入 2.7 删除pos位置 3.双向链表完整源码 List.h List.c 前言 在上一期中我们介绍了单链表&#xff0c;也做了一些练习题&…...

性能测试 —— Jmeter接口处理不低于200次/秒-场景

需求&#xff1a;期望某个接口系统的处理能力不低于200次/秒&#xff0c;如何设计&#xff1f; ①这个场景是看服务器对某个接口的TPS值是否能大于等于200&#xff0c;就可以了&#xff1b; ②系统处理能力&#xff1a;说的就是我们性能测试中的TPS&#xff1b; ③只要设计一…...

Qt中使用QNetworkAccessManager类发送https请求时状态码返回0

前言 在项目开发中&#xff0c;碰到一个问题&#xff0c;使用QNetworkAccessManager类对象发送https请求时&#xff0c;状态码一直返回0&#xff0c;抓包分析看请求响应也是正常的。费了好大劲终于搞定了&#xff0c;主要是两个原因导致的。 原因一&#xff1a;未设置支持SSL…...

Linux - 物理内存管理 - memmap

说明 裁减内核预留内存占用&#xff0c;在启动log中&#xff0c;发现memmap占用了大块内存&#xff08;446个pages&#xff09;。 On node 0 totalpages: 32576 memblock_alloc_try_nid: 1835008 bytes align0x40 nid0 from0x0000000000000000 max_addr0x0000000000000000 al…...

Python爬虫动态ip代理防止被封的方法

目录 前言 一、什么是动态IP代理&#xff1f; 二、如何获取代理IP&#xff1f; 1. 付费代理IP 2. 免费代理IP 3. 自建代理IP池 三、如何使用代理IP爬取数据&#xff1f; 1. 使用requests库设置代理IP 2. 使用urllib库设置代理IP 3. 使用selenium库设置代理IP 四、常…...

01Urllib

1.什么是互联网爬虫&#xff1f; 如果我们把互联网比作一张大的蜘蛛网&#xff0c;那一台计算机上的数据便是蜘蛛网上的一个猎物&#xff0c;而爬虫程序就是一只小蜘蛛&#xff0c;沿着蜘蛛网抓取自己想要的数据 解释1&#xff1a;通过一个程序&#xff0c;根据Url(http://www.…...

python爬取酷我音乐 根据歌名进行爬取

# _*_ coding:utf-8 _*_ # 开发工具:PyCharm # 公众号:小宇教程import urllib.parse from urllib.request import urlopen import json import time import sys import osdef Time_1...

【深度学习】吴恩达课程笔记(五)——超参数调试、batch norm、Softmax 回归

笔记为自我总结整理的学习笔记&#xff0c;若有错误欢迎指出哟~ 【吴恩达课程笔记专栏】 【深度学习】吴恩达课程笔记(一)——深度学习概论、神经网络基础 【深度学习】吴恩达课程笔记(二)——浅层神经网络、深层神经网络 【深度学习】吴恩达课程笔记(三)——参数VS超参数、深度…...

腾讯云轻量级服务器和云服务器什么区别?轻量服务器是干什么用的

随着互联网的迅速发展&#xff0c;服务器成为了许多人必备的工具。然而&#xff0c;面对众多的服务器选择&#xff0c;我们常常会陷入纠结之中。在这篇文章中&#xff0c;我们将探讨轻量服务器和标准云服务器的区别&#xff0c;帮助您选择最适合自己需求的服务器。 腾讯云双十…...

解决:虚拟机远程连接失败

问题 使用FinalShell远程连接虚拟机的时候连接不上 发现 虚拟机用的VMware&#xff0c;Linux发行版是CentOs 7&#xff0c;发现在虚拟机中使用ping www.baidu.com是成功的&#xff0c;但是使用FinalShell远程连接不上虚拟机&#xff0c;本地网络也ping不通虚拟机&#xff0c…...

SpringBoot项目集成发邮件功能

1&#xff1a;引入依赖2&#xff1a;配置设置3&#xff1a;授权码获取&#xff1a;4&#xff1a;核心代码5&#xff1a;postman模拟验证6&#xff1a;安全注意 1&#xff1a;引入依赖 <dependency><groupId>org.apache.commons</groupId><artifactId>c…...

【Spring篇】使用注解进行开发

&#x1f38a;专栏【Spring】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【如愿】 &#x1f970;欢迎并且感谢大家指出小吉的问题 文章目录 &#x1f33a;原代码&#xff08;无注解&#xff09;&#x1f384;加上注解⭐两个注…...

Flink(六)【DataFrame 转换算子(下)】

前言 今天学习剩下的转换算子&#xff1a;分区、分流、合流。 每天出来自学是一件孤独又充实的事情&#xff0c;希望多年以后回望自己的大学生活&#xff0c;不会因为自己的懒惰与懈怠而悔恨。 回答之所以起到了作用&#xff0c;原因是他们自己很努力。 …...

【2023春李宏毅机器学习】生成式学习的两种策略

文章目录 1 各个击破2 一步到位3 两种策略的对比 生成式学习的两种策略&#xff1a;各个击破、一步到位 对于文本生成&#xff1a;把每一个生成的元素称为token&#xff0c;中文当中token指的是字&#xff0c;英文中的token指的是word piece。比如对于unbreakable&#xff0c;他…...

Android13 adb 无法连接?

Android13 adb 无法连接? 文章目录 Android13 adb 无法连接?一、前言二、替换adbGoogle 官网对adb的介绍&#xff1a;Google 提供的adb tools的下载&#xff1a; 三、总结1、adb connect 连接后显示offline2、输入adb devices 报错&#xff1a;版本不匹配导致3、adb常用命令4…...

Ubuntu 20.04 调整交换分区大小

Ubuntu 调整交换分区大小 一、系统情况二、去除旧的交换分区文件三、配置并启用交换分区四、查看swap文件大小 一、系统情况 Ubuntu &#xff1a;Ubuntu 20.04.6 LTS 交换分区位置&#xff1a; cat /proc/swaps二、去除旧的交换分区文件 去掉旧的交换分区有两个步骤&#x…...

将Agent技术的灵活性引入RPA,清华等发布自动化智能体ProAgent

近日&#xff0c;来自清华大学的研究人员联合面壁智能、中国人民大学、MIT、CMU 等机构共同发布了新一代流程自动化范式 “智能体流程自动化” Agentic Process Automation&#xff08;APA&#xff09;&#xff0c;结合大模型智能体帮助人类进行工作流构建&#xff0c;并让智能…...

高济健康:数字化科技创新与新零售碰撞 助推医疗产业优化升级

近日&#xff0c;第六届中国国际进口博览会在上海圆满落幕&#xff0c;首次亮相的高济健康作为一家专注大健康领域的疾病和健康管理公司&#xff0c;在本届进博会上向业内外展示了围绕“15分钟步行健康生活圈”构建进行的全域数字化升级成果。高济健康通过数字化科技创新与新零…...

SystemVerilog学习 (5)——接口

一、概述 验证一个设计需要经过几个步骤&#xff1a; 生成输入激励捕获输出响应决定对错和衡量进度 但是&#xff0c;我们首先需要一个合适的测试平台&#xff0c;并将它连接到设计上。 测试平台包裹着设计,发送激励并且捕获设计的输出。测试平台组成了设计周围的“真实世界”,…...

vue3插槽的使用

什么是插槽 Vue 3 插槽&#xff08;Slots&#xff09;是一个强大的工具&#xff0c;用于在组件之间传递内容和逻辑。通过使用插槽&#xff0c;我们可以将子组件中的内容插入到父组件中的特定位置。本篇文章将总结 Vue 3 插槽的基本用法、特点以及使用场景。 基本用法 插槽分为…...

IPTABLES问题:DNAT下如何解决内网访问内部服务器问题

这个问题&#xff0c;困扰了我几年了&#xff0c;今天终于得到解决。 问题是这样的&#xff0c;在局域网内部有一台服务器&#xff0c;通过IPTABLES的网关提供对外服务&#xff0c;做过IPTABLES网关的人都知道&#xff0c;这很容易做到&#xff0c;只要在网关机器上写一个DNAT…...

异步任务线程池——最优雅的方式创建异步任务

对于刚刚从校园出来的菜鸡选手很容易写出自以为没问题的屎山代码&#xff0c;可是当上线后就会立即暴露出问题&#xff0c;这说到底还是基础不够扎实&#xff01;只会背八股文&#xff0c;却不理解&#xff0c;面试头头是道&#xff0c;一旦落地就啥也不是。此处&#xff0c;抛…...

uniapp 跨页面传值及跨页面方法调用

uniapp 跨页面传值及跨页面方法调用 1、跨页面传值 使用全局方法监听uni.$emit、uni.$on、uni.$off 发布、监听、移除 methods: {addFun(){let data [1]uni.navigateBack({ // 返回上一页delta: 1})uni.$emit(successFun,{data}) // 传值} }监听页 onLoad() {uni.$on(succ…...

无线物理层安全大作业

这个标题很帅 Beamforming Optimization for Physical Layer Security in MISO Wireless NetworksProblem Stateme![在这里插入图片描述](https://img-blog.csdnimg.cn/58ebb0df787c4e23b0c7be4189ebc322.png) Beamforming Optimization for Physical Layer Security in MISO W…...

目标检测标注工具AutoDistill

引言 在快速发展的机器学习领域&#xff0c;有一个方面一直保持不变&#xff1a;繁琐和耗时的数据标注任务。无论是用于图像分类、目标检测还是语义分割&#xff0c;长期以来人工标记的数据集一直是监督学习的基础。 然而&#xff0c;由于一个创新性的工具 AutoDistill&#x…...

关于SPJ表的数据库作业

打字不易&#xff0c;且复制且珍惜 建表 use 库名;create table S( --供应商 SNO char(6) not null, SNAME char(10) not null, STATUS INT, CITY char(10), primary key(SNO));create table P( --零件 PNO char(6) not null, PNAME char(12)not null, COLOR char(4), WEIGHT…...

【Nacos】配置管理、微服务配置拉取、实现配置热更新、多环境配置

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 Nacos 一、nacos实现配置管理1.1 统一配置管…...

HTML5学习系列之网页图像

HTML5学习系列之网页图像 前言定义图像定义流定义图标 总结 前言 学习记录 定义图像 标签可以直接把图像插入网页中。 <img src"xx" alt"xx"/>src&#xff1a;显示图像的URLalt&#xff1a;设置图像的替代文本height、width&#xff1a;图像的高度…...

go语言学习之旅之Go语言数据类型

学无止境&#xff0c;今天学习Go 语言数据类型 Go&#xff08;或Golang&#xff09;是一种静态类型语言&#xff0c;这意味着变量的数据类型必须显式声明&#xff0c;并且在运行时不能更改。以下是Go中的一些基本数据类型&#xff1a; 这里仅介绍最常用的类型 数值类型: int: …...