Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * relfilenumbermap.c
4 : : * relfilenumber to oid mapping cache.
5 : : *
6 : : * Portions Copyright (c) 1996-2026, 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 : 4406 : RelfilenumberMapInvalidateCallback(Datum arg, Oid relid)
53 : : {
54 : 4406 : HASH_SEQ_STATUS status;
55 : 4406 : RelfilenumberMapEntry *entry;
56 : :
57 : : /* callback only gets registered after creating the hash */
58 [ + - ]: 4406 : Assert(RelfilenumberMapHash != NULL);
59 : :
60 : 4406 : hash_seq_init(&status, RelfilenumberMapHash);
61 [ + + ]: 3941925 : 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 [ + - ]: 3937519 : if (relid == InvalidOid || /* complete reset */
69 [ + + - + ]: 3937519 : entry->relid == InvalidOid || /* negative cache entry */
70 : 3937518 : entry->relid == relid) /* individual flushed relation */
71 : : {
72 : 2 : if (hash_search(RelfilenumberMapHash,
73 : 1 : &entry->key,
74 : : HASH_REMOVE,
75 [ + - ]: 1 : NULL) == NULL)
76 [ # # # # ]: 0 : elog(ERROR, "hash table corrupted");
77 : 1 : }
78 : : }
79 : 4406 : }
80 : :
81 : : /*
82 : : * InitializeRelfilenumberMap
83 : : * Initialize cache, either on first use or after a reset.
84 : : */
85 : : static void
86 : 2 : InitializeRelfilenumberMap(void)
87 : : {
88 : 2 : HASHCTL ctl;
89 : 2 : int i;
90 : :
91 : : /* Make sure we've initialized CacheMemoryContext. */
92 [ + - ]: 2 : if (CacheMemoryContext == NULL)
93 : 0 : CreateCacheMemoryContext();
94 : :
95 : : /* build skey */
96 [ + - + - : 38 : MemSet(&relfilenumber_skey, 0, sizeof(relfilenumber_skey));
+ - - + +
+ ]
97 : :
98 [ + + ]: 6 : for (i = 0; i < 2; i++)
99 : : {
100 : 4 : fmgr_info_cxt(F_OIDEQ,
101 : 4 : &relfilenumber_skey[i].sk_func,
102 : 4 : CacheMemoryContext);
103 : 4 : relfilenumber_skey[i].sk_strategy = BTEqualStrategyNumber;
104 : 4 : relfilenumber_skey[i].sk_subtype = InvalidOid;
105 : 4 : relfilenumber_skey[i].sk_collation = InvalidOid;
106 : 4 : }
107 : :
108 : 2 : relfilenumber_skey[0].sk_attno = Anum_pg_class_reltablespace;
109 : 2 : 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 : 2 : ctl.keysize = sizeof(RelfilenumberMapKey);
117 : 2 : ctl.entrysize = sizeof(RelfilenumberMapEntry);
118 : 2 : ctl.hcxt = CacheMemoryContext;
119 : :
120 : 2 : RelfilenumberMapHash =
121 : 2 : hash_create("RelfilenumberMap cache", 64, &ctl,
122 : : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
123 : :
124 : : /* Watch for invalidation events. */
125 : 2 : CacheRegisterRelcacheCallback(RelfilenumberMapInvalidateCallback,
126 : : (Datum) 0);
127 : 2 : }
128 : :
129 : : /*
130 : : * Map a relation's (tablespace, relfilenumber) to a relation's oid and cache
131 : : * the result.
132 : : *
133 : : * A temporary relation may share its relfilenumber with a permanent relation
134 : : * or temporary relations created in other backends. Being able to uniquely
135 : : * identify a temporary relation would require a backend's proc number, which
136 : : * we do not know about. Hence, this function ignores this case.
137 : : *
138 : : * Returns InvalidOid if no relation matching the criteria could be found.
139 : : */
140 : : Oid
141 : 1423 : RelidByRelfilenumber(Oid reltablespace, RelFileNumber relfilenumber)
142 : : {
143 : 1423 : RelfilenumberMapKey key;
144 : 1423 : RelfilenumberMapEntry *entry;
145 : 1423 : bool found;
146 : 1423 : SysScanDesc scandesc;
147 : 1423 : Relation relation;
148 : 1423 : HeapTuple ntp;
149 : 1423 : Oid relid;
150 : :
151 [ + + ]: 1423 : if (RelfilenumberMapHash == NULL)
152 : 2 : InitializeRelfilenumberMap();
153 : :
154 : : /* pg_class will show 0 when the value is actually MyDatabaseTableSpace */
155 [ + - ]: 1423 : if (reltablespace == MyDatabaseTableSpace)
156 : 0 : reltablespace = 0;
157 : :
158 [ + - + - : 2846 : MemSet(&key, 0, sizeof(key));
+ - - + +
+ ]
159 : 1423 : key.reltablespace = reltablespace;
160 : 1423 : key.relfilenumber = relfilenumber;
161 : :
162 : : /*
163 : : * Check cache and return entry if one is found. Even if no target
164 : : * relation can be found later on we store the negative match and return a
165 : : * InvalidOid from cache. That's not really necessary for performance
166 : : * since querying invalid values isn't supposed to be a frequent thing,
167 : : * but it's basically free.
168 : : */
169 : 1423 : entry = hash_search(RelfilenumberMapHash, &key, HASH_FIND, &found);
170 : :
171 [ - + ]: 1423 : if (found)
172 : 0 : return entry->relid;
173 : :
174 : : /* ok, no previous cache entry, do it the hard way */
175 : :
176 : : /* initialize empty/negative cache entry before doing the actual lookups */
177 : 1423 : relid = InvalidOid;
178 : :
179 [ + + ]: 1423 : if (reltablespace == GLOBALTABLESPACE_OID)
180 : : {
181 : : /*
182 : : * Ok, shared table, check relmapper.
183 : : */
184 : 46 : relid = RelationMapFilenumberToOid(relfilenumber, true);
185 : 46 : }
186 : : else
187 : : {
188 : 1377 : ScanKeyData skey[2];
189 : :
190 : : /*
191 : : * Not a shared table, could either be a plain relation or a
192 : : * non-shared, nailed one, like e.g. pg_class.
193 : : */
194 : :
195 : : /* check for plain relations by looking in pg_class */
196 : 1377 : relation = table_open(RelationRelationId, AccessShareLock);
197 : :
198 : : /* copy scankey to local copy and set scan arguments */
199 : 1377 : memcpy(skey, relfilenumber_skey, sizeof(skey));
200 : 1377 : skey[0].sk_argument = ObjectIdGetDatum(reltablespace);
201 : 1377 : skey[1].sk_argument = ObjectIdGetDatum(relfilenumber);
202 : :
203 : 2754 : scandesc = systable_beginscan(relation,
204 : : ClassTblspcRelfilenodeIndexId,
205 : : true,
206 : : NULL,
207 : : 2,
208 : 1377 : skey);
209 : :
210 : 1377 : found = false;
211 : :
212 [ + + ]: 2737 : while (HeapTupleIsValid(ntp = systable_getnext(scandesc)))
213 : : {
214 : 1360 : Form_pg_class classform = (Form_pg_class) GETSTRUCT(ntp);
215 : :
216 [ + + ]: 1360 : if (classform->relpersistence == RELPERSISTENCE_TEMP)
217 : 1 : continue;
218 : :
219 [ + - ]: 1359 : if (found)
220 [ # # # # ]: 0 : elog(ERROR,
221 : : "unexpected duplicate for tablespace %u, relfilenumber %u",
222 : : reltablespace, relfilenumber);
223 : 1359 : found = true;
224 : :
225 [ - + ]: 1359 : Assert(classform->reltablespace == reltablespace);
226 [ - + ]: 1359 : Assert(classform->relfilenode == relfilenumber);
227 : 1359 : relid = classform->oid;
228 [ - + + ]: 1360 : }
229 : :
230 : 1377 : systable_endscan(scandesc);
231 : 1377 : table_close(relation, AccessShareLock);
232 : :
233 : : /* check for tables that are mapped but not shared */
234 [ + + ]: 1377 : if (!found)
235 : 18 : relid = RelationMapFilenumberToOid(relfilenumber, false);
236 : 1377 : }
237 : :
238 : : /*
239 : : * Only enter entry into cache now, our opening of pg_class could have
240 : : * caused cache invalidations to be executed which would have deleted a
241 : : * new entry if we had entered it above.
242 : : */
243 : 1423 : entry = hash_search(RelfilenumberMapHash, &key, HASH_ENTER, &found);
244 [ + - ]: 1423 : if (found)
245 [ # # # # ]: 0 : elog(ERROR, "corrupted hashtable");
246 : 1423 : entry->relid = relid;
247 : :
248 : 1423 : return relid;
249 : 1423 : }
|