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