From 88bd855896f95c48f6303ae1feb54b5228484847 Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Wed, 4 Nov 2026 22:09:30 -0500 Subject: [PATCH v2 3/3] Reject CREATE INDEX while a partition has an incomplete detach CREATE INDEX on a partitioned table skipped partitions pending detach, yet marked the parent index valid. A snapshot that still sees such a partition could then insert duplicate keys into it through a unique index. Building the index on that partition does not work, because the rest of ALTER TABLE skips it too. Raise an error instead, as ALTER TABLE does for such a partition. Backpatch-through: 14 --- src/backend/commands/indexcmds.c | 23 +++++++++++++++++++ .../detach-partition-concurrently-3.out | 23 +++++++++++++++++++ .../detach-partition-concurrently-3.spec | 4 ++++ 3 files changed, 50 insertions(+) diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 5a0312fe772..06eb35f783d 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -740,6 +740,8 @@ DefineIndex(ParseState *pstate, partitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE; if (partitioned) { + PartitionDesc pd; + /* * Note: we check 'stmt->concurrent' rather than 'concurrent', so that * the error is thrown also for temporary tables. Seems better to be @@ -751,6 +753,27 @@ DefineIndex(ParseState *pstate, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot create index on partitioned table \"%s\" concurrently", RelationGetRelationName(rel)))); + + /* + * A partition pending detach is still visible to older snapshots, but + * it cannot be altered, so it would be left without the index. Ask + * for the detach to be completed first. + */ + pd = RelationGetPartitionDesc(rel, false); + if (pd->detached_exist) + { + for (int i = 0; i < pd->nparts; i++) + { + if (PartitionHasPendingDetach(pd->oids[i])) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot create index on partitioned table \"%s\"", + RelationGetRelationName(rel)), + errdetail("Partition \"%s\" has an incomplete detach.", + get_rel_name(pd->oids[i])), + errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation."))); + } + } } /* diff --git a/src/test/isolation/expected/detach-partition-concurrently-3.out b/src/test/isolation/expected/detach-partition-concurrently-3.out index 485068dbd81..9049ac76997 100644 --- a/src/test/isolation/expected/detach-partition-concurrently-3.out +++ b/src/test/isolation/expected/detach-partition-concurrently-3.out @@ -570,3 +570,26 @@ indisvalid t (1 row) + +starting permutation: s2snitch s1b s1s s2detach s1cancel s1c s1idxall +step s2snitch: INSERT INTO d3_pid SELECT pg_backend_pid(); +step s1b: BEGIN; +step s1s: SELECT * FROM d3_listp; +a +- +1 +(1 row) + +step s2detach: ALTER TABLE d3_listp DETACH PARTITION d3_listp1 CONCURRENTLY; +step s1cancel: SELECT pg_cancel_backend(pid) FROM d3_pid; +step s2detach: <... completed> +ERROR: canceling statement due to user request +step s1cancel: <... completed> +pg_cancel_backend +----------------- +t +(1 row) + +step s1c: COMMIT; +step s1idxall: CREATE INDEX d3_idx ON d3_listp (a); +ERROR: cannot create index on partitioned table "d3_listp" diff --git a/src/test/isolation/specs/detach-partition-concurrently-3.spec b/src/test/isolation/specs/detach-partition-concurrently-3.spec index 7b7b6fee1d6..07bc7752950 100644 --- a/src/test/isolation/specs/detach-partition-concurrently-3.spec +++ b/src/test/isolation/specs/detach-partition-concurrently-3.spec @@ -49,6 +49,7 @@ step s1idx { CREATE TABLE d3_listp3 PARTITION OF d3_listp FOR VALUES IN (3); step s1attach2 { ALTER INDEX d3_idx ATTACH PARTITION d3_listp2_a; } step s1attach3 { CREATE UNIQUE INDEX d3_listp3_a ON d3_listp3 (a); ALTER INDEX d3_idx ATTACH PARTITION d3_listp3_a; } +step s1idxall { CREATE INDEX d3_idx ON d3_listp (a); } step s1valid { SELECT indisvalid FROM pg_index WHERE indexrelid = 'd3_idx'::regclass; } session s2 @@ -106,3 +107,6 @@ permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1c s2begin s2detachfin # The index of a partition pending detach must not count toward validity. permutation s2snitch s1idx s1b s1s s2detach s1cancel(s2detach) s1c s1attach2 s1valid s1attach3 s1valid + +# CREATE INDEX is rejected while a partition has an incomplete detach. +permutation s2snitch s1b s1s s2detach s1cancel(s2detach) s1c s1idxall -- 2.37.1 (Apple Git-137.1)