From e959928e5f67671b766388c7ef58f978c686135d Mon Sep 17 00:00:00 2001 From: Ewan Young Date: Fri, 7 Aug 2026 02:28:06 +0800 Subject: [PATCH v1 2/2] Prune the default partition for an equality prefix ending at MAXVALUE When a pruning step constrains only a prefix of the partition key, get_matching_range_bounds() gathers every bound that shares that prefix. For the smallest such bound it already skips the offset below it if the bound is (prefix, MINVALUE), because no row carrying the prefix can sort below that point. The mirror case was missing: if the greatest matching bound is (prefix, MAXVALUE), no row carrying the prefix can sort above it either, so the offset beyond that bound need not be returned. When no partition covers the key space up there, returning it means scanning the default partition for nothing: CREATE TABLE t (a int, b int) PARTITION BY RANGE (a, b); CREATE TABLE t1 PARTITION OF t FOR VALUES FROM (13, 0) TO (19, MAXVALUE); CREATE TABLE td PARTITION OF t DEFAULT; SELECT * FROM t WHERE a = 19 AND b >= 5; Every row with a = 19 lives in t1, but td was scanned as well. Note that this is specific to the prefix being pinned by an equality step: for "a >= 19 AND b >= 5" the rows above the bound do qualify, and the default partition is still scanned. A query that constrains only the prefix, as in "a = 19" alone, also still scans it, since a NULL in a later key is routed to the default partition. This is a pruning improvement, not a correctness fix. --- src/backend/partitioning/partprune.c | 12 ++++++++++-- src/test/regress/expected/partition_prune.out | 19 +++++++++++++++++++ src/test/regress/sql/partition_prune.sql | 5 +++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index ab48ea7ff40..e562764e8fb 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -3144,9 +3144,17 @@ get_matching_range_bounds(PartitionPruneContext *context, /* * off + 1, then would be the offset of the greatest bound - * to be included in the result. + * to be included in the result. The exception is the + * mirror image of the MINVALUE case above: if the matched + * bound is exactly (prefix, MAXVALUE), no row carrying + * this prefix can sort above it, so the key space beyond + * it need not be considered. */ - maxoff = off + 1; + if (boundinfo->kind[off][nvalues] == + PARTITION_RANGE_DATUM_MAXVALUE) + maxoff = off; + else + maxoff = off + 1; } Assert(minoff >= 0 && maxoff >= 0); diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index cf6ed4efff5..1349b27e6b6 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -1148,6 +1148,25 @@ select * from mc2pdmax where a = 19 and b >= 5; 19 | 5 (1 row) +-- With the prefix pinned to the bound's first key, nothing carrying that +-- prefix can sort above (19, maxvalue), so the default partition is not needed +explain (costs off) select * from mc2pdmax where a = 19 and b >= 5; + QUERY PLAN +----------------------------------- + Seq Scan on mc2pdmax1 mc2pdmax + Filter: ((b >= 5) AND (a = 19)) +(2 rows) + +explain (costs off) select * from mc2pdmax where a >= 19 and b >= 5; + QUERY PLAN +----------------------------------------------- + Append + -> Seq Scan on mc2pdmax1 mc2pdmax_1 + Filter: ((a >= 19) AND (b >= 5)) + -> Seq Scan on mc2pdmax_default mc2pdmax_2 + Filter: ((a >= 19) AND (b >= 5)) +(5 rows) + drop table mc2pdmax; create table mc2pdmin (a int, b int) partition by range (a, b); create table mc2pdmin_default partition of mc2pdmin default; diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index 5f3d4e104af..66ddd64a13f 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -210,6 +210,11 @@ select * from mc2pdmax where a >= 25 and b >= 0; insert into mc2pdmax values (19, 5); select * from mc2pdmax where a >= 19 and b >= 5; select * from mc2pdmax where a = 19 and b >= 5; + +-- With the prefix pinned to the bound's first key, nothing carrying that +-- prefix can sort above (19, maxvalue), so the default partition is not needed +explain (costs off) select * from mc2pdmax where a = 19 and b >= 5; +explain (costs off) select * from mc2pdmax where a >= 19 and b >= 5; drop table mc2pdmax; create table mc2pdmin (a int, b int) partition by range (a, b); -- 2.47.3