v18-0003-Extract-common-Append-MergeAppend-executor-logic.patch
application/octet-stream
Filename: v18-0003-Extract-common-Append-MergeAppend-executor-logic.patch
Type: application/octet-stream
Part: 4
Message:
Re: Asynchronous MergeAppend
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v18-0003
Subject: Extract common Append/MergeAppend executor logic into execAppend.c
| File | + | − |
|---|---|---|
| src/backend/executor/execAppend.c | 208 | 0 |
| src/backend/executor/Makefile | 1 | 0 |
| src/backend/executor/meson.build | 1 | 0 |
| src/backend/executor/nodeAppend.c | 33 | 183 |
| src/backend/executor/nodeMergeAppend.c | 13 | 138 |
| src/include/executor/execAppend.h | 26 | 0 |
From d2c6daaf54a12a9820cf6c486334e06275409c89 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 5 Apr 2026 03:53:13 +0300
Subject: [PATCH v18 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 <matheusssilv97@gmail.com>
Co-authored-by: Alexander Korotkov <aekorotkov@gmail.com>
Reviewed-by: Alexander Pyhalov <a.pyhalov@postgrespro.ru>
Reviewed-by: Alena Rybakina <a.rybakina@postgrespro.ru>
---
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)