OSDN Git Service

Change search for default operator classes so that it examines all opclasses
[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-2005, 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.17 2006/02/10 19:01:12 tgl Exp $
40  *
41  *-------------------------------------------------------------------------
42  */
43 #include "postgres.h"
44
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"
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
91 /*
92  * lookup_type_cache
93  *
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.
96  *
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.
101  */
102 TypeCacheEntry *
103 lookup_type_cache(Oid type_id, int flags)
104 {
105         TypeCacheEntry *typentry;
106         bool            found;
107
108         if (TypeCacheHash == NULL)
109         {
110                 /* First time through: initialize the hash table */
111                 HASHCTL         ctl;
112
113                 if (!CacheMemoryContext)
114                         CreateCacheMemoryContext();
115
116                 MemSet(&ctl, 0, sizeof(ctl));
117                 ctl.keysize = sizeof(Oid);
118                 ctl.entrysize = sizeof(TypeCacheEntry);
119                 ctl.hash = oid_hash;
120                 TypeCacheHash = hash_create("Type information cache", 64,
121                                                                         &ctl, HASH_ELEM | HASH_FUNCTION);
122         }
123
124         /* Try to look up an existing entry */
125         typentry = (TypeCacheEntry *) hash_search(TypeCacheHash,
126                                                                                           (void *) &type_id,
127                                                                                           HASH_FIND, NULL);
128         if (typentry == NULL)
129         {
130                 /*
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
133                  * invalid type OID.
134                  */
135                 HeapTuple       tp;
136                 Form_pg_type typtup;
137
138                 tp = SearchSysCache(TYPEOID,
139                                                         ObjectIdGetDatum(type_id),
140                                                         0, 0, 0);
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)
145                         ereport(ERROR,
146                                         (errcode(ERRCODE_UNDEFINED_OBJECT),
147                                          errmsg("type \"%s\" is only a shell",
148                                                         NameStr(typtup->typname))));
149
150                 /* Now make the typcache entry */
151                 typentry = (TypeCacheEntry *) hash_search(TypeCacheHash,
152                                                                                                   (void *) &type_id,
153                                                                                                   HASH_ENTER, &found);
154                 Assert(!found);                 /* it wasn't there a moment ago */
155
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;
163
164                 ReleaseSysCache(tp);
165         }
166
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 |
169                                   TYPECACHE_CMP_PROC |
170                                   TYPECACHE_EQ_OPR_FINFO | TYPECACHE_CMP_PROC_FINFO)) &&
171                 typentry->btree_opc == InvalidOid)
172         {
173                 typentry->btree_opc = GetDefaultOpClass(type_id,
174                                                                                                 BTREE_AM_OID);
175                 /* Only care about hash opclass if no btree opclass... */
176                 if (typentry->btree_opc == InvalidOid)
177                 {
178                         if (typentry->hash_opc == InvalidOid)
179                                 typentry->hash_opc = GetDefaultOpClass(type_id,
180                                                                                                            HASH_AM_OID);
181                 }
182                 else
183                 {
184                         /*
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.
188                          */
189                         typentry->eq_opr = InvalidOid;
190                         typentry->eq_opr_finfo.fn_oid = InvalidOid;
191                 }
192         }
193
194         /* Look for requested operators and functions */
195         if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO)) &&
196                 typentry->eq_opr == InvalidOid)
197         {
198                 if (typentry->btree_opc != InvalidOid)
199                         typentry->eq_opr = get_opclass_member(typentry->btree_opc,
200                                                                                                   InvalidOid,
201                                                                                                   BTEqualStrategyNumber);
202                 if (typentry->eq_opr == InvalidOid &&
203                         typentry->hash_opc != InvalidOid)
204                         typentry->eq_opr = get_opclass_member(typentry->hash_opc,
205                                                                                                   InvalidOid,
206                                                                                                   HTEqualStrategyNumber);
207         }
208         if ((flags & TYPECACHE_LT_OPR) && typentry->lt_opr == InvalidOid)
209         {
210                 if (typentry->btree_opc != InvalidOid)
211                         typentry->lt_opr = get_opclass_member(typentry->btree_opc,
212                                                                                                   InvalidOid,
213                                                                                                   BTLessStrategyNumber);
214         }
215         if ((flags & TYPECACHE_GT_OPR) && typentry->gt_opr == InvalidOid)
216         {
217                 if (typentry->btree_opc != InvalidOid)
218                         typentry->gt_opr = get_opclass_member(typentry->btree_opc,
219                                                                                                   InvalidOid,
220                                                                                                   BTGreaterStrategyNumber);
221         }
222         if ((flags & (TYPECACHE_CMP_PROC | TYPECACHE_CMP_PROC_FINFO)) &&
223                 typentry->cmp_proc == InvalidOid)
224         {
225                 if (typentry->btree_opc != InvalidOid)
226                         typentry->cmp_proc = get_opclass_proc(typentry->btree_opc,
227                                                                                                   InvalidOid,
228                                                                                                   BTORDER_PROC);
229         }
230
231         /*
232          * Set up fmgr lookup info as requested
233          *
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.
237          */
238         if ((flags & TYPECACHE_EQ_OPR_FINFO) &&
239                 typentry->eq_opr_finfo.fn_oid == InvalidOid &&
240                 typentry->eq_opr != InvalidOid)
241         {
242                 Oid                     eq_opr_func;
243
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,
247                                                   CacheMemoryContext);
248         }
249         if ((flags & TYPECACHE_CMP_PROC_FINFO) &&
250                 typentry->cmp_proc_finfo.fn_oid == InvalidOid &&
251                 typentry->cmp_proc != InvalidOid)
252         {
253                 fmgr_info_cxt(typentry->cmp_proc, &typentry->cmp_proc_finfo,
254                                           CacheMemoryContext);
255         }
256
257         /*
258          * If it's a composite type (row type), get tupdesc if requested
259          */
260         if ((flags & TYPECACHE_TUPDESC) &&
261                 typentry->tupDesc == NULL &&
262                 typentry->typtype == 'c')
263         {
264                 Relation        rel;
265
266                 if (!OidIsValid(typentry->typrelid))    /* should not happen */
267                         elog(ERROR, "invalid typrelid for composite type %u",
268                                  typentry->type_id);
269                 rel = relation_open(typentry->typrelid, AccessShareLock);
270                 Assert(rel->rd_rel->reltype == typentry->type_id);
271
272                 /*
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.
276                  */
277                 typentry->tupDesc = RelationGetDescr(rel);
278
279                 relation_close(rel, AccessShareLock);
280         }
281
282         return typentry;
283 }
284
285 /*
286  * lookup_rowtype_tupdesc
287  *
288  * Given a typeid/typmod that should describe a known composite type,
289  * return the tuple descriptor for the type.  Will ereport on failure.
290  *
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.
293  */
294 TupleDesc
295 lookup_rowtype_tupdesc(Oid type_id, int32 typmod)
296 {
297         return lookup_rowtype_tupdesc_noerror(type_id, typmod, false);
298 }
299
300 /*
301  * lookup_rowtype_tupdesc_noerror
302  *
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.)
306  */
307 TupleDesc
308 lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod, bool noError)
309 {
310         if (type_id != RECORDOID)
311         {
312                 /*
313                  * It's a named composite type, so use the regular typcache.
314                  */
315                 TypeCacheEntry *typentry;
316
317                 typentry = lookup_type_cache(type_id, TYPECACHE_TUPDESC);
318                 if (typentry->tupDesc == NULL && !noError)
319                         ereport(ERROR,
320                                         (errcode(ERRCODE_WRONG_OBJECT_TYPE),
321                                          errmsg("type %s is not composite",
322                                                         format_type_be(type_id))));
323                 return typentry->tupDesc;
324         }
325         else
326         {
327                 /*
328                  * It's a transient record type, so look in our record-type table.
329                  */
330                 if (typmod < 0 || typmod >= NextRecordTypmod)
331                 {
332                         if (!noError)
333                                 ereport(ERROR,
334                                                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
335                                                  errmsg("record type has not been registered")));
336                         return NULL;
337                 }
338                 return RecordCacheArray[typmod];
339         }
340 }
341
342
343 /*
344  * assign_record_type_typmod
345  *
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.
349  */
350 void
351 assign_record_type_typmod(TupleDesc tupDesc)
352 {
353         RecordCacheEntry *recentry;
354         TupleDesc       entDesc;
355         Oid                     hashkey[REC_HASH_KEYS];
356         bool            found;
357         int                     i;
358         ListCell   *l;
359         int32           newtypmod;
360         MemoryContext oldcxt;
361
362         Assert(tupDesc->tdtypeid == RECORDOID);
363
364         if (RecordCacheHash == NULL)
365         {
366                 /* First time through: initialize the hash table */
367                 HASHCTL         ctl;
368
369                 if (!CacheMemoryContext)
370                         CreateCacheMemoryContext();
371
372                 MemSet(&ctl, 0, sizeof(ctl));
373                 ctl.keysize = REC_HASH_KEYS * sizeof(Oid);
374                 ctl.entrysize = sizeof(RecordCacheEntry);
375                 ctl.hash = tag_hash;
376                 RecordCacheHash = hash_create("Record information cache", 64,
377                                                                           &ctl, HASH_ELEM | HASH_FUNCTION);
378         }
379
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++)
383         {
384                 if (i >= REC_HASH_KEYS)
385                         break;
386                 hashkey[i] = tupDesc->attrs[i]->atttypid;
387         }
388         recentry = (RecordCacheEntry *) hash_search(RecordCacheHash,
389                                                                                                 (void *) hashkey,
390                                                                                                 HASH_ENTER, &found);
391         if (!found)
392         {
393                 /* New entry ... hash_search initialized only the hash key */
394                 recentry->tupdescs = NIL;
395         }
396
397         /* Look for existing record cache entry */
398         foreach(l, recentry->tupdescs)
399         {
400                 entDesc = (TupleDesc) lfirst(l);
401                 if (equalTupleDescs(tupDesc, entDesc))
402                 {
403                         tupDesc->tdtypmod = entDesc->tdtypmod;
404                         return;
405                 }
406         }
407
408         /* Not present, so need to manufacture an entry */
409         oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
410
411         if (RecordCacheArray == NULL)
412         {
413                 RecordCacheArray = (TupleDesc *) palloc(64 * sizeof(TupleDesc));
414                 RecordCacheArrayLen = 64;
415         }
416         else if (NextRecordTypmod >= RecordCacheArrayLen)
417         {
418                 int32           newlen = RecordCacheArrayLen * 2;
419
420                 RecordCacheArray = (TupleDesc *) repalloc(RecordCacheArray,
421                                                                                                   newlen * sizeof(TupleDesc));
422                 RecordCacheArrayLen = newlen;
423         }
424
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;
432
433         /* report to caller as well */
434         tupDesc->tdtypmod = newtypmod;
435
436         MemoryContextSwitchTo(oldcxt);
437 }
438
439 /*
440  * flush_rowtype_cache
441  *
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.
445  */
446 void
447 flush_rowtype_cache(Oid type_id)
448 {
449         TypeCacheEntry *typentry;
450
451         if (TypeCacheHash == NULL)
452                 return;                                 /* no table, so certainly no entry */
453
454         typentry = (TypeCacheEntry *) hash_search(TypeCacheHash,
455                                                                                           (void *) &type_id,
456                                                                                           HASH_FIND, NULL);
457         if (typentry == NULL)
458                 return;                                 /* no matching entry */
459
460         typentry->tupDesc = NULL;
461 }