From 95cb0af42d75d23050b2f5ce01325863b7583e5c Mon Sep 17 00:00:00 2001 From: "Chao Li (Evan)" Date: Thu, 14 May 2026 13:55:37 +0800 Subject: [PATCH v2 4/4] Reject degenerate SPLIT PARTITION with DEFAULT partition ALTER TABLE ... SPLIT PARTITION allows a DEFAULT partition to be created as one of the replacement partitions when the parent table does not already have one. However, it should not allow the degenerate case where a non-DEFAULT partition keeps exactly the same bound as the split partition and the command merely adds a DEFAULT partition through the SPLIT PARTITION path. Detect that case by comparing the bound of the split partition with the bound of the only non-DEFAULT replacement partition, and raise an error when they are the same. Users should add a DEFAULT partition directly with CREATE TABLE ... PARTITION OF ... DEFAULT or ALTER TABLE ... ATTACH PARTITION ... DEFAULT instead. Author: Chao Li Reviewed-by: Discussion: https://postgr.es/m/C18878AB-DEB2-4A61-9995-A035DD644B81@gmail.com --- src/backend/partitioning/partbounds.c | 69 +++++++++++++++++++ src/test/regress/expected/partition_split.out | 18 +++++ src/test/regress/sql/partition_split.sql | 16 +++++ 3 files changed, 103 insertions(+) diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c index 19589dc687f..5c1919e6d10 100644 --- a/src/backend/partitioning/partbounds.c +++ b/src/backend/partitioning/partbounds.c @@ -36,6 +36,7 @@ #include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" #include "utils/partcache.h" #include "utils/ruleutils.h" #include "utils/snapmgr.h" @@ -5700,6 +5701,70 @@ check_parent_values_in_new_partitions(Relation parent, } } +/* + * check_split_partition_not_same_bound + * + * Reject splitting a non-DEFAULT partition into one non-DEFAULT partition + * with the original bound plus a DEFAULT partition. That form does not + * perform a real split; it merely adds a DEFAULT partition to the parent + * table through the split-partition path. Users should use + * CREATE TABLE ... PARTITION OF ... DEFAULT or ALTER TABLE ... ATTACH + * PARTITION ... DEFAULT for that. + */ +static void +check_split_partition_not_same_bound(Relation parent, + Oid splitPartOid, + SinglePartitionSpec **parts, + int nparts, + ParseState *pstate) +{ + PartitionKey key = RelationGetPartitionKey(parent); + PartitionBoundSpec *split_spec; + PartitionBoundSpec *new_specs[1]; + PartitionBoundSpec *old_specs[1]; + PartitionBoundInfo new_boundinfo; + PartitionBoundInfo old_boundinfo; + int *new_mapping; + int *old_mapping; + MemoryContext old_cxt; + MemoryContext tmp_cxt; + bool same_bound; + + if (nparts != 1) + return; + + tmp_cxt = AllocSetContextCreate(CurrentMemoryContext, + "split partition bound comparison", + ALLOCSET_SMALL_SIZES); + old_cxt = MemoryContextSwitchTo(tmp_cxt); + + split_spec = get_partition_bound_spec(splitPartOid); + + new_specs[0] = parts[0]->bound; + new_boundinfo = partition_bounds_create(new_specs, 1, key, &new_mapping); + + old_specs[0] = split_spec; + old_boundinfo = partition_bounds_create(old_specs, 1, key, &old_mapping); + + same_bound = partition_bounds_equal(key->partnatts, key->parttyplen, + key->parttypbyval, + new_boundinfo, old_boundinfo); + + MemoryContextSwitchTo(old_cxt); + MemoryContextDelete(tmp_cxt); + + if (!same_bound) + return; + + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot split partition \"%s\" only to add a DEFAULT partition", + get_rel_name(splitPartOid)), + errdetail("The non-DEFAULT partition would keep the same partition bound."), + errhint("Use CREATE TABLE ... PARTITION OF ... DEFAULT to add a DEFAULT partition."), + parser_errposition(pstate, parts[0]->name->location)); +} + /* * check_partitions_for_split * @@ -5774,6 +5839,10 @@ check_partitions_for_split(Relation parent, Assert(nparts == list_length(partlist) - 1); } + if (!isSplitPartDefault && createDefaultPart) + check_split_partition_not_same_bound(parent, splitPartOid, new_parts, + nparts, pstate); + if (strategy == PARTITION_STRATEGY_RANGE) { PartitionRangeBound **lower_bounds; diff --git a/src/test/regress/expected/partition_split.out b/src/test/regress/expected/partition_split.out index d4f536c5e00..6869a65badb 100644 --- a/src/test/regress/expected/partition_split.out +++ b/src/test/regress/expected/partition_split.out @@ -1188,6 +1188,24 @@ SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text DROP TABLE sales_range; -- +-- Test that SPLIT PARTITION rejects the degenerate case where the only +-- non-DEFAULT replacement partition keeps the original bound and the command +-- merely adds a DEFAULT partition. +-- +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_50 PARTITION OF t FOR VALUES FROM (0) TO (50); +INSERT INTO t VALUES (1); +-- ERROR +ALTER TABLE t SPLIT PARTITION tp_0_50 INTO + (PARTITION tp_0_50 FOR VALUES FROM (0) TO (50), + PARTITION tp_default DEFAULT); +ERROR: cannot split partition "tp_0_50" only to add a DEFAULT partition +LINE 2: (PARTITION tp_0_50 FOR VALUES FROM (0) TO (50), + ^ +DETAIL: The non-DEFAULT partition would keep the same partition bound. +HINT: Use CREATE TABLE ... PARTITION OF ... DEFAULT to add a DEFAULT partition. +DROP TABLE t; +-- -- Test that the explicit partition bound cannot extend outside the split -- partition's bound when a DEFAULT partition is specified. -- diff --git a/src/test/regress/sql/partition_split.sql b/src/test/regress/sql/partition_split.sql index d9821c5e2a3..e7bbcc9f054 100644 --- a/src/test/regress/sql/partition_split.sql +++ b/src/test/regress/sql/partition_split.sql @@ -834,6 +834,22 @@ SELECT tableoid::regclass, * FROM sales_range ORDER BY tableoid::regclass::text DROP TABLE sales_range; +-- +-- Test that SPLIT PARTITION rejects the degenerate case where the only +-- non-DEFAULT replacement partition keeps the original bound and the command +-- merely adds a DEFAULT partition. +-- +CREATE TABLE t (i int) PARTITION BY RANGE (i); +CREATE TABLE tp_0_50 PARTITION OF t FOR VALUES FROM (0) TO (50); +INSERT INTO t VALUES (1); + +-- ERROR +ALTER TABLE t SPLIT PARTITION tp_0_50 INTO + (PARTITION tp_0_50 FOR VALUES FROM (0) TO (50), + PARTITION tp_default DEFAULT); + +DROP TABLE t; + -- -- Test that the explicit partition bound cannot extend outside the split -- partition's bound when a DEFAULT partition is specified. -- 2.50.1 (Apple Git-155)