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