1 /*-------------------------------------------------------------------------
4 * POSTGRES type cache code
6 * The type cache exists to speed lookup of certain information about data
7 * types that is not directly available from a type's pg_type row. In
8 * particular, we use a type's default btree opclass, or the default hash
9 * opclass if no btree opclass exists, to determine which operators should
10 * be used for grouping and sorting the type (GROUP BY, ORDER BY ASC/DESC).
12 * Several seemingly-odd choices have been made to support use of the type
13 * cache by the generic array comparison routines array_eq() and array_cmp().
14 * Because these routines are used as index support operations, they cannot
15 * leak memory. To allow them to execute efficiently, all information that
16 * either of them would like to re-use across calls is made available in the
19 * Once created, a type cache entry lives as long as the backend does, so
20 * there is no need for a call to release a cache entry. (For present uses,
21 * it would be okay to flush type cache entries at the ends of transactions,
22 * if we needed to reclaim space.)
24 * There is presently no provision for clearing out a cache entry if the
25 * stored data becomes obsolete. (The code will work if a type acquires
26 * opclasses it didn't have before while a backend runs --- but not if the
27 * definition of an existing opclass is altered.) However, the relcache
28 * doesn't cope with opclasses changing under it, either, so this seems
29 * a low-priority problem.
31 * We do support clearing the tuple descriptor part of a rowtype's cache
32 * entry, since that may need to change as a consequence of ALTER TABLE.
35 * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
36 * Portions Copyright (c) 1994, Regents of the University of California
39 * $PostgreSQL: pgsql/src/backend/utils/cache/typcache.c,v 1.17 2006/02/10 19:01:12 tgl Exp $
41 *-------------------------------------------------------------------------
45 #include "access/heapam.h"
46 #include "access/hash.h"
47 #include "access/nbtree.h"
48 #include "catalog/pg_type.h"
49 #include "commands/defrem.h"
50 #include "utils/builtins.h"
51 #include "utils/catcache.h"
52 #include "utils/hsearch.h"
53 #include "utils/lsyscache.h"
54 #include "utils/syscache.h"
55 #include "utils/typcache.h"
58 /* The main type cache hashtable searched by lookup_type_cache */
59 static HTAB *TypeCacheHash = NULL;
62 * We use a separate table for storing the definitions of non-anonymous
63 * record types. Once defined, a record type will be remembered for the
64 * life of the backend. Subsequent uses of the "same" record type (where
65 * sameness means equalTupleDescs) will refer to the existing table entry.
67 * Stored record types are remembered in a linear array of TupleDescs,
68 * which can be indexed quickly with the assigned typmod. There is also
69 * a hash table to speed searches for matching TupleDescs. The hash key
70 * uses just the first N columns' type OIDs, and so we may have multiple
71 * entries with the same hash key.
73 #define REC_HASH_KEYS 16 /* use this many columns in hash key */
75 typedef struct RecordCacheEntry
77 /* the hash lookup key MUST BE FIRST */
78 Oid hashkey[REC_HASH_KEYS]; /* column type IDs, zero-filled */
80 /* list of TupleDescs for record types with this hashkey */
84 static HTAB *RecordCacheHash = NULL;
86 static TupleDesc *RecordCacheArray = NULL;
87 static int32 RecordCacheArrayLen = 0; /* allocated length of array */
88 static int32 NextRecordTypmod = 0; /* number of entries used */
94 * Fetch the type cache entry for the specified datatype, and make sure that
95 * all the fields requested by bits in 'flags' are valid.
97 * The result is never NULL --- we will elog() if the passed type OID is
98 * invalid. Note however that we may fail to find one or more of the
99 * requested opclass-dependent fields; the caller needs to check whether
100 * the fields are InvalidOid or not.
103 lookup_type_cache(Oid type_id, int flags)
105 TypeCacheEntry *typentry;
108 if (TypeCacheHash == NULL)
110 /* First time through: initialize the hash table */
113 if (!CacheMemoryContext)
114 CreateCacheMemoryContext();
116 MemSet(&ctl, 0, sizeof(ctl));
117 ctl.keysize = sizeof(Oid);
118 ctl.entrysize = sizeof(TypeCacheEntry);
120 TypeCacheHash = hash_create("Type information cache", 64,
121 &ctl, HASH_ELEM | HASH_FUNCTION);
124 /* Try to look up an existing entry */
125 typentry = (TypeCacheEntry *) hash_search(TypeCacheHash,
128 if (typentry == NULL)
131 * If we didn't find one, we want to make one. But first look up the
132 * pg_type row, just to make sure we don't make a cache entry for an
138 tp = SearchSysCache(TYPEOID,
139 ObjectIdGetDatum(type_id),
141 if (!HeapTupleIsValid(tp))
142 elog(ERROR, "cache lookup failed for type %u", type_id);
143 typtup = (Form_pg_type) GETSTRUCT(tp);
144 if (!typtup->typisdefined)
146 (errcode(ERRCODE_UNDEFINED_OBJECT),
147 errmsg("type \"%s\" is only a shell",
148 NameStr(typtup->typname))));
150 /* Now make the typcache entry */
151 typentry = (TypeCacheEntry *) hash_search(TypeCacheHash,
154 Assert(!found); /* it wasn't there a moment ago */
156 MemSet(typentry, 0, sizeof(TypeCacheEntry));
157 typentry->type_id = type_id;
158 typentry->typlen = typtup->typlen;
159 typentry->typbyval = typtup->typbyval;
160 typentry->typalign = typtup->typalign;
161 typentry->typtype = typtup->typtype;
162 typentry->typrelid = typtup->typrelid;
167 /* If we haven't already found the opclass, try to do so */
168 if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_LT_OPR | TYPECACHE_GT_OPR |
170 TYPECACHE_EQ_OPR_FINFO | TYPECACHE_CMP_PROC_FINFO)) &&
171 typentry->btree_opc == InvalidOid)
173 typentry->btree_opc = GetDefaultOpClass(type_id,
175 /* Only care about hash opclass if no btree opclass... */
176 if (typentry->btree_opc == InvalidOid)
178 if (typentry->hash_opc == InvalidOid)
179 typentry->hash_opc = GetDefaultOpClass(type_id,
185 * If we find a btree opclass where previously we only found a
186 * hash opclass, forget the hash equality operator so we can use
187 * the btree operator instead.
189 typentry->eq_opr = InvalidOid;
190 typentry->eq_opr_finfo.fn_oid = InvalidOid;
194 /* Look for requested operators and functions */
195 if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO)) &&
196 typentry->eq_opr == InvalidOid)
198 if (typentry->btree_opc != InvalidOid)
199 typentry->eq_opr = get_opclass_member(typentry->btree_opc,
201 BTEqualStrategyNumber);
202 if (typentry->eq_opr == InvalidOid &&
203 typentry->hash_opc != InvalidOid)
204 typentry->eq_opr = get_opclass_member(typentry->hash_opc,
206 HTEqualStrategyNumber);
208 if ((flags & TYPECACHE_LT_OPR) && typentry->lt_opr == InvalidOid)
210 if (typentry->btree_opc != InvalidOid)
211 typentry->lt_opr = get_opclass_member(typentry->btree_opc,
213 BTLessStrategyNumber);
215 if ((flags & TYPECACHE_GT_OPR) && typentry->gt_opr == InvalidOid)
217 if (typentry->btree_opc != InvalidOid)
218 typentry->gt_opr = get_opclass_member(typentry->btree_opc,
220 BTGreaterStrategyNumber);
222 if ((flags & (TYPECACHE_CMP_PROC | TYPECACHE_CMP_PROC_FINFO)) &&
223 typentry->cmp_proc == InvalidOid)
225 if (typentry->btree_opc != InvalidOid)
226 typentry->cmp_proc = get_opclass_proc(typentry->btree_opc,
232 * Set up fmgr lookup info as requested
234 * Note: we tell fmgr the finfo structures live in CacheMemoryContext,
235 * which is not quite right (they're really in DynaHashContext) but this
236 * will do for our purposes.
238 if ((flags & TYPECACHE_EQ_OPR_FINFO) &&
239 typentry->eq_opr_finfo.fn_oid == InvalidOid &&
240 typentry->eq_opr != InvalidOid)
244 eq_opr_func = get_opcode(typentry->eq_opr);
245 if (eq_opr_func != InvalidOid)
246 fmgr_info_cxt(eq_opr_func, &typentry->eq_opr_finfo,
249 if ((flags & TYPECACHE_CMP_PROC_FINFO) &&
250 typentry->cmp_proc_finfo.fn_oid == InvalidOid &&
251 typentry->cmp_proc != InvalidOid)
253 fmgr_info_cxt(typentry->cmp_proc, &typentry->cmp_proc_finfo,
258 * If it's a composite type (row type), get tupdesc if requested
260 if ((flags & TYPECACHE_TUPDESC) &&
261 typentry->tupDesc == NULL &&
262 typentry->typtype == 'c')
266 if (!OidIsValid(typentry->typrelid)) /* should not happen */
267 elog(ERROR, "invalid typrelid for composite type %u",
269 rel = relation_open(typentry->typrelid, AccessShareLock);
270 Assert(rel->rd_rel->reltype == typentry->type_id);
273 * Notice that we simply store a link to the relcache's tupdesc. Since
274 * we are relying on relcache to detect cache flush events, there's
275 * not a lot of point to maintaining an independent copy.
277 typentry->tupDesc = RelationGetDescr(rel);
279 relation_close(rel, AccessShareLock);
286 * lookup_rowtype_tupdesc
288 * Given a typeid/typmod that should describe a known composite type,
289 * return the tuple descriptor for the type. Will ereport on failure.
291 * Note: returned TupleDesc points to cached copy; caller must copy it
292 * if intending to scribble on it or keep a reference for a long time.
295 lookup_rowtype_tupdesc(Oid type_id, int32 typmod)
297 return lookup_rowtype_tupdesc_noerror(type_id, typmod, false);
301 * lookup_rowtype_tupdesc_noerror
303 * As above, but if the type is not a known composite type and noError
304 * is true, returns NULL instead of ereport'ing. (Note that if a bogus
305 * type_id is passed, you'll get an ereport anyway.)
308 lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod, bool noError)
310 if (type_id != RECORDOID)
313 * It's a named composite type, so use the regular typcache.
315 TypeCacheEntry *typentry;
317 typentry = lookup_type_cache(type_id, TYPECACHE_TUPDESC);
318 if (typentry->tupDesc == NULL && !noError)
320 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
321 errmsg("type %s is not composite",
322 format_type_be(type_id))));
323 return typentry->tupDesc;
328 * It's a transient record type, so look in our record-type table.
330 if (typmod < 0 || typmod >= NextRecordTypmod)
334 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
335 errmsg("record type has not been registered")));
338 return RecordCacheArray[typmod];
344 * assign_record_type_typmod
346 * Given a tuple descriptor for a RECORD type, find or create a cache entry
347 * for the type, and set the tupdesc's tdtypmod field to a value that will
348 * identify this cache entry to lookup_rowtype_tupdesc.
351 assign_record_type_typmod(TupleDesc tupDesc)
353 RecordCacheEntry *recentry;
355 Oid hashkey[REC_HASH_KEYS];
360 MemoryContext oldcxt;
362 Assert(tupDesc->tdtypeid == RECORDOID);
364 if (RecordCacheHash == NULL)
366 /* First time through: initialize the hash table */
369 if (!CacheMemoryContext)
370 CreateCacheMemoryContext();
372 MemSet(&ctl, 0, sizeof(ctl));
373 ctl.keysize = REC_HASH_KEYS * sizeof(Oid);
374 ctl.entrysize = sizeof(RecordCacheEntry);
376 RecordCacheHash = hash_create("Record information cache", 64,
377 &ctl, HASH_ELEM | HASH_FUNCTION);
380 /* Find or create a hashtable entry for this hash class */
381 MemSet(hashkey, 0, sizeof(hashkey));
382 for (i = 0; i < tupDesc->natts; i++)
384 if (i >= REC_HASH_KEYS)
386 hashkey[i] = tupDesc->attrs[i]->atttypid;
388 recentry = (RecordCacheEntry *) hash_search(RecordCacheHash,
393 /* New entry ... hash_search initialized only the hash key */
394 recentry->tupdescs = NIL;
397 /* Look for existing record cache entry */
398 foreach(l, recentry->tupdescs)
400 entDesc = (TupleDesc) lfirst(l);
401 if (equalTupleDescs(tupDesc, entDesc))
403 tupDesc->tdtypmod = entDesc->tdtypmod;
408 /* Not present, so need to manufacture an entry */
409 oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
411 if (RecordCacheArray == NULL)
413 RecordCacheArray = (TupleDesc *) palloc(64 * sizeof(TupleDesc));
414 RecordCacheArrayLen = 64;
416 else if (NextRecordTypmod >= RecordCacheArrayLen)
418 int32 newlen = RecordCacheArrayLen * 2;
420 RecordCacheArray = (TupleDesc *) repalloc(RecordCacheArray,
421 newlen * sizeof(TupleDesc));
422 RecordCacheArrayLen = newlen;
425 /* if fail in subrs, no damage except possibly some wasted memory... */
426 entDesc = CreateTupleDescCopy(tupDesc);
427 recentry->tupdescs = lcons(entDesc, recentry->tupdescs);
428 /* now it's safe to advance NextRecordTypmod */
429 newtypmod = NextRecordTypmod++;
430 entDesc->tdtypmod = newtypmod;
431 RecordCacheArray[newtypmod] = entDesc;
433 /* report to caller as well */
434 tupDesc->tdtypmod = newtypmod;
436 MemoryContextSwitchTo(oldcxt);
440 * flush_rowtype_cache
442 * If a typcache entry exists for a rowtype, delete the entry's cached
443 * tuple descriptor link. This is called from relcache.c when a cached
444 * relation tupdesc is about to be dropped.
447 flush_rowtype_cache(Oid type_id)
449 TypeCacheEntry *typentry;
451 if (TypeCacheHash == NULL)
452 return; /* no table, so certainly no entry */
454 typentry = (TypeCacheEntry *) hash_search(TypeCacheHash,
457 if (typentry == NULL)
458 return; /* no matching entry */
460 typentry->tupDesc = NULL;