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 : }
|