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. *