From dd18d8f8be73f5f429bc9eacee7cdc39c296b4d5 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 5 Apr 2026 04:00:15 +0300
Subject: [PATCH v21 2/5] Introduce AppendBase/AppendBaseState base types for
 Append/MergeAppend

Introduce common base types AppendBase (plan node) and AppendBaseState
(executor state) to unify the fields shared between Append/MergeAppend and
AppendState/MergeAppendState.

AppendBase holds the subplan list, appendrel identifiers, and partition
pruning index.  AppendBaseState holds the subplan state array, asynchronous
execution infrastructure, and partition pruning state.

Append and MergeAppend now embed AppendBase as their first field (ab),
while AppendState and MergeAppendState both embed AppendBaseState as
their first field (as).  This follows the same C struct inheritance
pattern used by Scan/ScanState and Join/JoinState throughout the
codebase.  The name "AppendBase" was chosen because just "Append" is already
taken.

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>
---
 contrib/pg_overexplain/pg_overexplain.c |   8 +-
 contrib/pg_plan_advice/pgpa_scan.c      |   4 +-
 contrib/pg_plan_advice/pgpa_walker.c    |   8 +-
 contrib/postgres_fdw/postgres_fdw.c     |  12 +-
 src/backend/commands/explain.c          |  52 +----
 src/backend/executor/execAmi.c          |   2 +-
 src/backend/executor/execCurrent.c      |   4 +-
 src/backend/executor/execProcnode.c     |   8 +-
 src/backend/executor/nodeAppend.c       | 284 ++++++++++++------------
 src/backend/executor/nodeMergeAppend.c  |  82 +++----
 src/backend/nodes/nodeFuncs.c           |   9 +-
 src/backend/optimizer/plan/createplan.c |  46 ++--
 src/backend/optimizer/plan/setrefs.c    | 173 ++++-----------
 src/backend/optimizer/plan/subselect.c  |   4 +-
 src/backend/utils/adt/ruleutils.c       |   8 +-
 src/include/nodes/execnodes.h           |  65 ++++--
 src/include/nodes/plannodes.h           |  66 +++---
 src/tools/pgindent/typedefs.list        |   2 +
 18 files changed, 370 insertions(+), 467 deletions(-)

diff --git a/contrib/pg_overexplain/pg_overexplain.c b/contrib/pg_overexplain/pg_overexplain.c
index b9933eb395c..0d5e85a5f7e 100644
--- a/contrib/pg_overexplain/pg_overexplain.c
+++ b/contrib/pg_overexplain/pg_overexplain.c
@@ -234,18 +234,18 @@ overexplain_per_node_hook(PlanState *planstate, List *ancestors,
 				break;
 			case T_Append:
 				overexplain_bitmapset("Append RTIs",
-									  ((Append *) plan)->apprelids,
+									  ((Append *) plan)->ab.apprelids,
 									  es);
 				overexplain_bitmapset_list("Child Append RTIs",
-										   ((Append *) plan)->child_append_relid_sets,
+										   ((Append *) plan)->ab.child_append_relid_sets,
 										   es);
 				break;
 			case T_MergeAppend:
 				overexplain_bitmapset("Append RTIs",
-									  ((MergeAppend *) plan)->apprelids,
+									  ((MergeAppend *) plan)->ab.apprelids,
 									  es);
 				overexplain_bitmapset_list("Child Append RTIs",
-										   ((MergeAppend *) plan)->child_append_relid_sets,
+										   ((MergeAppend *) plan)->ab.child_append_relid_sets,
 										   es);
 				break;
 			case T_Result:
diff --git a/contrib/pg_plan_advice/pgpa_scan.c b/contrib/pg_plan_advice/pgpa_scan.c
index 4da77936216..f8fc3d0bb63 100644
--- a/contrib/pg_plan_advice/pgpa_scan.c
+++ b/contrib/pg_plan_advice/pgpa_scan.c
@@ -164,7 +164,7 @@ pgpa_build_scan(pgpa_plan_walker_context *walker, Plan *plan,
 
 				/* Be sure to account for pulled-up scans. */
 				child_append_relid_sets =
-					((Append *) plan)->child_append_relid_sets;
+					((Append *) plan)->ab.child_append_relid_sets;
 				break;
 			case T_MergeAppend:
 				/* Same logic here as for Append, above. */
@@ -176,7 +176,7 @@ pgpa_build_scan(pgpa_plan_walker_context *walker, Plan *plan,
 
 				/* Be sure to account for pulled-up scans. */
 				child_append_relid_sets =
-					((MergeAppend *) plan)->child_append_relid_sets;
+					((MergeAppend *) plan)->ab.child_append_relid_sets;
 				break;
 			default:
 				strategy = PGPA_SCAN_ORDINARY;
diff --git a/contrib/pg_plan_advice/pgpa_walker.c b/contrib/pg_plan_advice/pgpa_walker.c
index 7b299440d4d..a3453d736bf 100644
--- a/contrib/pg_plan_advice/pgpa_walker.c
+++ b/contrib/pg_plan_advice/pgpa_walker.c
@@ -441,14 +441,14 @@ pgpa_walk_recursively(pgpa_plan_walker_context *walker, Plan *plan,
 			{
 				Append	   *aplan = (Append *) plan;
 
-				extraplans = aplan->appendplans;
+				extraplans = aplan->ab.subplans;
 			}
 			break;
 		case T_MergeAppend:
 			{
 				MergeAppend *maplan = (MergeAppend *) plan;
 
-				extraplans = maplan->mergeplans;
+				extraplans = maplan->ab.subplans;
 			}
 			break;
 		case T_BitmapAnd:
@@ -571,9 +571,9 @@ pgpa_relids(Plan *plan)
 	else if (IsA(plan, ForeignScan))
 		return ((ForeignScan *) plan)->fs_relids;
 	else if (IsA(plan, Append))
-		return ((Append *) plan)->apprelids;
+		return ((Append *) plan)->ab.apprelids;
 	else if (IsA(plan, MergeAppend))
-		return ((MergeAppend *) plan)->apprelids;
+		return ((MergeAppend *) plan)->ab.apprelids;
 
 	return NULL;
 }
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 8b660a6c02c..194f1c0a4a7 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -2506,8 +2506,8 @@ find_modifytable_subplan(PlannerInfo *root,
 	{
 		Append	   *appendplan = (Append *) subplan;
 
-		if (subplan_index < list_length(appendplan->appendplans))
-			subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index);
+		if (subplan_index < list_length(appendplan->ab.subplans))
+			subplan = (Plan *) list_nth(appendplan->ab.subplans, subplan_index);
 	}
 	else if (IsA(subplan, Result) &&
 			 outerPlan(subplan) != NULL &&
@@ -2515,8 +2515,8 @@ find_modifytable_subplan(PlannerInfo *root,
 	{
 		Append	   *appendplan = (Append *) outerPlan(subplan);
 
-		if (subplan_index < list_length(appendplan->appendplans))
-			subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index);
+		if (subplan_index < list_length(appendplan->ab.subplans))
+			subplan = (Plan *) list_nth(appendplan->ab.subplans, subplan_index);
 	}
 
 	/* Now, have we got a ForeignScan on the desired rel? */
@@ -8094,7 +8094,7 @@ postgresForeignAsyncConfigureWait(AsyncRequest *areq)
 	PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
 	AsyncRequest *pendingAreq = fsstate->conn_state->pendingAreq;
 	AppendState *requestor = (AppendState *) areq->requestor;
-	WaitEventSet *set = requestor->as_eventset;
+	WaitEventSet *set = requestor->as.eventset;
 
 	/* This should not be called unless callback_pending */
 	Assert(areq->callback_pending);
@@ -8136,7 +8136,7 @@ postgresForeignAsyncConfigureWait(AsyncRequest *areq)
 		 * below, because we might otherwise end up with no configured events
 		 * other than the postmaster death event.
 		 */
-		if (!bms_is_empty(requestor->as_needrequest))
+		if (!bms_is_empty(requestor->as.needrequest))
 			return;
 		if (GetNumRegisteredWaitEvents(set) > 1)
 			return;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index a40d03d35f3..6afedfd243c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1230,12 +1230,9 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 											linitial_int(((ModifyTable *) plan)->resultRelations));
 			break;
 		case T_Append:
-			*rels_used = bms_add_members(*rels_used,
-										 ((Append *) plan)->apprelids);
-			break;
 		case T_MergeAppend:
 			*rels_used = bms_add_members(*rels_used,
-										 ((MergeAppend *) plan)->apprelids);
+										 ((AppendBase *) plan)->apprelids);
 			break;
 		case T_Result:
 			*rels_used = bms_add_members(*rels_used,
@@ -1269,34 +1266,17 @@ plan_is_disabled(Plan *plan)
 	 * Handle special nodes first.  Children of BitmapOrs and BitmapAnds can't
 	 * be disabled, so no need to handle those specifically.
 	 */
-	if (IsA(plan, Append))
-	{
-		ListCell   *lc;
-		Append	   *aplan = (Append *) plan;
-
-		/*
-		 * Sum the Append childrens' disabled_nodes.  This purposefully
-		 * includes any run-time pruned children.  Ignoring those could give
-		 * us the incorrect number of disabled nodes.
-		 */
-		foreach(lc, aplan->appendplans)
-		{
-			Plan	   *subplan = lfirst(lc);
-
-			child_disabled_nodes += subplan->disabled_nodes;
-		}
-	}
-	else if (IsA(plan, MergeAppend))
+	if (IsA(plan, Append) || IsA(plan, MergeAppend))
 	{
 		ListCell   *lc;
-		MergeAppend *maplan = (MergeAppend *) plan;
+		AppendBase *abplan = (AppendBase *) plan;
 
 		/*
-		 * Sum the MergeAppend childrens' disabled_nodes.  This purposefully
-		 * includes any run-time pruned children.  Ignoring those could give
-		 * us the incorrect number of disabled nodes.
+		 * Sum the Append/MergeAppend childrens' disabled_nodes.  This
+		 * purposefully includes any run-time pruned children.  Ignoring
+		 * those could give us the incorrect number of disabled nodes.
 		 */
-		foreach(lc, maplan->mergeplans)
+		foreach(lc, abplan->subplans)
 		{
 			Plan	   *subplan = lfirst(lc);
 
@@ -2347,13 +2327,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
 	switch (nodeTag(plan))
 	{
 		case T_Append:
-			ExplainMissingMembers(((AppendState *) planstate)->as_nplans,
-								  list_length(((Append *) plan)->appendplans),
-								  es);
-			break;
 		case T_MergeAppend:
-			ExplainMissingMembers(((MergeAppendState *) planstate)->ms_nplans,
-								  list_length(((MergeAppend *) plan)->mergeplans),
+			ExplainMissingMembers(((AppendBaseState *) planstate)->nplans,
+								  list_length(((AppendBase *) plan)->subplans),
 								  es);
 			break;
 		default:
@@ -2397,13 +2373,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
 	switch (nodeTag(plan))
 	{
 		case T_Append:
-			ExplainMemberNodes(((AppendState *) planstate)->appendplans,
-							   ((AppendState *) planstate)->as_nplans,
-							   ancestors, es);
-			break;
 		case T_MergeAppend:
-			ExplainMemberNodes(((MergeAppendState *) planstate)->mergeplans,
-							   ((MergeAppendState *) planstate)->ms_nplans,
+			ExplainMemberNodes(((AppendBaseState *) planstate)->plans,
+							   ((AppendBaseState *) planstate)->nplans,
 							   ancestors, es);
 			break;
 		case T_BitmapAnd:
@@ -2617,7 +2589,7 @@ static void
 show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
 					   ExplainState *es)
 {
-	MergeAppend *plan = (MergeAppend *) mstate->ps.plan;
+	MergeAppend *plan = (MergeAppend *) mstate->as.ps.plan;
 
 	show_sort_group_keys((PlanState *) mstate, "Sort Key",
 						 plan->numCols, 0, plan->sortColIdx,
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 37fe03fdc37..2d8e621208f 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -538,7 +538,7 @@ ExecSupportsBackwardScan(Plan *node)
 				if (((Append *) node)->nasyncplans > 0)
 					return false;
 
-				foreach(l, ((Append *) node)->appendplans)
+				foreach(l, ((Append *) node)->ab.subplans)
 				{
 					if (!ExecSupportsBackwardScan((Plan *) lfirst(l)))
 						return false;
diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c
index 9058de2e998..b8cd0967c7b 100644
--- a/src/backend/executor/execCurrent.c
+++ b/src/backend/executor/execCurrent.c
@@ -374,9 +374,9 @@ search_plan_tree(PlanState *node, Oid table_oid,
 				AppendState *astate = (AppendState *) node;
 				int			i;
 
-				for (i = 0; i < astate->as_nplans; i++)
+				for (i = 0; i < astate->as.nplans; i++)
 				{
-					ScanState  *elem = search_plan_tree(astate->appendplans[i],
+					ScanState  *elem = search_plan_tree(astate->as.plans[i],
 														table_oid,
 														pending_rescan);
 
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index 7c4c66e323f..fa1c69bf89f 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -891,8 +891,8 @@ ExecSetTupleBound(int64 tuples_needed, PlanState *child_node)
 		AppendState *aState = (AppendState *) child_node;
 		int			i;
 
-		for (i = 0; i < aState->as_nplans; i++)
-			ExecSetTupleBound(tuples_needed, aState->appendplans[i]);
+		for (i = 0; i < aState->as.nplans; i++)
+			ExecSetTupleBound(tuples_needed, aState->as.plans[i]);
 	}
 	else if (IsA(child_node, MergeAppendState))
 	{
@@ -904,8 +904,8 @@ ExecSetTupleBound(int64 tuples_needed, PlanState *child_node)
 		MergeAppendState *maState = (MergeAppendState *) child_node;
 		int			i;
 
-		for (i = 0; i < maState->ms_nplans; i++)
-			ExecSetTupleBound(tuples_needed, maState->mergeplans[i]);
+		for (i = 0; i < maState->as.nplans; i++)
+			ExecSetTupleBound(tuples_needed, maState->as.plans[i]);
 	}
 	else if (IsA(child_node, ResultState))
 	{
diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index 987358e27fa..40201998fc7 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -128,9 +128,9 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 	/*
 	 * create new AppendState for our append node
 	 */
-	appendstate->ps.plan = (Plan *) node;
-	appendstate->ps.state = estate;
-	appendstate->ps.ExecProcNode = ExecAppend;
+	appendstate->as.ps.plan = (Plan *) node;
+	appendstate->as.ps.state = estate;
+	appendstate->as.ps.ExecProcNode = ExecAppend;
 
 	/* Let choose_next_subplan_* function handle setting the first subplan */
 	appendstate->as_whichplan = INVALID_SUBPLAN_INDEX;
@@ -138,7 +138,7 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 	appendstate->as_begun = false;
 
 	/* If run-time partition pruning is enabled, then set that up now */
-	if (node->part_prune_index >= 0)
+	if (node->ab.part_prune_index >= 0)
 	{
 		PartitionPruneState *prunestate;
 
@@ -147,12 +147,12 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 		 * subplans to initialize (validsubplans) by taking into account the
 		 * result of performing initial pruning if any.
 		 */
-		prunestate = ExecInitPartitionExecPruning(&appendstate->ps,
-												  list_length(node->appendplans),
-												  node->part_prune_index,
-												  node->apprelids,
+		prunestate = ExecInitPartitionExecPruning(&appendstate->as.ps,
+												  list_length(node->ab.subplans),
+												  node->ab.part_prune_index,
+												  node->ab.apprelids,
 												  &validsubplans);
-		appendstate->as_prune_state = prunestate;
+		appendstate->as.prune_state = prunestate;
 		nplans = bms_num_members(validsubplans);
 
 		/*
@@ -162,23 +162,23 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 		 */
 		if (!prunestate->do_exec_prune && nplans > 0)
 		{
-			appendstate->as_valid_subplans = bms_add_range(NULL, 0, nplans - 1);
-			appendstate->as_valid_subplans_identified = true;
+			appendstate->as.valid_subplans = bms_add_range(NULL, 0, nplans - 1);
+			appendstate->as.valid_subplans_identified = true;
 		}
 	}
 	else
 	{
-		nplans = list_length(node->appendplans);
+		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 =
+		appendstate->as.valid_subplans = validsubplans =
 			bms_add_range(NULL, 0, nplans - 1);
-		appendstate->as_valid_subplans_identified = true;
-		appendstate->as_prune_state = NULL;
+		appendstate->as.valid_subplans_identified = true;
+		appendstate->as.prune_state = NULL;
 	}
 
 	appendplanstates = (PlanState **) palloc(nplans *
@@ -197,7 +197,7 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 	i = -1;
 	while ((i = bms_next_member(validsubplans, i)) >= 0)
 	{
-		Plan	   *initNode = (Plan *) list_nth(node->appendplans, i);
+		Plan	   *initNode = (Plan *) list_nth(node->ab.subplans, i);
 
 		/*
 		 * Record async subplans.  When executing EvalPlanQual, we treat them
@@ -220,8 +220,8 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 	}
 
 	appendstate->as_first_partial_plan = firstvalid;
-	appendstate->appendplans = appendplanstates;
-	appendstate->as_nplans = nplans;
+	appendstate->as.plans = appendplanstates;
+	appendstate->as.nplans = nplans;
 
 	/*
 	 * Initialize Append's result tuple type and slot.  If the child plans all
@@ -235,30 +235,30 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 	appendops = ExecGetCommonSlotOps(appendplanstates, j);
 	if (appendops != NULL)
 	{
-		ExecInitResultTupleSlotTL(&appendstate->ps, appendops);
+		ExecInitResultTupleSlotTL(&appendstate->as.ps, appendops);
 	}
 	else
 	{
-		ExecInitResultTupleSlotTL(&appendstate->ps, &TTSOpsVirtual);
+		ExecInitResultTupleSlotTL(&appendstate->as.ps, &TTSOpsVirtual);
 		/* show that the output slot type is not fixed */
-		appendstate->ps.resultopsset = true;
-		appendstate->ps.resultopsfixed = false;
+		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.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;
+	appendstate->as.needrequest = NULL;
+	appendstate->as.eventset = NULL;
+	appendstate->as.valid_asyncplans = NULL;
 
 	if (nasyncplans > 0)
 	{
-		appendstate->as_asyncrequests = (AsyncRequest **)
+		appendstate->as.asyncrequests = (AsyncRequest **)
 			palloc0(nplans * sizeof(AsyncRequest *));
 
 		i = -1;
@@ -274,13 +274,13 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 			areq->request_complete = false;
 			areq->result = NULL;
 
-			appendstate->as_asyncrequests[i] = areq;
+			appendstate->as.asyncrequests[i] = areq;
 		}
 
-		appendstate->as_asyncresults = (TupleTableSlot **)
+		appendstate->as.asyncresults = (TupleTableSlot **)
 			palloc0(nasyncplans * sizeof(TupleTableSlot *));
 
-		if (appendstate->as_valid_subplans_identified)
+		if (appendstate->as.valid_subplans_identified)
 			classify_matching_subplans(appendstate);
 	}
 
@@ -288,7 +288,7 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 	 * Miscellaneous initialization
 	 */
 
-	appendstate->ps.ps_ProjInfo = NULL;
+	appendstate->as.ps.ps_ProjInfo = NULL;
 
 	/* For parallel query, this will be overridden later. */
 	appendstate->choose_next_subplan = choose_next_subplan_locally;
@@ -318,11 +318,11 @@ ExecAppend(PlanState *pstate)
 		Assert(!node->as_syncdone);
 
 		/* Nothing to do if there are no subplans */
-		if (node->as_nplans == 0)
-			return ExecClearTuple(node->ps.ps_ResultTupleSlot);
+		if (node->as.nplans == 0)
+			return ExecClearTuple(node->as.ps.ps_ResultTupleSlot);
 
 		/* If there are any async subplans, begin executing them. */
-		if (node->as_nasyncplans > 0)
+		if (node->as.nasyncplans > 0)
 			ExecAppendAsyncBegin(node);
 
 		/*
@@ -330,11 +330,11 @@ ExecAppend(PlanState *pstate)
 		 * proceeding.
 		 */
 		if (!node->choose_next_subplan(node) && node->as_nasyncremain == 0)
-			return ExecClearTuple(node->ps.ps_ResultTupleSlot);
+			return ExecClearTuple(node->as.ps.ps_ResultTupleSlot);
 
 		Assert(node->as_syncdone ||
 			   (node->as_whichplan >= 0 &&
-				node->as_whichplan < node->as_nplans));
+				node->as_whichplan < node->as.nplans));
 
 		/* And we're initialized. */
 		node->as_begun = true;
@@ -349,19 +349,19 @@ ExecAppend(PlanState *pstate)
 		/*
 		 * try to get a tuple from an async subplan if any
 		 */
-		if (node->as_syncdone || !bms_is_empty(node->as_needrequest))
+		if (node->as_syncdone || !bms_is_empty(node->as.needrequest))
 		{
 			if (ExecAppendAsyncGetNext(node, &result))
 				return result;
 			Assert(!node->as_syncdone);
-			Assert(bms_is_empty(node->as_needrequest));
+			Assert(bms_is_empty(node->as.needrequest));
 		}
 
 		/*
 		 * figure out which sync subplan we are currently processing
 		 */
-		Assert(node->as_whichplan >= 0 && node->as_whichplan < node->as_nplans);
-		subnode = node->appendplans[node->as_whichplan];
+		Assert(node->as_whichplan >= 0 && node->as_whichplan < node->as.nplans);
+		subnode = node->as.plans[node->as_whichplan];
 
 		/*
 		 * get a tuple from the subplan
@@ -388,7 +388,7 @@ ExecAppend(PlanState *pstate)
 
 		/* choose new sync subplan; if no sync/async subplans, we're done */
 		if (!node->choose_next_subplan(node) && node->as_nasyncremain == 0)
-			return ExecClearTuple(node->ps.ps_ResultTupleSlot);
+			return ExecClearTuple(node->as.ps.ps_ResultTupleSlot);
 	}
 }
 
@@ -410,8 +410,8 @@ ExecEndAppend(AppendState *node)
 	/*
 	 * get information from the node
 	 */
-	appendplans = node->appendplans;
-	nplans = node->as_nplans;
+	appendplans = node->as.plans;
+	nplans = node->as.nplans;
 
 	/*
 	 * shut down each of the subscans
@@ -423,7 +423,7 @@ ExecEndAppend(AppendState *node)
 void
 ExecReScanAppend(AppendState *node)
 {
-	int			nasyncplans = node->as_nasyncplans;
+	int			nasyncplans = node->as.nasyncplans;
 	int			i;
 
 	/*
@@ -431,27 +431,27 @@ ExecReScanAppend(AppendState *node)
 	 * 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->ps.chgParam,
-					node->as_prune_state->execparamids))
+	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;
+		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++)
+	for (i = 0; i < node->as.nplans; i++)
 	{
-		PlanState  *subnode = node->appendplans[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->ps.chgParam != NULL)
-			UpdateChangedParamSet(subnode, node->ps.chgParam);
+		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
@@ -465,9 +465,9 @@ ExecReScanAppend(AppendState *node)
 	if (nasyncplans > 0)
 	{
 		i = -1;
-		while ((i = bms_next_member(node->as_asyncplans, i)) >= 0)
+		while ((i = bms_next_member(node->as.asyncplans, i)) >= 0)
 		{
-			AsyncRequest *areq = node->as_asyncrequests[i];
+			AsyncRequest *areq = node->as.asyncrequests[i];
 
 			areq->callback_pending = false;
 			areq->request_complete = false;
@@ -476,8 +476,8 @@ ExecReScanAppend(AppendState *node)
 
 		node->as_nasyncresults = 0;
 		node->as_nasyncremain = 0;
-		bms_free(node->as_needrequest);
-		node->as_needrequest = NULL;
+		bms_free(node->as.needrequest);
+		node->as.needrequest = NULL;
 	}
 
 	/* Let choose_next_subplan_* function handle setting the first subplan */
@@ -504,7 +504,7 @@ ExecAppendEstimate(AppendState *node,
 {
 	node->pstate_len =
 		add_size(offsetof(ParallelAppendState, pa_finished),
-				 sizeof(bool) * node->as_nplans);
+				 sizeof(bool) * node->as.nplans);
 
 	shm_toc_estimate_chunk(&pcxt->estimator, node->pstate_len);
 	shm_toc_estimate_keys(&pcxt->estimator, 1);
@@ -526,7 +526,7 @@ ExecAppendInitializeDSM(AppendState *node,
 	pstate = shm_toc_allocate(pcxt->toc, node->pstate_len);
 	memset(pstate, 0, node->pstate_len);
 	LWLockInitialize(&pstate->pa_lock, LWTRANCHE_PARALLEL_APPEND);
-	shm_toc_insert(pcxt->toc, node->ps.plan->plan_node_id, pstate);
+	shm_toc_insert(pcxt->toc, node->as.ps.plan->plan_node_id, pstate);
 
 	node->as_pstate = pstate;
 	node->choose_next_subplan = choose_next_subplan_for_leader;
@@ -544,7 +544,7 @@ ExecAppendReInitializeDSM(AppendState *node, ParallelContext *pcxt)
 	ParallelAppendState *pstate = node->as_pstate;
 
 	pstate->pa_next_plan = 0;
-	memset(pstate->pa_finished, 0, sizeof(bool) * node->as_nplans);
+	memset(pstate->pa_finished, 0, sizeof(bool) * node->as.nplans);
 }
 
 /* ----------------------------------------------------------------
@@ -557,7 +557,7 @@ ExecAppendReInitializeDSM(AppendState *node, ParallelContext *pcxt)
 void
 ExecAppendInitializeWorker(AppendState *node, ParallelWorkerContext *pwcxt)
 {
-	node->as_pstate = shm_toc_lookup(pwcxt->toc, node->ps.plan->plan_node_id, false);
+	node->as_pstate = shm_toc_lookup(pwcxt->toc, node->as.ps.plan->plan_node_id, false);
 	node->choose_next_subplan = choose_next_subplan_for_worker;
 }
 
@@ -575,7 +575,7 @@ choose_next_subplan_locally(AppendState *node)
 	int			nextplan;
 
 	/* We should never be called when there are no subplans */
-	Assert(node->as_nplans > 0);
+	Assert(node->as.nplans > 0);
 
 	/* Nothing to do if syncdone */
 	if (node->as_syncdone)
@@ -590,33 +590,33 @@ choose_next_subplan_locally(AppendState *node)
 	 */
 	if (whichplan == INVALID_SUBPLAN_INDEX)
 	{
-		if (node->as_nasyncplans > 0)
+		if (node->as.nasyncplans > 0)
 		{
 			/* We'd have filled as_valid_subplans already */
-			Assert(node->as_valid_subplans_identified);
+			Assert(node->as.valid_subplans_identified);
 		}
-		else if (!node->as_valid_subplans_identified)
+		else if (!node->as.valid_subplans_identified)
 		{
-			node->as_valid_subplans =
-				ExecFindMatchingSubPlans(node->as_prune_state, false, NULL);
-			node->as_valid_subplans_identified = true;
+			node->as.valid_subplans =
+				ExecFindMatchingSubPlans(node->as.prune_state, false, NULL);
+			node->as.valid_subplans_identified = true;
 		}
 
 		whichplan = -1;
 	}
 
 	/* Ensure whichplan is within the expected range */
-	Assert(whichplan >= -1 && whichplan <= node->as_nplans);
+	Assert(whichplan >= -1 && whichplan <= node->as.nplans);
 
-	if (ScanDirectionIsForward(node->ps.state->es_direction))
-		nextplan = bms_next_member(node->as_valid_subplans, whichplan);
+	if (ScanDirectionIsForward(node->as.ps.state->es_direction))
+		nextplan = bms_next_member(node->as.valid_subplans, whichplan);
 	else
-		nextplan = bms_prev_member(node->as_valid_subplans, whichplan);
+		nextplan = bms_prev_member(node->as.valid_subplans, whichplan);
 
 	if (nextplan < 0)
 	{
 		/* Set as_syncdone if in async mode */
-		if (node->as_nasyncplans > 0)
+		if (node->as.nasyncplans > 0)
 			node->as_syncdone = true;
 		return false;
 	}
@@ -640,10 +640,10 @@ choose_next_subplan_for_leader(AppendState *node)
 	ParallelAppendState *pstate = node->as_pstate;
 
 	/* Backward scan is not supported by parallel-aware plans */
-	Assert(ScanDirectionIsForward(node->ps.state->es_direction));
+	Assert(ScanDirectionIsForward(node->as.ps.state->es_direction));
 
 	/* We should never be called when there are no subplans */
-	Assert(node->as_nplans > 0);
+	Assert(node->as.nplans > 0);
 
 	LWLockAcquire(&pstate->pa_lock, LW_EXCLUSIVE);
 
@@ -655,18 +655,18 @@ choose_next_subplan_for_leader(AppendState *node)
 	else
 	{
 		/* Start with last subplan. */
-		node->as_whichplan = node->as_nplans - 1;
+		node->as_whichplan = node->as.nplans - 1;
 
 		/*
 		 * If we've yet to determine the valid subplans then do so now.  If
 		 * run-time pruning is disabled then the valid subplans will always be
 		 * set to all subplans.
 		 */
-		if (!node->as_valid_subplans_identified)
+		if (!node->as.valid_subplans_identified)
 		{
-			node->as_valid_subplans =
-				ExecFindMatchingSubPlans(node->as_prune_state, false, NULL);
-			node->as_valid_subplans_identified = true;
+			node->as.valid_subplans =
+				ExecFindMatchingSubPlans(node->as.prune_state, false, NULL);
+			node->as.valid_subplans_identified = true;
 
 			/*
 			 * Mark each invalid plan as finished to allow the loop below to
@@ -722,10 +722,10 @@ choose_next_subplan_for_worker(AppendState *node)
 	ParallelAppendState *pstate = node->as_pstate;
 
 	/* Backward scan is not supported by parallel-aware plans */
-	Assert(ScanDirectionIsForward(node->ps.state->es_direction));
+	Assert(ScanDirectionIsForward(node->as.ps.state->es_direction));
 
 	/* We should never be called when there are no subplans */
-	Assert(node->as_nplans > 0);
+	Assert(node->as.nplans > 0);
 
 	LWLockAcquire(&pstate->pa_lock, LW_EXCLUSIVE);
 
@@ -738,11 +738,11 @@ choose_next_subplan_for_worker(AppendState *node)
 	 * run-time pruning is disabled then the valid subplans will always be set
 	 * to all subplans.
 	 */
-	else if (!node->as_valid_subplans_identified)
+	else if (!node->as.valid_subplans_identified)
 	{
-		node->as_valid_subplans =
-			ExecFindMatchingSubPlans(node->as_prune_state, false, NULL);
-		node->as_valid_subplans_identified = true;
+		node->as.valid_subplans =
+			ExecFindMatchingSubPlans(node->as.prune_state, false, NULL);
+		node->as.valid_subplans_identified = true;
 
 		mark_invalid_subplans_as_finished(node);
 	}
@@ -762,7 +762,7 @@ choose_next_subplan_for_worker(AppendState *node)
 	{
 		int			nextplan;
 
-		nextplan = bms_next_member(node->as_valid_subplans,
+		nextplan = bms_next_member(node->as.valid_subplans,
 								   pstate->pa_next_plan);
 		if (nextplan >= 0)
 		{
@@ -775,7 +775,7 @@ choose_next_subplan_for_worker(AppendState *node)
 			 * Try looping back to the first valid partial plan, if there is
 			 * one.  If there isn't, arrange to bail out below.
 			 */
-			nextplan = bms_next_member(node->as_valid_subplans,
+			nextplan = bms_next_member(node->as.valid_subplans,
 									   node->as_first_partial_plan - 1);
 			pstate->pa_next_plan =
 				nextplan < 0 ? node->as_whichplan : nextplan;
@@ -800,7 +800,7 @@ choose_next_subplan_for_worker(AppendState *node)
 
 	/* Pick the plan we found, and advance pa_next_plan one more time. */
 	node->as_whichplan = pstate->pa_next_plan;
-	pstate->pa_next_plan = bms_next_member(node->as_valid_subplans,
+	pstate->pa_next_plan = bms_next_member(node->as.valid_subplans,
 										   pstate->pa_next_plan);
 
 	/*
@@ -809,7 +809,7 @@ choose_next_subplan_for_worker(AppendState *node)
 	 */
 	if (pstate->pa_next_plan < 0)
 	{
-		int			nextplan = bms_next_member(node->as_valid_subplans,
+		int			nextplan = bms_next_member(node->as.valid_subplans,
 											   node->as_first_partial_plan - 1);
 
 		if (nextplan >= 0)
@@ -851,16 +851,16 @@ mark_invalid_subplans_as_finished(AppendState *node)
 	Assert(node->as_pstate);
 
 	/* Shouldn't have been called when run-time pruning is not enabled */
-	Assert(node->as_prune_state);
+	Assert(node->as.prune_state);
 
 	/* Nothing to do if all plans are valid */
-	if (bms_num_members(node->as_valid_subplans) == node->as_nplans)
+	if (bms_num_members(node->as.valid_subplans) == node->as.nplans)
 		return;
 
 	/* Mark all non-valid plans as finished */
-	for (i = 0; i < node->as_nplans; i++)
+	for (i = 0; i < node->as.nplans; i++)
 	{
-		if (!bms_is_member(i, node->as_valid_subplans))
+		if (!bms_is_member(i, node->as.valid_subplans))
 			node->as_pstate->pa_finished[i] = true;
 	}
 }
@@ -882,27 +882,27 @@ ExecAppendAsyncBegin(AppendState *node)
 	int			i;
 
 	/* Backward scan is not supported by async-aware Appends. */
-	Assert(ScanDirectionIsForward(node->ps.state->es_direction));
+	Assert(ScanDirectionIsForward(node->as.ps.state->es_direction));
 
 	/* We should never be called when there are no subplans */
-	Assert(node->as_nplans > 0);
+	Assert(node->as.nplans > 0);
 
 	/* We should never be called when there are no async subplans. */
-	Assert(node->as_nasyncplans > 0);
+	Assert(node->as.nasyncplans > 0);
 
 	/* If we've yet to determine the valid subplans then do so now. */
-	if (!node->as_valid_subplans_identified)
+	if (!node->as.valid_subplans_identified)
 	{
-		node->as_valid_subplans =
-			ExecFindMatchingSubPlans(node->as_prune_state, false, NULL);
-		node->as_valid_subplans_identified = true;
+		node->as.valid_subplans =
+			ExecFindMatchingSubPlans(node->as.prune_state, false, NULL);
+		node->as.valid_subplans_identified = true;
 
 		classify_matching_subplans(node);
 	}
 
 	/* Initialize state variables. */
-	node->as_syncdone = bms_is_empty(node->as_valid_subplans);
-	node->as_nasyncremain = bms_num_members(node->as_valid_asyncplans);
+	node->as_syncdone = bms_is_empty(node->as.valid_subplans);
+	node->as_nasyncremain = bms_num_members(node->as.valid_asyncplans);
 
 	/* Nothing to do if there are no valid async subplans. */
 	if (node->as_nasyncremain == 0)
@@ -910,9 +910,9 @@ ExecAppendAsyncBegin(AppendState *node)
 
 	/* Make a request for each of the valid async subplans. */
 	i = -1;
-	while ((i = bms_next_member(node->as_valid_asyncplans, i)) >= 0)
+	while ((i = bms_next_member(node->as.valid_asyncplans, i)) >= 0)
 	{
-		AsyncRequest *areq = node->as_asyncrequests[i];
+		AsyncRequest *areq = node->as.asyncrequests[i];
 
 		Assert(areq->request_index == i);
 		Assert(!areq->callback_pending);
@@ -964,7 +964,7 @@ ExecAppendAsyncGetNext(AppendState *node, TupleTableSlot **result)
 	if (node->as_syncdone)
 	{
 		Assert(node->as_nasyncremain == 0);
-		*result = ExecClearTuple(node->ps.ps_ResultTupleSlot);
+		*result = ExecClearTuple(node->as.ps.ps_ResultTupleSlot);
 		return true;
 	}
 
@@ -984,7 +984,7 @@ ExecAppendAsyncRequest(AppendState *node, TupleTableSlot **result)
 	int			i;
 
 	/* Nothing to do if there are no async subplans needing a new request. */
-	if (bms_is_empty(node->as_needrequest))
+	if (bms_is_empty(node->as.needrequest))
 	{
 		Assert(node->as_nasyncresults == 0);
 		return false;
@@ -997,17 +997,17 @@ ExecAppendAsyncRequest(AppendState *node, TupleTableSlot **result)
 	if (node->as_nasyncresults > 0)
 	{
 		--node->as_nasyncresults;
-		*result = node->as_asyncresults[node->as_nasyncresults];
+		*result = node->as.asyncresults[node->as_nasyncresults];
 		return true;
 	}
 
 	/* Make a new request for each of the async subplans that need it. */
-	needrequest = node->as_needrequest;
-	node->as_needrequest = NULL;
+	needrequest = node->as.needrequest;
+	node->as.needrequest = NULL;
 	i = -1;
 	while ((i = bms_next_member(needrequest, i)) >= 0)
 	{
-		AsyncRequest *areq = node->as_asyncrequests[i];
+		AsyncRequest *areq = node->as.asyncrequests[i];
 
 		/* Do the actual work. */
 		ExecAsyncRequest(areq);
@@ -1018,7 +1018,7 @@ ExecAppendAsyncRequest(AppendState *node, TupleTableSlot **result)
 	if (node->as_nasyncresults > 0)
 	{
 		--node->as_nasyncresults;
-		*result = node->as_asyncresults[node->as_nasyncresults];
+		*result = node->as.asyncresults[node->as_nasyncresults];
 		return true;
 	}
 
@@ -1034,7 +1034,7 @@ ExecAppendAsyncRequest(AppendState *node, TupleTableSlot **result)
 static void
 ExecAppendAsyncEventWait(AppendState *node)
 {
-	int			nevents = node->as_nasyncplans + 2;
+	int			nevents = node->as.nasyncplans + 2;
 	long		timeout = node->as_syncdone ? -1 : 0;
 	WaitEvent	occurred_event[EVENT_BUFFER_SIZE];
 	int			noccurred;
@@ -1043,16 +1043,16 @@ ExecAppendAsyncEventWait(AppendState *node)
 	/* We should never be called when there are no valid async subplans. */
 	Assert(node->as_nasyncremain > 0);
 
-	Assert(node->as_eventset == NULL);
-	node->as_eventset = CreateWaitEventSet(CurrentResourceOwner, nevents);
-	AddWaitEventToSet(node->as_eventset, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
+	Assert(node->as.eventset == NULL);
+	node->as.eventset = CreateWaitEventSet(CurrentResourceOwner, nevents);
+	AddWaitEventToSet(node->as.eventset, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
 					  NULL, NULL);
 
 	/* Give each waiting subplan a chance to add an event. */
 	i = -1;
-	while ((i = bms_next_member(node->as_asyncplans, i)) >= 0)
+	while ((i = bms_next_member(node->as.asyncplans, i)) >= 0)
 	{
-		AsyncRequest *areq = node->as_asyncrequests[i];
+		AsyncRequest *areq = node->as.asyncrequests[i];
 
 		if (areq->callback_pending)
 			ExecAsyncConfigureWait(areq);
@@ -1062,10 +1062,10 @@ ExecAppendAsyncEventWait(AppendState *node)
 	 * No need for further processing if none of the subplans configured any
 	 * events.
 	 */
-	if (GetNumRegisteredWaitEvents(node->as_eventset) == 1)
+	if (GetNumRegisteredWaitEvents(node->as.eventset) == 1)
 	{
-		FreeWaitEventSet(node->as_eventset);
-		node->as_eventset = NULL;
+		FreeWaitEventSet(node->as.eventset);
+		node->as.eventset = NULL;
 		return;
 	}
 
@@ -1081,7 +1081,7 @@ ExecAppendAsyncEventWait(AppendState *node)
 	 * we cannot change it now.  The pattern has possibly been copied to other
 	 * extensions too.
 	 */
-	AddWaitEventToSet(node->as_eventset, WL_LATCH_SET, PGINVALID_SOCKET,
+	AddWaitEventToSet(node->as.eventset, WL_LATCH_SET, PGINVALID_SOCKET,
 					  MyLatch, NULL);
 
 	/* Return at most EVENT_BUFFER_SIZE events in one call. */
@@ -1092,10 +1092,10 @@ ExecAppendAsyncEventWait(AppendState *node)
 	 * If the timeout is -1, wait until at least one event occurs.  If the
 	 * timeout is 0, poll for events, but do not wait at all.
 	 */
-	noccurred = WaitEventSetWait(node->as_eventset, timeout, occurred_event,
+	noccurred = WaitEventSetWait(node->as.eventset, timeout, occurred_event,
 								 nevents, WAIT_EVENT_APPEND_READY);
-	FreeWaitEventSet(node->as_eventset);
-	node->as_eventset = NULL;
+	FreeWaitEventSet(node->as.eventset);
+	node->as.eventset = NULL;
 	if (noccurred == 0)
 		return;
 
@@ -1168,14 +1168,14 @@ ExecAsyncAppendResponse(AsyncRequest *areq)
 	}
 
 	/* Save result so we can return it. */
-	Assert(node->as_nasyncresults < node->as_nasyncplans);
-	node->as_asyncresults[node->as_nasyncresults++] = slot;
+	Assert(node->as_nasyncresults < node->as.nasyncplans);
+	node->as.asyncresults[node->as_nasyncresults++] = slot;
 
 	/*
 	 * Mark the subplan that returned a result as ready for a new request.  We
 	 * don't launch another one here immediately because it might complete.
 	 */
-	node->as_needrequest = bms_add_member(node->as_needrequest,
+	node->as.needrequest = bms_add_member(node->as.needrequest,
 										  areq->request_index);
 }
 
@@ -1192,11 +1192,11 @@ classify_matching_subplans(AppendState *node)
 {
 	Bitmapset  *valid_asyncplans;
 
-	Assert(node->as_valid_subplans_identified);
-	Assert(node->as_valid_asyncplans == NULL);
+	Assert(node->as.valid_subplans_identified);
+	Assert(node->as.valid_asyncplans == NULL);
 
 	/* Nothing to do if there are no valid subplans. */
-	if (bms_is_empty(node->as_valid_subplans))
+	if (bms_is_empty(node->as.valid_subplans))
 	{
 		node->as_syncdone = true;
 		node->as_nasyncremain = 0;
@@ -1204,20 +1204,20 @@ classify_matching_subplans(AppendState *node)
 	}
 
 	/* Nothing to do if there are no valid async subplans. */
-	if (!bms_overlap(node->as_valid_subplans, node->as_asyncplans))
+	if (!bms_overlap(node->as.valid_subplans, node->as.asyncplans))
 	{
 		node->as_nasyncremain = 0;
 		return;
 	}
 
 	/* Get valid async subplans. */
-	valid_asyncplans = bms_intersect(node->as_asyncplans,
-									 node->as_valid_subplans);
+	valid_asyncplans = bms_intersect(node->as.asyncplans,
+									 node->as.valid_subplans);
 
 	/* Adjust the valid subplans to contain sync subplans only. */
-	node->as_valid_subplans = bms_del_members(node->as_valid_subplans,
+	node->as.valid_subplans = bms_del_members(node->as.valid_subplans,
 											  valid_asyncplans);
 
 	/* Save valid async subplans. */
-	node->as_valid_asyncplans = valid_asyncplans;
+	node->as.valid_asyncplans = valid_asyncplans;
 }
diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c
index 0173074d85d..e4cdbcc5eb2 100644
--- a/src/backend/executor/nodeMergeAppend.c
+++ b/src/backend/executor/nodeMergeAppend.c
@@ -80,12 +80,12 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
 	/*
 	 * create new MergeAppendState for our node
 	 */
-	mergestate->ps.plan = (Plan *) node;
-	mergestate->ps.state = estate;
-	mergestate->ps.ExecProcNode = ExecMergeAppend;
+	mergestate->as.ps.plan = (Plan *) node;
+	mergestate->as.ps.state = estate;
+	mergestate->as.ps.ExecProcNode = ExecMergeAppend;
 
 	/* If run-time partition pruning is enabled, then set that up now */
-	if (node->part_prune_index >= 0)
+	if (node->ab.part_prune_index >= 0)
 	{
 		PartitionPruneState *prunestate;
 
@@ -94,12 +94,12 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
 		 * subplans to initialize (validsubplans) by taking into account the
 		 * result of performing initial pruning if any.
 		 */
-		prunestate = ExecInitPartitionExecPruning(&mergestate->ps,
-												  list_length(node->mergeplans),
-												  node->part_prune_index,
-												  node->apprelids,
+		prunestate = ExecInitPartitionExecPruning(&mergestate->as.ps,
+												  list_length(node->ab.subplans),
+												  node->ab.part_prune_index,
+												  node->ab.apprelids,
 												  &validsubplans);
-		mergestate->ms_prune_state = prunestate;
+		mergestate->as.prune_state = prunestate;
 		nplans = bms_num_members(validsubplans);
 
 		/*
@@ -108,25 +108,25 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
 		 * later calls to ExecFindMatchingSubPlans.
 		 */
 		if (!prunestate->do_exec_prune && nplans > 0)
-			mergestate->ms_valid_subplans = bms_add_range(NULL, 0, nplans - 1);
+			mergestate->as.valid_subplans = bms_add_range(NULL, 0, nplans - 1);
 	}
 	else
 	{
-		nplans = list_length(node->mergeplans);
+		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);
-		mergestate->ms_valid_subplans = validsubplans =
+		mergestate->as.valid_subplans = validsubplans =
 			bms_add_range(NULL, 0, nplans - 1);
-		mergestate->ms_prune_state = NULL;
+		mergestate->as.prune_state = NULL;
 	}
 
 	mergeplanstates = palloc_array(PlanState *, nplans);
-	mergestate->mergeplans = mergeplanstates;
-	mergestate->ms_nplans = 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,
@@ -140,7 +140,7 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
 	i = -1;
 	while ((i = bms_next_member(validsubplans, i)) >= 0)
 	{
-		Plan	   *initNode = (Plan *) list_nth(node->mergeplans, i);
+		Plan	   *initNode = (Plan *) list_nth(node->ab.subplans, i);
 
 		mergeplanstates[j++] = ExecInitNode(initNode, estate, eflags);
 	}
@@ -157,20 +157,20 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
 	mergeops = ExecGetCommonSlotOps(mergeplanstates, j);
 	if (mergeops != NULL)
 	{
-		ExecInitResultTupleSlotTL(&mergestate->ps, mergeops);
+		ExecInitResultTupleSlotTL(&mergestate->as.ps, mergeops);
 	}
 	else
 	{
-		ExecInitResultTupleSlotTL(&mergestate->ps, &TTSOpsVirtual);
+		ExecInitResultTupleSlotTL(&mergestate->as.ps, &TTSOpsVirtual);
 		/* show that the output slot type is not fixed */
-		mergestate->ps.resultopsset = true;
-		mergestate->ps.resultopsfixed = false;
+		mergestate->as.ps.resultopsset = true;
+		mergestate->as.ps.resultopsfixed = false;
 	}
 
 	/*
 	 * Miscellaneous initialization
 	 */
-	mergestate->ps.ps_ProjInfo = NULL;
+	mergestate->as.ps.ps_ProjInfo = NULL;
 
 	/*
 	 * initialize sort-key information
@@ -225,26 +225,26 @@ ExecMergeAppend(PlanState *pstate)
 	if (!node->ms_initialized)
 	{
 		/* Nothing to do if all subplans were pruned */
-		if (node->ms_nplans == 0)
-			return ExecClearTuple(node->ps.ps_ResultTupleSlot);
+		if (node->as.nplans == 0)
+			return ExecClearTuple(node->as.ps.ps_ResultTupleSlot);
 
 		/*
 		 * If we've yet to determine the valid subplans then do so now.  If
 		 * run-time pruning is disabled then the valid subplans will always be
 		 * set to all subplans.
 		 */
-		if (node->ms_valid_subplans == NULL)
-			node->ms_valid_subplans =
-				ExecFindMatchingSubPlans(node->ms_prune_state, false, NULL);
+		if (node->as.valid_subplans == NULL)
+			node->as.valid_subplans =
+				ExecFindMatchingSubPlans(node->as.prune_state, false, NULL);
 
 		/*
 		 * First time through: pull the first tuple from each valid subplan,
 		 * and set up the heap.
 		 */
 		i = -1;
-		while ((i = bms_next_member(node->ms_valid_subplans, i)) >= 0)
+		while ((i = bms_next_member(node->as.valid_subplans, i)) >= 0)
 		{
-			node->ms_slots[i] = ExecProcNode(node->mergeplans[i]);
+			node->ms_slots[i] = ExecProcNode(node->as.plans[i]);
 			if (!TupIsNull(node->ms_slots[i]))
 				binaryheap_add_unordered(node->ms_heap, Int32GetDatum(i));
 		}
@@ -262,7 +262,7 @@ ExecMergeAppend(PlanState *pstate)
 		 * to not pull tuples until necessary.)
 		 */
 		i = DatumGetInt32(binaryheap_first(node->ms_heap));
-		node->ms_slots[i] = ExecProcNode(node->mergeplans[i]);
+		node->ms_slots[i] = ExecProcNode(node->as.plans[i]);
 		if (!TupIsNull(node->ms_slots[i]))
 			binaryheap_replace_first(node->ms_heap, Int32GetDatum(i));
 		else
@@ -272,7 +272,7 @@ ExecMergeAppend(PlanState *pstate)
 	if (binaryheap_empty(node->ms_heap))
 	{
 		/* All the subplans are exhausted, and so is the heap */
-		result = ExecClearTuple(node->ps.ps_ResultTupleSlot);
+		result = ExecClearTuple(node->as.ps.ps_ResultTupleSlot);
 	}
 	else
 	{
@@ -343,8 +343,8 @@ ExecEndMergeAppend(MergeAppendState *node)
 	/*
 	 * get information from the node
 	 */
-	mergeplans = node->mergeplans;
-	nplans = node->ms_nplans;
+	mergeplans = node->as.plans;
+	nplans = node->as.nplans;
 
 	/*
 	 * shut down each of the subscans
@@ -363,24 +363,24 @@ ExecReScanMergeAppend(MergeAppendState *node)
 	 * we'd better unset the valid subplans so that they are reselected for
 	 * the new parameter values.
 	 */
-	if (node->ms_prune_state &&
-		bms_overlap(node->ps.chgParam,
-					node->ms_prune_state->execparamids))
+	if (node->as.prune_state &&
+		bms_overlap(node->as.ps.chgParam,
+					node->as.prune_state->execparamids))
 	{
-		bms_free(node->ms_valid_subplans);
-		node->ms_valid_subplans = NULL;
+		bms_free(node->as.valid_subplans);
+		node->as.valid_subplans = NULL;
 	}
 
-	for (i = 0; i < node->ms_nplans; i++)
+	for (i = 0; i < node->as.nplans; i++)
 	{
-		PlanState  *subnode = node->mergeplans[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->ps.chgParam != NULL)
-			UpdateChangedParamSet(subnode, node->ps.chgParam);
+		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
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 2a2e00b372e..7f18282dd81 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -4904,14 +4904,9 @@ planstate_tree_walker_impl(PlanState *planstate,
 	switch (nodeTag(plan))
 	{
 		case T_Append:
-			if (planstate_walk_members(((AppendState *) planstate)->appendplans,
-									   ((AppendState *) planstate)->as_nplans,
-									   walker, context))
-				return true;
-			break;
 		case T_MergeAppend:
-			if (planstate_walk_members(((MergeAppendState *) planstate)->mergeplans,
-									   ((MergeAppendState *) planstate)->ms_nplans,
+			if (planstate_walk_members(((AppendBaseState *) planstate)->plans,
+									   ((AppendBaseState *) planstate)->nplans,
 									   walker, context))
 				return true;
 			break;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index eded095d4ea..a41fcb2ebe6 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -1253,12 +1253,12 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 	 * child plans, to make cross-checking the sort info easier.
 	 */
 	plan = makeNode(Append);
-	plan->plan.targetlist = tlist;
-	plan->plan.qual = NIL;
-	plan->plan.lefttree = NULL;
-	plan->plan.righttree = NULL;
-	plan->apprelids = rel->relids;
-	plan->child_append_relid_sets = best_path->child_append_relid_sets;
+	plan->ab.plan.targetlist = tlist;
+	plan->ab.plan.qual = NIL;
+	plan->ab.plan.lefttree = NULL;
+	plan->ab.plan.righttree = NULL;
+	plan->ab.apprelids = rel->relids;
+	plan->ab.child_append_relid_sets = best_path->child_append_relid_sets;
 
 	if (pathkeys != NIL)
 	{
@@ -1277,7 +1277,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 										  &nodeSortOperators,
 										  &nodeCollations,
 										  &nodeNullsFirst);
-		tlist_was_changed = (orig_tlist_length != list_length(plan->plan.targetlist));
+		tlist_was_changed = (orig_tlist_length != list_length(plan->ab.plan.targetlist));
 	}
 
 	/* If appropriate, consider async append */
@@ -1387,7 +1387,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 	}
 
 	/* Set below if we find quals that we can use to run-time prune */
-	plan->part_prune_index = -1;
+	plan->ab.part_prune_index = -1;
 
 	/*
 	 * If any quals exist, they may be useful to perform further partition
@@ -1412,16 +1412,16 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 		}
 
 		if (prunequal != NIL)
-			plan->part_prune_index = make_partition_pruneinfo(root, rel,
-															  best_path->subpaths,
-															  prunequal);
+			plan->ab.part_prune_index = make_partition_pruneinfo(root, rel,
+																 best_path->subpaths,
+																 prunequal);
 	}
 
-	plan->appendplans = subplans;
+	plan->ab.subplans = subplans;
 	plan->nasyncplans = nasyncplans;
 	plan->first_partial_plan = best_path->first_partial_path;
 
-	copy_generic_path_info(&plan->plan, (Path *) best_path);
+	copy_generic_path_info(&plan->ab.plan, (Path *) best_path);
 
 	/*
 	 * If prepare_sort_from_pathkeys added sort columns, but we were told to
@@ -1430,9 +1430,9 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 	 */
 	if (tlist_was_changed && (flags & (CP_EXACT_TLIST | CP_SMALL_TLIST)))
 	{
-		tlist = list_copy_head(plan->plan.targetlist, orig_tlist_length);
+		tlist = list_copy_head(plan->ab.plan.targetlist, orig_tlist_length);
 		return inject_projection_plan((Plan *) plan, tlist,
-									  plan->plan.parallel_safe);
+									  plan->ab.plan.parallel_safe);
 	}
 	else
 		return (Plan *) plan;
@@ -1450,7 +1450,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
 						 int flags)
 {
 	MergeAppend *node = makeNode(MergeAppend);
-	Plan	   *plan = &node->plan;
+	Plan	   *plan = &node->ab.plan;
 	List	   *tlist = build_path_tlist(root, &best_path->path);
 	int			orig_tlist_length = list_length(tlist);
 	bool		tlist_was_changed;
@@ -1470,8 +1470,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
 	plan->qual = NIL;
 	plan->lefttree = NULL;
 	plan->righttree = NULL;
-	node->apprelids = rel->relids;
-	node->child_append_relid_sets = best_path->child_append_relid_sets;
+	node->ab.apprelids = rel->relids;
+	node->ab.child_append_relid_sets = best_path->child_append_relid_sets;
 
 	/*
 	 * Compute sort column info, and adjust MergeAppend's tlist as needed.
@@ -1577,7 +1577,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
 	}
 
 	/* Set below if we find quals that we can use to run-time prune */
-	node->part_prune_index = -1;
+	node->ab.part_prune_index = -1;
 
 	/*
 	 * If any quals exist, they may be useful to perform further partition
@@ -1594,12 +1594,12 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
 		Assert(best_path->path.param_info == NULL);
 
 		if (prunequal != NIL)
-			node->part_prune_index = make_partition_pruneinfo(root, rel,
-															  best_path->subpaths,
-															  prunequal);
+			node->ab.part_prune_index = make_partition_pruneinfo(root, rel,
+																 best_path->subpaths,
+																 prunequal);
 	}
 
-	node->mergeplans = subplans;
+	node->ab.subplans = subplans;
 
 	/*
 	 * If prepare_sort_from_pathkeys added sort columns, but we were told to
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index e1182255055..03dfb4d756f 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -147,12 +147,9 @@ static void set_foreignscan_references(PlannerInfo *root,
 static void set_customscan_references(PlannerInfo *root,
 									  CustomScan *cscan,
 									  int rtoffset);
-static Plan *set_append_references(PlannerInfo *root,
-								   Append *aplan,
-								   int rtoffset);
-static Plan *set_mergeappend_references(PlannerInfo *root,
-										MergeAppend *mplan,
-										int rtoffset);
+static Plan *set_appendbase_references(PlannerInfo *root,
+									   AppendBase *plan,
+									   int rtoffset);
 static void set_hash_references(PlannerInfo *root, Plan *plan, int rtoffset);
 static Relids offset_relid_set(Relids relids, int rtoffset);
 static Node *fix_scan_expr(PlannerInfo *root, Node *node,
@@ -1298,15 +1295,11 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 		case T_Append:
-			/* Needs special treatment, see comments below */
-			return set_append_references(root,
-										 (Append *) plan,
-										 rtoffset);
 		case T_MergeAppend:
 			/* Needs special treatment, see comments below */
-			return set_mergeappend_references(root,
-											  (MergeAppend *) plan,
-											  rtoffset);
+			return set_appendbase_references(root,
+											 (AppendBase *) plan,
+											 rtoffset);
 		case T_RecursiveUnion:
 			/* This doesn't evaluate targetlist or check quals either */
 			set_dummy_tlist_references(plan, rtoffset);
@@ -1783,7 +1776,7 @@ set_customscan_references(PlannerInfo *root,
 
 /*
  * register_partpruneinfo
- *		Subroutine for set_append_references and set_mergeappend_references
+ *		Subroutine for set_appendbase_references
  *
  * Add the PartitionPruneInfo from root->partPruneInfos at the given index
  * into PlannerGlobal->partPruneInfos and return its index there.
@@ -1850,158 +1843,84 @@ register_partpruneinfo(PlannerInfo *root, int part_prune_index, int rtoffset)
 }
 
 /*
- * set_append_references
- *		Do set_plan_references processing on an Append
- *
- * We try to strip out the Append entirely; if we can't, we have
- * to do the normal processing on it.
+ * set_appendbase_references
+ *		Do set_plan_references processing on an Append or MergeAppend
+ *
+ * We try to strip out the node entirely; if we can't, we have to do the
+ * normal processing on it.  Append and MergeAppend behave identically here
+ * (both are represented via the common AppendBase struct), so this is
+ * shared between them; the only thing that differs is which NodeTag to
+ * report as elided, and nodeTag(plan) already gives us that.
  */
 static Plan *
-set_append_references(PlannerInfo *root,
-					  Append *aplan,
-					  int rtoffset)
+set_appendbase_references(PlannerInfo *root,
+						  AppendBase *plan,
+						  int rtoffset)
 {
 	ListCell   *l;
 
 	/*
-	 * Append, like Sort et al, doesn't actually evaluate its targetlist or
-	 * check quals.  If it's got exactly one child plan, then it's not doing
-	 * anything useful at all, and we can strip it out.
+	 * Append/MergeAppend, like Sort et al, doesn't actually evaluate its
+	 * targetlist or check quals.  If it's got exactly one child plan, then
+	 * it's not doing anything useful at all, and we can strip it out.
 	 */
-	Assert(aplan->plan.qual == NIL);
+	Assert(plan->plan.qual == NIL);
 
 	/* First, we gotta recurse on the children */
-	foreach(l, aplan->appendplans)
+	foreach(l, plan->subplans)
 	{
 		lfirst(l) = set_plan_refs(root, (Plan *) lfirst(l), rtoffset);
 	}
 
 	/*
-	 * See if it's safe to get rid of the Append entirely.  For this to be
-	 * safe, there must be only one child plan and that child plan's parallel
-	 * awareness must match the Append's.  The reason for the latter is that
-	 * if the Append is parallel aware and the child is not, then the calling
-	 * plan may execute the non-parallel aware child multiple times.  (If you
-	 * change these rules, update create_append_path to match.)
+	 * See if it's safe to get rid of the node entirely.  For this to be
+	 * safe, there must be only one child plan and that child plan's
+	 * parallel awareness must match the node's.  The reason for the latter
+	 * is that if the node is parallel aware and the child is not, then the
+	 * calling plan may execute the non-parallel aware child multiple times.
+	 * (If you change these rules, update create_append_path and
+	 * create_merge_append_path to match.)
 	 */
-	if (list_length(aplan->appendplans) == 1)
+	if (list_length(plan->subplans) == 1)
 	{
-		Plan	   *p = (Plan *) linitial(aplan->appendplans);
+		Plan	   *p = (Plan *) linitial(plan->subplans);
 
-		if (p->parallel_aware == aplan->plan.parallel_aware)
+		if (p->parallel_aware == plan->plan.parallel_aware)
 		{
 			Plan	   *result;
 
-			result = clean_up_removed_plan_level((Plan *) aplan, p);
+			result = clean_up_removed_plan_level((Plan *) plan, p);
 
-			/* Remember that we removed an Append */
-			record_elided_node(root->glob, p->plan_node_id, T_Append,
-							   offset_relid_set(aplan->apprelids, rtoffset));
+			/* Remember that we removed an Append/MergeAppend */
+			record_elided_node(root->glob, p->plan_node_id, nodeTag(plan),
+							   offset_relid_set(plan->apprelids, rtoffset));
 
 			return result;
 		}
 	}
 
 	/*
-	 * Otherwise, clean up the Append as needed.  It's okay to do this after
+	 * Otherwise, clean up the node as needed.  It's okay to do this after
 	 * recursing to the children, because set_dummy_tlist_references doesn't
 	 * look at those.
 	 */
-	set_dummy_tlist_references((Plan *) aplan, rtoffset);
-
-	aplan->apprelids = offset_relid_set(aplan->apprelids, rtoffset);
-
-	/*
-	 * Add PartitionPruneInfo, if any, to PlannerGlobal and update the index.
-	 * Also update the RT indexes present in it to add the offset.
-	 */
-	if (aplan->part_prune_index >= 0)
-		aplan->part_prune_index =
-			register_partpruneinfo(root, aplan->part_prune_index, rtoffset);
-
-	/* We don't need to recurse to lefttree or righttree ... */
-	Assert(aplan->plan.lefttree == NULL);
-	Assert(aplan->plan.righttree == NULL);
-
-	return (Plan *) aplan;
-}
-
-/*
- * set_mergeappend_references
- *		Do set_plan_references processing on a MergeAppend
- *
- * We try to strip out the MergeAppend entirely; if we can't, we have
- * to do the normal processing on it.
- */
-static Plan *
-set_mergeappend_references(PlannerInfo *root,
-						   MergeAppend *mplan,
-						   int rtoffset)
-{
-	ListCell   *l;
-
-	/*
-	 * MergeAppend, like Sort et al, doesn't actually evaluate its targetlist
-	 * or check quals.  If it's got exactly one child plan, then it's not
-	 * doing anything useful at all, and we can strip it out.
-	 */
-	Assert(mplan->plan.qual == NIL);
-
-	/* First, we gotta recurse on the children */
-	foreach(l, mplan->mergeplans)
-	{
-		lfirst(l) = set_plan_refs(root, (Plan *) lfirst(l), rtoffset);
-	}
-
-	/*
-	 * See if it's safe to get rid of the MergeAppend entirely.  For this to
-	 * be safe, there must be only one child plan and that child plan's
-	 * parallel awareness must match the MergeAppend's.  The reason for the
-	 * latter is that if the MergeAppend is parallel aware and the child is
-	 * not, then the calling plan may execute the non-parallel aware child
-	 * multiple times.  (If you change these rules, update
-	 * create_merge_append_path to match.)
-	 */
-	if (list_length(mplan->mergeplans) == 1)
-	{
-		Plan	   *p = (Plan *) linitial(mplan->mergeplans);
-
-		if (p->parallel_aware == mplan->plan.parallel_aware)
-		{
-			Plan	   *result;
-
-			result = clean_up_removed_plan_level((Plan *) mplan, p);
-
-			/* Remember that we removed a MergeAppend */
-			record_elided_node(root->glob, p->plan_node_id, T_MergeAppend,
-							   offset_relid_set(mplan->apprelids, rtoffset));
-
-			return result;
-		}
-	}
-
-	/*
-	 * Otherwise, clean up the MergeAppend as needed.  It's okay to do this
-	 * after recursing to the children, because set_dummy_tlist_references
-	 * doesn't look at those.
-	 */
-	set_dummy_tlist_references((Plan *) mplan, rtoffset);
+	set_dummy_tlist_references((Plan *) plan, rtoffset);
 
-	mplan->apprelids = offset_relid_set(mplan->apprelids, rtoffset);
+	plan->apprelids = offset_relid_set(plan->apprelids, rtoffset);
 
 	/*
 	 * Add PartitionPruneInfo, if any, to PlannerGlobal and update the index.
 	 * Also update the RT indexes present in it to add the offset.
 	 */
-	if (mplan->part_prune_index >= 0)
-		mplan->part_prune_index =
-			register_partpruneinfo(root, mplan->part_prune_index, rtoffset);
+	if (plan->part_prune_index >= 0)
+		plan->part_prune_index =
+			register_partpruneinfo(root, plan->part_prune_index, rtoffset);
 
 	/* We don't need to recurse to lefttree or righttree ... */
-	Assert(mplan->plan.lefttree == NULL);
-	Assert(mplan->plan.righttree == NULL);
+	Assert(plan->plan.lefttree == NULL);
+	Assert(plan->plan.righttree == NULL);
 
-	return (Plan *) mplan;
+	return (Plan *) plan;
 }
 
 /*
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 6aa8971c95d..e6758724744 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2906,7 +2906,7 @@ finalize_plan(PlannerInfo *root, Plan *plan,
 
 		case T_Append:
 			{
-				foreach(l, ((Append *) plan)->appendplans)
+				foreach(l, ((Append *) plan)->ab.subplans)
 				{
 					context.paramids =
 						bms_add_members(context.paramids,
@@ -2921,7 +2921,7 @@ finalize_plan(PlannerInfo *root, Plan *plan,
 
 		case T_MergeAppend:
 			{
-				foreach(l, ((MergeAppend *) plan)->mergeplans)
+				foreach(l, ((MergeAppend *) plan)->ab.subplans)
 				{
 					context.paramids =
 						bms_add_members(context.paramids,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 908134594cb..2f2fa9bd758 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5525,9 +5525,9 @@ set_deparse_plan(deparse_namespace *dpns, Plan *plan)
 	 * natural choice.
 	 */
 	if (IsA(plan, Append))
-		dpns->outer_plan = linitial(((Append *) plan)->appendplans);
+		dpns->outer_plan = linitial(((Append *) plan)->ab.subplans);
 	else if (IsA(plan, MergeAppend))
-		dpns->outer_plan = linitial(((MergeAppend *) plan)->mergeplans);
+		dpns->outer_plan = linitial(((MergeAppend *) plan)->ab.subplans);
 	else
 		dpns->outer_plan = outerPlan(plan);
 
@@ -8507,10 +8507,10 @@ resolve_special_varno(Node *node, deparse_context *context,
 
 		if (IsA(dpns->plan, Append))
 			context->appendparents = bms_union(context->appendparents,
-											   ((Append *) dpns->plan)->apprelids);
+											   ((Append *) dpns->plan)->ab.apprelids);
 		else if (IsA(dpns->plan, MergeAppend))
 			context->appendparents = bms_union(context->appendparents,
-											   ((MergeAppend *) dpns->plan)->apprelids);
+											   ((MergeAppend *) dpns->plan)->ab.apprelids);
 
 		push_child_plan(dpns, dpns->outer_plan, &save_dpns);
 		resolve_special_varno((Node *) tle->expr, context,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e95ac3eda35..3d26fcdccc8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1512,6 +1512,39 @@ typedef struct ModifyTableState
 	List	   *mt_fdwPrivLists;
 } ModifyTableState;
 
+/* ----------------
+ *	 AppendBaseState information
+ *
+ *		Common base for AppendState and MergeAppendState.
+ *		Contains fields shared by both node types: the array of subplan
+ *		states, asynchronous execution infrastructure, and partition
+ *		pruning state.
+ * ----------------
+ */
+typedef struct AppendBaseState
+{
+	pg_node_attr(abstract)
+
+	PlanState	ps;				/* its first field is NodeTag */
+	PlanState **plans;			/* array of PlanStates for my inputs */
+	int			nplans;
+
+	/* Asynchronous execution state */
+	Bitmapset  *asyncplans;		/* asynchronous plans indexes */
+	int			nasyncplans;	/* # of asynchronous plans */
+	AsyncRequest **asyncrequests;	/* array of AsyncRequests */
+	TupleTableSlot **asyncresults;	/* unreturned results of async plans */
+	Bitmapset  *needrequest;	/* asynchronous plans needing a new request */
+	struct WaitEventSet *eventset;	/* WaitEventSet used to configure file
+									 * descriptor wait events */
+
+	/* Partition pruning state */
+	struct PartitionPruneState *prune_state;
+	bool		valid_subplans_identified;	/* is valid_subplans valid? */
+	Bitmapset  *valid_subplans;
+	Bitmapset  *valid_asyncplans;	/* valid asynchronous plans indexes */
+} AppendBaseState;
+
 /* ----------------
  *	 AppendState information
  *
@@ -1533,30 +1566,21 @@ struct PartitionPruneState;
 
 struct AppendState
 {
-	PlanState	ps;				/* its first field is NodeTag */
-	PlanState **appendplans;	/* array of PlanStates for my inputs */
-	int			as_nplans;
+	AppendBaseState as;			/* its first field is NodeTag */
+
 	int			as_whichplan;
 	bool		as_begun;		/* false means need to initialize */
-	Bitmapset  *as_asyncplans;	/* asynchronous plans indexes */
-	int			as_nasyncplans; /* # of asynchronous plans */
-	AsyncRequest **as_asyncrequests;	/* array of AsyncRequests */
-	TupleTableSlot **as_asyncresults;	/* unreturned results of async plans */
-	int			as_nasyncresults;	/* # of valid entries in as_asyncresults */
+	int			as_nasyncresults;	/* # of valid entries in asyncresults */
 	bool		as_syncdone;	/* true if all synchronous plans done in
 								 * asynchronous mode, else false */
 	int			as_nasyncremain;	/* # of remaining asynchronous plans */
-	Bitmapset  *as_needrequest; /* asynchronous plans needing a new request */
-	struct WaitEventSet *as_eventset;	/* WaitEventSet used to configure file
-										 * descriptor wait events */
-	int			as_first_partial_plan;	/* Index of 'appendplans' containing
-										 * the first partial plan */
+	int			as_first_partial_plan;	/* Index of 'as.plans' containing the
+										 * first partial plan */
+
+	/* Parallel append specific */
 	ParallelAppendState *as_pstate; /* parallel coordination info */
 	Size		pstate_len;		/* size of parallel coordination info */
-	struct PartitionPruneState *as_prune_state;
-	bool		as_valid_subplans_identified;	/* is as_valid_subplans valid? */
-	Bitmapset  *as_valid_subplans;
-	Bitmapset  *as_valid_asyncplans;	/* valid asynchronous plans indexes */
+
 	bool		(*choose_next_subplan) (AppendState *);
 };
 
@@ -1577,16 +1601,13 @@ struct AppendState
  */
 typedef struct MergeAppendState
 {
-	PlanState	ps;				/* its first field is NodeTag */
-	PlanState **mergeplans;		/* array of PlanStates for my inputs */
-	int			ms_nplans;
+	AppendBaseState as;			/* its first field is NodeTag */
+
 	int			ms_nkeys;
 	SortSupport ms_sortkeys;	/* array of length ms_nkeys */
 	TupleTableSlot **ms_slots;	/* array of length ms_nplans */
 	struct binaryheap *ms_heap; /* binary heap of slot indices */
 	bool		ms_initialized; /* are subplans started? */
-	struct PartitionPruneState *ms_prune_state;
-	Bitmapset  *ms_valid_subplans;
 } MergeAppendState;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index b880ce0f4be..12c777bf1c1 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -391,38 +391,48 @@ typedef struct ModifyTable
 struct PartitionPruneInfo;		/* forward reference to struct below */
 
 /* ----------------
- *	 Append node -
- *		Generate the concatenation of the results of sub-plans.
+ *	 AppendBase node -
+ *		Common base for Append and MergeAppend plan nodes.
+ *		Contains fields shared by both node types: the list of subplans,
+ *		appendrel identifiers, and run-time partition pruning info.
  * ----------------
  */
-typedef struct Append
+typedef struct AppendBase
 {
-	Plan		plan;
+	pg_node_attr(abstract)
 
-	/* RTIs of appendrel(s) formed by this node */
-	Bitmapset  *apprelids;
+	Plan		plan;			/* its first field is NodeTag */
+	Bitmapset  *apprelids;		/* RTIs of appendrel(s) formed by this node */
+	List	   *child_append_relid_sets;	/* sets of RTIs of appendrels
+											 * consolidated into this node */
+	List	   *subplans;		/* List of Plans (formerly
+								 * appendplans/mergeplans) */
 
-	/* sets of RTIs of appendrels consolidated into this node */
-	List	   *child_append_relid_sets;
+	/*
+	 * Index into PlannedStmt.partPruneInfos and parallel lists in EState:
+	 * es_part_prune_states and es_part_prune_results. Set to -1 if no
+	 * run-time pruning is used.
+	 */
+	int			part_prune_index;
+} AppendBase;
 
-	/* plans to run */
-	List	   *appendplans;
+/* ----------------
+ *	 Append node -
+ *		Generate the concatenation of the results of sub-plans.
+ * ----------------
+ */
+typedef struct Append
+{
+	AppendBase	ab;				/* its first field is NodeTag */
 
 	/* # of asynchronous plans */
 	int			nasyncplans;
 
 	/*
-	 * All 'appendplans' preceding this index are non-partial plans. All
-	 * 'appendplans' from this index onwards are partial plans.
+	 * All 'subplans' preceding this index are non-partial plans. All
+	 * 'subplans' from this index onwards are partial plans.
 	 */
 	int			first_partial_plan;
-
-	/*
-	 * Index into PlannedStmt.partPruneInfos and parallel lists in EState:
-	 * es_part_prune_states and es_part_prune_results. Set to -1 if no
-	 * run-time pruning is used.
-	 */
-	int			part_prune_index;
 } Append;
 
 /* ----------------
@@ -432,16 +442,7 @@ typedef struct Append
  */
 typedef struct MergeAppend
 {
-	Plan		plan;
-
-	/* RTIs of appendrel(s) formed by this node */
-	Bitmapset  *apprelids;
-
-	/* sets of RTIs of appendrels consolidated into this node */
-	List	   *child_append_relid_sets;
-
-	/* plans to run */
-	List	   *mergeplans;
+	AppendBase	ab;				/* its first field is NodeTag */
 
 	/* these fields are just like the sort-key info in struct Sort: */
 
@@ -459,13 +460,6 @@ typedef struct MergeAppend
 
 	/* NULLS FIRST/LAST directions */
 	bool	   *nullsFirst pg_node_attr(array_size(numCols));
-
-	/*
-	 * Index into PlannedStmt.partPruneInfos and parallel lists in EState:
-	 * es_part_prune_states and es_part_prune_results. Set to -1 if no
-	 * run-time pruning is used.
-	 */
-	int			part_prune_index;
 } MergeAppend;
 
 /* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 85d989f395d..1edc835157f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -128,6 +128,8 @@ AnlExprData
 AnlIndexData
 AnyArrayType
 Append
+AppendBase
+AppendBaseState
 AppendPath
 AppendPathInput
 AppendRelInfo
-- 
2.50.1 (Apple Git-155)

