From 5b4baa1446a451b033a14496971aab56ef75a0b7 Mon Sep 17 00:00:00 2001 From: Dilip Kumar Date: Sun, 1 Nov 2026 06:11:09 +0000 Subject: [PATCH v3] Ignore invalid leaf indexes on partitions during CREATE INDEX When DefineIndex() creates an index on a partitioned table (without ONLY), it recurses to each partition and scans existing indexes on the partition to see if a matching, unattached index can be reused and attached to the newly created parent index. Previously, this check only compared the structural index definition via CompareIndexInfo() without checking pg_index.indisvalid. As a result, an invalid index left behind on a leaf partition by a previously failed CREATE [UNIQUE] INDEX CONCURRENTLY (with indisvalid = false and possibly indisready = false) would be attached as a child index, marking the parent partitioned index indisvalid = false and skipping index creation on the partition. In particular, for CREATE UNIQUE INDEX (or ALTER TABLE ADD UNIQUE / PRIMARY KEY), the command would report success even when the partition contained duplicate values, and when indisready = false on the reused leaf index, subsequent inserts and updates would not enforce the unique constraint either (since ExecInsertIndexTuples() skips !indisready indexes). Furthermore, once attached to the parent index, the broken child index could no longer be dropped directly with DROP INDEX due to its partition dependency. Fix this in DefineIndex() by skipping existing non-partitioned (RELKIND_INDEX) indexes on partitions where !indisvalid, so that a new valid index is built on the leaf partition instead. Partitioned indexes (RELKIND_PARTITIONED_INDEX) on sub-partitioned tables created with ON ONLY remain invalid until their own partitions are attached, so continue to allow attaching those and marking the parent index invalid. Reported-by: chunling qin Authored-by: Dilip Kumar Reviewed-by: Kirill Reshke Reviewed-by: shihao zhong --- doc/src/sgml/ref/create_index.sgml | 2 +- src/backend/commands/indexcmds.c | 14 +++++++ src/test/regress/expected/indexing.out | 57 +++++++++++++++++--------- src/test/regress/sql/indexing.sql | 30 +++++++++----- 4 files changed, 73 insertions(+), 30 deletions(-) diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgml index bb7505d171b..290aecfde48 100644 --- a/doc/src/sgml/ref/create_index.sgml +++ b/doc/src/sgml/ref/create_index.sgml @@ -759,7 +759,7 @@ Indexes: If no matching index exists, a new index will be created and automatically attached; the name of the new index in each partition will be determined as if no index name had been specified in the - command. + command. Invalid indexes on leaf partitions are skipped. If the ONLY option is specified, no recursion is done, and the index is marked invalid. (ALTER INDEX ... ATTACH PARTITION marks the index diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 5a0312fe772..f9a46fadea1 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -1458,6 +1458,20 @@ DefineIndex(ParseState *pstate, continue; cldidx = index_open(cldidxid, lockmode); + + /* + * Can't use an invalid regular index (e.g., left behind + * by a failed CREATE INDEX CONCURRENTLY). Partitioned + * indexes made with ON ONLY are invalid until their + * partitions are attached, so we still allow those. + */ + if (cldidx->rd_rel->relkind != RELKIND_PARTITIONED_INDEX && + !cldidx->rd_index->indisvalid) + { + index_close(cldidx, lockmode); + continue; + } + cldIdxInfo = BuildIndexInfo(cldidx); if (CompareIndexInfo(cldIdxInfo, indexInfo, cldidx->rd_indcollation, diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.out index 4a0a652e9f6..87c08e24546 100644 --- a/src/test/regress/expected/indexing.out +++ b/src/test/regress/expected/indexing.out @@ -1661,10 +1661,10 @@ select indexrelid::regclass, indisvalid, (5 rows) drop table parted_inval_tab; --- Check setup of indisvalid across a complex partition tree on index --- creation. If one index in a partition index is invalid, so should its --- partitioned index. -create table parted_isvalid_tab (a int, b int) partition by range (a); +-- Check that invalid leaf indexes are not selected when creating an index on +-- a partitioned table, while partitioned indexes created with ON ONLY on +-- sub-partitioned tables are still attached (marking the parent invalid). +create table parted_isvalid_tab (a int not null, b int) partition by range (a); create table parted_isvalid_tab_1 partition of parted_isvalid_tab for values from (1) to (10) partition by range (a); create table parted_isvalid_tab_2 partition of parted_isvalid_tab @@ -1673,27 +1673,46 @@ create table parted_isvalid_tab_11 partition of parted_isvalid_tab_1 for values from (1) to (5); create table parted_isvalid_tab_12 partition of parted_isvalid_tab_1 for values from (5) to (10); --- create an invalid index on one of the partitions. -insert into parted_isvalid_tab_11 values (1, 0); -create index concurrently parted_isvalid_idx_11 on parted_isvalid_tab_11 ((a/b)); -ERROR: division by zero --- The previous invalid index is selected, invalidating all the indexes up to --- the top-most parent. -create index parted_isvalid_idx on parted_isvalid_tab ((a/b)); +-- create an invalid index on one of the leaf partitions. +insert into parted_isvalid_tab_11 values (1, 1), (1, 1); +create unique index concurrently parted_isvalid_idx_11 on parted_isvalid_tab_11 (a); +ERROR: could not create unique index "parted_isvalid_idx_11" +DETAIL: Key (a)=(1) is duplicated. +-- The previous invalid index is skipped, so attempting to build a new index +-- on the partitioned table fails on the duplicate rows. +create unique index parted_isvalid_idx on parted_isvalid_tab (a); +ERROR: could not create unique index "parted_isvalid_tab_11_a_idx" +DETAIL: Key (a)=(1) is duplicated. +delete from parted_isvalid_tab_11; +-- Now creating the index on the partitioned table succeeds and creates new +-- valid indexes, discarding the invalid index created previously. +create unique index parted_isvalid_idx on parted_isvalid_tab (a); +-- Uniqueness is enforced on the partition: +insert into parted_isvalid_tab values (1, 1), (1, 2); +ERROR: duplicate key value violates unique constraint "parted_isvalid_tab_11_a_idx" +DETAIL: Key (a)=(1) already exists. +-- A partitioned index created with ON ONLY on a sub-partitioned table is +-- attached and invalidates the parent index until its partitions are attached. +alter table only parted_isvalid_tab_1 add primary key (a); +alter table parted_isvalid_tab add primary key (a); select indexrelid::regclass, indisvalid, indrelid::regclass, inhparent::regclass from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid) where indexrelid::regclass::text like 'parted_isvalid%' order by indexrelid::regclass::text collate "C"; - indexrelid | indisvalid | indrelid | inhparent --------------------------------+------------+-----------------------+------------------------------ - parted_isvalid_idx | f | parted_isvalid_tab | - parted_isvalid_idx_11 | f | parted_isvalid_tab_11 | parted_isvalid_tab_1_a_b_idx - parted_isvalid_tab_12_a_b_idx | t | parted_isvalid_tab_12 | parted_isvalid_tab_1_a_b_idx - parted_isvalid_tab_1_a_b_idx | f | parted_isvalid_tab_1 | parted_isvalid_idx - parted_isvalid_tab_2_a_b_idx | t | parted_isvalid_tab_2 | parted_isvalid_idx -(5 rows) + indexrelid | indisvalid | indrelid | inhparent +-----------------------------+------------+-----------------------+---------------------------- + parted_isvalid_idx | t | parted_isvalid_tab | + parted_isvalid_idx_11 | f | parted_isvalid_tab_11 | + parted_isvalid_tab_11_a_idx | t | parted_isvalid_tab_11 | parted_isvalid_tab_1_a_idx + parted_isvalid_tab_12_a_idx | t | parted_isvalid_tab_12 | parted_isvalid_tab_1_a_idx + parted_isvalid_tab_1_a_idx | t | parted_isvalid_tab_1 | parted_isvalid_idx + parted_isvalid_tab_1_pkey | f | parted_isvalid_tab_1 | parted_isvalid_tab_pkey + parted_isvalid_tab_2_a_idx | t | parted_isvalid_tab_2 | parted_isvalid_idx + parted_isvalid_tab_2_pkey | t | parted_isvalid_tab_2 | parted_isvalid_tab_pkey + parted_isvalid_tab_pkey | f | parted_isvalid_tab | +(9 rows) drop table parted_isvalid_tab; -- Check state of replica indexes when attaching a partition. diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sql index bbcfb365281..24ce5516493 100644 --- a/src/test/regress/sql/indexing.sql +++ b/src/test/regress/sql/indexing.sql @@ -909,10 +909,10 @@ select indexrelid::regclass, indisvalid, order by indexrelid::regclass::text collate "C"; drop table parted_inval_tab; --- Check setup of indisvalid across a complex partition tree on index --- creation. If one index in a partition index is invalid, so should its --- partitioned index. -create table parted_isvalid_tab (a int, b int) partition by range (a); +-- Check that invalid leaf indexes are not selected when creating an index on +-- a partitioned table, while partitioned indexes created with ON ONLY on +-- sub-partitioned tables are still attached (marking the parent invalid). +create table parted_isvalid_tab (a int not null, b int) partition by range (a); create table parted_isvalid_tab_1 partition of parted_isvalid_tab for values from (1) to (10) partition by range (a); create table parted_isvalid_tab_2 partition of parted_isvalid_tab @@ -921,12 +921,22 @@ create table parted_isvalid_tab_11 partition of parted_isvalid_tab_1 for values from (1) to (5); create table parted_isvalid_tab_12 partition of parted_isvalid_tab_1 for values from (5) to (10); --- create an invalid index on one of the partitions. -insert into parted_isvalid_tab_11 values (1, 0); -create index concurrently parted_isvalid_idx_11 on parted_isvalid_tab_11 ((a/b)); --- The previous invalid index is selected, invalidating all the indexes up to --- the top-most parent. -create index parted_isvalid_idx on parted_isvalid_tab ((a/b)); +-- create an invalid index on one of the leaf partitions. +insert into parted_isvalid_tab_11 values (1, 1), (1, 1); +create unique index concurrently parted_isvalid_idx_11 on parted_isvalid_tab_11 (a); +-- The previous invalid index is skipped, so attempting to build a new index +-- on the partitioned table fails on the duplicate rows. +create unique index parted_isvalid_idx on parted_isvalid_tab (a); +delete from parted_isvalid_tab_11; +-- Now creating the index on the partitioned table succeeds and creates new +-- valid indexes, discarding the invalid index created previously. +create unique index parted_isvalid_idx on parted_isvalid_tab (a); +-- Uniqueness is enforced on the partition: +insert into parted_isvalid_tab values (1, 1), (1, 2); +-- A partitioned index created with ON ONLY on a sub-partitioned table is +-- attached and invalidates the parent index until its partitions are attached. +alter table only parted_isvalid_tab_1 add primary key (a); +alter table parted_isvalid_tab add primary key (a); select indexrelid::regclass, indisvalid, indrelid::regclass, inhparent::regclass from pg_index idx left join -- 2.56.0.385.gac671c819a-goog