LCOV - code coverage report
Current view: top level - contrib/amcheck - verify_common.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 33 39 84.6 %
Date: 2025-04-01 15:15:16 Functions: 3 3 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * verify_common.c
       4             :  *      Utility functions common to all access methods.
       5             :  *
       6             :  * Copyright (c) 2016-2025, PostgreSQL Global Development Group
       7             :  *
       8             :  * IDENTIFICATION
       9             :  *    contrib/amcheck/verify_common.c
      10             :  *
      11             :  *-------------------------------------------------------------------------
      12             :  */
      13             : #include "postgres.h"
      14             : 
      15             : #include "access/genam.h"
      16             : #include "access/table.h"
      17             : #include "access/tableam.h"
      18             : #include "verify_common.h"
      19             : #include "catalog/index.h"
      20             : #include "catalog/pg_am.h"
      21             : #include "commands/tablecmds.h"
      22             : #include "utils/guc.h"
      23             : #include "utils/syscache.h"
      24             : 
      25             : static bool amcheck_index_mainfork_expected(Relation rel);
      26             : 
      27             : 
      28             : /*
      29             :  * Check if index relation should have a file for its main relation fork.
      30             :  * Verification uses this to skip unlogged indexes when in hot standby mode,
      31             :  * where there is simply nothing to verify.
      32             :  *
      33             :  * NB: Caller should call index_checkable() before calling here.
      34             :  */
      35             : static bool
      36        8126 : amcheck_index_mainfork_expected(Relation rel)
      37             : {
      38        8126 :     if (rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED ||
      39           0 :         !RecoveryInProgress())
      40        8126 :         return true;
      41             : 
      42           0 :     ereport(NOTICE,
      43             :             (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
      44             :              errmsg("cannot verify unlogged index \"%s\" during recovery, skipping",
      45             :                     RelationGetRelationName(rel))));
      46             : 
      47           0 :     return false;
      48             : }
      49             : 
      50             : /*
      51             : * Amcheck main workhorse.
      52             : * Given index relation OID, lock relation.
      53             : * Next, take a number of standard actions:
      54             : * 1) Make sure the index can be checked
      55             : * 2) change the context of the user,
      56             : * 3) keep track of GUCs modified via index functions
      57             : * 4) execute callback function to verify integrity.
      58             : */
      59             : void
      60        8136 : amcheck_lock_relation_and_check(Oid indrelid,
      61             :                                 Oid am_id,
      62             :                                 IndexDoCheckCallback check,
      63             :                                 LOCKMODE lockmode,
      64             :                                 void *state)
      65             : {
      66             :     Oid         heapid;
      67             :     Relation    indrel;
      68             :     Relation    heaprel;
      69             :     Oid         save_userid;
      70             :     int         save_sec_context;
      71             :     int         save_nestlevel;
      72             : 
      73             :     /*
      74             :      * We must lock table before index to avoid deadlocks.  However, if the
      75             :      * passed indrelid isn't an index then IndexGetRelation() will fail.
      76             :      * Rather than emitting a not-very-helpful error message, postpone
      77             :      * complaining, expecting that the is-it-an-index test below will fail.
      78             :      *
      79             :      * In hot standby mode this will raise an error when parentcheck is true.
      80             :      */
      81        8136 :     heapid = IndexGetRelation(indrelid, true);
      82        8136 :     if (OidIsValid(heapid))
      83             :     {
      84        8128 :         heaprel = table_open(heapid, lockmode);
      85             : 
      86             :         /*
      87             :          * Switch to the table owner's userid, so that any index functions are
      88             :          * run as that user.  Also lock down security-restricted operations
      89             :          * and arrange to make GUC variable changes local to this command.
      90             :          */
      91        8128 :         GetUserIdAndSecContext(&save_userid, &save_sec_context);
      92        8128 :         SetUserIdAndSecContext(heaprel->rd_rel->relowner,
      93             :                                save_sec_context | SECURITY_RESTRICTED_OPERATION);
      94        8128 :         save_nestlevel = NewGUCNestLevel();
      95             :     }
      96             :     else
      97             :     {
      98           8 :         heaprel = NULL;
      99             :         /* Set these just to suppress "uninitialized variable" warnings */
     100           8 :         save_userid = InvalidOid;
     101           8 :         save_sec_context = -1;
     102           8 :         save_nestlevel = -1;
     103             :     }
     104             : 
     105             :     /*
     106             :      * Open the target index relations separately (like relation_openrv(), but
     107             :      * with heap relation locked first to prevent deadlocking).  In hot
     108             :      * standby mode this will raise an error when parentcheck is true.
     109             :      *
     110             :      * There is no need for the usual indcheckxmin usability horizon test
     111             :      * here, even in the heapallindexed case, because index undergoing
     112             :      * verification only needs to have entries for a new transaction snapshot.
     113             :      * (If this is a parentcheck verification, there is no question about
     114             :      * committed or recently dead heap tuples lacking index entries due to
     115             :      * concurrent activity.)
     116             :      */
     117        8136 :     indrel = index_open(indrelid, lockmode);
     118             : 
     119             :     /*
     120             :      * Since we did the IndexGetRelation call above without any lock, it's
     121             :      * barely possible that a race against an index drop/recreation could have
     122             :      * netted us the wrong table.
     123             :      */
     124        8128 :     if (heaprel == NULL || heapid != IndexGetRelation(indrelid, false))
     125           0 :         ereport(ERROR,
     126             :                 (errcode(ERRCODE_UNDEFINED_TABLE),
     127             :                  errmsg("could not open parent table of index \"%s\"",
     128             :                         RelationGetRelationName(indrel))));
     129             : 
     130             :     /* Check that relation suitable for checking */
     131        8128 :     if (index_checkable(indrel, am_id))
     132        8126 :         check(indrel, heaprel, state, lockmode == ShareLock);
     133             : 
     134             :     /* Roll back any GUC changes executed by index functions */
     135        8050 :     AtEOXact_GUC(false, save_nestlevel);
     136             : 
     137             :     /* Restore userid and security context */
     138        8050 :     SetUserIdAndSecContext(save_userid, save_sec_context);
     139             : 
     140             :     /*
     141             :      * Release locks early. That's ok here because nothing in the called
     142             :      * routines will trigger shared cache invalidations to be sent, so we can
     143             :      * relax the usual pattern of only releasing locks after commit.
     144             :      */
     145        8050 :     index_close(indrel, lockmode);
     146        8050 :     if (heaprel)
     147        8050 :         table_close(heaprel, lockmode);
     148        8050 : }
     149             : 
     150             : /*
     151             :  * Basic checks about the suitability of a relation for checking as an index.
     152             :  *
     153             :  *
     154             :  * NB: Intentionally not checking permissions, the function is normally not
     155             :  * callable by non-superusers. If granted, it's useful to be able to check a
     156             :  * whole cluster.
     157             :  */
     158             : bool
     159        8128 : index_checkable(Relation rel, Oid am_id)
     160             : {
     161        8128 :     if (rel->rd_rel->relkind != RELKIND_INDEX ||
     162        8128 :         rel->rd_rel->relam != am_id)
     163             :     {
     164             :         HeapTuple   amtup;
     165             :         HeapTuple   amtuprel;
     166             : 
     167           2 :         amtup = SearchSysCache1(AMOID, ObjectIdGetDatum(am_id));
     168           2 :         amtuprel = SearchSysCache1(AMOID, ObjectIdGetDatum(rel->rd_rel->relam));
     169           2 :         ereport(ERROR,
     170             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     171             :                  errmsg("expected \"%s\" index as targets for verification", NameStr(((Form_pg_am) GETSTRUCT(amtup))->amname)),
     172             :                  errdetail("Relation \"%s\" is a %s index.",
     173             :                            RelationGetRelationName(rel), NameStr(((Form_pg_am) GETSTRUCT(amtuprel))->amname))));
     174             :     }
     175             : 
     176        8126 :     if (RELATION_IS_OTHER_TEMP(rel))
     177           0 :         ereport(ERROR,
     178             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     179             :                  errmsg("cannot access temporary tables of other sessions"),
     180             :                  errdetail("Index \"%s\" is associated with temporary relation.",
     181             :                            RelationGetRelationName(rel))));
     182             : 
     183        8126 :     if (!rel->rd_index->indisvalid)
     184           0 :         ereport(ERROR,
     185             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     186             :                  errmsg("cannot check index \"%s\"",
     187             :                         RelationGetRelationName(rel)),
     188             :                  errdetail("Index is not valid.")));
     189             : 
     190        8126 :     return amcheck_index_mainfork_expected(rel);
     191             : }

Generated by: LCOV version 1.14