diff options
Diffstat (limited to 'src/backend/utils/cache')
-rw-r--r-- | src/backend/utils/cache/relcache.c | 30 | ||||
-rw-r--r-- | src/backend/utils/cache/typcache.c | 268 |
2 files changed, 282 insertions, 16 deletions
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 5c302e18d39..85ad6ffe788 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.200 2004/03/16 05:05:58 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.201 2004/04/01 21:28:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -63,6 +63,7 @@ #include "utils/lsyscache.h" #include "utils/relcache.h" #include "utils/syscache.h" +#include "utils/typcache.h" /* @@ -432,7 +433,10 @@ RelationBuildTupleDesc(RelationBuildDescInfo buildinfo, AttrDefault *attrdef = NULL; int ndef = 0; - relation->rd_att->tdhasoid = RelationGetForm(relation)->relhasoids; + /* copy some fields from pg_class row to rd_att */ + relation->rd_att->tdtypeid = relation->rd_rel->reltype; + relation->rd_att->tdtypmod = -1; /* unnecessary, but... */ + relation->rd_att->tdhasoid = relation->rd_rel->relhasoids; constr = (TupleConstr *) MemoryContextAlloc(CacheMemoryContext, sizeof(TupleConstr)); @@ -1312,9 +1316,12 @@ formrdesc(const char *relationName, * Unlike the case with the relation tuple, this data had better be right * because it will never be replaced. The input values must be * correctly defined by macros in src/include/catalog/ headers. + * + * Note however that rd_att's tdtypeid, tdtypmod, tdhasoid fields are + * not right at this point. They will be fixed later when the real + * pg_class row is loaded. */ - relation->rd_att = CreateTemplateTupleDesc(natts, - relation->rd_rel->relhasoids); + relation->rd_att = CreateTemplateTupleDesc(natts, false); /* * initialize tuple desc info @@ -1595,6 +1602,7 @@ RelationReloadClassinfo(Relation relation) static void RelationClearRelation(Relation relation, bool rebuild) { + Oid old_reltype = relation->rd_rel->reltype; MemoryContext oldcxt; /* @@ -1679,6 +1687,7 @@ RelationClearRelation(Relation relation, bool rebuild) if (!rebuild) { /* ok to zap remaining substructure */ + flush_rowtype_cache(old_reltype); FreeTupleDesc(relation->rd_att); if (relation->rd_rulescxt) MemoryContextDelete(relation->rd_rulescxt); @@ -1704,6 +1713,7 @@ RelationClearRelation(Relation relation, bool rebuild) if (RelationBuildDesc(buildinfo, relation) != relation) { /* Should only get here if relation was deleted */ + flush_rowtype_cache(old_reltype); FreeTupleDesc(old_att); if (old_rulescxt) MemoryContextDelete(old_rulescxt); @@ -1715,11 +1725,15 @@ RelationClearRelation(Relation relation, bool rebuild) relation->rd_isnew = old_isnew; if (equalTupleDescs(old_att, relation->rd_att)) { + /* needn't flush typcache here */ FreeTupleDesc(relation->rd_att); relation->rd_att = old_att; } else + { + flush_rowtype_cache(old_reltype); FreeTupleDesc(old_att); + } if (equalRuleLocks(old_rules, relation->rd_rules)) { if (relation->rd_rulescxt) @@ -2329,6 +2343,12 @@ RelationCacheInitializePhase2(void) */ Assert(relation->rd_rel != NULL); memcpy((char *) relation->rd_rel, (char *) relp, CLASS_TUPLE_SIZE); + + /* + * Also update the derived fields in rd_att. + */ + relation->rd_att->tdtypeid = relp->reltype; + relation->rd_att->tdtypmod = -1; /* unnecessary, but... */ relation->rd_att->tdhasoid = relp->relhasoids; ReleaseSysCache(htup); @@ -2918,6 +2938,8 @@ load_relcache_init_file(void) /* initialize attribute tuple forms */ rel->rd_att = CreateTemplateTupleDesc(relform->relnatts, relform->relhasoids); + rel->rd_att->tdtypeid = relform->reltype; + rel->rd_att->tdtypmod = -1; /* unnecessary, but... */ /* next read all the attribute tuple form data entries */ has_not_null = false; 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; +} |