diff options
Diffstat (limited to 'src/backend/utils/cache/relfilenumbermap.c')
-rw-r--r-- | src/backend/utils/cache/relfilenumbermap.c | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/src/backend/utils/cache/relfilenumbermap.c b/src/backend/utils/cache/relfilenumbermap.c new file mode 100644 index 00000000000..c4245d5ccdd --- /dev/null +++ b/src/backend/utils/cache/relfilenumbermap.c @@ -0,0 +1,244 @@ +/*------------------------------------------------------------------------- + * + * relfilenumbermap.c + * relfilenumber to oid mapping cache. + * + * Portions Copyright (c) 1996-2022, 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, + (void *) &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, (void *) &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, (void *) &key, HASH_ENTER, &found); + if (found) + elog(ERROR, "corrupted hashtable"); + entry->relid = relid; + + return relid; +} |