From 090f76247159f9f69a96aa6872194ce9a4a48125 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Mon, 5 Oct 2026 07:44:37 +0900 Subject: [PATCH v1 1/3] Fix eager aggregation grouping on cross-type join keys When a Var of the relation to be partially aggregated is known equal to a GROUP BY expression through an EC, get_expression_sortgroupref() let it reuse that expression's SortGroupClause. If the two are of different types, as with an int2 grouping key joined to an int4 column, the Var was then grouped using the other type's equality operator, which could put unequal values into the same group and give wrong results. To fix, accept only EC members of the same type as the grouping expression. A cross-type Var is then treated like any other join key and gets a SortGroupClause built for its own type. Reported-by: Robert Haas Author: Richard Guo Discussion: https://postgr.es/m/CA+Tgmob7iSM9YkRM44VjUDuaCchW-fY54MV5njpTZTL9uNyV4w@mail.gmail.com Backpatch-through: 19 --- src/backend/optimizer/util/relnode.c | 4 ++ src/test/regress/expected/eager_aggregate.out | 44 +++++++++++++++++++ src/test/regress/sql/eager_aggregate.sql | 23 ++++++++++ 3 files changed, 71 insertions(+) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 8e2409ccd9d..36e7a7a9bd1 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -3329,6 +3329,10 @@ get_expression_sortgroupref(PlannerInfo *root, Expr *expr) !bms_is_member(((Var *) expr)->varno, ge_info->ec->ec_relids)) continue; + /* The grouping operators can't be applied to a cross-type member */ + if (exprType((Node *) expr) != exprType((Node *) ge_info->expr)) + continue; + /* * Scan the EquivalenceClass, looking for a match to the given * expression. We ignore child members here. diff --git a/src/test/regress/expected/eager_aggregate.out b/src/test/regress/expected/eager_aggregate.out index 091ae48a92b..9bcd4a26fad 100644 --- a/src/test/regress/expected/eager_aggregate.out +++ b/src/test/regress/expected/eager_aggregate.out @@ -556,6 +556,50 @@ GROUP BY t2.b ORDER BY t2.b; 9 | 100 (9 rows) +-- Ensure a join key of a different type than the grouping key is not grouped +-- using the grouping key's operators +CREATE TABLE eager_agg_s1 (a int2); +CREATE TABLE eager_agg_s2 (b int4, c double precision); +INSERT INTO eager_agg_s1 VALUES (5); +INSERT INTO eager_agg_s2 SELECT 5, 1 FROM generate_series(1, 100); +INSERT INTO eager_agg_s2 SELECT 65541, 1 FROM generate_series(1, 100); +ANALYZE eager_agg_s1, eager_agg_s2; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT s1.a, sum(s2.c) + FROM eager_agg_s1 s1 + JOIN eager_agg_s2 s2 ON s1.a = s2.b +GROUP BY s1.a; + QUERY PLAN +------------------------------------------------------------ + Finalize GroupAggregate + Output: s1.a, sum(s2.c) + Group Key: s1.a + -> Sort + Output: s1.a, (PARTIAL sum(s2.c)) + Sort Key: s1.a + -> Nested Loop + Output: s1.a, (PARTIAL sum(s2.c)) + Join Filter: (s1.a = s2.b) + -> Seq Scan on public.eager_agg_s1 s1 + Output: s1.a + -> Partial HashAggregate + Output: s2.b, PARTIAL sum(s2.c) + Group Key: s2.b + -> Seq Scan on public.eager_agg_s2 s2 + Output: s2.b, s2.c +(16 rows) + +SELECT s1.a, sum(s2.c) + FROM eager_agg_s1 s1 + JOIN eager_agg_s2 s2 ON s1.a = s2.b +GROUP BY s1.a; + a | sum +---+----- + 5 | 100 +(1 row) + +DROP TABLE eager_agg_s1; +DROP TABLE eager_agg_s2; DROP TABLE eager_agg_t1; DROP TABLE eager_agg_t2; DROP TABLE eager_agg_t3; diff --git a/src/test/regress/sql/eager_aggregate.sql b/src/test/regress/sql/eager_aggregate.sql index 7bca9c524da..105a6391734 100644 --- a/src/test/regress/sql/eager_aggregate.sql +++ b/src/test/regress/sql/eager_aggregate.sql @@ -203,6 +203,29 @@ SELECT t2.b, count(*) WHERE EXISTS (SELECT 1 FROM eager_agg_t1 t1 WHERE t1.b = t2.b) GROUP BY t2.b ORDER BY t2.b; +-- Ensure a join key of a different type than the grouping key is not grouped +-- using the grouping key's operators +CREATE TABLE eager_agg_s1 (a int2); +CREATE TABLE eager_agg_s2 (b int4, c double precision); +INSERT INTO eager_agg_s1 VALUES (5); +INSERT INTO eager_agg_s2 SELECT 5, 1 FROM generate_series(1, 100); +INSERT INTO eager_agg_s2 SELECT 65541, 1 FROM generate_series(1, 100); +ANALYZE eager_agg_s1, eager_agg_s2; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT s1.a, sum(s2.c) + FROM eager_agg_s1 s1 + JOIN eager_agg_s2 s2 ON s1.a = s2.b +GROUP BY s1.a; + +SELECT s1.a, sum(s2.c) + FROM eager_agg_s1 s1 + JOIN eager_agg_s2 s2 ON s1.a = s2.b +GROUP BY s1.a; + +DROP TABLE eager_agg_s1; +DROP TABLE eager_agg_s2; + DROP TABLE eager_agg_t1; DROP TABLE eager_agg_t2; DROP TABLE eager_agg_t3; -- 2.37.1 (Apple Git-137.1)