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
1260 rhaas@postgresql.org 52 :CBC 25094 : 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 [ - + ]: 25094 : Assert(RelfilenumberMapHash != NULL);
59 : :
60 : 25094 : hash_seq_init(&status, RelfilenumberMapHash);
61 [ + + ]: 11862802 : 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 : : */
3101 tgl@sss.pgh.pa.us 68 [ + + ]: 11837708 : if (relid == InvalidOid || /* complete reset */
69 [ + + ]: 11837275 : entry->relid == InvalidOid || /* negative cache entry */
70 [ + + ]: 11837216 : entry->relid == relid) /* individual flushed relation */
71 : : {
1260 rhaas@postgresql.org 72 [ - + ]: 700 : if (hash_search(RelfilenumberMapHash,
1045 peter@eisentraut.org 73 : 700 : &entry->key,
74 : : HASH_REMOVE,
75 : : NULL) == NULL)
4531 rhaas@postgresql.org 76 [ # # ]:UBC 0 : elog(ERROR, "hash table corrupted");
77 : : }
78 : : }
4531 rhaas@postgresql.org 79 :CBC 25094 : }
80 : :
81 : : /*
82 : : * InitializeRelfilenumberMap
83 : : * Initialize cache, either on first use or after a reset.
84 : : */
85 : : static void
1260 86 : 226 : InitializeRelfilenumberMap(void)
87 : : {
88 : : HASHCTL ctl;
89 : : int i;
90 : :
91 : : /* Make sure we've initialized CacheMemoryContext. */
4531 92 [ - + ]: 226 : if (CacheMemoryContext == NULL)
4531 rhaas@postgresql.org 93 :UBC 0 : CreateCacheMemoryContext();
94 : :
95 : : /* build skey */
1260 rhaas@postgresql.org 96 [ + - + - :CBC 4294 : MemSet(&relfilenumber_skey, 0, sizeof(relfilenumber_skey));
+ - + - +
+ ]
97 : :
1176 98 [ + + ]: 678 : for (i = 0; i < 2; i++)
99 : : {
100 : 452 : fmgr_info_cxt(F_OIDEQ,
101 : : &relfilenumber_skey[i].sk_func,
102 : : CacheMemoryContext);
103 : 452 : relfilenumber_skey[i].sk_strategy = BTEqualStrategyNumber;
104 : 452 : relfilenumber_skey[i].sk_subtype = InvalidOid;
105 : 452 : relfilenumber_skey[i].sk_collation = InvalidOid;
106 : : }
107 : :
108 : 226 : relfilenumber_skey[0].sk_attno = Anum_pg_class_reltablespace;
1260 109 : 226 : 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 : 226 : ctl.keysize = sizeof(RelfilenumberMapKey);
117 : 226 : ctl.entrysize = sizeof(RelfilenumberMapEntry);
1828 tgl@sss.pgh.pa.us 118 : 226 : ctl.hcxt = CacheMemoryContext;
119 : :
1260 rhaas@postgresql.org 120 : 226 : RelfilenumberMapHash =
121 : 226 : hash_create("RelfilenumberMap cache", 64, &ctl,
122 : : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
123 : :
124 : : /* Watch for invalidation events. */
125 : 226 : CacheRegisterRelcacheCallback(RelfilenumberMapInvalidateCallback,
126 : : (Datum) 0);
4531 127 : 226 : }
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
1260 141 : 344940 : 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 [ + + ]: 344940 : if (RelfilenumberMapHash == NULL)
152 : 226 : InitializeRelfilenumberMap();
153 : :
154 : : /* pg_class will show 0 when the value is actually MyDatabaseTableSpace */
4531 155 [ + + ]: 344940 : if (reltablespace == MyDatabaseTableSpace)
156 : 340658 : reltablespace = 0;
157 : :
158 [ + - + - : 689880 : MemSet(&key, 0, sizeof(key));
+ - + - +
+ ]
159 : 344940 : key.reltablespace = reltablespace;
1260 160 : 344940 : 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 : : */
1045 peter@eisentraut.org 169 : 344940 : entry = hash_search(RelfilenumberMapHash, &key, HASH_FIND, &found);
170 : :
4531 rhaas@postgresql.org 171 [ + + ]: 344940 : if (found)
172 : 339440 : 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 */
4417 177 : 5500 : relid = InvalidOid;
178 : :
4531 179 [ + + ]: 5500 : if (reltablespace == GLOBALTABLESPACE_OID)
180 : : {
181 : : /*
182 : : * Ok, shared table, check relmapper.
183 : : */
1260 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 */
2522 andres@anarazel.de 196 : 5349 : relation = table_open(RelationRelationId, AccessShareLock);
197 : :
198 : : /* copy scankey to local copy and set scan arguments */
1260 rhaas@postgresql.org 199 : 5346 : memcpy(skey, relfilenumber_skey, sizeof(skey));
4417 200 : 5346 : skey[0].sk_argument = ObjectIdGetDatum(reltablespace);
1176 201 : 5346 : skey[1].sk_argument = ObjectIdGetDatum(relfilenumber);
202 : :
4417 203 : 5346 : scandesc = systable_beginscan(relation,
204 : : ClassTblspcRelfilenodeIndexId,
205 : : true,
206 : : NULL,
207 : : 2,
208 : : skey);
209 : :
210 : 5346 : found = false;
211 : :
212 [ + + ]: 10290 : while (HeapTupleIsValid(ntp = systable_getnext(scandesc)))
213 : : {
2584 andres@anarazel.de 214 : 4944 : Form_pg_class classform = (Form_pg_class) GETSTRUCT(ntp);
215 : :
117 michael@paquier.xyz 216 [ + + ]: 4944 : if (classform->relpersistence == RELPERSISTENCE_TEMP)
217 : 3 : continue;
218 : :
4417 rhaas@postgresql.org 219 [ - + ]: 4941 : if (found)
4417 rhaas@postgresql.org 220 [ # # ]:UBC 0 : elog(ERROR,
221 : : "unexpected duplicate for tablespace %u, relfilenumber %u",
222 : : reltablespace, relfilenumber);
4417 rhaas@postgresql.org 223 :CBC 4941 : found = true;
224 : :
2584 andres@anarazel.de 225 [ - + ]: 4941 : Assert(classform->reltablespace == reltablespace);
1260 rhaas@postgresql.org 226 [ - + ]: 4941 : Assert(classform->relfilenode == relfilenumber);
2584 andres@anarazel.de 227 : 4941 : relid = classform->oid;
228 : : }
229 : :
4417 rhaas@postgresql.org 230 : 5341 : systable_endscan(scandesc);
2522 andres@anarazel.de 231 : 5341 : table_close(relation, AccessShareLock);
232 : :
233 : : /* check for tables that are mapped but not shared */
4417 rhaas@postgresql.org 234 [ + + ]: 5341 : if (!found)
1260 235 : 400 : 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 : : */
1045 peter@eisentraut.org 243 : 5492 : entry = hash_search(RelfilenumberMapHash, &key, HASH_ENTER, &found);
4417 rhaas@postgresql.org 244 [ - + ]: 5492 : if (found)
4417 rhaas@postgresql.org 245 [ # # ]:UBC 0 : elog(ERROR, "corrupted hashtable");
4417 rhaas@postgresql.org 246 :CBC 5492 : entry->relid = relid;
247 : :
248 : 5492 : return relid;
249 : : }
|