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