From 6bcd3f5840fdd7a9e613f140a3da8750ae595636 Mon Sep 17 00:00:00 2001 From: Jan Nidzwetzki Date: Fri, 28 Aug 2026 15:03:59 +0200 Subject: [PATCH v6 3/3] Enable partitionwise join for outer joins on RelabelType-wrapped keys Strip RelabelType decorations in match_expr_to_partition_keys() to support partitionwise join for outer joins on RelabelType-wrapped keys. --- src/backend/optimizer/util/relnode.c | 27 ++++++++++--- src/test/regress/expected/partition_join.out | 41 ++++++++++++++++++++ src/test/regress/sql/partition_join.sql | 23 +++++++++++ 3 files changed, 86 insertions(+), 5 deletions(-) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index ee69f81945f..1d45a013acf 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -2480,6 +2480,18 @@ have_partkey_equi_join(PlannerInfo *root, RelOptInfo *joinrel, return false; } +/* + * strip_relabel_decorations + * Remove any RelabelType decorations from "expr". + */ +static Expr * +strip_relabel_decorations(Expr *expr) +{ + while (expr && IsA(expr, RelabelType)) + expr = ((RelabelType *) expr)->arg; + return expr; +} + /* * match_expr_to_partition_keys * @@ -2501,9 +2513,14 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) Assert(rel->partexprs); Assert(rel->nullable_partexprs); - /* Remove any relabel decorations. */ - while (IsA(expr, RelabelType)) - expr = (Expr *) (castNode(RelabelType, expr))->arg; + /* + * Remove any relabel decorations, from the clause expression here and + * from each partition key expression below. A key involving a + * binary-compatible cast is itself stored wrapped in a RelabelType. The + * collation is not lost, as the caller compares the clause's inputcollid + * against the partition collation. + */ + expr = strip_relabel_decorations(expr); for (cnt = 0; cnt < rel->part_scheme->partnatts; cnt++) { @@ -2512,7 +2529,7 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) /* We can always match to the non-nullable partition keys. */ foreach(lc, rel->partexprs[cnt]) { - if (equal(lfirst(lc), expr)) + if (equal(strip_relabel_decorations(lfirst(lc)), expr)) return cnt; } @@ -2528,7 +2545,7 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) */ foreach(lc, rel->nullable_partexprs[cnt]) { - if (equal(lfirst(lc), expr)) + if (equal(strip_relabel_decorations(lfirst(lc)), expr)) return cnt; } } diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 8a122a6cc24..304de6e1b49 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1806,6 +1806,47 @@ SELECT count(*) FROM pht3 t1 JOIN pht4 t2 ON t1.d = t2.d WHERE t1.c = '0002' AND 40 (1 row) +RESET enable_hashjoin; +RESET enable_mergejoin; +-- outer join on a partition key that is an expression wrapped in a RelabelType +CREATE TABLE pht5 (a int, c varchar(40)) PARTITION BY HASH ((c::text)); +CREATE TABLE pht5_p1 PARTITION OF pht5 FOR VALUES WITH (MODULUS 2, REMAINDER 0); +CREATE TABLE pht5_p2 PARTITION OF pht5 FOR VALUES WITH (MODULUS 2, REMAINDER 1); +INSERT INTO pht5 SELECT i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE pht5; +CREATE TABLE pht6 (a int, c varchar(40)) PARTITION BY HASH ((c::text)); +CREATE TABLE pht6_p1 PARTITION OF pht6 FOR VALUES WITH (MODULUS 2, REMAINDER 0); +CREATE TABLE pht6_p2 PARTITION OF pht6 FOR VALUES WITH (MODULUS 2, REMAINDER 1); +INSERT INTO pht6 SELECT i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 3) i; +ANALYZE pht6; +-- avoid hash and merge joins, whose costs here are close enough to the +-- partitionwise nested loop's to make the test output unstable +SET enable_hashjoin = off; +SET enable_mergejoin = off; +EXPLAIN (COSTS OFF) +SELECT count(*) FROM pht5 t1 LEFT JOIN pht6 t2 ON t1.c::text = t2.c::text; + QUERY PLAN +-------------------------------------------------------------- + Aggregate + -> Append + -> Nested Loop Left Join + Join Filter: ((t1_1.c)::text = (t2_1.c)::text) + -> Seq Scan on pht5_p1 t1_1 + -> Materialize + -> Seq Scan on pht6_p1 t2_1 + -> Nested Loop Left Join + Join Filter: ((t1_2.c)::text = (t2_2.c)::text) + -> Seq Scan on pht5_p2 t1_2 + -> Materialize + -> Seq Scan on pht6_p2 t2_2 +(12 rows) + +SELECT count(*) FROM pht5 t1 LEFT JOIN pht6 t2 ON t1.c::text = t2.c::text; + count +------- + 5000 +(1 row) + RESET enable_hashjoin; RESET enable_mergejoin; -- test default partition behavior for range diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql index 2a423f14190..009cfc2452d 100644 --- a/src/test/regress/sql/partition_join.sql +++ b/src/test/regress/sql/partition_join.sql @@ -387,6 +387,29 @@ SELECT count(*) FROM pht3 t1 JOIN pht4 t2 ON t1.d = t2.d WHERE t1.c = '0002' AND RESET enable_hashjoin; RESET enable_mergejoin; +-- outer join on a partition key that is an expression wrapped in a RelabelType +CREATE TABLE pht5 (a int, c varchar(40)) PARTITION BY HASH ((c::text)); +CREATE TABLE pht5_p1 PARTITION OF pht5 FOR VALUES WITH (MODULUS 2, REMAINDER 0); +CREATE TABLE pht5_p2 PARTITION OF pht5 FOR VALUES WITH (MODULUS 2, REMAINDER 1); +INSERT INTO pht5 SELECT i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; +ANALYZE pht5; + +CREATE TABLE pht6 (a int, c varchar(40)) PARTITION BY HASH ((c::text)); +CREATE TABLE pht6_p1 PARTITION OF pht6 FOR VALUES WITH (MODULUS 2, REMAINDER 0); +CREATE TABLE pht6_p2 PARTITION OF pht6 FOR VALUES WITH (MODULUS 2, REMAINDER 1); +INSERT INTO pht6 SELECT i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 3) i; +ANALYZE pht6; + +-- avoid hash and merge joins, whose costs here are close enough to the +-- partitionwise nested loop's to make the test output unstable +SET enable_hashjoin = off; +SET enable_mergejoin = off; +EXPLAIN (COSTS OFF) +SELECT count(*) FROM pht5 t1 LEFT JOIN pht6 t2 ON t1.c::text = t2.c::text; +SELECT count(*) FROM pht5 t1 LEFT JOIN pht6 t2 ON t1.c::text = t2.c::text; +RESET enable_hashjoin; +RESET enable_mergejoin; + -- test default partition behavior for range ALTER TABLE prt1 DETACH PARTITION prt1_p3; ALTER TABLE prt1 ATTACH PARTITION prt1_p3 DEFAULT; -- 2.47.3