From 0ed5c5fddefa5fba5b6112a3741332d2d05ac272 Mon Sep 17 00:00:00 2001 From: Matheus Alcantara Date: Wed, 26 Aug 2026 15:45:56 -0300 Subject: [PATCH v5 1/2] Fix partition pruning for partition keys wrapped by RelabelType match_clause_to_partition_key() strips RelabelType decorations from the operands of the clause being matched, but not from the partition key expression itself. 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. The equal() comparisons against the stripped clause operands therefore never match, so no pruning steps are generated at all and every partition is scanned regardless of the clause. Fix by stripping the partition key expression the same way, once, up front. This covers all of the clause shapes handled by the function (OpExpr, ScalarArrayOpExpr, NullTest, and the Boolean clause forms recognized by match_boolean_partition_clause()), since they all receive the same partkey. Collation correctness does not depend on the partition key expression's exposed collation: for the operator clause forms it is established by checking the clause's input collation against the partition collation via PartCollMatchesExprColl(), and nullness and Boolean tests do not depend on collation at all. Reviewed-by: Jan Nidzwetzki Discussion: https://postgr.es/m/DEYVEFEFMSDC.23KLLYQ3F81R9@gmail.com --- src/backend/partitioning/partprune.c | 10 ++++ src/test/regress/expected/partition_prune.out | 51 +++++++++++++++++++ src/test/regress/sql/partition_prune.sql | 17 +++++++ 3 files changed, 78 insertions(+) diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 06566c8ce8a..6560f8c93ab 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -1836,6 +1836,16 @@ match_clause_to_partition_key(GeneratePruningStepsContext *context, Expr *expr; bool notclause; + /* + * Strip any RelabelType from the partition key expression itself, to + * match the stripping already done below on the clause operands. + * Partition key expressions can be wrapped in a RelabelType. Collation + * correctness doesn't depend on keeping the RelabelType here, since it's + * separately verified below via PartCollMatchesExprColl(). + */ + while (IsA(partkey, RelabelType)) + partkey = ((const RelabelType *) partkey)->arg; + /* * Recognize specially shaped clauses that match a Boolean partition key. */ diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index aa821646011..b4bf8c1b83c 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -186,6 +186,57 @@ explain (costs off) select * from coll_pruning where a collate "POSIX" = 'a' col Filter: ((a)::text = 'a'::text COLLATE "POSIX") (7 rows) +-- the partition key expression involves a binary-compatible cast, so it is +-- stored wrapped in a RelabelType; that has to be ignored when matching +-- clauses to the partition key, else no pruning occurs +create table cast_pruning (a int, c varchar(40)) partition by list ((c::text)); +create table cast_pruning_a partition of cast_pruning for values in ('a'); +create table cast_pruning_b partition of cast_pruning for values in ('b'); +create table cast_pruning_null partition of cast_pruning for values in (null); +insert into cast_pruning values (1, 'a'), (2, 'b'), (3, null); +explain (costs off) select * from cast_pruning where c = 'a'; + QUERY PLAN +----------------------------------------- + Seq Scan on cast_pruning_a cast_pruning + Filter: ((c)::text = 'a'::text) +(2 rows) + +explain (costs off) select * from cast_pruning where c in ('a', 'b'); + QUERY PLAN +----------------------------------------------------- + Append + -> Seq Scan on cast_pruning_a cast_pruning_1 + Filter: ((c)::text = ANY ('{a,b}'::text[])) + -> Seq Scan on cast_pruning_b cast_pruning_2 + Filter: ((c)::text = ANY ('{a,b}'::text[])) +(5 rows) + +explain (costs off) select * from cast_pruning where c is null; + QUERY PLAN +-------------------------------------------- + Seq Scan on cast_pruning_null cast_pruning + Filter: (c IS NULL) +(2 rows) + +select * from cast_pruning where c = 'a'; + a | c +---+--- + 1 | a +(1 row) + +select * from cast_pruning where c in ('a', 'b') order by a; + a | c +---+--- + 1 | a + 2 | b +(2 rows) + +select * from cast_pruning where c is null; + a | c +---+--- + 3 | +(1 row) + create table rlp (a int, b varchar) partition by range (a); create table rlp_default partition of rlp default partition by list (a); create table rlp_default_default partition of rlp_default default; diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index dac673ef80a..80560144fa2 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -53,6 +53,23 @@ explain (costs off) select * from coll_pruning where a collate "C" = 'a' collate -- collation doesn't match the partitioning collation, no pruning occurs explain (costs off) select * from coll_pruning where a collate "POSIX" = 'a' collate "POSIX"; +-- the partition key expression involves a binary-compatible cast, so it is +-- stored wrapped in a RelabelType; that has to be ignored when matching +-- clauses to the partition key, else no pruning occurs +create table cast_pruning (a int, c varchar(40)) partition by list ((c::text)); +create table cast_pruning_a partition of cast_pruning for values in ('a'); +create table cast_pruning_b partition of cast_pruning for values in ('b'); +create table cast_pruning_null partition of cast_pruning for values in (null); +insert into cast_pruning values (1, 'a'), (2, 'b'), (3, null); + +explain (costs off) select * from cast_pruning where c = 'a'; +explain (costs off) select * from cast_pruning where c in ('a', 'b'); +explain (costs off) select * from cast_pruning where c is null; + +select * from cast_pruning where c = 'a'; +select * from cast_pruning where c in ('a', 'b') order by a; +select * from cast_pruning where c is null; + create table rlp (a int, b varchar) partition by range (a); create table rlp_default partition of rlp default partition by list (a); create table rlp_default_default partition of rlp_default default; -- 2.50.1 (Apple Git-155)