1 /*-------------------------------------------------------------------------
2  *
3  * relfilenodemap.c
4  *	  relfilenode to oid mapping cache.
5  *
6  * Portions Copyright (c) 1996-2016, 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