OSDN Git Service

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