From 1b2c40bf66106d9bd6532eb52f4ac496b6dbebd3 Mon Sep 17 00:00:00 2001 From: William Bernbaum 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