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