Age Owner Branch data TLA 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
161 tomas.vondra@postgre 38 :CBC 4013 : amcheck_index_mainfork_expected(Relation rel)
39 : : {
40 [ - + ]: 4013 : if (rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED ||
161 tomas.vondra@postgre 41 [ # # ]:UBC 0 : !RecoveryInProgress())
161 tomas.vondra@postgre 42 :CBC 4013 : return true;
43 : :
161 tomas.vondra@postgre 44 [ # # ]:UBC 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
161 tomas.vondra@postgre 62 :CBC 4019 : 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 : 4019 : heapid = IndexGetRelation(indrelid, true);
84 [ + + ]: 4019 : if (OidIsValid(heapid))
85 : : {
86 : 4015 : 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 : 4015 : GetUserIdAndSecContext(&save_userid, &save_sec_context);
94 : 4015 : SetUserIdAndSecContext(heaprel->rd_rel->relowner,
95 : : save_sec_context | SECURITY_RESTRICTED_OPERATION);
96 : 4015 : save_nestlevel = NewGUCNestLevel();
97 : : }
98 : : else
99 : : {
100 : 4 : heaprel = NULL;
101 : : /* Set these just to suppress "uninitialized variable" warnings */
102 : 4 : save_userid = InvalidOid;
103 : 4 : save_sec_context = -1;
104 : 4 : 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 : 4019 : 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 [ + - - + ]: 4015 : if (heaprel == NULL || heapid != IndexGetRelation(indrelid, false))
161 tomas.vondra@postgre 127 [ # # ]:UBC 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 */
161 tomas.vondra@postgre 133 [ + - ]:CBC 4015 : if (index_checkable(indrel, am_id))
134 : 4013 : check(indrel, heaprel, state, lockmode == ShareLock);
135 : :
136 : : /* Roll back any GUC changes executed by index functions */
137 : 3970 : AtEOXact_GUC(false, save_nestlevel);
138 : :
139 : : /* Restore userid and security context */
140 : 3970 : 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 : 3970 : index_close(indrel, lockmode);
148 [ + - ]: 3970 : if (heaprel)
149 : 3970 : table_close(heaprel, lockmode);
150 : 3970 : }
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 : 4015 : index_checkable(Relation rel, Oid am_id)
162 : : {
54 fujii@postgresql.org 163 [ + + ]:GNC 4015 : if (rel->rd_rel->relkind != RELKIND_INDEX)
164 [ + - ]: 1 : 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 [ + + ]: 4014 : if (rel->rd_rel->relam != am_id)
161 tomas.vondra@postgre 170 [ + - ]:CBC 1 : 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 [ - + - - ]: 4013 : if (RELATION_IS_OTHER_TEMP(rel))
161 tomas.vondra@postgre 177 [ # # ]:UBC 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 : :
161 tomas.vondra@postgre 183 [ - + ]:CBC 4013 : if (!rel->rd_index->indisvalid)
161 tomas.vondra@postgre 184 [ # # ]:UBC 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 : :
161 tomas.vondra@postgre 190 :CBC 4013 : return amcheck_index_mainfork_expected(rel);
191 : : }
|