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/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 8046 : amcheck_index_mainfork_expected(Relation rel)
39 : {
40 8046 : if (rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED ||
41 0 : !RecoveryInProgress())
42 8046 : 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 8058 : 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 8058 : heapid = IndexGetRelation(indrelid, true);
84 8058 : if (OidIsValid(heapid))
85 : {
86 8050 : 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 8050 : GetUserIdAndSecContext(&save_userid, &save_sec_context);
94 8050 : SetUserIdAndSecContext(heaprel->rd_rel->relowner,
95 : save_sec_context | SECURITY_RESTRICTED_OPERATION);
96 8050 : save_nestlevel = NewGUCNestLevel();
97 : }
98 : else
99 : {
100 8 : heaprel = NULL;
101 : /* Set these just to suppress "uninitialized variable" warnings */
102 8 : save_userid = InvalidOid;
103 8 : save_sec_context = -1;
104 8 : save_nestlevel = -1;
105 : }
106 :
107 : /*
108 : * Open the target index relations separately (like relation_openrv(), but
109 : * with heap relation locked first to prevent deadlocking). In hot
110 : * standby mode this will raise an error when parentcheck is true.
111 : *
112 : * There is no need for the usual indcheckxmin usability horizon test
113 : * here, even in the heapallindexed case, because index undergoing
114 : * verification only needs to have entries for a new transaction snapshot.
115 : * (If this is a parentcheck verification, there is no question about
116 : * committed or recently dead heap tuples lacking index entries due to
117 : * concurrent activity.)
118 : */
119 8058 : indrel = index_open(indrelid, lockmode);
120 :
121 : /*
122 : * Since we did the IndexGetRelation call above without any lock, it's
123 : * barely possible that a race against an index drop/recreation could have
124 : * netted us the wrong table.
125 : */
126 8050 : if (heaprel == NULL || heapid != IndexGetRelation(indrelid, false))
127 0 : ereport(ERROR,
128 : (errcode(ERRCODE_UNDEFINED_TABLE),
129 : errmsg("could not open parent table of index \"%s\"",
130 : RelationGetRelationName(indrel))));
131 :
132 : /* Check that relation suitable for checking */
133 8050 : if (index_checkable(indrel, am_id))
134 8046 : check(indrel, heaprel, state, lockmode == ShareLock);
135 :
136 : /* Roll back any GUC changes executed by index functions */
137 7960 : AtEOXact_GUC(false, save_nestlevel);
138 :
139 : /* Restore userid and security context */
140 7960 : SetUserIdAndSecContext(save_userid, save_sec_context);
141 :
142 : /*
143 : * Release locks early. That's ok here because nothing in the called
144 : * routines will trigger shared cache invalidations to be sent, so we can
145 : * relax the usual pattern of only releasing locks after commit.
146 : */
147 7960 : index_close(indrel, lockmode);
148 7960 : if (heaprel)
149 7960 : table_close(heaprel, lockmode);
150 7960 : }
151 :
152 : /*
153 : * Basic checks about the suitability of a relation for checking as an index.
154 : *
155 : *
156 : * NB: Intentionally not checking permissions, the function is normally not
157 : * callable by non-superusers. If granted, it's useful to be able to check a
158 : * whole cluster.
159 : */
160 : static bool
161 8050 : index_checkable(Relation rel, Oid am_id)
162 : {
163 8050 : if (rel->rd_rel->relkind != RELKIND_INDEX)
164 2 : ereport(ERROR,
165 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
166 : errmsg("expected index as targets for verification"),
167 : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
168 :
169 8048 : if (rel->rd_rel->relam != am_id)
170 2 : ereport(ERROR,
171 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
172 : errmsg("expected \"%s\" index as targets for verification", get_am_name(am_id)),
173 : errdetail("Relation \"%s\" is a %s index.",
174 : RelationGetRelationName(rel), get_am_name(rel->rd_rel->relam))));
175 :
176 8046 : 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 8046 : if (!rel->rd_index->indisvalid)
184 0 : ereport(ERROR,
185 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
186 : errmsg("cannot check index \"%s\"",
187 : RelationGetRelationName(rel)),
188 : errdetail("Index is not valid.")));
189 :
190 8046 : return amcheck_index_mainfork_expected(rel);
191 : }
|