diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 0866a680d7c..af87807fa30 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -17462,22 +17462,31 @@ ATExecDetachPartition(Relation rel, RangeVar *name) foreach(cell, indexes) { Oid idxid = lfirst_oid(cell); + Oid parentidx; Relation idx; Oid constrOid; + Oid parentConstrOid; if (!has_superclass(idxid)) continue; - Assert((IndexGetRelation(get_partition_parent(idxid), false) == - RelationGetRelid(rel))); + parentidx = get_partition_parent(idxid); + Assert(IndexGetRelation(parentidx, false) == RelationGetRelid(rel)); idx = index_open(idxid, AccessExclusiveLock); IndexSetParentIndex(idx, InvalidOid); - /* If there's a constraint associated with the index, detach it too */ + /* + * If there's a constraint associated with the index, detach it too. + * Careful: it is possible for a constraint index in a partition to be + * the child of a non-constraint index, so verify whether the parent + * index does actually have a constraint. + */ constrOid = get_relation_idx_constraint_oid(RelationGetRelid(partRel), idxid); - if (OidIsValid(constrOid)) + parentConstrOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), + parentidx); + if (OidIsValid(parentConstrOid) && OidIsValid(constrOid)) ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid); index_close(idx, NoLock); diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 3e05efd7b5b..723c270ba26 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -172,6 +172,8 @@ quad_poly_tbl|t radix_text_tbl|t ramp|f real_city|f +regress_constr_partition1|t +regress_constr_partitioned|t road|t shighway|t slow_emp4000|f diff --git a/src/test/regress/input/constraints.source b/src/test/regress/input/constraints.source index d7996a5d837..47f2bfa0b4f 100644 --- a/src/test/regress/input/constraints.source +++ b/src/test/regress/input/constraints.source @@ -429,6 +429,46 @@ ALTER TABLE parted_fk_naming ATTACH PARTITION parted_fk_naming_1 FOR VALUES IN ( SELECT conname FROM pg_constraint WHERE conrelid = 'parted_fk_naming_1'::regclass AND contype = 'f'; DROP TABLE parted_fk_naming; +-- +-- Test various ways to create primary keys on partitions, linked to unique +-- indexes (without constraints) on the partitioned table. Ideally these should +-- fail, but we don't dare change released behavior, so instead cope with it at +-- DETACH time. +CREATE TEMP TABLE t (a integer, b integer) PARTITION BY HASH (a, b); +CREATE TEMP TABLE tp (a integer, b integer, PRIMARY KEY (a, b), UNIQUE (b, a)); +ALTER TABLE t ATTACH PARTITION tp FOR VALUES WITH (MODULUS 1, REMAINDER 0); +CREATE UNIQUE INDEX t_a_idx ON t (a, b); +CREATE UNIQUE INDEX t_b_idx ON t (b, a); +ALTER INDEX t_a_idx ATTACH PARTITION tp_pkey; +ALTER INDEX t_b_idx ATTACH PARTITION tp_b_a_key; +ALTER TABLE t DETACH PARTITION tp; +SELECT conname, conparentid, conislocal, coninhcount + FROM pg_constraint WHERE conname IN ('tp_pkey', 'tp_b_a_key'); +DROP TABLE t, tp; + +CREATE TEMP TABLE t (a integer) PARTITION BY LIST (a); +CREATE TEMP TABLE tp (a integer PRIMARY KEY); +CREATE UNIQUE INDEX t_a_idx ON t (a); +ALTER TABLE t ATTACH PARTITION tp FOR VALUES IN (1); +ALTER TABLE t DETACH PARTITION tp; +DROP TABLE t, tp; + +CREATE TEMP TABLE t (a integer) PARTITION BY LIST (a); +CREATE TEMP TABLE tp (a integer PRIMARY KEY); +CREATE UNIQUE INDEX t_a_idx ON ONLY t (a); +ALTER TABLE t ATTACH PARTITION tp FOR VALUES IN (1); +ALTER TABLE t DETACH PARTITION tp; +DROP TABLE t, tp; + +CREATE TABLE regress_constr_partitioned (a integer) PARTITION BY LIST (a); +CREATE TABLE regress_constr_partition1 PARTITION OF regress_constr_partitioned FOR VALUES IN (1); +ALTER TABLE regress_constr_partition1 ADD PRIMARY KEY (a); +CREATE UNIQUE INDEX ON regress_constr_partitioned (a); +BEGIN; +ALTER TABLE regress_constr_partitioned DETACH PARTITION regress_constr_partition1; +ROLLBACK; +-- Leave this one in funny state for pg_upgrade testing + -- test a HOT update that invalidates the conflicting tuple. -- the trigger should still fire and catch the violation diff --git a/src/test/regress/output/constraints.source b/src/test/regress/output/constraints.source index 5d60451b9a1..d0cbc70b1a6 100644 --- a/src/test/regress/output/constraints.source +++ b/src/test/regress/output/constraints.source @@ -598,6 +598,48 @@ SELECT conname FROM pg_constraint WHERE conrelid = 'parted_fk_naming_1'::regclas (1 row) DROP TABLE parted_fk_naming; +-- +-- Test various ways to create primary keys on partitions, linked to unique +-- indexes (without constraints) on the partitioned table. Ideally these should +-- fail, but we don't dare change released behavior, so instead cope with it at +-- DETACH time. +CREATE TEMP TABLE t (a integer, b integer) PARTITION BY HASH (a, b); +CREATE TEMP TABLE tp (a integer, b integer, PRIMARY KEY (a, b), UNIQUE (b, a)); +ALTER TABLE t ATTACH PARTITION tp FOR VALUES WITH (MODULUS 1, REMAINDER 0); +CREATE UNIQUE INDEX t_a_idx ON t (a, b); +CREATE UNIQUE INDEX t_b_idx ON t (b, a); +ALTER INDEX t_a_idx ATTACH PARTITION tp_pkey; +ALTER INDEX t_b_idx ATTACH PARTITION tp_b_a_key; +ALTER TABLE t DETACH PARTITION tp; +SELECT conname, conparentid, conislocal, coninhcount + FROM pg_constraint WHERE conname IN ('tp_pkey', 'tp_b_a_key'); + conname | conparentid | conislocal | coninhcount +------------+-------------+------------+------------- + tp_pkey | 0 | t | 0 + tp_b_a_key | 0 | t | 0 +(2 rows) + +DROP TABLE t, tp; +CREATE TEMP TABLE t (a integer) PARTITION BY LIST (a); +CREATE TEMP TABLE tp (a integer PRIMARY KEY); +CREATE UNIQUE INDEX t_a_idx ON t (a); +ALTER TABLE t ATTACH PARTITION tp FOR VALUES IN (1); +ALTER TABLE t DETACH PARTITION tp; +DROP TABLE t, tp; +CREATE TEMP TABLE t (a integer) PARTITION BY LIST (a); +CREATE TEMP TABLE tp (a integer PRIMARY KEY); +CREATE UNIQUE INDEX t_a_idx ON ONLY t (a); +ALTER TABLE t ATTACH PARTITION tp FOR VALUES IN (1); +ALTER TABLE t DETACH PARTITION tp; +DROP TABLE t, tp; +CREATE TABLE regress_constr_partitioned (a integer) PARTITION BY LIST (a); +CREATE TABLE regress_constr_partition1 PARTITION OF regress_constr_partitioned FOR VALUES IN (1); +ALTER TABLE regress_constr_partition1 ADD PRIMARY KEY (a); +CREATE UNIQUE INDEX ON regress_constr_partitioned (a); +BEGIN; +ALTER TABLE regress_constr_partitioned DETACH PARTITION regress_constr_partition1; +ROLLBACK; +-- Leave this one in funny state for pg_upgrade testing -- test a HOT update that invalidates the conflicting tuple. -- the trigger should still fire and catch the violation BEGIN;