From 1ba171ac9db1b7994aaf4115001e6e761fce3808 Mon Sep 17 00:00:00 2001 From: David Rowley Date: Tue, 25 Aug 2026 20:23:54 +1200 Subject: [PATCH v1] Fix incorrect multi-column RANGE partition pruning When performing partition pruning with a RANGE partitioned table where the pruning quals are only present for a leading prefix of the partition key and when using a <= operator, it was possible that partition pruning would accidentally prune away some partitions which shouldn't be pruned when those partitions matched the given qual. This happened due to an incorrectly coded loop bound which was checking the bound was within the required range. The loop failed to consider that the loop body would adjust the bound offset to a value within range. Here we fix this by moving the loop condition so we break out of the loop if the adjusted offset is not within the required range. p: partition by range (a, b); p1: for values from (1, 4) to (1, 7); p2: for values from (1, 7) to (3, 8); p3: for values from (4, 8) to (6, 9); def: default; select * from p where a <= 1; Here p2 was pruned by mistake. --- src/backend/partitioning/partprune.c | 9 +++++++-- src/test/regress/expected/partition_prune.out | 20 +++++++++++++++++++ src/test/regress/sql/partition_prune.sql | 11 ++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 06566c8ce8a..95ba62fa067 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -3265,16 +3265,21 @@ get_matching_range_bounds(PartitionPruneContext *context, if (off >= 0) { /* - * See the comment above. + * As above, check adjacent bounds to see if the bound is + * equal to the lookup value. */ if (is_equal && nvalues < partnatts) { - while (off >= 1 && off < boundinfo->ndatums - 1) + while (true) { int32 cmpval; int nextoff; nextoff = inclusive ? off + 1 : off - 1; + + if (nextoff < 1 || nextoff >= boundinfo->ndatums - 1) + break; + cmpval = partition_rbound_datum_cmp(partsupfunc, partcollation, boundinfo->datums[nextoff], diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index aa821646011..64977836295 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -1099,6 +1099,26 @@ explain (costs off) select * from mc2p where b is null; Filter: (b IS NULL) (2 rows) +create table mc2ap (a int, b int) partition by range (a, b); +create table mc2ap1 partition of mc2ap for values from (1, 4) to (1, 7); +create table mc2ap2 partition of mc2ap for values from (1, 7) to (3, 8); +create table mc2ap3 partition of mc2ap for values from (4, 8) to (6, 9); +create table mc2ap_def partition of mc2ap default; +-- Ensure we scan all partitions apart from mc2ap3 +explain (costs off) select count(*) from mc2ap where a <= 1; + QUERY PLAN +------------------------------------------- + Aggregate + -> Append + -> Seq Scan on mc2ap1 mc2ap_1 + Filter: (a <= 1) + -> Seq Scan on mc2ap2 mc2ap_2 + Filter: (a <= 1) + -> Seq Scan on mc2ap_def mc2ap_3 + Filter: (a <= 1) +(8 rows) + +drop table mc2ap; -- boolean partitioning create table boolpart (a bool) partition by list (a); create table boolpart_default partition of boolpart default; diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index dac673ef80a..9d67aa2d874 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -192,6 +192,17 @@ explain (costs off) select * from mc2p where a is null and b = 1; explain (costs off) select * from mc2p where a is null; explain (costs off) select * from mc2p where b is null; +create table mc2ap (a int, b int) partition by range (a, b); +create table mc2ap1 partition of mc2ap for values from (1, 4) to (1, 7); +create table mc2ap2 partition of mc2ap for values from (1, 7) to (3, 8); +create table mc2ap3 partition of mc2ap for values from (4, 8) to (6, 9); +create table mc2ap_def partition of mc2ap default; + +-- Ensure we scan all partitions apart from mc2ap3 +explain (costs off) select count(*) from mc2ap where a <= 1; + +drop table mc2ap; + -- boolean partitioning create table boolpart (a bool) partition by list (a); create table boolpart_default partition of boolpart default; -- 2.53.0