From e14a2354d9f7e44b0ffcaeaf187aa475e3e7d11b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Sun, 13 Sep 2026 14:06:00 +0200 Subject: [PATCH] Fail REPACK in presence of !indisready indexes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Like VACUUM FULL, non-concurrent REPACK would try to rebuild such indexes, which can sometimes succeed. Concurrent REPACK would however fail. The inconsistency is not good, so make them both throw an error quickly to force the user to make a decision on those indexes (most likely, drop them). Author: Álvaro Herrera Reported-by: Zsolt Parragi Suggested-by: Kyotaro Horiguchi Discussion: https://postgr.es/m/CAN4CZFO5A3YE0Dd-bn7eKrB20pECO3=U0wKg1z2rO=DxgWJJHQ@mail.gmail.com --- contrib/test_decoding/expected/repack.out | 16 +++++ contrib/test_decoding/sql/repack.sql | 8 +++ src/backend/commands/repack.c | 71 +++++++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/contrib/test_decoding/expected/repack.out b/contrib/test_decoding/expected/repack.out index 0d229050146..e7060a7e577 100644 --- a/contrib/test_decoding/expected/repack.out +++ b/contrib/test_decoding/expected/repack.out @@ -51,6 +51,22 @@ SELECT * FROM rpk_missing; (3 rows) DROP TABLE rpk_missing; +-- Verify handling of !indisready indexes +CREATE TABLE repack_conc_invidx (i int PRIMARY KEY, j int); +INSERT INTO repack_conc_invidx VALUES (1, 0), (2, 0); +CREATE UNIQUE INDEX CONCURRENTLY repack_conc_invidx_uq ON repack_conc_invidx (j); +ERROR: could not create unique index "repack_conc_invidx_uq" +DETAIL: Key (j)=(0) is duplicated. +CREATE INDEX CONCURRENTLY repack_conc_invalid_expr ON repack_conc_invidx ((1/j)); +ERROR: division by zero +REPACK repack_conc_invidx; +ERROR: cannot execute REPACK on relation "repack_conc_invidx" +DETAIL: Some invalid indexes cannot be processed correctly: "repack_conc_invidx_uq", "repack_conc_invalid_expr". +HINT: Use DROP INDEX or REINDEX. +REPACK (CONCURRENTLY) repack_conc_invidx; +ERROR: cannot execute REPACK on relation "repack_conc_invidx" +DETAIL: Some invalid indexes cannot be processed correctly: "repack_conc_invidx_uq", "repack_conc_invalid_expr". +HINT: Use DROP INDEX or REINDEX. -- Error cases for concurrent mode -- Doesn't like partitioned tables CREATE TABLE clstrpart (a int) PARTITION BY RANGE (a); diff --git a/contrib/test_decoding/sql/repack.sql b/contrib/test_decoding/sql/repack.sql index 6e51aedb72d..0107e0bf89d 100644 --- a/contrib/test_decoding/sql/repack.sql +++ b/contrib/test_decoding/sql/repack.sql @@ -33,6 +33,14 @@ REPACK (CONCURRENTLY) rpk_missing; SELECT * FROM rpk_missing; DROP TABLE rpk_missing; +-- Verify handling of !indisready indexes +CREATE TABLE repack_conc_invidx (i int PRIMARY KEY, j int); +INSERT INTO repack_conc_invidx VALUES (1, 0), (2, 0); +CREATE UNIQUE INDEX CONCURRENTLY repack_conc_invidx_uq ON repack_conc_invidx (j); +CREATE INDEX CONCURRENTLY repack_conc_invalid_expr ON repack_conc_invidx ((1/j)); +REPACK repack_conc_invidx; +REPACK (CONCURRENTLY) repack_conc_invidx; + -- Error cases for concurrent mode -- Doesn't like partitioned tables diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 83168a4e6f3..609e2c1d77e 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -159,6 +159,7 @@ static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, int options); static void check_concurrent_repack_requirements(Relation rel, Oid *ident_idx_p); +static void check_index_requirements(Relation rel, RepackCommand cmd); static void rebuild_relation(Relation OldHeap, Relation index, bool verbose, Oid ident_idx); static void copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, @@ -527,6 +528,14 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, if (concurrent) check_concurrent_repack_requirements(OldHeap, &ident_idx); + /* + * Also check the state of indexes; this can abort the command for REPACK. + * Historically this hasn't affected CLUSTER or VACUUM FULL, so don't do + * it for those commands. + */ + if (cmd == REPACK_COMMAND_REPACK) + check_index_requirements(OldHeap, cmd); + /* Check for user-requested abort. */ CHECK_FOR_INTERRUPTS(); @@ -874,6 +883,68 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal) table_close(pg_index, RowExclusiveLock); } +/* + * check_index_requirements: verify index state on relation being processed + * + * Throw an error if any !indisready indexes are found. + * + * Indexes that are not ready for inserts, such as ones left behind by failed + * CREATE INDEX CONCURRENTLY, are not maintained by DML. In some cases they + * may fail to build altogether. Throwing an error here forces the user to + * take action on these indexes separately from the table reconstruction, + * which prevents perpetuating them for no reason. + */ +static void +check_index_requirements(Relation rel, RepackCommand cmd) +{ + Relation indrel; + SysScanDesc indscan; + ScanKeyData skey; + HeapTuple htup; + int num_invalid_idxs = 0; + StringInfoData dest; + + initStringInfo(&dest); + + /* Prepare to scan pg_index for entries having indrelid = this rel. */ + ScanKeyInit(&skey, + Anum_pg_index_indrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(rel))); + + indrel = table_open(IndexRelationId, AccessShareLock); + indscan = systable_beginscan(indrel, IndexIndrelidIndexId, true, + NULL, 1, &skey); + + while (HeapTupleIsValid(htup = systable_getnext(indscan))) + { + Form_pg_index index = (Form_pg_index) GETSTRUCT(htup); + + if (!index->indisready) + { + Assert(!index->indisvalid); + if (num_invalid_idxs == 0) + appendStringInfo(&dest, _("\"%s\""), get_rel_name(index->indexrelid)); + else + appendStringInfo(&dest, _(", \"%s\""), get_rel_name(index->indexrelid)); + num_invalid_idxs++; + } + } + systable_endscan(indscan); + table_close(indrel, AccessShareLock); + + if (num_invalid_idxs > 0) + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot execute %s on relation \"%s\"", + RepackCommandAsString(cmd), RelationGetRelationName(rel)), + errdetail_plural("An invalid index cannot be processed correctly: %s.", + "Some invalid indexes cannot be processed correctly: %s.", + num_invalid_idxs, + dest.data), + errhint("Use DROP INDEX or REINDEX.")); +} + /* * Check if the CONCURRENTLY option is legal for the relation. *