mirror of https://github.com/postgres/postgres
Before performing checks on an index, we need to take some safety measures that apply to all index AMs. This includes: * verifying that the index can be checked - Only selected AMs are supported by amcheck (right now only B-Tree). The index has to be valid and not a temporary index from another session. * changing (and then restoring) user's security context * obtaining proper locks on the index (and table, if needed) * discarding GUC changes from the index functions Until now this was implemented in the B-Tree amcheck module, but it's something every AM will have to do. So relocate the code into a new module verify_common for reuse. The shared steps are implemented by amcheck_lock_relation_and_check(), receiving the AM-specific verification as a callback. Custom parameters may be supplied using a pointer. Author: Andrey Borodin <amborodin@acm.org> Reviewed-By: José Villanova <jose.arthur@gmail.com> Reviewed-By: Aleksander Alekseev <aleksander@timescale.com> Reviewed-By: Nikolay Samokhvalov <samokhvalov@gmail.com> Reviewed-By: Andres Freund <andres@anarazel.de> Reviewed-By: Tomas Vondra <tomas@vondra.me> Reviewed-By: Mark Dilger <mark.dilger@enterprisedb.com> Reviewed-By: Peter Geoghegan <pg@bowt.ie> Reviewed-By: Kirill Reshke <reshkekirill@gmail.com> Discussion: https://postgr.es/m/45AC9B0A-2B45-40EE-B08F-BDCF5739D1E1%40yandex-team.rupull/208/head
parent
fb9dff7663
commit
d70b17636d
@ -0,0 +1,191 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* verify_common.c |
||||
* Utility functions common to all access methods. |
||||
* |
||||
* Copyright (c) 2016-2025, PostgreSQL Global Development Group |
||||
* |
||||
* IDENTIFICATION |
||||
* contrib/amcheck/verify_common.c |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "access/genam.h" |
||||
#include "access/table.h" |
||||
#include "access/tableam.h" |
||||
#include "verify_common.h" |
||||
#include "catalog/index.h" |
||||
#include "catalog/pg_am.h" |
||||
#include "commands/tablecmds.h" |
||||
#include "utils/guc.h" |
||||
#include "utils/syscache.h" |
||||
|
||||
static bool amcheck_index_mainfork_expected(Relation rel); |
||||
|
||||
|
||||
/*
|
||||
* Check if index relation should have a file for its main relation fork. |
||||
* Verification uses this to skip unlogged indexes when in hot standby mode, |
||||
* where there is simply nothing to verify. |
||||
* |
||||
* NB: Caller should call index_checkable() before calling here. |
||||
*/ |
||||
static bool |
||||
amcheck_index_mainfork_expected(Relation rel) |
||||
{ |
||||
if (rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED || |
||||
!RecoveryInProgress()) |
||||
return true; |
||||
|
||||
ereport(NOTICE, |
||||
(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION), |
||||
errmsg("cannot verify unlogged index \"%s\" during recovery, skipping", |
||||
RelationGetRelationName(rel)))); |
||||
|
||||
return false; |
||||
} |
||||
|
||||
/*
|
||||
* Amcheck main workhorse. |
||||
* Given index relation OID, lock relation. |
||||
* Next, take a number of standard actions: |
||||
* 1) Make sure the index can be checked |
||||
* 2) change the context of the user, |
||||
* 3) keep track of GUCs modified via index functions |
||||
* 4) execute callback function to verify integrity. |
||||
*/ |
||||
void |
||||
amcheck_lock_relation_and_check(Oid indrelid, |
||||
Oid am_id, |
||||
IndexDoCheckCallback check, |
||||
LOCKMODE lockmode, |
||||
void *state) |
||||
{ |
||||
Oid heapid; |
||||
Relation indrel; |
||||
Relation heaprel; |
||||
Oid save_userid; |
||||
int save_sec_context; |
||||
int save_nestlevel; |
||||
|
||||
/*
|
||||
* We must lock table before index to avoid deadlocks. However, if the |
||||
* passed indrelid isn't an index then IndexGetRelation() will fail. |
||||
* Rather than emitting a not-very-helpful error message, postpone |
||||
* complaining, expecting that the is-it-an-index test below will fail. |
||||
* |
||||
* In hot standby mode this will raise an error when parentcheck is true. |
||||
*/ |
||||
heapid = IndexGetRelation(indrelid, true); |
||||
if (OidIsValid(heapid)) |
||||
{ |
||||
heaprel = table_open(heapid, lockmode); |
||||
|
||||
/*
|
||||
* Switch to the table owner's userid, so that any index functions are |
||||
* run as that user. Also lock down security-restricted operations |
||||
* and arrange to make GUC variable changes local to this command. |
||||
*/ |
||||
GetUserIdAndSecContext(&save_userid, &save_sec_context); |
||||
SetUserIdAndSecContext(heaprel->rd_rel->relowner, |
||||
save_sec_context | SECURITY_RESTRICTED_OPERATION); |
||||
save_nestlevel = NewGUCNestLevel(); |
||||
} |
||||
else |
||||
{ |
||||
heaprel = NULL; |
||||
/* Set these just to suppress "uninitialized variable" warnings */ |
||||
save_userid = InvalidOid; |
||||
save_sec_context = -1; |
||||
save_nestlevel = -1; |
||||
} |
||||
|
||||
/*
|
||||
* Open the target index relations separately (like relation_openrv(), but |
||||
* with heap relation locked first to prevent deadlocking). In hot |
||||
* standby mode this will raise an error when parentcheck is true. |
||||
* |
||||
* There is no need for the usual indcheckxmin usability horizon test |
||||
* here, even in the heapallindexed case, because index undergoing |
||||
* verification only needs to have entries for a new transaction snapshot. |
||||
* (If this is a parentcheck verification, there is no question about |
||||
* committed or recently dead heap tuples lacking index entries due to |
||||
* concurrent activity.) |
||||
*/ |
||||
indrel = index_open(indrelid, lockmode); |
||||
|
||||
/*
|
||||
* Since we did the IndexGetRelation call above without any lock, it's |
||||
* barely possible that a race against an index drop/recreation could have |
||||
* netted us the wrong table. |
||||
*/ |
||||
if (heaprel == NULL || heapid != IndexGetRelation(indrelid, false)) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_UNDEFINED_TABLE), |
||||
errmsg("could not open parent table of index \"%s\"", |
||||
RelationGetRelationName(indrel)))); |
||||
|
||||
/* Check that relation suitable for checking */ |
||||
if (index_checkable(indrel, am_id)) |
||||
check(indrel, heaprel, state, lockmode == ShareLock); |
||||
|
||||
/* Roll back any GUC changes executed by index functions */ |
||||
AtEOXact_GUC(false, save_nestlevel); |
||||
|
||||
/* Restore userid and security context */ |
||||
SetUserIdAndSecContext(save_userid, save_sec_context); |
||||
|
||||
/*
|
||||
* Release locks early. That's ok here because nothing in the called |
||||
* routines will trigger shared cache invalidations to be sent, so we can |
||||
* relax the usual pattern of only releasing locks after commit. |
||||
*/ |
||||
index_close(indrel, lockmode); |
||||
if (heaprel) |
||||
table_close(heaprel, lockmode); |
||||
} |
||||
|
||||
/*
|
||||
* Basic checks about the suitability of a relation for checking as an index. |
||||
* |
||||
* |
||||
* NB: Intentionally not checking permissions, the function is normally not |
||||
* callable by non-superusers. If granted, it's useful to be able to check a |
||||
* whole cluster. |
||||
*/ |
||||
bool |
||||
index_checkable(Relation rel, Oid am_id) |
||||
{ |
||||
if (rel->rd_rel->relkind != RELKIND_INDEX || |
||||
rel->rd_rel->relam != am_id) |
||||
{ |
||||
HeapTuple amtup; |
||||
HeapTuple amtuprel; |
||||
|
||||
amtup = SearchSysCache1(AMOID, ObjectIdGetDatum(am_id)); |
||||
amtuprel = SearchSysCache1(AMOID, ObjectIdGetDatum(rel->rd_rel->relam)); |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("expected \"%s\" index as targets for verification", NameStr(((Form_pg_am) GETSTRUCT(amtup))->amname)), |
||||
errdetail("Relation \"%s\" is a %s index.", |
||||
RelationGetRelationName(rel), NameStr(((Form_pg_am) GETSTRUCT(amtuprel))->amname)))); |
||||
} |
||||
|
||||
if (RELATION_IS_OTHER_TEMP(rel)) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("cannot access temporary tables of other sessions"), |
||||
errdetail("Index \"%s\" is associated with temporary relation.", |
||||
RelationGetRelationName(rel)))); |
||||
|
||||
if (!rel->rd_index->indisvalid) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("cannot check index \"%s\"", |
||||
RelationGetRelationName(rel)), |
||||
errdetail("Index is not valid."))); |
||||
|
||||
return amcheck_index_mainfork_expected(rel); |
||||
} |
@ -0,0 +1,31 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* amcheck.h |
||||
* Shared routines for amcheck verifications. |
||||
* |
||||
* Copyright (c) 2016-2025, PostgreSQL Global Development Group |
||||
* |
||||
* IDENTIFICATION |
||||
* contrib/amcheck/amcheck.h |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#include "storage/bufpage.h" |
||||
#include "storage/lmgr.h" |
||||
#include "storage/lockdefs.h" |
||||
#include "utils/relcache.h" |
||||
#include "miscadmin.h" |
||||
|
||||
/* Typedefs for callback functions for amcheck_lock_relation */ |
||||
typedef void (*IndexCheckableCallback) (Relation index); |
||||
typedef void (*IndexDoCheckCallback) (Relation rel, |
||||
Relation heaprel, |
||||
void *state, |
||||
bool readonly); |
||||
|
||||
extern void amcheck_lock_relation_and_check(Oid indrelid, |
||||
Oid am_id, |
||||
IndexDoCheckCallback check, |
||||
LOCKMODE lockmode, void *state); |
||||
|
||||
extern bool index_checkable(Relation rel, Oid am_id); |
Loading…
Reference in new issue