v2-0004-Reject-degenerate-SPLIT-PARTITION-with-DEFAULT-pa.patch
application/octet-stream
Filename: v2-0004-Reject-degenerate-SPLIT-PARTITION-with-DEFAULT-pa.patch
Type: application/octet-stream
Part: 3
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0004
Subject: Reject degenerate SPLIT PARTITION with DEFAULT partition
| File | + | − |
|---|---|---|
| src/backend/partitioning/partbounds.c | 69 | 0 |
| src/test/regress/expected/partition_split.out | 18 | 0 |
| src/test/regress/sql/partition_split.sql | 16 | 0 |
From 95cb0af42d75d23050b2f5ce01325863b7583e5c Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <lic@highgo.com>
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 <lic@highgo.com>
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)