diff options
Diffstat (limited to 'src/backend/utils/cache/catcache.c')
-rw-r--r-- | src/backend/utils/cache/catcache.c | 806 |
1 files changed, 493 insertions, 313 deletions
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c index b79d6712559..e04792d71fa 100644 --- a/src/backend/utils/cache/catcache.c +++ b/src/backend/utils/cache/catcache.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/cache/catcache.c,v 1.89 2002/03/02 21:39:32 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/cache/catcache.c,v 1.90 2002/03/03 17:47:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -91,15 +91,15 @@ static const Oid eqproc[] = { #define EQPROC(SYSTEMTYPEOID) eqproc[(SYSTEMTYPEOID)-BOOLOID] -static void CatCacheRemoveCTup(CatCache *cache, CatCTup *ct); -static Index CatalogCacheComputeHashIndex(CatCache *cache, +static uint32 CatalogCacheComputeHashValue(CatCache *cache, ScanKey cur_skey); -static Index CatalogCacheComputeTupleHashIndex(CatCache *cache, +static uint32 CatalogCacheComputeTupleHashValue(CatCache *cache, HeapTuple tuple); -static void CatalogCacheInitializeCache(CatCache *cache); #ifdef CATCACHE_STATS static void CatCachePrintStats(void); #endif +static void CatCacheRemoveCTup(CatCache *cache, CatCTup *ct); +static void CatalogCacheInitializeCache(CatCache *cache); /* @@ -136,221 +136,17 @@ GetCCHashFunc(Oid keytype) } } -#ifdef CATCACHE_STATS - -static void -CatCachePrintStats(void) -{ - CatCache *cache; - long cc_searches = 0; - long cc_hits = 0; - long cc_newloads = 0; - - elog(LOG, "Catcache stats dump: %d/%d tuples in catcaches", - CacheHdr->ch_ntup, CacheHdr->ch_maxtup); - - for (cache = CacheHdr->ch_caches; cache; cache = cache->cc_next) - { - if (cache->cc_ntup == 0 && cache->cc_searches == 0) - continue; /* don't print unused caches */ - elog(LOG, "Catcache %s/%s: %d tup, %ld srch, %ld hits, %ld loads, %ld not found", - cache->cc_relname, - cache->cc_indname, - cache->cc_ntup, - cache->cc_searches, - cache->cc_hits, - cache->cc_newloads, - cache->cc_searches - cache->cc_hits - cache->cc_newloads); - cc_searches += cache->cc_searches; - cc_hits += cache->cc_hits; - cc_newloads += cache->cc_newloads; - } - elog(LOG, "Catcache totals: %d tup, %ld srch, %ld hits, %ld loads, %ld not found", - CacheHdr->ch_ntup, - cc_searches, - cc_hits, - cc_newloads, - cc_searches - cc_hits - cc_newloads); -} - -#endif /* CATCACHE_STATS */ - - -/* - * 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_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); -} - - /* - * CatalogCacheInitializeCache + * CatalogCacheComputeHashValue * - * 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! + * Compute the hash value associated with a given set of lookup keys */ -#ifdef CACHEDEBUG -#define CatalogCacheInitializeCache_DEBUG1 \ - elog(LOG, "CatalogCacheInitializeCache: cache @%p %s", cache, \ - cache->cc_relname) - -#define CatalogCacheInitializeCache_DEBUG2 \ -do { \ - if (cache->cc_key[i] > 0) { \ - elog(LOG, "CatalogCacheInitializeCache: load %d/%d w/%d, %u", \ - i+1, cache->cc_nkeys, cache->cc_key[i], \ - tupdesc->attrs[cache->cc_key[i] - 1]->atttypid); \ - } else { \ - elog(LOG, "CatalogCacheInitializeCache: load %d/%d w/%d", \ - i+1, cache->cc_nkeys, cache->cc_key[i]); \ - } \ -} while(0) - -#else -#define CatalogCacheInitializeCache_DEBUG1 -#define CatalogCacheInitializeCache_DEBUG2 -#endif - -static void -CatalogCacheInitializeCache(CatCache *cache) +static uint32 +CatalogCacheComputeHashValue(CatCache *cache, ScanKey cur_skey) { - Relation relation; - MemoryContext oldcxt; - TupleDesc tupdesc; - int i; - - CatalogCacheInitializeCache_DEBUG1; - - /* - * Open the relation without locking --- we only need the tupdesc, - * which we assume will never change ... - */ - relation = heap_openr(cache->cc_relname, NoLock); - Assert(RelationIsValid(relation)); - - /* - * 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)); - - /* - * get the relation's relisshared flag, too - */ - cache->cc_relisshared = RelationGetForm(relation)->relisshared; - - /* - * return to the caller's memory context and close the rel - */ - MemoryContextSwitchTo(oldcxt); - - heap_close(relation, NoLock); + uint32 hashValue = 0; - CACHE3_elog(LOG, "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; - - CatalogCacheInitializeCache_DEBUG2; - - if (cache->cc_key[i] > 0) - keytype = tupdesc->attrs[cache->cc_key[i] - 1]->atttypid; - else - { - if (cache->cc_key[i] != ObjectIdAttributeNumber) - elog(FATAL, "CatalogCacheInit: only sys attr supported is OID"); - keytype = OIDOID; - } - - cache->cc_hashfunc[i] = GetCCHashFunc(keytype); - - /* - * If GetCCHashFunc liked the type, safe to index into eqproc[] - */ - cache->cc_skey[i].sk_procedure = EQPROC(keytype); - - /* Do function lookup */ - fmgr_info_cxt(cache->cc_skey[i].sk_procedure, - &cache->cc_skey[i].sk_func, - CacheMemoryContext); - - /* Initialize sk_attno suitably for HeapKeyTest() and heap scans */ - cache->cc_skey[i].sk_attno = cache->cc_key[i]; - - CACHE4_elog(LOG, "CatalogCacheInit %s %d %p", - cache->cc_relname, - i, - cache); - } - - /* - * mark this cache fully initialized - */ - cache->cc_tupdesc = tupdesc; -} - -/* - * InitCatCachePhase2 -- external interface for CatalogCacheInitializeCache - * - * The only reason to call this routine is to ensure that the relcache - * has created entries for all the catalogs and indexes referenced by - * catcaches. Therefore, open the index too. An exception is the indexes - * on pg_am, which we don't use (cf. IndexScanOK). - */ -void -InitCatCachePhase2(CatCache *cache) -{ - if (cache->cc_tupdesc == NULL) - CatalogCacheInitializeCache(cache); - - if (cache->id != AMOID && - cache->id != AMNAME) - { - Relation idesc; - - idesc = index_openr(cache->cc_indname); - index_close(idesc); - } -} - -/* - * CatalogCacheComputeHashIndex - */ -static Index -CatalogCacheComputeHashIndex(CatCache *cache, ScanKey cur_skey) -{ - uint32 hashIndex = 0; - - CACHE4_elog(LOG, "CatalogCacheComputeHashIndex %s %d %p", + CACHE4_elog(DEBUG1, "CatalogCacheComputeHashValue %s %d %p", cache->cc_relname, cache->cc_nkeys, cache); @@ -358,39 +154,40 @@ CatalogCacheComputeHashIndex(CatCache *cache, ScanKey cur_skey) switch (cache->cc_nkeys) { case 4: - hashIndex ^= + hashValue ^= DatumGetUInt32(DirectFunctionCall1(cache->cc_hashfunc[3], cur_skey[3].sk_argument)) << 9; /* FALLTHROUGH */ case 3: - hashIndex ^= + hashValue ^= DatumGetUInt32(DirectFunctionCall1(cache->cc_hashfunc[2], cur_skey[2].sk_argument)) << 6; /* FALLTHROUGH */ case 2: - hashIndex ^= + hashValue ^= DatumGetUInt32(DirectFunctionCall1(cache->cc_hashfunc[1], cur_skey[1].sk_argument)) << 3; /* FALLTHROUGH */ case 1: - hashIndex ^= + hashValue ^= DatumGetUInt32(DirectFunctionCall1(cache->cc_hashfunc[0], cur_skey[0].sk_argument)); break; default: - elog(FATAL, "CCComputeHashIndex: %d cc_nkeys", cache->cc_nkeys); + elog(FATAL, "CCComputeHashValue: %d cc_nkeys", cache->cc_nkeys); break; } - hashIndex %= (uint32) cache->cc_size; - return (Index) hashIndex; + + return hashValue; } /* - * CatalogCacheComputeTupleHashIndex + * CatalogCacheComputeTupleHashValue + * + * Compute the hash value associated with a given tuple to be cached */ -static Index -CatalogCacheComputeTupleHashIndex(CatCache *cache, - HeapTuple tuple) +static uint32 +CatalogCacheComputeTupleHashValue(CatCache *cache, HeapTuple tuple) { ScanKeyData cur_skey[4]; bool isNull = false; @@ -442,16 +239,75 @@ CatalogCacheComputeTupleHashIndex(CatCache *cache, Assert(!isNull); break; default: - elog(FATAL, "CCComputeTupleHashIndex: %d cc_nkeys", + elog(FATAL, "CCComputeTupleHashValue: %d cc_nkeys", cache->cc_nkeys); break; } - return CatalogCacheComputeHashIndex(cache, cur_skey); + return CatalogCacheComputeHashValue(cache, cur_skey); } + +#ifdef CATCACHE_STATS + +static void +CatCachePrintStats(void) +{ + CatCache *cache; + long cc_searches = 0; + long cc_hits = 0; + long cc_neg_hits = 0; + long cc_newloads = 0; + long cc_invals = 0; + long cc_discards = 0; + + elog(DEBUG1, "Catcache stats dump: %d/%d tuples in catcaches", + CacheHdr->ch_ntup, CacheHdr->ch_maxtup); + + for (cache = CacheHdr->ch_caches; cache; cache = cache->cc_next) + { + if (cache->cc_ntup == 0 && cache->cc_searches == 0) + continue; /* don't print unused caches */ + elog(DEBUG1, "Catcache %s/%s: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld discards", + cache->cc_relname, + cache->cc_indname, + 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_discards); + 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_discards += cache->cc_discards; + } + elog(DEBUG1, "Catcache totals: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld discards", + 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_discards); +} + +#endif /* CATCACHE_STATS */ + + /* * CatCacheRemoveCTup + * + * Unlink and delete the given cache entry */ static void CatCacheRemoveCTup(CatCache *cache, CatCTup *ct) @@ -473,16 +329,26 @@ CatCacheRemoveCTup(CatCache *cache, CatCTup *ct) } /* - * CatalogCacheIdInvalidate() + * CatalogCacheIdInvalidate + * + * Invalidate entries in the specified cache, given a hash value and + * item pointer. Positive entries are deleted if they match the item + * pointer. Negative entries must be deleted if they match the hash + * value (since we do not have the exact key of the tuple that's being + * inserted). But this should only rarely result in loss of a cache + * entry that could have been kept. * - * Invalidate a tuple given a cache id. In this case the id should always - * be found (whether the cache has opened its relation or not). Of course, - * if the cache has yet to open its relation, there will be no tuples so - * no problem. + * Note that it's not very relevant whether the tuple identified by + * the item pointer is being inserted or deleted. We don't expect to + * find matching positive entries in the one case, and we don't expect + * to find matching negative entries in the other; but we will do the + * right things in any case. + * + * This routine is only quasi-public: it should only be used by inval.c. */ void CatalogCacheIdInvalidate(int cacheId, - Index hashIndex, + uint32 hashValue, ItemPointer pointer) { CatCache *ccp; @@ -491,37 +357,51 @@ CatalogCacheIdInvalidate(int cacheId, * sanity checks */ Assert(ItemPointerIsValid(pointer)); - CACHE1_elog(LOG, "CatalogCacheIdInvalidate: called"); + CACHE1_elog(DEBUG1, "CatalogCacheIdInvalidate: called"); /* * inspect caches to find the proper cache */ for (ccp = CacheHdr->ch_caches; ccp; ccp = ccp->cc_next) { + Index hashIndex; Dlelem *elt, *nextelt; if (cacheId != ccp->id) continue; - Assert(hashIndex < ccp->cc_size); + /* + * 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. + */ /* - * inspect the hash bucket until we find a match or exhaust + * inspect the proper hash bucket for matches */ + hashIndex = (Index) (hashValue % (uint32) ccp->cc_size); + for (elt = DLGetHead(&ccp->cc_bucket[hashIndex]); elt; elt = nextelt) { CatCTup *ct = (CatCTup *) DLE_VAL(elt); nextelt = DLGetSucc(elt); - if (ItemPointerEquals(pointer, &ct->tuple.t_self)) + if (hashValue != ct->hash_value) + continue; /* ignore non-matching hash values */ + + if (ct->negative || + ItemPointerEquals(pointer, &ct->tuple.t_self)) { if (ct->refcount > 0) ct->dead = true; else CatCacheRemoveCTup(ccp, ct); - CACHE1_elog(LOG, "CatalogCacheIdInvalidate: invalidated"); + CACHE1_elog(DEBUG1, "CatalogCacheIdInvalidate: invalidated"); +#ifdef CATCACHE_STATS + ccp->cc_invals++; +#endif /* could be multiple matches, so keep looking! */ } } @@ -531,18 +411,34 @@ CatalogCacheIdInvalidate(int cacheId, /* ---------------------------------------------------------------- * public functions - * - * AtEOXact_CatCache - * ResetCatalogCaches - * InitCatCache - * SearchCatCache - * ReleaseCatCache - * RelationInvalidateCatalogCacheTuple * ---------------------------------------------------------------- */ /* + * 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_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); +} + + +/* * AtEOXact_CatCache * * Clean up catcaches at end of transaction (either commit or abort) @@ -609,6 +505,9 @@ ResetCatalogCache(CatCache *cache) ct->dead = true; else CatCacheRemoveCTup(cache, ct); +#ifdef CATCACHE_STATS + cache->cc_invals++; +#endif } } } @@ -623,12 +522,12 @@ ResetCatalogCaches(void) { CatCache *cache; - CACHE1_elog(LOG, "ResetCatalogCaches called"); + CACHE1_elog(DEBUG1, "ResetCatalogCaches called"); for (cache = CacheHdr->ch_caches; cache; cache = cache->cc_next) ResetCatalogCache(cache); - CACHE1_elog(LOG, "end of ResetCatalogCaches call"); + CACHE1_elog(DEBUG1, "end of ResetCatalogCaches call"); } /* @@ -656,7 +555,7 @@ CatalogCacheFlushRelation(Oid relId) { CatCache *cache; - CACHE2_elog(LOG, "CatalogCacheFlushRelation called for %u", relId); + CACHE2_elog(DEBUG1, "CatalogCacheFlushRelation called for %u", relId); for (cache = CacheHdr->ch_caches; cache; cache = cache->cc_next) { @@ -691,6 +590,13 @@ CatalogCacheFlushRelation(Oid relId) nextelt = DLGetSucc(elt); + /* + * Negative entries are never considered related to a rel, + * even if the rel is part of their lookup key. + */ + if (ct->negative) + continue; + if (cache->cc_reloidattr == ObjectIdAttributeNumber) tupRelid = ct->tuple.t_data->t_oid; else @@ -711,12 +617,15 @@ CatalogCacheFlushRelation(Oid relId) ct->dead = true; else CatCacheRemoveCTup(cache, ct); +#ifdef CATCACHE_STATS + cache->cc_invals++; +#endif } } } } - CACHE1_elog(LOG, "end of CatalogCacheFlushRelation call"); + CACHE1_elog(DEBUG1, "end of CatalogCacheFlushRelation call"); } /* @@ -730,7 +639,7 @@ CatalogCacheFlushRelation(Oid relId) #ifdef CACHEDEBUG #define InitCatCache_DEBUG1 \ do { \ - elog(LOG, "InitCatCache: rel=%s id=%d nkeys=%d size=%d\n", \ + elog(DEBUG1, "InitCatCache: rel=%s id=%d nkeys=%d size=%d\n", \ cp->cc_relname, cp->id, cp->cc_nkeys, cp->cc_size); \ } while(0) @@ -791,9 +700,10 @@ InitCatCache(int id, cp->id = id; cp->cc_relname = relname; cp->cc_indname = indname; - cp->cc_reloidattr = reloidattr; + cp->cc_reloid = InvalidOid; /* temporary */ cp->cc_relisshared = false; /* temporary */ cp->cc_tupdesc = (TupleDesc) NULL; + cp->cc_reloidattr = reloidattr; cp->cc_ntup = 0; cp->cc_size = NCCBUCKETS; cp->cc_nkeys = nkeys; @@ -820,6 +730,152 @@ InitCatCache(int id, return cp; } +/* + * 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(DEBUG1, "CatalogCacheInitializeCache: cache @%p %s", cache, \ + cache->cc_relname) + +#define CatalogCacheInitializeCache_DEBUG2 \ +do { \ + if (cache->cc_key[i] > 0) { \ + elog(DEBUG1, "CatalogCacheInitializeCache: load %d/%d w/%d, %u", \ + i+1, cache->cc_nkeys, cache->cc_key[i], \ + tupdesc->attrs[cache->cc_key[i] - 1]->atttypid); \ + } else { \ + elog(DEBUG1, "CatalogCacheInitializeCache: load %d/%d w/%d", \ + i+1, cache->cc_nkeys, cache->cc_key[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; + + /* + * Open the relation without locking --- we only need the tupdesc, + * which we assume will never change ... + */ + relation = heap_openr(cache->cc_relname, NoLock); + Assert(RelationIsValid(relation)); + + /* + * 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)); + + /* + * get the relation's OID and relisshared flag, too + */ + cache->cc_reloid = RelationGetRelid(relation); + cache->cc_relisshared = RelationGetForm(relation)->relisshared; + + /* + * return to the caller's memory context and close the rel + */ + MemoryContextSwitchTo(oldcxt); + + heap_close(relation, NoLock); + + CACHE3_elog(DEBUG1, "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; + + CatalogCacheInitializeCache_DEBUG2; + + if (cache->cc_key[i] > 0) + keytype = tupdesc->attrs[cache->cc_key[i] - 1]->atttypid; + else + { + if (cache->cc_key[i] != ObjectIdAttributeNumber) + elog(FATAL, "CatalogCacheInit: only sys attr supported is OID"); + keytype = OIDOID; + } + + cache->cc_hashfunc[i] = GetCCHashFunc(keytype); + + cache->cc_isname[i] = (keytype == NAMEOID); + + /* + * If GetCCHashFunc liked the type, safe to index into eqproc[] + */ + cache->cc_skey[i].sk_procedure = EQPROC(keytype); + + /* Do function lookup */ + fmgr_info_cxt(cache->cc_skey[i].sk_procedure, + &cache->cc_skey[i].sk_func, + CacheMemoryContext); + + /* Initialize sk_attno suitably for HeapKeyTest() and heap scans */ + cache->cc_skey[i].sk_attno = cache->cc_key[i]; + + CACHE4_elog(DEBUG1, "CatalogCacheInit %s %d %p", + cache->cc_relname, + i, + cache); + } + + /* + * mark this cache fully initialized + */ + cache->cc_tupdesc = tupdesc; +} + +/* + * InitCatCachePhase2 -- external interface for CatalogCacheInitializeCache + * + * The only reason to call this routine is to ensure that the relcache + * has created entries for all the catalogs and indexes referenced by + * catcaches. Therefore, open the index too. An exception is the indexes + * on pg_am, which we don't use (cf. IndexScanOK). + */ +void +InitCatCachePhase2(CatCache *cache) +{ + if (cache->cc_tupdesc == NULL) + CatalogCacheInitializeCache(cache); + + if (cache->id != AMOID && + cache->id != AMNAME) + { + Relation idesc; + + idesc = index_openr(cache->cc_indname); + index_close(idesc); + } +} + /* * IndexScanOK @@ -874,10 +930,20 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey) } /* - * SearchCatCache + * SearchCatCache * * This call searches a system cache for a tuple, opening the relation - * if necessary (the first access to a particular cache). + * 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, @@ -887,11 +953,13 @@ SearchCatCache(CatCache *cache, Datum v4) { ScanKeyData cur_skey[4]; - Index hash; + uint32 hashValue; + Index hashIndex; Dlelem *elt; CatCTup *ct; - HeapTuple ntp; Relation relation; + HeapTuple ntp; + int i; MemoryContext oldcxt; /* @@ -916,12 +984,13 @@ SearchCatCache(CatCache *cache, /* * find the hash bucket in which to look for the tuple */ - hash = CatalogCacheComputeHashIndex(cache, cur_skey); + hashValue = CatalogCacheComputeHashValue(cache, cur_skey); + hashIndex = (Index) (hashValue % (uint32) cache->cc_size); /* * scan the hash bucket until we find a match or exhaust our tuples */ - for (elt = DLGetHead(&cache->cc_bucket[hash]); + for (elt = DLGetHead(&cache->cc_bucket[hashIndex]); elt; elt = DLGetSucc(elt)) { @@ -932,9 +1001,11 @@ SearchCatCache(CatCache *cache, if (ct->dead) continue; /* ignore dead entries */ + if (ct->hash_value != hashValue) + continue; /* quickly skip entry if wrong hash val */ + /* - * see if the cached tuple matches our key. (should we be worried - * about time ranges? -cim 10/2/90) + * see if the cached tuple matches our key. */ HeapKeyTest(&ct->tuple, cache->cc_tupdesc, @@ -945,33 +1016,53 @@ SearchCatCache(CatCache *cache, continue; /* - * we found a tuple in the cache: bump its refcount, move it to - * the front of the LRU list, and return it. We also 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.) + * we found a match in the cache: move it to the front of the global + * LRU list. We also 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.) */ - ct->refcount++; - DLMoveToFront(&ct->lrulist_elem); DLMoveToFront(&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) + { + ct->refcount++; + #ifdef CACHEDEBUG - CACHE3_elog(LOG, "SearchCatCache(%s): found in bucket %d", - cache->cc_relname, hash); + CACHE3_elog(DEBUG1, "SearchCatCache(%s): found in bucket %d", + cache->cc_relname, hashIndex); #endif /* CACHEDEBUG */ #ifdef CATCACHE_STATS - cache->cc_hits++; + cache->cc_hits++; #endif - return &ct->tuple; + return &ct->tuple; + } + else + { +#ifdef CACHEDEBUG + CACHE3_elog(DEBUG1, "SearchCatCache(%s): found neg entry in bucket %d", + cache->cc_relname, hashIndex); +#endif /* CACHEDEBUG */ + +#ifdef CATCACHE_STATS + cache->cc_neg_hits++; +#endif + + return NULL; + } } /* - * Tuple was not found in cache, so we have to try and retrieve it - * directly from the relation. If it's found, we add it to the cache. + * 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 @@ -987,14 +1078,18 @@ SearchCatCache(CatCache *cache, /* * open the relation associated with the cache */ - relation = heap_openr(cache->cc_relname, AccessShareLock); + relation = heap_open(cache->cc_reloid, AccessShareLock); + + /* + * Pre-create cache entry header, and mark no tuple found. + */ + ct = (CatCTup *) MemoryContextAlloc(CacheMemoryContext, sizeof(CatCTup)); + ct->negative = true; /* * Scan the relation to find the tuple. If there's an index, and if * it's safe to do so, use the index. Else do a heap scan. */ - ct = NULL; - if ((RelationGetForm(relation))->relhasindex && !IsIgnoringSystemIndexes() && IndexScanOK(cache, cur_skey)) @@ -1004,9 +1099,8 @@ SearchCatCache(CatCache *cache, RetrieveIndexResult indexRes; HeapTupleData tuple; Buffer buffer; - int i; - CACHE2_elog(LOG, "SearchCatCache(%s): performing index scan", + CACHE2_elog(DEBUG1, "SearchCatCache(%s): performing index scan", cache->cc_relname); /* @@ -1031,8 +1125,8 @@ SearchCatCache(CatCache *cache, { /* Copy tuple into our context */ oldcxt = MemoryContextSwitchTo(CacheMemoryContext); - ct = (CatCTup *) palloc(sizeof(CatCTup)); heap_copytuple_with_tuple(&tuple, &ct->tuple); + ct->negative = false; MemoryContextSwitchTo(oldcxt); ReleaseBuffer(buffer); break; @@ -1045,7 +1139,7 @@ SearchCatCache(CatCache *cache, { HeapScanDesc sd; - CACHE2_elog(LOG, "SearchCatCache(%s): performing heap scan", + CACHE2_elog(DEBUG1, "SearchCatCache(%s): performing heap scan", cache->cc_relname); sd = heap_beginscan(relation, 0, SnapshotNow, @@ -1057,8 +1151,8 @@ SearchCatCache(CatCache *cache, { /* Copy tuple into our context */ oldcxt = MemoryContextSwitchTo(CacheMemoryContext); - ct = (CatCTup *) palloc(sizeof(CatCTup)); heap_copytuple_with_tuple(ntp, &ct->tuple); + ct->negative = false; MemoryContextSwitchTo(oldcxt); /* We should not free the result of heap_getnext... */ } @@ -1072,58 +1166,140 @@ SearchCatCache(CatCache *cache, heap_close(relation, AccessShareLock); /* - * scan is complete. if tup was found, we can add it to the cache. + * scan is complete. If tuple was not found, we need to build + * a fake tuple for the negative cache entry. The fake tuple has + * the correct key columns, but nulls everywhere else. */ - if (ct == NULL) - return NULL; + if (ct->negative) + { + TupleDesc tupDesc = cache->cc_tupdesc; + Datum *values; + char *nulls; + Oid negOid = InvalidOid; + + values = (Datum *) palloc(tupDesc->natts * sizeof(Datum)); + nulls = (char *) palloc(tupDesc->natts * sizeof(char)); + + memset(values, 0, tupDesc->natts * sizeof(Datum)); + memset(nulls, 'n', tupDesc->natts * sizeof(char)); + + for (i = 0; i < cache->cc_nkeys; i++) + { + int attindex = cache->cc_key[i]; + Datum keyval = cur_skey[i].sk_argument; + + if (attindex > 0) + { + /* + * Here we 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 in heap_formtuple could fall off the + * end of memory. + */ + if (cache->cc_isname[i]) + { + Name newval = (Name) palloc(NAMEDATALEN); + + namestrcpy(newval, DatumGetCString(keyval)); + keyval = NameGetDatum(newval); + } + values[attindex-1] = keyval; + nulls[attindex-1] = ' '; + } + else + { + Assert(attindex == ObjectIdAttributeNumber); + negOid = DatumGetObjectId(keyval); + } + } + + ntp = heap_formtuple(tupDesc, values, nulls); + + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + heap_copytuple_with_tuple(ntp, &ct->tuple); + ct->tuple.t_data->t_oid = negOid; + MemoryContextSwitchTo(oldcxt); + + heap_freetuple(ntp); + for (i = 0; i < cache->cc_nkeys; i++) + { + if (cache->cc_isname[i]) + pfree(DatumGetName(values[cache->cc_key[i]-1])); + } + pfree(values); + pfree(nulls); + } /* * Finish initializing the CatCTup header, and add it to the linked * lists. */ - CACHE1_elog(LOG, "SearchCatCache: found tuple"); - ct->ct_magic = CT_MAGIC; ct->my_cache = cache; DLInitElem(&ct->lrulist_elem, (void *) ct); DLInitElem(&ct->cache_elem, (void *) ct); ct->refcount = 1; /* count this first reference */ ct->dead = false; + ct->hash_value = hashValue; DLAddHead(&CacheHdr->ch_lrulist, &ct->lrulist_elem); - DLAddHead(&cache->cc_bucket[hash], &ct->cache_elem); - -#ifdef CATCACHE_STATS - cache->cc_newloads++; -#endif + DLAddHead(&cache->cc_bucket[hashIndex], &ct->cache_elem); /* * If we've exceeded the desired size of the caches, try to throw away - * the least recently used entry. + * the least recently used entry. NB: the newly-built entry cannot + * get thrown away here, because it has positive refcount. */ ++cache->cc_ntup; if (++CacheHdr->ch_ntup > CacheHdr->ch_maxtup) { - for (elt = DLGetTail(&CacheHdr->ch_lrulist); - elt; - elt = DLGetPred(elt)) + Dlelem *prevelt; + + for (elt = DLGetTail(&CacheHdr->ch_lrulist); elt; elt = prevelt) { CatCTup *oldct = (CatCTup *) DLE_VAL(elt); + prevelt = DLGetPred(elt); + if (oldct->refcount == 0) { - CACHE2_elog(LOG, "SearchCatCache(%s): Overflow, LRU removal", + CACHE2_elog(DEBUG1, "SearchCatCache(%s): Overflow, LRU removal", cache->cc_relname); +#ifdef CATCACHE_STATS + oldct->my_cache->cc_discards++; +#endif CatCacheRemoveCTup(oldct->my_cache, oldct); - break; + if (CacheHdr->ch_ntup <= CacheHdr->ch_maxtup) + break; } } } - CACHE4_elog(LOG, "SearchCatCache(%s): Contains %d/%d tuples", + CACHE4_elog(DEBUG1, "SearchCatCache(%s): Contains %d/%d tuples", cache->cc_relname, cache->cc_ntup, CacheHdr->ch_ntup); - CACHE3_elog(LOG, "SearchCatCache(%s): put in bucket %d", - cache->cc_relname, hash); + + if (ct->negative) + { + CACHE3_elog(DEBUG1, "SearchCatCache(%s): put neg entry in bucket %d", + cache->cc_relname, hashIndex); + + /* + * We are not returning the new entry to the caller, so reset its + * refcount. Note it would be uncool to set the refcount to 0 + * before doing the extra-entry removal step above. + */ + ct->refcount = 0; /* negative entries never have refs */ + + return NULL; + } + + CACHE3_elog(DEBUG1, "SearchCatCache(%s): put in bucket %d", + cache->cc_relname, hashIndex); + +#ifdef CATCACHE_STATS + cache->cc_newloads++; +#endif return &ct->tuple; } @@ -1164,7 +1340,7 @@ ReleaseCatCache(HeapTuple tuple) * * This is part of a rather subtle chain of events, so pay attention: * - * When a tuple is updated or deleted, it cannot be flushed from the + * 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. @@ -1172,15 +1348,16 @@ ReleaseCatCache(HeapTuple tuple) * 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 hashindex for each such catcache, and call the specified function - * to record the cache id, hashindex, and tuple ItemPointer in inval.c's + * correct hash value for each such catcache, and call the specified function + * to record the cache id, hash value, and tuple ItemPointer in inval.c's * lists. CatalogCacheIdInvalidate will be called later, if appropriate, * using the recorded information. * * 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 might be in other backends' caches - * --- so we have to be prepared to flush it. + * 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 @@ -1190,11 +1367,12 @@ ReleaseCatCache(HeapTuple tuple) void PrepareToInvalidateCacheTuple(Relation relation, HeapTuple tuple, - void (*function) (int, Index, ItemPointer, Oid)) + void (*function) (int, uint32, ItemPointer, Oid)) { CatCache *ccp; + Oid reloid; - CACHE1_elog(LOG, "PrepareToInvalidateCacheTuple: called"); + CACHE1_elog(DEBUG1, "PrepareToInvalidateCacheTuple: called"); /* * sanity checks @@ -1204,25 +1382,27 @@ PrepareToInvalidateCacheTuple(Relation relation, 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 index in this cache, + * compute the tuple's hash value in this cache, * and call the passed function to register the information. * ---------------- */ for (ccp = CacheHdr->ch_caches; ccp; ccp = ccp->cc_next) { - if (strcmp(ccp->cc_relname, RelationGetRelationName(relation)) != 0) - continue; - /* Just in case cache hasn't finished initialization yet... */ if (ccp->cc_tupdesc == NULL) CatalogCacheInitializeCache(ccp); + if (ccp->cc_reloid != reloid) + continue; + (*function) (ccp->id, - CatalogCacheComputeTupleHashIndex(ccp, tuple), + CatalogCacheComputeTupleHashValue(ccp, tuple), &tuple->t_self, ccp->cc_relisshared ? (Oid) 0 : MyDatabaseId); } |