On Thu, 2006-01-19 at 04:14 -0500, Neil Conway wrote:
> The patch is WIP: a few regression tests fail due to TupleDesc leaks I
> haven't fixed yet but that should be easily fixable, and there are a few
> other minor issues to address. I'm just posting the patch now to get any
> feedback.

Attached is a revised patch: all the regression tests patch, and I'm not
aware of any major remaining issues. Barring any objections, I'll apply
this tomorrow.

(I'll take a look at using refcounting for TupleDescs in other parts of
the system after that...)

-Neil

============================================================
*** src/backend/access/common/tupdesc.c	83ca807d4fdd572c409bc9214922b6ba9da7ce18
--- src/backend/access/common/tupdesc.c	1a84f6faf6fc647c2bad0c2381f976f65b703f59
***************
*** 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"
  
  
***************
*** 84,89 ****
--- 85,91 ----
  	desc->tdtypeid = RECORDOID;
  	desc->tdtypmod = -1;
  	desc->tdhasoid = hasoid;
+ 	desc->refcount = 0;
  
  	return desc;
  }
***************
*** 116,121 ****
--- 118,124 ----
  	desc->tdtypeid = RECORDOID;
  	desc->tdtypmod = -1;
  	desc->tdhasoid = hasoid;
+ 	desc->refcount = 0;
  
  	return desc;
  }
***************
*** 214,219 ****
--- 217,224 ----
  {
  	int			i;
  
+ 	Assert(tupdesc->refcount == 0);
+ 
  	if (tupdesc->constr)
  	{
  		if (tupdesc->constr->num_defval > 0)
***************
*** 246,251 ****
--- 251,277 ----
  	pfree(tupdesc);
  }
  
+ void
+ IncrTupleDescRefCount(TupleDesc tupdesc)
+ {
+ 	Assert(tupdesc->refcount >= 0);
+ 
+ 	ResourceOwnerEnlargeTupleDescs(CurrentResourceOwner);
+ 	tupdesc->refcount++;
+ 	ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc);
+ }
+ 
+ void
+ DecrTupleDescRefCount(TupleDesc tupdesc)
+ {
+ 	Assert(tupdesc->refcount > 0);
+ 
+ 	tupdesc->refcount--;
+ 	ResourceOwnerForgetTupleDesc(CurrentResourceOwner, tupdesc);
+ 	if (tupdesc->refcount == 0)
+ 		FreeTupleDesc(tupdesc);
+ }
+ 
  /*
   * Compare two TupleDesc structures for logical equality
   *
============================================================
*** src/backend/access/heap/tuptoaster.c	f3ec822c0e6b6328bc0026716fcf4b133f89b092
--- src/backend/access/heap/tuptoaster.c	bbdea6bdd98d250326fad68899d07a8b153fb263
***************
*** 892,898 ****
--- 892,901 ----
  	 * If nothing to untoast, just return the original tuple.
  	 */
  	if (!need_change)
+ 	{
+ 		DecrTupleDescRefCount(tupleDesc);
  		return value;
+ 	}
  
  	/*
  	 * Calculate the new size of the tuple.  Header size should not change,
***************
*** 930,935 ****
--- 933,939 ----
  		if (toast_free[i])
  			pfree(DatumGetPointer(toast_values[i]));
  
+ 	DecrTupleDescRefCount(tupleDesc);
  	return PointerGetDatum(new_data);
  }
  
============================================================
*** src/backend/executor/execQual.c	eb1daef85341439578a3f6bb73549c4420292bc3
--- src/backend/executor/execQual.c	00e68d0f9dc2b692b994608acc001f640679f7a8
***************
*** 94,99 ****
--- 94,100 ----
  static Datum ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
  					   ExprContext *econtext,
  					   bool *isNull, ExprDoneCond *isDone);
+ static void CleanupConvertRowtypeCallback(Datum arg);
  static Datum ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
  			 bool *isNull, ExprDoneCond *isDone);
  static Datum ExecEvalCaseTestExpr(ExprState *exprstate,
***************
*** 132,140 ****
--- 133,143 ----
  static Datum ExecEvalFieldSelect(FieldSelectState *fstate,
  					ExprContext *econtext,
  					bool *isNull, ExprDoneCond *isDone);
+ static void CleanupFieldSelectCallback(Datum arg);
  static Datum ExecEvalFieldStore(FieldStoreState *fstate,
  				   ExprContext *econtext,
  				   bool *isNull, ExprDoneCond *isDone);
+ static void CleanupFieldStoreCallback(Datum arg);
  static Datum ExecEvalRelabelType(GenericExprState *exprstate,
  					ExprContext *econtext,
  					bool *isNull, ExprDoneCond *isDone);
***************
*** 707,712 ****
--- 710,717 ----
  						  attrno,
  						  tupDesc,
  						  isNull);
+ 
+ 	DecrTupleDescRefCount(tupDesc);
  	return result;
  }
  
***************
*** 765,770 ****
--- 770,777 ----
  						  attrno,
  						  tupDesc,
  						  isNull);
+ 
+ 	DecrTupleDescRefCount(tupDesc);
  	return result;
  }
  
***************
*** 1149,1157 ****
  /*
   *		ExecMakeTableFunctionResult
   *
!  * Evaluate a table function, producing a materialized result in a Tuplestore
!  * object.	*returnDesc is set to the tupledesc actually returned by the
!  * function, or NULL if it didn't provide one.
   */
  Tuplestorestate *
  ExecMakeTableFunctionResult(ExprState *funcexpr,
--- 1156,1166 ----
  /*
   *		ExecMakeTableFunctionResult
   *
!  * Evaluate a table function, producing a materialized result in a
!  * Tuplestore object. *returnDesc is set to the tupledesc actually
!  * returned by the function, or NULL if it didn't provide one. If
!  * non-NULL, the caller should decrement the reference count on the
!  * TupleDesc when finished with it.
   */
  Tuplestorestate *
  ExecMakeTableFunctionResult(ExprState *funcexpr,
***************
*** 1171,1176 ****
--- 1180,1186 ----
  	MemoryContext oldcontext;
  	bool		direct_function_call;
  	bool		first_time = true;
+ 	bool		need_refcount = true;
  
  	callerContext = CurrentMemoryContext;
  
***************
*** 1336,1342 ****
  				{
  					/*
  					 * Use the type info embedded in the rowtype Datum to look
! 					 * up the needed tupdesc.  Make a copy for the query.
  					 */
  					HeapTupleHeader td;
  
--- 1346,1352 ----
  				{
  					/*
  					 * Use the type info embedded in the rowtype Datum to look
! 					 * up the needed tupdesc.
  					 */
  					HeapTupleHeader td;
  
***************
*** 1343,1349 ****
  					td = DatumGetHeapTupleHeader(result);
  					tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(td),
  											   HeapTupleHeaderGetTypMod(td));
! 					tupdesc = CreateTupleDescCopy(tupdesc);
  				}
  				else
  				{
--- 1353,1363 ----
  					td = DatumGetHeapTupleHeader(result);
  					tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(td),
  											   HeapTupleHeaderGetTypMod(td));
! 					/*
! 					 * the tupdesc has already has its reference count
! 					 * bumped, so we needn't do it again
! 					 */
! 					need_refcount = false;
  				}
  				else
  				{
***************
*** 1448,1453 ****
--- 1462,1472 ----
  
  	/* The returned pointers are those in rsinfo */
  	*returnDesc = rsinfo.setDesc;
+ 
+ 	/* If we haven't bumped the tdesc's refcount yet, do so */
+ 	if (*returnDesc && need_refcount)
+ 		IncrTupleDescRefCount(*returnDesc);
+ 
  	return rsinfo.setResult;
  }
  
***************
*** 1913,1925 ****
  	Datum		tupDatum;
  	HeapTupleHeader tuple;
  	HeapTupleData tmptup;
- 	AttrNumber *attrMap = cstate->attrMap;
- 	Datum	   *invalues = cstate->invalues;
- 	bool	   *inisnull = cstate->inisnull;
- 	Datum	   *outvalues = cstate->outvalues;
- 	bool	   *outisnull = cstate->outisnull;
  	int			i;
! 	int			outnatts = cstate->outdesc->natts;
  
  	tupDatum = ExecEvalExpr(cstate->arg, econtext, isNull, isDone);
  
--- 1932,1939 ----
  	Datum		tupDatum;
  	HeapTupleHeader tuple;
  	HeapTupleData tmptup;
  	int			i;
! 	int			outnatts;
  
  	tupDatum = ExecEvalExpr(cstate->arg, econtext, isNull, isDone);
  
***************
*** 1927,1932 ****
--- 1941,2022 ----
  	if (*isNull)
  		return tupDatum;
  
+ 	/* if this is the first time through, do initialization */
+ 	if (!cstate->indesc)
+ 	{
+ 		ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) cstate->xprstate.expr;
+ 		MemoryContext	old_cxt;
+ 		int				n;
+ 
+ 		/* nothing should been initialized yet */
+ 		Assert(!cstate->outdesc);
+ 		Assert(!cstate->attrMap);
+ 		Assert(!cstate->invalues);
+ 		Assert(!cstate->inisnull);
+ 		Assert(!cstate->outvalues);
+ 		Assert(!cstate->outisnull);
+ 
+ 		/* allocate state in long-lived memory context */
+ 		old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ 
+ 		/* lookup needed tuple descriptors */
+ 		cstate->indesc = lookup_rowtype_tupdesc(exprType((Node *) convert->arg), -1);
+ 		cstate->outdesc = lookup_rowtype_tupdesc(convert->resulttype, -1);
+ 
+ 		/* register a callback to release tupledesc refcounts */
+ 		RegisterExprContextCallback(econtext, CleanupConvertRowtypeCallback,
+ 									PointerGetDatum(cstate));
+ 
+ 		/* prepare map from old to new attribute numbers */
+ 		n = cstate->outdesc->natts;
+ 		cstate->attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber));
+ 		for (i = 0; i < n; i++)
+ 		{
+ 			Form_pg_attribute att = cstate->outdesc->attrs[i];
+ 			char	   *attname;
+ 			Oid			atttypid;
+ 			int32		atttypmod;
+ 			int			j;
+ 
+ 			if (att->attisdropped)
+ 				continue;		/* attrMap[i] is already 0 */
+ 			attname = NameStr(att->attname);
+ 			atttypid = att->atttypid;
+ 			atttypmod = att->atttypmod;
+ 			for (j = 0; j < cstate->indesc->natts; j++)
+ 			{
+ 				att = cstate->indesc->attrs[j];
+ 				if (att->attisdropped)
+ 					continue;
+ 				if (strcmp(attname, NameStr(att->attname)) == 0)
+ 				{
+ 					/* Found it, check type */
+ 					if (atttypid != att->atttypid || atttypmod != att->atttypmod)
+ 						elog(ERROR, "attribute \"%s\" of type %s does not match corresponding attribute of type %s",
+ 							 attname,
+ 							 format_type_be(cstate->indesc->tdtypeid),
+ 							 format_type_be(cstate->outdesc->tdtypeid));
+ 					cstate->attrMap[i] = (AttrNumber) (j + 1);
+ 					break;
+ 				}
+ 			}
+ 			if (cstate->attrMap[i] == 0)
+ 				elog(ERROR, "attribute \"%s\" of type %s does not exist",
+ 					 attname, format_type_be(cstate->indesc->tdtypeid));
+ 		}
+ 
+ 		/* allocate workspace for Datum arrays */
+ 		n = cstate->indesc->natts + 1;	/* +1 for NULL */
+ 		cstate->invalues = (Datum *) palloc(n * sizeof(Datum));
+ 		cstate->inisnull = (bool *) palloc(n * sizeof(bool));
+ 		n = cstate->outdesc->natts;
+ 		cstate->outvalues = (Datum *) palloc(n * sizeof(Datum));
+ 		cstate->outisnull = (bool *) palloc(n * sizeof(bool));
+ 
+ 		MemoryContextSwitchTo(old_cxt);
+ 	}
+ 
+ 	outnatts = cstate->outdesc->natts;
  	tuple = DatumGetHeapTupleHeader(tupDatum);
  
  	Assert(HeapTupleHeaderGetTypeId(tuple) == cstate->indesc->tdtypeid);
***************
*** 1943,1951 ****
  	 * invalues[0] is NULL and invalues[1] is the first source attribute; this
  	 * exactly matches the numbering convention in attrMap.
  	 */
! 	heap_deform_tuple(&tmptup, cstate->indesc, invalues + 1, inisnull + 1);
! 	invalues[0] = (Datum) 0;
! 	inisnull[0] = true;
  
  	/*
  	 * Transpose into proper fields of the new tuple.
--- 2033,2042 ----
  	 * invalues[0] is NULL and invalues[1] is the first source attribute; this
  	 * exactly matches the numbering convention in attrMap.
  	 */
! 	heap_deform_tuple(&tmptup, cstate->indesc, cstate->invalues + 1,
! 					  cstate->inisnull + 1);
! 	cstate->invalues[0] = (Datum) 0;
! 	cstate->inisnull[0] = true;
  
  	/*
  	 * Transpose into proper fields of the new tuple.
***************
*** 1952,1961 ****
  	 */
  	for (i = 0; i < outnatts; i++)
  	{
! 		int			j = attrMap[i];
  
! 		outvalues[i] = invalues[j];
! 		outisnull[i] = inisnull[j];
  	}
  
  	/*
--- 2043,2052 ----
  	 */
  	for (i = 0; i < outnatts; i++)
  	{
! 		int			j = cstate->attrMap[i];
  
! 		cstate->outvalues[i] = cstate->invalues[j];
! 		cstate->outisnull[i] = cstate->inisnull[j];
  	}
  
  	/*
***************
*** 1961,1969 ****
  	/*
  	 * Now form the new tuple.
  	 */
! 	result = heap_form_tuple(cstate->outdesc, outvalues, outisnull);
  
! 	return HeapTupleGetDatum(result);
  }
  
  /* ----------------------------------------------------------------
--- 2052,2075 ----
  	/*
  	 * Now form the new tuple.
  	 */
! 	result = heap_form_tuple(cstate->outdesc, cstate->outvalues,
! 							 cstate->outisnull);
! 	return HeapTupleGetDatum(result);
! }
  
! static void
! CleanupConvertRowtypeCallback(Datum arg)
! {
! 	ConvertRowtypeExprState *cstate =
! 		(ConvertRowtypeExprState *) DatumGetPointer(arg);
! 
! 	if (cstate->indesc)
! 	{
! 		Assert(cstate->outdesc);
! 
! 		DecrTupleDescRefCount(cstate->indesc);
! 		DecrTupleDescRefCount(cstate->outdesc);
! 	}
  }
  
  /* ----------------------------------------------------------------
***************
*** 2786,2807 ****
  	tupType = HeapTupleHeaderGetTypeId(tuple);
  	tupTypmod = HeapTupleHeaderGetTypMod(tuple);
  
  	/* Lookup tupdesc if first time through or if type changes */
  	tupDesc = fstate->argdesc;
  	if (tupDesc == NULL ||
  		tupType != tupDesc->tdtypeid ||
  		tupTypmod != tupDesc->tdtypmod)
  	{
! 		MemoryContext oldcontext;
  
  		tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
- 		/* Copy the tupdesc into query storage for safety */
- 		oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
- 		tupDesc = CreateTupleDescCopy(tupDesc);
- 		if (fstate->argdesc)
- 			FreeTupleDesc(fstate->argdesc);
  		fstate->argdesc = tupDesc;
- 		MemoryContextSwitchTo(oldcontext);
  	}
  
  	/*
--- 2892,2916 ----
  	tupType = HeapTupleHeaderGetTypeId(tuple);
  	tupTypmod = HeapTupleHeaderGetTypMod(tuple);
  
+ 	/*
+ 	 * If this is the first time through, setup a callback to ensure
+ 	 * we release the reference count we hold on the TupleDesc
+ 	 */
+ 	if (fstate->argdesc == NULL)
+ 		RegisterExprContextCallback(econtext, CleanupFieldSelectCallback,
+ 									PointerGetDatum(fstate));
+ 
  	/* Lookup tupdesc if first time through or if type changes */
  	tupDesc = fstate->argdesc;
  	if (tupDesc == NULL ||
  		tupType != tupDesc->tdtypeid ||
  		tupTypmod != tupDesc->tdtypmod)
  	{
! 		if (fstate->argdesc)
! 			DecrTupleDescRefCount(fstate->argdesc);
  
  		tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
  		fstate->argdesc = tupDesc;
  	}
  
  	/*
***************
*** 2821,2826 ****
--- 2930,2944 ----
  	return result;
  }
  
+ static void
+ CleanupFieldSelectCallback(Datum arg)
+ {
+ 	FieldSelectState *fstate = (FieldSelectState *) DatumGetPointer(arg);
+ 
+ 	if (fstate->argdesc)
+ 		DecrTupleDescRefCount(fstate->argdesc);
+ }
+ 
  /* ----------------------------------------------------------------
   *		ExecEvalFieldStore
   *
***************
*** 2849,2869 ****
  	if (isDone && *isDone == ExprEndResult)
  		return tupDatum;
  
  	/* Lookup tupdesc if first time through or if type changes */
  	tupDesc = fstate->argdesc;
  	if (tupDesc == NULL ||
  		fstore->resulttype != tupDesc->tdtypeid)
  	{
! 		MemoryContext oldcontext;
  
  		tupDesc = lookup_rowtype_tupdesc(fstore->resulttype, -1);
- 		/* Copy the tupdesc into query storage for safety */
- 		oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
- 		tupDesc = CreateTupleDescCopy(tupDesc);
- 		if (fstate->argdesc)
- 			FreeTupleDesc(fstate->argdesc);
  		fstate->argdesc = tupDesc;
- 		MemoryContextSwitchTo(oldcontext);
  	}
  
  	/* Allocate workspace */
--- 2967,2990 ----
  	if (isDone && *isDone == ExprEndResult)
  		return tupDatum;
  
+ 	/*
+ 	 * If this is the first time through, setup a callback to ensure
+ 	 * we release the reference count we hold on the TupleDesc
+ 	 */
+ 	if (fstate->argdesc == NULL)
+ 		RegisterExprContextCallback(econtext, CleanupFieldStoreCallback,
+ 									PointerGetDatum(fstate));
+ 
  	/* Lookup tupdesc if first time through or if type changes */
  	tupDesc = fstate->argdesc;
  	if (tupDesc == NULL ||
  		fstore->resulttype != tupDesc->tdtypeid)
  	{
! 		if (fstate->argdesc)
! 			DecrTupleDescRefCount(fstate->argdesc);
  
  		tupDesc = lookup_rowtype_tupdesc(fstore->resulttype, -1);
  		fstate->argdesc = tupDesc;
  	}
  
  	/* Allocate workspace */
***************
*** 2933,2938 ****
--- 3054,3068 ----
  	return HeapTupleGetDatum(tuple);
  }
  
+ static void
+ CleanupFieldStoreCallback(Datum arg)
+ {
+ 	FieldStoreState *fstate = (FieldStoreState *) DatumGetPointer(arg);
+ 
+ 	if (fstate->argdesc)
+ 		DecrTupleDescRefCount(fstate->argdesc);
+ }
+ 
  /* ----------------------------------------------------------------
   *		ExecEvalRelabelType
   *
***************
*** 3237,3297 ****
  			{
  				ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
  				ConvertRowtypeExprState *cstate = makeNode(ConvertRowtypeExprState);
- 				int			i;
- 				int			n;
  
  				cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalConvertRowtype;
  				cstate->arg = ExecInitExpr(convert->arg, parent);
- 				/* save copies of needed tuple descriptors */
- 				cstate->indesc = lookup_rowtype_tupdesc(exprType((Node *) convert->arg), -1);
- 				cstate->indesc = CreateTupleDescCopy(cstate->indesc);
- 				cstate->outdesc = lookup_rowtype_tupdesc(convert->resulttype, -1);
- 				cstate->outdesc = CreateTupleDescCopy(cstate->outdesc);
- 				/* prepare map from old to new attribute numbers */
- 				n = cstate->outdesc->natts;
- 				cstate->attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber));
- 				for (i = 0; i < n; i++)
- 				{
- 					Form_pg_attribute att = cstate->outdesc->attrs[i];
- 					char	   *attname;
- 					Oid			atttypid;
- 					int32		atttypmod;
- 					int			j;
- 
- 					if (att->attisdropped)
- 						continue;		/* attrMap[i] is already 0 */
- 					attname = NameStr(att->attname);
- 					atttypid = att->atttypid;
- 					atttypmod = att->atttypmod;
- 					for (j = 0; j < cstate->indesc->natts; j++)
- 					{
- 						att = cstate->indesc->attrs[j];
- 						if (att->attisdropped)
- 							continue;
- 						if (strcmp(attname, NameStr(att->attname)) == 0)
- 						{
- 							/* Found it, check type */
- 							if (atttypid != att->atttypid || atttypmod != att->atttypmod)
- 								elog(ERROR, "attribute \"%s\" of type %s does not match corresponding attribute of type %s",
- 									 attname,
- 									 format_type_be(cstate->indesc->tdtypeid),
- 								  format_type_be(cstate->outdesc->tdtypeid));
- 							cstate->attrMap[i] = (AttrNumber) (j + 1);
- 							break;
- 						}
- 					}
- 					if (cstate->attrMap[i] == 0)
- 						elog(ERROR, "attribute \"%s\" of type %s does not exist",
- 							 attname,
- 							 format_type_be(cstate->indesc->tdtypeid));
- 				}
- 				/* preallocate workspace for Datum arrays */
- 				n = cstate->indesc->natts + 1;	/* +1 for NULL */
- 				cstate->invalues = (Datum *) palloc(n * sizeof(Datum));
- 				cstate->inisnull = (bool *) palloc(n * sizeof(bool));
- 				n = cstate->outdesc->natts;
- 				cstate->outvalues = (Datum *) palloc(n * sizeof(Datum));
- 				cstate->outisnull = (bool *) palloc(n * sizeof(bool));
  				state = (ExprState *) cstate;
  			}
  			break;
--- 3367,3375 ----
***************
*** 3366,3373 ****
  				else
  				{
  					/* it's been cast to a named type, use that */
! 					rstate->tupdesc = lookup_rowtype_tupdesc(rowexpr->row_typeid, -1);
! 					rstate->tupdesc = CreateTupleDescCopy(rstate->tupdesc);
  				}
  				/* Set up evaluation, skipping any deleted columns */
  				Assert(list_length(rowexpr->args) <= rstate->tupdesc->natts);
--- 3444,3450 ----
  				else
  				{
  					/* it's been cast to a named type, use that */
! 					rstate->tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1);
  				}
  				/* Set up evaluation, skipping any deleted columns */
  				Assert(list_length(rowexpr->args) <= rstate->tupdesc->natts);
============================================================
*** src/backend/executor/nodeFunctionscan.c	aaa361c427457b027312c88f8cbd9dff4713b4e1
--- src/backend/executor/nodeFunctionscan.c	276225550a66664d415b5d236e51f81e46ddfb57
***************
*** 80,86 ****
--- 80,90 ----
  		 * do it always.
  		 */
  		if (funcTupdesc)
+ 		{
  			tupledesc_match(node->tupdesc, funcTupdesc);
+ 			/* Decrement its refcount now that we're finished with it */
+ 			DecrTupleDescRefCount(funcTupdesc);
+ 		}
  	}
  
  	/*
============================================================
*** src/backend/optimizer/util/clauses.c	bbe724038d294e1730ac04e4ef5a4a445186a690
--- src/backend/optimizer/util/clauses.c	8bfea1ba4129ae9583ffd892674cbe892a5ebc9b
***************
*** 1289,1309 ****
  rowtype_field_matches(Oid rowtypeid, int fieldnum,
  					  Oid expectedtype, int32 expectedtypmod)
  {
  	TupleDesc	tupdesc;
- 	Form_pg_attribute attr;
  
  	/* No issue for RECORD, since there is no way to ALTER such a type */
  	if (rowtypeid == RECORDOID)
  		return true;
  	tupdesc = lookup_rowtype_tupdesc(rowtypeid, -1);
  	if (fieldnum <= 0 || fieldnum > tupdesc->natts)
! 		return false;
! 	attr = tupdesc->attrs[fieldnum - 1];
! 	if (attr->attisdropped ||
! 		attr->atttypid != expectedtype ||
! 		attr->atttypmod != expectedtypmod)
! 		return false;
! 	return true;
  }
  
  
--- 1289,1317 ----
  rowtype_field_matches(Oid rowtypeid, int fieldnum,
  					  Oid expectedtype, int32 expectedtypmod)
  {
+ 	bool		result;
  	TupleDesc	tupdesc;
  
  	/* No issue for RECORD, since there is no way to ALTER such a type */
  	if (rowtypeid == RECORDOID)
  		return true;
+ 
  	tupdesc = lookup_rowtype_tupdesc(rowtypeid, -1);
  	if (fieldnum <= 0 || fieldnum > tupdesc->natts)
! 		result = false;
! 	else
! 	{
! 		Form_pg_attribute attr = tupdesc->attrs[fieldnum - 1];
! 		if (attr->attisdropped ||
! 			attr->atttypid != expectedtype ||
! 			attr->atttypmod != expectedtypmod)
! 			result = false;
! 		else
! 			result = true;
! 	}
! 
! 	DecrTupleDescRefCount(tupdesc);
! 	return result;
  }
  
  
============================================================
*** src/backend/parser/parse_coerce.c	3737c5680e258c68490479e5f12fec046aaebf86
--- src/backend/parser/parse_coerce.c	6e158f35e8953bd62a64f145b041987ac203ef90
***************
*** 763,768 ****
--- 763,771 ----
  						format_type_be(targetTypeId)),
  				 errdetail("Input has too many columns.")));
  
+ 
+ 	DecrTupleDescRefCount(tupdesc);
+ 
  	rowexpr = makeNode(RowExpr);
  	rowexpr->args = newargs;
  	rowexpr->row_typeid = targetTypeId;
============================================================
*** src/backend/parser/parse_target.c	bc7dd09c579281b54dd356e999c3b17d8fa23310
--- src/backend/parser/parse_target.c	fa7ebfe84615c82aabd457e20898bdec56c9b036
***************
*** 803,812 ****
  
  	/*
  	 * Verify it's a composite type, and get the tupdesc.  We use
! 	 * get_expr_result_type() because that can handle references to functions
! 	 * returning anonymous record types.  If that fails, use
! 	 * lookup_rowtype_tupdesc(), which will almost certainly fail as well, but
! 	 * it will give an appropriate error message.
  	 *
  	 * If it's a Var of type RECORD, we have to work even harder: we have to
  	 * find what the Var refers to, and pass that to get_expr_result_type.
--- 803,812 ----
  
  	/*
  	 * Verify it's a composite type, and get the tupdesc.  We use
! 	 * get_expr_result_type() because that can handle references to
! 	 * functions returning anonymous record types.  If that fails, use
! 	 * lookup_rowtype_tupdesc_copy(), which will almost certainly fail
! 	 * as well, but it will give an appropriate error message.
  	 *
  	 * If it's a Var of type RECORD, we have to work even harder: we have to
  	 * find what the Var refers to, and pass that to get_expr_result_type.
***************
*** 816,822 ****
  		((Var *) expr)->vartype == RECORDOID)
  		tupleDesc = expandRecordVariable(pstate, (Var *) expr, 0);
  	else if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
! 		tupleDesc = lookup_rowtype_tupdesc(exprType(expr), exprTypmod(expr));
  	Assert(tupleDesc);
  
  	/* Generate a list of references to the individual fields */
--- 816,823 ----
  		((Var *) expr)->vartype == RECORDOID)
  		tupleDesc = expandRecordVariable(pstate, (Var *) expr, 0);
  	else if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
! 		tupleDesc = lookup_rowtype_tupdesc_copy(exprType(expr),
! 												exprTypmod(expr));
  	Assert(tupleDesc);
  
  	/* Generate a list of references to the individual fields */
***************
*** 993,999 ****
  	 * appropriate error message while failing.
  	 */
  	if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
! 		tupleDesc = lookup_rowtype_tupdesc(exprType(expr), exprTypmod(expr));
  
  	return tupleDesc;
  }
--- 994,1001 ----
  	 * appropriate error message while failing.
  	 */
  	if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
! 		tupleDesc = lookup_rowtype_tupdesc_copy(exprType(expr),
! 												exprTypmod(expr));
  
  	return tupleDesc;
  }
============================================================
*** src/backend/utils/adt/rowtypes.c	07d7136a68bc720c43b726e3c9cf9e85fc09b807
--- src/backend/utils/adt/rowtypes.c	28c870e40e6df794c511938063b11c8826b021b9
***************
*** 257,262 ****
--- 257,263 ----
  	result = (HeapTupleHeader) palloc(tuple->t_len);
  	memcpy(result, tuple->t_data, tuple->t_len);
  
+ 	DecrTupleDescRefCount(tupdesc);
  	heap_freetuple(tuple);
  	pfree(buf.data);
  	pfree(values);
***************
*** 407,412 ****
--- 408,414 ----
  
  	appendStringInfoChar(&buf, ')');
  
+ 	DecrTupleDescRefCount(tupdesc);
  	pfree(values);
  	pfree(nulls);
  
***************
*** 594,599 ****
--- 596,602 ----
  	result = (HeapTupleHeader) palloc(tuple->t_len);
  	memcpy(result, tuple->t_data, tuple->t_len);
  
+ 	DecrTupleDescRefCount(tupdesc);
  	heap_freetuple(tuple);
  	pfree(values);
  	pfree(nulls);
***************
*** 722,727 ****
--- 725,731 ----
  		pfree(outputbytes);
  	}
  
+ 	DecrTupleDescRefCount(tupdesc);
  	pfree(values);
  	pfree(nulls);
  
============================================================
*** src/backend/utils/adt/ruleutils.c	ecb75470bb827232a0cdeeee9ae3ec4f6d511782
--- src/backend/utils/adt/ruleutils.c	b67411eea2fd387bbc9881125a81d9fa4efd9271
***************
*** 2689,2700 ****
  
  	/*
  	 * We now have an expression we can't expand any more, so see if
! 	 * get_expr_result_type() can do anything with it.	If not, pass to
! 	 * lookup_rowtype_tupdesc() which will probably fail, but will give an
! 	 * appropriate error message while failing.
  	 */
  	if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
! 		tupleDesc = lookup_rowtype_tupdesc(exprType(expr), exprTypmod(expr));
  
  	/* Got the tupdesc, so we can extract the field name */
  	Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
--- 2689,2701 ----
  
  	/*
  	 * We now have an expression we can't expand any more, so see if
! 	 * get_expr_result_type() can do anything with it. If not, pass to
! 	 * lookup_rowtype_tupdesc_copy() which will probably fail, but
! 	 * will give an appropriate error message while failing.
  	 */
  	if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
! 		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);
***************
*** 3326,3333 ****
  					TupleDesc	tupdesc;
  
  					if (get_expr_result_type(arg, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
! 						tupdesc = lookup_rowtype_tupdesc(exprType(arg),
! 														 exprTypmod(arg));
  					Assert(tupdesc);
  					/* Got the tupdesc, so we can extract the field name */
  					Assert(fno >= 1 && fno <= tupdesc->natts);
--- 3327,3334 ----
  					TupleDesc	tupdesc;
  
  					if (get_expr_result_type(arg, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
! 						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);
***************
*** 3528,3533 ****
--- 3529,3536 ----
  						}
  						i++;
  					}
+ 
+ 					DecrTupleDescRefCount(tupdesc);
  				}
  				appendStringInfo(buf, ")");
  				if (rowexpr->row_format == COERCE_EXPLICIT_CAST)
============================================================
*** src/backend/utils/cache/relcache.c	48d99232044a32d0d9aa934885c2ae69978ad129
--- src/backend/utils/cache/relcache.c	842112d31618dc4a211222691efe4980885a2159
***************
*** 1592,1598 ****
  	{
  		/* ok to zap remaining substructure */
  		flush_rowtype_cache(old_reltype);
- 		FreeTupleDesc(relation->rd_att);
  		if (relation->rd_rulescxt)
  			MemoryContextDelete(relation->rd_rulescxt);
  		pfree(relation);
--- 1592,1597 ----
***************
*** 1619,1625 ****
  		{
  			/* Should only get here if relation was deleted */
  			flush_rowtype_cache(old_reltype);
- 			FreeTupleDesc(old_att);
  			if (old_rulescxt)
  				MemoryContextDelete(old_rulescxt);
  			pfree(relation);
--- 1618,1623 ----
***************
*** 1636,1642 ****
  		else
  		{
  			flush_rowtype_cache(old_reltype);
- 			FreeTupleDesc(old_att);
  		}
  		if (equalRuleLocks(old_rules, relation->rd_rules))
  		{
--- 1634,1639 ----
============================================================
*** src/backend/utils/cache/typcache.c	85eb6ae0e0048cd300ef5142aa3926b2f5907b2f
--- src/backend/utils/cache/typcache.c	a041f580bfec88291f8b368c79243351fa77e8ed
***************
*** 283,288 ****
--- 283,295 ----
  		 */
  		typentry->tupDesc = RelationGetDescr(rel);
  
+ 		/*
+ 		 * We keep a reference to the TupleDesc to ensure it survives
+ 		 * until there is a cache flush. The reference lasts longer
+ 		 * than the current transaction, so bump the refcount manually.
+ 		 */
+ 		typentry->tupDesc->refcount++;
+ 
  		relation_close(rel, AccessShareLock);
  	}
  
***************
*** 373,382 ****
   * 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)
--- 380,389 ----
   * 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. We hold a reference on the returned TupleDesc; the caller
!  * should decrement its reference count when they are finished with
!  * it.
   */
  TupleDesc
  lookup_rowtype_tupdesc(Oid type_id, int32 typmod)
***************
*** 385,390 ****
--- 392,416 ----
  }
  
  /*
+  * lookup_rowtype_tupdesc_copy
+  *
+  * Like lookup_rowtype_tupdesc(), but the returned TupleDesc has been
+  * copied into the CurrentMemoryContext and has a zero reference count.
+  */
+ TupleDesc
+ lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod)
+ {
+ 	TupleDesc tmp;
+ 	TupleDesc result;
+ 
+ 	tmp = lookup_rowtype_tupdesc_noerror(type_id, typmod, false);
+ 	result = CreateTupleDescCopyConstr(tmp);
+ 	DecrTupleDescRefCount(tmp);
+ 
+ 	return result;
+ }
+ 
+ /*
   * lookup_rowtype_tupdesc_noerror
   *
   * As above, but if the type is not a known composite type and noError
***************
*** 394,399 ****
--- 420,427 ----
  TupleDesc
  lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod, bool noError)
  {
+ 	TupleDesc result;
+ 
  	if (type_id != RECORDOID)
  	{
  		/*
***************
*** 407,413 ****
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  					 errmsg("type %s is not composite",
  							format_type_be(type_id))));
! 		return typentry->tupDesc;
  	}
  	else
  	{
--- 435,441 ----
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  					 errmsg("type %s is not composite",
  							format_type_be(type_id))));
! 		result = typentry->tupDesc;
  	}
  	else
  	{
***************
*** 422,429 ****
  						 errmsg("record type has not been registered")));
  			return NULL;
  		}
! 		return RecordCacheArray[typmod];
  	}
  }
  
  
--- 450,461 ----
  						 errmsg("record type has not been registered")));
  			return NULL;
  		}
! 		result = RecordCacheArray[typmod];
  	}
+ 
+ 	if (result)
+ 		IncrTupleDescRefCount(result);
+ 	return result;
  }
  
  
***************
*** 511,517 ****
--- 543,552 ----
  
  	/* if fail in subrs, no damage except possibly some wasted memory... */
  	entDesc = CreateTupleDescCopy(tupDesc);
+ 	/* increment the tupdesc's refcount, so it will not be freed */
+ 	entDesc->refcount++;
  	recentry->tupdescs = lcons(entDesc, recentry->tupdescs);
+ 
  	/* now it's safe to advance NextRecordTypmod */
  	newtypmod = NextRecordTypmod++;
  	entDesc->tdtypmod = newtypmod;
***************
*** 534,539 ****
--- 569,575 ----
  flush_rowtype_cache(Oid type_id)
  {
  	TypeCacheEntry *typentry;
+ 	TupleDesc		tupdesc;
  
  	if (TypeCacheHash == NULL)
  		return;					/* no table, so certainly no entry */
***************
*** 544,548 ****
--- 580,598 ----
  	if (typentry == NULL)
  		return;					/* no matching entry */
  
+ 	/*
+ 	 * Clear out the tuple descriptor. Decrement our reference on the
+ 	 * TupleDesc; if we're the last reference, we can free it now,
+ 	 * otherwise wait for it be freed when the refcount reaches zero.
+ 	 */
+ 	tupdesc = typentry->tupDesc;
  	typentry->tupDesc = NULL;
+ 
+ 	if (tupdesc)
+ 	{
+ 		Assert(tupdesc->refcount > 0);
+ 		tupdesc->refcount--;
+ 		if (tupdesc->refcount == 0)
+ 			FreeTupleDesc(tupdesc);
+ 	}
  }
============================================================
*** src/backend/utils/fmgr/funcapi.c	1e22765271d66af893b77287b1907c075fe5a866
--- src/backend/utils/fmgr/funcapi.c	114647ae1f1c708a71d206de6c7a679497bee208
***************
*** 180,192 ****
  
  /*
   * get_call_result_type
!  *		Given a function's call info record, determine the kind of datatype
!  *		it is supposed to return.  If resultTypeId isn't NULL, *resultTypeId
!  *		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.
   *
   * One hard case that this handles is resolution of actual rowtypes for
   * functions returning RECORD (from either the function's OUT parameter
--- 180,192 ----
  
  /*
   * get_call_result_type
!  *		Given a function's call info record, determine the kind of
!  *		datatype it is supposed to return.  If resultTypeId isn't
!  *		NULL, *resultTypeId 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.
   *
   * One hard case that this handles is resolution of actual rowtypes for
   * functions returning RECORD (from either the function's OUT parameter
***************
*** 246,252 ****
  			*resultTupleDesc = NULL;
  		result = get_type_func_class(typid);
  		if (result == TYPEFUNC_COMPOSITE && resultTupleDesc)
! 			*resultTupleDesc = lookup_rowtype_tupdesc(typid, -1);
  	}
  
  	return result;
--- 246,252 ----
  			*resultTupleDesc = NULL;
  		result = get_type_func_class(typid);
  		if (result == TYPEFUNC_COMPOSITE && resultTupleDesc)
! 			*resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, -1);
  	}
  
  	return result;
***************
*** 363,369 ****
  	{
  		case TYPEFUNC_COMPOSITE:
  			if (resultTupleDesc)
! 				*resultTupleDesc = lookup_rowtype_tupdesc(rettype, -1);
  			/* Named composite types can't have any polymorphic columns */
  			break;
  		case TYPEFUNC_SCALAR:
--- 363,369 ----
  	{
  		case TYPEFUNC_COMPOSITE:
  			if (resultTupleDesc)
! 				*resultTupleDesc = lookup_rowtype_tupdesc_copy(rettype, -1);
  			/* Named composite types can't have any polymorphic columns */
  			break;
  		case TYPEFUNC_SCALAR:
***************
*** 1053,1059 ****
  	if (functypclass == TYPEFUNC_COMPOSITE)
  	{
  		/* Composite data type, e.g. a table's row type */
! 		tupdesc = CreateTupleDescCopy(lookup_rowtype_tupdesc(typeoid, -1));
  
  		if (colaliases != NIL)
  		{
--- 1053,1059 ----
  	if (functypclass == TYPEFUNC_COMPOSITE)
  	{
  		/* Composite data type, e.g. a table's row type */
! 		tupdesc = lookup_rowtype_tupdesc_copy(typeoid, -1);
  
  		if (colaliases != NIL)
  		{
============================================================
*** src/backend/utils/resowner/resowner.c	8e55e3ae813bd6f162ef5de540740cedbe7d56a3
--- src/backend/utils/resowner/resowner.c	bdb3c3b7e32e7103828eb66dd8b8c5d4edad4368
***************
*** 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 support for 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);
  
  
  /*****************************************************************************
***************
*** 276,281 ****
--- 284,297 ----
  			ReleaseCatCacheList(owner->catlistrefs[owner->ncatlistrefs - 1]);
  		}
  
+ 		/* Clean up tupledescs */
+ 		while (owner->ntupdescs > 0)
+ 		{
+ 			if (isCommit)
+ 				PrintTupleDescLeakWarning(owner->tupdescs[owner->ntupdescs - 1]);
+ 			DecrTupleDescRefCount(owner->tupdescs[owner->ntupdescs - 1]);
+ 		}
+ 
  		/* Clean up index scans too */
  		ReleaseResources_gist();
  		ReleaseResources_hash();
***************
*** 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,842 ----
  }
  
  /*
!  * 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	6719800eb7fffc8904eea1773cf1545672462ffe
***************
*** 57,62 ****
--- 57,65 ----
   * tdtypeid is RECORDOID, and tdtypmod can be either -1 for a fully anonymous
   * row type, or a value >= 0 to allow the rowtype to be looked up in the
   * typcache.c type cache.
+  *
+  * If a TupleDesc has non-zero refcount, we assume it should be
+  * automatically freed when the refcount reaches zero.
   */
  typedef struct tupleDesc
  {
***************
*** 67,72 ****
--- 70,76 ----
  	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;		/* if > 0, the # of references */
  }	*TupleDesc;
  
  
***************
*** 81,86 ****
--- 85,93 ----
  
  extern void FreeTupleDesc(TupleDesc tupdesc);
  
+ extern void IncrTupleDescRefCount(TupleDesc tupdesc);
+ extern void DecrTupleDescRefCount(TupleDesc tupdesc);
+ 
  extern bool equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2);
  
  extern void TupleDescInitEntry(TupleDesc desc,
============================================================
*** 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/include/utils/typcache.h	60ff2e4a19fc06afb5c41d440c20aff03efd79ae
--- src/include/utils/typcache.h	9ad569c8067bc41063bac65866783065a9a16cf7
***************
*** 75,81 ****
  extern TypeCacheEntry *lookup_type_cache(Oid type_id, int flags);
  
  extern TupleDesc lookup_rowtype_tupdesc(Oid type_id, int32 typmod);
! 
  extern TupleDesc lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod,
  							   bool noError);
  
--- 75,81 ----
  extern TypeCacheEntry *lookup_type_cache(Oid type_id, int flags);
  
  extern TupleDesc lookup_rowtype_tupdesc(Oid type_id, int32 typmod);
! extern TupleDesc lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod);
  extern TupleDesc lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod,
  							   bool noError);
  
============================================================
*** src/pl/plperl/plperl.c	a4ba9c99e0d5d19ee7fcf9fb6762594a43fd2fae
--- src/pl/plperl/plperl.c	298226f8719779be83482cbe35d3037759bd4092
***************
*** 828,833 ****
--- 828,834 ----
  
  			hashref = plperl_hash_from_tuple(&tmptup, tupdesc);
  			XPUSHs(sv_2mortal(hashref));
+ 			DecrTupleDescRefCount(tupdesc);
  		}
  		else
  		{
============================================================
*** src/pl/plpgsql/src/pl_exec.c	0f3dff3cfb05fda66b508752d7583ca187aa567a
--- src/pl/plpgsql/src/pl_exec.c	6a0d816314313208c62d62c31495506e16b37e12
***************
*** 259,264 ****
--- 259,265 ----
  						tmptup.t_tableOid = InvalidOid;
  						tmptup.t_data = td;
  						exec_move_row(&estate, NULL, row, &tmptup, tupdesc);
+ 						DecrTupleDescRefCount(tupdesc);
  					}
  					else
  					{
***************
*** 3114,3119 ****
--- 3115,3121 ----
  					tmptup.t_tableOid = InvalidOid;
  					tmptup.t_data = td;
  					exec_move_row(estate, NULL, row, &tmptup, tupdesc);
+ 					DecrTupleDescRefCount(tupdesc);
  				}
  				break;
  			}
***************
*** 3156,3161 ****
--- 3158,3164 ----
  					tmptup.t_tableOid = InvalidOid;
  					tmptup.t_data = td;
  					exec_move_row(estate, rec, NULL, &tmptup, tupdesc);
+ 					DecrTupleDescRefCount(tupdesc);
  				}
  				break;
  			}
============================================================
*** src/pl/plpython/plpython.c	103ec857e125a1ca082966ee70f7a2d7dbc19928
--- src/pl/plpython/plpython.c	a8a59377ad8334e910cdaae7845d5a2a20796714
***************
*** 864,869 ****
--- 864,870 ----
  					tmptup.t_data = td;
  
  					arg = PLyDict_FromTuple(&(proc->args[i]), &tmptup, tupdesc);
+ 					DecrTupleDescRefCount(tupdesc);
  				}
  			}
  			else
============================================================
*** src/pl/tcl/pltcl.c	e9000795907af26a6a066ad43a6b7749423df32b
--- src/pl/tcl/pltcl.c	7e30fd2195d7a7a99eee6a35cf7eaec5877c6035
***************
*** 539,544 ****
--- 539,545 ----
  					pltcl_build_tuple_argument(&tmptup, tupdesc, &list_tmp);
  					Tcl_DStringAppendElement(&tcl_cmd,
  											 Tcl_DStringValue(&list_tmp));
+ 					DecrTupleDescRefCount(tupdesc);
  				}
  			}
  			else
---------------------------(end of broadcast)---------------------------
TIP 3: Have you checked our extensive FAQ?

               http://www.postgresql.org/docs/faq

Reply via email to