}
}
-/*
- * 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
*
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);
}
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.
--
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.