From a124140b82ad54cdbfae9ba591040495d479b41b Mon Sep 17 00:00:00 2001 From: William Bernbaum Date: Wed, 26 Aug 2026 13:44:07 -0700 Subject: [PATCH v1 11/16] Compute aggregates above a pushed-down deduplication If the grouping keys come from the aggregates, eager aggregation pushes down a deduplication, and the aggregates stay above it. - create_ordinary_grouping_paths() aggregates the deduplicated rows as another path. - create_partial_grouping_paths() ignores them Also split copy_rel_without_paths() out of build_grouped_rel() --- src/backend/optimizer/path/allpaths.c | 5 ++-- src/backend/optimizer/plan/planmain.c | 1 + src/backend/optimizer/plan/planner.c | 38 +++++++++++++++++++++++++-- src/backend/optimizer/util/relnode.c | 38 ++++++++++++++++++--------- src/include/nodes/pathnodes.h | 3 +++ src/include/optimizer/pathnode.h | 1 + 6 files changed, 70 insertions(+), 16 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index a841a5ec45a..db3b69f60fa 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -3558,9 +3558,10 @@ generate_grouped_paths(PlannerInfo *root, RelOptInfo *grouped_rel, /* * Determine whether we should consider hash-based implementations of - * grouping. + * grouping. An ordered aggregate cannot be hashed. Under eager + * deduplication the aggregates stay above the join. */ - Assert(root->numOrderedAggs == 0); + Assert(root->numOrderedAggs == 0 || root->eager_dedup_only); can_hash = (agg_info->group_clauses != NIL && grouping_is_hashable(agg_info->group_clauses)); diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 7ebe8d966ed..ec30ee584b0 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -79,6 +79,7 @@ query_planner(PlannerInfo *root, root->agg_clause_list = NIL; root->group_expr_list = NIL; root->eager_group_clause = NIL; + root->eager_dedup_only = false; root->filter_only_rels = NULL; root->tlist_vars = NIL; root->fkey_list = NIL; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 750b430f495..69f769da050 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -4427,6 +4427,37 @@ create_ordinary_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, partially_grouped_rel, agg_costs, gd, extra); + /* + * Where the grouping keys come from the aggregates, eager aggregation may + * push a deduplication below the joins. Every aggregate here ignores how + * often a row arrives, so the deduplicated rows yield the same results as + * the join's rows. Aggregate them as a path. + */ + if (root->eager_dedup_only && + input_rel->grouped_rel != NULL && + !IS_DUMMY_REL(input_rel->grouped_rel) && + input_rel->grouped_rel->pathlist != NIL) + { + RelOptInfo *dedup_rel; + ListCell *lc; + + dedup_rel = copy_rel_without_paths(input_rel); + + foreach(lc, input_rel->grouped_rel->pathlist) + { + Path *path = (Path *) lfirst(lc); + + add_path(dedup_rel, + (Path *) create_projection_path(root, dedup_rel, + complete_dedup_path(path), + input_rel->reltarget)); + } + + set_cheapest(dedup_rel); + add_paths_to_grouping_rel(root, dedup_rel, grouped_rel, NULL, + agg_costs, gd, extra); + } + /* Give a helpful error if we failed to find any implementation */ if (grouped_rel->pathlist == NIL) ereport(ERROR, @@ -7675,9 +7706,12 @@ create_partial_grouping_paths(PlannerInfo *root, /* * Check whether any partially aggregated paths have been generated - * through eager aggregation. + * through eager aggregation. Where the grouping keys come from the + * aggregates, the pushdown is a deduplication rather than a partial + * aggregate, and create_ordinary_grouping_paths() aggregates it. */ - if (input_rel->grouped_rel && + if (!root->eager_dedup_only && + input_rel->grouped_rel && !IS_DUMMY_REL(input_rel->grouped_rel) && input_rel->grouped_rel->pathlist != NIL) eager_agg_rel = input_rel->grouped_rel; diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 6df6f2fc4d7..44de2cebb73 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -500,18 +500,7 @@ build_grouped_rel(PlannerInfo *root, RelOptInfo *rel) { RelOptInfo *grouped_rel; - grouped_rel = makeNode(RelOptInfo); - memcpy(grouped_rel, rel, sizeof(RelOptInfo)); - - /* - * clear path info - */ - grouped_rel->pathlist = NIL; - grouped_rel->ppilist = NIL; - grouped_rel->partial_pathlist = NIL; - grouped_rel->cheapest_startup_path = NULL; - grouped_rel->cheapest_total_path = NULL; - grouped_rel->cheapest_parameterized_paths = NIL; + grouped_rel = copy_rel_without_paths(rel); /* * clear partition info @@ -536,6 +525,31 @@ build_grouped_rel(PlannerInfo *root, RelOptInfo *rel) return grouped_rel; } +/* + * copy_rel_without_paths + * Flat copy a relation, leaving the copy's path lists empty. + * + * The copy shares everything else with the original, so a caller wanting + * different size estimates or partitioning must reset those itself. + */ +RelOptInfo * +copy_rel_without_paths(RelOptInfo *rel) +{ + RelOptInfo *newrel; + + newrel = makeNode(RelOptInfo); + memcpy(newrel, rel, sizeof(RelOptInfo)); + + newrel->pathlist = NIL; + newrel->ppilist = NIL; + newrel->partial_pathlist = NIL; + newrel->cheapest_startup_path = NULL; + newrel->cheapest_total_path = NULL; + newrel->cheapest_parameterized_paths = NIL; + + return newrel; +} + /* * find_base_rel * Find a base or otherrel relation entry, which must already exist. diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index e899c3bec37..6e8b8b4c300 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -504,6 +504,9 @@ struct PlannerInfo /* the SortGroupClauses the grouping expressions were derived from */ List *eager_group_clause; + /* true if a deduplication is pushed down, with the aggregates above it */ + bool eager_dedup_only; + /* base rels supplying nothing the query outputs, or NULL if none */ Relids filter_only_rels; diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index e8db321f92b..1f5a911c388 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -346,6 +346,7 @@ extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *build_simple_grouped_rel(PlannerInfo *root, RelOptInfo *rel); extern RelOptInfo *build_grouped_rel(PlannerInfo *root, RelOptInfo *rel); +extern RelOptInfo *copy_rel_without_paths(RelOptInfo *rel); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); extern RelOptInfo *find_base_rel_noerr(PlannerInfo *root, int relid); extern RelOptInfo *find_base_rel_ignore_join(PlannerInfo *root, int relid);