aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils')
-rw-r--r--src/backend/utils/adt/rowtypes.c6
-rw-r--r--src/backend/utils/adt/ruleutils.c11
-rw-r--r--src/backend/utils/cache/relcache.c31
-rw-r--r--src/backend/utils/cache/typcache.c104
-rw-r--r--src/backend/utils/fmgr/funcapi.c11
-rw-r--r--src/backend/utils/resowner/README8
-rw-r--r--src/backend/utils/resowner/resowner.c103
7 files changed, 226 insertions, 48 deletions
diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c
index bb61dc29568..f701da8582b 100644
--- a/src/backend/utils/adt/rowtypes.c
+++ b/src/backend/utils/adt/rowtypes.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.15 2006/04/04 19:35:36 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/rowtypes.c,v 1.16 2006/06/16 18:42:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -264,6 +264,7 @@ record_in(PG_FUNCTION_ARGS)
pfree(buf.data);
pfree(values);
pfree(nulls);
+ ReleaseTupleDesc(tupdesc);
PG_RETURN_HEAPTUPLEHEADER(result);
}
@@ -411,6 +412,7 @@ record_out(PG_FUNCTION_ARGS)
pfree(values);
pfree(nulls);
+ ReleaseTupleDesc(tupdesc);
PG_RETURN_CSTRING(buf.data);
}
@@ -605,6 +607,7 @@ record_recv(PG_FUNCTION_ARGS)
heap_freetuple(tuple);
pfree(values);
pfree(nulls);
+ ReleaseTupleDesc(tupdesc);
PG_RETURN_HEAPTUPLEHEADER(result);
}
@@ -731,6 +734,7 @@ record_send(PG_FUNCTION_ARGS)
pfree(values);
pfree(nulls);
+ ReleaseTupleDesc(tupdesc);
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 83697436c0e..182d3cf1072 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2,7 +2,7 @@
* ruleutils.c - Functions to convert stored expressions/querytrees
* back to source text
*
- * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.223 2006/05/28 21:13:53 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.224 2006/06/16 18:42:22 tgl Exp $
**********************************************************************/
#include "postgres.h"
@@ -2697,7 +2697,8 @@ get_name_for_var_field(Var *var, int fieldno,
* appropriate error message while failing.
*/
if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
- tupleDesc = lookup_rowtype_tupdesc(exprType(expr), exprTypmod(expr));
+ tupleDesc = lookup_rowtype_tupdesc_copy(exprType(expr),
+ exprTypmod(expr));
/* Got the tupdesc, so we can extract the field name */
Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
@@ -3312,8 +3313,8 @@ get_rule_expr(Node *node, deparse_context *context,
TupleDesc tupdesc;
if (get_expr_result_type(arg, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- tupdesc = lookup_rowtype_tupdesc(exprType(arg),
- exprTypmod(arg));
+ tupdesc = lookup_rowtype_tupdesc_copy(exprType(arg),
+ exprTypmod(arg));
Assert(tupdesc);
/* Got the tupdesc, so we can extract the field name */
Assert(fno >= 1 && fno <= tupdesc->natts);
@@ -3514,6 +3515,8 @@ get_rule_expr(Node *node, deparse_context *context,
}
i++;
}
+
+ ReleaseTupleDesc(tupdesc);
}
appendStringInfo(buf, ")");
if (rowexpr->row_format == COERCE_EXPLICIT_CAST)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9d4ffa4b148..82de42020e8 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.241 2006/05/06 15:51:07 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.242 2006/06/16 18:42:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -313,6 +313,8 @@ AllocateRelationDesc(Relation relation, Form_pg_class relp)
/* and allocate attribute tuple form storage */
relation->rd_att = CreateTemplateTupleDesc(relationForm->relnatts,
relationForm->relhasoids);
+ /* which we mark as a reference-counted tupdesc */
+ relation->rd_att->tdrefcount = 1;
MemoryContextSwitchTo(oldcxt);
@@ -1234,6 +1236,8 @@ formrdesc(const char *relationName, Oid relationReltype,
* defined by macros in src/include/catalog/ headers.
*/
relation->rd_att = CreateTemplateTupleDesc(natts, hasoids);
+ relation->rd_att->tdrefcount = 1; /* mark as refcounted */
+
relation->rd_att->tdtypeid = relationReltype;
relation->rd_att->tdtypmod = -1; /* unnecessary, but... */
@@ -1591,7 +1595,10 @@ RelationClearRelation(Relation relation, bool rebuild)
{
/* ok to zap remaining substructure */
flush_rowtype_cache(old_reltype);
- FreeTupleDesc(relation->rd_att);
+ /* can't use DecrTupleDescRefCount here */
+ Assert(relation->rd_att->tdrefcount > 0);
+ if (--relation->rd_att->tdrefcount == 0)
+ FreeTupleDesc(relation->rd_att);
if (relation->rd_rulescxt)
MemoryContextDelete(relation->rd_rulescxt);
pfree(relation);
@@ -1601,7 +1608,10 @@ RelationClearRelation(Relation relation, bool rebuild)
/*
* When rebuilding an open relcache entry, must preserve ref count and
* rd_createSubid state. Also attempt to preserve the tupledesc and
- * rewrite-rule substructures in place.
+ * rewrite-rule substructures in place. (Note: the refcount mechanism
+ * for tupledescs may eventually ensure that we don't really need to
+ * preserve the tupledesc in-place, but for now there are still a lot
+ * of places that assume an open rel's tupledesc won't move.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
@@ -1618,7 +1628,9 @@ RelationClearRelation(Relation relation, bool rebuild)
{
/* Should only get here if relation was deleted */
flush_rowtype_cache(old_reltype);
- FreeTupleDesc(old_att);
+ Assert(old_att->tdrefcount > 0);
+ if (--old_att->tdrefcount == 0)
+ FreeTupleDesc(old_att);
if (old_rulescxt)
MemoryContextDelete(old_rulescxt);
pfree(relation);
@@ -1629,13 +1641,17 @@ RelationClearRelation(Relation relation, bool rebuild)
if (equalTupleDescs(old_att, relation->rd_att))
{
/* needn't flush typcache here */
- FreeTupleDesc(relation->rd_att);
+ Assert(relation->rd_att->tdrefcount == 1);
+ if (--relation->rd_att->tdrefcount == 0)
+ FreeTupleDesc(relation->rd_att);
relation->rd_att = old_att;
}
else
{
flush_rowtype_cache(old_reltype);
- FreeTupleDesc(old_att);
+ Assert(old_att->tdrefcount > 0);
+ if (--old_att->tdrefcount == 0)
+ FreeTupleDesc(old_att);
}
if (equalRuleLocks(old_rules, relation->rd_rules))
{
@@ -2075,6 +2091,7 @@ RelationBuildLocalRelation(const char *relname,
* catalogs. We can copy attnotnull constraints here, however.
*/
rel->rd_att = CreateTupleDescCopy(tupDesc);
+ rel->rd_att->tdrefcount = 1; /* mark as refcounted */
has_not_null = false;
for (i = 0; i < natts; i++)
{
@@ -2996,6 +3013,8 @@ load_relcache_init_file(void)
/* initialize attribute tuple forms */
rel->rd_att = CreateTemplateTupleDesc(relform->relnatts,
relform->relhasoids);
+ rel->rd_att->tdrefcount = 1; /* mark as refcounted */
+
rel->rd_att->tdtypeid = relform->reltype;
rel->rd_att->tdtypmod = -1; /* unnecessary, but... */
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index c3f0afa6853..fbeefad7457 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -36,7 +36,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/cache/typcache.c,v 1.18 2006/03/05 15:58:45 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/cache/typcache.c,v 1.19 2006/06/16 18:42:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -270,12 +270,16 @@ lookup_type_cache(Oid type_id, int flags)
Assert(rel->rd_rel->reltype == typentry->type_id);
/*
- * Notice that we simply store a link to the relcache's tupdesc. Since
- * we are relying on relcache to detect cache flush events, there's
- * not a lot of point to maintaining an independent copy.
+ * Link to the tupdesc and increment its refcount (we assert it's
+ * a refcounted descriptor). We don't use IncrTupleDescRefCount()
+ * for this, because the reference mustn't be entered in the current
+ * resource owner; it can outlive the current query.
*/
typentry->tupDesc = RelationGetDescr(rel);
+ Assert(typentry->tupDesc->tdrefcount > 0);
+ typentry->tupDesc->tdrefcount++;
+
relation_close(rel, AccessShareLock);
}
@@ -283,29 +287,13 @@ lookup_type_cache(Oid type_id, int flags)
}
/*
- * lookup_rowtype_tupdesc
- *
- * Given a typeid/typmod that should describe a known composite type,
- * return the tuple descriptor for the type. Will ereport on failure.
- *
- * Note: returned TupleDesc points to cached copy; caller must copy it
- * if intending to scribble on it or keep a reference for a long time.
- */
-TupleDesc
-lookup_rowtype_tupdesc(Oid type_id, int32 typmod)
-{
- return lookup_rowtype_tupdesc_noerror(type_id, typmod, false);
-}
-
-/*
- * lookup_rowtype_tupdesc_noerror
+ * lookup_rowtype_tupdesc_internal --- internal routine to lookup a rowtype
*
- * As above, but if the type is not a known composite type and noError
- * is true, returns NULL instead of ereport'ing. (Note that if a bogus
- * type_id is passed, you'll get an ereport anyway.)
+ * Same API as lookup_rowtype_tupdesc_noerror, but the returned tupdesc
+ * hasn't had its refcount bumped.
*/
-TupleDesc
-lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod, bool noError)
+static TupleDesc
+lookup_rowtype_tupdesc_internal(Oid type_id, int32 typmod, bool noError)
{
if (type_id != RECORDOID)
{
@@ -339,6 +327,59 @@ lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod, bool noError)
}
}
+/*
+ * lookup_rowtype_tupdesc
+ *
+ * Given a typeid/typmod that should describe a known composite type,
+ * return the tuple descriptor for the type. Will ereport on failure.
+ *
+ * Note: on success, we increment the refcount of the returned TupleDesc,
+ * and log the reference in CurrentResourceOwner. Caller should call
+ * ReleaseTupleDesc or DecrTupleDescRefCount when done using the tupdesc.
+ */
+TupleDesc
+lookup_rowtype_tupdesc(Oid type_id, int32 typmod)
+{
+ TupleDesc tupDesc;
+
+ tupDesc = lookup_rowtype_tupdesc_internal(type_id, typmod, false);
+ IncrTupleDescRefCount(tupDesc);
+ return tupDesc;
+}
+
+/*
+ * lookup_rowtype_tupdesc_noerror
+ *
+ * As above, but if the type is not a known composite type and noError
+ * is true, returns NULL instead of ereport'ing. (Note that if a bogus
+ * type_id is passed, you'll get an ereport anyway.)
+ */
+TupleDesc
+lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod, bool noError)
+{
+ TupleDesc tupDesc;
+
+ tupDesc = lookup_rowtype_tupdesc_internal(type_id, typmod, noError);
+ if (tupDesc != NULL)
+ IncrTupleDescRefCount(tupDesc);
+ return tupDesc;
+}
+
+/*
+ * lookup_rowtype_tupdesc_copy
+ *
+ * Like lookup_rowtype_tupdesc(), but the returned TupleDesc has been
+ * copied into the CurrentMemoryContext and is not reference-counted.
+ */
+TupleDesc
+lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod)
+{
+ TupleDesc tmp;
+
+ tmp = lookup_rowtype_tupdesc_internal(type_id, typmod, false);
+ return CreateTupleDescCopyConstr(tmp);
+}
+
/*
* assign_record_type_typmod
@@ -425,6 +466,8 @@ assign_record_type_typmod(TupleDesc tupDesc)
/* if fail in subrs, no damage except possibly some wasted memory... */
entDesc = CreateTupleDescCopy(tupDesc);
recentry->tupdescs = lcons(entDesc, recentry->tupdescs);
+ /* mark it as a reference-counted tupdesc */
+ entDesc->tdrefcount = 1;
/* now it's safe to advance NextRecordTypmod */
newtypmod = NextRecordTypmod++;
entDesc->tdtypmod = newtypmod;
@@ -456,6 +499,17 @@ flush_rowtype_cache(Oid type_id)
HASH_FIND, NULL);
if (typentry == NULL)
return; /* no matching entry */
+ if (typentry->tupDesc == NULL)
+ return; /* tupdesc hasn't been requested */
+
+ /*
+ * Release our refcount and free the tupdesc if none remain.
+ * (Can't use DecrTupleDescRefCount because this reference is not
+ * logged in current resource owner.)
+ */
+ Assert(typentry->tupDesc->tdrefcount > 0);
+ if (--typentry->tupDesc->tdrefcount == 0)
+ FreeTupleDesc(typentry->tupDesc);
typentry->tupDesc = NULL;
}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 6508d275bbe..851ef2917ce 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -7,7 +7,7 @@
* Copyright (c) 2002-2006, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.29 2006/03/05 15:58:46 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.30 2006/06/16 18:42:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -185,8 +185,7 @@ shutdown_MultiFuncCall(Datum arg)
* receives the actual datatype OID (this is mainly useful for scalar
* result types). If resultTupleDesc isn't NULL, *resultTupleDesc
* receives a pointer to a TupleDesc when the result is of a composite
- * type, or NULL when it's a scalar result. NB: the tupledesc should
- * be copied if it is to be accessed over a long period.
+ * type, or NULL when it's a scalar result.
*
* One hard case that this handles is resolution of actual rowtypes for
* functions returning RECORD (from either the function's OUT parameter
@@ -246,7 +245,7 @@ get_expr_result_type(Node *expr,
*resultTupleDesc = NULL;
result = get_type_func_class(typid);
if (result == TYPEFUNC_COMPOSITE && resultTupleDesc)
- *resultTupleDesc = lookup_rowtype_tupdesc(typid, -1);
+ *resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, -1);
}
return result;
@@ -363,7 +362,7 @@ internal_get_result_type(Oid funcid,
{
case TYPEFUNC_COMPOSITE:
if (resultTupleDesc)
- *resultTupleDesc = lookup_rowtype_tupdesc(rettype, -1);
+ *resultTupleDesc = lookup_rowtype_tupdesc_copy(rettype, -1);
/* Named composite types can't have any polymorphic columns */
break;
case TYPEFUNC_SCALAR:
@@ -1053,7 +1052,7 @@ TypeGetTupleDesc(Oid typeoid, List *colaliases)
if (functypclass == TYPEFUNC_COMPOSITE)
{
/* Composite data type, e.g. a table's row type */
- tupdesc = CreateTupleDescCopy(lookup_rowtype_tupdesc(typeoid, -1));
+ tupdesc = lookup_rowtype_tupdesc_copy(typeoid, -1);
if (colaliases != NIL)
{
diff --git a/src/backend/utils/resowner/README b/src/backend/utils/resowner/README
index 474dc76c775..56629d5089d 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -1,4 +1,4 @@
-$PostgreSQL: pgsql/src/backend/utils/resowner/README,v 1.3 2004/08/25 18:43:43 tgl Exp $
+$PostgreSQL: pgsql/src/backend/utils/resowner/README,v 1.4 2006/06/16 18:42:23 tgl Exp $
Notes about resource owners
---------------------------
@@ -61,9 +61,9 @@ ResourceOwner transfers lock ownership to the parent instead of actually
releasing the lock, if isCommit is true.
Currently, ResourceOwners contain direct support for recording ownership
-of buffer pins, lmgr locks, and catcache and relcache references. Other
-objects can be associated with a ResourceOwner by recording the address of
-the owning ResourceOwner in such an object. There is an API for other
+of buffer pins, lmgr locks, and catcache, relcache, and tupdesc references.
+Other objects can be associated with a ResourceOwner by recording the address
+of the owning ResourceOwner in such an object. There is an API for other
modules to get control during ResourceOwner release, so that they can scan
their own data structures to find the objects that need to be deleted.
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 97ce5f12100..af379803754 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -14,7 +14,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/resowner/resowner.c,v 1.19 2006/04/03 13:44:33 teodor Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/resowner/resowner.c,v 1.20 2006/06/16 18:42:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -57,6 +57,11 @@ typedef struct ResourceOwnerData
int nrelrefs; /* number of owned relcache pins */
Relation *relrefs; /* dynamically allocated array */
int maxrelrefs; /* currently allocated array size */
+
+ /* We have built-in support for remembering tupdesc references */
+ int ntupdescs; /* number of owned tupdesc references */
+ TupleDesc *tupdescs; /* dynamically allocated array */
+ int maxtupdescs; /* currently allocated array size */
} ResourceOwnerData;
@@ -87,6 +92,7 @@ static void ResourceOwnerReleaseInternal(ResourceOwner owner,
bool isCommit,
bool isTopLevel);
static void PrintRelCacheLeakWarning(Relation rel);
+static void PrintTupleDescLeakWarning(TupleDesc tupdesc);
/*****************************************************************************
@@ -258,7 +264,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
/*
* Release catcache references. Note that ReleaseCatCache will remove
* the catref entry from my list, so I just have to iterate till there
- * are none. Ditto for catcache lists.
+ * are none.
*
* As with buffer pins, warn if any are left at commit time, and
* release back-to-front for speed.
@@ -269,12 +275,20 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
PrintCatCacheLeakWarning(owner->catrefs[owner->ncatrefs - 1]);
ReleaseCatCache(owner->catrefs[owner->ncatrefs - 1]);
}
+ /* Ditto for catcache lists */
while (owner->ncatlistrefs > 0)
{
if (isCommit)
PrintCatCacheListLeakWarning(owner->catlistrefs[owner->ncatlistrefs - 1]);
ReleaseCatCacheList(owner->catlistrefs[owner->ncatlistrefs - 1]);
}
+ /* Ditto for tupdesc references */
+ while (owner->ntupdescs > 0)
+ {
+ if (isCommit)
+ PrintTupleDescLeakWarning(owner->tupdescs[owner->ntupdescs - 1]);
+ DecrTupleDescRefCount(owner->tupdescs[owner->ntupdescs - 1]);
+ }
/* Clean up index scans too */
ReleaseResources_hash();
@@ -304,6 +318,7 @@ ResourceOwnerDelete(ResourceOwner owner)
Assert(owner->ncatrefs == 0);
Assert(owner->ncatlistrefs == 0);
Assert(owner->nrelrefs == 0);
+ Assert(owner->ntupdescs == 0);
/*
* Delete children. The recursive call will delink the child from me, so
@@ -328,6 +343,8 @@ ResourceOwnerDelete(ResourceOwner owner)
pfree(owner->catlistrefs);
if (owner->relrefs)
pfree(owner->relrefs);
+ if (owner->tupdescs)
+ pfree(owner->tupdescs);
pfree(owner);
}
@@ -742,3 +759,85 @@ PrintRelCacheLeakWarning(Relation rel)
elog(WARNING, "relcache reference leak: relation \"%s\" not closed",
RelationGetRelationName(rel));
}
+
+/*
+ * Make sure there is room for at least one more entry in a ResourceOwner's
+ * tupdesc reference array.
+ *
+ * This is separate from actually inserting an entry because if we run out
+ * of memory, it's critical to do so *before* acquiring the resource.
+ */
+void
+ResourceOwnerEnlargeTupleDescs(ResourceOwner owner)
+{
+ int newmax;
+
+ if (owner->ntupdescs < owner->maxtupdescs)
+ return; /* nothing to do */
+
+ if (owner->tupdescs == NULL)
+ {
+ newmax = 16;
+ owner->tupdescs = (TupleDesc *)
+ MemoryContextAlloc(TopMemoryContext, newmax * sizeof(TupleDesc));
+ owner->maxtupdescs = newmax;
+ }
+ else
+ {
+ newmax = owner->maxtupdescs * 2;
+ owner->tupdescs = (TupleDesc *)
+ repalloc(owner->tupdescs, newmax * sizeof(TupleDesc));
+ owner->maxtupdescs = newmax;
+ }
+}
+
+/*
+ * Remember that a tupdesc reference is owned by a ResourceOwner
+ *
+ * Caller must have previously done ResourceOwnerEnlargeTupleDescs()
+ */
+void
+ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
+{
+ Assert(owner->ntupdescs < owner->maxtupdescs);
+ owner->tupdescs[owner->ntupdescs] = tupdesc;
+ owner->ntupdescs++;
+}
+
+/*
+ * Forget that a tupdesc reference is owned by a ResourceOwner
+ */
+void
+ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
+{
+ TupleDesc *tupdescs = owner->tupdescs;
+ int nt1 = owner->ntupdescs - 1;
+ int i;
+
+ for (i = nt1; i >= 0; i--)
+ {
+ if (tupdescs[i] == tupdesc)
+ {
+ while (i < nt1)
+ {
+ tupdescs[i] = tupdescs[i + 1];
+ i++;
+ }
+ owner->ntupdescs = nt1;
+ return;
+ }
+ }
+ elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
+ tupdesc, owner->name);
+}
+
+/*
+ * Debugging subroutine
+ */
+static void
+PrintTupleDescLeakWarning(TupleDesc tupdesc)
+{
+ elog(WARNING,
+ "TupleDesc reference leak: TupleDesc %p (%u,%d) still referenced",
+ tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod);
+}