/*------------------------------------------------------------------------- * * typcache.c * POSTGRES type cache code * * The type cache exists to speed lookup of certain information about data * types that is not directly available from a type's pg_type row. In * particular, we use a type's default btree opclass, or the default hash * opclass if no btree opclass exists, to determine which operators should * be used for grouping and sorting the type (GROUP BY, ORDER BY ASC/DESC). * * Several seemingly-odd choices have been made to support use of the type * cache by the generic array comparison routines array_eq() and array_cmp(). * Because these routines are used as index support operations, they cannot * leak memory. To allow them to execute efficiently, all information that * either of them would like to re-use across calls is made available in the * type cache. * * Once created, a type cache entry lives as long as the backend does, so * there is no need for a call to release a cache entry. (For present uses, * it would be okay to flush type cache entries at the ends of transactions, * if we needed to reclaim space.) * * There is presently no provision for clearing out a cache entry if the * stored data becomes obsolete. (The code will work if a type acquires * opclasses it didn't have before while a backend runs --- but not if the * definition of an existing opclass is altered.) However, the relcache * doesn't cope with opclasses changing under it, either, so this seems * a low-priority problem. * * * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * $Header: /cvsroot/pgsql/src/backend/utils/cache/typcache.c,v 1.3 2003/11/12 21:15:56 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/genam.h" #include "access/heapam.h" #include "access/hash.h" #include "access/nbtree.h" #include "catalog/catname.h" #include "catalog/indexing.h" #include "catalog/pg_am.h" #include "catalog/pg_opclass.h" #include "parser/parse_coerce.h" #include "utils/builtins.h" #include "utils/catcache.h" #include "utils/fmgroids.h" #include "utils/hsearch.h" #include "utils/lsyscache.h" #include "utils/typcache.h" static HTAB *TypeCacheHash = NULL; static Oid lookup_default_opclass(Oid type_id, Oid am_id); /* * lookup_type_cache * * Fetch the type cache entry for the specified datatype, and make sure that * all the fields requested by bits in 'flags' are valid. * * The result is never NULL --- we will elog() if the passed type OID is * invalid. Note however that we may fail to find one or more of the * requested opclass-dependent fields; the caller needs to check whether * the fields are InvalidOid or not. */ TypeCacheEntry * lookup_type_cache(Oid type_id, int flags) { TypeCacheEntry *typentry; bool found; if (TypeCacheHash == NULL) { /* First time through: initialize the hash table */ HASHCTL ctl; if (!CacheMemoryContext) CreateCacheMemoryContext(); MemSet(&ctl, 0, sizeof(ctl)); ctl.keysize = sizeof(Oid); ctl.entrysize = sizeof(TypeCacheEntry); ctl.hash = tag_hash; TypeCacheHash = hash_create("Type information cache", 64, &ctl, HASH_ELEM | HASH_FUNCTION); } /* Try to look up an existing entry */ typentry = (TypeCacheEntry *) hash_search(TypeCacheHash, (void *) &type_id, HASH_FIND, NULL); if (typentry == NULL) { /* * If we didn't find one, we want to make one. But first get the * required info from the pg_type row, just to make sure we don't * make a cache entry for an invalid type OID. */ int16 typlen; bool typbyval; char typalign; get_typlenbyvalalign(type_id, &typlen, &typbyval, &typalign); typentry = (TypeCacheEntry *) hash_search(TypeCacheHash, (void *) &type_id, HASH_ENTER, &found); if (typentry == NULL) ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); Assert(!found); /* it wasn't there a moment ago */ MemSet(typentry, 0, sizeof(TypeCacheEntry)); typentry->type_id = type_id; typentry->typlen = typlen; typentry->typbyval = typbyval; typentry->typalign = typalign; } /* If we haven't already found the opclass, try to do so */ if (flags != 0 && typentry->btree_opc == InvalidOid) { typentry->btree_opc = lookup_default_opclass(type_id, BTREE_AM_OID); /* Only care about hash opclass if no btree opclass... */ if (typentry->btree_opc == InvalidOid) { if (typentry->hash_opc == InvalidOid) typentry->hash_opc = lookup_default_opclass(type_id, HASH_AM_OID); } else { /* * If we find a btree opclass where previously we only found * a hash opclass, forget the hash equality operator so we * can use the btree operator instead. */ typentry->eq_opr = InvalidOid; typentry->eq_opr_finfo.fn_oid = InvalidOid; } } /* Look for requested operators and functions */ if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO)) && typentry->eq_opr == InvalidOid) { if (typentry->btree_opc != InvalidOid) typentry->eq_opr = get_opclass_member(typentry->btree_opc, InvalidOid, BTEqualStrategyNumber); if (typentry->eq_opr == InvalidOid && typentry->hash_opc != InvalidOid) typentry->eq_opr = get_opclass_member(typentry->hash_opc, InvalidOid, HTEqualStrategyNumber); } if ((flags & TYPECACHE_LT_OPR) && typentry->lt_opr == InvalidOid) { if (typentry->btree_opc != InvalidOid) typentry->lt_opr = get_opclass_member(typentry->btree_opc, InvalidOid, BTLessStrategyNumber); } if ((flags & TYPECACHE_GT_OPR) && typentry->gt_opr == InvalidOid) { if (typentry->btree_opc != InvalidOid) typentry->gt_opr = get_opclass_member(typentry->btree_opc, InvalidOid, BTGreaterStrategyNumber); } if ((flags & (TYPECACHE_CMP_PROC | TYPECACHE_CMP_PROC_FINFO)) && typentry->cmp_proc == InvalidOid) { if (typentry->btree_opc != InvalidOid) typentry->cmp_proc = get_opclass_proc(typentry->btree_opc, InvalidOid, BTORDER_PROC); } /* * Set up fmgr lookup info as requested * * Note: we tell fmgr the finfo structures live in CacheMemoryContext, * which is not quite right (they're really in DynaHashContext) but this * will do for our purposes. */ if ((flags & TYPECACHE_EQ_OPR_FINFO) && typentry->eq_opr_finfo.fn_oid == InvalidOid && typentry->eq_opr != InvalidOid) { Oid eq_opr_func; eq_opr_func = get_opcode(typentry->eq_opr); if (eq_opr_func != InvalidOid) fmgr_info_cxt(eq_opr_func, &typentry->eq_opr_finfo, CacheMemoryContext); } if ((flags & TYPECACHE_CMP_PROC_FINFO) && typentry->cmp_proc_finfo.fn_oid == InvalidOid && typentry->cmp_proc != InvalidOid) { fmgr_info_cxt(typentry->cmp_proc, &typentry->cmp_proc_finfo, CacheMemoryContext); } return typentry; } /* * lookup_default_opclass * * Given the OIDs of a datatype and an access method, find the default * operator class, if any. Returns InvalidOid if there is none. */ static Oid lookup_default_opclass(Oid type_id, Oid am_id) { int nexact = 0; int ncompatible = 0; Oid exactOid = InvalidOid; Oid compatibleOid = InvalidOid; Relation rel; ScanKeyData skey[1]; SysScanDesc scan; HeapTuple tup; /* If it's a domain, look at the base type instead */ type_id = getBaseType(type_id); /* * We scan through all the opclasses available for the access method, * looking for one that is marked default and matches the target type * (either exactly or binary-compatibly, but prefer an exact match). * * We could find more than one binary-compatible match, in which case we * require the user to specify which one he wants. If we find more * than one exact match, then someone put bogus entries in pg_opclass. * * This is the same logic as GetDefaultOpClass() in indexcmds.c, except * that we consider all opclasses, regardless of the current search path. */ rel = heap_openr(OperatorClassRelationName, AccessShareLock); ScanKeyInit(&skey[0], Anum_pg_opclass_opcamid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(am_id)); scan = systable_beginscan(rel, OpclassAmNameNspIndex, true, SnapshotNow, 1, skey); while (HeapTupleIsValid(tup = systable_getnext(scan))) { Form_pg_opclass opclass = (Form_pg_opclass) GETSTRUCT(tup); if (opclass->opcdefault) { if (opclass->opcintype == type_id) { nexact++; exactOid = HeapTupleGetOid(tup); } else if (IsBinaryCoercible(type_id, opclass->opcintype)) { ncompatible++; compatibleOid = HeapTupleGetOid(tup); } } } systable_endscan(scan); heap_close(rel, AccessShareLock); if (nexact == 1) return exactOid; if (nexact != 0) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("there are multiple default operator classes for data type %s", format_type_be(type_id)))); if (ncompatible == 1) return compatibleOid; return InvalidOid; }