Attached is a WIP patch that adds reference counting for TupleDescs. Two
issues that I ran into while implementing it:
(1) How should the lifetime of a TupleDesc be managed? The existing
ResourceOwner stuff seems to assume that it is managing "per-query"
resources. A TupleDesc will often live beyond the lifetime of a single
transaction, and might even be created before transactions can be
started (e.g. formrdesc() in relcache.c, when we're creating relcache
entries for nailed-in-cache relations early in InitPostgres()).
In current sources, the lifetime of a TupleDesc is the lifetime of the
memory context in which it is allocated (and/or whenever FreeTupleDesc()
is invoked). We could imitate that behavior by optionally linking a
ResourceOwner with each MemoryContext, and releasing the ResourceOwner
when the MemoryContext is reset or deleted. However, I'm not sure that
that's the right approach...
(2) The existing ResourceOwner users issue a warning if the resource
they are managing is not explicitly released before a transaction
successfully commits (so they elog(WARNING)). I don't see the need to be
that strict for TupleDescs -- as we do with palloc() without a matching
pfree(), I think it should be okay to just silently clean up "leaked"
TupleDescs when releasing a ResourceOwner.
Thoughts?
-Neil
============================================================
*** src/backend/access/common/tupdesc.c 83ca807d4fdd572c409bc9214922b6ba9da7ce18
--- src/backend/access/common/tupdesc.c 63efd0b6adc911b57cef00cf0c4611760a91fa2c
***************
*** 23,28 ****
--- 23,29 ----
#include "catalog/pg_type.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
+ #include "utils/resowner.h"
#include "utils/syscache.h"
***************
*** 30,37 ****
* CreateTemplateTupleDesc
* This function allocates an empty tuple descriptor structure.
*
! * Tuple type ID information is initially set for an anonymous record type;
! * caller can overwrite this if needed.
*/
TupleDesc
CreateTemplateTupleDesc(int natts, bool hasoid)
--- 31,42 ----
* CreateTemplateTupleDesc
* This function allocates an empty tuple descriptor structure.
*
! * Tuple type ID information is initially set for an anonymous record
! * type; caller can overwrite this if needed.
! *
! * The reference count on the TupleDesc is initially 1: the caller
! * should decrement the reference count via DecrTupleDescRefCount()
! * when finished.
*/
TupleDesc
CreateTemplateTupleDesc(int natts, bool hasoid)
***************
*** 85,90 ****
--- 90,99 ----
desc->tdtypmod = -1;
desc->tdhasoid = hasoid;
+ /* Set to zero, then inform the resowner and increment refcount */
+ desc->refcount = 0;
+ IncrTupleDescRefCount(desc);
+
return desc;
}
***************
*** 93,103 ****
* This function allocates a new TupleDesc pointing to a given
* Form_pg_attribute array.
*
! * Note: if the TupleDesc is ever freed, the Form_pg_attribute array
* will not be freed thereby.
*
* Tuple type ID information is initially set for an anonymous record type;
* caller can overwrite this if needed.
*/
TupleDesc
CreateTupleDesc(int natts, bool hasoid, Form_pg_attribute *attrs)
--- 102,116 ----
* This function allocates a new TupleDesc pointing to a given
* Form_pg_attribute array.
*
! * Note: when the TupleDesc is destroyed, the Form_pg_attribute array
* will not be freed thereby.
*
* Tuple type ID information is initially set for an anonymous record type;
* caller can overwrite this if needed.
+ *
+ * The reference count on the TupleDesc is initially 1: the caller
+ * should decrement the reference count via DecrTupleDescRefCount()
+ * when finished.
*/
TupleDesc
CreateTupleDesc(int natts, bool hasoid, Form_pg_attribute *attrs)
***************
*** 117,122 ****
--- 130,139 ----
desc->tdtypmod = -1;
desc->tdhasoid = hasoid;
+ /* Set to zero, then inform the resowner and increment refcount */
+ desc->refcount = 0;
+ IncrTupleDescRefCount(desc);
+
return desc;
}
***************
*** 125,130 ****
--- 142,151 ----
* This function creates a new TupleDesc by copying from an existing
* TupleDesc.
*
+ * The reference count on the TupleDesc is initially 1: the caller
+ * should decrement the reference count via DecrTupleDescRefCount()
+ * when finished.
+ *
* !!! Constraints and defaults are not copied !!!
*/
TupleDesc
***************
*** 144,150 ****
desc->tdtypeid = tupdesc->tdtypeid;
desc->tdtypmod = tupdesc->tdtypmod;
!
return desc;
}
--- 165,171 ----
desc->tdtypeid = tupdesc->tdtypeid;
desc->tdtypmod = tupdesc->tdtypmod;
!
return desc;
}
***************
*** 152,157 ****
--- 173,182 ----
* CreateTupleDescCopyConstr
* This function creates a new TupleDesc by copying from an existing
* TupleDesc (including its constraints and defaults).
+ *
+ * The reference count on the TupleDesc is initially 1: the caller
+ * should decrement the reference count via DecrTupleDescRefCount()
+ * when finished.
*/
TupleDesc
CreateTupleDescCopyConstr(TupleDesc tupdesc)
***************
*** 207,235 ****
}
/*
! * Free a TupleDesc including all substructure
*/
void
! FreeTupleDesc(TupleDesc tupdesc)
{
! int i;
! if (tupdesc->constr)
{
! if (tupdesc->constr->num_defval > 0)
! {
! AttrDefault *attrdef = tupdesc->constr->defval;
! for (i = tupdesc->constr->num_defval - 1; i >= 0; i--)
{
! if (attrdef[i].adbin)
! pfree(attrdef[i].adbin);
! }
! pfree(attrdef);
! }
! if (tupdesc->constr->num_check > 0)
! {
! ConstrCheck *check = tupdesc->constr->check;
for (i = tupdesc->constr->num_check - 1; i >= 0; i--)
{
--- 232,274 ----
}
/*
! * Increment the reference count on a TupleDesc
*/
void
! IncrTupleDescRefCount(TupleDesc tupdesc)
{
! Assert(tupdesc->refcount >= 0);
! ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner);
! tupdesc->refcount++;
! ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc);
! }
!
! /*
! * Decrement the reference count on a TupleDesc. When the refcount
! * reaches zero, the TupleDesc is destroyed.
! *
! * Note that the TupleDesc's "attrs" array is NOT free'd when the
! * TupleDesc is destroyed.
! */
! void
! DecrTupleDescRefCount(TupleDesc tupdesc)
! {
! Assert(tupdesc->refcount > 0);
!
! tupdesc->refcount--;
! ResourceOwnerForgetTupleDesc(CurrentResourceOwner, tupdesc);
!
! /* If this was the last reference, we can free the tupdesc */
! if (tupdesc->refcount == 0)
{
! int i;
! if (tupdesc->constr)
! {
! if (tupdesc->constr->num_defval > 0)
{
! AttrDefault *attrdef = tupdesc->constr->defval;
for (i = tupdesc->constr->num_defval - 1; i >= 0; i--)
{
***************
*** 231,249 ****
{
ConstrCheck *check = tupdesc->constr->check;
! for (i = tupdesc->constr->num_check - 1; i >= 0; i--)
{
! if (check[i].ccname)
! pfree(check[i].ccname);
! if (check[i].ccbin)
! pfree(check[i].ccbin);
}
! pfree(check);
}
- pfree(tupdesc->constr);
- }
! pfree(tupdesc);
}
/*
--- 270,300 ----
{
ConstrCheck *check = tupdesc->constr->check;
! for (i = tupdesc->constr->num_defval - 1; i >= 0; i--)
! {
! if (attrdef[i].adbin)
! pfree(attrdef[i].adbin);
! }
! pfree(attrdef);
! }
! if (tupdesc->constr->num_check > 0)
{
! ConstrCheck *check = tupdesc->constr->check;
!
! for (i = tupdesc->constr->num_check - 1; i >= 0; i--)
! {
! if (check[i].ccname)
! pfree(check[i].ccname);
! if (check[i].ccbin)
! pfree(check[i].ccbin);
! }
! pfree(check);
}
! pfree(tupdesc->constr);
}
! pfree(tupdesc);
! }
}
/*
***************
*** 256,265 ****
bool
equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
{
! int i,
! j,
! n;
if (tupdesc1->natts != tupdesc2->natts)
return false;
if (tupdesc1->tdtypeid != tupdesc2->tdtypeid)
--- 307,315 ----
bool
equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
{
! int i;
+ /* Don't compare refcount, as it does not affect logical equality */
if (tupdesc1->natts != tupdesc2->natts)
return false;
if (tupdesc1->tdtypeid != tupdesc2->tdtypeid)
***************
*** 318,323 ****
--- 368,375 ----
{
TupleConstr *constr1 = tupdesc1->constr;
TupleConstr *constr2 = tupdesc2->constr;
+ int j,
+ n;
if (constr2 == NULL)
return false;
============================================================
*** src/backend/executor/execMain.c 6db9f62f201a639c74256aba2decf1c74af5be0c
--- src/backend/executor/execMain.c b890e66550ba3f43f89c96ea9ad0adb9cb1f8fa6
***************
*** 764,770 ****
ONCOMMIT_NOOP,
allowSystemTableMods);
! FreeTupleDesc(tupdesc);
/*
* Advance command counter so that the newly-created relation's
--- 764,770 ----
ONCOMMIT_NOOP,
allowSystemTableMods);
! DecrTupleDescRefCount(tupdesc);
/*
* Advance command counter so that the newly-created relation's
============================================================
*** src/backend/executor/execQual.c eb1daef85341439578a3f6bb73549c4420292bc3
--- src/backend/executor/execQual.c 1b28f5b4b07c37515c114ccea7191ea31786899f
***************
*** 2799,2805 ****
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
tupDesc = CreateTupleDescCopy(tupDesc);
if (fstate->argdesc)
! FreeTupleDesc(fstate->argdesc);
fstate->argdesc = tupDesc;
MemoryContextSwitchTo(oldcontext);
}
--- 2799,2805 ----
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
tupDesc = CreateTupleDescCopy(tupDesc);
if (fstate->argdesc)
! DecrTupleDescRefCount(fstate->argdesc);
fstate->argdesc = tupDesc;
MemoryContextSwitchTo(oldcontext);
}
***************
*** 2861,2867 ****
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
tupDesc = CreateTupleDescCopy(tupDesc);
if (fstate->argdesc)
! FreeTupleDesc(fstate->argdesc);
fstate->argdesc = tupDesc;
MemoryContextSwitchTo(oldcontext);
}
--- 2861,2867 ----
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
tupDesc = CreateTupleDescCopy(tupDesc);
if (fstate->argdesc)
! DecrTupleDescRefCount(fstate->argdesc);
fstate->argdesc = tupDesc;
MemoryContextSwitchTo(oldcontext);
}
============================================================
*** src/backend/executor/execTuples.c beb35fc6de01cf74389db494cfb73dc6bb1824b0
--- src/backend/executor/execTuples.c 8c1d695a1d01cd521049b774cdf84026d31ad2f4
***************
*** 190,196 ****
ExecClearTuple(slot);
if (slot->tts_shouldFreeDesc)
! FreeTupleDesc(slot->tts_tupleDescriptor);
if (slot->tts_values)
pfree(slot->tts_values);
if (slot->tts_isnull)
--- 190,196 ----
ExecClearTuple(slot);
if (slot->tts_shouldFreeDesc)
! DecrTupleDescRefCount(slot->tts_tupleDescriptor);
if (slot->tts_values)
pfree(slot->tts_values);
if (slot->tts_isnull)
***************
*** 251,257 ****
ExecClearTuple(slot);
if (slot->tts_shouldFreeDesc)
! FreeTupleDesc(slot->tts_tupleDescriptor);
if (slot->tts_values)
pfree(slot->tts_values);
if (slot->tts_isnull)
--- 251,257 ----
ExecClearTuple(slot);
if (slot->tts_shouldFreeDesc)
! DecrTupleDescRefCount(slot->tts_tupleDescriptor);
if (slot->tts_values)
pfree(slot->tts_values);
if (slot->tts_isnull)
***************
*** 325,331 ****
* present (we don't bother to check if they could be re-used).
*/
if (slot->tts_shouldFreeDesc)
! FreeTupleDesc(slot->tts_tupleDescriptor);
if (slot->tts_values)
pfree(slot->tts_values);
--- 325,331 ----
* present (we don't bother to check if they could be re-used).
*/
if (slot->tts_shouldFreeDesc)
! DecrTupleDescRefCount(slot->tts_tupleDescriptor);
if (slot->tts_values)
pfree(slot->tts_values);
============================================================
*** src/backend/utils/cache/relcache.c 33edc76756ebe142a44c81e0785cafe4d50de293
--- src/backend/utils/cache/relcache.c 810b5d53ffdca5085dcba1879a26b10d61d334c3
***************
*** 1571,1577 ****
{
/* ok to zap remaining substructure */
flush_rowtype_cache(old_reltype);
! FreeTupleDesc(relation->rd_att);
if (relation->rd_rulescxt)
MemoryContextDelete(relation->rd_rulescxt);
pfree(relation);
--- 1571,1577 ----
{
/* ok to zap remaining substructure */
flush_rowtype_cache(old_reltype);
! DecrTupleDescRefCount(relation->rd_att);
if (relation->rd_rulescxt)
MemoryContextDelete(relation->rd_rulescxt);
pfree(relation);
***************
*** 1598,1604 ****
{
/* Should only get here if relation was deleted */
flush_rowtype_cache(old_reltype);
! FreeTupleDesc(old_att);
if (old_rulescxt)
MemoryContextDelete(old_rulescxt);
pfree(relation);
--- 1598,1604 ----
{
/* Should only get here if relation was deleted */
flush_rowtype_cache(old_reltype);
! DecrTupleDescRefCount(old_att);
if (old_rulescxt)
MemoryContextDelete(old_rulescxt);
pfree(relation);
***************
*** 1609,1621 ****
if (equalTupleDescs(old_att, relation->rd_att))
{
/* needn't flush typcache here */
! FreeTupleDesc(relation->rd_att);
relation->rd_att = old_att;
}
else
{
flush_rowtype_cache(old_reltype);
! FreeTupleDesc(old_att);
}
if (equalRuleLocks(old_rules, relation->rd_rules))
{
--- 1609,1621 ----
if (equalTupleDescs(old_att, relation->rd_att))
{
/* needn't flush typcache here */
! DecrTupleDescRefCount(relation->rd_att);
relation->rd_att = old_att;
}
else
{
flush_rowtype_cache(old_reltype);
! DecrTupleDescRefCount(old_att);
}
if (equalRuleLocks(old_rules, relation->rd_rules))
{
***************
*** 2139,2144 ****
--- 2139,2145 ----
{
MemoryContext oldcxt;
HASHCTL ctl;
+ ResourceOwner tmpOwner;
/*
* switch to cache memory context
***************
*** 2163,2168 ****
--- 2164,2180 ----
* now. Otherwise, initialize the cache with pre-made descriptors for the
* critical "nailed-in" system catalogs.
*/
+
+ /*
+ * XXX: when we initialize the relcache, we create some
+ * TupleDescs. There is no CurrentResourceOwner, however, because
+ * we're not inside a transaction. For now, just fake it and
+ * create a ResourceOwner that is never cleaned up. Obviously some
+ * better solution needed...
+ */
+ tmpOwner = ResourceOwnerCreate(NULL, "relcache init resowner");
+ CurrentResourceOwner = tmpOwner;
+
if (IsBootstrapProcessingMode() ||
!load_relcache_init_file())
{
***************
*** 2177,2182 ****
--- 2189,2195 ----
#define NUM_CRITICAL_RELS 4 /* fix if you change list above */
}
+ CurrentResourceOwner = NULL;
MemoryContextSwitchTo(oldcxt);
}
============================================================
*** src/backend/utils/resowner/resowner.c 8e55e3ae813bd6f162ef5de540740cedbe7d56a3
--- src/backend/utils/resowner/resowner.c 5bee544ef9490f4e24956420ce3ec2d7335ebce7
***************
*** 39,50 ****
ResourceOwner nextchild; /* next child of same parent */
const char *name; /* name (just for debugging) */
! /* We have built-in support for remembering owned buffers */
int nbuffers; /* number of owned buffer pins */
Buffer *buffers; /* dynamically allocated array */
int maxbuffers; /* currently allocated array size */
- /* We have built-in support for remembering catcache references */
int ncatrefs; /* number of owned catcache pins */
HeapTuple *catrefs; /* dynamically allocated array */
int maxcatrefs; /* currently allocated array size */
--- 39,54 ----
ResourceOwner nextchild; /* next child of same parent */
const char *name; /* name (just for debugging) */
! /*
! * We have built-in supportfor remembering buffer pins, catcache
! * references, catcache-list pins, relcache references, and
! * tupledescs.
! */
!
int nbuffers; /* number of owned buffer pins */
Buffer *buffers; /* dynamically allocated array */
int maxbuffers; /* currently allocated array size */
int ncatrefs; /* number of owned catcache pins */
HeapTuple *catrefs; /* dynamically allocated array */
int maxcatrefs; /* currently allocated array size */
***************
*** 53,62 ****
CatCList **catlistrefs; /* dynamically allocated array */
int maxcatlistrefs; /* currently allocated array size */
- /* We have built-in support for remembering relcache references */
int nrelrefs; /* number of owned relcache pins */
Relation *relrefs; /* dynamically allocated array */
int maxrelrefs; /* currently allocated array size */
} ResourceOwnerData;
--- 57,69 ----
CatCList **catlistrefs; /* dynamically allocated array */
int maxcatlistrefs; /* currently allocated array size */
int nrelrefs; /* number of owned relcache pins */
Relation *relrefs; /* dynamically allocated array */
int maxrelrefs; /* currently allocated array size */
+
+ int ntupdescs; /* number of owned tupledescs */
+ TupleDesc *tupdescs; /* dynamically allocated array */
+ int maxtupdescs; /* currently allocated array size */
} ResourceOwnerData;
***************
*** 87,92 ****
--- 94,100 ----
bool isCommit,
bool isTopLevel);
static void PrintRelCacheLeakWarning(Relation rel);
+ static void PrintTupleDescLeakWarning(TupleDesc tupdesc);
/*****************************************************************************
***************
*** 279,284 ****
--- 287,300 ----
/* Clean up index scans too */
ReleaseResources_gist();
ReleaseResources_hash();
+
+ /* Clean up tupledescs */
+ while (owner->ntupdescs > 0)
+ {
+ if (isCommit)
+ PrintTupleDescLeakWarning(owner->tupdescs[owner->ntupdescs - 1]);
+ DecrTupleDescRefCount(owner->tupdescs[owner->ntupdescs - 1]);
+ }
}
/* Let add-on modules get a chance too */
***************
*** 305,310 ****
--- 321,327 ----
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
***************
*** 329,334 ****
--- 346,353 ----
pfree(owner->catlistrefs);
if (owner->relrefs)
pfree(owner->relrefs);
+ if (owner->tupdescs)
+ pfree(owner->tupdescs);
pfree(owner);
}
***************
*** 735,745 ****
}
/*
! * Debugging subroutine
*/
static void
PrintRelCacheLeakWarning(Relation rel)
{
elog(WARNING, "relcache reference leak: relation \"%s\" not closed",
RelationGetRelationName(rel));
}
--- 754,844 ----
}
/*
! * Make sure there is room for at least one more entry in a ResourceOwner's
! * TupleDesc 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 TupleDesc 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 TupleDesc 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, "TupleDesc %p is not owned by resource owner %s",
+ tupdesc, owner->name);
+ }
+
+
+ /*
+ * Debugging subroutines
+ */
static void
PrintRelCacheLeakWarning(Relation rel)
{
elog(WARNING, "relcache reference leak: relation \"%s\" not closed",
RelationGetRelationName(rel));
}
+
+ static void
+ PrintTupleDescLeakWarning(TupleDesc tupdesc)
+ {
+ elog(WARNING, "TupleDesc reference leak: TupleDesc %p is still referenced",
+ tupdesc);
+ }
+
============================================================
*** src/include/access/tupdesc.h 838f14152479f66e6f98d9055886ab655da40f70
--- src/include/access/tupdesc.h f54dc201cc227804dbf310d5bde62a2ac8e30bb3
***************
*** 67,72 ****
--- 67,73 ----
Oid tdtypeid; /* composite type ID for tuple type */
int32 tdtypmod; /* typmod for tuple type */
bool tdhasoid; /* tuple has oid attribute in its header */
+ int refcount; /* number of active references */
} *TupleDesc;
***************
*** 76,85 ****
Form_pg_attribute *attrs);
extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
-
extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
! extern void FreeTupleDesc(TupleDesc tupdesc);
extern bool equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2);
--- 77,86 ----
Form_pg_attribute *attrs);
extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
! extern void IncrTupleDescRefCount(TupleDesc tupdesc);
! extern void DecrTupleDescRefCount(TupleDesc tupdesc);
extern bool equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2);
============================================================
*** src/include/utils/resowner.h ba8a6dae7162d1f5026daf7a9552db29f9615e91
--- src/include/utils/resowner.h 7322c8b8cd6206c6090099eb837516a866049d50
***************
*** 19,24 ****
--- 19,25 ----
#ifndef RESOWNER_H
#define RESOWNER_H
+ #include "access/tupdesc.h"
#include "storage/buf.h"
#include "utils/catcache.h"
#include "utils/rel.h"
***************
*** 107,110 ****
--- 108,118 ----
extern void ResourceOwnerForgetRelationRef(ResourceOwner owner,
Relation rel);
+ /* support for tupledesc refcount management */
+ extern void ResourceOwnerEnlargeTupleDescs(ResourceOwner owner);
+ extern void ResourceOwnerRememberTupleDesc(ResourceOwner owner,
+ TupleDesc tupdesc);
+ extern void ResourceOwnerForgetTupleDesc(ResourceOwner owner,
+ TupleDesc tupdesc);
+
#endif /* RESOWNER_H */
============================================================
*** src/pl/plpgsql/src/pl_exec.c 0f3dff3cfb05fda66b508752d7583ca187aa567a
--- src/pl/plpgsql/src/pl_exec.c 66015f5313e5dbf816cb4e235eaa4965100dab62
***************
*** 851,857 ****
if (rec->freetup)
{
heap_freetuple(rec->tup);
! FreeTupleDesc(rec->tupdesc);
rec->freetup = false;
}
--- 851,857 ----
if (rec->freetup)
{
heap_freetuple(rec->tup);
! DecrTupleDescRefCount(rec->tupdesc);
rec->freetup = false;
}
***************
*** 3915,3921 ****
}
if (rec->freetupdesc)
{
! FreeTupleDesc(rec->tupdesc);
rec->freetupdesc = false;
}
--- 3915,3921 ----
}
if (rec->freetupdesc)
{
! DecrTupleDescRefCount(rec->tupdesc);
rec->freetupdesc = false;
}
---------------------------(end of broadcast)---------------------------
TIP 9: In versions below 8.0, the planner will ignore your desire to
choose an index scan if your joining column's datatypes do not
match