v9-0004-Store-information-about-Append-node-consolidation.patch

application/octet-stream

Filename: v9-0004-Store-information-about-Append-node-consolidation.patch
Type: application/octet-stream
Part: 1
Message: Re: plan shape work

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 v9-0004
Subject: Store information about Append node consolidation in the final plan.
File+
contrib/pg_overexplain/expected/pg_overexplain.out 3 1
contrib/pg_overexplain/pg_overexplain.c 56 0
src/backend/optimizer/path/allpaths.c 77 21
src/backend/optimizer/path/joinrels.c 1 1
src/backend/optimizer/plan/createplan.c 2 0
src/backend/optimizer/plan/planner.c 1 0
src/backend/optimizer/prep/prepunion.c 3 2
src/backend/optimizer/util/pathnode.c 5 0
src/include/nodes/pathnodes.h 10 0
src/include/nodes/plannodes.h 11 0
src/include/optimizer/pathnode.h 2 0
From 419920680cf71cdf0834fcee804c7a6941085feb Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 21 Jul 2025 15:49:06 -0400
Subject: [PATCH v9 4/4] Store information about Append node consolidation in
 the final plan.

When we build an AppendPath or MergeAppendPath, we sometimes pull all
child paths from a subordinate AppendPath or MergeAppendPath instead
of having one such path atop another. This results in the RTIs that
would have been associated with the subordinate path disappearing
from the final plan, making things difficult for code that wants
to scrutinize the final plan and extract information from it about
what happened during the planning process.

To avoid this, propagate the RTI sets that would have been present
in the 'apprelids' field of the subordinate Append or MergeAppend
nodes that would have been created into the surviving Append or
MergeAppend node, using a new 'child_append_relid_sets' field for
that purpose.

This commit also updates pg_overexplain to display these details.
---
 .../expected/pg_overexplain.out               |  4 +-
 contrib/pg_overexplain/pg_overexplain.c       | 56 +++++++++++
 src/backend/optimizer/path/allpaths.c         | 98 +++++++++++++++----
 src/backend/optimizer/path/joinrels.c         |  2 +-
 src/backend/optimizer/plan/createplan.c       |  2 +
 src/backend/optimizer/plan/planner.c          |  1 +
 src/backend/optimizer/prep/prepunion.c        |  5 +-
 src/backend/optimizer/util/pathnode.c         |  5 +
 src/include/nodes/pathnodes.h                 | 10 ++
 src/include/nodes/plannodes.h                 | 11 +++
 src/include/optimizer/pathnode.h              |  2 +
 11 files changed, 171 insertions(+), 25 deletions(-)

diff --git a/contrib/pg_overexplain/expected/pg_overexplain.out b/contrib/pg_overexplain/expected/pg_overexplain.out
index ca9a23ea61f..a377fb2571d 100644
--- a/contrib/pg_overexplain/expected/pg_overexplain.out
+++ b/contrib/pg_overexplain/expected/pg_overexplain.out
@@ -104,6 +104,7 @@ $$);
                Parallel Safe: true
                Plan Node ID: 2
                Append RTIs: 1
+               Child Append RTIs: none
                ->  Seq Scan on brassica vegetables_1
                      Disabled Nodes: 0
                      Parallel Safe: true
@@ -142,7 +143,7 @@ $$);
    Relation Kind: relation
    Relation Lock Mode: AccessShareLock
  Unprunable RTIs: 1 3 4
-(53 rows)
+(54 rows)
 
 -- Test a different output format.
 SELECT explain_filter($$
@@ -197,6 +198,7 @@ $$);
                <extParam>none</extParam>                            +
                <allParam>none</allParam>                            +
                <Append-RTIs>1</Append-RTIs>                         +
+               <Child-Append-RTIs>none</Child-Append-RTIs>          +
                <Subplans-Removed>0</Subplans-Removed>               +
                <Plans>                                              +
                  <Plan>                                             +
diff --git a/contrib/pg_overexplain/pg_overexplain.c b/contrib/pg_overexplain/pg_overexplain.c
index fa907fa472e..6538ffcafb0 100644
--- a/contrib/pg_overexplain/pg_overexplain.c
+++ b/contrib/pg_overexplain/pg_overexplain.c
@@ -54,6 +54,8 @@ static void overexplain_alias(const char *qlabel, Alias *alias,
 							  ExplainState *es);
 static void overexplain_bitmapset(const char *qlabel, Bitmapset *bms,
 								  ExplainState *es);
+static void overexplain_bitmapset_list(const char *qlabel, List *bms_list,
+									   ExplainState *es);
 static void overexplain_intlist(const char *qlabel, List *list,
 								ExplainState *es);
 
@@ -232,11 +234,17 @@ overexplain_per_node_hook(PlanState *planstate, List *ancestors,
 				overexplain_bitmapset("Append RTIs",
 									  ((Append *) plan)->apprelids,
 									  es);
+				overexplain_bitmapset_list("Child Append RTIs",
+										   ((Append *) plan)->child_append_relid_sets,
+										   es);
 				break;
 			case T_MergeAppend:
 				overexplain_bitmapset("Append RTIs",
 									  ((MergeAppend *) plan)->apprelids,
 									  es);
+				overexplain_bitmapset_list("Child Append RTIs",
+										   ((MergeAppend *) plan)->child_append_relid_sets,
+										   es);
 				break;
 			case T_Result:
 
@@ -815,6 +823,54 @@ overexplain_bitmapset(const char *qlabel, Bitmapset *bms, ExplainState *es)
 	pfree(buf.data);
 }
 
+/*
+ * Emit a text property describing the contents of a list of bitmapsets.
+ * If a bitmapset contains exactly 1 member, we just print an integer;
+ * otherwise, we surround the list of members by parentheses.
+ *
+ * If there are no bitmapsets in the list, we print the word "none".
+ */
+static void
+overexplain_bitmapset_list(const char *qlabel, List *bms_list,
+						   ExplainState *es)
+{
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+
+	foreach_node(Bitmapset, bms, bms_list)
+	{
+		if (bms_membership(bms) == BMS_SINGLETON)
+			appendStringInfo(&buf, " %d", bms_singleton_member(bms));
+		else
+		{
+			int			x = -1;
+			bool		first = true;
+
+			appendStringInfoString(&buf, " (");
+			while ((x = bms_next_member(bms, x)) >= 0)
+			{
+				if (first)
+					first = false;
+				else
+					appendStringInfoChar(&buf, ' ');
+				appendStringInfo(&buf, "%d", x);
+			}
+			appendStringInfoChar(&buf, ')');
+		}
+	}
+
+	if (buf.len == 0)
+	{
+		ExplainPropertyText(qlabel, "none", es);
+		return;
+	}
+
+	Assert(buf.data[0] == ' ');
+	ExplainPropertyText(qlabel, buf.data + 1, es);
+	pfree(buf.data);
+}
+
 /*
  * Emit a text property describing the contents of a list of integers, OIDs,
  * or XIDs -- either a space-separated list of integer members, or the word
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index ffc21a6afeb..bf29e348ee6 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -122,8 +122,10 @@ static Path *get_cheapest_parameterized_child_path(PlannerInfo *root,
 												   Relids required_outer);
 static void accumulate_append_subpath(Path *path,
 									  List **subpaths,
-									  List **special_subpaths);
-static Path *get_singleton_append_subpath(Path *path);
+									  List **special_subpaths,
+									  List **child_append_relid_sets);
+static Path *get_singleton_append_subpath(Path *path,
+										  List **child_append_relid_sets);
 static void set_dummy_rel_pathlist(RelOptInfo *rel);
 static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 								  Index rti, RangeTblEntry *rte);
@@ -1323,11 +1325,15 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 {
 	List	   *subpaths = NIL;
 	bool		subpaths_valid = true;
+	List	   *subpath_cars = NIL;
 	List	   *startup_subpaths = NIL;
 	bool		startup_subpaths_valid = true;
+	List	   *startup_subpath_cars = NIL;
 	List	   *partial_subpaths = NIL;
+	List	   *partial_subpath_cars = NIL;
 	List	   *pa_partial_subpaths = NIL;
 	List	   *pa_nonpartial_subpaths = NIL;
+	List	   *pa_subpath_cars = NIL;
 	bool		partial_subpaths_valid = true;
 	bool		pa_subpaths_valid;
 	List	   *all_child_pathkeys = NIL;
@@ -1360,7 +1366,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 		if (childrel->pathlist != NIL &&
 			childrel->cheapest_total_path->param_info == NULL)
 			accumulate_append_subpath(childrel->cheapest_total_path,
-									  &subpaths, NULL);
+									  &subpaths, NULL, &subpath_cars);
 		else
 			subpaths_valid = false;
 
@@ -1389,7 +1395,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 			Assert(cheapest_path->param_info == NULL);
 			accumulate_append_subpath(cheapest_path,
 									  &startup_subpaths,
-									  NULL);
+									  NULL,
+									  &startup_subpath_cars);
 		}
 		else
 			startup_subpaths_valid = false;
@@ -1400,7 +1407,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 		{
 			cheapest_partial_path = linitial(childrel->partial_pathlist);
 			accumulate_append_subpath(cheapest_partial_path,
-									  &partial_subpaths, NULL);
+									  &partial_subpaths, NULL,
+									  &partial_subpath_cars);
 		}
 		else
 			partial_subpaths_valid = false;
@@ -1429,7 +1437,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 				Assert(cheapest_partial_path != NULL);
 				accumulate_append_subpath(cheapest_partial_path,
 										  &pa_partial_subpaths,
-										  &pa_nonpartial_subpaths);
+										  &pa_nonpartial_subpaths,
+										  &pa_subpath_cars);
 			}
 			else
 			{
@@ -1448,7 +1457,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 				 */
 				accumulate_append_subpath(nppath,
 										  &pa_nonpartial_subpaths,
-										  NULL);
+										  NULL,
+										  &pa_subpath_cars);
 			}
 		}
 
@@ -1523,14 +1533,16 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 	 * if we have zero or one live subpath due to constraint exclusion.)
 	 */
 	if (subpaths_valid)
-		add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL,
+		add_path(rel, (Path *) create_append_path(root, rel, subpaths,
+												  NIL, subpath_cars,
 												  NIL, NULL, 0, false,
 												  -1));
 
 	/* build an AppendPath for the cheap startup paths, if valid */
 	if (startup_subpaths_valid)
 		add_path(rel, (Path *) create_append_path(root, rel, startup_subpaths,
-												  NIL, NIL, NULL, 0, false, -1));
+												  NIL, startup_subpath_cars,
+												  NIL, NULL, 0, false, -1));
 
 	/*
 	 * Consider an append of unordered, unparameterized partial paths.  Make
@@ -1571,6 +1583,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 
 		/* Generate a partial append path. */
 		appendpath = create_append_path(root, rel, NIL, partial_subpaths,
+										partial_subpath_cars,
 										NIL, NULL, parallel_workers,
 										enable_parallel_append,
 										-1);
@@ -1621,6 +1634,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 
 		appendpath = create_append_path(root, rel, pa_nonpartial_subpaths,
 										pa_partial_subpaths,
+										pa_subpath_cars,
 										NIL, NULL, parallel_workers, true,
 										partial_rows);
 		add_partial_path(rel, (Path *) appendpath);
@@ -1654,6 +1668,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 
 		/* Select the child paths for an Append with this parameterization */
 		subpaths = NIL;
+		subpath_cars = NIL;
 		subpaths_valid = true;
 		foreach(lcr, live_childrels)
 		{
@@ -1676,12 +1691,13 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 				subpaths_valid = false;
 				break;
 			}
-			accumulate_append_subpath(subpath, &subpaths, NULL);
+			accumulate_append_subpath(subpath, &subpaths, NULL,
+									  &subpath_cars);
 		}
 
 		if (subpaths_valid)
 			add_path(rel, (Path *)
-					 create_append_path(root, rel, subpaths, NIL,
+					 create_append_path(root, rel, subpaths, NIL, subpath_cars,
 										NIL, required_outer, 0, false,
 										-1));
 	}
@@ -1708,6 +1724,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 				continue;
 
 			appendpath = create_append_path(root, rel, NIL, list_make1(path),
+											list_make1(rel->relids),
 											NIL, NULL,
 											path->parallel_workers, true,
 											partial_rows);
@@ -1789,8 +1806,11 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 	{
 		List	   *pathkeys = (List *) lfirst(lcp);
 		List	   *startup_subpaths = NIL;
+		List	   *startup_subpath_cars = NIL;
 		List	   *total_subpaths = NIL;
+		List	   *total_subpath_cars = NIL;
 		List	   *fractional_subpaths = NIL;
+		List	   *fractional_subpath_cars = NIL;
 		bool		startup_neq_total = false;
 		bool		match_partition_order;
 		bool		match_partition_order_desc;
@@ -1942,16 +1962,23 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 				 * just a single subpath (and hence aren't doing anything
 				 * useful).
 				 */
-				cheapest_startup = get_singleton_append_subpath(cheapest_startup);
-				cheapest_total = get_singleton_append_subpath(cheapest_total);
+				cheapest_startup =
+					get_singleton_append_subpath(cheapest_startup,
+												 &startup_subpath_cars);
+				cheapest_total =
+					get_singleton_append_subpath(cheapest_total,
+												 &total_subpath_cars);
 
 				startup_subpaths = lappend(startup_subpaths, cheapest_startup);
 				total_subpaths = lappend(total_subpaths, cheapest_total);
 
 				if (cheapest_fractional)
 				{
-					cheapest_fractional = get_singleton_append_subpath(cheapest_fractional);
-					fractional_subpaths = lappend(fractional_subpaths, cheapest_fractional);
+					cheapest_fractional =
+						get_singleton_append_subpath(cheapest_fractional,
+													 &fractional_subpath_cars);
+					fractional_subpaths =
+						lappend(fractional_subpaths, cheapest_fractional);
 				}
 			}
 			else
@@ -1961,13 +1988,16 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 				 * child paths for the MergeAppend.
 				 */
 				accumulate_append_subpath(cheapest_startup,
-										  &startup_subpaths, NULL);
+										  &startup_subpaths, NULL,
+										  &startup_subpath_cars);
 				accumulate_append_subpath(cheapest_total,
-										  &total_subpaths, NULL);
+										  &total_subpaths, NULL,
+										  &total_subpath_cars);
 
 				if (cheapest_fractional)
 					accumulate_append_subpath(cheapest_fractional,
-											  &fractional_subpaths, NULL);
+											  &fractional_subpaths, NULL,
+											  &fractional_subpath_cars);
 			}
 		}
 
@@ -1979,6 +2009,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 													  rel,
 													  startup_subpaths,
 													  NIL,
+													  startup_subpath_cars,
 													  pathkeys,
 													  NULL,
 													  0,
@@ -1989,6 +2020,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 														  rel,
 														  total_subpaths,
 														  NIL,
+														  total_subpath_cars,
 														  pathkeys,
 														  NULL,
 														  0,
@@ -2000,6 +2032,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 														  rel,
 														  fractional_subpaths,
 														  NIL,
+														  fractional_subpath_cars,
 														  pathkeys,
 														  NULL,
 														  0,
@@ -2012,12 +2045,14 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 			add_path(rel, (Path *) create_merge_append_path(root,
 															rel,
 															startup_subpaths,
+															startup_subpath_cars,
 															pathkeys,
 															NULL));
 			if (startup_neq_total)
 				add_path(rel, (Path *) create_merge_append_path(root,
 																rel,
 																total_subpaths,
+																total_subpath_cars,
 																pathkeys,
 																NULL));
 
@@ -2025,6 +2060,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 				add_path(rel, (Path *) create_merge_append_path(root,
 																rel,
 																fractional_subpaths,
+																fractional_subpath_cars,
 																pathkeys,
 																NULL));
 		}
@@ -2127,7 +2163,8 @@ get_cheapest_parameterized_child_path(PlannerInfo *root, RelOptInfo *rel,
  * paths).
  */
 static void
-accumulate_append_subpath(Path *path, List **subpaths, List **special_subpaths)
+accumulate_append_subpath(Path *path, List **subpaths, List **special_subpaths,
+						  List **child_append_relid_sets)
 {
 	if (IsA(path, AppendPath))
 	{
@@ -2136,6 +2173,8 @@ accumulate_append_subpath(Path *path, List **subpaths, List **special_subpaths)
 		if (!apath->path.parallel_aware || apath->first_partial_path == 0)
 		{
 			*subpaths = list_concat(*subpaths, apath->subpaths);
+			*child_append_relid_sets =
+				lappend(*child_append_relid_sets, path->parent->relids);
 			return;
 		}
 		else if (special_subpaths != NULL)
@@ -2150,6 +2189,8 @@ accumulate_append_subpath(Path *path, List **subpaths, List **special_subpaths)
 												  apath->first_partial_path);
 			*special_subpaths = list_concat(*special_subpaths,
 											new_special_subpaths);
+			*child_append_relid_sets =
+				lappend(*child_append_relid_sets, path->parent->relids);
 			return;
 		}
 	}
@@ -2158,6 +2199,8 @@ accumulate_append_subpath(Path *path, List **subpaths, List **special_subpaths)
 		MergeAppendPath *mpath = (MergeAppendPath *) path;
 
 		*subpaths = list_concat(*subpaths, mpath->subpaths);
+		*child_append_relid_sets =
+			lappend(*child_append_relid_sets, path->parent->relids);
 		return;
 	}
 
@@ -2169,10 +2212,15 @@ accumulate_append_subpath(Path *path, List **subpaths, List **special_subpaths)
  *		Returns the single subpath of an Append/MergeAppend, or just
  *		return 'path' if it's not a single sub-path Append/MergeAppend.
  *
+ * As a side effect, whenever we return a single subpath rather than the
+ * original path, add the relid set for the original path to
+ * child_append_relid_sets, so that those relids don't entirely disappear
+ * from the final plan.
+ *
  * Note: 'path' must not be a parallel-aware path.
  */
 static Path *
-get_singleton_append_subpath(Path *path)
+get_singleton_append_subpath(Path *path, List **child_append_relid_sets)
 {
 	Assert(!path->parallel_aware);
 
@@ -2181,14 +2229,22 @@ get_singleton_append_subpath(Path *path)
 		AppendPath *apath = (AppendPath *) path;
 
 		if (list_length(apath->subpaths) == 1)
+		{
+			*child_append_relid_sets =
+				lappend(*child_append_relid_sets, path->parent->relids);
 			return (Path *) linitial(apath->subpaths);
+		}
 	}
 	else if (IsA(path, MergeAppendPath))
 	{
 		MergeAppendPath *mpath = (MergeAppendPath *) path;
 
 		if (list_length(mpath->subpaths) == 1)
+		{
+			*child_append_relid_sets =
+				lappend(*child_append_relid_sets, path->parent->relids);
 			return (Path *) linitial(mpath->subpaths);
+		}
 	}
 
 	return path;
@@ -2217,7 +2273,7 @@ set_dummy_rel_pathlist(RelOptInfo *rel)
 	rel->partial_pathlist = NIL;
 
 	/* Set up the dummy path */
-	add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL,
+	add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL, NIL,
 											  NIL, rel->lateral_relids,
 											  0, false, -1));
 
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 535248aa525..6bddfc537d2 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -1342,7 +1342,7 @@ mark_dummy_rel(RelOptInfo *rel)
 
 	/* Set up the dummy path */
 	add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL,
-											  NIL, rel->lateral_relids,
+											  NIL, NIL, rel->lateral_relids,
 											  0, false, -1));
 
 	/* Set or update cheapest_total_path and related fields */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index c9dba7ff346..71d0fe00133 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -1266,6 +1266,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 	plan->plan.lefttree = NULL;
 	plan->plan.righttree = NULL;
 	plan->apprelids = rel->relids;
+	plan->child_append_relid_sets = best_path->child_append_relid_sets;
 
 	if (pathkeys != NIL)
 	{
@@ -1478,6 +1479,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
 	plan->lefttree = NULL;
 	plan->righttree = NULL;
 	node->apprelids = rel->relids;
+	node->child_append_relid_sets = best_path->child_append_relid_sets;
 
 	/*
 	 * Compute sort column info, and adjust MergeAppend's tlist as needed.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d977cbc92ad..12788989a8f 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -3979,6 +3979,7 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
 							   paths,
 							   NIL,
 							   NIL,
+							   NIL,
 							   NULL,
 							   0,
 							   false,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 598877c4f9d..505282c054f 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -815,7 +815,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
 	 * union child.
 	 */
 	apath = (Path *) create_append_path(root, result_rel, cheapest_pathlist,
-										NIL, NIL, NULL, 0, false, -1);
+										NIL, NIL, NIL, NULL, 0, false, -1);
 
 	/*
 	 * Estimate number of groups.  For now we just assume the output is unique
@@ -861,7 +861,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
 
 		papath = (Path *)
 			create_append_path(root, result_rel, NIL, partial_pathlist,
-							   NIL, NULL, parallel_workers,
+							   NIL, NIL, NULL, parallel_workers,
 							   enable_parallel_append, -1);
 		gpath = (Path *)
 			create_gather_path(root, result_rel, papath,
@@ -969,6 +969,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
 			path = (Path *) create_merge_append_path(root,
 													 result_rel,
 													 ordered_pathlist,
+													 NIL,
 													 union_pathkeys,
 													 NULL);
 
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index b0da28150d3..b4546c8842d 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1298,6 +1298,7 @@ AppendPath *
 create_append_path(PlannerInfo *root,
 				   RelOptInfo *rel,
 				   List *subpaths, List *partial_subpaths,
+				   List *child_append_relid_sets,
 				   List *pathkeys, Relids required_outer,
 				   int parallel_workers, bool parallel_aware,
 				   double rows)
@@ -1307,6 +1308,7 @@ create_append_path(PlannerInfo *root,
 
 	Assert(!parallel_aware || parallel_workers > 0);
 
+	pathnode->child_append_relid_sets = child_append_relid_sets;
 	pathnode->path.pathtype = T_Append;
 	pathnode->path.parent = rel;
 	pathnode->path.pathtarget = rel->reltarget;
@@ -1469,6 +1471,7 @@ MergeAppendPath *
 create_merge_append_path(PlannerInfo *root,
 						 RelOptInfo *rel,
 						 List *subpaths,
+						 List *child_append_relid_sets,
 						 List *pathkeys,
 						 Relids required_outer)
 {
@@ -1484,6 +1487,7 @@ create_merge_append_path(PlannerInfo *root,
 	 */
 	Assert(bms_is_empty(rel->lateral_relids) && bms_is_empty(required_outer));
 
+	pathnode->child_append_relid_sets = child_append_relid_sets;
 	pathnode->path.pathtype = T_MergeAppend;
 	pathnode->path.parent = rel;
 	pathnode->path.pathtarget = rel->reltarget;
@@ -3951,6 +3955,7 @@ reparameterize_path(PlannerInfo *root, Path *path,
 				}
 				return (Path *)
 					create_append_path(root, rel, childpaths, partialpaths,
+									   apath->child_append_relid_sets,
 									   apath->path.pathkeys, required_outer,
 									   apath->path.parallel_workers,
 									   apath->path.parallel_aware,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 3bff0763f7b..83e5ad35995 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -2089,6 +2089,12 @@ typedef struct CustomPath
  * For partial Append, 'subpaths' contains non-partial subpaths followed by
  * partial subpaths.
  *
+ * Whenever accumulate_append_subpath() allows us to consolidate multiple
+ * levels of Append paths are consolidated down to one, we store the RTI
+ * sets for the omitted paths in child_append_relid_sets. This is not necessary
+ * for planning or execution; we do it for the benefit of code that wants
+ * to inspect the final plan and understand how it came to be.
+ *
  * Note: it is possible for "subpaths" to contain only one, or even no,
  * elements.  These cases are optimized during create_append_plan.
  * In particular, an AppendPath with no subpaths is a "dummy" path that
@@ -2104,6 +2110,7 @@ typedef struct AppendPath
 	/* Index of first partial path in subpaths; list_length(subpaths) if none */
 	int			first_partial_path;
 	Cardinality limit_tuples;	/* hard limit on output tuples, or -1 */
+	List	   *child_append_relid_sets;
 } AppendPath;
 
 #define IS_DUMMY_APPEND(p) \
@@ -2120,12 +2127,15 @@ extern bool is_dummy_rel(RelOptInfo *rel);
 /*
  * MergeAppendPath represents a MergeAppend plan, ie, the merging of sorted
  * results from several member plans to produce similarly-sorted output.
+ *
+ * child_append_relid_sets has the same meaning here as for AppendPath.
  */
 typedef struct MergeAppendPath
 {
 	Path		path;
 	List	   *subpaths;		/* list of component Paths */
 	Cardinality limit_tuples;	/* hard limit on output tuples, or -1 */
+	List	   *child_append_relid_sets;
 } MergeAppendPath;
 
 /*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 218f8f1ad6d..c5b823e79b6 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -387,9 +387,16 @@ struct PartitionPruneInfo;		/* forward reference to struct below */
 typedef struct Append
 {
 	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	   *appendplans;
+
 	/* # of asynchronous plans */
 	int			nasyncplans;
 
@@ -419,6 +426,10 @@ typedef struct MergeAppend
 	/* 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;
 
 	/* these fields are just like the sort-key info in struct Sort: */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 763cd25bb3c..5f43b7fd0cb 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -70,12 +70,14 @@ extern TidRangePath *create_tidrangescan_path(PlannerInfo *root,
 											  Relids required_outer);
 extern AppendPath *create_append_path(PlannerInfo *root, RelOptInfo *rel,
 									  List *subpaths, List *partial_subpaths,
+									  List *child_append_relid_sets,
 									  List *pathkeys, Relids required_outer,
 									  int parallel_workers, bool parallel_aware,
 									  double rows);
 extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
 												 RelOptInfo *rel,
 												 List *subpaths,
+												 List *child_append_relid_sets,
 												 List *pathkeys,
 												 Relids required_outer);
 extern GroupResultPath *create_group_result_path(PlannerInfo *root,
-- 
2.39.5 (Apple Git-154)