]> granicus.if.org Git - postgresql/blob - src/backend/utils/cache/typcache.c
Remove cvs keywords from all files.
[postgresql] / src / backend / utils / cache / typcache.c
1 /*-------------------------------------------------------------------------
2  *
3  * typcache.c
4  *        POSTGRES type cache code
5  *
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.  For
8  * example, 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).
11  *
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 those 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
17  * type cache.
18  *
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.)
23  *
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.
30  *
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.
33  *
34  *
35  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
36  * Portions Copyright (c) 1994, Regents of the University of California
37  *
38  * IDENTIFICATION
39  *        src/backend/utils/cache/typcache.c
40  *
41  *-------------------------------------------------------------------------
42  */
43 #include "postgres.h"
44
45 #include "access/hash.h"
46 #include "access/heapam.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/inval.h"
52 #include "utils/lsyscache.h"
53 #include "utils/rel.h"
54 #include "utils/syscache.h"
55 #include "utils/typcache.h"
56
57
58 /* The main type cache hashtable searched by lookup_type_cache */
59 static HTAB *TypeCacheHash = NULL;
60
61 /*
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.
66  *
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.
72  */
73 #define REC_HASH_KEYS   16              /* use this many columns in hash key */
74
75 typedef struct RecordCacheEntry
76 {
77         /* the hash lookup key MUST BE FIRST */
78         Oid                     hashkey[REC_HASH_KEYS]; /* column type IDs, zero-filled */
79
80         /* list of TupleDescs for record types with this hashkey */
81         List       *tupdescs;
82 } RecordCacheEntry;
83
84 static HTAB *RecordCacheHash = NULL;
85
86 static TupleDesc *RecordCacheArray = NULL;
87 static int32 RecordCacheArrayLen = 0;   /* allocated length of array */
88 static int32 NextRecordTypmod = 0;              /* number of entries used */
89
90 static void TypeCacheRelCallback(Datum arg, Oid relid);
91
92
93 /*
94  * lookup_type_cache
95  *
96  * Fetch the type cache entry for the specified datatype, and make sure that
97  * all the fields requested by bits in 'flags' are valid.
98  *
99  * The result is never NULL --- we will elog() if the passed type OID is
100  * invalid.  Note however that we may fail to find one or more of the
101  * requested opclass-dependent fields; the caller needs to check whether
102  * the fields are InvalidOid or not.
103  */
104 TypeCacheEntry *
105 lookup_type_cache(Oid type_id, int flags)
106 {
107         TypeCacheEntry *typentry;
108         bool            found;
109
110         if (TypeCacheHash == NULL)
111         {
112                 /* First time through: initialize the hash table */
113                 HASHCTL         ctl;
114
115                 MemSet(&ctl, 0, sizeof(ctl));
116                 ctl.keysize = sizeof(Oid);
117                 ctl.entrysize = sizeof(TypeCacheEntry);
118                 ctl.hash = oid_hash;
119                 TypeCacheHash = hash_create("Type information cache", 64,
120                                                                         &ctl, HASH_ELEM | HASH_FUNCTION);
121
122                 /* Also set up a callback for relcache SI invalidations */
123                 CacheRegisterRelcacheCallback(TypeCacheRelCallback, (Datum) 0);
124
125                 /* Also make sure CacheMemoryContext exists */
126                 if (!CacheMemoryContext)
127                         CreateCacheMemoryContext();
128         }
129
130         /* Try to look up an existing entry */
131         typentry = (TypeCacheEntry *) hash_search(TypeCacheHash,
132                                                                                           (void *) &type_id,
133                                                                                           HASH_FIND, NULL);
134         if (typentry == NULL)
135         {
136                 /*
137                  * If we didn't find one, we want to make one.  But first look up the
138                  * pg_type row, just to make sure we don't make a cache entry for an
139                  * invalid type OID.
140                  */
141                 HeapTuple       tp;
142                 Form_pg_type typtup;
143
144                 tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_id));
145                 if (!HeapTupleIsValid(tp))
146                         elog(ERROR, "cache lookup failed for type %u", type_id);
147                 typtup = (Form_pg_type) GETSTRUCT(tp);
148                 if (!typtup->typisdefined)
149                         ereport(ERROR,
150                                         (errcode(ERRCODE_UNDEFINED_OBJECT),
151                                          errmsg("type \"%s\" is only a shell",
152                                                         NameStr(typtup->typname))));
153
154                 /* Now make the typcache entry */
155                 typentry = (TypeCacheEntry *) hash_search(TypeCacheHash,
156                                                                                                   (void *) &type_id,
157                                                                                                   HASH_ENTER, &found);
158                 Assert(!found);                 /* it wasn't there a moment ago */
159
160                 MemSet(typentry, 0, sizeof(TypeCacheEntry));
161                 typentry->type_id = type_id;
162                 typentry->typlen = typtup->typlen;
163                 typentry->typbyval = typtup->typbyval;
164                 typentry->typalign = typtup->typalign;
165                 typentry->typtype = typtup->typtype;
166                 typentry->typrelid = typtup->typrelid;
167
168                 ReleaseSysCache(tp);
169         }
170
171         /* If we haven't already found the opclass, try to do so */
172         if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_LT_OPR | TYPECACHE_GT_OPR |
173                                   TYPECACHE_CMP_PROC |
174                                   TYPECACHE_EQ_OPR_FINFO | TYPECACHE_CMP_PROC_FINFO |
175                                   TYPECACHE_BTREE_OPFAMILY)) &&
176                 typentry->btree_opf == InvalidOid)
177         {
178                 Oid                     opclass;
179
180                 opclass = GetDefaultOpClass(type_id, BTREE_AM_OID);
181                 if (OidIsValid(opclass))
182                 {
183                         typentry->btree_opf = get_opclass_family(opclass);
184                         typentry->btree_opintype = get_opclass_input_type(opclass);
185                 }
186                 /* Only care about hash opclass if no btree opclass... */
187                 if (typentry->btree_opf == InvalidOid)
188                 {
189                         if (typentry->hash_opf == InvalidOid)
190                         {
191                                 opclass = GetDefaultOpClass(type_id, HASH_AM_OID);
192                                 if (OidIsValid(opclass))
193                                 {
194                                         typentry->hash_opf = get_opclass_family(opclass);
195                                         typentry->hash_opintype = get_opclass_input_type(opclass);
196                                 }
197                         }
198                 }
199                 else
200                 {
201                         /*
202                          * If we find a btree opclass where previously we only found a
203                          * hash opclass, forget the hash equality operator so we can use
204                          * the btree operator instead.
205                          */
206                         typentry->eq_opr = InvalidOid;
207                         typentry->eq_opr_finfo.fn_oid = InvalidOid;
208                 }
209         }
210
211         /* Look for requested operators and functions */
212         if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO)) &&
213                 typentry->eq_opr == InvalidOid)
214         {
215                 if (typentry->btree_opf != InvalidOid)
216                         typentry->eq_opr = get_opfamily_member(typentry->btree_opf,
217                                                                                                    typentry->btree_opintype,
218                                                                                                    typentry->btree_opintype,
219                                                                                                    BTEqualStrategyNumber);
220                 if (typentry->eq_opr == InvalidOid &&
221                         typentry->hash_opf != InvalidOid)
222                         typentry->eq_opr = get_opfamily_member(typentry->hash_opf,
223                                                                                                    typentry->hash_opintype,
224                                                                                                    typentry->hash_opintype,
225                                                                                                    HTEqualStrategyNumber);
226         }
227         if ((flags & TYPECACHE_LT_OPR) && typentry->lt_opr == InvalidOid)
228         {
229                 if (typentry->btree_opf != InvalidOid)
230                         typentry->lt_opr = get_opfamily_member(typentry->btree_opf,
231                                                                                                    typentry->btree_opintype,
232                                                                                                    typentry->btree_opintype,
233                                                                                                    BTLessStrategyNumber);
234         }
235         if ((flags & TYPECACHE_GT_OPR) && typentry->gt_opr == InvalidOid)
236         {
237                 if (typentry->btree_opf != InvalidOid)
238                         typentry->gt_opr = get_opfamily_member(typentry->btree_opf,
239                                                                                                    typentry->btree_opintype,
240                                                                                                    typentry->btree_opintype,
241                                                                                                    BTGreaterStrategyNumber);
242         }
243         if ((flags & (TYPECACHE_CMP_PROC | TYPECACHE_CMP_PROC_FINFO)) &&
244                 typentry->cmp_proc == InvalidOid)
245         {
246                 if (typentry->btree_opf != InvalidOid)
247                         typentry->cmp_proc = get_opfamily_proc(typentry->btree_opf,
248                                                                                                    typentry->btree_opintype,
249                                                                                                    typentry->btree_opintype,
250                                                                                                    BTORDER_PROC);
251         }
252
253         /*
254          * Set up fmgr lookup info as requested
255          *
256          * Note: we tell fmgr the finfo structures live in CacheMemoryContext,
257          * which is not quite right (they're really in the hash table's private
258          * memory context) but this will do for our purposes.
259          */
260         if ((flags & TYPECACHE_EQ_OPR_FINFO) &&
261                 typentry->eq_opr_finfo.fn_oid == InvalidOid &&
262                 typentry->eq_opr != InvalidOid)
263         {
264                 Oid                     eq_opr_func;
265
266                 eq_opr_func = get_opcode(typentry->eq_opr);
267                 if (eq_opr_func != InvalidOid)
268                         fmgr_info_cxt(eq_opr_func, &typentry->eq_opr_finfo,
269                                                   CacheMemoryContext);
270         }
271         if ((flags & TYPECACHE_CMP_PROC_FINFO) &&
272                 typentry->cmp_proc_finfo.fn_oid == InvalidOid &&
273                 typentry->cmp_proc != InvalidOid)
274         {
275                 fmgr_info_cxt(typentry->cmp_proc, &typentry->cmp_proc_finfo,
276                                           CacheMemoryContext);
277         }
278
279         /*
280          * If it's a composite type (row type), get tupdesc if requested
281          */
282         if ((flags & TYPECACHE_TUPDESC) &&
283                 typentry->tupDesc == NULL &&
284                 typentry->typtype == TYPTYPE_COMPOSITE)
285         {
286                 Relation        rel;
287
288                 if (!OidIsValid(typentry->typrelid))    /* should not happen */
289                         elog(ERROR, "invalid typrelid for composite type %u",
290                                  typentry->type_id);
291                 rel = relation_open(typentry->typrelid, AccessShareLock);
292                 Assert(rel->rd_rel->reltype == typentry->type_id);
293
294                 /*
295                  * Link to the tupdesc and increment its refcount (we assert it's a
296                  * refcounted descriptor).      We don't use IncrTupleDescRefCount() for
297                  * this, because the reference mustn't be entered in the current
298                  * resource owner; it can outlive the current query.
299                  */
300                 typentry->tupDesc = RelationGetDescr(rel);
301
302                 Assert(typentry->tupDesc->tdrefcount > 0);
303                 typentry->tupDesc->tdrefcount++;
304
305                 relation_close(rel, AccessShareLock);
306         }
307
308         return typentry;
309 }
310
311 /*
312  * lookup_rowtype_tupdesc_internal --- internal routine to lookup a rowtype
313  *
314  * Same API as lookup_rowtype_tupdesc_noerror, but the returned tupdesc
315  * hasn't had its refcount bumped.
316  */
317 static TupleDesc
318 lookup_rowtype_tupdesc_internal(Oid type_id, int32 typmod, bool noError)
319 {
320         if (type_id != RECORDOID)
321         {
322                 /*
323                  * It's a named composite type, so use the regular typcache.
324                  */
325                 TypeCacheEntry *typentry;
326
327                 typentry = lookup_type_cache(type_id, TYPECACHE_TUPDESC);
328                 if (typentry->tupDesc == NULL && !noError)
329                         ereport(ERROR,
330                                         (errcode(ERRCODE_WRONG_OBJECT_TYPE),
331                                          errmsg("type %s is not composite",
332                                                         format_type_be(type_id))));
333                 return typentry->tupDesc;
334         }
335         else
336         {
337                 /*
338                  * It's a transient record type, so look in our record-type table.
339                  */
340                 if (typmod < 0 || typmod >= NextRecordTypmod)
341                 {
342                         if (!noError)
343                                 ereport(ERROR,
344                                                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
345                                                  errmsg("record type has not been registered")));
346                         return NULL;
347                 }
348                 return RecordCacheArray[typmod];
349         }
350 }
351
352 /*
353  * lookup_rowtype_tupdesc
354  *
355  * Given a typeid/typmod that should describe a known composite type,
356  * return the tuple descriptor for the type.  Will ereport on failure.
357  *
358  * Note: on success, we increment the refcount of the returned TupleDesc,
359  * and log the reference in CurrentResourceOwner.  Caller should call
360  * ReleaseTupleDesc or DecrTupleDescRefCount when done using the tupdesc.
361  */
362 TupleDesc
363 lookup_rowtype_tupdesc(Oid type_id, int32 typmod)
364 {
365         TupleDesc       tupDesc;
366
367         tupDesc = lookup_rowtype_tupdesc_internal(type_id, typmod, false);
368         IncrTupleDescRefCount(tupDesc);
369         return tupDesc;
370 }
371
372 /*
373  * lookup_rowtype_tupdesc_noerror
374  *
375  * As above, but if the type is not a known composite type and noError
376  * is true, returns NULL instead of ereport'ing.  (Note that if a bogus
377  * type_id is passed, you'll get an ereport anyway.)
378  */
379 TupleDesc
380 lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod, bool noError)
381 {
382         TupleDesc       tupDesc;
383
384         tupDesc = lookup_rowtype_tupdesc_internal(type_id, typmod, noError);
385         if (tupDesc != NULL)
386                 IncrTupleDescRefCount(tupDesc);
387         return tupDesc;
388 }
389
390 /*
391  * lookup_rowtype_tupdesc_copy
392  *
393  * Like lookup_rowtype_tupdesc(), but the returned TupleDesc has been
394  * copied into the CurrentMemoryContext and is not reference-counted.
395  */
396 TupleDesc
397 lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod)
398 {
399         TupleDesc       tmp;
400
401         tmp = lookup_rowtype_tupdesc_internal(type_id, typmod, false);
402         return CreateTupleDescCopyConstr(tmp);
403 }
404
405
406 /*
407  * assign_record_type_typmod
408  *
409  * Given a tuple descriptor for a RECORD type, find or create a cache entry
410  * for the type, and set the tupdesc's tdtypmod field to a value that will
411  * identify this cache entry to lookup_rowtype_tupdesc.
412  */
413 void
414 assign_record_type_typmod(TupleDesc tupDesc)
415 {
416         RecordCacheEntry *recentry;
417         TupleDesc       entDesc;
418         Oid                     hashkey[REC_HASH_KEYS];
419         bool            found;
420         int                     i;
421         ListCell   *l;
422         int32           newtypmod;
423         MemoryContext oldcxt;
424
425         Assert(tupDesc->tdtypeid == RECORDOID);
426
427         if (RecordCacheHash == NULL)
428         {
429                 /* First time through: initialize the hash table */
430                 HASHCTL         ctl;
431
432                 MemSet(&ctl, 0, sizeof(ctl));
433                 ctl.keysize = REC_HASH_KEYS * sizeof(Oid);
434                 ctl.entrysize = sizeof(RecordCacheEntry);
435                 ctl.hash = tag_hash;
436                 RecordCacheHash = hash_create("Record information cache", 64,
437                                                                           &ctl, HASH_ELEM | HASH_FUNCTION);
438
439                 /* Also make sure CacheMemoryContext exists */
440                 if (!CacheMemoryContext)
441                         CreateCacheMemoryContext();
442         }
443
444         /* Find or create a hashtable entry for this hash class */
445         MemSet(hashkey, 0, sizeof(hashkey));
446         for (i = 0; i < tupDesc->natts; i++)
447         {
448                 if (i >= REC_HASH_KEYS)
449                         break;
450                 hashkey[i] = tupDesc->attrs[i]->atttypid;
451         }
452         recentry = (RecordCacheEntry *) hash_search(RecordCacheHash,
453                                                                                                 (void *) hashkey,
454                                                                                                 HASH_ENTER, &found);
455         if (!found)
456         {
457                 /* New entry ... hash_search initialized only the hash key */
458                 recentry->tupdescs = NIL;
459         }
460
461         /* Look for existing record cache entry */
462         foreach(l, recentry->tupdescs)
463         {
464                 entDesc = (TupleDesc) lfirst(l);
465                 if (equalTupleDescs(tupDesc, entDesc))
466                 {
467                         tupDesc->tdtypmod = entDesc->tdtypmod;
468                         return;
469                 }
470         }
471
472         /* Not present, so need to manufacture an entry */
473         oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
474
475         if (RecordCacheArray == NULL)
476         {
477                 RecordCacheArray = (TupleDesc *) palloc(64 * sizeof(TupleDesc));
478                 RecordCacheArrayLen = 64;
479         }
480         else if (NextRecordTypmod >= RecordCacheArrayLen)
481         {
482                 int32           newlen = RecordCacheArrayLen * 2;
483
484                 RecordCacheArray = (TupleDesc *) repalloc(RecordCacheArray,
485                                                                                                   newlen * sizeof(TupleDesc));
486                 RecordCacheArrayLen = newlen;
487         }
488
489         /* if fail in subrs, no damage except possibly some wasted memory... */
490         entDesc = CreateTupleDescCopy(tupDesc);
491         recentry->tupdescs = lcons(entDesc, recentry->tupdescs);
492         /* mark it as a reference-counted tupdesc */
493         entDesc->tdrefcount = 1;
494         /* now it's safe to advance NextRecordTypmod */
495         newtypmod = NextRecordTypmod++;
496         entDesc->tdtypmod = newtypmod;
497         RecordCacheArray[newtypmod] = entDesc;
498
499         /* report to caller as well */
500         tupDesc->tdtypmod = newtypmod;
501
502         MemoryContextSwitchTo(oldcxt);
503 }
504
505 /*
506  * TypeCacheRelCallback
507  *              Relcache inval callback function
508  *
509  * Delete the cached tuple descriptor (if any) for the given rel's composite
510  * type, or for all composite types if relid == InvalidOid.
511  *
512  * This is called when a relcache invalidation event occurs for the given
513  * relid.  We must scan the whole typcache hash since we don't know the
514  * type OID corresponding to the relid.  We could do a direct search if this
515  * were a syscache-flush callback on pg_type, but then we would need all
516  * ALTER-TABLE-like commands that could modify a rowtype to issue syscache
517  * invals against the rel's pg_type OID.  The extra SI signaling could very
518  * well cost more than we'd save, since in most usages there are not very
519  * many entries in a backend's typcache.  The risk of bugs-of-omission seems
520  * high, too.
521  *
522  * Another possibility, with only localized impact, is to maintain a second
523  * hashtable that indexes composite-type typcache entries by their typrelid.
524  * But it's still not clear it's worth the trouble.
525  */
526 static void
527 TypeCacheRelCallback(Datum arg, Oid relid)
528 {
529         HASH_SEQ_STATUS status;
530         TypeCacheEntry *typentry;
531
532         /* TypeCacheHash must exist, else this callback wouldn't be registered */
533         hash_seq_init(&status, TypeCacheHash);
534         while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
535         {
536                 if (typentry->tupDesc == NULL)
537                         continue;       /* not composite, or tupdesc hasn't been requested */
538
539                 /* Delete if match, or if we're zapping all composite types */
540                 if (relid == typentry->typrelid || relid == InvalidOid)
541                 {
542                         /*
543                          * Release our refcount, and free the tupdesc if none remain.
544                          * (Can't use DecrTupleDescRefCount because this reference is not
545                          * logged in current resource owner.)
546                          */
547                         Assert(typentry->tupDesc->tdrefcount > 0);
548                         if (--typentry->tupDesc->tdrefcount == 0)
549                                 FreeTupleDesc(typentry->tupDesc);
550                         typentry->tupDesc = NULL;
551                 }
552         }
553 }