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