Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * relfilenumbermap.c
4 : * relfilenumber to oid mapping cache.
5 : *
6 : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : * IDENTIFICATION
10 : * src/backend/utils/cache/relfilenumbermap.c
11 : *
12 : *-------------------------------------------------------------------------
13 : */
14 : #include "postgres.h"
15 :
16 : #include "access/genam.h"
17 : #include "access/htup_details.h"
18 : #include "access/table.h"
19 : #include "catalog/pg_class.h"
20 : #include "catalog/pg_tablespace.h"
21 : #include "miscadmin.h"
22 : #include "utils/catcache.h"
23 : #include "utils/fmgroids.h"
24 : #include "utils/hsearch.h"
25 : #include "utils/inval.h"
26 : #include "utils/relfilenumbermap.h"
27 : #include "utils/relmapper.h"
28 :
29 : /* Hash table for information about each relfilenumber <-> oid pair */
30 : static HTAB *RelfilenumberMapHash = NULL;
31 :
32 : /* built first time through in InitializeRelfilenumberMap */
33 : static ScanKeyData relfilenumber_skey[2];
34 :
35 : typedef struct
36 : {
37 : Oid reltablespace;
38 : RelFileNumber relfilenumber;
39 : } RelfilenumberMapKey;
40 :
41 : typedef struct
42 : {
43 : RelfilenumberMapKey key; /* lookup key - must be first */
44 : Oid relid; /* pg_class.oid */
45 : } RelfilenumberMapEntry;
46 :
47 : /*
48 : * RelfilenumberMapInvalidateCallback
49 : * Flush mapping entries when pg_class is updated in a relevant fashion.
50 : */
51 : static void
52 34780 : RelfilenumberMapInvalidateCallback(Datum arg, Oid relid)
53 : {
54 : HASH_SEQ_STATUS status;
55 : RelfilenumberMapEntry *entry;
56 :
57 : /* callback only gets registered after creating the hash */
58 : Assert(RelfilenumberMapHash != NULL);
59 :
60 34780 : hash_seq_init(&status, RelfilenumberMapHash);
61 20212146 : while ((entry = (RelfilenumberMapEntry *) hash_seq_search(&status)) != NULL)
62 : {
63 : /*
64 : * If relid is InvalidOid, signaling a complete reset, we must remove
65 : * all entries, otherwise just remove the specific relation's entry.
66 : * Always remove negative cache entries.
67 : */
68 20177366 : if (relid == InvalidOid || /* complete reset */
69 20176578 : entry->relid == InvalidOid || /* negative cache entry */
70 20176472 : entry->relid == relid) /* individual flushed relation */
71 : {
72 1276 : if (hash_search(RelfilenumberMapHash,
73 1276 : &entry->key,
74 : HASH_REMOVE,
75 : NULL) == NULL)
76 0 : elog(ERROR, "hash table corrupted");
77 : }
78 : }
79 34780 : }
80 :
81 : /*
82 : * InitializeRelfilenumberMap
83 : * Initialize cache, either on first use or after a reset.
84 : */
85 : static void
86 350 : InitializeRelfilenumberMap(void)
87 : {
88 : HASHCTL ctl;
89 : int i;
90 :
91 : /* Make sure we've initialized CacheMemoryContext. */
92 350 : if (CacheMemoryContext == NULL)
93 0 : CreateCacheMemoryContext();
94 :
95 : /* build skey */
96 6650 : MemSet(&relfilenumber_skey, 0, sizeof(relfilenumber_skey));
97 :
98 1050 : for (i = 0; i < 2; i++)
99 : {
100 700 : fmgr_info_cxt(F_OIDEQ,
101 : &relfilenumber_skey[i].sk_func,
102 : CacheMemoryContext);
103 700 : relfilenumber_skey[i].sk_strategy = BTEqualStrategyNumber;
104 700 : relfilenumber_skey[i].sk_subtype = InvalidOid;
105 700 : relfilenumber_skey[i].sk_collation = InvalidOid;
106 : }
107 :
108 350 : relfilenumber_skey[0].sk_attno = Anum_pg_class_reltablespace;
109 350 : relfilenumber_skey[1].sk_attno = Anum_pg_class_relfilenode;
110 :
111 : /*
112 : * Only create the RelfilenumberMapHash now, so we don't end up partially
113 : * initialized when fmgr_info_cxt() above ERRORs out with an out of memory
114 : * error.
115 : */
116 350 : ctl.keysize = sizeof(RelfilenumberMapKey);
117 350 : ctl.entrysize = sizeof(RelfilenumberMapEntry);
118 350 : ctl.hcxt = CacheMemoryContext;
119 :
120 350 : RelfilenumberMapHash =
121 350 : hash_create("RelfilenumberMap cache", 64, &ctl,
122 : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
123 :
124 : /* Watch for invalidation events. */
125 350 : CacheRegisterRelcacheCallback(RelfilenumberMapInvalidateCallback,
126 : (Datum) 0);
127 350 : }
128 :
129 : /*
130 : * Map a relation's (tablespace, relfilenumber) to a relation's oid and cache
131 : * the result.
132 : *
133 : * Returns InvalidOid if no relation matching the criteria could be found.
134 : */
135 : Oid
136 687250 : RelidByRelfilenumber(Oid reltablespace, RelFileNumber relfilenumber)
137 : {
138 : RelfilenumberMapKey key;
139 : RelfilenumberMapEntry *entry;
140 : bool found;
141 : SysScanDesc scandesc;
142 : Relation relation;
143 : HeapTuple ntp;
144 : ScanKeyData skey[2];
145 : Oid relid;
146 :
147 687250 : if (RelfilenumberMapHash == NULL)
148 350 : InitializeRelfilenumberMap();
149 :
150 : /* pg_class will show 0 when the value is actually MyDatabaseTableSpace */
151 687250 : if (reltablespace == MyDatabaseTableSpace)
152 679148 : reltablespace = 0;
153 :
154 1374500 : MemSet(&key, 0, sizeof(key));
155 687250 : key.reltablespace = reltablespace;
156 687250 : key.relfilenumber = relfilenumber;
157 :
158 : /*
159 : * Check cache and return entry if one is found. Even if no target
160 : * relation can be found later on we store the negative match and return a
161 : * InvalidOid from cache. That's not really necessary for performance
162 : * since querying invalid values isn't supposed to be a frequent thing,
163 : * but it's basically free.
164 : */
165 687250 : entry = hash_search(RelfilenumberMapHash, &key, HASH_FIND, &found);
166 :
167 687250 : if (found)
168 677062 : return entry->relid;
169 :
170 : /* ok, no previous cache entry, do it the hard way */
171 :
172 : /* initialize empty/negative cache entry before doing the actual lookups */
173 10188 : relid = InvalidOid;
174 :
175 10188 : if (reltablespace == GLOBALTABLESPACE_OID)
176 : {
177 : /*
178 : * Ok, shared table, check relmapper.
179 : */
180 314 : relid = RelationMapFilenumberToOid(relfilenumber, true);
181 : }
182 : else
183 : {
184 : /*
185 : * Not a shared table, could either be a plain relation or a
186 : * non-shared, nailed one, like e.g. pg_class.
187 : */
188 :
189 : /* check for plain relations by looking in pg_class */
190 9874 : relation = table_open(RelationRelationId, AccessShareLock);
191 :
192 : /* copy scankey to local copy, it will be modified during the scan */
193 9868 : memcpy(skey, relfilenumber_skey, sizeof(skey));
194 :
195 : /* set scan arguments */
196 9868 : skey[0].sk_argument = ObjectIdGetDatum(reltablespace);
197 9868 : skey[1].sk_argument = ObjectIdGetDatum(relfilenumber);
198 :
199 9868 : scandesc = systable_beginscan(relation,
200 : ClassTblspcRelfilenodeIndexId,
201 : true,
202 : NULL,
203 : 2,
204 : skey);
205 :
206 9868 : found = false;
207 :
208 18996 : while (HeapTupleIsValid(ntp = systable_getnext(scandesc)))
209 : {
210 9128 : Form_pg_class classform = (Form_pg_class) GETSTRUCT(ntp);
211 :
212 9128 : if (found)
213 0 : elog(ERROR,
214 : "unexpected duplicate for tablespace %u, relfilenumber %u",
215 : reltablespace, relfilenumber);
216 9128 : found = true;
217 :
218 : Assert(classform->reltablespace == reltablespace);
219 : Assert(classform->relfilenode == relfilenumber);
220 9128 : relid = classform->oid;
221 : }
222 :
223 9860 : systable_endscan(scandesc);
224 9860 : table_close(relation, AccessShareLock);
225 :
226 : /* check for tables that are mapped but not shared */
227 9860 : if (!found)
228 732 : relid = RelationMapFilenumberToOid(relfilenumber, false);
229 : }
230 :
231 : /*
232 : * Only enter entry into cache now, our opening of pg_class could have
233 : * caused cache invalidations to be executed which would have deleted a
234 : * new entry if we had entered it above.
235 : */
236 10174 : entry = hash_search(RelfilenumberMapHash, &key, HASH_ENTER, &found);
237 10174 : if (found)
238 0 : elog(ERROR, "corrupted hashtable");
239 10174 : entry->relid = relid;
240 :
241 10174 : return relid;
242 : }
|