diff options
Diffstat (limited to 'src/backend/utils/cache/catcache.c')
-rw-r--r-- | src/backend/utils/cache/catcache.c | 2095 |
1 files changed, 2095 insertions, 0 deletions
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c new file mode 100644 index 0000000..7b44ee1 --- /dev/null +++ b/src/backend/utils/cache/catcache.c @@ -0,0 +1,2095 @@ +/*------------------------------------------------------------------------- + * + * catcache.c + * System catalog cache for tuples matching a key. + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/cache/catcache.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heaptoast.h" +#include "access/relscan.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "access/valid.h" +#include "access/xact.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_type.h" +#include "common/hashfn.h" +#include "miscadmin.h" +#ifdef CATCACHE_STATS +#include "storage/ipc.h" /* for on_proc_exit */ +#endif +#include "storage/lmgr.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/resowner_private.h" +#include "utils/syscache.h" + + + /* #define CACHEDEBUG */ /* turns DEBUG elogs on */ + +/* + * Given a hash value and the size of the hash table, find the bucket + * in which the hash value belongs. Since the hash table must contain + * a power-of-2 number of elements, this is a simple bitmask. + */ +#define HASH_INDEX(h, sz) ((Index) ((h) & ((sz) - 1))) + + +/* + * variables, macros and other stuff + */ + +#ifdef CACHEDEBUG +#define CACHE_elog(...) elog(__VA_ARGS__) +#else +#define CACHE_elog(...) +#endif + +/* Cache management header --- pointer is NULL until created */ +static CatCacheHeader *CacheHdr = NULL; + +static inline HeapTuple SearchCatCacheInternal(CatCache *cache, + int nkeys, + Datum v1, Datum v2, + Datum v3, Datum v4); + +static pg_noinline HeapTuple SearchCatCacheMiss(CatCache *cache, + int nkeys, + uint32 hashValue, + Index hashIndex, + Datum v1, Datum v2, + Datum v3, Datum v4); + +static uint32 CatalogCacheComputeHashValue(CatCache *cache, int nkeys, + Datum v1, Datum v2, Datum v3, Datum v4); +static uint32 CatalogCacheComputeTupleHashValue(CatCache *cache, int nkeys, + HeapTuple tuple); +static inline bool CatalogCacheCompareTuple(const CatCache *cache, int nkeys, + const Datum *cachekeys, + const Datum *searchkeys); + +#ifdef CATCACHE_STATS +static void CatCachePrintStats(int code, Datum arg); +#endif +static void CatCacheRemoveCTup(CatCache *cache, CatCTup *ct); +static void CatCacheRemoveCList(CatCache *cache, CatCList *cl); +static void CatalogCacheInitializeCache(CatCache *cache); +static CatCTup *CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, + Datum *arguments, + uint32 hashValue, Index hashIndex, + bool negative); + +static void CatCacheFreeKeys(TupleDesc tupdesc, int nkeys, int *attnos, + Datum *keys); +static void CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos, + Datum *srckeys, Datum *dstkeys); + + +/* + * internal support functions + */ + +/* + * Hash and equality functions for system types that are used as cache key + * fields. In some cases, we just call the regular SQL-callable functions for + * the appropriate data type, but that tends to be a little slow, and the + * speed of these functions is performance-critical. Therefore, for data + * types that frequently occur as catcache keys, we hard-code the logic here. + * Avoiding the overhead of DirectFunctionCallN(...) is a substantial win, and + * in certain cases (like int4) we can adopt a faster hash algorithm as well. + */ + +static bool +chareqfast(Datum a, Datum b) +{ + return DatumGetChar(a) == DatumGetChar(b); +} + +static uint32 +charhashfast(Datum datum) +{ + return murmurhash32((int32) DatumGetChar(datum)); +} + +static bool +nameeqfast(Datum a, Datum b) +{ + char *ca = NameStr(*DatumGetName(a)); + char *cb = NameStr(*DatumGetName(b)); + + return strncmp(ca, cb, NAMEDATALEN) == 0; +} + +static uint32 +namehashfast(Datum datum) +{ + char *key = NameStr(*DatumGetName(datum)); + + return hash_any((unsigned char *) key, strlen(key)); +} + +static bool +int2eqfast(Datum a, Datum b) +{ + return DatumGetInt16(a) == DatumGetInt16(b); +} + +static uint32 +int2hashfast(Datum datum) +{ + return murmurhash32((int32) DatumGetInt16(datum)); +} + +static bool +int4eqfast(Datum a, Datum b) +{ + return DatumGetInt32(a) == DatumGetInt32(b); +} + +static uint32 +int4hashfast(Datum datum) +{ + return murmurhash32((int32) DatumGetInt32(datum)); +} + +static bool +texteqfast(Datum a, Datum b) +{ + /* + * The use of DEFAULT_COLLATION_OID is fairly arbitrary here. We just + * want to take the fast "deterministic" path in texteq(). + */ + return DatumGetBool(DirectFunctionCall2Coll(texteq, DEFAULT_COLLATION_OID, a, b)); +} + +static uint32 +texthashfast(Datum datum) +{ + /* analogously here as in texteqfast() */ + return DatumGetInt32(DirectFunctionCall1Coll(hashtext, DEFAULT_COLLATION_OID, datum)); +} + +static bool +oidvectoreqfast(Datum a, Datum b) +{ + return DatumGetBool(DirectFunctionCall2(oidvectoreq, a, b)); +} + +static uint32 +oidvectorhashfast(Datum datum) +{ + return DatumGetInt32(DirectFunctionCall1(hashoidvector, datum)); +} + +/* Lookup support functions for a type. */ +static void +GetCCHashEqFuncs(Oid keytype, CCHashFN *hashfunc, RegProcedure *eqfunc, CCFastEqualFN *fasteqfunc) +{ + switch (keytype) + { + case BOOLOID: + *hashfunc = charhashfast; + *fasteqfunc = chareqfast; + *eqfunc = F_BOOLEQ; + break; + case CHAROID: + *hashfunc = charhashfast; + *fasteqfunc = chareqfast; + *eqfunc = F_CHAREQ; + break; + case NAMEOID: + *hashfunc = namehashfast; + *fasteqfunc = nameeqfast; + *eqfunc = F_NAMEEQ; + break; + case INT2OID: + *hashfunc = int2hashfast; + *fasteqfunc = int2eqfast; + *eqfunc = F_INT2EQ; + break; + case INT4OID: + *hashfunc = int4hashfast; + *fasteqfunc = int4eqfast; + *eqfunc = F_INT4EQ; + break; + case TEXTOID: + *hashfunc = texthashfast; + *fasteqfunc = texteqfast; + *eqfunc = F_TEXTEQ; + break; + case OIDOID: + case REGPROCOID: + case REGPROCEDUREOID: + case REGOPEROID: + case REGOPERATOROID: + case REGCLASSOID: + case REGTYPEOID: + case REGCOLLATIONOID: + case REGCONFIGOID: + case REGDICTIONARYOID: + case REGROLEOID: + case REGNAMESPACEOID: + *hashfunc = int4hashfast; + *fasteqfunc = int4eqfast; + *eqfunc = F_OIDEQ; + break; + case OIDVECTOROID: + *hashfunc = oidvectorhashfast; + *fasteqfunc = oidvectoreqfast; + *eqfunc = F_OIDVECTOREQ; + break; + default: + elog(FATAL, "type %u not supported as catcache key", keytype); + *hashfunc = NULL; /* keep compiler quiet */ + + *eqfunc = InvalidOid; + break; + } +} + +/* + * CatalogCacheComputeHashValue + * + * Compute the hash value associated with a given set of lookup keys + */ +static uint32 +CatalogCacheComputeHashValue(CatCache *cache, int nkeys, + Datum v1, Datum v2, Datum v3, Datum v4) +{ + uint32 hashValue = 0; + uint32 oneHash; + CCHashFN *cc_hashfunc = cache->cc_hashfunc; + + CACHE_elog(DEBUG2, "CatalogCacheComputeHashValue %s %d %p", + cache->cc_relname, nkeys, cache); + + switch (nkeys) + { + case 4: + oneHash = (cc_hashfunc[3]) (v4); + + hashValue ^= oneHash << 24; + hashValue ^= oneHash >> 8; + /* FALLTHROUGH */ + case 3: + oneHash = (cc_hashfunc[2]) (v3); + + hashValue ^= oneHash << 16; + hashValue ^= oneHash >> 16; + /* FALLTHROUGH */ + case 2: + oneHash = (cc_hashfunc[1]) (v2); + + hashValue ^= oneHash << 8; + hashValue ^= oneHash >> 24; + /* FALLTHROUGH */ + case 1: + oneHash = (cc_hashfunc[0]) (v1); + + hashValue ^= oneHash; + break; + default: + elog(FATAL, "wrong number of hash keys: %d", nkeys); + break; + } + + return hashValue; +} + +/* + * CatalogCacheComputeTupleHashValue + * + * Compute the hash value associated with a given tuple to be cached + */ +static uint32 +CatalogCacheComputeTupleHashValue(CatCache *cache, int nkeys, HeapTuple tuple) +{ + Datum v1 = 0, + v2 = 0, + v3 = 0, + v4 = 0; + bool isNull = false; + int *cc_keyno = cache->cc_keyno; + TupleDesc cc_tupdesc = cache->cc_tupdesc; + + /* Now extract key fields from tuple, insert into scankey */ + switch (nkeys) + { + case 4: + v4 = fastgetattr(tuple, + cc_keyno[3], + cc_tupdesc, + &isNull); + Assert(!isNull); + /* FALLTHROUGH */ + case 3: + v3 = fastgetattr(tuple, + cc_keyno[2], + cc_tupdesc, + &isNull); + Assert(!isNull); + /* FALLTHROUGH */ + case 2: + v2 = fastgetattr(tuple, + cc_keyno[1], + cc_tupdesc, + &isNull); + Assert(!isNull); + /* FALLTHROUGH */ + case 1: + v1 = fastgetattr(tuple, + cc_keyno[0], + cc_tupdesc, + &isNull); + Assert(!isNull); + break; + default: + elog(FATAL, "wrong number of hash keys: %d", nkeys); + break; + } + + return CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4); +} + +/* + * CatalogCacheCompareTuple + * + * Compare a tuple to the passed arguments. + */ +static inline bool +CatalogCacheCompareTuple(const CatCache *cache, int nkeys, + const Datum *cachekeys, + const Datum *searchkeys) +{ + const CCFastEqualFN *cc_fastequal = cache->cc_fastequal; + int i; + + for (i = 0; i < nkeys; i++) + { + if (!(cc_fastequal[i]) (cachekeys[i], searchkeys[i])) + return false; + } + return true; +} + + +#ifdef CATCACHE_STATS + +static void +CatCachePrintStats(int code, Datum arg) +{ + slist_iter iter; + long cc_searches = 0; + long cc_hits = 0; + long cc_neg_hits = 0; + long cc_newloads = 0; + long cc_invals = 0; + long cc_lsearches = 0; + long cc_lhits = 0; + + slist_foreach(iter, &CacheHdr->ch_caches) + { + CatCache *cache = slist_container(CatCache, cc_next, iter.cur); + + if (cache->cc_ntup == 0 && cache->cc_searches == 0) + continue; /* don't print unused caches */ + elog(DEBUG2, "catcache %s/%u: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld lsrch, %ld lhits", + cache->cc_relname, + cache->cc_indexoid, + cache->cc_ntup, + cache->cc_searches, + cache->cc_hits, + cache->cc_neg_hits, + cache->cc_hits + cache->cc_neg_hits, + cache->cc_newloads, + cache->cc_searches - cache->cc_hits - cache->cc_neg_hits - cache->cc_newloads, + cache->cc_searches - cache->cc_hits - cache->cc_neg_hits, + cache->cc_invals, + cache->cc_lsearches, + cache->cc_lhits); + cc_searches += cache->cc_searches; + cc_hits += cache->cc_hits; + cc_neg_hits += cache->cc_neg_hits; + cc_newloads += cache->cc_newloads; + cc_invals += cache->cc_invals; + cc_lsearches += cache->cc_lsearches; + cc_lhits += cache->cc_lhits; + } + elog(DEBUG2, "catcache totals: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld lsrch, %ld lhits", + CacheHdr->ch_ntup, + cc_searches, + cc_hits, + cc_neg_hits, + cc_hits + cc_neg_hits, + cc_newloads, + cc_searches - cc_hits - cc_neg_hits - cc_newloads, + cc_searches - cc_hits - cc_neg_hits, + cc_invals, + cc_lsearches, + cc_lhits); +} +#endif /* CATCACHE_STATS */ + + +/* + * CatCacheRemoveCTup + * + * Unlink and delete the given cache entry + * + * NB: if it is a member of a CatCList, the CatCList is deleted too. + * Both the cache entry and the list had better have zero refcount. + */ +static void +CatCacheRemoveCTup(CatCache *cache, CatCTup *ct) +{ + Assert(ct->refcount == 0); + Assert(ct->my_cache == cache); + + if (ct->c_list) + { + /* + * The cleanest way to handle this is to call CatCacheRemoveCList, + * which will recurse back to me, and the recursive call will do the + * work. Set the "dead" flag to make sure it does recurse. + */ + ct->dead = true; + CatCacheRemoveCList(cache, ct->c_list); + return; /* nothing left to do */ + } + + /* delink from linked list */ + dlist_delete(&ct->cache_elem); + + /* + * Free keys when we're dealing with a negative entry, normal entries just + * point into tuple, allocated together with the CatCTup. + */ + if (ct->negative) + CatCacheFreeKeys(cache->cc_tupdesc, cache->cc_nkeys, + cache->cc_keyno, ct->keys); + + pfree(ct); + + --cache->cc_ntup; + --CacheHdr->ch_ntup; +} + +/* + * CatCacheRemoveCList + * + * Unlink and delete the given cache list entry + * + * NB: any dead member entries that become unreferenced are deleted too. + */ +static void +CatCacheRemoveCList(CatCache *cache, CatCList *cl) +{ + int i; + + Assert(cl->refcount == 0); + Assert(cl->my_cache == cache); + + /* delink from member tuples */ + for (i = cl->n_members; --i >= 0;) + { + CatCTup *ct = cl->members[i]; + + Assert(ct->c_list == cl); + ct->c_list = NULL; + /* if the member is dead and now has no references, remove it */ + if ( +#ifndef CATCACHE_FORCE_RELEASE + ct->dead && +#endif + ct->refcount == 0) + CatCacheRemoveCTup(cache, ct); + } + + /* delink from linked list */ + dlist_delete(&cl->cache_elem); + + /* free associated column data */ + CatCacheFreeKeys(cache->cc_tupdesc, cl->nkeys, + cache->cc_keyno, cl->keys); + + pfree(cl); +} + + +/* + * CatCacheInvalidate + * + * Invalidate entries in the specified cache, given a hash value. + * + * We delete cache entries that match the hash value, whether positive + * or negative. We don't care whether the invalidation is the result + * of a tuple insertion or a deletion. + * + * We used to try to match positive cache entries by TID, but that is + * unsafe after a VACUUM FULL on a system catalog: an inval event could + * be queued before VACUUM FULL, and then processed afterwards, when the + * target tuple that has to be invalidated has a different TID than it + * did when the event was created. So now we just compare hash values and + * accept the small risk of unnecessary invalidations due to false matches. + * + * This routine is only quasi-public: it should only be used by inval.c. + */ +void +CatCacheInvalidate(CatCache *cache, uint32 hashValue) +{ + Index hashIndex; + dlist_mutable_iter iter; + + CACHE_elog(DEBUG2, "CatCacheInvalidate: called"); + + /* + * We don't bother to check whether the cache has finished initialization + * yet; if not, there will be no entries in it so no problem. + */ + + /* + * Invalidate *all* CatCLists in this cache; it's too hard to tell which + * searches might still be correct, so just zap 'em all. + */ + dlist_foreach_modify(iter, &cache->cc_lists) + { + CatCList *cl = dlist_container(CatCList, cache_elem, iter.cur); + + if (cl->refcount > 0) + cl->dead = true; + else + CatCacheRemoveCList(cache, cl); + } + + /* + * inspect the proper hash bucket for tuple matches + */ + hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets); + dlist_foreach_modify(iter, &cache->cc_bucket[hashIndex]) + { + CatCTup *ct = dlist_container(CatCTup, cache_elem, iter.cur); + + if (hashValue == ct->hash_value) + { + if (ct->refcount > 0 || + (ct->c_list && ct->c_list->refcount > 0)) + { + ct->dead = true; + /* list, if any, was marked dead above */ + Assert(ct->c_list == NULL || ct->c_list->dead); + } + else + CatCacheRemoveCTup(cache, ct); + CACHE_elog(DEBUG2, "CatCacheInvalidate: invalidated"); +#ifdef CATCACHE_STATS + cache->cc_invals++; +#endif + /* could be multiple matches, so keep looking! */ + } + } +} + +/* ---------------------------------------------------------------- + * public functions + * ---------------------------------------------------------------- + */ + + +/* + * Standard routine for creating cache context if it doesn't exist yet + * + * There are a lot of places (probably far more than necessary) that check + * whether CacheMemoryContext exists yet and want to create it if not. + * We centralize knowledge of exactly how to create it here. + */ +void +CreateCacheMemoryContext(void) +{ + /* + * Purely for paranoia, check that context doesn't exist; caller probably + * did so already. + */ + if (!CacheMemoryContext) + CacheMemoryContext = AllocSetContextCreate(TopMemoryContext, + "CacheMemoryContext", + ALLOCSET_DEFAULT_SIZES); +} + + +/* + * ResetCatalogCache + * + * Reset one catalog cache to empty. + * + * This is not very efficient if the target cache is nearly empty. + * However, it shouldn't need to be efficient; we don't invoke it often. + */ +static void +ResetCatalogCache(CatCache *cache) +{ + dlist_mutable_iter iter; + int i; + + /* Remove each list in this cache, or at least mark it dead */ + dlist_foreach_modify(iter, &cache->cc_lists) + { + CatCList *cl = dlist_container(CatCList, cache_elem, iter.cur); + + if (cl->refcount > 0) + cl->dead = true; + else + CatCacheRemoveCList(cache, cl); + } + + /* Remove each tuple in this cache, or at least mark it dead */ + for (i = 0; i < cache->cc_nbuckets; i++) + { + dlist_head *bucket = &cache->cc_bucket[i]; + + dlist_foreach_modify(iter, bucket) + { + CatCTup *ct = dlist_container(CatCTup, cache_elem, iter.cur); + + if (ct->refcount > 0 || + (ct->c_list && ct->c_list->refcount > 0)) + { + ct->dead = true; + /* list, if any, was marked dead above */ + Assert(ct->c_list == NULL || ct->c_list->dead); + } + else + CatCacheRemoveCTup(cache, ct); +#ifdef CATCACHE_STATS + cache->cc_invals++; +#endif + } + } +} + +/* + * ResetCatalogCaches + * + * Reset all caches when a shared cache inval event forces it + */ +void +ResetCatalogCaches(void) +{ + slist_iter iter; + + CACHE_elog(DEBUG2, "ResetCatalogCaches called"); + + slist_foreach(iter, &CacheHdr->ch_caches) + { + CatCache *cache = slist_container(CatCache, cc_next, iter.cur); + + ResetCatalogCache(cache); + } + + CACHE_elog(DEBUG2, "end of ResetCatalogCaches call"); +} + +/* + * CatalogCacheFlushCatalog + * + * Flush all catcache entries that came from the specified system catalog. + * This is needed after VACUUM FULL/CLUSTER on the catalog, since the + * tuples very likely now have different TIDs than before. (At one point + * we also tried to force re-execution of CatalogCacheInitializeCache for + * the cache(s) on that catalog. This is a bad idea since it leads to all + * kinds of trouble if a cache flush occurs while loading cache entries. + * We now avoid the need to do it by copying cc_tupdesc out of the relcache, + * rather than relying on the relcache to keep a tupdesc for us. Of course + * this assumes the tupdesc of a cachable system table will not change...) + */ +void +CatalogCacheFlushCatalog(Oid catId) +{ + slist_iter iter; + + CACHE_elog(DEBUG2, "CatalogCacheFlushCatalog called for %u", catId); + + slist_foreach(iter, &CacheHdr->ch_caches) + { + CatCache *cache = slist_container(CatCache, cc_next, iter.cur); + + /* Does this cache store tuples of the target catalog? */ + if (cache->cc_reloid == catId) + { + /* Yes, so flush all its contents */ + ResetCatalogCache(cache); + + /* Tell inval.c to call syscache callbacks for this cache */ + CallSyscacheCallbacks(cache->id, 0); + } + } + + CACHE_elog(DEBUG2, "end of CatalogCacheFlushCatalog call"); +} + +/* + * InitCatCache + * + * This allocates and initializes a cache for a system catalog relation. + * Actually, the cache is only partially initialized to avoid opening the + * relation. The relation will be opened and the rest of the cache + * structure initialized on the first access. + */ +#ifdef CACHEDEBUG +#define InitCatCache_DEBUG2 \ +do { \ + elog(DEBUG2, "InitCatCache: rel=%u ind=%u id=%d nkeys=%d size=%d", \ + cp->cc_reloid, cp->cc_indexoid, cp->id, \ + cp->cc_nkeys, cp->cc_nbuckets); \ +} while(0) +#else +#define InitCatCache_DEBUG2 +#endif + +CatCache * +InitCatCache(int id, + Oid reloid, + Oid indexoid, + int nkeys, + const int *key, + int nbuckets) +{ + CatCache *cp; + MemoryContext oldcxt; + size_t sz; + int i; + + /* + * nbuckets is the initial number of hash buckets to use in this catcache. + * It will be enlarged later if it becomes too full. + * + * nbuckets must be a power of two. We check this via Assert rather than + * a full runtime check because the values will be coming from constant + * tables. + * + * If you're confused by the power-of-two check, see comments in + * bitmapset.c for an explanation. + */ + Assert(nbuckets > 0 && (nbuckets & -nbuckets) == nbuckets); + + /* + * first switch to the cache context so our allocations do not vanish at + * the end of a transaction + */ + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + /* + * if first time through, initialize the cache group header + */ + if (CacheHdr == NULL) + { + CacheHdr = (CatCacheHeader *) palloc(sizeof(CatCacheHeader)); + slist_init(&CacheHdr->ch_caches); + CacheHdr->ch_ntup = 0; +#ifdef CATCACHE_STATS + /* set up to dump stats at backend exit */ + on_proc_exit(CatCachePrintStats, 0); +#endif + } + + /* + * Allocate a new cache structure, aligning to a cacheline boundary + * + * Note: we rely on zeroing to initialize all the dlist headers correctly + */ + sz = sizeof(CatCache) + PG_CACHE_LINE_SIZE; + cp = (CatCache *) CACHELINEALIGN(palloc0(sz)); + cp->cc_bucket = palloc0(nbuckets * sizeof(dlist_head)); + + /* + * initialize the cache's relation information for the relation + * corresponding to this cache, and initialize some of the new cache's + * other internal fields. But don't open the relation yet. + */ + cp->id = id; + cp->cc_relname = "(not known yet)"; + cp->cc_reloid = reloid; + cp->cc_indexoid = indexoid; + cp->cc_relisshared = false; /* temporary */ + cp->cc_tupdesc = (TupleDesc) NULL; + cp->cc_ntup = 0; + cp->cc_nbuckets = nbuckets; + cp->cc_nkeys = nkeys; + for (i = 0; i < nkeys; ++i) + cp->cc_keyno[i] = key[i]; + + /* + * new cache is initialized as far as we can go for now. print some + * debugging information, if appropriate. + */ + InitCatCache_DEBUG2; + + /* + * add completed cache to top of group header's list + */ + slist_push_head(&CacheHdr->ch_caches, &cp->cc_next); + + /* + * back to the old context before we return... + */ + MemoryContextSwitchTo(oldcxt); + + return cp; +} + +/* + * Enlarge a catcache, doubling the number of buckets. + */ +static void +RehashCatCache(CatCache *cp) +{ + dlist_head *newbucket; + int newnbuckets; + int i; + + elog(DEBUG1, "rehashing catalog cache id %d for %s; %d tups, %d buckets", + cp->id, cp->cc_relname, cp->cc_ntup, cp->cc_nbuckets); + + /* Allocate a new, larger, hash table. */ + newnbuckets = cp->cc_nbuckets * 2; + newbucket = (dlist_head *) MemoryContextAllocZero(CacheMemoryContext, newnbuckets * sizeof(dlist_head)); + + /* Move all entries from old hash table to new. */ + for (i = 0; i < cp->cc_nbuckets; i++) + { + dlist_mutable_iter iter; + + dlist_foreach_modify(iter, &cp->cc_bucket[i]) + { + CatCTup *ct = dlist_container(CatCTup, cache_elem, iter.cur); + int hashIndex = HASH_INDEX(ct->hash_value, newnbuckets); + + dlist_delete(iter.cur); + dlist_push_head(&newbucket[hashIndex], &ct->cache_elem); + } + } + + /* Switch to the new array. */ + pfree(cp->cc_bucket); + cp->cc_nbuckets = newnbuckets; + cp->cc_bucket = newbucket; +} + +/* + * CatalogCacheInitializeCache + * + * This function does final initialization of a catcache: obtain the tuple + * descriptor and set up the hash and equality function links. We assume + * that the relcache entry can be opened at this point! + */ +#ifdef CACHEDEBUG +#define CatalogCacheInitializeCache_DEBUG1 \ + elog(DEBUG2, "CatalogCacheInitializeCache: cache @%p rel=%u", cache, \ + cache->cc_reloid) + +#define CatalogCacheInitializeCache_DEBUG2 \ +do { \ + if (cache->cc_keyno[i] > 0) { \ + elog(DEBUG2, "CatalogCacheInitializeCache: load %d/%d w/%d, %u", \ + i+1, cache->cc_nkeys, cache->cc_keyno[i], \ + TupleDescAttr(tupdesc, cache->cc_keyno[i] - 1)->atttypid); \ + } else { \ + elog(DEBUG2, "CatalogCacheInitializeCache: load %d/%d w/%d", \ + i+1, cache->cc_nkeys, cache->cc_keyno[i]); \ + } \ +} while(0) +#else +#define CatalogCacheInitializeCache_DEBUG1 +#define CatalogCacheInitializeCache_DEBUG2 +#endif + +static void +CatalogCacheInitializeCache(CatCache *cache) +{ + Relation relation; + MemoryContext oldcxt; + TupleDesc tupdesc; + int i; + + CatalogCacheInitializeCache_DEBUG1; + + relation = table_open(cache->cc_reloid, AccessShareLock); + + /* + * switch to the cache context so our allocations do not vanish at the end + * of a transaction + */ + Assert(CacheMemoryContext != NULL); + + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + /* + * copy the relcache's tuple descriptor to permanent cache storage + */ + tupdesc = CreateTupleDescCopyConstr(RelationGetDescr(relation)); + + /* + * save the relation's name and relisshared flag, too (cc_relname is used + * only for debugging purposes) + */ + cache->cc_relname = pstrdup(RelationGetRelationName(relation)); + cache->cc_relisshared = RelationGetForm(relation)->relisshared; + + /* + * return to the caller's memory context and close the rel + */ + MemoryContextSwitchTo(oldcxt); + + table_close(relation, AccessShareLock); + + CACHE_elog(DEBUG2, "CatalogCacheInitializeCache: %s, %d keys", + cache->cc_relname, cache->cc_nkeys); + + /* + * initialize cache's key information + */ + for (i = 0; i < cache->cc_nkeys; ++i) + { + Oid keytype; + RegProcedure eqfunc; + + CatalogCacheInitializeCache_DEBUG2; + + if (cache->cc_keyno[i] > 0) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, + cache->cc_keyno[i] - 1); + + keytype = attr->atttypid; + /* cache key columns should always be NOT NULL */ + Assert(attr->attnotnull); + } + else + { + if (cache->cc_keyno[i] < 0) + elog(FATAL, "sys attributes are not supported in caches"); + keytype = OIDOID; + } + + GetCCHashEqFuncs(keytype, + &cache->cc_hashfunc[i], + &eqfunc, + &cache->cc_fastequal[i]); + + /* + * Do equality-function lookup (we assume this won't need a catalog + * lookup for any supported type) + */ + fmgr_info_cxt(eqfunc, + &cache->cc_skey[i].sk_func, + CacheMemoryContext); + + /* Initialize sk_attno suitably for HeapKeyTest() and heap scans */ + cache->cc_skey[i].sk_attno = cache->cc_keyno[i]; + + /* Fill in sk_strategy as well --- always standard equality */ + cache->cc_skey[i].sk_strategy = BTEqualStrategyNumber; + cache->cc_skey[i].sk_subtype = InvalidOid; + /* If a catcache key requires a collation, it must be C collation */ + cache->cc_skey[i].sk_collation = C_COLLATION_OID; + + CACHE_elog(DEBUG2, "CatalogCacheInitializeCache %s %d %p", + cache->cc_relname, i, cache); + } + + /* + * mark this cache fully initialized + */ + cache->cc_tupdesc = tupdesc; +} + +/* + * InitCatCachePhase2 -- external interface for CatalogCacheInitializeCache + * + * One reason to call this routine is to ensure that the relcache has + * created entries for all the catalogs and indexes referenced by catcaches. + * Therefore, provide an option to open the index as well as fixing the + * cache itself. An exception is the indexes on pg_am, which we don't use + * (cf. IndexScanOK). + */ +void +InitCatCachePhase2(CatCache *cache, bool touch_index) +{ + if (cache->cc_tupdesc == NULL) + CatalogCacheInitializeCache(cache); + + if (touch_index && + cache->id != AMOID && + cache->id != AMNAME) + { + Relation idesc; + + /* + * We must lock the underlying catalog before opening the index to + * avoid deadlock, since index_open could possibly result in reading + * this same catalog, and if anyone else is exclusive-locking this + * catalog and index they'll be doing it in that order. + */ + LockRelationOid(cache->cc_reloid, AccessShareLock); + idesc = index_open(cache->cc_indexoid, AccessShareLock); + + /* + * While we've got the index open, let's check that it's unique (and + * not just deferrable-unique, thank you very much). This is just to + * catch thinkos in definitions of new catcaches, so we don't worry + * about the pg_am indexes not getting tested. + */ + Assert(idesc->rd_index->indisunique && + idesc->rd_index->indimmediate); + + index_close(idesc, AccessShareLock); + UnlockRelationOid(cache->cc_reloid, AccessShareLock); + } +} + + +/* + * IndexScanOK + * + * This function checks for tuples that will be fetched by + * IndexSupportInitialize() during relcache initialization for + * certain system indexes that support critical syscaches. + * We can't use an indexscan to fetch these, else we'll get into + * infinite recursion. A plain heap scan will work, however. + * Once we have completed relcache initialization (signaled by + * criticalRelcachesBuilt), we don't have to worry anymore. + * + * Similarly, during backend startup we have to be able to use the + * pg_authid, pg_auth_members and pg_database syscaches for + * authentication even if we don't yet have relcache entries for those + * catalogs' indexes. + */ +static bool +IndexScanOK(CatCache *cache, ScanKey cur_skey) +{ + switch (cache->id) + { + case INDEXRELID: + + /* + * Rather than tracking exactly which indexes have to be loaded + * before we can use indexscans (which changes from time to time), + * just force all pg_index searches to be heap scans until we've + * built the critical relcaches. + */ + if (!criticalRelcachesBuilt) + return false; + break; + + case AMOID: + case AMNAME: + + /* + * Always do heap scans in pg_am, because it's so small there's + * not much point in an indexscan anyway. We *must* do this when + * initially building critical relcache entries, but we might as + * well just always do it. + */ + return false; + + case AUTHNAME: + case AUTHOID: + case AUTHMEMMEMROLE: + case DATABASEOID: + + /* + * Protect authentication lookups occurring before relcache has + * collected entries for shared indexes. + */ + if (!criticalSharedRelcachesBuilt) + return false; + break; + + default: + break; + } + + /* Normal case, allow index scan */ + return true; +} + +/* + * SearchCatCache + * + * This call searches a system cache for a tuple, opening the relation + * if necessary (on the first access to a particular cache). + * + * The result is NULL if not found, or a pointer to a HeapTuple in + * the cache. The caller must not modify the tuple, and must call + * ReleaseCatCache() when done with it. + * + * The search key values should be expressed as Datums of the key columns' + * datatype(s). (Pass zeroes for any unused parameters.) As a special + * exception, the passed-in key for a NAME column can be just a C string; + * the caller need not go to the trouble of converting it to a fully + * null-padded NAME. + */ +HeapTuple +SearchCatCache(CatCache *cache, + Datum v1, + Datum v2, + Datum v3, + Datum v4) +{ + return SearchCatCacheInternal(cache, cache->cc_nkeys, v1, v2, v3, v4); +} + + +/* + * SearchCatCacheN() are SearchCatCache() versions for a specific number of + * arguments. The compiler can inline the body and unroll loops, making them a + * bit faster than SearchCatCache(). + */ + +HeapTuple +SearchCatCache1(CatCache *cache, + Datum v1) +{ + return SearchCatCacheInternal(cache, 1, v1, 0, 0, 0); +} + + +HeapTuple +SearchCatCache2(CatCache *cache, + Datum v1, Datum v2) +{ + return SearchCatCacheInternal(cache, 2, v1, v2, 0, 0); +} + + +HeapTuple +SearchCatCache3(CatCache *cache, + Datum v1, Datum v2, Datum v3) +{ + return SearchCatCacheInternal(cache, 3, v1, v2, v3, 0); +} + + +HeapTuple +SearchCatCache4(CatCache *cache, + Datum v1, Datum v2, Datum v3, Datum v4) +{ + return SearchCatCacheInternal(cache, 4, v1, v2, v3, v4); +} + +/* + * Work-horse for SearchCatCache/SearchCatCacheN. + */ +static inline HeapTuple +SearchCatCacheInternal(CatCache *cache, + int nkeys, + Datum v1, + Datum v2, + Datum v3, + Datum v4) +{ + Datum arguments[CATCACHE_MAXKEYS]; + uint32 hashValue; + Index hashIndex; + dlist_iter iter; + dlist_head *bucket; + CatCTup *ct; + + /* Make sure we're in an xact, even if this ends up being a cache hit */ + Assert(IsTransactionState()); + + Assert(cache->cc_nkeys == nkeys); + + /* + * one-time startup overhead for each cache + */ + if (unlikely(cache->cc_tupdesc == NULL)) + CatalogCacheInitializeCache(cache); + +#ifdef CATCACHE_STATS + cache->cc_searches++; +#endif + + /* Initialize local parameter array */ + arguments[0] = v1; + arguments[1] = v2; + arguments[2] = v3; + arguments[3] = v4; + + /* + * find the hash bucket in which to look for the tuple + */ + hashValue = CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4); + hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets); + + /* + * scan the hash bucket until we find a match or exhaust our tuples + * + * Note: it's okay to use dlist_foreach here, even though we modify the + * dlist within the loop, because we don't continue the loop afterwards. + */ + bucket = &cache->cc_bucket[hashIndex]; + dlist_foreach(iter, bucket) + { + ct = dlist_container(CatCTup, cache_elem, iter.cur); + + if (ct->dead) + continue; /* ignore dead entries */ + + if (ct->hash_value != hashValue) + continue; /* quickly skip entry if wrong hash val */ + + if (!CatalogCacheCompareTuple(cache, nkeys, ct->keys, arguments)) + continue; + + /* + * We found a match in the cache. Move it to the front of the list + * for its hashbucket, in order to speed subsequent searches. (The + * most frequently accessed elements in any hashbucket will tend to be + * near the front of the hashbucket's list.) + */ + dlist_move_head(bucket, &ct->cache_elem); + + /* + * If it's a positive entry, bump its refcount and return it. If it's + * negative, we can report failure to the caller. + */ + if (!ct->negative) + { + ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner); + ct->refcount++; + ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple); + + CACHE_elog(DEBUG2, "SearchCatCache(%s): found in bucket %d", + cache->cc_relname, hashIndex); + +#ifdef CATCACHE_STATS + cache->cc_hits++; +#endif + + return &ct->tuple; + } + else + { + CACHE_elog(DEBUG2, "SearchCatCache(%s): found neg entry in bucket %d", + cache->cc_relname, hashIndex); + +#ifdef CATCACHE_STATS + cache->cc_neg_hits++; +#endif + + return NULL; + } + } + + return SearchCatCacheMiss(cache, nkeys, hashValue, hashIndex, v1, v2, v3, v4); +} + +/* + * Search the actual catalogs, rather than the cache. + * + * This is kept separate from SearchCatCacheInternal() to keep the fast-path + * as small as possible. To avoid that effort being undone by a helpful + * compiler, try to explicitly forbid inlining. + */ +static pg_noinline HeapTuple +SearchCatCacheMiss(CatCache *cache, + int nkeys, + uint32 hashValue, + Index hashIndex, + Datum v1, + Datum v2, + Datum v3, + Datum v4) +{ + ScanKeyData cur_skey[CATCACHE_MAXKEYS]; + Relation relation; + SysScanDesc scandesc; + HeapTuple ntp; + CatCTup *ct; + Datum arguments[CATCACHE_MAXKEYS]; + + /* Initialize local parameter array */ + arguments[0] = v1; + arguments[1] = v2; + arguments[2] = v3; + arguments[3] = v4; + + /* + * Ok, need to make a lookup in the relation, copy the scankey and fill + * out any per-call fields. + */ + memcpy(cur_skey, cache->cc_skey, sizeof(ScanKeyData) * nkeys); + cur_skey[0].sk_argument = v1; + cur_skey[1].sk_argument = v2; + cur_skey[2].sk_argument = v3; + cur_skey[3].sk_argument = v4; + + /* + * Tuple was not found in cache, so we have to try to retrieve it directly + * from the relation. If found, we will add it to the cache; if not + * found, we will add a negative cache entry instead. + * + * NOTE: it is possible for recursive cache lookups to occur while reading + * the relation --- for example, due to shared-cache-inval messages being + * processed during table_open(). This is OK. It's even possible for one + * of those lookups to find and enter the very same tuple we are trying to + * fetch here. If that happens, we will enter a second copy of the tuple + * into the cache. The first copy will never be referenced again, and + * will eventually age out of the cache, so there's no functional problem. + * This case is rare enough that it's not worth expending extra cycles to + * detect. + */ + relation = table_open(cache->cc_reloid, AccessShareLock); + + scandesc = systable_beginscan(relation, + cache->cc_indexoid, + IndexScanOK(cache, cur_skey), + NULL, + nkeys, + cur_skey); + + ct = NULL; + + while (HeapTupleIsValid(ntp = systable_getnext(scandesc))) + { + ct = CatalogCacheCreateEntry(cache, ntp, arguments, + hashValue, hashIndex, + false); + /* immediately set the refcount to 1 */ + ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner); + ct->refcount++; + ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple); + break; /* assume only one match */ + } + + systable_endscan(scandesc); + + table_close(relation, AccessShareLock); + + /* + * If tuple was not found, we need to build a negative cache entry + * containing a fake tuple. The fake tuple has the correct key columns, + * but nulls everywhere else. + * + * In bootstrap mode, we don't build negative entries, because the cache + * invalidation mechanism isn't alive and can't clear them if the tuple + * gets created later. (Bootstrap doesn't do UPDATEs, so it doesn't need + * cache inval for that.) + */ + if (ct == NULL) + { + if (IsBootstrapProcessingMode()) + return NULL; + + ct = CatalogCacheCreateEntry(cache, NULL, arguments, + hashValue, hashIndex, + true); + + CACHE_elog(DEBUG2, "SearchCatCache(%s): Contains %d/%d tuples", + cache->cc_relname, cache->cc_ntup, CacheHdr->ch_ntup); + CACHE_elog(DEBUG2, "SearchCatCache(%s): put neg entry in bucket %d", + cache->cc_relname, hashIndex); + + /* + * We are not returning the negative entry to the caller, so leave its + * refcount zero. + */ + + return NULL; + } + + CACHE_elog(DEBUG2, "SearchCatCache(%s): Contains %d/%d tuples", + cache->cc_relname, cache->cc_ntup, CacheHdr->ch_ntup); + CACHE_elog(DEBUG2, "SearchCatCache(%s): put in bucket %d", + cache->cc_relname, hashIndex); + +#ifdef CATCACHE_STATS + cache->cc_newloads++; +#endif + + return &ct->tuple; +} + +/* + * ReleaseCatCache + * + * Decrement the reference count of a catcache entry (releasing the + * hold grabbed by a successful SearchCatCache). + * + * NOTE: if compiled with -DCATCACHE_FORCE_RELEASE then catcache entries + * will be freed as soon as their refcount goes to zero. In combination + * with aset.c's CLOBBER_FREED_MEMORY option, this provides a good test + * to catch references to already-released catcache entries. + */ +void +ReleaseCatCache(HeapTuple tuple) +{ + CatCTup *ct = (CatCTup *) (((char *) tuple) - + offsetof(CatCTup, tuple)); + + /* Safety checks to ensure we were handed a cache entry */ + Assert(ct->ct_magic == CT_MAGIC); + Assert(ct->refcount > 0); + + ct->refcount--; + ResourceOwnerForgetCatCacheRef(CurrentResourceOwner, &ct->tuple); + + if ( +#ifndef CATCACHE_FORCE_RELEASE + ct->dead && +#endif + ct->refcount == 0 && + (ct->c_list == NULL || ct->c_list->refcount == 0)) + CatCacheRemoveCTup(ct->my_cache, ct); +} + + +/* + * GetCatCacheHashValue + * + * Compute the hash value for a given set of search keys. + * + * The reason for exposing this as part of the API is that the hash value is + * exposed in cache invalidation operations, so there are places outside the + * catcache code that need to be able to compute the hash values. + */ +uint32 +GetCatCacheHashValue(CatCache *cache, + Datum v1, + Datum v2, + Datum v3, + Datum v4) +{ + /* + * one-time startup overhead for each cache + */ + if (cache->cc_tupdesc == NULL) + CatalogCacheInitializeCache(cache); + + /* + * calculate the hash value + */ + return CatalogCacheComputeHashValue(cache, cache->cc_nkeys, v1, v2, v3, v4); +} + + +/* + * SearchCatCacheList + * + * Generate a list of all tuples matching a partial key (that is, + * a key specifying just the first K of the cache's N key columns). + * + * It doesn't make any sense to specify all of the cache's key columns + * here: since the key is unique, there could be at most one match, so + * you ought to use SearchCatCache() instead. Hence this function takes + * one fewer Datum argument than SearchCatCache() does. + * + * The caller must not modify the list object or the pointed-to tuples, + * and must call ReleaseCatCacheList() when done with the list. + */ +CatCList * +SearchCatCacheList(CatCache *cache, + int nkeys, + Datum v1, + Datum v2, + Datum v3) +{ + Datum v4 = 0; /* dummy last-column value */ + Datum arguments[CATCACHE_MAXKEYS]; + uint32 lHashValue; + dlist_iter iter; + CatCList *cl; + CatCTup *ct; + List *volatile ctlist; + ListCell *ctlist_item; + int nmembers; + bool ordered; + HeapTuple ntp; + MemoryContext oldcxt; + int i; + + /* + * one-time startup overhead for each cache + */ + if (cache->cc_tupdesc == NULL) + CatalogCacheInitializeCache(cache); + + Assert(nkeys > 0 && nkeys < cache->cc_nkeys); + +#ifdef CATCACHE_STATS + cache->cc_lsearches++; +#endif + + /* Initialize local parameter array */ + arguments[0] = v1; + arguments[1] = v2; + arguments[2] = v3; + arguments[3] = v4; + + /* + * compute a hash value of the given keys for faster search. We don't + * presently divide the CatCList items into buckets, but this still lets + * us skip non-matching items quickly most of the time. + */ + lHashValue = CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4); + + /* + * scan the items until we find a match or exhaust our list + * + * Note: it's okay to use dlist_foreach here, even though we modify the + * dlist within the loop, because we don't continue the loop afterwards. + */ + dlist_foreach(iter, &cache->cc_lists) + { + cl = dlist_container(CatCList, cache_elem, iter.cur); + + if (cl->dead) + continue; /* ignore dead entries */ + + if (cl->hash_value != lHashValue) + continue; /* quickly skip entry if wrong hash val */ + + /* + * see if the cached list matches our key. + */ + if (cl->nkeys != nkeys) + continue; + + if (!CatalogCacheCompareTuple(cache, nkeys, cl->keys, arguments)) + continue; + + /* + * We found a matching list. Move the list to the front of the + * cache's list-of-lists, to speed subsequent searches. (We do not + * move the members to the fronts of their hashbucket lists, however, + * since there's no point in that unless they are searched for + * individually.) + */ + dlist_move_head(&cache->cc_lists, &cl->cache_elem); + + /* Bump the list's refcount and return it */ + ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner); + cl->refcount++; + ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl); + + CACHE_elog(DEBUG2, "SearchCatCacheList(%s): found list", + cache->cc_relname); + +#ifdef CATCACHE_STATS + cache->cc_lhits++; +#endif + + return cl; + } + + /* + * List was not found in cache, so we have to build it by reading the + * relation. For each matching tuple found in the relation, use an + * existing cache entry if possible, else build a new one. + * + * We have to bump the member refcounts temporarily to ensure they won't + * get dropped from the cache while loading other members. We use a PG_TRY + * block to ensure we can undo those refcounts if we get an error before + * we finish constructing the CatCList. + */ + ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner); + + ctlist = NIL; + + PG_TRY(); + { + ScanKeyData cur_skey[CATCACHE_MAXKEYS]; + Relation relation; + SysScanDesc scandesc; + + /* + * Ok, need to make a lookup in the relation, copy the scankey and + * fill out any per-call fields. + */ + memcpy(cur_skey, cache->cc_skey, sizeof(ScanKeyData) * cache->cc_nkeys); + cur_skey[0].sk_argument = v1; + cur_skey[1].sk_argument = v2; + cur_skey[2].sk_argument = v3; + cur_skey[3].sk_argument = v4; + + relation = table_open(cache->cc_reloid, AccessShareLock); + + scandesc = systable_beginscan(relation, + cache->cc_indexoid, + IndexScanOK(cache, cur_skey), + NULL, + nkeys, + cur_skey); + + /* The list will be ordered iff we are doing an index scan */ + ordered = (scandesc->irel != NULL); + + while (HeapTupleIsValid(ntp = systable_getnext(scandesc))) + { + uint32 hashValue; + Index hashIndex; + bool found = false; + dlist_head *bucket; + + /* + * See if there's an entry for this tuple already. + */ + ct = NULL; + hashValue = CatalogCacheComputeTupleHashValue(cache, cache->cc_nkeys, ntp); + hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets); + + bucket = &cache->cc_bucket[hashIndex]; + dlist_foreach(iter, bucket) + { + ct = dlist_container(CatCTup, cache_elem, iter.cur); + + if (ct->dead || ct->negative) + continue; /* ignore dead and negative entries */ + + if (ct->hash_value != hashValue) + continue; /* quickly skip entry if wrong hash val */ + + if (!ItemPointerEquals(&(ct->tuple.t_self), &(ntp->t_self))) + continue; /* not same tuple */ + + /* + * Found a match, but can't use it if it belongs to another + * list already + */ + if (ct->c_list) + continue; + + found = true; + break; /* A-OK */ + } + + if (!found) + { + /* We didn't find a usable entry, so make a new one */ + ct = CatalogCacheCreateEntry(cache, ntp, arguments, + hashValue, hashIndex, + false); + } + + /* Careful here: add entry to ctlist, then bump its refcount */ + /* This way leaves state correct if lappend runs out of memory */ + ctlist = lappend(ctlist, ct); + ct->refcount++; + } + + systable_endscan(scandesc); + + table_close(relation, AccessShareLock); + + /* Now we can build the CatCList entry. */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + nmembers = list_length(ctlist); + cl = (CatCList *) + palloc(offsetof(CatCList, members) + nmembers * sizeof(CatCTup *)); + + /* Extract key values */ + CatCacheCopyKeys(cache->cc_tupdesc, nkeys, cache->cc_keyno, + arguments, cl->keys); + MemoryContextSwitchTo(oldcxt); + + /* + * We are now past the last thing that could trigger an elog before we + * have finished building the CatCList and remembering it in the + * resource owner. So it's OK to fall out of the PG_TRY, and indeed + * we'd better do so before we start marking the members as belonging + * to the list. + */ + + } + PG_CATCH(); + { + foreach(ctlist_item, ctlist) + { + ct = (CatCTup *) lfirst(ctlist_item); + Assert(ct->c_list == NULL); + Assert(ct->refcount > 0); + ct->refcount--; + if ( +#ifndef CATCACHE_FORCE_RELEASE + ct->dead && +#endif + ct->refcount == 0 && + (ct->c_list == NULL || ct->c_list->refcount == 0)) + CatCacheRemoveCTup(cache, ct); + } + + PG_RE_THROW(); + } + PG_END_TRY(); + + cl->cl_magic = CL_MAGIC; + cl->my_cache = cache; + cl->refcount = 0; /* for the moment */ + cl->dead = false; + cl->ordered = ordered; + cl->nkeys = nkeys; + cl->hash_value = lHashValue; + cl->n_members = nmembers; + + i = 0; + foreach(ctlist_item, ctlist) + { + cl->members[i++] = ct = (CatCTup *) lfirst(ctlist_item); + Assert(ct->c_list == NULL); + ct->c_list = cl; + /* release the temporary refcount on the member */ + Assert(ct->refcount > 0); + ct->refcount--; + /* mark list dead if any members already dead */ + if (ct->dead) + cl->dead = true; + } + Assert(i == nmembers); + + dlist_push_head(&cache->cc_lists, &cl->cache_elem); + + /* Finally, bump the list's refcount and return it */ + cl->refcount++; + ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl); + + CACHE_elog(DEBUG2, "SearchCatCacheList(%s): made list of %d members", + cache->cc_relname, nmembers); + + return cl; +} + +/* + * ReleaseCatCacheList + * + * Decrement the reference count of a catcache list. + */ +void +ReleaseCatCacheList(CatCList *list) +{ + /* Safety checks to ensure we were handed a cache entry */ + Assert(list->cl_magic == CL_MAGIC); + Assert(list->refcount > 0); + list->refcount--; + ResourceOwnerForgetCatCacheListRef(CurrentResourceOwner, list); + + if ( +#ifndef CATCACHE_FORCE_RELEASE + list->dead && +#endif + list->refcount == 0) + CatCacheRemoveCList(list->my_cache, list); +} + + +/* + * CatalogCacheCreateEntry + * Create a new CatCTup entry, copying the given HeapTuple and other + * supplied data into it. The new entry initially has refcount 0. + */ +static CatCTup * +CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments, + uint32 hashValue, Index hashIndex, + bool negative) +{ + CatCTup *ct; + HeapTuple dtp; + MemoryContext oldcxt; + + /* negative entries have no tuple associated */ + if (ntp) + { + int i; + + Assert(!negative); + + /* + * If there are any out-of-line toasted fields in the tuple, expand + * them in-line. This saves cycles during later use of the catcache + * entry, and also protects us against the possibility of the toast + * tuples being freed before we attempt to fetch them, in case of + * something using a slightly stale catcache entry. + */ + if (HeapTupleHasExternal(ntp)) + dtp = toast_flatten_tuple(ntp, cache->cc_tupdesc); + else + dtp = ntp; + + /* Allocate memory for CatCTup and the cached tuple in one go */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + ct = (CatCTup *) palloc(sizeof(CatCTup) + + MAXIMUM_ALIGNOF + dtp->t_len); + ct->tuple.t_len = dtp->t_len; + ct->tuple.t_self = dtp->t_self; + ct->tuple.t_tableOid = dtp->t_tableOid; + ct->tuple.t_data = (HeapTupleHeader) + MAXALIGN(((char *) ct) + sizeof(CatCTup)); + /* copy tuple contents */ + memcpy((char *) ct->tuple.t_data, + (const char *) dtp->t_data, + dtp->t_len); + MemoryContextSwitchTo(oldcxt); + + if (dtp != ntp) + heap_freetuple(dtp); + + /* extract keys - they'll point into the tuple if not by-value */ + for (i = 0; i < cache->cc_nkeys; i++) + { + Datum atp; + bool isnull; + + atp = heap_getattr(&ct->tuple, + cache->cc_keyno[i], + cache->cc_tupdesc, + &isnull); + Assert(!isnull); + ct->keys[i] = atp; + } + } + else + { + Assert(negative); + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + ct = (CatCTup *) palloc(sizeof(CatCTup)); + + /* + * Store keys - they'll point into separately allocated memory if not + * by-value. + */ + CatCacheCopyKeys(cache->cc_tupdesc, cache->cc_nkeys, cache->cc_keyno, + arguments, ct->keys); + MemoryContextSwitchTo(oldcxt); + } + + /* + * Finish initializing the CatCTup header, and add it to the cache's + * linked list and counts. + */ + ct->ct_magic = CT_MAGIC; + ct->my_cache = cache; + ct->c_list = NULL; + ct->refcount = 0; /* for the moment */ + ct->dead = false; + ct->negative = negative; + ct->hash_value = hashValue; + + dlist_push_head(&cache->cc_bucket[hashIndex], &ct->cache_elem); + + cache->cc_ntup++; + CacheHdr->ch_ntup++; + + /* + * If the hash table has become too full, enlarge the buckets array. Quite + * arbitrarily, we enlarge when fill factor > 2. + */ + if (cache->cc_ntup > cache->cc_nbuckets * 2) + RehashCatCache(cache); + + return ct; +} + +/* + * Helper routine that frees keys stored in the keys array. + */ +static void +CatCacheFreeKeys(TupleDesc tupdesc, int nkeys, int *attnos, Datum *keys) +{ + int i; + + for (i = 0; i < nkeys; i++) + { + int attnum = attnos[i]; + Form_pg_attribute att; + + /* system attribute are not supported in caches */ + Assert(attnum > 0); + + att = TupleDescAttr(tupdesc, attnum - 1); + + if (!att->attbyval) + pfree(DatumGetPointer(keys[i])); + } +} + +/* + * Helper routine that copies the keys in the srckeys array into the dstkeys + * one, guaranteeing that the datums are fully allocated in the current memory + * context. + */ +static void +CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos, + Datum *srckeys, Datum *dstkeys) +{ + int i; + + /* + * XXX: memory and lookup performance could possibly be improved by + * storing all keys in one allocation. + */ + + for (i = 0; i < nkeys; i++) + { + int attnum = attnos[i]; + Form_pg_attribute att = TupleDescAttr(tupdesc, attnum - 1); + Datum src = srckeys[i]; + NameData srcname; + + /* + * Must be careful in case the caller passed a C string where a NAME + * is wanted: convert the given argument to a correctly padded NAME. + * Otherwise the memcpy() done by datumCopy() could fall off the end + * of memory. + */ + if (att->atttypid == NAMEOID) + { + namestrcpy(&srcname, DatumGetCString(src)); + src = NameGetDatum(&srcname); + } + + dstkeys[i] = datumCopy(src, + att->attbyval, + att->attlen); + } + +} + +/* + * PrepareToInvalidateCacheTuple() + * + * This is part of a rather subtle chain of events, so pay attention: + * + * When a tuple is inserted or deleted, it cannot be flushed from the + * catcaches immediately, for reasons explained at the top of cache/inval.c. + * Instead we have to add entry(s) for the tuple to a list of pending tuple + * invalidations that will be done at the end of the command or transaction. + * + * The lists of tuples that need to be flushed are kept by inval.c. This + * routine is a helper routine for inval.c. Given a tuple belonging to + * the specified relation, find all catcaches it could be in, compute the + * correct hash value for each such catcache, and call the specified + * function to record the cache id and hash value in inval.c's lists. + * SysCacheInvalidate will be called later, if appropriate, + * using the recorded information. + * + * For an insert or delete, tuple is the target tuple and newtuple is NULL. + * For an update, we are called just once, with tuple being the old tuple + * version and newtuple the new version. We should make two list entries + * if the tuple's hash value changed, but only one if it didn't. + * + * Note that it is irrelevant whether the given tuple is actually loaded + * into the catcache at the moment. Even if it's not there now, it might + * be by the end of the command, or there might be a matching negative entry + * to flush --- or other backends' caches might have such entries --- so + * we have to make list entries to flush it later. + * + * Also note that it's not an error if there are no catcaches for the + * specified relation. inval.c doesn't know exactly which rels have + * catcaches --- it will call this routine for any tuple that's in a + * system relation. + */ +void +PrepareToInvalidateCacheTuple(Relation relation, + HeapTuple tuple, + HeapTuple newtuple, + void (*function) (int, uint32, Oid)) +{ + slist_iter iter; + Oid reloid; + + CACHE_elog(DEBUG2, "PrepareToInvalidateCacheTuple: called"); + + /* + * sanity checks + */ + Assert(RelationIsValid(relation)); + Assert(HeapTupleIsValid(tuple)); + Assert(PointerIsValid(function)); + Assert(CacheHdr != NULL); + + reloid = RelationGetRelid(relation); + + /* ---------------- + * for each cache + * if the cache contains tuples from the specified relation + * compute the tuple's hash value(s) in this cache, + * and call the passed function to register the information. + * ---------------- + */ + + slist_foreach(iter, &CacheHdr->ch_caches) + { + CatCache *ccp = slist_container(CatCache, cc_next, iter.cur); + uint32 hashvalue; + Oid dbid; + + if (ccp->cc_reloid != reloid) + continue; + + /* Just in case cache hasn't finished initialization yet... */ + if (ccp->cc_tupdesc == NULL) + CatalogCacheInitializeCache(ccp); + + hashvalue = CatalogCacheComputeTupleHashValue(ccp, ccp->cc_nkeys, tuple); + dbid = ccp->cc_relisshared ? (Oid) 0 : MyDatabaseId; + + (*function) (ccp->id, hashvalue, dbid); + + if (newtuple) + { + uint32 newhashvalue; + + newhashvalue = CatalogCacheComputeTupleHashValue(ccp, ccp->cc_nkeys, newtuple); + + if (newhashvalue != hashvalue) + (*function) (ccp->id, newhashvalue, dbid); + } + } +} + + +/* + * Subroutines for warning about reference leaks. These are exported so + * that resowner.c can call them. + */ +void +PrintCatCacheLeakWarning(HeapTuple tuple) +{ + CatCTup *ct = (CatCTup *) (((char *) tuple) - + offsetof(CatCTup, tuple)); + + /* Safety check to ensure we were handed a cache entry */ + Assert(ct->ct_magic == CT_MAGIC); + + elog(WARNING, "cache reference leak: cache %s (%d), tuple %u/%u has count %d", + ct->my_cache->cc_relname, ct->my_cache->id, + ItemPointerGetBlockNumber(&(tuple->t_self)), + ItemPointerGetOffsetNumber(&(tuple->t_self)), + ct->refcount); +} + +void +PrintCatCacheListLeakWarning(CatCList *list) +{ + elog(WARNING, "cache reference leak: cache %s (%d), list %p has count %d", + list->my_cache->cc_relname, list->my_cache->id, + list, list->refcount); +} |