aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/cache/relfilenumbermap.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/cache/relfilenumbermap.c')
-rw-r--r--src/backend/utils/cache/relfilenumbermap.c244
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;
+}