Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * relfilenodemap.c
4 : * relfilenode to oid mapping cache.
5 : *
6 : * Portions Copyright (c) 1996-2017, 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
55 986 : 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 986 : Assert(RelfilenodeMapHash != NULL);
62 :
63 986 : hash_seq_init(&status, RelfilenodeMapHash);
64 986 : 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 1717018 : if (relid == InvalidOid || /* complete reset */
72 1717018 : entry->relid == InvalidOid || /* negative cache entry */
73 858509 : entry->relid == relid) /* individual flushed relation */
74 : {
75 10 : if (hash_search(RelfilenodeMapHash,
76 10 : (void *) &entry->key,
77 : HASH_REMOVE,
78 : NULL) == NULL)
79 0 : elog(ERROR, "hash table corrupted");
80 : }
81 : }
82 986 : }
83 :
84 : /*
85 : * RelfilenodeMapInvalidateCallback
86 : * Initialize cache, either on first use or after a reset.
87 : */
88 : static void
89 1 : InitializeRelfilenodeMap(void)
90 : {
91 : HASHCTL ctl;
92 : int i;
93 :
94 : /* Make sure we've initialized CacheMemoryContext. */
95 1 : if (CacheMemoryContext == NULL)
96 0 : CreateCacheMemoryContext();
97 :
98 : /* build skey */
99 1 : MemSet(&relfilenode_skey, 0, sizeof(relfilenode_skey));
100 :
101 3 : for (i = 0; i < 2; i++)
102 : {
103 2 : fmgr_info_cxt(F_OIDEQ,
104 : &relfilenode_skey[i].sk_func,
105 : CacheMemoryContext);
106 2 : relfilenode_skey[i].sk_strategy = BTEqualStrategyNumber;
107 2 : relfilenode_skey[i].sk_subtype = InvalidOid;
108 2 : relfilenode_skey[i].sk_collation = InvalidOid;
109 : }
110 :
111 1 : relfilenode_skey[0].sk_attno = Anum_pg_class_reltablespace;
112 1 : relfilenode_skey[1].sk_attno = Anum_pg_class_relfilenode;
113 :
114 : /* Initialize the hash table. */
115 1 : MemSet(&ctl, 0, sizeof(ctl));
116 1 : ctl.keysize = sizeof(RelfilenodeMapKey);
117 1 : ctl.entrysize = sizeof(RelfilenodeMapEntry);
118 1 : 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 1 : RelfilenodeMapHash =
126 1 : hash_create("RelfilenodeMap cache", 64, &ctl,
127 : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
128 :
129 : /* Watch for invalidation events. */
130 1 : CacheRegisterRelcacheCallback(RelfilenodeMapInvalidateCallback,
131 : (Datum) 0);
132 1 : }
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
141 871 : 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 871 : if (RelfilenodeMapHash == NULL)
153 1 : InitializeRelfilenodeMap();
154 :
155 : /* pg_class will show 0 when the value is actually MyDatabaseTableSpace */
156 871 : if (reltablespace == MyDatabaseTableSpace)
157 0 : reltablespace = 0;
158 :
159 871 : MemSet(&key, 0, sizeof(key));
160 871 : key.reltablespace = reltablespace;
161 871 : 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 871 : entry = hash_search(RelfilenodeMapHash, (void *) &key, HASH_FIND, &found);
171 :
172 871 : if (found)
173 0 : 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 871 : relid = InvalidOid;
179 :
180 871 : if (reltablespace == GLOBALTABLESPACE_OID)
181 : {
182 : /*
183 : * Ok, shared table, check relmapper.
184 : */
185 35 : 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 836 : relation = heap_open(RelationRelationId, AccessShareLock);
196 :
197 : /* copy scankey to local copy, it will be modified during the scan */
198 836 : memcpy(skey, relfilenode_skey, sizeof(skey));
199 :
200 : /* set scan arguments */
201 836 : skey[0].sk_argument = ObjectIdGetDatum(reltablespace);
202 836 : skey[1].sk_argument = ObjectIdGetDatum(relfilenode);
203 :
204 836 : scandesc = systable_beginscan(relation,
205 : ClassTblspcRelfilenodeIndexId,
206 : true,
207 : NULL,
208 : 2,
209 : skey);
210 :
211 836 : found = false;
212 :
213 2493 : while (HeapTupleIsValid(ntp = systable_getnext(scandesc)))
214 : {
215 821 : if (found)
216 0 : elog(ERROR,
217 : "unexpected duplicate for tablespace %u, relfilenode %u",
218 : reltablespace, relfilenode);
219 821 : found = true;
220 :
221 : #ifdef USE_ASSERT_CHECKING
222 : {
223 : bool isnull;
224 : Oid check;
225 :
226 821 : check = fastgetattr(ntp, Anum_pg_class_reltablespace,
227 : RelationGetDescr(relation),
228 : &isnull);
229 821 : Assert(!isnull && check == reltablespace);
230 :
231 821 : check = fastgetattr(ntp, Anum_pg_class_relfilenode,
232 : RelationGetDescr(relation),
233 : &isnull);
234 821 : Assert(!isnull && check == relfilenode);
235 : }
236 : #endif
237 821 : relid = HeapTupleGetOid(ntp);
238 : }
239 :
240 836 : systable_endscan(scandesc);
241 836 : heap_close(relation, AccessShareLock);
242 :
243 : /* check for tables that are mapped but not shared */
244 836 : if (!found)
245 15 : 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 871 : entry = hash_search(RelfilenodeMapHash, (void *) &key, HASH_ENTER, &found);
254 871 : if (found)
255 0 : elog(ERROR, "corrupted hashtable");
256 871 : entry->relid = relid;
257 :
258 871 : return relid;
259 : }
|