]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Revert "Reject degenerate SPLIT PARTITION with DEFAULT partition"
authorAlexander Korotkov <akorotkov@postgresql.org>
Wed, 20 May 2026 20:23:49 +0000 (23:23 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Wed, 20 May 2026 20:23:49 +0000 (23:23 +0300)
This reverts commit d8af73010033cb8ad6c941942d6b03d74d7e4f7e.  Per buildfarm
failures.

src/backend/partitioning/partbounds.c
src/test/regress/expected/partition_split.out
src/test/regress/sql/partition_split.sql

index eac807ddb773ff17aaf348861f7464fd7c23a648..7d3580cbc10c2c14dd718cad0f6b894b43a7145c 100644 (file)
@@ -5700,146 +5700,6 @@ check_parent_values_in_new_partitions(Relation parent,
        }
 }
 
-/*
- * split_partition_values_contained_in_new_part
- *
- * (function for BY LIST partitioning)
- *
- * Returns true if all values in the LIST bound of the partition being split
- * are contained in the specified non-DEFAULT replacement partition's bound.
- *
- * The caller must already have verified containment in the other direction,
- * so this check is sufficient to prove that the two LIST bounds are equal.
- */
-static bool
-split_partition_values_contained_in_new_part(Relation parent,
-                                                                                        Oid splitPartOid,
-                                                                                        SinglePartitionSpec *part)
-{
-       PartitionKey key = RelationGetPartitionKey(parent);
-       PartitionDesc partdesc = RelationGetPartitionDesc(parent, false);
-       PartitionBoundInfo boundinfo = partdesc->boundinfo;
-       SinglePartitionSpec *parts[1];
-       Datum           datum = PointerGetDatum(NULL);
-
-       Assert(key->strategy == PARTITION_STRATEGY_LIST);
-
-       parts[0] = part;
-
-       /*
-        * Special processing for NULL value.  Search for a NULL value if the
-        * split partition contains it.
-        */
-       if (partition_bound_accepts_nulls(boundinfo) &&
-               partdesc->oids[boundinfo->null_index] == splitPartOid)
-       {
-               if (!find_value_in_new_partitions_list(&key->partsupfunc[0],
-                                                                                          key->partcollation, parts, 1,
-                                                                                          datum, true))
-                       return false;
-       }
-
-       /*
-        * Search all values of the split partition in the single non-DEFAULT
-        * replacement partition.
-        */
-       for (int i = 0; i < boundinfo->ndatums; i++)
-       {
-               if (partdesc->oids[boundinfo->indexes[i]] == splitPartOid)
-               {
-                       datum = boundinfo->datums[i][0];
-
-                       if (!find_value_in_new_partitions_list(&key->partsupfunc[0],
-                                                                                                  key->partcollation, parts, 1,
-                                                                                                  datum, false))
-                               return false;
-               }
-       }
-
-       return true;
-}
-
-/*
- * 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.
- *
- * Must be called after the per-partition bound validation in
- * check_partitions_for_split() so that containment of new bounds within the
- * split partition is already established.  Given containment, RANGE bounds
- * are equal iff their lower and upper rbounds match; LIST bound sets are
- * equal iff the split partition's values are also contained in the new
- * partition (the containment is then bidirectional).  Both checks honor
- * the partition key collation via the operator-family comparators
- * (partition_rbound_cmp / find_value_in_new_partitions_list), so e.g.
- * ('a','b') and ('A','B') under a case-insensitive ICU collation are
- * correctly recognised as the same bound.
- */
-static void
-check_split_partition_not_same_bound(Relation parent,
-                                                                        Oid splitPartOid,
-                                                                        SinglePartitionSpec **parts,
-                                                                        int nparts,
-                                                                        ParseState *pstate)
-{
-       PartitionKey key = RelationGetPartitionKey(parent);
-       PartitionBoundSpec *new_spec;
-       PartitionBoundSpec *split_spec;
-
-       if (nparts != 1)
-               return;
-
-       new_spec = parts[0]->bound;
-       split_spec = get_partition_bound_spec(splitPartOid);
-
-       Assert(new_spec->strategy == split_spec->strategy);
-
-       if (key->strategy == PARTITION_STRATEGY_RANGE)
-       {
-               PartitionRangeBound *new_lower;
-               PartitionRangeBound *new_upper;
-               PartitionRangeBound *split_lower;
-               PartitionRangeBound *split_upper;
-
-               new_lower = make_one_partition_rbound(key, -1, new_spec->lowerdatums, true);
-               new_upper = make_one_partition_rbound(key, -1, new_spec->upperdatums, false);
-               split_lower = make_one_partition_rbound(key, -1, split_spec->lowerdatums, true);
-               split_upper = make_one_partition_rbound(key, -1, split_spec->upperdatums, false);
-
-               if (partition_rbound_cmp(key->partnatts, key->partsupfunc,
-                                                                key->partcollation,
-                                                                new_lower->datums, new_lower->kind, true,
-                                                                split_lower) != 0)
-                       return;
-               if (partition_rbound_cmp(key->partnatts, key->partsupfunc,
-                                                                key->partcollation,
-                                                                new_upper->datums, new_upper->kind, false,
-                                                                split_upper) != 0)
-                       return;
-       }
-       else
-       {
-               Assert(key->strategy == PARTITION_STRATEGY_LIST);
-
-               if (!split_partition_values_contained_in_new_part(parent, splitPartOid,
-                                                                                                                 parts[0]))
-                       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
  *
@@ -6011,15 +5871,5 @@ check_partitions_for_split(Relation parent,
                                                                                                  new_parts, nparts, pstate);
        }
 
-       /*
-        * Reject the degenerate form where the single non-DEFAULT replacement
-        * partition keeps the bound of the split partition; the command then does
-        * nothing beyond adding a DEFAULT partition.  Containment was established
-        * by the per-partition validation above, so an equality check is enough.
-        */
-       if (!isSplitPartDefault && createDefaultPart)
-               check_split_partition_not_same_bound(parent, splitPartOid, new_parts,
-                                                                                        nparts, pstate);
-
        pfree(new_parts);
 }
index 2fd9aee1dcc124b65725fb70ace50921bc696e41..2b9a6aa50edce09fcb2e4429be775381f3ccdeb9 100644 (file)
@@ -1188,65 +1188,6 @@ 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 a LIST split with DEFAULT is not considered degenerate when
--- only NULL is removed from the explicit replacement partition.
---
-CREATE TABLE t (i int) PARTITION BY LIST (i);
-CREATE TABLE tp_null_1 PARTITION OF t FOR VALUES IN (NULL, 1);
-ALTER TABLE t SPLIT PARTITION tp_null_1 INTO
-  (PARTITION tp_1 FOR VALUES IN (1),
-   PARTITION tp_default DEFAULT);
-INSERT INTO t VALUES (NULL), (1), (2);
-SELECT tableoid::regclass, i FROM t ORDER BY tableoid::regclass::text COLLATE "C", i NULLS FIRST;
-  tableoid  | i 
-------------+---
- tp_1       | 1
- tp_default |  
- tp_default | 2
-(3 rows)
-
-DROP TABLE t;
---
--- Test that the same-bound check for LIST partitioning uses partition
--- comparison semantics, not raw list length.  The case-insensitive collation
--- treats 'a' and 'A' as equal, so the non-DEFAULT replacement partition
--- covers only the 'a' group and the DEFAULT partition covers the rest.
---
-CREATE COLLATION case_insensitive (provider = icu, locale = 'und-u-ks-level2', deterministic = false);
-CREATE TABLE t (b text COLLATE case_insensitive) PARTITION BY LIST (b);
-CREATE TABLE tp_ab PARTITION OF t FOR VALUES IN ('a', 'b');
-ALTER TABLE t SPLIT PARTITION tp_ab INTO
-  (PARTITION tp_a FOR VALUES IN ('a', 'A'),
-   PARTITION tp_default DEFAULT);
-INSERT INTO t VALUES ('a'), ('A'), ('b'), ('c');
-SELECT tableoid::regclass, count(*) FROM t GROUP BY 1 ORDER BY 1;
-  tableoid  | count 
-------------+-------
- tp_a       |     2
- tp_default |     2
-(2 rows)
-
-DROP TABLE t;
-DROP COLLATION case_insensitive;
---
 -- Test that the explicit partition bound cannot extend outside the split
 -- partition's bound when a DEFAULT partition is specified.
 --
index ede89ad02286edcdd1206efa596b9edb1a71932f..d9821c5e2a34a1c4a7a6310afe3f71fa1357f2eb 100644 (file)
@@ -834,58 +834,6 @@ 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 a LIST split with DEFAULT is not considered degenerate when
--- only NULL is removed from the explicit replacement partition.
---
-CREATE TABLE t (i int) PARTITION BY LIST (i);
-CREATE TABLE tp_null_1 PARTITION OF t FOR VALUES IN (NULL, 1);
-
-ALTER TABLE t SPLIT PARTITION tp_null_1 INTO
-  (PARTITION tp_1 FOR VALUES IN (1),
-   PARTITION tp_default DEFAULT);
-
-INSERT INTO t VALUES (NULL), (1), (2);
-SELECT tableoid::regclass, i FROM t ORDER BY tableoid::regclass::text COLLATE "C", i NULLS FIRST;
-
-DROP TABLE t;
-
---
--- Test that the same-bound check for LIST partitioning uses partition
--- comparison semantics, not raw list length.  The case-insensitive collation
--- treats 'a' and 'A' as equal, so the non-DEFAULT replacement partition
--- covers only the 'a' group and the DEFAULT partition covers the rest.
---
-CREATE COLLATION case_insensitive (provider = icu, locale = 'und-u-ks-level2', deterministic = false);
-CREATE TABLE t (b text COLLATE case_insensitive) PARTITION BY LIST (b);
-CREATE TABLE tp_ab PARTITION OF t FOR VALUES IN ('a', 'b');
-
-ALTER TABLE t SPLIT PARTITION tp_ab INTO
-  (PARTITION tp_a FOR VALUES IN ('a', 'A'),
-   PARTITION tp_default DEFAULT);
-
-INSERT INTO t VALUES ('a'), ('A'), ('b'), ('c');
-SELECT tableoid::regclass, count(*) FROM t GROUP BY 1 ORDER BY 1;
-
-DROP TABLE t;
-DROP COLLATION case_insensitive;
-
 --
 -- Test that the explicit partition bound cannot extend outside the split
 -- partition's bound when a DEFAULT partition is specified.