1 /*-------------------------------------------------------------------------
2 *
3 * relfilenodemap.c
4 * relfilenode to oid mapping cache.
5 *
6 * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
8 *
9 * IDENTIFICATION
10 * src/backend/utils/cache/relfilenode.c
11 *
12 *-------------------------------------------------------------------------
13 */
14 #include "postgres.h"
15
16 #include "access/genam.h"
17 #include "access/heapam.h"
18 #include "access/htup_details.h"
19 #include "catalog/indexing.h"
20 #include "catalog/pg_class.h"
21 #include "catalog/pg_tablespace.h"
22 #include "miscadmin.h"
23 #include "utils/builtins.h"
24 #include "utils/catcache.h"
25 #include "utils/hsearch.h"
26 #include "utils/inval.h"
27 #include "utils/fmgroids.h"
28 #include "utils/rel.h"
29 #include "utils/relfilenodemap.h"
30 #include "utils/relmapper.h"
31
32 /* Hash table for informations about each relfilenode <-> oid pair */
33 static HTAB *RelfilenodeMapHash = NULL;
34
35 /* built first time through in InitializeRelfilenodeMap */
36 static ScanKeyData relfilenode_skey[2];
37
38 typedef struct
39 {
40 Oid reltablespace;
41 Oid relfilenode;
42 } RelfilenodeMapKey;
43
44 typedef struct
45 {
46 RelfilenodeMapKey key; /* lookup key - must be first */
47 Oid relid; /* pg_class.oid */
48 } RelfilenodeMapEntry;
49
50 /*
51 * RelfilenodeMapInvalidateCallback
52 * Flush mapping entries when pg_class is updated in a relevant fashion.
53 */
54 static void
RelfilenodeMapInvalidateCallback(Datum arg,Oid relid)55 RelfilenodeMapInvalidateCallback(Datum arg, Oid relid)
56 {
57 HASH_SEQ_STATUS status;
58 RelfilenodeMapEntry *entry;
59
60 /* callback only gets registered after creating the hash */
61 Assert(RelfilenodeMapHash != NULL);
62
63 hash_seq_init(&status, RelfilenodeMapHash);
64 while ((entry = (RelfilenodeMapEntry *) hash_seq_search(&status)) != NULL)
65 {
66 /*
67 * If relid is InvalidOid, signalling a complete reset, we must remove
68 * all entries, otherwise just remove the specific relation's entry.
69 * Always remove negative cache entries.
70 */
71 if (relid == InvalidOid || /* complete reset */
72 entry->relid == InvalidOid || /* negative cache entry */
73 entry->relid == relid) /* individual flushed relation */
74 {
75 if (hash_search(RelfilenodeMapHash,
76 (void *) &entry->key,
77 HASH_REMOVE,
78 NULL) == NULL)
79 elog(ERROR, "hash table corrupted");
80 }
81 }
82 }
83
84 /*
85 * RelfilenodeMapInvalidateCallback
86 * Initialize cache, either on first use or after a reset.
87 */
88 static void
InitializeRelfilenodeMap(void)89 InitializeRelfilenodeMap(void)
90 {
91 HASHCTL ctl;
92 int i;
93
94 /* Make sure we've initialized CacheMemoryContext. */
95 if (CacheMemoryContext == NULL)
96 CreateCacheMemoryContext();
97
98 /* build skey */
99 MemSet(&relfilenode_skey, 0, sizeof(relfilenode_skey));
100
101 for (i = 0; i < 2; i++)
102 {
103 fmgr_info_cxt(F_OIDEQ,
104 &relfilenode_skey[i].sk_func,
105 CacheMemoryContext);
106 relfilenode_skey[i].sk_strategy = BTEqualStrategyNumber;
107 relfilenode_skey[i].sk_subtype = InvalidOid;
108 relfilenode_skey[i].sk_collation = InvalidOid;
109 }
110
111 relfilenode_skey[0].sk_attno = Anum_pg_class_reltablespace;
112 relfilenode_skey[1].sk_attno = Anum_pg_class_relfilenode;
113
114 /* Initialize the hash table. */
115 MemSet(&ctl, 0, sizeof(ctl));
116 ctl.keysize = sizeof(RelfilenodeMapKey);
117 ctl.entrysize = sizeof(RelfilenodeMapEntry);
118 ctl.hcxt = CacheMemoryContext;
119
120 /*
121 * Only create the RelfilenodeMapHash now, so we don't end up partially
122 * initialized when fmgr_info_cxt() above ERRORs out with an out of memory
123 * error.
124 */
125 RelfilenodeMapHash =
126 hash_create("RelfilenodeMap cache", 64, &ctl,
127 HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
128
129 /* Watch for invalidation events. */
130 CacheRegisterRelcacheCallback(RelfilenodeMapInvalidateCallback,
131 (Datum) 0);
132 }
133
134 /*
135 * Map a relation's (tablespace, filenode) to a relation's oid and cache the
136 * result.
137 *
138 * Returns InvalidOid if no relation matching the criteria could be found.
139 */
140 Oid
RelidByRelfilenode(Oid reltablespace,Oid relfilenode)141 RelidByRelfilenode(Oid reltablespace, Oid relfilenode)
142 {
143 RelfilenodeMapKey key;
144 RelfilenodeMapEntry *entry;
145 bool found;
146 SysScanDesc scandesc;
147 Relation relation;
148 HeapTuple ntp;
149 ScanKeyData skey[2];
150 Oid relid;
151
152 if (RelfilenodeMapHash == NULL)
153 InitializeRelfilenodeMap();
154
155 /* pg_class will show 0 when the value is actually MyDatabaseTableSpace */
156 if (reltablespace == MyDatabaseTableSpace)
157 reltablespace = 0;
158
159 MemSet(&key, 0, sizeof(key));
160 key.reltablespace = reltablespace;
161 key.relfilenode = relfilenode;
162
163 /*
164 * Check cache and return entry if one is found. Even if no target
165 * relation can be found later on we store the negative match and return a
166 * InvalidOid from cache. That's not really necessary for performance
167 * since querying invalid values isn't supposed to be a frequent thing,
168 * but it's basically free.
169 */
170 entry = hash_search(RelfilenodeMapHash, (void *) &key, HASH_FIND, &found);
171
172 if (found)
173 return entry->relid;
174
175 /* ok, no previous cache entry, do it the hard way */
176
177 /* initialize empty/negative cache entry before doing the actual lookups */
178 relid = InvalidOid;
179
180 if (reltablespace == GLOBALTABLESPACE_OID)
181 {
182 /*
183 * Ok, shared table, check relmapper.
184 */
185 relid = RelationMapFilenodeToOid(relfilenode, true);
186 }
187 else
188 {
189 /*
190 * Not a shared table, could either be a plain relation or a
191 * non-shared, nailed one, like e.g. pg_class.
192 */
193
194 /* check for plain relations by looking in pg_class */
195 relation = heap_open(RelationRelationId, AccessShareLock);
196
197 /* copy scankey to local copy, it will be modified during the scan */
198 memcpy(skey, relfilenode_skey, sizeof(skey));
199
200 /* set scan arguments */
201 skey[0].sk_argument = ObjectIdGetDatum(reltablespace);
202 skey[1].sk_argument = ObjectIdGetDatum(relfilenode);
203
204 scandesc = systable_beginscan(relation,
205 ClassTblspcRelfilenodeIndexId,
206 true,
207 NULL,
208 2,
209 skey);
210
211 found = false;
212
213 while (HeapTupleIsValid(ntp = systable_getnext(scandesc)))
214 {
215 if (found)
216 elog(ERROR,
217 "unexpected duplicate for tablespace %u, relfilenode %u",
218 reltablespace, relfilenode);
219 found = true;
220
221 #ifdef USE_ASSERT_CHECKING
222 {
223 bool isnull;
224 Oid check;
225
226 check = fastgetattr(ntp, Anum_pg_class_reltablespace,
227 RelationGetDescr(relation),
228 &isnull);
229 Assert(!isnull && check == reltablespace);
230
231 check = fastgetattr(ntp, Anum_pg_class_relfilenode,
232 RelationGetDescr(relation),
233 &isnull);
234 Assert(!isnull && check == relfilenode);
235 }
236 #endif
237 relid = HeapTupleGetOid(ntp);
238 }
239
240 systable_endscan(scandesc);
241 heap_close(relation, AccessShareLock);
242
243 /* check for tables that are mapped but not shared */
244 if (!found)
245 relid = RelationMapFilenodeToOid(relfilenode, false);
246 }
247
248 /*
249 * Only enter entry into cache now, our opening of pg_class could have
250 * caused cache invalidations to be executed which would have deleted a
251 * new entry if we had entered it above.
252 */
253 entry = hash_search(RelfilenodeMapHash, (void *) &key, HASH_ENTER, &found);
254 if (found)
255 elog(ERROR, "corrupted hashtable");
256 entry->relid = relid;
257
258 return relid;
259 }
260