/*------------------------------------------------------------------------- * * relfilenumbermap.c * relfilenumber to oid mapping cache. * * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * src/backend/utils/cache/relfilenumbermap.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/genam.h" #include "access/htup_details.h" #include "access/table.h" #include "catalog/pg_class.h" #include "catalog/pg_tablespace.h" #include "miscadmin.h" #include "utils/builtins.h" #include "utils/catcache.h" #include "utils/fmgroids.h" #include "utils/hsearch.h" #include "utils/inval.h" #include "utils/rel.h" #include "utils/relfilenumbermap.h" #include "utils/relmapper.h" /* Hash table for information about each relfilenumber <-> oid pair */ static HTAB *RelfilenumberMapHash = NULL; /* built first time through in InitializeRelfilenumberMap */ static ScanKeyData relfilenumber_skey[2]; typedef struct { Oid reltablespace; RelFileNumber relfilenumber; } RelfilenumberMapKey; typedef struct { RelfilenumberMapKey key; /* lookup key - must be first */ Oid relid; /* pg_class.oid */ } RelfilenumberMapEntry; /* * RelfilenumberMapInvalidateCallback * Flush mapping entries when pg_class is updated in a relevant fashion. */ static void RelfilenumberMapInvalidateCallback(Datum arg, Oid relid) { HASH_SEQ_STATUS status; RelfilenumberMapEntry *entry; /* callback only gets registered after creating the hash */ Assert(RelfilenumberMapHash != NULL); hash_seq_init(&status, RelfilenumberMapHash); while ((entry = (RelfilenumberMapEntry *) hash_seq_search(&status)) != NULL) { /* * If relid is InvalidOid, signaling a complete reset, we must remove * all entries, otherwise just remove the specific relation's entry. * Always remove negative cache entries. */ if (relid == InvalidOid || /* complete reset */ entry->relid == InvalidOid || /* negative cache entry */ entry->relid == relid) /* individual flushed relation */ { if (hash_search(RelfilenumberMapHash, &entry->key, HASH_REMOVE, NULL) == NULL) elog(ERROR, "hash table corrupted"); } } } /* * InitializeRelfilenumberMap * Initialize cache, either on first use or after a reset. */ static void InitializeRelfilenumberMap(void) { HASHCTL ctl; int i; /* Make sure we've initialized CacheMemoryContext. */ if (CacheMemoryContext == NULL) CreateCacheMemoryContext(); /* build skey */ MemSet(&relfilenumber_skey, 0, sizeof(relfilenumber_skey)); for (i = 0; i < 2; i++) { fmgr_info_cxt(F_OIDEQ, &relfilenumber_skey[i].sk_func, CacheMemoryContext); relfilenumber_skey[i].sk_strategy = BTEqualStrategyNumber; relfilenumber_skey[i].sk_subtype = InvalidOid; relfilenumber_skey[i].sk_collation = InvalidOid; } relfilenumber_skey[0].sk_attno = Anum_pg_class_reltablespace; relfilenumber_skey[1].sk_attno = Anum_pg_class_relfilenode; /* * Only create the RelfilenumberMapHash now, so we don't end up partially * initialized when fmgr_info_cxt() above ERRORs out with an out of memory * error. */ ctl.keysize = sizeof(RelfilenumberMapKey); ctl.entrysize = sizeof(RelfilenumberMapEntry); ctl.hcxt = CacheMemoryContext; RelfilenumberMapHash = hash_create("RelfilenumberMap cache", 64, &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); /* Watch for invalidation events. */ CacheRegisterRelcacheCallback(RelfilenumberMapInvalidateCallback, (Datum) 0); } /* * Map a relation's (tablespace, relfilenumber) to a relation's oid and cache * the result. * * Returns InvalidOid if no relation matching the criteria could be found. */ Oid RelidByRelfilenumber(Oid reltablespace, RelFileNumber relfilenumber) { RelfilenumberMapKey key; RelfilenumberMapEntry *entry; bool found; SysScanDesc scandesc; Relation relation; HeapTuple ntp; ScanKeyData skey[2]; Oid relid; if (RelfilenumberMapHash == NULL) InitializeRelfilenumberMap(); /* pg_class will show 0 when the value is actually MyDatabaseTableSpace */ if (reltablespace == MyDatabaseTableSpace) reltablespace = 0; MemSet(&key, 0, sizeof(key)); key.reltablespace = reltablespace; key.relfilenumber = relfilenumber; /* * Check cache and return entry if one is found. Even if no target * relation can be found later on we store the negative match and return a * InvalidOid from cache. That's not really necessary for performance * since querying invalid values isn't supposed to be a frequent thing, * but it's basically free. */ entry = hash_search(RelfilenumberMapHash, &key, HASH_FIND, &found); if (found) return entry->relid; /* ok, no previous cache entry, do it the hard way */ /* initialize empty/negative cache entry before doing the actual lookups */ relid = InvalidOid; if (reltablespace == GLOBALTABLESPACE_OID) { /* * Ok, shared table, check relmapper. */ relid = RelationMapFilenumberToOid(relfilenumber, true); } else { /* * Not a shared table, could either be a plain relation or a * non-shared, nailed one, like e.g. pg_class. */ /* check for plain relations by looking in pg_class */ relation = table_open(RelationRelationId, AccessShareLock); /* copy scankey to local copy, it will be modified during the scan */ memcpy(skey, relfilenumber_skey, sizeof(skey)); /* set scan arguments */ skey[0].sk_argument = ObjectIdGetDatum(reltablespace); skey[1].sk_argument = ObjectIdGetDatum(relfilenumber); scandesc = systable_beginscan(relation, ClassTblspcRelfilenodeIndexId, true, NULL, 2, skey); found = false; while (HeapTupleIsValid(ntp = systable_getnext(scandesc))) { Form_pg_class classform = (Form_pg_class) GETSTRUCT(ntp); if (found) elog(ERROR, "unexpected duplicate for tablespace %u, relfilenumber %u", reltablespace, relfilenumber); found = true; Assert(classform->reltablespace == reltablespace); Assert(classform->relfilenode == relfilenumber); relid = classform->oid; } systable_endscan(scandesc); table_close(relation, AccessShareLock); /* check for tables that are mapped but not shared */ if (!found) relid = RelationMapFilenumberToOid(relfilenumber, false); } /* * Only enter entry into cache now, our opening of pg_class could have * caused cache invalidations to be executed which would have deleted a * new entry if we had entered it above. */ entry = hash_search(RelfilenumberMapHash, &key, HASH_ENTER, &found); if (found) elog(ERROR, "corrupted hashtable"); entry->relid = relid; return relid; }