From e78510038ec0e7f0a5427b9a1bf39ea81abff04f Mon Sep 17 00:00:00 2001
From: William Bernbaum <wbernbaum@dwdev.com>
Date: Wed, 26 Aug 2026 07:43:42 -0700
Subject: [PATCH v2 02/17] Name the grouping clause for eager aggregation

Since the clause we group on will not always be the query's GROUP BY,
record it in PlannerInfo and read it from there.
---
 src/backend/optimizer/plan/initsplan.c | 95 ++++++++++++++------------
 src/backend/optimizer/plan/planmain.c  |  1 +
 src/backend/optimizer/util/relnode.c   |  2 +-
 src/include/nodes/pathnodes.h          |  3 +
 4 files changed, 58 insertions(+), 43 deletions(-)

diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index e769b41905e..5fe77ccdd68 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -98,6 +98,7 @@ typedef struct GroupByColInfo
 static bool is_partial_agg_memory_risky(PlannerInfo *root);
 static void collect_eager_agg_infos(PlannerInfo *root);
 static void create_agg_clause_infos(PlannerInfo *root);
+static bool grouping_key_usable(Expr *expr);
 static void create_grouping_expr_infos(PlannerInfo *root);
 static EquivalenceClass *get_eclass_for_sortgroupclause(PlannerInfo *root,
 														SortGroupClause *sgc,
@@ -637,9 +638,12 @@ setup_eager_aggregation(PlannerInfo *root)
 {
 	collect_eager_agg_infos(root);
 
-	/* Nothing usable was found */
+	/* Retain the grouping clause only when keys were collected */
 	if (root->group_expr_list == NIL)
+	{
+		root->eager_group_clause = NIL;
 		return;
+	}
 
 	/* Push a partial aggregate if there is one, otherwise a deduplication */
 	root->eager_agg_mode = (root->agg_clause_list == NIL) ?
@@ -669,6 +673,8 @@ collect_eager_agg_infos(PlannerInfo *root)
 	if (!root->processed_groupClause)
 		return;
 
+	root->eager_group_clause = root->processed_groupClause;
+
 	/*
 	 * For now we don't try to support grouping sets.
 	 */
@@ -883,6 +889,50 @@ create_agg_clause_infos(PlannerInfo *root)
 	}
 }
 
+/*
+ * grouping_key_usable
+ *	  Can the given expression serve as a grouping key?
+ *
+ * For now we only support plain Vars.  Beyond that, equality must imply image
+ * equality, or else merging two keys could lose information an upper qual
+ * needs.  NUMERIC is the standard counterexample: 0 and 0.0 are equal to the
+ * equality operator but do not have the same byte image.
+ */
+static bool
+grouping_key_usable(Expr *expr)
+{
+	TypeCacheEntry *tce;
+	Oid			equalimageproc;
+
+	if (!IsA(expr, Var))
+		return false;
+
+	tce = lookup_type_cache(exprType((Node *) expr),
+							TYPECACHE_BTREE_OPFAMILY);
+	if (!OidIsValid(tce->btree_opf) ||
+		!OidIsValid(tce->btree_opintype))
+		return false;
+
+	equalimageproc = get_opfamily_proc(tce->btree_opf,
+									   tce->btree_opintype,
+									   tce->btree_opintype,
+									   BTEQUALIMAGE_PROC);
+
+	/*
+	 * If there is no BTEQUALIMAGE_PROC, eager aggregation is assumed to be
+	 * unsafe.  Otherwise, we call the procedure to check.  We must be careful
+	 * to pass the expression's actual collation, rather than the data type's
+	 * default collation, to ensure that non-deterministic collations are
+	 * correctly handled.
+	 */
+	if (!OidIsValid(equalimageproc))
+		return false;
+
+	return DatumGetBool(OidFunctionCall1Coll(equalimageproc,
+											 exprCollation((Node *) expr),
+											 ObjectIdGetDatum(tce->btree_opintype)));
+}
+
 /*
  * create_grouping_expr_infos
  *	  Create a GroupingExprInfo for each expression usable as grouping key.
@@ -903,53 +953,14 @@ create_grouping_expr_infos(PlannerInfo *root)
 
 	Assert(root->group_expr_list == NIL);
 
-	foreach(lc, root->processed_groupClause)
+	foreach(lc, root->eager_group_clause)
 	{
 		SortGroupClause *sgc = lfirst_node(SortGroupClause, lc);
 		TargetEntry *tle = get_sortgroupclause_tle(sgc, root->processed_tlist);
-		TypeCacheEntry *tce;
-		Oid			equalimageproc;
 
 		Assert(tle->ressortgroupref > 0);
 
-		/*
-		 * For now we only support plain Vars as grouping expressions.
-		 */
-		if (!IsA(tle->expr, Var))
-			return;
-
-		/*
-		 * Eager aggregation is only possible if equality implies image
-		 * equality for each grouping key.  Otherwise, placing keys with
-		 * different byte images into the same group may result in the loss of
-		 * information that could be necessary to evaluate upper qual clauses.
-		 *
-		 * For instance, the NUMERIC data type is not supported, as values
-		 * that are considered equal by the equality operator (e.g., 0 and
-		 * 0.0) can have different scales.
-		 */
-		tce = lookup_type_cache(exprType((Node *) tle->expr),
-								TYPECACHE_BTREE_OPFAMILY);
-		if (!OidIsValid(tce->btree_opf) ||
-			!OidIsValid(tce->btree_opintype))
-			return;
-
-		equalimageproc = get_opfamily_proc(tce->btree_opf,
-										   tce->btree_opintype,
-										   tce->btree_opintype,
-										   BTEQUALIMAGE_PROC);
-
-		/*
-		 * If there is no BTEQUALIMAGE_PROC, eager aggregation is assumed to
-		 * be unsafe.  Otherwise, we call the procedure to check.  We must be
-		 * careful to pass the expression's actual collation, rather than the
-		 * data type's default collation, to ensure that non-deterministic
-		 * collations are correctly handled.
-		 */
-		if (!OidIsValid(equalimageproc) ||
-			!DatumGetBool(OidFunctionCall1Coll(equalimageproc,
-											   exprCollation((Node *) tle->expr),
-											   ObjectIdGetDatum(tce->btree_opintype))))
+		if (!grouping_key_usable(tle->expr))
 			return;
 
 		exprs = lappend(exprs, tle->expr);
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 21688cc0c0a..0be862260ec 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -108,6 +108,7 @@ restart:
 	root->placeholdersFrozen = false;
 	root->agg_clause_list = NIL;
 	root->group_expr_list = NIL;
+	root->eager_group_clause = NIL;
 	root->eager_agg_mode = EAGER_AGG_NONE;
 	root->tlist_vars = NIL;
 	root->fkey_list = NIL;
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 865187824f6..ea52cc09836 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -2991,7 +2991,7 @@ init_grouping_targets(PlannerInfo *root, RelOptInfo *rel,
 			SortGroupClause *sgc;
 
 			/* Find the matching SortGroupClause */
-			sgc = get_sortgroupref_clause(sortgroupref, root->processed_groupClause);
+			sgc = get_sortgroupref_clause(sortgroupref, root->eager_group_clause);
 			Assert(sgc->tleSortGroupRef <= maxSortGroupRef);
 
 			/*
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index b5f856e9327..ae1e117880e 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -513,6 +513,9 @@ struct PlannerInfo
 	/* list of GroupingExprInfos */
 	List	   *group_expr_list;
 
+	/* the SortGroupClauses the grouping expressions were derived from */
+	List	   *eager_group_clause;
+
 	/* what eager aggregation pushes below the joins, if anything */
 	EagerAggMode eager_agg_mode;
 
