* 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
+ * types that is not directly available from a type's pg_type row. For
+ * example, 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
+ * Because those 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.
* entry, since that may need to change as a consequence of ALTER TABLE.
*
*
- * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/cache/typcache.c,v 1.17 2006/02/10 19:01:12 tgl Exp $
+ * src/backend/utils/cache/typcache.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
-#include "access/heapam.h"
#include "access/hash.h"
+#include "access/heapam.h"
#include "access/nbtree.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "utils/builtins.h"
-#include "utils/catcache.h"
-#include "utils/hsearch.h"
+#include "utils/inval.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
static int32 RecordCacheArrayLen = 0; /* allocated length of array */
static int32 NextRecordTypmod = 0; /* number of entries used */
+static void TypeCacheRelCallback(Datum arg, Oid relid);
+
/*
* lookup_type_cache
/* 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 = oid_hash;
TypeCacheHash = hash_create("Type information cache", 64,
&ctl, HASH_ELEM | HASH_FUNCTION);
+
+ /* Also set up a callback for relcache SI invalidations */
+ CacheRegisterRelcacheCallback(TypeCacheRelCallback, (Datum) 0);
+
+ /* Also make sure CacheMemoryContext exists */
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
}
/* Try to look up an existing entry */
HeapTuple tp;
Form_pg_type typtup;
- tp = SearchSysCache(TYPEOID,
- ObjectIdGetDatum(type_id),
- 0, 0, 0);
+ tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_id));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for type %u", type_id);
typtup = (Form_pg_type) GETSTRUCT(tp);
/* If we haven't already found the opclass, try to do so */
if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_LT_OPR | TYPECACHE_GT_OPR |
TYPECACHE_CMP_PROC |
- TYPECACHE_EQ_OPR_FINFO | TYPECACHE_CMP_PROC_FINFO)) &&
- typentry->btree_opc == InvalidOid)
+ TYPECACHE_EQ_OPR_FINFO | TYPECACHE_CMP_PROC_FINFO |
+ TYPECACHE_BTREE_OPFAMILY)) &&
+ typentry->btree_opf == InvalidOid)
{
- typentry->btree_opc = GetDefaultOpClass(type_id,
- BTREE_AM_OID);
+ Oid opclass;
+
+ opclass = GetDefaultOpClass(type_id, BTREE_AM_OID);
+ if (OidIsValid(opclass))
+ {
+ typentry->btree_opf = get_opclass_family(opclass);
+ typentry->btree_opintype = get_opclass_input_type(opclass);
+ }
/* Only care about hash opclass if no btree opclass... */
- if (typentry->btree_opc == InvalidOid)
+ if (typentry->btree_opf == InvalidOid)
{
- if (typentry->hash_opc == InvalidOid)
- typentry->hash_opc = GetDefaultOpClass(type_id,
- HASH_AM_OID);
+ if (typentry->hash_opf == InvalidOid)
+ {
+ opclass = GetDefaultOpClass(type_id, HASH_AM_OID);
+ if (OidIsValid(opclass))
+ {
+ typentry->hash_opf = get_opclass_family(opclass);
+ typentry->hash_opintype = get_opclass_input_type(opclass);
+ }
+ }
}
else
{
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->btree_opf != InvalidOid)
+ typentry->eq_opr = get_opfamily_member(typentry->btree_opf,
+ typentry->btree_opintype,
+ typentry->btree_opintype,
+ BTEqualStrategyNumber);
if (typentry->eq_opr == InvalidOid &&
- typentry->hash_opc != InvalidOid)
- typentry->eq_opr = get_opclass_member(typentry->hash_opc,
- InvalidOid,
- HTEqualStrategyNumber);
+ typentry->hash_opf != InvalidOid)
+ typentry->eq_opr = get_opfamily_member(typentry->hash_opf,
+ typentry->hash_opintype,
+ typentry->hash_opintype,
+ 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 (typentry->btree_opf != InvalidOid)
+ typentry->lt_opr = get_opfamily_member(typentry->btree_opf,
+ typentry->btree_opintype,
+ typentry->btree_opintype,
+ 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 (typentry->btree_opf != InvalidOid)
+ typentry->gt_opr = get_opfamily_member(typentry->btree_opf,
+ typentry->btree_opintype,
+ typentry->btree_opintype,
+ 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);
+ if (typentry->btree_opf != InvalidOid)
+ typentry->cmp_proc = get_opfamily_proc(typentry->btree_opf,
+ typentry->btree_opintype,
+ typentry->btree_opintype,
+ 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.
+ * which is not quite right (they're really in the hash table's private
+ * memory context) but this will do for our purposes.
*/
if ((flags & TYPECACHE_EQ_OPR_FINFO) &&
typentry->eq_opr_finfo.fn_oid == InvalidOid &&
*/
if ((flags & TYPECACHE_TUPDESC) &&
typentry->tupDesc == NULL &&
- typentry->typtype == 'c')
+ typentry->typtype == TYPTYPE_COMPOSITE)
{
Relation rel;
Assert(rel->rd_rel->reltype == typentry->type_id);
/*
- * Notice that we simply store a link to the relcache's tupdesc. Since
- * we are relying on relcache to detect cache flush events, there's
- * not a lot of point to maintaining an independent copy.
+ * Link to the tupdesc and increment its refcount (we assert it's a
+ * refcounted descriptor). We don't use IncrTupleDescRefCount() for
+ * this, because the reference mustn't be entered in the current
+ * resource owner; it can outlive the current query.
*/
typentry->tupDesc = RelationGetDescr(rel);
+ Assert(typentry->tupDesc->tdrefcount > 0);
+ typentry->tupDesc->tdrefcount++;
+
relation_close(rel, AccessShareLock);
}
}
/*
- * lookup_rowtype_tupdesc
- *
- * Given a typeid/typmod that should describe a known composite type,
- * return the tuple descriptor for the type. Will ereport on failure.
- *
- * Note: returned TupleDesc points to cached copy; caller must copy it
- * if intending to scribble on it or keep a reference for a long time.
- */
-TupleDesc
-lookup_rowtype_tupdesc(Oid type_id, int32 typmod)
-{
- return lookup_rowtype_tupdesc_noerror(type_id, typmod, false);
-}
-
-/*
- * lookup_rowtype_tupdesc_noerror
+ * lookup_rowtype_tupdesc_internal --- internal routine to lookup a rowtype
*
- * As above, but if the type is not a known composite type and noError
- * is true, returns NULL instead of ereport'ing. (Note that if a bogus
- * type_id is passed, you'll get an ereport anyway.)
+ * Same API as lookup_rowtype_tupdesc_noerror, but the returned tupdesc
+ * hasn't had its refcount bumped.
*/
-TupleDesc
-lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod, bool noError)
+static TupleDesc
+lookup_rowtype_tupdesc_internal(Oid type_id, int32 typmod, bool noError)
{
if (type_id != RECORDOID)
{
}
}
+/*
+ * lookup_rowtype_tupdesc
+ *
+ * Given a typeid/typmod that should describe a known composite type,
+ * return the tuple descriptor for the type. Will ereport on failure.
+ *
+ * Note: on success, we increment the refcount of the returned TupleDesc,
+ * and log the reference in CurrentResourceOwner. Caller should call
+ * ReleaseTupleDesc or DecrTupleDescRefCount when done using the tupdesc.
+ */
+TupleDesc
+lookup_rowtype_tupdesc(Oid type_id, int32 typmod)
+{
+ TupleDesc tupDesc;
+
+ tupDesc = lookup_rowtype_tupdesc_internal(type_id, typmod, false);
+ IncrTupleDescRefCount(tupDesc);
+ return tupDesc;
+}
+
+/*
+ * lookup_rowtype_tupdesc_noerror
+ *
+ * As above, but if the type is not a known composite type and noError
+ * is true, returns NULL instead of ereport'ing. (Note that if a bogus
+ * type_id is passed, you'll get an ereport anyway.)
+ */
+TupleDesc
+lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod, bool noError)
+{
+ TupleDesc tupDesc;
+
+ tupDesc = lookup_rowtype_tupdesc_internal(type_id, typmod, noError);
+ if (tupDesc != NULL)
+ IncrTupleDescRefCount(tupDesc);
+ return tupDesc;
+}
+
+/*
+ * lookup_rowtype_tupdesc_copy
+ *
+ * Like lookup_rowtype_tupdesc(), but the returned TupleDesc has been
+ * copied into the CurrentMemoryContext and is not reference-counted.
+ */
+TupleDesc
+lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod)
+{
+ TupleDesc tmp;
+
+ tmp = lookup_rowtype_tupdesc_internal(type_id, typmod, false);
+ return CreateTupleDescCopyConstr(tmp);
+}
+
/*
* assign_record_type_typmod
/* First time through: initialize the hash table */
HASHCTL ctl;
- if (!CacheMemoryContext)
- CreateCacheMemoryContext();
-
MemSet(&ctl, 0, sizeof(ctl));
ctl.keysize = REC_HASH_KEYS * sizeof(Oid);
ctl.entrysize = sizeof(RecordCacheEntry);
ctl.hash = tag_hash;
RecordCacheHash = hash_create("Record information cache", 64,
&ctl, HASH_ELEM | HASH_FUNCTION);
+
+ /* Also make sure CacheMemoryContext exists */
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
}
/* Find or create a hashtable entry for this hash class */
/* if fail in subrs, no damage except possibly some wasted memory... */
entDesc = CreateTupleDescCopy(tupDesc);
recentry->tupdescs = lcons(entDesc, recentry->tupdescs);
+ /* mark it as a reference-counted tupdesc */
+ entDesc->tdrefcount = 1;
/* now it's safe to advance NextRecordTypmod */
newtypmod = NextRecordTypmod++;
entDesc->tdtypmod = newtypmod;
}
/*
- * flush_rowtype_cache
+ * TypeCacheRelCallback
+ * Relcache inval callback function
*
- * If a typcache entry exists for a rowtype, delete the entry's cached
- * tuple descriptor link. This is called from relcache.c when a cached
- * relation tupdesc is about to be dropped.
+ * Delete the cached tuple descriptor (if any) for the given rel's composite
+ * type, or for all composite types if relid == InvalidOid.
+ *
+ * This is called when a relcache invalidation event occurs for the given
+ * relid. We must scan the whole typcache hash since we don't know the
+ * type OID corresponding to the relid. We could do a direct search if this
+ * were a syscache-flush callback on pg_type, but then we would need all
+ * ALTER-TABLE-like commands that could modify a rowtype to issue syscache
+ * invals against the rel's pg_type OID. The extra SI signaling could very
+ * well cost more than we'd save, since in most usages there are not very
+ * many entries in a backend's typcache. The risk of bugs-of-omission seems
+ * high, too.
+ *
+ * Another possibility, with only localized impact, is to maintain a second
+ * hashtable that indexes composite-type typcache entries by their typrelid.
+ * But it's still not clear it's worth the trouble.
*/
-void
-flush_rowtype_cache(Oid type_id)
+static void
+TypeCacheRelCallback(Datum arg, Oid relid)
{
+ HASH_SEQ_STATUS status;
TypeCacheEntry *typentry;
- if (TypeCacheHash == NULL)
- return; /* no table, so certainly no entry */
-
- typentry = (TypeCacheEntry *) hash_search(TypeCacheHash,
- (void *) &type_id,
- HASH_FIND, NULL);
- if (typentry == NULL)
- return; /* no matching entry */
+ /* TypeCacheHash must exist, else this callback wouldn't be registered */
+ hash_seq_init(&status, TypeCacheHash);
+ while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
+ {
+ if (typentry->tupDesc == NULL)
+ continue; /* not composite, or tupdesc hasn't been requested */
- typentry->tupDesc = NULL;
+ /* Delete if match, or if we're zapping all composite types */
+ if (relid == typentry->typrelid || relid == InvalidOid)
+ {
+ /*
+ * Release our refcount, and free the tupdesc if none remain.
+ * (Can't use DecrTupleDescRefCount because this reference is not
+ * logged in current resource owner.)
+ */
+ Assert(typentry->tupDesc->tdrefcount > 0);
+ if (--typentry->tupDesc->tdrefcount == 0)
+ FreeTupleDesc(typentry->tupDesc);
+ typentry->tupDesc = NULL;
+ }
+ }
}