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

Reply via email to