* catcache.c
* System catalog cache for tuples matching a key.
*
- * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/cache/catcache.c,v 1.99 2002/09/04 20:31:29 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/cache/catcache.c,v 1.121 2005/05/06 17:24:54 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
-#include "catalog/catname.h"
#include "catalog/indexing.h"
#include "miscadmin.h"
#ifdef CATCACHE_STATS
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/catcache.h"
+#include "utils/memutils.h"
#include "utils/relcache.h"
+#include "utils/resowner.h"
#include "utils/syscache.h"
- /* #define CACHEDEBUG */ /* turns DEBUG elogs on */
+/* #define CACHEDEBUG */ /* turns DEBUG elogs on */
/*
* Constants related to size of the catcache.
/* Cache management header --- pointer is NULL until created */
static CatCacheHeader *CacheHdr = NULL;
-/*
- * EQPROC is used in CatalogCacheInitializeCache to find the equality
- * functions for system types that are used as cache key fields.
- * See also GetCCHashFunc, which should support the same set of types.
- *
- * XXX this should be replaced by catalog lookups,
- * but that seems to pose considerable risk of circularity...
- */
-static const Oid eqproc[] = {
- F_BOOLEQ, InvalidOid, F_CHAREQ, F_NAMEEQ, InvalidOid,
- F_INT2EQ, F_INT2VECTOREQ, F_INT4EQ, F_OIDEQ, F_TEXTEQ,
- F_OIDEQ, InvalidOid, InvalidOid, InvalidOid, F_OIDVECTOREQ
-};
-
-#define EQPROC(SYSTEMTYPEOID) eqproc[(SYSTEMTYPEOID)-BOOLOID]
-
static uint32 CatalogCacheComputeHashValue(CatCache *cache, int nkeys,
ScanKey cur_skey);
* internal support functions
*/
-static PGFunction
-GetCCHashFunc(Oid keytype)
+/*
+ * Look up the hash and equality functions for system types that are used
+ * as cache key fields.
+ *
+ * XXX this should be replaced by catalog lookups,
+ * but that seems to pose considerable risk of circularity...
+ */
+static void
+GetCCHashEqFuncs(Oid keytype, PGFunction *hashfunc, RegProcedure *eqfunc)
{
switch (keytype)
{
case BOOLOID:
+ *hashfunc = hashchar;
+ *eqfunc = F_BOOLEQ;
+ break;
case CHAROID:
- return hashchar;
+ *hashfunc = hashchar;
+ *eqfunc = F_CHAREQ;
+ break;
case NAMEOID:
- return hashname;
+ *hashfunc = hashname;
+ *eqfunc = F_NAMEEQ;
+ break;
case INT2OID:
- return hashint2;
+ *hashfunc = hashint2;
+ *eqfunc = F_INT2EQ;
+ break;
case INT2VECTOROID:
- return hashint2vector;
+ *hashfunc = hashint2vector;
+ *eqfunc = F_INT2VECTOREQ;
+ break;
case INT4OID:
- return hashint4;
+ *hashfunc = hashint4;
+ *eqfunc = F_INT4EQ;
+ break;
case TEXTOID:
- return hashvarlena;
+ *hashfunc = hashtext;
+ *eqfunc = F_TEXTEQ;
+ break;
case OIDOID:
case REGPROCOID:
case REGPROCEDUREOID:
case REGOPERATOROID:
case REGCLASSOID:
case REGTYPEOID:
- return hashoid;
+ *hashfunc = hashoid;
+ *eqfunc = F_OIDEQ;
+ break;
case OIDVECTOROID:
- return hashoidvector;
+ *hashfunc = hashoidvector;
+ *eqfunc = F_OIDVECTOREQ;
+ break;
default:
- elog(FATAL, "GetCCHashFunc: type %u unsupported as catcache key",
- keytype);
- return (PGFunction) NULL;
+ elog(FATAL, "type %u not supported as catcache key", keytype);
+ break;
}
}
{
uint32 hashValue = 0;
- CACHE4_elog(DEBUG1, "CatalogCacheComputeHashValue %s %d %p",
+ CACHE4_elog(DEBUG2, "CatalogCacheComputeHashValue %s %d %p",
cache->cc_relname,
nkeys,
cache);
cur_skey[0].sk_argument));
break;
default:
- elog(FATAL, "CCComputeHashValue: %d nkeys", nkeys);
+ elog(FATAL, "wrong number of hash keys: %d", nkeys);
break;
}
Assert(!isNull);
break;
default:
- elog(FATAL, "CCComputeTupleHashValue: %d cc_nkeys",
- cache->cc_nkeys);
+ elog(FATAL, "wrong number of hash keys: %d", cache->cc_nkeys);
break;
}
long cc_lsearches = 0;
long cc_lhits = 0;
- elog(DEBUG1, "Catcache stats dump: %d/%d tuples in catcaches",
+ elog(DEBUG2, "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, %ld lsrch, %ld lhits",
+ elog(DEBUG2, "catcache %s/%u: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld discards, %ld lsrch, %ld lhits",
cache->cc_relname,
- cache->cc_indname,
+ cache->cc_indexoid,
cache->cc_ntup,
cache->cc_searches,
cache->cc_hits,
cc_lsearches += cache->cc_lsearches;
cc_lhits += cache->cc_lhits;
}
- elog(DEBUG1, "Catcache totals: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld discards, %ld lsrch, %ld lhits",
+ elog(DEBUG2, "catcache totals: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld discards, %ld lsrch, %ld lhits",
CacheHdr->ch_ntup,
cc_searches,
cc_hits,
* sanity checks
*/
Assert(ItemPointerIsValid(pointer));
- CACHE1_elog(DEBUG1, "CatalogCacheIdInvalidate: called");
+ CACHE1_elog(DEBUG2, "CatalogCacheIdInvalidate: called");
/*
* inspect caches to find the proper cache
ct->dead = true;
else
CatCacheRemoveCTup(ccp, ct);
- CACHE1_elog(DEBUG1, "CatalogCacheIdInvalidate: invalidated");
+ CACHE1_elog(DEBUG2, "CatalogCacheIdInvalidate: invalidated");
#ifdef CATCACHE_STATS
ccp->cc_invals++;
#endif
/*
* AtEOXact_CatCache
*
- * Clean up catcaches at end of transaction (either commit or abort)
+ * Clean up catcaches at end of main transaction (either commit or abort)
*
* We scan the caches to reset refcounts to zero. This is of course
* necessary in the abort case, since elog() may have interrupted routines.
if (cl->refcount != 0)
{
if (isCommit)
- elog(WARNING, "Cache reference leak: cache %s (%d), list %p has count %d",
- ccp->cc_relname, ccp->id, cl, cl->refcount);
+ PrintCatCacheListLeakWarning(cl);
cl->refcount = 0;
}
if (ct->refcount != 0)
{
if (isCommit)
- elog(WARNING, "Cache reference leak: cache %s (%d), tuple %u has count %d",
- ct->my_cache->cc_relname, ct->my_cache->id,
- HeapTupleGetOid(&ct->tuple),
- ct->refcount);
+ PrintCatCacheLeakWarning(&ct->tuple);
ct->refcount = 0;
}
{
CatCache *cache;
- CACHE1_elog(DEBUG1, "ResetCatalogCaches called");
+ CACHE1_elog(DEBUG2, "ResetCatalogCaches called");
for (cache = CacheHdr->ch_caches; cache; cache = cache->cc_next)
ResetCatalogCache(cache);
- CACHE1_elog(DEBUG1, "end of ResetCatalogCaches call");
+ CACHE1_elog(DEBUG2, "end of ResetCatalogCaches call");
}
/*
{
CatCache *cache;
- CACHE2_elog(DEBUG1, "CatalogCacheFlushRelation called for %u", relId);
+ CACHE2_elog(DEBUG2, "CatalogCacheFlushRelation called for %u", relId);
for (cache = CacheHdr->ch_caches; cache; cache = cache->cc_next)
{
}
}
- CACHE1_elog(DEBUG1, "end of CatalogCacheFlushRelation call");
+ CACHE1_elog(DEBUG2, "end of CatalogCacheFlushRelation call");
}
/*
* structure initialized on the first access.
*/
#ifdef CACHEDEBUG
-#define InitCatCache_DEBUG1 \
+#define InitCatCache_DEBUG2 \
do { \
- elog(DEBUG1, "InitCatCache: rel=%s id=%d nkeys=%d size=%d\n", \
- cp->cc_relname, cp->id, cp->cc_nkeys, cp->cc_nbuckets); \
+ 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_DEBUG1
+#define InitCatCache_DEBUG2
#endif
CatCache *
InitCatCache(int id,
- const char *relname,
- const char *indname,
+ Oid reloid,
+ Oid indexoid,
int reloidattr,
int nkeys,
const int *key)
*
* Note: we assume zeroing initializes the Dllist headers correctly
*/
- cp = (CatCache *) palloc(sizeof(CatCache) + NCCBUCKETS * sizeof(Dllist));
- MemSet((char *) cp, 0, sizeof(CatCache) + NCCBUCKETS * sizeof(Dllist));
+ cp = (CatCache *) palloc0(sizeof(CatCache) + NCCBUCKETS * sizeof(Dllist));
/*
* initialize the cache's relation information for the relation
* other internal fields. But don't open the relation yet.
*/
cp->id = id;
- cp->cc_relname = relname;
- cp->cc_indname = indname;
- cp->cc_reloid = InvalidOid; /* temporary */
+ 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_reloidattr = reloidattr;
* new cache is initialized as far as we can go for now. print some
* debugging information, if appropriate.
*/
- InitCatCache_DEBUG1;
+ InitCatCache_DEBUG2;
/*
* add completed cache to top of group header's list
*/
#ifdef CACHEDEBUG
#define CatalogCacheInitializeCache_DEBUG1 \
- elog(DEBUG1, "CatalogCacheInitializeCache: cache @%p %s", cache, \
- cache->cc_relname)
+ elog(DEBUG2, "CatalogCacheInitializeCache: cache @%p rel=%u", cache, \
+ cache->cc_reloid)
#define CatalogCacheInitializeCache_DEBUG2 \
do { \
if (cache->cc_key[i] > 0) { \
- elog(DEBUG1, "CatalogCacheInitializeCache: load %d/%d w/%d, %u", \
+ elog(DEBUG2, "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", \
+ elog(DEBUG2, "CatalogCacheInitializeCache: load %d/%d w/%d", \
i+1, cache->cc_nkeys, cache->cc_key[i]); \
} \
} while(0)
* Open the relation without locking --- we only need the tupdesc,
* which we assume will never change ...
*/
- relation = heap_openr(cache->cc_relname, NoLock);
+ relation = heap_open(cache->cc_reloid, NoLock);
Assert(RelationIsValid(relation));
/*
tupdesc = CreateTupleDescCopyConstr(RelationGetDescr(relation));
/*
- * get the relation's OID and relisshared flag, too
+ * save the relation's name and relisshared flag, too (cc_relname
+ * is used only for debugging purposes)
*/
- cache->cc_reloid = RelationGetRelid(relation);
+ cache->cc_relname = pstrdup(RelationGetRelationName(relation));
cache->cc_relisshared = RelationGetForm(relation)->relisshared;
/*
heap_close(relation, NoLock);
- CACHE3_elog(DEBUG1, "CatalogCacheInitializeCache: %s, %d keys",
+ CACHE3_elog(DEBUG2, "CatalogCacheInitializeCache: %s, %d keys",
cache->cc_relname, cache->cc_nkeys);
/*
for (i = 0; i < cache->cc_nkeys; ++i)
{
Oid keytype;
+ RegProcedure eqfunc;
CatalogCacheInitializeCache_DEBUG2;
else
{
if (cache->cc_key[i] != ObjectIdAttributeNumber)
- elog(FATAL, "CatalogCacheInit: only sys attr supported is OID");
+ elog(FATAL, "only sys attr supported in caches is OID");
keytype = OIDOID;
}
- cache->cc_hashfunc[i] = GetCCHashFunc(keytype);
+ GetCCHashEqFuncs(keytype,
+ &cache->cc_hashfunc[i],
+ &eqfunc);
cache->cc_isname[i] = (keytype == NAMEOID);
/*
- * If GetCCHashFunc liked the type, safe to index into eqproc[]
+ * Do equality-function lookup (we assume this won't need a
+ * catalog lookup for any supported type)
*/
- cache->cc_skey[i].sk_procedure = EQPROC(keytype);
-
- /* Do function lookup */
- fmgr_info_cxt(cache->cc_skey[i].sk_procedure,
+ 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_key[i];
- CACHE4_elog(DEBUG1, "CatalogCacheInit %s %d %p",
+ /* Fill in sk_strategy as well --- always standard equality */
+ cache->cc_skey[i].sk_strategy = BTEqualStrategyNumber;
+ cache->cc_skey[i].sk_subtype = InvalidOid;
+
+ CACHE4_elog(DEBUG2, "CatalogCacheInit %s %d %p",
cache->cc_relname,
i,
cache);
{
Relation idesc;
- idesc = index_openr(cache->cc_indname);
+ idesc = index_open(cache->cc_indexoid);
index_close(idesc);
}
}
*/
if (!ct->negative)
{
+ ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
ct->refcount++;
+ ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
- CACHE3_elog(DEBUG1, "SearchCatCache(%s): found in bucket %d",
+ CACHE3_elog(DEBUG2, "SearchCatCache(%s): found in bucket %d",
cache->cc_relname, hashIndex);
#ifdef CATCACHE_STATS
}
else
{
- CACHE3_elog(DEBUG1, "SearchCatCache(%s): found neg entry in bucket %d",
+ CACHE3_elog(DEBUG2, "SearchCatCache(%s): found neg entry in bucket %d",
cache->cc_relname, hashIndex);
#ifdef CATCACHE_STATS
relation = heap_open(cache->cc_reloid, AccessShareLock);
scandesc = systable_beginscan(relation,
- cache->cc_indname,
+ cache->cc_indexoid,
IndexScanOK(cache, cur_skey),
SnapshotNow,
cache->cc_nkeys,
ct = CatalogCacheCreateEntry(cache, ntp,
hashValue, hashIndex,
false);
+ /* immediately set the refcount to 1 */
+ ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+ ct->refcount++;
+ ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
break; /* assume only one match */
}
* 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;
+
ntp = build_dummy_tuple(cache, cache->cc_nkeys, cur_skey);
ct = CatalogCacheCreateEntry(cache, ntp,
hashValue, hashIndex,
true);
heap_freetuple(ntp);
- CACHE4_elog(DEBUG1, "SearchCatCache(%s): Contains %d/%d tuples",
+ CACHE4_elog(DEBUG2, "SearchCatCache(%s): Contains %d/%d tuples",
cache->cc_relname, cache->cc_ntup, CacheHdr->ch_ntup);
- CACHE3_elog(DEBUG1, "SearchCatCache(%s): put neg entry in bucket %d",
+ CACHE3_elog(DEBUG2, "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.
+ * We are not returning the negative entry to the caller, so leave
+ * its refcount zero.
*/
- ct->refcount = 0; /* negative entries never have refs */
return NULL;
}
- CACHE4_elog(DEBUG1, "SearchCatCache(%s): Contains %d/%d tuples",
+ CACHE4_elog(DEBUG2, "SearchCatCache(%s): Contains %d/%d tuples",
cache->cc_relname, cache->cc_ntup, CacheHdr->ch_ntup);
- CACHE3_elog(DEBUG1, "SearchCatCache(%s): put in bucket %d",
+ CACHE3_elog(DEBUG2, "SearchCatCache(%s): put in bucket %d",
cache->cc_relname, hashIndex);
#ifdef CATCACHE_STATS
Assert(ct->refcount > 0);
ct->refcount--;
+ ResourceOwnerForgetCatCacheRef(CurrentResourceOwner, &ct->tuple);
if (ct->refcount == 0
#ifndef CATCACHE_FORCE_RELEASE
CatCList *cl;
CatCTup *ct;
List *ctlist;
+ ListCell *ctlist_item;
int nmembers;
Relation relation;
SysScanDesc scandesc;
* 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.) Also bump the members' refcounts.
+ * (member refcounts are NOT registered separately with the
+ * resource owner.)
*/
+ ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner);
for (i = 0; i < cl->n_members; i++)
{
cl->members[i]->refcount++;
/* Bump the list's refcount and return it */
cl->refcount++;
+ ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl);
- CACHE2_elog(DEBUG1, "SearchCatCacheList(%s): found list",
+ CACHE2_elog(DEBUG2, "SearchCatCacheList(%s): found list",
cache->cc_relname);
#ifdef CATCACHE_STATS
relation = heap_open(cache->cc_reloid, AccessShareLock);
scandesc = systable_beginscan(relation,
- cache->cc_indname,
+ cache->cc_indexoid,
true,
SnapshotNow,
nkeys,
if (ct->c_list)
continue;
- /* Found a match, so bump its refcount and move to front */
- ct->refcount++;
-
+ /* Found a match, so move it to front */
DLMoveToFront(&ct->lrulist_elem);
break;
false);
}
- ctlist = lcons(ct, ctlist);
+ /*
+ * We have to bump the member refcounts immediately to ensure they
+ * won't get dropped from the cache while loading other members.
+ * If we get an error before we finish constructing the CatCList
+ * then we will leak those reference counts. This is annoying but
+ * it has no real consequence beyond possibly generating some
+ * warning messages at the next transaction commit, so it's not
+ * worth fixing.
+ */
+ ct->refcount++;
+ ctlist = lappend(ctlist, ct);
nmembers++;
}
cl->cl_magic = CL_MAGIC;
cl->my_cache = cache;
- DLInitElem(&cl->cache_elem, (void *) cl);
- cl->refcount = 1; /* count this first reference */
+ DLInitElem(&cl->cache_elem, cl);
+ cl->refcount = 0; /* for the moment */
cl->dead = false;
cl->ordered = ordered;
cl->nkeys = nkeys;
cl->hash_value = lHashValue;
cl->n_members = nmembers;
- /* The list is backwards because we built it with lcons */
- for (i = nmembers; --i >= 0;)
+
+ Assert(nmembers == list_length(ctlist));
+ ctlist_item = list_head(ctlist);
+ for (i = 0; i < nmembers; i++)
{
- cl->members[i] = ct = (CatCTup *) lfirst(ctlist);
+ cl->members[i] = ct = (CatCTup *) lfirst(ctlist_item);
Assert(ct->c_list == NULL);
ct->c_list = cl;
/* mark list dead if any members already dead */
if (ct->dead)
cl->dead = true;
- ctlist = lnext(ctlist);
+ ctlist_item = lnext(ctlist_item);
}
DLAddHead(&cache->cc_lists, &cl->cache_elem);
- CACHE3_elog(DEBUG1, "SearchCatCacheList(%s): made list of %d members",
+ CACHE3_elog(DEBUG2, "SearchCatCacheList(%s): made list of %d members",
cache->cc_relname, nmembers);
+ /* Finally, bump the list's refcount and return it */
+ ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner);
+ cl->refcount++;
+ ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl);
+
return cl;
}
}
list->refcount--;
+ ResourceOwnerForgetCatCacheListRef(CurrentResourceOwner, list);
if (list->refcount == 0
#ifndef CATCACHE_FORCE_RELEASE
/*
* CatalogCacheCreateEntry
* Create a new CatCTup entry, copying the given HeapTuple and other
- * supplied data into it. The new entry is given refcount 1.
+ * supplied data into it. The new entry initially has refcount 0.
*/
static CatCTup *
CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp,
DLInitElem(&ct->lrulist_elem, (void *) ct);
DLInitElem(&ct->cache_elem, (void *) ct);
ct->c_list = NULL;
- ct->refcount = 1; /* count this first reference */
+ ct->refcount = 0; /* for the moment */
ct->dead = false;
ct->negative = negative;
ct->hash_value = hashValue;
/*
* If we've exceeded the desired size of the caches, try to throw away
- * the least recently used entry. NB: the newly-built entry cannot
- * get thrown away here, because it has positive refcount.
+ * the least recently used entry. NB: be careful not to throw away
+ * the newly-built entry...
*/
if (CacheHdr->ch_ntup > CacheHdr->ch_maxtup)
{
prevelt = DLGetPred(elt);
- if (oldct->refcount == 0)
+ if (oldct->refcount == 0 && oldct != ct)
{
- CACHE2_elog(DEBUG1, "CatCacheCreateEntry(%s): Overflow, LRU removal",
+ CACHE2_elog(DEBUG2, "CatCacheCreateEntry(%s): Overflow, LRU removal",
cache->cc_relname);
#ifdef CATCACHE_STATS
oldct->my_cache->cc_discards++;
CatCache *ccp;
Oid reloid;
- CACHE1_elog(DEBUG1, "PrepareToInvalidateCacheTuple: called");
+ CACHE1_elog(DEBUG2, "PrepareToInvalidateCacheTuple: called");
/*
* sanity checks
ccp->cc_relisshared ? (Oid) 0 : MyDatabaseId);
}
}
+
+
+/*
+ * 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);
+}