Diagnose !indisvalid in more SQL functions.
authorNoah Misch <[email protected]>
Mon, 30 Oct 2023 21:46:05 +0000 (14:46 -0700)
committerNoah Misch <[email protected]>
Mon, 30 Oct 2023 21:46:09 +0000 (14:46 -0700)
pgstatindex failed with ERRCODE_DATA_CORRUPTED, of the "can't-happen"
class XX.  The other functions succeeded on an empty index; they might
have malfunctioned if the failed index build left torn I/O or other
complex state.  Report an ERROR in statistics functions pgstatindex,
pgstatginindex, pgstathashindex, and pgstattuple.  Report DEBUG1 and
skip all index I/O in maintenance functions brin_desummarize_range,
brin_summarize_new_values, brin_summarize_range, and
gin_clean_pending_list.  Back-patch to v11 (all supported versions).

Discussion: https://postgr.es/m/20231001195309[email protected]

contrib/pgstattuple/pgstatindex.c
contrib/pgstattuple/pgstattuple.c
src/backend/access/brin/brin.c
src/backend/access/gin/ginfast.c

index 5368bb30f0c556593cbb14e56d3543763086b4f7..f3d714193c3b6bac525da9dc8e0bad8a71bc7442 100644 (file)
@@ -237,6 +237,18 @@ pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("cannot access temporary tables of other sessions")));
 
+   /*
+    * A !indisready index could lead to ERRCODE_DATA_CORRUPTED later, so exit
+    * early.  We're capable of assessing an indisready&&!indisvalid index,
+    * but the results could be confusing.  For example, the index's size
+    * could be too low for a valid index of the table.
+    */
+   if (!rel->rd_index->indisvalid)
+       ereport(ERROR,
+               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                errmsg("index \"%s\" is not valid",
+                       RelationGetRelationName(rel))));
+
    /*
     * Read metapage
     */
@@ -542,6 +554,13 @@ pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo)
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("cannot access temporary indexes of other sessions")));
 
+   /* see pgstatindex_impl */
+   if (!rel->rd_index->indisvalid)
+       ereport(ERROR,
+               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                errmsg("index \"%s\" is not valid",
+                       RelationGetRelationName(rel))));
+
    /*
     * Read metapage
     */
@@ -619,6 +638,13 @@ pgstathashindex(PG_FUNCTION_ARGS)
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("cannot access temporary indexes of other sessions")));
 
+   /* see pgstatindex_impl */
+   if (!rel->rd_index->indisvalid)
+       ereport(ERROR,
+               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                errmsg("index \"%s\" is not valid",
+                       RelationGetRelationName(rel))));
+
    /* Get the information we need from the metapage. */
    memset(&stats, 0, sizeof(stats));
    metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
index 21fdeff8afd54cb8d5442b4ca48166288eb58a5e..32ac4103ce6cb01590e54e0ab00a7f62a8256d40 100644 (file)
@@ -260,6 +260,13 @@ pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
        case RELKIND_SEQUENCE:
            return pgstat_heap(rel, fcinfo);
        case RELKIND_INDEX:
+           /* see pgstatindex_impl */
+           if (!rel->rd_index->indisvalid)
+               ereport(ERROR,
+                       (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                        errmsg("index \"%s\" is not valid",
+                               RelationGetRelationName(rel))));
+
            switch (rel->rd_rel->relam)
            {
                case BTREE_AM_OID:
index 3d894cc821f8a1e039086da0d96793161001be5b..8c6ee96ca81cc0cbdf04e36f4aa444b9f39d3b2b 100644 (file)
@@ -1105,8 +1105,14 @@ brin_summarize_range(PG_FUNCTION_ARGS)
                 errmsg("could not open parent table of index \"%s\"",
                        RelationGetRelationName(indexRel))));
 
-   /* OK, do it */
-   brinsummarize(indexRel, heapRel, heapBlk, true, &numSummarized, NULL);
+   /* see gin_clean_pending_list() */
+   if (indexRel->rd_index->indisvalid)
+       brinsummarize(indexRel, heapRel, heapBlk, true, &numSummarized, NULL);
+   else
+       ereport(DEBUG1,
+               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                errmsg("index \"%s\" is not valid",
+                       RelationGetRelationName(indexRel))));
 
    /* Roll back any GUC changes executed by index functions */
    AtEOXact_GUC(false, save_nestlevel);
@@ -1191,12 +1197,21 @@ brin_desummarize_range(PG_FUNCTION_ARGS)
                 errmsg("could not open parent table of index \"%s\"",
                        RelationGetRelationName(indexRel))));
 
-   /* the revmap does the hard work */
-   do
+   /* see gin_clean_pending_list() */
+   if (indexRel->rd_index->indisvalid)
    {
-       done = brinRevmapDesummarizeRange(indexRel, heapBlk);
+       /* the revmap does the hard work */
+       do
+       {
+           done = brinRevmapDesummarizeRange(indexRel, heapBlk);
+       }
+       while (!done);
    }
-   while (!done);
+   else
+       ereport(DEBUG1,
+               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                errmsg("index \"%s\" is not valid",
+                       RelationGetRelationName(indexRel))));
 
    relation_close(indexRel, ShareUpdateExclusiveLock);
    relation_close(heapRel, ShareUpdateExclusiveLock);
index ba36915835490d8d9470797a74140faff5ae9b83..3640977290a9b65cbfb49c35928090b76129d8f4 100644 (file)
@@ -1035,7 +1035,6 @@ gin_clean_pending_list(PG_FUNCTION_ARGS)
    Oid         indexoid = PG_GETARG_OID(0);
    Relation    indexRel = index_open(indexoid, RowExclusiveLock);
    IndexBulkDeleteResult stats;
-   GinState    ginstate;
 
    if (RecoveryInProgress())
        ereport(ERROR,
@@ -1067,8 +1066,26 @@ gin_clean_pending_list(PG_FUNCTION_ARGS)
                       RelationGetRelationName(indexRel));
 
    memset(&stats, 0, sizeof(stats));
-   initGinState(&ginstate, indexRel);
-   ginInsertCleanup(&ginstate, true, true, true, &stats);
+
+   /*
+    * Can't assume anything about the content of an !indisready index.  Make
+    * those a no-op, not an error, so users can just run this function on all
+    * indexes of the access method.  Since an indisready&&!indisvalid index
+    * is merely awaiting missed aminsert calls, we're capable of processing
+    * it.  Decline to do so, out of an abundance of caution.
+    */
+   if (indexRel->rd_index->indisvalid)
+   {
+       GinState    ginstate;
+
+       initGinState(&ginstate, indexRel);
+       ginInsertCleanup(&ginstate, true, true, true, &stats);
+   }
+   else
+       ereport(DEBUG1,
+               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                errmsg("index \"%s\" is not valid",
+                       RelationGetRelationName(indexRel))));
 
    index_close(indexRel, RowExclusiveLock);