From 5c4eab98178b7ae3ad71b27c5d0d0b1db2bf207c Mon Sep 17 00:00:00 2001 From: Matheus Alcantara Date: Mon, 31 Aug 2026 10:49:50 -0300 Subject: [PATCH v7 4/4] Enable full partitionwise aggregate for partition keys wrapped by RelabelType group_by_has_partkey() decides whether a partitioned aggregate can be computed independently per partition (PARTITIONWISE_AGGREGATE_FULL) or whether partial aggregates have to be combined afterwards (PARTITIONWISE_AGGREGATE_PARTIAL). It does so by matching each partition key expression against the GROUP BY expressions with equal(). The grouping expressions have their RelabelType decorations stripped before that comparison, but the partition key expressions do not. When the partition key is an expression involving a binary-compatible cast, for example PARTITION BY LIST ((col::text)) on a varchar column, the stored partition key expression is itself a RelabelType, so no GROUP BY expression can ever match it: grouping by the column compares a bare Var against a RelabelType, and spelling out the cast strips the grouping side down to a bare Var while the partition key side stays wrapped. Full partitionwise aggregation is therefore never chosen for such a key, and the plan falls back to partial aggregation with a finalize step on top. Fix by stripping RelabelType from the partition key expression as well. Collation correctness is unaffected: the partition collation comes from the PartitionScheme rather than from the expression, and the grouping expression's collation is captured before it is stripped, so the existing comparison of the two is unchanged. Discussion: https://postgr.es/m/DEYVEFEFMSDC.23KLLYQ3F81R9@gmail.com --- src/backend/optimizer/plan/planner.c | 11 ++++ .../regress/expected/partition_aggregate.out | 55 +++++++++++++++++++ src/test/regress/sql/partition_aggregate.sql | 20 +++++++ 3 files changed, 86 insertions(+) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index c3c158a253d..d997e877a5e 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -8534,6 +8534,17 @@ group_by_has_partkey(RelOptInfo *input_rel, Expr *partexpr = lfirst(lc); Oid partcoll = input_rel->part_scheme->partcollation[cnt]; + /* + * Strip any RelabelType decorations, to match the stripping done + * on the grouping expressions below. A partition key involving a + * binary-compatible cast, such as ((col::text)) on a varchar + * column, is itself stored wrapped in a RelabelType. The + * collation is not lost, since partcoll and groupcoll are + * compared separately below. + */ + while (partexpr && IsA(partexpr, RelabelType)) + partexpr = ((RelabelType *) partexpr)->arg; + foreach(lg, groupexprs) { Expr *groupexpr = lfirst(lg); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index c30304b99c7..23d657c30a8 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -961,6 +961,61 @@ SELECT a, c, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY (a+b)/2, 2, 1 HAV 20 | 40 | 50 | 40.0000000000000000 | 5 (6 rows) +-- Partition by an expression that is a binary-compatible cast, so that the +-- stored partition key expression is itself wrapped in a RelabelType +CREATE TABLE pagg_tab_v (a int, c varchar(40)) PARTITION BY LIST ((c::text)); +CREATE TABLE pagg_tab_v_p1 PARTITION OF pagg_tab_v FOR VALUES IN ('0000', '0001'); +CREATE TABLE pagg_tab_v_p2 PARTITION OF pagg_tab_v FOR VALUES IN ('0002'); +INSERT INTO pagg_tab_v SELECT i, to_char(i % 3, 'FM0000') FROM generate_series(0, 2999) i; +ANALYZE pagg_tab_v; +-- Full aggregation as GROUP BY clause matches with PARTITION KEY +EXPLAIN (COSTS OFF) +SELECT c, sum(a), count(*) FROM pagg_tab_v GROUP BY c ORDER BY 1; + QUERY PLAN +---------------------------------------------------------- + Sort + Sort Key: pagg_tab_v.c + -> Append + -> HashAggregate + Group Key: pagg_tab_v.c + -> Seq Scan on pagg_tab_v_p1 pagg_tab_v + -> HashAggregate + Group Key: pagg_tab_v_1.c + -> Seq Scan on pagg_tab_v_p2 pagg_tab_v_1 +(9 rows) + +SELECT c, sum(a), count(*) FROM pagg_tab_v GROUP BY c ORDER BY 1; + c | sum | count +------+---------+------- + 0000 | 1498500 | 1000 + 0001 | 1499500 | 1000 + 0002 | 1500500 | 1000 +(3 rows) + +-- Full aggregation also when the GROUP BY clause spells out the cast +EXPLAIN (COSTS OFF) +SELECT c::text, sum(a), count(*) FROM pagg_tab_v GROUP BY c::text ORDER BY 1; + QUERY PLAN +---------------------------------------------------------- + Sort + Sort Key: ((pagg_tab_v.c)::text) + -> Append + -> HashAggregate + Group Key: (pagg_tab_v.c)::text + -> Seq Scan on pagg_tab_v_p1 pagg_tab_v + -> HashAggregate + Group Key: (pagg_tab_v_1.c)::text + -> Seq Scan on pagg_tab_v_p2 pagg_tab_v_1 +(9 rows) + +SELECT c::text, sum(a), count(*) FROM pagg_tab_v GROUP BY c::text ORDER BY 1; + c | sum | count +------+---------+------- + 0000 | 1498500 | 1000 + 0001 | 1499500 | 1000 + 0002 | 1500500 | 1000 +(3 rows) + -- Test with multi-level partitioning scheme CREATE TABLE pagg_tab_ml (a int, b int, c text) PARTITION BY RANGE(a); CREATE TABLE pagg_tab_ml_p1 PARTITION OF pagg_tab_ml FOR VALUES FROM (0) TO (12); diff --git a/src/test/regress/sql/partition_aggregate.sql b/src/test/regress/sql/partition_aggregate.sql index 7c725e2663a..77566fbf146 100644 --- a/src/test/regress/sql/partition_aggregate.sql +++ b/src/test/regress/sql/partition_aggregate.sql @@ -208,6 +208,26 @@ SELECT a, c, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY (a+b)/2, 2, 1 HAV SELECT a, c, sum(b), avg(c), count(*) FROM pagg_tab_m GROUP BY (a+b)/2, 2, 1 HAVING sum(b) = 50 AND avg(c) > 25 ORDER BY 1, 2, 3; +-- Partition by an expression that is a binary-compatible cast, so that the +-- stored partition key expression is itself wrapped in a RelabelType + +CREATE TABLE pagg_tab_v (a int, c varchar(40)) PARTITION BY LIST ((c::text)); +CREATE TABLE pagg_tab_v_p1 PARTITION OF pagg_tab_v FOR VALUES IN ('0000', '0001'); +CREATE TABLE pagg_tab_v_p2 PARTITION OF pagg_tab_v FOR VALUES IN ('0002'); +INSERT INTO pagg_tab_v SELECT i, to_char(i % 3, 'FM0000') FROM generate_series(0, 2999) i; +ANALYZE pagg_tab_v; + +-- Full aggregation as GROUP BY clause matches with PARTITION KEY +EXPLAIN (COSTS OFF) +SELECT c, sum(a), count(*) FROM pagg_tab_v GROUP BY c ORDER BY 1; +SELECT c, sum(a), count(*) FROM pagg_tab_v GROUP BY c ORDER BY 1; + +-- Full aggregation also when the GROUP BY clause spells out the cast +EXPLAIN (COSTS OFF) +SELECT c::text, sum(a), count(*) FROM pagg_tab_v GROUP BY c::text ORDER BY 1; +SELECT c::text, sum(a), count(*) FROM pagg_tab_v GROUP BY c::text ORDER BY 1; + + -- Test with multi-level partitioning scheme CREATE TABLE pagg_tab_ml (a int, b int, c text) PARTITION BY RANGE(a); -- 2.50.1 (Apple Git-155)