v6-0007-Build-grouped-relations-out-of-join-relations.patch

application/octet-stream

Filename: v6-0007-Build-grouped-relations-out-of-join-relations.patch
Type: application/octet-stream
Part: 6
Message: Re: Eager aggregation, take 3

Patch

Format: format-patch
Series: patch v6-0007
Subject: Build grouped relations out of join relations
File+
src/backend/optimizer/geqo/geqo_eval.c 66 18
src/backend/optimizer/path/allpaths.c 48 0
src/backend/optimizer/path/joinrels.c 122 0
src/backend/optimizer/plan/planner.c 59 25
src/backend/optimizer/util/appendinfo.c 60 0
src/backend/optimizer/util/relnode.c 0 2
src/include/nodes/pathnodes.h 0 6
From 552892b0b78128392d2adb6bae2d367316f07885 Mon Sep 17 00:00:00 2001
From: Richard Guo <guofenglinux@gmail.com>
Date: Fri, 23 Feb 2024 13:33:09 +0800
Subject: [PATCH v6 7/9] Build grouped relations out of join relations

This commit builds grouped relations for each just-processed join
relation if possible, and generates aggregation paths for the grouped
join relations.

The changes made to make_join_rel() are relatively minor, with the
addition of a new function make_grouped_join_rel(), which finds or
creates a grouped relation for the just-processed joinrel, and generates
grouped paths by joining a grouped input relation with a non-grouped
input relation.

The other way to generate grouped paths is by adding sorted and hashed
partial aggregation paths on top of paths of the joinrel.  This occurs
in standard_join_search(), after we've run set_cheapest() for the
joinrel.  The reason for performing this step after set_cheapest() is
that we need to know the joinrel's cheapest paths (see
generate_grouped_paths()).

This patch also makes the grouped relation for the topmost join rel act
as the upper rel representing the result of partial aggregation, so that
we can add the final aggregation on top of that.  Additionally, this
patch extends the functionality of eager aggregation to work with
partitionwise join and geqo.

This patch also makes eager aggregation work with outer joins.  With
outer joins, the aggregate cannot be pushed down if any column
referenced by grouping expressions or aggregate functions is nullable by
an outer join above the relation to which we want to apply the partial
aggregation.  Thanks to Tom's outer-join-aware-Var infrastructure, we
can easily identify such situations and subsequently refrain from
pushing down the aggregates.

Starting from this patch, you should be able to see plans with eager
aggregation.
---
 src/backend/optimizer/geqo/geqo_eval.c  |  84 ++++++++++++----
 src/backend/optimizer/path/allpaths.c   |  48 ++++++++++
 src/backend/optimizer/path/joinrels.c   | 122 ++++++++++++++++++++++++
 src/backend/optimizer/plan/planner.c    |  84 +++++++++++-----
 src/backend/optimizer/util/appendinfo.c |  60 ++++++++++++
 src/backend/optimizer/util/relnode.c    |   2 -
 src/include/nodes/pathnodes.h           |   6 --
 7 files changed, 355 insertions(+), 51 deletions(-)

diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
index 1141156899..278857d767 100644
--- a/src/backend/optimizer/geqo/geqo_eval.c
+++ b/src/backend/optimizer/geqo/geqo_eval.c
@@ -60,8 +60,12 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
 	MemoryContext oldcxt;
 	RelOptInfo *joinrel;
 	Cost		fitness;
-	int			savelength;
-	struct HTAB *savehash;
+	int			savelength_join_rel;
+	struct HTAB *savehash_join_rel;
+	int			savelength_grouped_rel;
+	struct HTAB *savehash_grouped_rel;
+	int			savelength_grouped_info;
+	struct HTAB *savehash_grouped_info;
 
 	/*
 	 * Create a private memory context that will hold all temp storage
@@ -78,25 +82,38 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
 	oldcxt = MemoryContextSwitchTo(mycontext);
 
 	/*
-	 * gimme_tree will add entries to root->join_rel_list, which may or may
-	 * not already contain some entries.  The newly added entries will be
-	 * recycled by the MemoryContextDelete below, so we must ensure that the
-	 * list is restored to its former state before exiting.  We can do this by
-	 * truncating the list to its original length.  NOTE this assumes that any
-	 * added entries are appended at the end!
+	 * gimme_tree will add entries to root->join_rel_list, root->agg_info_list
+	 * and root->upper_rels[UPPERREL_PARTIAL_GROUP_AGG], which may or may not
+	 * already contain some entries.  The newly added entries will be recycled
+	 * by the MemoryContextDelete below, so we must ensure that each list of
+	 * the RelInfoList structures is restored to its former state before
+	 * exiting.  We can do this by truncating each list to its original length.
+	 * NOTE this assumes that any added entries are appended at the end!
 	 *
-	 * We also must take care not to mess up the outer join_rel_list->hash, if
-	 * there is one.  We can do this by just temporarily setting the link to
-	 * NULL.  (If we are dealing with enough join rels, which we very likely
-	 * are, a new hash table will get built and used locally.)
+	 * We also must take care not to mess up the outer hash tables of the
+	 * RelInfoList structures, if any.  We can do this by just temporarily
+	 * setting each link to NULL.  (If we are dealing with enough join rels,
+	 * which we very likely are, new hash tables will get built and used
+	 * locally.)
 	 *
 	 * join_rel_level[] shouldn't be in use, so just Assert it isn't.
 	 */
-	savelength = list_length(root->join_rel_list->items);
-	savehash = root->join_rel_list->hash;
+	savelength_join_rel = list_length(root->join_rel_list->items);
+	savehash_join_rel = root->join_rel_list->hash;
+
+	savelength_grouped_rel =
+		list_length(root->upper_rels[UPPERREL_PARTIAL_GROUP_AGG].items);
+	savehash_grouped_rel =
+		root->upper_rels[UPPERREL_PARTIAL_GROUP_AGG].hash;
+
+	savelength_grouped_info = list_length(root->agg_info_list->items);
+	savehash_grouped_info = root->agg_info_list->hash;
+
 	Assert(root->join_rel_level == NULL);
 
 	root->join_rel_list->hash = NULL;
+	root->upper_rels[UPPERREL_PARTIAL_GROUP_AGG].hash = NULL;
+	root->agg_info_list->hash = NULL;
 
 	/* construct the best path for the given combination of relations */
 	joinrel = gimme_tree(root, tour, num_gene);
@@ -118,12 +135,22 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
 		fitness = DBL_MAX;
 
 	/*
-	 * Restore join_rel_list to its former state, and put back original
-	 * hashtable if any.
+	 * Restore each of the list in join_rel_list, agg_info_list and
+	 * upper_rels[UPPERREL_PARTIAL_GROUP_AGG] to its former state, and put back
+	 * original hashtable if any.
 	 */
 	root->join_rel_list->items = list_truncate(root->join_rel_list->items,
-											   savelength);
-	root->join_rel_list->hash = savehash;
+											   savelength_join_rel);
+	root->join_rel_list->hash = savehash_join_rel;
+
+	root->upper_rels[UPPERREL_PARTIAL_GROUP_AGG].items =
+		list_truncate(root->upper_rels[UPPERREL_PARTIAL_GROUP_AGG].items,
+					  savelength_grouped_rel);
+	root->upper_rels[UPPERREL_PARTIAL_GROUP_AGG].hash = savehash_grouped_rel;
+
+	root->agg_info_list->items = list_truncate(root->agg_info_list->items,
+											   savelength_grouped_info);
+	root->agg_info_list->hash = savehash_grouped_info;
 
 	/* release all the memory acquired within gimme_tree */
 	MemoryContextSwitchTo(oldcxt);
@@ -279,6 +306,27 @@ merge_clump(PlannerInfo *root, List *clumps, Clump *new_clump, int num_gene,
 				/* Find and save the cheapest paths for this joinrel */
 				set_cheapest(joinrel);
 
+				/*
+				 * Except for the topmost scan/join rel, consider generating
+				 * partial aggregation paths for the grouped relation on top of the
+				 * paths of this rel.  After that, we're done creating paths for
+				 * the grouped relation, so run set_cheapest().
+				 */
+				if (!bms_equal(joinrel->relids, root->all_query_rels))
+				{
+					RelOptInfo   *rel_grouped;
+					RelAggInfo   *agg_info;
+
+					rel_grouped = find_grouped_rel(root, joinrel->relids,
+												   &agg_info);
+					if (rel_grouped)
+					{
+						generate_grouped_paths(root, rel_grouped, joinrel,
+											   agg_info);
+						set_cheapest(rel_grouped);
+					}
+				}
+
 				/* Absorb new clump into old */
 				old_clump->joinrel = joinrel;
 				old_clump->size += new_clump->size;
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index ef699ab630..0e2c984442 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -3866,6 +3866,10 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
 		 *
 		 * After that, we're done creating paths for the joinrel, so run
 		 * set_cheapest().
+		 *
+		 * In addition, we also run generate_grouped_paths() for the grouped
+		 * relation of each just-processed joinrel, and run set_cheapest() for
+		 * the grouped relation afterwards.
 		 */
 		foreach(lc, root->join_rel_level[lev])
 		{
@@ -3886,6 +3890,27 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
 			/* Find and save the cheapest paths for this rel */
 			set_cheapest(rel);
 
+			/*
+			 * Except for the topmost scan/join rel, consider generating
+			 * partial aggregation paths for the grouped relation on top of the
+			 * paths of this rel.  After that, we're done creating paths for
+			 * the grouped relation, so run set_cheapest().
+			 */
+			if (!bms_equal(rel->relids, root->all_query_rels))
+			{
+				RelOptInfo   *rel_grouped;
+				RelAggInfo   *agg_info;
+
+				rel_grouped = find_grouped_rel(root, rel->relids,
+											   &agg_info);
+				if (rel_grouped)
+				{
+					generate_grouped_paths(root, rel_grouped, rel,
+										   agg_info);
+					set_cheapest(rel_grouped);
+				}
+			}
+
 #ifdef OPTIMIZER_DEBUG
 			pprint(rel);
 #endif
@@ -4754,6 +4779,29 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel)
 		if (IS_DUMMY_REL(child_rel))
 			continue;
 
+		/*
+		 * Except for the topmost scan/join rel, consider generating partial
+		 * aggregation paths for the grouped relation on top of the paths of
+		 * this partitioned child-join.  After that, we're done creating paths
+		 * for the grouped relation, so run set_cheapest().
+		 */
+		if (!bms_equal(IS_OTHER_REL(rel) ?
+					   rel->top_parent_relids : rel->relids,
+					   root->all_query_rels))
+		{
+			RelOptInfo   *rel_grouped;
+			RelAggInfo   *agg_info;
+
+			rel_grouped = find_grouped_rel(root, child_rel->relids,
+										   &agg_info);
+			if (rel_grouped)
+			{
+				generate_grouped_paths(root, rel_grouped, child_rel,
+									   agg_info);
+				set_cheapest(rel_grouped);
+			}
+		}
+
 #ifdef OPTIMIZER_DEBUG
 		pprint(child_rel);
 #endif
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index f3a9412d18..ba1d15e85a 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -16,11 +16,13 @@
 
 #include "miscadmin.h"
 #include "optimizer/appendinfo.h"
+#include "optimizer/cost.h"
 #include "optimizer/joininfo.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "partitioning/partbounds.h"
 #include "utils/memutils.h"
+#include "utils/selfuncs.h"
 
 
 static void make_rels_by_clause_joins(PlannerInfo *root,
@@ -35,6 +37,9 @@ static bool has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel);
 static bool restriction_is_constant_false(List *restrictlist,
 										  RelOptInfo *joinrel,
 										  bool only_pushed_down);
+static void make_grouped_join_rel(PlannerInfo *root, RelOptInfo *rel1,
+								  RelOptInfo *rel2, RelOptInfo *joinrel,
+								  SpecialJoinInfo *sjinfo, List *restrictlist);
 static void populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
 										RelOptInfo *rel2, RelOptInfo *joinrel,
 										SpecialJoinInfo *sjinfo, List *restrictlist);
@@ -771,6 +776,10 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
 		return joinrel;
 	}
 
+	/* Build a grouped join relation for 'joinrel' if possible. */
+	make_grouped_join_rel(root, rel1, rel2, joinrel, sjinfo,
+						  restrictlist);
+
 	/* Add paths to the join relation. */
 	populate_joinrel_with_paths(root, rel1, rel2, joinrel, sjinfo,
 								restrictlist);
@@ -882,6 +891,114 @@ add_outer_joins_to_relids(PlannerInfo *root, Relids input_relids,
 	return input_relids;
 }
 
+/*
+ * make_grouped_join_rel
+ *	  Build a grouped join relation out of 'joinrel' if eager aggregation is
+ *	  possible and the 'joinrel' can produce grouped paths.
+ *
+ * We also generate partial aggregation paths for the grouped relation by
+ * joining the grouped paths of 'rel1' to the plain paths of 'rel2', or by
+ * joining the grouped paths of 'rel2' to the plain paths of 'rel1'.
+ */
+static void
+make_grouped_join_rel(PlannerInfo *root, RelOptInfo *rel1,
+					  RelOptInfo *rel2, RelOptInfo *joinrel,
+					  SpecialJoinInfo *sjinfo, List *restrictlist)
+{
+	RelOptInfo	   *rel_grouped;
+	RelAggInfo	   *agg_info = NULL;
+	RelOptInfo	   *rel1_grouped;
+	RelOptInfo	   *rel2_grouped;
+	bool			rel1_empty;
+	bool			rel2_empty;
+
+	/*
+	 * If there are no aggregate expressions or grouping expressions, eager
+	 * aggregation is not possible.
+	 */
+	if (root->agg_clause_list == NIL ||
+		root->group_expr_list == NIL)
+		return;
+
+	/*
+	 * See if we already have a grouped joinrel for this joinrel.
+	 */
+	rel_grouped = find_grouped_rel(root, joinrel->relids, &agg_info);
+
+	/*
+	 * Construct a new RelOptInfo for the grouped join relation if there is no
+	 * existing one.
+	 */
+	if (rel_grouped == NULL)
+	{
+		/*
+		 * Prepare the information we need to create grouped paths for this
+		 * join relation.
+		 */
+		agg_info = create_rel_agg_info(root, joinrel);
+		if (agg_info == NULL)
+			return;
+
+		/* build a grouped relation out of the plain relation */
+		rel_grouped = build_grouped_rel(root, joinrel);
+		rel_grouped->reltarget = agg_info->target;
+		rel_grouped->rows = agg_info->grouped_rows;
+
+		/*
+		 * Make the grouped relation available for further joining or for
+		 * acting as the upper rel representing the result of partial
+		 * aggregation.
+		 */
+		add_grouped_rel(root, rel_grouped, agg_info);
+	}
+
+	Assert(agg_info != NULL);
+
+	/*
+	 * If we've already proven this grouped join relation is empty, we needn't
+	 * consider any more paths for it.
+	 */
+	if (IS_DUMMY_REL(rel_grouped))
+		return;
+
+	/* retrieve the grouped relations for the two input rels */
+	rel1_grouped = find_grouped_rel(root, rel1->relids, NULL);
+	rel2_grouped = find_grouped_rel(root, rel2->relids, NULL);
+
+	rel1_empty = (rel1_grouped == NULL || IS_DUMMY_REL(rel1_grouped));
+	rel2_empty = (rel2_grouped == NULL || IS_DUMMY_REL(rel2_grouped));
+
+	/* Nothing to do if there's no grouped relation. */
+	if (rel1_empty && rel2_empty)
+		return;
+
+	/*
+	 * Join of two grouped relations is currently not supported.  In such a
+	 * case, grouping of one side would change the occurrence of the other
+	 * side's aggregate transient states on the input of the final aggregation.
+	 * This can be handled by adjusting the transient states, but it's not
+	 * worth the effort for now.
+	 */
+	if (!rel1_empty && !rel2_empty)
+		return;
+
+	/* generate partial aggregation paths for the grouped relation */
+	if (!rel1_empty)
+	{
+		set_joinrel_size_estimates(root, rel_grouped, rel1_grouped, rel2,
+								   sjinfo, restrictlist);
+		populate_joinrel_with_paths(root, rel1_grouped, rel2, rel_grouped,
+									sjinfo, restrictlist);
+	}
+	else if (!rel2_empty)
+	{
+		set_joinrel_size_estimates(root, rel_grouped, rel1, rel2_grouped,
+								   sjinfo, restrictlist);
+		populate_joinrel_with_paths(root, rel1, rel2_grouped, rel_grouped,
+									sjinfo, restrictlist);
+	}
+}
+
 /*
  * populate_joinrel_with_paths
  *	  Add paths to the given joinrel for given pair of joining relations. The
@@ -1671,6 +1788,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
 						 adjust_child_relids(joinrel->relids,
 											 nappinfos, appinfos)));
 
+		/* Build a grouped join relation for 'child_joinrel' if possible */
+		make_grouped_join_rel(root, child_rel1, child_rel2,
+							  child_joinrel, child_sjinfo,
+							  child_restrictlist);
+
 		/* And make paths for the child join */
 		populate_joinrel_with_paths(root, child_rel1, child_rel2,
 									child_joinrel, child_sjinfo,
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5320da51a0..4a6386a09d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -225,7 +225,6 @@ static void add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 									  RelOptInfo *partially_grouped_rel,
 									  const AggClauseCosts *agg_costs,
 									  grouping_sets_data *gd,
-									  double dNumGroups,
 									  GroupPathExtraData *extra);
 static RelOptInfo *create_partial_grouping_paths(PlannerInfo *root,
 												 RelOptInfo *grouped_rel,
@@ -3913,9 +3912,7 @@ create_ordinary_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
 							   GroupPathExtraData *extra,
 							   RelOptInfo **partially_grouped_rel_p)
 {
-	Path	   *cheapest_path = input_rel->cheapest_total_path;
 	RelOptInfo *partially_grouped_rel = NULL;
-	double		dNumGroups;
 	PartitionwiseAggregateType patype = PARTITIONWISE_AGGREGATE_NONE;
 
 	/*
@@ -3996,23 +3993,21 @@ create_ordinary_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
 
 	/* Gather any partially grouped partial paths. */
 	if (partially_grouped_rel && partially_grouped_rel->partial_pathlist)
-	{
 		gather_grouping_paths(root, partially_grouped_rel);
-		set_cheapest(partially_grouped_rel);
-	}
 
 	/*
-	 * Estimate number of groups.
+	 * Now choose the best path(s) for partially_grouped_rel.
+	 *
+	 * Note that the non-partial paths can come either from the Gather above or
+	 * from eager aggregation.
 	 */
-	dNumGroups = get_number_of_groups(root,
-									  cheapest_path->rows,
-									  gd,
-									  extra->targetList);
+	if (partially_grouped_rel && partially_grouped_rel->pathlist)
+		set_cheapest(partially_grouped_rel);
 
 	/* Build final grouping paths */
 	add_paths_to_grouping_rel(root, input_rel, grouped_rel,
 							  partially_grouped_rel, agg_costs, gd,
-							  dNumGroups, extra);
+							  extra);
 
 	/* Give a helpful error if we failed to find any implementation */
 	if (grouped_rel->pathlist == NIL)
@@ -6843,16 +6838,42 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 						  RelOptInfo *grouped_rel,
 						  RelOptInfo *partially_grouped_rel,
 						  const AggClauseCosts *agg_costs,
-						  grouping_sets_data *gd, double dNumGroups,
+						  grouping_sets_data *gd,
 						  GroupPathExtraData *extra)
 {
 	Query	   *parse = root->parse;
 	Path	   *cheapest_path = input_rel->cheapest_total_path;
+	Path	   *cheapest_partially_grouped_path = NULL;
 	ListCell   *lc;
 	bool		can_hash = (extra->flags & GROUPING_CAN_USE_HASH) != 0;
 	bool		can_sort = (extra->flags & GROUPING_CAN_USE_SORT) != 0;
 	List	   *havingQual = (List *) extra->havingQual;
 	AggClauseCosts *agg_final_costs = &extra->agg_final_costs;
+	double		dNumGroups = 0;
+	double		dNumFinalGroups = 0;
+
+	/*
+	 * Estimate number of groups for non-split aggregation.
+	 */
+	dNumGroups = get_number_of_groups(root,
+									  cheapest_path->rows,
+									  gd,
+									  extra->targetList);
+
+	if (partially_grouped_rel && partially_grouped_rel->pathlist)
+	{
+		cheapest_partially_grouped_path =
+			partially_grouped_rel->cheapest_total_path;
+
+		/*
+		 * Estimate number of groups for final phase of partial aggregation.
+		 */
+		dNumFinalGroups =
+			get_number_of_groups(root,
+								 cheapest_partially_grouped_path->rows,
+								 gd,
+								 extra->targetList);
+	}
 
 	if (can_sort)
 	{
@@ -6964,7 +6985,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 					path = make_ordered_path(root,
 											 grouped_rel,
 											 path,
-											 partially_grouped_rel->cheapest_total_path,
+											 cheapest_partially_grouped_path,
 											 info->pathkeys);
 
 					if (path == NULL)
@@ -6981,7 +7002,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 												 info->clauses,
 												 havingQual,
 												 agg_final_costs,
-												 dNumGroups));
+												 dNumFinalGroups));
 					else
 						add_path(grouped_rel, (Path *)
 								 create_group_path(root,
@@ -6989,7 +7010,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 												   path,
 												   info->clauses,
 												   havingQual,
-												   dNumGroups));
+												   dNumFinalGroups));
 
 				}
 			}
@@ -7031,19 +7052,17 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 		 */
 		if (partially_grouped_rel && partially_grouped_rel->pathlist)
 		{
-			Path	   *path = partially_grouped_rel->cheapest_total_path;
-
 			add_path(grouped_rel, (Path *)
 					 create_agg_path(root,
 									 grouped_rel,
-									 path,
+									 cheapest_partially_grouped_path,
 									 grouped_rel->reltarget,
 									 AGG_HASHED,
 									 AGGSPLIT_FINAL_DESERIAL,
 									 root->processed_groupClause,
 									 havingQual,
 									 agg_final_costs,
-									 dNumGroups));
+									 dNumFinalGroups));
 		}
 	}
 
@@ -7093,6 +7112,13 @@ create_partial_grouping_paths(PlannerInfo *root,
 	bool		can_hash = (extra->flags & GROUPING_CAN_USE_HASH) != 0;
 	bool		can_sort = (extra->flags & GROUPING_CAN_USE_SORT) != 0;
 
+	/*
+	 * The partially_grouped_rel could have been already created due to eager
+	 * aggregation.
+	 */
+	partially_grouped_rel = find_grouped_rel(root, input_rel->relids, NULL);
+	Assert(enable_eager_aggregate || partially_grouped_rel == NULL);
+
 	/*
 	 * Consider whether we should generate partially aggregated non-partial
 	 * paths.  We can only do this if we have a non-partial path, and only if
@@ -7116,19 +7142,27 @@ create_partial_grouping_paths(PlannerInfo *root,
 	 * If we can't partially aggregate partial paths, and we can't partially
 	 * aggregate non-partial paths, then don't bother creating the new
 	 * RelOptInfo at all, unless the caller specified force_rel_creation.
+	 *
+	 * Note that the partially_grouped_rel could have been already created and
+	 * populated with appropriate paths by eager aggregation.
 	 */
 	if (cheapest_total_path == NULL &&
 		cheapest_partial_path == NULL &&
+		(partially_grouped_rel == NULL ||
+		 partially_grouped_rel->pathlist == NIL) &&
 		!force_rel_creation)
 		return NULL;
 
 	/*
 	 * Build a new upper relation to represent the result of partially
-	 * aggregating the rows from the input relation.
-	 */
-	partially_grouped_rel = fetch_upper_rel(root,
-											UPPERREL_PARTIAL_GROUP_AGG,
-											grouped_rel->relids);
+	 * aggregating the rows from the input relation.  The relation may already
+	 * exist due to eager aggregation, in which case we don't need to create
+	 * it.
+	 */
+	if (partially_grouped_rel == NULL)
+		partially_grouped_rel = fetch_upper_rel(root,
+												UPPERREL_PARTIAL_GROUP_AGG,
+												grouped_rel->relids);
 	partially_grouped_rel->consider_parallel =
 		grouped_rel->consider_parallel;
 	partially_grouped_rel->reloptkind = grouped_rel->reloptkind;
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 6ba4eba224..08de77d439 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -495,6 +495,66 @@ adjust_appendrel_attrs_mutator(Node *node,
 		return (Node *) newinfo;
 	}
 
+	/*
+	 * We have to process RelAggInfo nodes specially.
+	 */
+	if (IsA(node, RelAggInfo))
+	{
+		RelAggInfo *oldinfo = (RelAggInfo *) node;
+		RelAggInfo *newinfo = makeNode(RelAggInfo);
+
+		/* Copy all flat-copiable fields */
+		memcpy(newinfo, oldinfo, sizeof(RelAggInfo));
+
+		newinfo->relids = adjust_child_relids(oldinfo->relids,
+											  context->nappinfos,
+											  context->appinfos);
+
+		newinfo->target = (PathTarget *)
+			adjust_appendrel_attrs_mutator((Node *) oldinfo->target,
+										   context);
+
+		newinfo->agg_input = (PathTarget *)
+			adjust_appendrel_attrs_mutator((Node *) oldinfo->agg_input,
+										   context);
+
+		newinfo->group_clauses = (List *)
+			adjust_appendrel_attrs_mutator((Node *) oldinfo->group_clauses,
+										   context);
+
+		newinfo->group_exprs = (List *)
+			adjust_appendrel_attrs_mutator((Node *) oldinfo->group_exprs,
+										   context);
+
+		return (Node *) newinfo;
+	}
+
+	/*
+	 * We have to process PathTarget nodes specially.
+	 */
+	if (IsA(node, PathTarget))
+	{
+		PathTarget *oldtarget = (PathTarget *) node;
+		PathTarget *newtarget = makeNode(PathTarget);
+
+		/* Copy all flat-copiable fields */
+		memcpy(newtarget, oldtarget, sizeof(PathTarget));
+
+		if (oldtarget->sortgrouprefs)
+		{
+			Size	nbytes = list_length(oldtarget->exprs) * sizeof(Index);
+
+			newtarget->exprs = (List *)
+				adjust_appendrel_attrs_mutator((Node *) oldtarget->exprs,
+											   context);
+
+			newtarget->sortgrouprefs = (Index *) palloc(nbytes);
+			memcpy(newtarget->sortgrouprefs, oldtarget->sortgrouprefs, nbytes);
+		}
+
+		return (Node *) newtarget;
+	}
+
 	/*
 	 * NOTE: we do not need to recurse into sublinks, because they should
 	 * already have been converted to subplans before we see them.
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index b14f99a9ea..6087a14a76 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -2833,8 +2833,6 @@ create_rel_agg_info(PlannerInfo *root, RelOptInfo *rel)
 		mark_partial_aggref(aggref, AGGSPLIT_INITIAL_SERIAL);
 
 		add_column_to_pathtarget(target, (Expr *) aggref, 0);
-
-		result->agg_exprs = lappend(result->agg_exprs, aggref);
 	}
 
 	/*
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index ac639abe31..2c93dc3241 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1116,9 +1116,6 @@ typedef struct RelOptInfo
  * "group_clauses", "group_exprs" and "group_pathkeys" are lists of
  * SortGroupClause, the corresponding grouping expressions and PathKey
  * respectively.
- *
- * "agg_exprs" is a list of Aggref nodes for the aggregation of the relation's
- * paths.
  */
 typedef struct RelAggInfo
 {
@@ -1154,9 +1151,6 @@ typedef struct RelAggInfo
 	List	   *group_exprs;
 	/* a list of PathKeys */
 	List	   *group_pathkeys;
-
-	/* a list of Aggref nodes */
-	List	   *agg_exprs;
 } RelAggInfo;
 
 /*
-- 
2.31.0