Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * relfilenumbermap.c
4 : : * relfilenumber to oid mapping cache.
5 : : *
6 : : * Portions Copyright (c) 1996-2025, 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
1210 rhaas@postgresql.org 52 :CBC 25120 : 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 [ - + ]: 25120 : Assert(RelfilenumberMapHash != NULL);
59 : :
60 : 25120 : hash_seq_init(&status, RelfilenumberMapHash);
61 [ + + ]: 11854693 : 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 : : */
3051 tgl@sss.pgh.pa.us 68 [ + + ]: 11829573 : if (relid == InvalidOid || /* complete reset */
69 [ + + ]: 11829138 : entry->relid == InvalidOid || /* negative cache entry */
70 [ + + ]: 11829079 : entry->relid == relid) /* individual flushed relation */
71 : : {
1210 rhaas@postgresql.org 72 [ - + ]: 703 : if (hash_search(RelfilenumberMapHash,
995 peter@eisentraut.org 73 : 703 : &entry->key,
74 : : HASH_REMOVE,
75 : : NULL) == NULL)
4481 rhaas@postgresql.org 76 [ # # ]:UBC 0 : elog(ERROR, "hash table corrupted");
77 : : }
78 : : }
4481 rhaas@postgresql.org 79 :CBC 25120 : }
80 : :
81 : : /*
82 : : * InitializeRelfilenumberMap
83 : : * Initialize cache, either on first use or after a reset.
84 : : */
85 : : static void
1210 86 : 223 : InitializeRelfilenumberMap(void)
87 : : {
88 : : HASHCTL ctl;
89 : : int i;
90 : :
91 : : /* Make sure we've initialized CacheMemoryContext. */
4481 92 [ - + ]: 223 : if (CacheMemoryContext == NULL)
4481 rhaas@postgresql.org 93 :UBC 0 : CreateCacheMemoryContext();
94 : :
95 : : /* build skey */
1210 rhaas@postgresql.org 96 [ + - + - :CBC 4237 : MemSet(&relfilenumber_skey, 0, sizeof(relfilenumber_skey));
+ - + - +
+ ]
97 : :
1126 98 [ + + ]: 669 : for (i = 0; i < 2; i++)
99 : : {
100 : 446 : fmgr_info_cxt(F_OIDEQ,
101 : : &relfilenumber_skey[i].sk_func,
102 : : CacheMemoryContext);
103 : 446 : relfilenumber_skey[i].sk_strategy = BTEqualStrategyNumber;
104 : 446 : relfilenumber_skey[i].sk_subtype = InvalidOid;
105 : 446 : relfilenumber_skey[i].sk_collation = InvalidOid;
106 : : }
107 : :
108 : 223 : relfilenumber_skey[0].sk_attno = Anum_pg_class_reltablespace;
1210 109 : 223 : 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 : 223 : ctl.keysize = sizeof(RelfilenumberMapKey);
117 : 223 : ctl.entrysize = sizeof(RelfilenumberMapEntry);
1778 tgl@sss.pgh.pa.us 118 : 223 : ctl.hcxt = CacheMemoryContext;
119 : :
1210 rhaas@postgresql.org 120 : 223 : RelfilenumberMapHash =
121 : 223 : hash_create("RelfilenumberMap cache", 64, &ctl,
122 : : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
123 : :
124 : : /* Watch for invalidation events. */
125 : 223 : CacheRegisterRelcacheCallback(RelfilenumberMapInvalidateCallback,
126 : : (Datum) 0);
4481 127 : 223 : }
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
1210 141 : 344945 : RelidByRelfilenumber(Oid reltablespace, RelFileNumber relfilenumber)
142 : : {
143 : : RelfilenumberMapKey key;
144 : : RelfilenumberMapEntry *entry;
145 : : bool found;
146 : : SysScanDesc scandesc;
147 : : Relation relation;
148 : : HeapTuple ntp;
149 : : Oid relid;
150 : :
151 [ + + ]: 344945 : if (RelfilenumberMapHash == NULL)
152 : 223 : InitializeRelfilenumberMap();
153 : :
154 : : /* pg_class will show 0 when the value is actually MyDatabaseTableSpace */
4481 155 [ + + ]: 344945 : if (reltablespace == MyDatabaseTableSpace)
156 : 340666 : reltablespace = 0;
157 : :
158 [ + - + - : 689890 : MemSet(&key, 0, sizeof(key));
+ - + - +
+ ]
159 : 344945 : key.reltablespace = reltablespace;
1210 160 : 344945 : 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 : : */
995 peter@eisentraut.org 169 : 344945 : entry = hash_search(RelfilenumberMapHash, &key, HASH_FIND, &found);
170 : :
4481 rhaas@postgresql.org 171 [ + + ]: 344945 : if (found)
172 : 339426 : 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 */
4367 177 : 5519 : relid = InvalidOid;
178 : :
4481 179 [ + + ]: 5519 : if (reltablespace == GLOBALTABLESPACE_OID)
180 : : {
181 : : /*
182 : : * Ok, shared table, check relmapper.
183 : : */
1210 184 : 151 : relid = RelationMapFilenumberToOid(relfilenumber, true);
185 : : }
186 : : else
187 : : {
188 : : 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 */
2472 andres@anarazel.de 196 : 5368 : relation = table_open(RelationRelationId, AccessShareLock);
197 : :
198 : : /* copy scankey to local copy and set scan arguments */
1210 rhaas@postgresql.org 199 : 5365 : memcpy(skey, relfilenumber_skey, sizeof(skey));
4367 200 : 5365 : skey[0].sk_argument = ObjectIdGetDatum(reltablespace);
1126 201 : 5365 : skey[1].sk_argument = ObjectIdGetDatum(relfilenumber);
202 : :
4367 203 : 5365 : scandesc = systable_beginscan(relation,
204 : : ClassTblspcRelfilenodeIndexId,
205 : : true,
206 : : NULL,
207 : : 2,
208 : : skey);
209 : :
210 : 5365 : found = false;
211 : :
212 [ + + ]: 10327 : while (HeapTupleIsValid(ntp = systable_getnext(scandesc)))
213 : : {
2534 andres@anarazel.de 214 : 4962 : Form_pg_class classform = (Form_pg_class) GETSTRUCT(ntp);
215 : :
67 michael@paquier.xyz 216 [ + + ]: 4962 : if (classform->relpersistence == RELPERSISTENCE_TEMP)
217 : 3 : continue;
218 : :
4367 rhaas@postgresql.org 219 [ - + ]: 4959 : if (found)
4367 rhaas@postgresql.org 220 [ # # ]:UBC 0 : elog(ERROR,
221 : : "unexpected duplicate for tablespace %u, relfilenumber %u",
222 : : reltablespace, relfilenumber);
4367 rhaas@postgresql.org 223 :CBC 4959 : found = true;
224 : :
2534 andres@anarazel.de 225 [ - + ]: 4959 : Assert(classform->reltablespace == reltablespace);
1210 rhaas@postgresql.org 226 [ - + ]: 4959 : Assert(classform->relfilenode == relfilenumber);
2534 andres@anarazel.de 227 : 4959 : relid = classform->oid;
228 : : }
229 : :
4367 rhaas@postgresql.org 230 : 5360 : systable_endscan(scandesc);
2472 andres@anarazel.de 231 : 5360 : table_close(relation, AccessShareLock);
232 : :
233 : : /* check for tables that are mapped but not shared */
4367 rhaas@postgresql.org 234 [ + + ]: 5360 : if (!found)
1210 235 : 401 : relid = RelationMapFilenumberToOid(relfilenumber, false);
236 : : }
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 : : */
995 peter@eisentraut.org 243 : 5511 : entry = hash_search(RelfilenumberMapHash, &key, HASH_ENTER, &found);
4367 rhaas@postgresql.org 244 [ - + ]: 5511 : if (found)
4367 rhaas@postgresql.org 245 [ # # ]:UBC 0 : elog(ERROR, "corrupted hashtable");
4367 rhaas@postgresql.org 246 :CBC 5511 : entry->relid = relid;
247 : :
248 : 5511 : return relid;
249 : : }
|