From 1b2c40bf66106d9bd6532eb52f4ac496b6dbebd3 Mon Sep 17 00:00:00 2001
From: William Bernbaum <wbernbaum@dwdev.com>
Date: Wed, 26 Aug 2026 07:31:33 -0700
Subject: [PATCH v2 01/17] Stop assuming eager aggregation always has an
 aggregate expression to push

A deduplication can be pushed below a join like a partial aggregate,
letting the same machinery handle an empty aggregate list.
---
 src/backend/optimizer/path/allpaths.c  | 32 +++++++++++++++-----------
 src/backend/optimizer/plan/initsplan.c | 22 ++++++++++++++++++
 src/backend/optimizer/plan/planmain.c  |  1 +
 src/backend/optimizer/util/relnode.c   | 14 ++++-------
 src/include/nodes/pathnodes.h          | 15 ++++++++++++
 src/tools/pgindent/typedefs.list       |  1 +
 6 files changed, 63 insertions(+), 22 deletions(-)

diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 24a6a8d11dd..b9f56b1519a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -352,11 +352,10 @@ setup_simple_grouped_rels(PlannerInfo *root)
 	Index		rti;
 
 	/*
-	 * If there are no aggregate expressions or grouping expressions, eager
-	 * aggregation is not possible.
+	 * If there are no grouping expressions, eager aggregation is not
+	 * possible.
 	 */
-	if (root->agg_clause_list == NIL ||
-		root->group_expr_list == NIL)
+	if (root->group_expr_list == NIL)
 		return;
 
 	for (rti = 1; rti < root->simple_rel_array_size; rti++)
@@ -1386,11 +1385,10 @@ set_grouped_rel_pathlist(PlannerInfo *root, RelOptInfo *rel)
 	RelOptInfo *grouped_rel;
 
 	/*
-	 * If there are no aggregate expressions or grouping expressions, eager
-	 * aggregation is not possible.
+	 * If there are no grouping expressions, eager aggregation is not
+	 * possible.
 	 */
-	if (root->agg_clause_list == NIL ||
-		root->group_expr_list == NIL)
+	if (root->group_expr_list == NIL)
 		return;
 
 	/* Add paths to the grouped base relation if one exists. */
@@ -3511,6 +3509,7 @@ generate_grouped_paths(PlannerInfo *root, RelOptInfo *grouped_rel,
 {
 	RelAggInfo *agg_info = grouped_rel->agg_info;
 	AggClauseCosts agg_costs;
+	AggSplit	aggsplit;
 	bool		can_hash;
 	bool		can_sort;
 	Path	   *cheapest_total_path = NULL;
@@ -3533,8 +3532,15 @@ generate_grouped_paths(PlannerInfo *root, RelOptInfo *grouped_rel,
 		!agg_info->agg_useful)
 		return;
 
+	/* A deduplication emits final rows and carries zero aggregate cost */
 	MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
-	get_agg_clause_costs(root, AGGSPLIT_INITIAL_SERIAL, &agg_costs);
+	if (root->eager_agg_mode == EAGER_AGG_DEDUP)
+		aggsplit = AGGSPLIT_SIMPLE;
+	else
+	{
+		aggsplit = AGGSPLIT_INITIAL_SERIAL;
+		get_agg_clause_costs(root, aggsplit, &agg_costs);
+	}
 
 	/*
 	 * Determine whether it's possible to perform sort-based implementations
@@ -3679,7 +3685,7 @@ generate_grouped_paths(PlannerInfo *root, RelOptInfo *grouped_rel,
 											path,
 											agg_info->target,
 											AGG_SORTED,
-											AGGSPLIT_INITIAL_SERIAL,
+											aggsplit,
 											agg_info->group_clauses,
 											NIL,
 											&agg_costs,
@@ -3755,7 +3761,7 @@ generate_grouped_paths(PlannerInfo *root, RelOptInfo *grouped_rel,
 											path,
 											agg_info->target,
 											AGG_SORTED,
-											AGGSPLIT_INITIAL_SERIAL,
+											aggsplit,
 											agg_info->group_clauses,
 											NIL,
 											&agg_costs,
@@ -3791,7 +3797,7 @@ generate_grouped_paths(PlannerInfo *root, RelOptInfo *grouped_rel,
 										path,
 										agg_info->target,
 										AGG_HASHED,
-										AGGSPLIT_INITIAL_SERIAL,
+										aggsplit,
 										agg_info->group_clauses,
 										NIL,
 										&agg_costs,
@@ -3826,7 +3832,7 @@ generate_grouped_paths(PlannerInfo *root, RelOptInfo *grouped_rel,
 										path,
 										agg_info->target,
 										AGG_HASHED,
-										AGGSPLIT_INITIAL_SERIAL,
+										aggsplit,
 										agg_info->group_clauses,
 										NIL,
 										&agg_costs,
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index a2fb0b55a79..e769b41905e 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -96,6 +96,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 void create_grouping_expr_infos(PlannerInfo *root);
 static EquivalenceClass *get_eclass_for_sortgroupclause(PlannerInfo *root,
@@ -633,6 +634,27 @@ remove_useless_groupby_columns(PlannerInfo *root)
  */
 void
 setup_eager_aggregation(PlannerInfo *root)
+{
+	collect_eager_agg_infos(root);
+
+	/* Nothing usable was found */
+	if (root->group_expr_list == NIL)
+		return;
+
+	/* Push a partial aggregate if there is one, otherwise a deduplication */
+	root->eager_agg_mode = (root->agg_clause_list == NIL) ?
+		EAGER_AGG_DEDUP : EAGER_AGG_PARTIAL;
+}
+
+/*
+ * collect_eager_agg_infos
+ *	  Collect the aggregate expressions and grouping expressions that eager
+ *	  aggregation can push down, if any.
+ *
+ * Leaves root->group_expr_list NIL if the query cannot be handled.
+ */
+static void
+collect_eager_agg_infos(PlannerInfo *root)
 {
 	/*
 	 * Don't apply eager aggregation if disabled by user.
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 68d3409476c..21688cc0c0a 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_agg_mode = EAGER_AGG_NONE;
 	root->tlist_vars = NIL;
 	root->fkey_list = NIL;
 	root->initial_rels = NIL;
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index ee69f81945f..865187824f6 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -451,10 +451,9 @@ build_simple_grouped_rel(PlannerInfo *root, RelOptInfo *rel)
 	RelAggInfo *agg_info;
 
 	/*
-	 * We should have available aggregate expressions and grouping
-	 * expressions, otherwise we cannot reach here.
+	 * We should have available grouping expressions, otherwise we cannot
+	 * reach here.
 	 */
-	Assert(root->agg_clause_list != NIL);
 	Assert(root->group_expr_list != NIL);
 
 	/* nothing to do for dummy rel */
@@ -2723,11 +2722,7 @@ create_rel_agg_info(PlannerInfo *root, RelOptInfo *rel,
 	List	   *group_clauses = NIL;
 	List	   *group_exprs = NIL;
 
-	/*
-	 * The lists of aggregate expressions and grouping expressions should have
-	 * been constructed.
-	 */
-	Assert(root->agg_clause_list != NIL);
+	/* The list of grouping expressions should have been constructed. */
 	Assert(root->group_expr_list != NIL);
 
 	/*
@@ -3089,7 +3084,8 @@ init_grouping_targets(PlannerInfo *root, RelOptInfo *rel,
 			*group_clauses = lappend(*group_clauses, sgc);
 			*group_exprs = lappend(*group_exprs, expr);
 		}
-		else if (is_var_in_aggref_only(root, (Var *) expr))
+		else if (root->eager_agg_mode == EAGER_AGG_PARTIAL &&
+				 is_var_in_aggref_only(root, (Var *) expr))
 		{
 			/*
 			 * The expression is referenced by an aggregate function pushed
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index c48e656ce80..b5f856e9327 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -153,6 +153,18 @@ typedef enum UpperRelationKind
 	/* NB: UPPERREL_FINAL must be last enum entry; it's used to size arrays */
 } UpperRelationKind;
 
+/*
+ * This enum identifies what eager aggregation pushes below the joins, if
+ * anything.  A partial aggregate emits transition values; a deduplication
+ * emits final rows.
+ */
+typedef enum EagerAggMode
+{
+	EAGER_AGG_NONE = 0,			/* eager aggregation does not apply */
+	EAGER_AGG_PARTIAL,			/* partial aggregates are pushed down */
+	EAGER_AGG_DEDUP,			/* a plain deduplication is pushed down */
+} EagerAggMode;
+
 /*----------
  * PlannerGlobal
  *		Global information for planning/optimization
@@ -501,6 +513,9 @@ struct PlannerInfo
 	/* list of GroupingExprInfos */
 	List	   *group_expr_list;
 
+	/* what eager aggregation pushes below the joins, if anything */
+	EagerAggMode eager_agg_mode;
+
 	/* list of plain Vars contained in targetlist and havingQual */
 	List	   *tlist_vars;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 15b10e1703f..2eaace8e529 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -734,6 +734,7 @@ EVP_MD
 EVP_MD_CTX
 EVP_PKEY
 EachState
+EagerAggMode
 Edge
 EditableObjectType
 ElementsState

base-commit: 92819e57945d106317eaaa4273180dd8663f91bd
