aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/cache/typcache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/cache/typcache.c')
-rw-r--r--src/backend/utils/cache/typcache.c268
1 files changed, 256 insertions, 12 deletions
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index d6e560c34c5..7a8e67c83c8 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -28,12 +28,15 @@
* doesn't cope with opclasses changing under it, either, so this seems
* a low-priority problem.
*
+ * We do support clearing the tuple descriptor part of a rowtype's cache
+ * entry, since that may need to change as a consequence of ALTER TABLE.
+ *
*
* Portions Copyright (c) 1996-2003, 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.4 2003/11/29 19:52:00 pgsql Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/cache/typcache.c,v 1.5 2004/04/01 21:28:45 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -53,11 +56,42 @@
#include "utils/fmgroids.h"
#include "utils/hsearch.h"
#include "utils/lsyscache.h"
+#include "utils/syscache.h"
#include "utils/typcache.h"
+/* The main type cache hashtable searched by lookup_type_cache */
static HTAB *TypeCacheHash = NULL;
+/*
+ * We use a separate table for storing the definitions of non-anonymous
+ * record types. Once defined, a record type will be remembered for the
+ * life of the backend. Subsequent uses of the "same" record type (where
+ * sameness means equalTupleDescs) will refer to the existing table entry.
+ *
+ * Stored record types are remembered in a linear array of TupleDescs,
+ * which can be indexed quickly with the assigned typmod. There is also
+ * a hash table to speed searches for matching TupleDescs. The hash key
+ * uses just the first N columns' type OIDs, and so we may have multiple
+ * entries with the same hash key.
+ */
+#define REC_HASH_KEYS 16 /* use this many columns in hash key */
+
+typedef struct RecordCacheEntry
+{
+ /* the hash lookup key MUST BE FIRST */
+ Oid hashkey[REC_HASH_KEYS]; /* column type IDs, zero-filled */
+
+ /* list of TupleDescs for record types with this hashkey */
+ List *tupdescs;
+} RecordCacheEntry;
+
+static HTAB *RecordCacheHash = NULL;
+
+static TupleDesc *RecordCacheArray = NULL;
+static int32 RecordCacheArrayLen = 0; /* allocated length of array */
+static int32 NextRecordTypmod = 0; /* number of entries used */
+
static Oid lookup_default_opclass(Oid type_id, Oid am_id);
@@ -102,16 +136,26 @@ lookup_type_cache(Oid type_id, int flags)
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.
+ * If we didn't find one, we want to make one. But first look up
+ * 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;
+ HeapTuple tp;
+ Form_pg_type typtup;
- get_typlenbyvalalign(type_id, &typlen, &typbyval, &typalign);
+ tp = SearchSysCache(TYPEOID,
+ ObjectIdGetDatum(type_id),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for type %u", type_id);
+ typtup = (Form_pg_type) GETSTRUCT(tp);
+ if (!typtup->typisdefined)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("type \"%s\" is only a shell",
+ NameStr(typtup->typname))));
+ /* Now make the typcache entry */
typentry = (TypeCacheEntry *) hash_search(TypeCacheHash,
(void *) &type_id,
HASH_ENTER, &found);
@@ -123,13 +167,20 @@ lookup_type_cache(Oid type_id, int flags)
MemSet(typentry, 0, sizeof(TypeCacheEntry));
typentry->type_id = type_id;
- typentry->typlen = typlen;
- typentry->typbyval = typbyval;
- typentry->typalign = typalign;
+ typentry->typlen = typtup->typlen;
+ typentry->typbyval = typtup->typbyval;
+ typentry->typalign = typtup->typalign;
+ typentry->typtype = typtup->typtype;
+ typentry->typrelid = typtup->typrelid;
+
+ ReleaseSysCache(tp);
}
/* If we haven't already found the opclass, try to do so */
- if (flags != 0 && typentry->btree_opc == InvalidOid)
+ 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)
{
typentry->btree_opc = lookup_default_opclass(type_id,
BTREE_AM_OID);
@@ -215,6 +266,30 @@ lookup_type_cache(Oid type_id, int flags)
CacheMemoryContext);
}
+ /*
+ * If it's a composite type (row type), get tupdesc if requested
+ */
+ if ((flags & TYPECACHE_TUPDESC) &&
+ typentry->tupDesc == NULL &&
+ typentry->typtype == 'c')
+ {
+ Relation rel;
+
+ if (!OidIsValid(typentry->typrelid)) /* should not happen */
+ elog(ERROR, "invalid typrelid for composite type %u",
+ typentry->type_id);
+ rel = relation_open(typentry->typrelid, AccessShareLock);
+ 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.
+ */
+ typentry->tupDesc = RelationGetDescr(rel);
+
+ relation_close(rel, AccessShareLock);
+ }
+
return typentry;
}
@@ -296,3 +371,172 @@ lookup_default_opclass(Oid type_id, Oid am_id)
return InvalidOid;
}
+
+
+/*
+ * 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)
+{
+ if (type_id != RECORDOID)
+ {
+ /*
+ * It's a named composite type, so use the regular typcache.
+ */
+ TypeCacheEntry *typentry;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_TUPDESC);
+ /* this should not happen unless caller messed up: */
+ if (typentry->tupDesc == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("type %u is not composite",
+ type_id)));
+ return typentry->tupDesc;
+ }
+ else
+ {
+ /*
+ * It's a transient record type, so look in our record-type table.
+ */
+ if (typmod < 0 || typmod >= NextRecordTypmod)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("record type has not been registered")));
+ }
+ return RecordCacheArray[typmod];
+ }
+}
+
+
+/*
+ * assign_record_type_typmod
+ *
+ * Given a tuple descriptor for a RECORD type, find or create a cache entry
+ * for the type, and set the tupdesc's tdtypmod field to a value that will
+ * identify this cache entry to lookup_rowtype_tupdesc.
+ */
+void
+assign_record_type_typmod(TupleDesc tupDesc)
+{
+ RecordCacheEntry *recentry;
+ TupleDesc entDesc;
+ Oid hashkey[REC_HASH_KEYS];
+ bool found;
+ int i;
+ List *l;
+ int32 newtypmod;
+ MemoryContext oldcxt;
+
+ Assert(tupDesc->tdtypeid == RECORDOID);
+
+ if (RecordCacheHash == NULL)
+ {
+ /* 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);
+ }
+
+ /* Find or create a hashtable entry for this hash class */
+ MemSet(hashkey, 0, sizeof(hashkey));
+ for (i = 0; i < tupDesc->natts; i++)
+ {
+ if (i >= REC_HASH_KEYS)
+ break;
+ hashkey[i] = tupDesc->attrs[i]->atttypid;
+ }
+ recentry = (RecordCacheEntry *) hash_search(RecordCacheHash,
+ (void *) hashkey,
+ HASH_ENTER, &found);
+ if (recentry == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ if (!found)
+ {
+ /* New entry ... hash_search initialized only the hash key */
+ recentry->tupdescs = NIL;
+ }
+
+ /* Look for existing record cache entry */
+ foreach(l, recentry->tupdescs)
+ {
+ entDesc = (TupleDesc) lfirst(l);
+ if (equalTupleDescs(tupDesc, entDesc))
+ {
+ tupDesc->tdtypmod = entDesc->tdtypmod;
+ return;
+ }
+ }
+
+ /* Not present, so need to manufacture an entry */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+ if (RecordCacheArray == NULL)
+ {
+ RecordCacheArray = (TupleDesc *) palloc(64 * sizeof(TupleDesc));
+ RecordCacheArrayLen = 64;
+ }
+ else if (NextRecordTypmod >= RecordCacheArrayLen)
+ {
+ int32 newlen = RecordCacheArrayLen * 2;
+
+ RecordCacheArray = (TupleDesc *) repalloc(RecordCacheArray,
+ newlen * sizeof(TupleDesc));
+ RecordCacheArrayLen = newlen;
+ }
+
+ /* if fail in subrs, no damage except possibly some wasted memory... */
+ entDesc = CreateTupleDescCopy(tupDesc);
+ recentry->tupdescs = lcons(entDesc, recentry->tupdescs);
+ /* now it's safe to advance NextRecordTypmod */
+ newtypmod = NextRecordTypmod++;
+ entDesc->tdtypmod = newtypmod;
+ RecordCacheArray[newtypmod] = entDesc;
+
+ /* report to caller as well */
+ tupDesc->tdtypmod = newtypmod;
+
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * flush_rowtype_cache
+ *
+ * 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.
+ */
+void
+flush_rowtype_cache(Oid type_id)
+{
+ 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 */
+
+ typentry->tupDesc = NULL;
+}