From efd1d59a83e144126ccf726fa0b157e7a3cfb138 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Sun, 5 Apr 2026 03:53:13 +0300 Subject: [PATCH v17 3/5] Extract common Append/MergeAppend executor logic into execAppend.c Extract shared non-async executor operations for Append and MergeAppend nodes into a new execAppend.c file, reducing code duplication. The extracted functions operate on the common AppendBaseState base type introduced in the previous commit: - ExecInitAppendBase(): shared subplan initialization, partition pruning setup, and result tuple slot creation. - ExecEndAppendBase(): shut down all subplan nodes. - ExecReScanAppendBase(): propagate rescan to subplans and reset pruning. Async subplan detection, setup, and execution remain in nodeAppend.c, since MergeAppend does not yet support async. The tuple-fetching logic also remains specific to each node type, preserving their distinct execution semantics (sequential iteration for Append, binary heap merge for MergeAppend). Discussion: https://postgr.es/m/59be194c5a409fb9fc9f2031581b8a44%40postgrespro.ru Author: Matheus Alcantara Co-authored-by: Alexander Korotkov Reviewed-by: Alexander Pyhalov Reviewed-by: Alena Rybakina --- src/backend/executor/Makefile | 1 + src/backend/executor/execAppend.c | 208 ++++++++++++++++++++++++ src/backend/executor/meson.build | 1 + src/backend/executor/nodeAppend.c | 216 ++++--------------------- src/backend/executor/nodeMergeAppend.c | 151 ++--------------- src/include/executor/execAppend.h | 26 +++ 6 files changed, 282 insertions(+), 321 deletions(-) create mode 100644 src/backend/executor/execAppend.c create mode 100644 src/include/executor/execAppend.h diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index 11118d0ce02..2b12a1eb17e 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -14,6 +14,7 @@ include $(top_builddir)/src/Makefile.global OBJS = \ execAmi.o \ + execAppend.o \ execAsync.o \ execCurrent.o \ execExpr.o \ diff --git a/src/backend/executor/execAppend.c b/src/backend/executor/execAppend.c new file mode 100644 index 00000000000..9599d10a952 --- /dev/null +++ b/src/backend/executor/execAppend.c @@ -0,0 +1,208 @@ +/*------------------------------------------------------------------------- + * + * execAppend.c + * This code provides support functions for executing MergeAppend and + * Append nodes. + * + * Copyright (c) 2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/executor/execAppend.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "executor/execAppend.h" +#include "executor/execPartition.h" +#include "executor/executor.h" + +/* Begin all of the subscans of an AppendBase node. */ +void +ExecInitAppendBase(AppendBaseState *state, + AppendBase *node, + EState *estate, + int eflags, + int first_partial_plan, + int *first_valid_partial_plan) +{ + PlanState **appendplanstates; + const TupleTableSlotOps *appendops; + Bitmapset *validsubplans; + int nplans; + int firstvalid; + int i, + j; + + /* If run-time partition pruning is enabled, then set that up now */ + if (node->part_prune_index >= 0) + { + PartitionPruneState *prunestate; + + /* + * Set up pruning data structure. This also initializes the set of + * subplans to initialize (validsubplans) by taking into account the + * result of performing initial pruning if any. + */ + prunestate = ExecInitPartitionExecPruning(&state->ps, + list_length(node->subplans), + node->part_prune_index, + node->apprelids, + &validsubplans); + state->prune_state = prunestate; + nplans = bms_num_members(validsubplans); + + /* + * When no run-time pruning is required and there's at least one + * subplan, we can fill valid_subplans immediately, preventing later + * calls to ExecFindMatchingSubPlans. + */ + if (!prunestate->do_exec_prune && nplans > 0) + { + state->valid_subplans = bms_add_range(NULL, 0, nplans - 1); + state->valid_subplans_identified = true; + } + } + else + { + nplans = list_length(node->subplans); + + /* + * When run-time partition pruning is not enabled we can just mark all + * subplans as valid; they must also all be initialized. + */ + Assert(nplans > 0); + state->valid_subplans = validsubplans = + bms_add_range(NULL, 0, nplans - 1); + state->valid_subplans_identified = true; + state->prune_state = NULL; + } + + appendplanstates = palloc0_array(PlanState *, nplans); + + /* + * call ExecInitNode on each of the valid plans to be executed and save + * the results into the appendplanstates array. + * + * While at it, find out the first valid partial plan. + */ + j = 0; + firstvalid = nplans; + i = -1; + while ((i = bms_next_member(validsubplans, i)) >= 0) + { + Plan *initNode = (Plan *) list_nth(node->subplans, i); + + /* + * Record the lowest appendplans index which is a valid partial plan. + */ + if (i >= first_partial_plan && j < firstvalid) + firstvalid = j; + + appendplanstates[j++] = ExecInitNode(initNode, estate, eflags); + } + + if (first_valid_partial_plan) + *first_valid_partial_plan = firstvalid; + + state->plans = appendplanstates; + state->nplans = nplans; + + /* + * Initialize Append's result tuple type and slot. If the child plans all + * produce the same fixed slot type, we can use that slot type; otherwise + * make a virtual slot. (Note that the result slot itself is used only to + * return a null tuple at end of execution; real tuples are returned to + * the caller in the children's own result slots. What we are doing here + * is allowing the parent plan node to optimize if the Append will return + * only one kind of slot.) + */ + appendops = ExecGetCommonSlotOps(appendplanstates, j); + if (appendops != NULL) + { + ExecInitResultTupleSlotTL(&state->ps, appendops); + } + else + { + ExecInitResultTupleSlotTL(&state->ps, &TTSOpsVirtual); + /* show that the output slot type is not fixed */ + state->ps.resultopsset = true; + state->ps.resultopsfixed = false; + } + + /* Initialize async state to safe defaults */ + state->asyncplans = NULL; + state->nasyncplans = 0; + state->asyncrequests = NULL; + state->asyncresults = NULL; + state->needrequest = NULL; + state->eventset = NULL; + state->valid_asyncplans = NULL; + + /* + * Miscellaneous initialization + */ + state->ps.ps_ProjInfo = NULL; +} + +void +ExecReScanAppendBase(AppendBaseState *node) +{ + int i; + + /* + * If any PARAM_EXEC Params used in pruning expressions have changed, then + * we'd better unset the valid subplans so that they are reselected for + * the new parameter values. + */ + if (node->prune_state && + bms_overlap(node->ps.chgParam, + node->prune_state->execparamids)) + { + node->valid_subplans_identified = false; + bms_free(node->valid_subplans); + node->valid_subplans = NULL; + bms_free(node->valid_asyncplans); + node->valid_asyncplans = NULL; + } + + for (i = 0; i < node->nplans; i++) + { + PlanState *subnode = node->plans[i]; + + /* + * ExecReScan doesn't know about my subplans, so I have to do + * changed-parameter signaling myself. + */ + 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. + */ + if (subnode->chgParam == NULL) + ExecReScan(subnode); + } +} + +/* Shuts down the subplans of an AppendBase node. */ +void +ExecEndAppendBase(AppendBaseState *node) +{ + PlanState **subplans; + int nplans; + int i; + + /* + * get information from the node + */ + subplans = node->plans; + nplans = node->nplans; + + /* + * shut down each of the subscans + */ + for (i = 0; i < nplans; i++) + ExecEndNode(subplans[i]); +} diff --git a/src/backend/executor/meson.build b/src/backend/executor/meson.build index dc45be0b2ce..c2f261ff22d 100644 --- a/src/backend/executor/meson.build +++ b/src/backend/executor/meson.build @@ -2,6 +2,7 @@ backend_sources += files( 'execAmi.c', + 'execAppend.c', 'execAsync.c', 'execCurrent.c', 'execExpr.c', diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c index 272bf52fc2d..f267ffe13fa 100644 --- a/src/backend/executor/nodeAppend.c +++ b/src/backend/executor/nodeAppend.c @@ -57,6 +57,7 @@ #include "postgres.h" +#include "executor/execAppend.h" #include "executor/execAsync.h" #include "executor/execPartition.h" #include "executor/executor.h" @@ -111,15 +112,10 @@ AppendState * ExecInitAppend(Append *node, EState *estate, int eflags) { AppendState *appendstate = makeNode(AppendState); - PlanState **appendplanstates; - const TupleTableSlotOps *appendops; - Bitmapset *validsubplans; Bitmapset *asyncplans; - int nplans; int nasyncplans; - int firstvalid; - int i, - j; + int nplans; + int i; /* check for unsupported flags */ Assert(!(eflags & EXEC_FLAG_MARK)); @@ -136,124 +132,38 @@ ExecInitAppend(Append *node, EState *estate, int eflags) appendstate->as_syncdone = false; appendstate->as_begun = false; - /* If run-time partition pruning is enabled, then set that up now */ - if (node->ab.part_prune_index >= 0) - { - PartitionPruneState *prunestate; + /* Initialize common fields */ + ExecInitAppendBase(&appendstate->as, + &node->ab, + estate, + eflags, + node->first_partial_plan, + &appendstate->as_first_partial_plan); - /* - * Set up pruning data structure. This also initializes the set of - * subplans to initialize (validsubplans) by taking into account the - * result of performing initial pruning if any. - */ - prunestate = ExecInitPartitionExecPruning(&appendstate->as.ps, - list_length(node->ab.subplans), - node->ab.part_prune_index, - node->ab.apprelids, - &validsubplans); - appendstate->as.prune_state = prunestate; - nplans = bms_num_members(validsubplans); - - /* - * When no run-time pruning is required and there's at least one - * subplan, we can fill as_valid_subplans immediately, preventing - * later calls to ExecFindMatchingSubPlans. - */ - if (!prunestate->do_exec_prune && nplans > 0) - { - appendstate->as.valid_subplans = bms_add_range(NULL, 0, nplans - 1); - appendstate->as.valid_subplans_identified = true; - } - } - else - { - nplans = list_length(node->ab.subplans); - - /* - * When run-time partition pruning is not enabled we can just mark all - * subplans as valid; they must also all be initialized. - */ - Assert(nplans > 0); - appendstate->as.valid_subplans = validsubplans = - bms_add_range(NULL, 0, nplans - 1); - appendstate->as.valid_subplans_identified = true; - appendstate->as.prune_state = NULL; - } - - appendplanstates = (PlanState **) palloc(nplans * - sizeof(PlanState *)); + nplans = appendstate->as.nplans; /* - * call ExecInitNode on each of the valid plans to be executed and save - * the results into the appendplanstates array. - * - * While at it, find out the first valid partial plan. + * Detect async-capable subplans. When executing EvalPlanQual, we treat + * them as sync ones; don't do this when initializing an EvalPlanQual plan + * tree. */ - j = 0; asyncplans = NULL; nasyncplans = 0; - firstvalid = nplans; - i = -1; - while ((i = bms_next_member(validsubplans, i)) >= 0) + for (i = 0; i < nplans; i++) { - Plan *initNode = (Plan *) list_nth(node->ab.subplans, i); - - /* - * Record async subplans. When executing EvalPlanQual, we treat them - * as sync ones; don't do this when initializing an EvalPlanQual plan - * tree. - */ - if (initNode->async_capable && estate->es_epq_active == NULL) + if (appendstate->as.plans[i]->plan->async_capable && + estate->es_epq_active == NULL) { - asyncplans = bms_add_member(asyncplans, j); + asyncplans = bms_add_member(asyncplans, i); nasyncplans++; } - - /* - * Record the lowest appendplans index which is a valid partial plan. - */ - if (i >= node->first_partial_plan && j < firstvalid) - firstvalid = j; - - appendplanstates[j++] = ExecInitNode(initNode, estate, eflags); - } - - appendstate->as_first_partial_plan = firstvalid; - appendstate->as.plans = appendplanstates; - appendstate->as.nplans = nplans; - - /* - * Initialize Append's result tuple type and slot. If the child plans all - * produce the same fixed slot type, we can use that slot type; otherwise - * make a virtual slot. (Note that the result slot itself is used only to - * return a null tuple at end of execution; real tuples are returned to - * the caller in the children's own result slots. What we are doing here - * is allowing the parent plan node to optimize if the Append will return - * only one kind of slot.) - */ - appendops = ExecGetCommonSlotOps(appendplanstates, j); - if (appendops != NULL) - { - ExecInitResultTupleSlotTL(&appendstate->as.ps, appendops); - } - else - { - ExecInitResultTupleSlotTL(&appendstate->as.ps, &TTSOpsVirtual); - /* show that the output slot type is not fixed */ - appendstate->as.ps.resultopsset = true; - appendstate->as.ps.resultopsfixed = false; } /* Initialize async state */ appendstate->as.asyncplans = asyncplans; appendstate->as.nasyncplans = nasyncplans; - appendstate->as.asyncrequests = NULL; - appendstate->as.asyncresults = NULL; appendstate->as_nasyncresults = 0; appendstate->as_nasyncremain = 0; - appendstate->as.needrequest = NULL; - appendstate->as.eventset = NULL; - appendstate->as.valid_asyncplans = NULL; if (nasyncplans > 0) { @@ -267,7 +177,7 @@ ExecInitAppend(Append *node, EState *estate, int eflags) areq = palloc_object(AsyncRequest); areq->requestor = (PlanState *) appendstate; - areq->requestee = appendplanstates[i]; + areq->requestee = appendstate->as.plans[i]; areq->request_index = i; areq->callback_pending = false; areq->request_complete = false; @@ -283,12 +193,6 @@ ExecInitAppend(Append *node, EState *estate, int eflags) classify_matching_subplans(appendstate); } - /* - * Miscellaneous initialization - */ - - appendstate->as.ps.ps_ProjInfo = NULL; - /* For parallel query, this will be overridden later. */ appendstate->choose_next_subplan = choose_next_subplan_locally; @@ -402,67 +306,21 @@ ExecAppend(PlanState *pstate) void ExecEndAppend(AppendState *node) { - PlanState **appendplans; - int nplans; - int i; - - /* - * get information from the node - */ - appendplans = node->as.plans; - nplans = node->as.nplans; - - /* - * shut down each of the subscans - */ - for (i = 0; i < nplans; i++) - ExecEndNode(appendplans[i]); + ExecEndAppendBase(&node->as); } void ExecReScanAppend(AppendState *node) { int nasyncplans = node->as.nasyncplans; - int i; - - /* - * If any PARAM_EXEC Params used in pruning expressions have changed, then - * we'd better unset the valid subplans so that they are reselected for - * the new parameter values. - */ - if (node->as.prune_state && - bms_overlap(node->as.ps.chgParam, - node->as.prune_state->execparamids)) - { - node->as.valid_subplans_identified = false; - bms_free(node->as.valid_subplans); - node->as.valid_subplans = NULL; - bms_free(node->as.valid_asyncplans); - node->as.valid_asyncplans = NULL; - } - - for (i = 0; i < node->as.nplans; i++) - { - PlanState *subnode = node->as.plans[i]; - - /* - * ExecReScan doesn't know about my subplans, so I have to do - * changed-parameter signaling myself. - */ - if (node->as.ps.chgParam != NULL) - UpdateChangedParamSet(subnode, node->as.ps.chgParam); - /* - * If chgParam of subnode is not null then plan will be re-scanned by - * first ExecProcNode or by first ExecAsyncRequest. - */ - if (subnode->chgParam == NULL) - ExecReScan(subnode); - } + ExecReScanAppendBase(&node->as); /* Reset async state */ if (nasyncplans > 0) { + int i; + i = -1; while ((i = bms_next_member(node->as.asyncplans, i)) >= 0) { @@ -878,17 +736,6 @@ mark_invalid_subplans_as_finished(AppendState *node) static void ExecAppendAsyncBegin(AppendState *node) { - int i; - - /* Backward scan is not supported by async-aware Appends. */ - Assert(ScanDirectionIsForward(node->as.ps.state->es_direction)); - - /* We should never be called when there are no subplans */ - Assert(node->as.nplans > 0); - - /* We should never be called when there are no async subplans. */ - Assert(node->as.nasyncplans > 0); - /* If we've yet to determine the valid subplans then do so now. */ if (!node->as.valid_subplans_identified) { @@ -908,16 +755,19 @@ ExecAppendAsyncBegin(AppendState *node) return; /* Make a request for each of the valid async subplans. */ - i = -1; - while ((i = bms_next_member(node->as.valid_asyncplans, i)) >= 0) { - AsyncRequest *areq = node->as.asyncrequests[i]; + int i = -1; - Assert(areq->request_index == i); - Assert(!areq->callback_pending); + while ((i = bms_next_member(node->as.valid_asyncplans, i)) >= 0) + { + AsyncRequest *areq = node->as.asyncrequests[i]; - /* Do the actual work. */ - ExecAsyncRequest(areq); + Assert(areq->request_index == i); + Assert(!areq->callback_pending); + + /* Do the actual work. */ + ExecAsyncRequest(areq); + } } } diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c index d7d2de08147..6928152f16f 100644 --- a/src/backend/executor/nodeMergeAppend.c +++ b/src/backend/executor/nodeMergeAppend.c @@ -38,6 +38,7 @@ #include "postgres.h" +#include "executor/execAppend.h" #include "executor/executor.h" #include "executor/execPartition.h" #include "executor/nodeMergeAppend.h" @@ -66,12 +67,7 @@ MergeAppendState * ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags) { MergeAppendState *mergestate = makeNode(MergeAppendState); - PlanState **mergeplanstates; - const TupleTableSlotOps *mergeops; - Bitmapset *validsubplans; - int nplans; - int i, - j; + int i; /* check for unsupported flags */ Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); @@ -83,94 +79,18 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags) mergestate->as.ps.state = estate; mergestate->as.ps.ExecProcNode = ExecMergeAppend; - /* If run-time partition pruning is enabled, then set that up now */ - if (node->ab.part_prune_index >= 0) - { - PartitionPruneState *prunestate; - - /* - * Set up pruning data structure. This also initializes the set of - * subplans to initialize (validsubplans) by taking into account the - * result of performing initial pruning if any. - */ - prunestate = ExecInitPartitionExecPruning(&mergestate->as.ps, - list_length(node->ab.subplans), - node->ab.part_prune_index, - node->ab.apprelids, - &validsubplans); - mergestate->as.prune_state = prunestate; - nplans = bms_num_members(validsubplans); - - /* - * When no run-time pruning is required and there's at least one - * subplan, we can fill ms_valid_subplans immediately, preventing - * later calls to ExecFindMatchingSubPlans. - */ - if (!prunestate->do_exec_prune && nplans > 0) - mergestate->as.valid_subplans = bms_add_range(NULL, 0, nplans - 1); - } - else - { - nplans = list_length(node->ab.subplans); + /* Initialize common fields */ + ExecInitAppendBase(&mergestate->as, + &node->ab, + estate, + eflags, + -1, + NULL); - /* - * When run-time partition pruning is not enabled we can just mark all - * subplans as valid; they must also all be initialized. - */ - Assert(nplans > 0); - mergestate->as.valid_subplans = validsubplans = - bms_add_range(NULL, 0, nplans - 1); - mergestate->as.prune_state = NULL; - } - - mergeplanstates = palloc_array(PlanState *, nplans); - mergestate->as.plans = mergeplanstates; - mergestate->as.nplans = nplans; - - mergestate->ms_slots = palloc0_array(TupleTableSlot *, nplans); - mergestate->ms_heap = binaryheap_allocate(nplans, heap_compare_slots, + mergestate->ms_slots = palloc0_array(TupleTableSlot *, mergestate->as.nplans); + mergestate->ms_heap = binaryheap_allocate(mergestate->as.nplans, heap_compare_slots, mergestate); - /* - * call ExecInitNode on each of the valid plans to be executed and save - * the results into the mergeplanstates array. - */ - j = 0; - i = -1; - while ((i = bms_next_member(validsubplans, i)) >= 0) - { - Plan *initNode = (Plan *) list_nth(node->ab.subplans, i); - - mergeplanstates[j++] = ExecInitNode(initNode, estate, eflags); - } - - /* - * Initialize MergeAppend's result tuple type and slot. If the child - * plans all produce the same fixed slot type, we can use that slot type; - * otherwise make a virtual slot. (Note that the result slot itself is - * used only to return a null tuple at end of execution; real tuples are - * returned to the caller in the children's own result slots. What we are - * doing here is allowing the parent plan node to optimize if the - * MergeAppend will return only one kind of slot.) - */ - mergeops = ExecGetCommonSlotOps(mergeplanstates, j); - if (mergeops != NULL) - { - ExecInitResultTupleSlotTL(&mergestate->as.ps, mergeops); - } - else - { - ExecInitResultTupleSlotTL(&mergestate->as.ps, &TTSOpsVirtual); - /* show that the output slot type is not fixed */ - mergestate->as.ps.resultopsset = true; - mergestate->as.ps.resultopsfixed = false; - } - - /* - * Miscellaneous initialization - */ - mergestate->as.ps.ps_ProjInfo = NULL; - /* * initialize sort-key information */ @@ -335,59 +255,14 @@ heap_compare_slots(Datum a, Datum b, void *arg) void ExecEndMergeAppend(MergeAppendState *node) { - PlanState **mergeplans; - int nplans; - int i; - - /* - * get information from the node - */ - mergeplans = node->as.plans; - nplans = node->as.nplans; - - /* - * shut down each of the subscans - */ - for (i = 0; i < nplans; i++) - ExecEndNode(mergeplans[i]); + ExecEndAppendBase(&node->as); } void ExecReScanMergeAppend(MergeAppendState *node) { - int i; + ExecReScanAppendBase(&node->as); - /* - * If any PARAM_EXEC Params used in pruning expressions have changed, then - * we'd better unset the valid subplans so that they are reselected for - * the new parameter values. - */ - if (node->as.prune_state && - bms_overlap(node->as.ps.chgParam, - node->as.prune_state->execparamids)) - { - bms_free(node->as.valid_subplans); - node->as.valid_subplans = NULL; - } - - for (i = 0; i < node->as.nplans; i++) - { - PlanState *subnode = node->as.plans[i]; - - /* - * ExecReScan doesn't know about my subplans, so I have to do - * changed-parameter signaling myself. - */ - if (node->as.ps.chgParam != NULL) - UpdateChangedParamSet(subnode, node->as.ps.chgParam); - - /* - * If chgParam of subnode is not null then plan will be re-scanned by - * first ExecProcNode. - */ - if (subnode->chgParam == NULL) - ExecReScan(subnode); - } binaryheap_reset(node->ms_heap); node->ms_initialized = false; } diff --git a/src/include/executor/execAppend.h b/src/include/executor/execAppend.h new file mode 100644 index 00000000000..a8f41bad921 --- /dev/null +++ b/src/include/executor/execAppend.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * execAppend.h + * Support functions for MergeAppend and Append nodes. + * + * Copyright (c) 2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/include/executor/execAppend.h + *------------------------------------------------------------------------- + */ + +#ifndef EXECAPPEND_H +#define EXECAPPEND_H + +#include "nodes/execnodes.h" + +extern void ExecInitAppendBase(AppendBaseState *state, + AppendBase *node, + EState *estate, + int eflags, + int first_partial_plan, + int *first_valid_partial_plan); +extern void ExecEndAppendBase(AppendBaseState *node); +extern void ExecReScanAppendBase(AppendBaseState *node); + +#endif /* EXECAPPEND_H */ -- 2.39.5 (Apple Git-154)