Attached is a draft patch for creating statement-level temporary memory
contexts in plpgsql. It creates such contexts only as needed, and there
are a lot of simpler plpgsql statements that don't need them, so I think
the performance impact should be pretty minimal --- though I've not tried
to measure it, since I don't have a credible benchmark for overall plpgsql
performance.
This fixes the originally complained-of leak and several others besides,
including at least one path that leaked function-lifespan memory even
without an error :-(.
In addition to the patch proper, I attach for amusement's sake some
additional hacking I did for testing purposes, to make sure I'd found and
accounted for all the places that were allocating memory in the SPI Proc
context. There's a glibc-dependent hack in aset.c that reports any
plpgsql-driven palloc or pfree against a context named "SPI Proc", as
well as changes in pl_comp.c so that transient junk created during initial
parsing of a plpgsql function body doesn't end up in the SPI Proc context.
(I did that just to cut the amount of noise I had to chase down. I suppose
in large functions it might be worth adopting such a change so that that
junk can be released immediately after parsing; but I suspect for most
functions it'd just be an extra context without much gain.)
Although this is in principle a bug fix, it's invasive enough that I'd
be pretty hesitant to back-patch it, or even to stick it into HEAD
post-beta. I'm inclined to sign it up for the next commitfest instead.
regards, tom lane
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 586ff1f..e23ca96 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** typedef struct
*** 48,54 ****
Oid *types; /* types of arguments */
Datum *values; /* evaluated argument values */
char *nulls; /* null markers (' '/'n' style) */
- bool *freevals; /* which arguments are pfree-able */
} PreparedParamsData;
/*
--- 48,53 ----
*************** static EState *shared_simple_eval_estate
*** 88,93 ****
--- 87,122 ----
static SimpleEcontextStackEntry *simple_econtext_stack = NULL;
/*
+ * Memory management within a plpgsql function generally works with three
+ * contexts:
+ *
+ * 1. Function-call-lifespan data, such as variable values, is kept in the
+ * "main" context, a/k/a the "SPI Proc" context established by SPI_connect().
+ * This is usually the CurrentMemoryContext while running code in this module
+ * (which is not good, because careless coding can easily cause
+ * function-lifespan memory leaks, but we live with it for now).
+ *
+ * 2. Some statement-execution routines need statement-lifespan workspace.
+ * A suitable context is created on-demand by get_stmt_mcontext(), and must
+ * be reset at the end of the requesting routine. Error recovery will clean
+ * it up automatically. Nested statements requiring statement-lifespan
+ * workspace will result in a stack of such contexts, see push_stmt_mcontext().
+ *
+ * 3. We use the eval_econtext's per-tuple memory context for expression
+ * evaluation, and as a general-purpose workspace for short-lived allocations.
+ * Such allocations usually aren't explicitly freed, but are left to be
+ * cleaned up by a context reset, typically done by exec_eval_cleanup().
+ *
+ * These macros are for use in making short-lived allocations:
+ */
+ #define get_eval_mcontext(estate) \
+ ((estate)->eval_econtext->ecxt_per_tuple_memory)
+ #define eval_mcontext_alloc(estate, sz) \
+ MemoryContextAlloc(get_eval_mcontext(estate), sz)
+ #define eval_mcontext_alloc0(estate, sz) \
+ MemoryContextAllocZero(get_eval_mcontext(estate), sz)
+
+ /*
* We use a session-wide hash table for caching cast information.
*
* Once built, the compiled expression trees (cast_expr fields) survive for
*************** static HTAB *shared_cast_hash = NULL;
*** 128,133 ****
--- 157,165 ----
************************************************************/
static void plpgsql_exec_error_callback(void *arg);
static PLpgSQL_datum *copy_plpgsql_datum(PLpgSQL_datum *datum);
+ static MemoryContext get_stmt_mcontext(PLpgSQL_execstate *estate);
+ static void push_stmt_mcontext(PLpgSQL_execstate *estate);
+ static void pop_stmt_mcontext(PLpgSQL_execstate *estate);
static int exec_stmt_block(PLpgSQL_execstate *estate,
PLpgSQL_stmt_block *block);
*************** static void exec_eval_cleanup(PLpgSQL_ex
*** 191,197 ****
static void exec_prepare_plan(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, int cursorOptions);
static bool exec_simple_check_node(Node *node);
! static void exec_simple_check_plan(PLpgSQL_expr *expr);
static void exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan);
static void exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno);
static bool contains_target_param(Node *node, int *target_dno);
--- 223,229 ----
static void exec_prepare_plan(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, int cursorOptions);
static bool exec_simple_check_node(Node *node);
! static void exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
static void exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan);
static void exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno);
static bool contains_target_param(Node *node, int *target_dno);
*************** static void assign_text_var(PLpgSQL_exec
*** 271,281 ****
const char *str);
static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate,
List *params);
- static void free_params_data(PreparedParamsData *ppd);
static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
PLpgSQL_expr *dynquery, List *params,
const char *portalname, int cursorOptions);
-
static char *format_expr_params(PLpgSQL_execstate *estate,
const PLpgSQL_expr *expr);
static char *format_preparedparamsdata(PLpgSQL_execstate *estate,
--- 303,311 ----
*************** plpgsql_exec_function(PLpgSQL_function *
*** 562,567 ****
--- 592,598 ----
/* Clean up any leftover temporary memory */
plpgsql_destroy_econtext(&estate);
exec_eval_cleanup(&estate);
+ /* stmt_mcontext will be destroyed when function's main context is */
/*
* Pop the error context stack
*************** plpgsql_exec_trigger(PLpgSQL_function *f
*** 832,837 ****
--- 863,869 ----
/* Clean up any leftover temporary memory */
plpgsql_destroy_econtext(&estate);
exec_eval_cleanup(&estate);
+ /* stmt_mcontext will be destroyed when function's main context is */
/*
* Pop the error context stack
*************** plpgsql_exec_trigger(PLpgSQL_function *f
*** 844,849 ****
--- 876,886 ----
return rettup;
}
+ /* ----------
+ * plpgsql_exec_event_trigger Called by the call handler for
+ * event trigger execution.
+ * ----------
+ */
void
plpgsql_exec_event_trigger(PLpgSQL_function *func, EventTriggerData *trigdata)
{
*************** plpgsql_exec_event_trigger(PLpgSQL_funct
*** 915,920 ****
--- 952,958 ----
/* Clean up any leftover temporary memory */
plpgsql_destroy_econtext(&estate);
exec_eval_cleanup(&estate);
+ /* stmt_mcontext will be destroyed when function's main context is */
/*
* Pop the error context stack
*************** copy_plpgsql_datum(PLpgSQL_datum *datum)
*** 1041,1047 ****
--- 1079,1142 ----
return result;
}
+ /*
+ * Create a memory context for statement-lifespan variables, if we don't
+ * have one already. It will be a child of stmt_mcontext_parent, which is
+ * either the function's main context or a pushed-down outer stmt_mcontext.
+ */
+ static MemoryContext
+ get_stmt_mcontext(PLpgSQL_execstate *estate)
+ {
+ if (estate->stmt_mcontext == NULL)
+ {
+ estate->stmt_mcontext =
+ AllocSetContextCreate(estate->stmt_mcontext_parent,
+ "PLpgSQL per-statement data",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+ }
+ return estate->stmt_mcontext;
+ }
+
+ /*
+ * Push down the current stmt_mcontext so that called statements won't use it.
+ * This is needed by statements that have statement-lifespan data and need to
+ * preserve it across some inner statements. The caller should eventually do
+ * pop_stmt_mcontext().
+ */
+ static void
+ push_stmt_mcontext(PLpgSQL_execstate *estate)
+ {
+ /* Should have done get_stmt_mcontext() first */
+ Assert(estate->stmt_mcontext != NULL);
+ /* Assert we've not messed up the stack linkage */
+ Assert(MemoryContextGetParent(estate->stmt_mcontext) == estate->stmt_mcontext_parent);
+ /* Push it down to become the parent of any nested stmt mcontext */
+ estate->stmt_mcontext_parent = estate->stmt_mcontext;
+ /* And make it not available for use directly */
+ estate->stmt_mcontext = NULL;
+ }
+
+ /*
+ * Undo push_stmt_mcontext(). We assume this is done just before or after
+ * resetting the caller's stmt_mcontext; since that action will also delete
+ * any child contexts, there's no need to explicitly delete whatever context
+ * might currently be estate->stmt_mcontext.
+ */
+ static void
+ pop_stmt_mcontext(PLpgSQL_execstate *estate)
+ {
+ /* We need only pop the stack */
+ estate->stmt_mcontext = estate->stmt_mcontext_parent;
+ estate->stmt_mcontext_parent = MemoryContextGetParent(estate->stmt_mcontext);
+ }
+
+ /*
+ * Subroutine for exec_stmt_block: does any condition in the condition list
+ * match the current exception?
+ */
static bool
exception_matches_conditions(ErrorData *edata, PLpgSQL_condition *cond)
{
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1174,1182 ****
--- 1269,1289 ----
ResourceOwner oldowner = CurrentResourceOwner;
ExprContext *old_eval_econtext = estate->eval_econtext;
ErrorData *save_cur_error = estate->cur_error;
+ MemoryContext stmt_mcontext;
estate->err_text = gettext_noop("during statement block entry");
+ /*
+ * We will need a stmt_mcontext to hold the error data if an error
+ * occurs. It seems best to force it to exist before entering the
+ * subtransaction, so that we reduce the risk of out-of-memory during
+ * error recovery, and because this greatly simplifies restoring the
+ * stmt_mcontext stack to the correct state after an error. We can
+ * ameliorate the cost of this by allowing the called statements to
+ * use this mcontext too; so we don't push it down here.
+ */
+ stmt_mcontext = get_stmt_mcontext(estate);
+
BeginInternalSubTransaction(NULL);
/* Want to run statements inside function's memory context */
MemoryContextSwitchTo(oldcontext);
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1202,1208 ****
* If the block ended with RETURN, we may need to copy the return
* value out of the subtransaction eval_context. This is
* currently only needed for scalar result types --- rowtype
! * values will always exist in the function's own memory context.
*/
if (rc == PLPGSQL_RC_RETURN &&
!estate->retisset &&
--- 1309,1317 ----
* If the block ended with RETURN, we may need to copy the return
* value out of the subtransaction eval_context. This is
* currently only needed for scalar result types --- rowtype
! * values will always exist in the function's main memory context,
! * cf. exec_stmt_return(). We can avoid a physical copy if the
! * value happens to be a R/W expanded object.
*/
if (rc == PLPGSQL_RC_RETURN &&
!estate->retisset &&
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1213,1220 ****
bool resTypByVal;
get_typlenbyval(estate->rettype, &resTypLen, &resTypByVal);
! estate->retval = datumCopy(estate->retval,
! resTypByVal, resTypLen);
}
/* Commit the inner transaction, return to outer xact context */
--- 1322,1329 ----
bool resTypByVal;
get_typlenbyval(estate->rettype, &resTypLen, &resTypByVal);
! estate->retval = datumTransfer(estate->retval,
! resTypByVal, resTypLen);
}
/* Commit the inner transaction, return to outer xact context */
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1222,1227 ****
--- 1331,1339 ----
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
+ /* Assert that the stmt_mcontext stack is unchanged */
+ Assert(stmt_mcontext == estate->stmt_mcontext);
+
/*
* Revert to outer eval_econtext. (The inner one was
* automatically cleaned up during subxact exit.)
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1241,1248 ****
estate->err_text = gettext_noop("during exception cleanup");
! /* Save error info */
! MemoryContextSwitchTo(oldcontext);
edata = CopyErrorData();
FlushErrorState();
--- 1353,1360 ----
estate->err_text = gettext_noop("during exception cleanup");
! /* Save error info in our stmt_mcontext */
! MemoryContextSwitchTo(stmt_mcontext);
edata = CopyErrorData();
FlushErrorState();
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1251,1256 ****
--- 1363,1388 ----
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
+ /*
+ * Set up the stmt_mcontext stack as though we had restored our
+ * previous state and then done push_stmt_mcontext(). The push is
+ * needed so that statements in the exception handler won't
+ * clobber the error data that's in our stmt_mcontext.
+ */
+ estate->stmt_mcontext_parent = stmt_mcontext;
+ estate->stmt_mcontext = NULL;
+
+ /*
+ * Now we can delete any nested stmt_mcontexts that might have
+ * been created as children of ours. (Note: we do not immediately
+ * release any statement-lifespan data that might have been left
+ * behind in stmt_mcontext itself. We could attempt that by doing
+ * a MemoryContextReset on it before collecting the error data
+ * above, but it seems too risky to do any significant amount of
+ * work before collecting the error.)
+ */
+ MemoryContextDeleteChildren(stmt_mcontext);
+
/* Revert to outer eval_econtext */
estate->eval_econtext = old_eval_econtext;
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1319,1326 ****
/* If no match found, re-throw the error */
if (e == NULL)
ReThrowError(edata);
! else
! FreeErrorData(edata);
}
PG_END_TRY();
--- 1451,1460 ----
/* If no match found, re-throw the error */
if (e == NULL)
ReThrowError(edata);
!
! /* Restore stmt_mcontext stack and release the error data */
! pop_stmt_mcontext(estate);
! MemoryContextReset(stmt_mcontext);
}
PG_END_TRY();
*************** exec_stmt_getdiag(PLpgSQL_execstate *est
*** 1663,1673 ****
case PLPGSQL_GETDIAG_CONTEXT:
{
! char *contextstackstr = GetErrorContextStack();
! exec_assign_c_string(estate, var, contextstackstr);
! pfree(contextstackstr);
}
break;
--- 1797,1811 ----
case PLPGSQL_GETDIAG_CONTEXT:
{
! char *contextstackstr;
! MemoryContext oldcontext;
! /* Use eval_mcontext for short-lived string */
! oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
! contextstackstr = GetErrorContextStack();
! MemoryContextSwitchTo(oldcontext);
! exec_assign_c_string(estate, var, contextstackstr);
}
break;
*************** exec_stmt_getdiag(PLpgSQL_execstate *est
*** 1677,1682 ****
--- 1815,1822 ----
}
}
+ exec_eval_cleanup(estate);
+
return PLPGSQL_RC_OK;
}
*************** exec_stmt_case(PLpgSQL_execstate *estate
*** 1738,1744 ****
/*
* When expected datatype is different from real, change it. Note that
* what we're modifying here is an execution copy of the datum, so
! * this doesn't affect the originally stored function parse tree.
*/
if (t_var->datatype->typoid != t_typoid ||
t_var->datatype->atttypmod != t_typmod)
--- 1878,1887 ----
/*
* When expected datatype is different from real, change it. Note that
* what we're modifying here is an execution copy of the datum, so
! * this doesn't affect the originally stored function parse tree. (In
! * theory, if the expression datatype keeps changing during execution,
! * this could cause a function-lifespan memory leak. Doesn't seem
! * worth worrying about though.)
*/
if (t_var->datatype->typoid != t_typoid ||
t_var->datatype->atttypmod != t_typmod)
*************** static int
*** 2132,2137 ****
--- 2275,2281 ----
exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
{
PLpgSQL_var *curvar;
+ MemoryContext stmt_mcontext = NULL;
char *curname = NULL;
PLpgSQL_expr *query;
ParamListInfo paramLI;
*************** exec_stmt_forc(PLpgSQL_execstate *estate
*** 2146,2152 ****
--- 2290,2303 ----
curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
if (!curvar->isnull)
{
+ MemoryContext oldcontext;
+
+ /* We only need stmt_mcontext to hold the cursor name string */
+ stmt_mcontext = get_stmt_mcontext(estate);
+ oldcontext = MemoryContextSwitchTo(stmt_mcontext);
curname = TextDatumGetCString(curvar->value);
+ MemoryContextSwitchTo(oldcontext);
+
if (SPI_cursor_find(curname) != NULL)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_CURSOR),
*************** exec_stmt_forc(PLpgSQL_execstate *estate
*** 2216,2225 ****
elog(ERROR, "could not open cursor: %s",
SPI_result_code_string(SPI_result));
- /* don't need paramlist any more */
- if (paramLI)
- pfree(paramLI);
-
/*
* If cursor variable was NULL, store the generated portal name in it
*/
--- 2367,2372 ----
*************** exec_stmt_forc(PLpgSQL_execstate *estate
*** 2227,2232 ****
--- 2374,2386 ----
assign_text_var(estate, curvar, portal->name);
/*
+ * Clean up before entering exec_for_query
+ */
+ exec_eval_cleanup(estate);
+ if (stmt_mcontext)
+ MemoryContextReset(stmt_mcontext);
+
+ /*
* Execute the loop. We can't prefetch because the cursor is accessible
* to the user, for instance via UPDATE WHERE CURRENT OF within the loop.
*/
*************** exec_stmt_forc(PLpgSQL_execstate *estate
*** 2241,2249 ****
if (curname == NULL)
assign_simple_var(estate, curvar, (Datum) 0, true, false);
- if (curname)
- pfree(curname);
-
return rc;
}
--- 2395,2400 ----
*************** exec_stmt_foreach_a(PLpgSQL_execstate *e
*** 2266,2271 ****
--- 2417,2424 ----
Oid loop_var_elem_type;
bool found = false;
int rc = PLPGSQL_RC_OK;
+ MemoryContext stmt_mcontext;
+ MemoryContext oldcontext;
ArrayIterator array_iterator;
Oid iterator_result_type;
int32 iterator_result_typmod;
*************** exec_stmt_foreach_a(PLpgSQL_execstate *e
*** 2279,2284 ****
--- 2432,2446 ----
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("FOREACH expression must not be null")));
+ /*
+ * Do as much as possible of the code below in stmt_mcontext, to avoid any
+ * leaks from called subroutines. We need a private stmt_mcontext since
+ * we'll be calling arbitrary statement code.
+ */
+ stmt_mcontext = get_stmt_mcontext(estate);
+ push_stmt_mcontext(estate);
+ oldcontext = MemoryContextSwitchTo(stmt_mcontext);
+
/* check the type of the expression - must be an array */
if (!OidIsValid(get_element_type(arrtype)))
ereport(ERROR,
*************** exec_stmt_foreach_a(PLpgSQL_execstate *e
*** 2287,2295 ****
format_type_be(arrtype))));
/*
! * We must copy the array, else it will disappear in exec_eval_cleanup.
! * This is annoying, but cleanup will certainly happen while running the
! * loop body, so we have little choice.
*/
arr = DatumGetArrayTypePCopy(value);
--- 2449,2457 ----
format_type_be(arrtype))));
/*
! * We must copy the array into stmt_mcontext, else it will disappear in
! * exec_eval_cleanup. This is annoying, but cleanup will certainly happen
! * while running the loop body, so we have little choice.
*/
arr = DatumGetArrayTypePCopy(value);
*************** exec_stmt_foreach_a(PLpgSQL_execstate *e
*** 2355,2360 ****
--- 2517,2525 ----
{
found = true; /* looped at least once */
+ /* exec_assign_value and exec_stmts must run in the main context */
+ MemoryContextSwitchTo(oldcontext);
+
/* Assign current element/slice to the loop variable */
exec_assign_value(estate, loop_var, value, isnull,
iterator_result_type, iterator_result_typmod);
*************** exec_stmt_foreach_a(PLpgSQL_execstate *e
*** 2413,2423 ****
break;
}
}
}
/* Release temporary memory, including the array value */
! array_free_iterator(array_iterator);
! pfree(arr);
/*
* Set the FOUND variable to indicate the result of executing the loop
--- 2578,2593 ----
break;
}
}
+
+ MemoryContextSwitchTo(stmt_mcontext);
}
+ /* Restore memory context state */
+ MemoryContextSwitchTo(oldcontext);
+ pop_stmt_mcontext(estate);
+
/* Release temporary memory, including the array value */
! MemoryContextReset(stmt_mcontext);
/*
* Set the FOUND variable to indicate the result of executing the loop
*************** exec_stmt_exit(PLpgSQL_execstate *estate
*** 2465,2470 ****
--- 2635,2647 ----
/* ----------
* exec_stmt_return Evaluate an expression and start
* returning from the function.
+ *
+ * Note: in the retistuple code paths, the returned tuple is always in the
+ * function's main context, whereas for non-tuple data types the result may
+ * be in the eval_mcontext. The former case is not a memory leak since we're
+ * about to exit the function anyway. (If you want to change it, note that
+ * exec_stmt_block() knows about this behavior.) The latter case means that
+ * we must not do exec_eval_cleanup while unwinding the control stack.
* ----------
*/
static int
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2639,2646 ****
{
TupleDesc tupdesc;
int natts;
! HeapTuple tuple = NULL;
! bool free_tuple = false;
if (!estate->retisset)
ereport(ERROR,
--- 2816,2823 ----
{
TupleDesc tupdesc;
int natts;
! HeapTuple tuple;
! MemoryContext oldcontext;
if (!estate->retisset)
ereport(ERROR,
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2712,2728 ****
rec->refname),
errdetail("The tuple structure of a not-yet-assigned"
" record is indeterminate.")));
tupmap = convert_tuples_by_position(rec->tupdesc,
tupdesc,
gettext_noop("wrong record type supplied in RETURN NEXT"));
tuple = rec->tup;
- /* it might need conversion */
if (tupmap)
- {
tuple = do_convert_tuple(tuple, tupmap);
! free_conversion_map(tupmap);
! free_tuple = true;
! }
}
break;
--- 2889,2905 ----
rec->refname),
errdetail("The tuple structure of a not-yet-assigned"
" record is indeterminate.")));
+
+ /* Use eval_mcontext for tuple conversion work */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
tupmap = convert_tuples_by_position(rec->tupdesc,
tupdesc,
gettext_noop("wrong record type supplied in RETURN NEXT"));
tuple = rec->tup;
if (tupmap)
tuple = do_convert_tuple(tuple, tupmap);
! tuplestore_puttuple(estate->tuple_store, tuple);
! MemoryContextSwitchTo(oldcontext);
}
break;
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2730,2741 ****
{
PLpgSQL_row *row = (PLpgSQL_row *) retvar;
tuple = make_tuple_from_row(estate, row, tupdesc);
if (tuple == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("wrong record type supplied in RETURN NEXT")));
! free_tuple = true;
}
break;
--- 2907,2921 ----
{
PLpgSQL_row *row = (PLpgSQL_row *) retvar;
+ /* Use eval_mcontext for tuple conversion work */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
tuple = make_tuple_from_row(estate, row, tupdesc);
if (tuple == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("wrong record type supplied in RETURN NEXT")));
! tuplestore_puttuple(estate->tuple_store, tuple);
! MemoryContextSwitchTo(oldcontext);
}
break;
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2770,2793 ****
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot return non-composite value from function returning composite type")));
tuple = get_tuple_from_datum(retval);
- free_tuple = true; /* tuple is always freshly palloc'd */
-
- /* it might need conversion */
retvaldesc = get_tupdesc_from_datum(retval);
tupmap = convert_tuples_by_position(retvaldesc, tupdesc,
gettext_noop("returned record type does not match expected record type"));
if (tupmap)
! {
! HeapTuple newtuple;
!
! newtuple = do_convert_tuple(tuple, tupmap);
! free_conversion_map(tupmap);
! heap_freetuple(tuple);
! tuple = newtuple;
! }
ReleaseTupleDesc(retvaldesc);
! /* tuple will be stored into tuplestore below */
}
else
{
--- 2950,2966 ----
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot return non-composite value from function returning composite type")));
+ /* Use eval_mcontext for tuple conversion work */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
tuple = get_tuple_from_datum(retval);
retvaldesc = get_tupdesc_from_datum(retval);
tupmap = convert_tuples_by_position(retvaldesc, tupdesc,
gettext_noop("returned record type does not match expected record type"));
if (tupmap)
! tuple = do_convert_tuple(tuple, tupmap);
! tuplestore_puttuple(estate->tuple_store, tuple);
ReleaseTupleDesc(retvaldesc);
! MemoryContextSwitchTo(oldcontext);
}
else
{
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2795,2807 ****
Datum *nulldatums;
bool *nullflags;
! nulldatums = (Datum *) palloc0(natts * sizeof(Datum));
! nullflags = (bool *) palloc(natts * sizeof(bool));
memset(nullflags, true, natts * sizeof(bool));
tuplestore_putvalues(estate->tuple_store, tupdesc,
nulldatums, nullflags);
- pfree(nulldatums);
- pfree(nullflags);
}
}
else
--- 2968,2980 ----
Datum *nulldatums;
bool *nullflags;
! nulldatums = (Datum *)
! eval_mcontext_alloc0(estate, natts * sizeof(Datum));
! nullflags = (bool *)
! eval_mcontext_alloc(estate, natts * sizeof(bool));
memset(nullflags, true, natts * sizeof(bool));
tuplestore_putvalues(estate->tuple_store, tupdesc,
nulldatums, nullflags);
}
}
else
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2832,2845 ****
errmsg("RETURN NEXT must have a parameter")));
}
- if (HeapTupleIsValid(tuple))
- {
- tuplestore_puttuple(estate->tuple_store, tuple);
-
- if (free_tuple)
- heap_freetuple(tuple);
- }
-
exec_eval_cleanup(estate);
return PLPGSQL_RC_OK;
--- 3005,3010 ----
*************** exec_stmt_return_query(PLpgSQL_execstate
*** 2858,2863 ****
--- 3023,3029 ----
Portal portal;
uint64 processed = 0;
TupleConversionMap *tupmap;
+ MemoryContext oldcontext;
if (!estate->retisset)
ereport(ERROR,
*************** exec_stmt_return_query(PLpgSQL_execstate
*** 2881,2886 ****
--- 3047,3055 ----
CURSOR_OPT_PARALLEL_OK);
}
+ /* Use eval_mcontext for tuple conversion work */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+
tupmap = convert_tuples_by_position(portal->tupDesc,
estate->rettupdesc,
gettext_noop("structure of query does not match function result type"));
*************** exec_stmt_return_query(PLpgSQL_execstate
*** 2890,2895 ****
--- 3059,3068 ----
uint64 i;
SPI_cursor_fetch(portal, true, 50);
+
+ /* SPI will have reset CurrentMemoryContext */
+ MemoryContextSwitchTo(get_eval_mcontext(estate));
+
if (SPI_processed == 0)
break;
*************** exec_stmt_return_query(PLpgSQL_execstate
*** 2908,2919 ****
SPI_freetuptable(SPI_tuptable);
}
- if (tupmap)
- free_conversion_map(tupmap);
-
SPI_freetuptable(SPI_tuptable);
SPI_cursor_close(portal);
estate->eval_processed = processed;
exec_set_found(estate, processed != 0);
--- 3081,3092 ----
SPI_freetuptable(SPI_tuptable);
}
SPI_freetuptable(SPI_tuptable);
SPI_cursor_close(portal);
+ MemoryContextSwitchTo(oldcontext);
+ exec_eval_cleanup(estate);
+
estate->eval_processed = processed;
exec_set_found(estate, processed != 0);
*************** do { \
*** 2965,2971 ****
(errcode(ERRCODE_SYNTAX_ERROR), \
errmsg("RAISE option already specified: %s", \
name))); \
! opt = pstrdup(extval); \
} while (0)
/* ----------
--- 3138,3144 ----
(errcode(ERRCODE_SYNTAX_ERROR), \
errmsg("RAISE option already specified: %s", \
name))); \
! opt = MemoryContextStrdup(stmt_mcontext, extval); \
} while (0)
/* ----------
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 2985,2990 ****
--- 3158,3164 ----
char *err_datatype = NULL;
char *err_table = NULL;
char *err_schema = NULL;
+ MemoryContext stmt_mcontext;
ListCell *lc;
/* RAISE with no parameters: re-throw current exception */
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 2999,3008 ****
errmsg("RAISE without parameters cannot be used outside an exception handler")));
}
if (stmt->condname)
{
err_code = plpgsql_recognize_err_condition(stmt->condname, true);
! condname = pstrdup(stmt->condname);
}
if (stmt->message)
--- 3173,3185 ----
errmsg("RAISE without parameters cannot be used outside an exception handler")));
}
+ /* We'll need to accumulate the various strings in stmt_mcontext */
+ stmt_mcontext = get_stmt_mcontext(estate);
+
if (stmt->condname)
{
err_code = plpgsql_recognize_err_condition(stmt->condname, true);
! condname = MemoryContextStrdup(stmt_mcontext, stmt->condname);
}
if (stmt->message)
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 3010,3017 ****
--- 3187,3199 ----
StringInfoData ds;
ListCell *current_param;
char *cp;
+ MemoryContext oldcontext;
+ /* build string in stmt_mcontext */
+ oldcontext = MemoryContextSwitchTo(stmt_mcontext);
initStringInfo(&ds);
+ MemoryContextSwitchTo(oldcontext);
+
current_param = list_head(stmt->params);
for (cp = stmt->message; *cp; cp++)
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 3064,3070 ****
elog(ERROR, "unexpected RAISE parameter list length");
err_message = ds.data;
- /* No pfree(ds.data), the pfree(err_message) does it */
}
foreach(lc, stmt->options)
--- 3246,3251 ----
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 3096,3102 ****
errmsg("RAISE option already specified: %s",
"ERRCODE")));
err_code = plpgsql_recognize_err_condition(extval, true);
! condname = pstrdup(extval);
break;
case PLPGSQL_RAISEOPTION_MESSAGE:
SET_RAISE_OPTION_TEXT(err_message, "MESSAGE");
--- 3277,3283 ----
errmsg("RAISE option already specified: %s",
"ERRCODE")));
err_code = plpgsql_recognize_err_condition(extval, true);
! condname = MemoryContextStrdup(stmt_mcontext, extval);
break;
case PLPGSQL_RAISEOPTION_MESSAGE:
SET_RAISE_OPTION_TEXT(err_message, "MESSAGE");
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 3142,3148 ****
condname = NULL;
}
else
! err_message = pstrdup(unpack_sql_state(err_code));
}
/*
--- 3323,3330 ----
condname = NULL;
}
else
! err_message = MemoryContextStrdup(stmt_mcontext,
! unpack_sql_state(err_code));
}
/*
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 3164,3187 ****
(err_schema != NULL) ?
err_generic_string(PG_DIAG_SCHEMA_NAME, err_schema) : 0));
! if (condname != NULL)
! pfree(condname);
! if (err_message != NULL)
! pfree(err_message);
! if (err_detail != NULL)
! pfree(err_detail);
! if (err_hint != NULL)
! pfree(err_hint);
! if (err_column != NULL)
! pfree(err_column);
! if (err_constraint != NULL)
! pfree(err_constraint);
! if (err_datatype != NULL)
! pfree(err_datatype);
! if (err_table != NULL)
! pfree(err_table);
! if (err_schema != NULL)
! pfree(err_schema);
return PLPGSQL_RC_OK;
}
--- 3346,3353 ----
(err_schema != NULL) ?
err_generic_string(PG_DIAG_SCHEMA_NAME, err_schema) : 0));
! /* Clean up transient strings */
! MemoryContextReset(stmt_mcontext);
return PLPGSQL_RC_OK;
}
*************** plpgsql_estate_setup(PLpgSQL_execstate *
*** 3329,3334 ****
--- 3495,3508 ----
estate->cast_hash_context = shared_cast_context;
}
+ /*
+ * We start with no stmt_mcontext; one will be created only if needed.
+ * That context will be a direct child of the function's main execution
+ * context. Additional stmt_mcontexts might be created as children of it.
+ */
+ estate->stmt_mcontext = NULL;
+ estate->stmt_mcontext_parent = CurrentMemoryContext;
+
estate->eval_tuptable = NULL;
estate->eval_processed = 0;
estate->eval_lastoid = InvalidOid;
*************** exec_eval_cleanup(PLpgSQL_execstate *est
*** 3378,3384 ****
SPI_freetuptable(estate->eval_tuptable);
estate->eval_tuptable = NULL;
! /* Clear result of exec_eval_simple_expr (but keep the econtext) */
if (estate->eval_econtext != NULL)
ResetExprContext(estate->eval_econtext);
}
--- 3552,3561 ----
SPI_freetuptable(estate->eval_tuptable);
estate->eval_tuptable = NULL;
! /*
! * Clear result of exec_eval_simple_expr (but keep the econtext). This
! * also clears any short-lived allocations done via get_eval_mcontext.
! */
if (estate->eval_econtext != NULL)
ResetExprContext(estate->eval_econtext);
}
*************** exec_prepare_plan(PLpgSQL_execstate *est
*** 3430,3436 ****
expr->plan = plan;
/* Check to see if it's a simple expression */
! exec_simple_check_plan(expr);
/*
* Mark expression as not using a read-write param. exec_assign_value has
--- 3607,3613 ----
expr->plan = plan;
/* Check to see if it's a simple expression */
! exec_simple_check_plan(estate, expr);
/*
* Mark expression as not using a read-write param. exec_assign_value has
*************** exec_prepare_plan(PLpgSQL_execstate *est
*** 3443,3448 ****
--- 3620,3628 ----
/* ----------
* exec_stmt_execsql Execute an SQL statement (possibly with INTO).
+ *
+ * Note: some callers rely on this not touching stmt_mcontext. If it ever
+ * needs to use that, fix those callers to push/pop stmt_mcontext.
* ----------
*/
static int
*************** exec_stmt_dynexecute(PLpgSQL_execstate *
*** 3675,3680 ****
--- 3855,3861 ----
char *querystr;
int exec_res;
PreparedParamsData *ppd = NULL;
+ MemoryContext stmt_mcontext = get_stmt_mcontext(estate);
/*
* First we evaluate the string expression after the EXECUTE keyword. Its
*************** exec_stmt_dynexecute(PLpgSQL_execstate *
*** 3689,3696 ****
/* Get the C-String representation */
querystr = convert_value_to_string(estate, query, restype);
! /* copy it out of the temporary context before we clean up */
! querystr = pstrdup(querystr);
exec_eval_cleanup(estate);
--- 3870,3877 ----
/* Get the C-String representation */
querystr = convert_value_to_string(estate, query, restype);
! /* copy it into the stmt_mcontext before we clean up */
! querystr = MemoryContextStrdup(stmt_mcontext, querystr);
exec_eval_cleanup(estate);
*************** exec_stmt_dynexecute(PLpgSQL_execstate *
*** 3843,3854 ****
*/
}
! if (ppd)
! free_params_data(ppd);
!
! /* Release any result from SPI_execute, as well as the querystring */
SPI_freetuptable(SPI_tuptable);
! pfree(querystr);
return PLPGSQL_RC_OK;
}
--- 4024,4032 ----
*/
}
! /* Release any result from SPI_execute, as well as transient data */
SPI_freetuptable(SPI_tuptable);
! MemoryContextReset(stmt_mcontext);
return PLPGSQL_RC_OK;
}
*************** static int
*** 3892,3897 ****
--- 4070,4076 ----
exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
{
PLpgSQL_var *curvar;
+ MemoryContext stmt_mcontext = NULL;
char *curname = NULL;
PLpgSQL_expr *query;
Portal portal;
*************** exec_stmt_open(PLpgSQL_execstate *estate
*** 3905,3911 ****
--- 4084,4097 ----
curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
if (!curvar->isnull)
{
+ MemoryContext oldcontext;
+
+ /* We only need stmt_mcontext to hold the cursor name string */
+ stmt_mcontext = get_stmt_mcontext(estate);
+ oldcontext = MemoryContextSwitchTo(stmt_mcontext);
curname = TextDatumGetCString(curvar->value);
+ MemoryContextSwitchTo(oldcontext);
+
if (SPI_cursor_find(curname) != NULL)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_CURSOR),
*************** exec_stmt_open(PLpgSQL_execstate *estate
*** 3942,3948 ****
stmt->cursor_options);
/*
! * If cursor variable was NULL, store the generated portal name in it
*/
if (curname == NULL)
assign_text_var(estate, curvar, portal->name);
--- 4128,4137 ----
stmt->cursor_options);
/*
! * If cursor variable was NULL, store the generated portal name in it.
! * Note: exec_dynquery_with_params already reset the stmt_mcontext, so
! * curname is a dangling pointer here; but testing it for nullness is
! * OK.
*/
if (curname == NULL)
assign_text_var(estate, curvar, portal->name);
*************** exec_stmt_open(PLpgSQL_execstate *estate
*** 4019,4028 ****
if (curname == NULL)
assign_text_var(estate, curvar, portal->name);
! if (curname)
! pfree(curname);
! if (paramLI)
! pfree(paramLI);
return PLPGSQL_RC_OK;
}
--- 4208,4217 ----
if (curname == NULL)
assign_text_var(estate, curvar, portal->name);
! /* If we had any transient data, clean it up */
! exec_eval_cleanup(estate);
! if (stmt_mcontext)
! MemoryContextReset(stmt_mcontext);
return PLPGSQL_RC_OK;
}
*************** exec_stmt_open(PLpgSQL_execstate *estate
*** 4036,4042 ****
static int
exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
{
! PLpgSQL_var *curvar = NULL;
PLpgSQL_rec *rec = NULL;
PLpgSQL_row *row = NULL;
long how_many = stmt->how_many;
--- 4225,4231 ----
static int
exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
{
! PLpgSQL_var *curvar;
PLpgSQL_rec *rec = NULL;
PLpgSQL_row *row = NULL;
long how_many = stmt->how_many;
*************** exec_stmt_fetch(PLpgSQL_execstate *estat
*** 4044,4049 ****
--- 4233,4239 ----
Portal portal;
char *curname;
uint64 n;
+ MemoryContext oldcontext;
/* ----------
* Get the portal of the cursor by name
*************** exec_stmt_fetch(PLpgSQL_execstate *estat
*** 4054,4067 ****
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("cursor variable \"%s\" is null", curvar->refname)));
curname = TextDatumGetCString(curvar->value);
portal = SPI_cursor_find(curname);
if (portal == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_CURSOR),
errmsg("cursor \"%s\" does not exist", curname)));
- pfree(curname);
/* Calculate position for FETCH_RELATIVE or FETCH_ABSOLUTE */
if (stmt->expr)
--- 4244,4260 ----
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("cursor variable \"%s\" is null", curvar->refname)));
+
+ /* Use eval_mcontext for short-lived string */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
curname = TextDatumGetCString(curvar->value);
+ MemoryContextSwitchTo(oldcontext);
portal = SPI_cursor_find(curname);
if (portal == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_CURSOR),
errmsg("cursor \"%s\" does not exist", curname)));
/* Calculate position for FETCH_RELATIVE or FETCH_ABSOLUTE */
if (stmt->expr)
*************** exec_stmt_fetch(PLpgSQL_execstate *estat
*** 4133,4141 ****
static int
exec_stmt_close(PLpgSQL_execstate *estate, PLpgSQL_stmt_close *stmt)
{
! PLpgSQL_var *curvar = NULL;
Portal portal;
char *curname;
/* ----------
* Get the portal of the cursor by name
--- 4326,4335 ----
static int
exec_stmt_close(PLpgSQL_execstate *estate, PLpgSQL_stmt_close *stmt)
{
! PLpgSQL_var *curvar;
Portal portal;
char *curname;
+ MemoryContext oldcontext;
/* ----------
* Get the portal of the cursor by name
*************** exec_stmt_close(PLpgSQL_execstate *estat
*** 4146,4159 ****
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("cursor variable \"%s\" is null", curvar->refname)));
curname = TextDatumGetCString(curvar->value);
portal = SPI_cursor_find(curname);
if (portal == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_CURSOR),
errmsg("cursor \"%s\" does not exist", curname)));
- pfree(curname);
/* ----------
* And close it.
--- 4340,4356 ----
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("cursor variable \"%s\" is null", curvar->refname)));
+
+ /* Use eval_mcontext for short-lived string */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
curname = TextDatumGetCString(curvar->value);
+ MemoryContextSwitchTo(oldcontext);
portal = SPI_cursor_find(curname);
if (portal == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_CURSOR),
errmsg("cursor \"%s\" does not exist", curname)));
/* ----------
* And close it.
*************** exec_assign_expr(PLpgSQL_execstate *esta
*** 4201,4206 ****
--- 4398,4406 ----
* exec_assign_c_string Put a C string into a text variable.
*
* We take a NULL pointer as signifying empty string, not SQL null.
+ *
+ * As with the underlying exec_assign_value, caller is expected to do
+ * exec_eval_cleanup later.
* ----------
*/
static void
*************** exec_assign_c_string(PLpgSQL_execstate *
*** 4208,4228 ****
const char *str)
{
text *value;
if (str != NULL)
value = cstring_to_text(str);
else
value = cstring_to_text("");
exec_assign_value(estate, target, PointerGetDatum(value), false,
TEXTOID, -1);
- pfree(value);
}
/* ----------
* exec_assign_value Put a value into a target datum
*
! * Note: in some code paths, this will leak memory in the eval_econtext;
* we assume that will be cleaned up later by exec_eval_cleanup. We cannot
* call exec_eval_cleanup here for fear of destroying the input Datum value.
* ----------
--- 4408,4432 ----
const char *str)
{
text *value;
+ MemoryContext oldcontext;
+ /* Use eval_mcontext for short-lived text value */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
if (str != NULL)
value = cstring_to_text(str);
else
value = cstring_to_text("");
+ MemoryContextSwitchTo(oldcontext);
+
exec_assign_value(estate, target, PointerGetDatum(value), false,
TEXTOID, -1);
}
/* ----------
* exec_assign_value Put a value into a target datum
*
! * Note: in some code paths, this will leak memory in the eval_mcontext;
* we assume that will be cleaned up later by exec_eval_cleanup. We cannot
* call exec_eval_cleanup here for fear of destroying the input Datum value.
* ----------
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4259,4268 ****
/*
* If type is by-reference, copy the new value (which is
! * probably in the eval_econtext) into the procedure's memory
! * context. But if it's a read/write reference to an expanded
! * object, no physical copy needs to happen; at most we need
! * to reparent the object's memory context.
*
* If it's an array, we force the value to be stored in R/W
* expanded form. This wins if the function later does, say,
--- 4463,4472 ----
/*
* If type is by-reference, copy the new value (which is
! * probably in the eval_mcontext) into the procedure's main
! * memory context. But if it's a read/write reference to an
! * expanded object, no physical copy needs to happen; at most
! * we need to reparent the object's memory context.
*
* If it's an array, we force the value to be stored in R/W
* expanded form. This wins if the function later does, say,
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4402,4410 ****
* the attributes except the one we want to replace, use the
* value that's in the old tuple.
*/
! values = palloc(sizeof(Datum) * natts);
! nulls = palloc(sizeof(bool) * natts);
! replaces = palloc(sizeof(bool) * natts);
memset(replaces, false, sizeof(bool) * natts);
replaces[fno] = true;
--- 4606,4614 ----
* the attributes except the one we want to replace, use the
* value that's in the old tuple.
*/
! values = eval_mcontext_alloc(estate, sizeof(Datum) * natts);
! nulls = eval_mcontext_alloc(estate, sizeof(bool) * natts);
! replaces = eval_mcontext_alloc(estate, sizeof(bool) * natts);
memset(replaces, false, sizeof(bool) * natts);
replaces[fno] = true;
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4437,4446 ****
rec->tup = newtup;
rec->freetup = true;
- pfree(values);
- pfree(nulls);
- pfree(replaces);
-
break;
}
--- 4641,4646 ----
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4601,4607 ****
return;
/* empty array, if any, and newarraydatum are short-lived */
! oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
if (oldarrayisnull)
oldarraydatum = PointerGetDatum(construct_empty_array(arrayelem->elemtypoid));
--- 4801,4807 ----
return;
/* empty array, if any, and newarraydatum are short-lived */
! oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
if (oldarrayisnull)
oldarraydatum = PointerGetDatum(construct_empty_array(arrayelem->elemtypoid));
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4655,4661 ****
* responsibility that the results are semantically OK.
*
* In some cases we have to palloc a return value, and in such cases we put
! * it into the estate's short-term memory context.
*/
static void
exec_eval_datum(PLpgSQL_execstate *estate,
--- 4855,4861 ----
* responsibility that the results are semantically OK.
*
* In some cases we have to palloc a return value, and in such cases we put
! * it into the estate's eval_mcontext.
*/
static void
exec_eval_datum(PLpgSQL_execstate *estate,
*************** exec_eval_datum(PLpgSQL_execstate *estat
*** 4689,4695 ****
elog(ERROR, "row variable has no tupdesc");
/* Make sure we have a valid type/typmod setting */
BlessTupleDesc(row->rowtupdesc);
! oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
tup = make_tuple_from_row(estate, row, row->rowtupdesc);
if (tup == NULL) /* should not happen */
elog(ERROR, "row not compatible with its own tupdesc");
--- 4889,4895 ----
elog(ERROR, "row variable has no tupdesc");
/* Make sure we have a valid type/typmod setting */
BlessTupleDesc(row->rowtupdesc);
! oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
tup = make_tuple_from_row(estate, row, row->rowtupdesc);
if (tup == NULL) /* should not happen */
elog(ERROR, "row not compatible with its own tupdesc");
*************** exec_eval_datum(PLpgSQL_execstate *estat
*** 4715,4721 ****
/* Make sure we have a valid type/typmod setting */
BlessTupleDesc(rec->tupdesc);
! oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
*typeid = rec->tupdesc->tdtypeid;
*typetypmod = rec->tupdesc->tdtypmod;
*value = heap_copy_tuple_as_datum(rec->tup, rec->tupdesc);
--- 4915,4921 ----
/* Make sure we have a valid type/typmod setting */
BlessTupleDesc(rec->tupdesc);
! oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
*typeid = rec->tupdesc->tdtypeid;
*typetypmod = rec->tupdesc->tdtypmod;
*value = heap_copy_tuple_as_datum(rec->tup, rec->tupdesc);
*************** exec_run_select(PLpgSQL_execstate *estat
*** 5107,5114 ****
if (*portalP == NULL)
elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
expr->query, SPI_result_code_string(SPI_result));
! if (paramLI)
! pfree(paramLI);
return SPI_OK_CURSOR;
}
--- 5307,5313 ----
if (*portalP == NULL)
elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
expr->query, SPI_result_code_string(SPI_result));
! exec_eval_cleanup(estate);
return SPI_OK_CURSOR;
}
*************** loop_exit:
*** 5323,5331 ****
* give correct results if that happens, and it's unlikely to be worth the
* cycles to check.
*
! * Note: if pass-by-reference, the result is in the eval_econtext's
! * temporary memory context. It will be freed when exec_eval_cleanup
! * is done.
* ----------
*/
static bool
--- 5522,5529 ----
* give correct results if that happens, and it's unlikely to be worth the
* cycles to check.
*
! * Note: if pass-by-reference, the result is in the eval_mcontext.
! * It will be freed when exec_eval_cleanup is done.
* ----------
*/
static bool
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 5357,5365 ****
/*
* Revalidate cached plan, so that we will notice if it became stale. (We
! * need to hold a refcount while using the plan, anyway.)
*/
cplan = SPI_plan_get_cached_plan(expr->plan);
/*
* We can't get a failure here, because the number of CachedPlanSources in
--- 5555,5566 ----
/*
* Revalidate cached plan, so that we will notice if it became stale. (We
! * need to hold a refcount while using the plan, anyway.) If replanning
! * is needed, do that work in the eval_mcontext.
*/
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
cplan = SPI_plan_get_cached_plan(expr->plan);
+ MemoryContextSwitchTo(oldcontext);
/*
* We can't get a failure here, because the number of CachedPlanSources in
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 5411,5417 ****
*/
SPI_push();
! oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
if (!estate->readonly_func)
{
CommandCounterIncrement();
--- 5612,5618 ----
*/
SPI_push();
! oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
if (!estate->readonly_func)
{
CommandCounterIncrement();
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 5449,5456 ****
/* Assorted cleanup */
expr->expr_simple_in_use = false;
! if (paramLI && paramLI != estate->paramLI)
! pfree(paramLI);
estate->paramLI->parserSetupArg = save_setup_arg;
--- 5650,5656 ----
/* Assorted cleanup */
expr->expr_simple_in_use = false;
! econtext->ecxt_param_list_info = NULL;
estate->paramLI->parserSetupArg = save_setup_arg;
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 5498,5505 ****
* throw errors (for example "no such record field") and we do not want that
* to happen in a part of the expression that might never be evaluated at
* runtime. For another thing, exec_eval_datum() may return short-lived
! * values stored in the estate's short-term memory context, which will not
! * necessarily survive to the next SPI operation. And for a third thing, ROW
* and RECFIELD datums' values depend on other datums, and we don't have a
* cheap way to track that. Therefore, param slots for non-VAR datum types
* are always reset here and then filled on-demand by plpgsql_param_fetch().
--- 5698,5705 ----
* throw errors (for example "no such record field") and we do not want that
* to happen in a part of the expression that might never be evaluated at
* runtime. For another thing, exec_eval_datum() may return short-lived
! * values stored in the estate's eval_mcontext, which will not necessarily
! * survive to the next SPI operation. And for a third thing, ROW
* and RECFIELD datums' values depend on other datums, and we don't have a
* cheap way to track that. Therefore, param slots for non-VAR datum types
* are always reset here and then filled on-demand by plpgsql_param_fetch().
*************** setup_param_list(PLpgSQL_execstate *esta
*** 5598,5604 ****
* to some trusted function. We don't want the R/W pointer to get into the
* shared param list, where it could get passed to some less-trusted function.
*
! * Caller should pfree the result after use, if it's not NULL.
*
* XXX. Could we use ParamListInfo's new paramMask to avoid creating unshared
* parameter lists?
--- 5798,5804 ----
* to some trusted function. We don't want the R/W pointer to get into the
* shared param list, where it could get passed to some less-trusted function.
*
! * The result, if not NULL, is in the estate's eval_mcontext.
*
* XXX. Could we use ParamListInfo's new paramMask to avoid creating unshared
* parameter lists?
*************** setup_unshared_param_list(PLpgSQL_execst
*** 5626,5633 ****
/* initialize ParamListInfo with one entry per datum, all invalid */
paramLI = (ParamListInfo)
! palloc0(offsetof(ParamListInfoData, params) +
! estate->ndatums * sizeof(ParamExternData));
paramLI->paramFetch = plpgsql_param_fetch;
paramLI->paramFetchArg = (void *) estate;
paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
--- 5826,5834 ----
/* initialize ParamListInfo with one entry per datum, all invalid */
paramLI = (ParamListInfo)
! eval_mcontext_alloc0(estate,
! offsetof(ParamListInfoData, params) +
! estate->ndatums * sizeof(ParamExternData));
paramLI->paramFetch = plpgsql_param_fetch;
paramLI->paramFetchArg = (void *) estate;
paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
*************** exec_move_row(PLpgSQL_execstate *estate,
*** 5784,5795 ****
/* If we have a tupdesc but no data, form an all-nulls tuple */
bool *nulls;
! nulls = (bool *) palloc(tupdesc->natts * sizeof(bool));
memset(nulls, true, tupdesc->natts * sizeof(bool));
tup = heap_form_tuple(tupdesc, NULL, nulls);
-
- pfree(nulls);
}
if (tupdesc)
--- 5985,5995 ----
/* If we have a tupdesc but no data, form an all-nulls tuple */
bool *nulls;
! nulls = (bool *)
! eval_mcontext_alloc(estate, tupdesc->natts * sizeof(bool));
memset(nulls, true, tupdesc->natts * sizeof(bool));
tup = heap_form_tuple(tupdesc, NULL, nulls);
}
if (tupdesc)
*************** exec_move_row(PLpgSQL_execstate *estate,
*** 5907,5912 ****
--- 6107,6115 ----
* make_tuple_from_row Make a tuple from the values of a row object
*
* A NULL return indicates rowtype mismatch; caller must raise suitable error
+ *
+ * The result tuple is freshly palloc'd in caller's context. Some junk
+ * may be left behind in eval_mcontext, too.
* ----------
*/
static HeapTuple
*************** make_tuple_from_row(PLpgSQL_execstate *e
*** 5923,5930 ****
if (natts != row->nfields)
return NULL;
! dvalues = (Datum *) palloc0(natts * sizeof(Datum));
! nulls = (bool *) palloc(natts * sizeof(bool));
for (i = 0; i < natts; i++)
{
--- 6126,6133 ----
if (natts != row->nfields)
return NULL;
! dvalues = (Datum *) eval_mcontext_alloc0(estate, natts * sizeof(Datum));
! nulls = (bool *) eval_mcontext_alloc(estate, natts * sizeof(bool));
for (i = 0; i < natts; i++)
{
*************** make_tuple_from_row(PLpgSQL_execstate *e
*** 5949,5964 ****
tuple = heap_form_tuple(tupdesc, dvalues, nulls);
- pfree(dvalues);
- pfree(nulls);
-
return tuple;
}
/* ----------
* get_tuple_from_datum extract a tuple from a composite Datum
*
! * Returns a freshly palloc'd HeapTuple.
*
* Note: it's caller's responsibility to be sure value is of composite type.
* ----------
--- 6152,6164 ----
tuple = heap_form_tuple(tupdesc, dvalues, nulls);
return tuple;
}
/* ----------
* get_tuple_from_datum extract a tuple from a composite Datum
*
! * Returns a HeapTuple, freshly palloc'd in caller's context.
*
* Note: it's caller's responsibility to be sure value is of composite type.
* ----------
*************** exec_move_row_from_datum(PLpgSQL_execsta
*** 6041,6047 ****
/* ----------
* convert_value_to_string Convert a non-null Datum to C string
*
! * Note: the result is in the estate's eval_econtext, and will be cleared
* by the next exec_eval_cleanup() call. The invoked output function might
* leave additional cruft there as well, so just pfree'ing the result string
* would not be enough to avoid memory leaks if we did not do it like this.
--- 6241,6247 ----
/* ----------
* convert_value_to_string Convert a non-null Datum to C string
*
! * Note: the result is in the estate's eval_mcontext, and will be cleared
* by the next exec_eval_cleanup() call. The invoked output function might
* leave additional cruft there as well, so just pfree'ing the result string
* would not be enough to avoid memory leaks if we did not do it like this.
*************** convert_value_to_string(PLpgSQL_execstat
*** 6061,6067 ****
Oid typoutput;
bool typIsVarlena;
! oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
result = OidOutputFunctionCall(typoutput, value);
MemoryContextSwitchTo(oldcontext);
--- 6261,6267 ----
Oid typoutput;
bool typIsVarlena;
! oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
result = OidOutputFunctionCall(typoutput, value);
MemoryContextSwitchTo(oldcontext);
*************** convert_value_to_string(PLpgSQL_execstat
*** 6076,6082 ****
* unlikely that a cast operation would produce null from non-null or vice
* versa, that could happen in principle.
*
! * Note: the estate's eval_econtext is used for temporary storage, and may
* also contain the result Datum if we have to do a conversion to a pass-
* by-reference data type. Be sure to do an exec_eval_cleanup() call when
* done with the result.
--- 6276,6282 ----
* unlikely that a cast operation would produce null from non-null or vice
* versa, that could happen in principle.
*
! * Note: the estate's eval_mcontext is used for temporary storage, and may
* also contain the result Datum if we have to do a conversion to a pass-
* by-reference data type. Be sure to do an exec_eval_cleanup() call when
* done with the result.
*************** exec_cast_value(PLpgSQL_execstate *estat
*** 6104,6110 ****
ExprContext *econtext = estate->eval_econtext;
MemoryContext oldcontext;
! oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
econtext->caseValue_datum = value;
econtext->caseValue_isNull = *isnull;
--- 6304,6310 ----
ExprContext *econtext = estate->eval_econtext;
MemoryContext oldcontext;
! oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
econtext->caseValue_datum = value;
econtext->caseValue_isNull = *isnull;
*************** get_cast_hashentry(PLpgSQL_execstate *es
*** 6161,6170 ****
/*
* Since we could easily fail (no such coercion), construct a
! * temporary coercion expression tree in a short-lived context, then
! * if successful copy it to cast_hash_context.
*/
! oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
/*
* We use a CaseTestExpr as the base of the coercion tree, since it's
--- 6361,6370 ----
/*
* Since we could easily fail (no such coercion), construct a
! * temporary coercion expression tree in the short-lived
! * eval_mcontext, then if successful copy it to cast_hash_context.
*/
! oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
/*
* We use a CaseTestExpr as the base of the coercion tree, since it's
*************** exec_simple_check_node(Node *node)
*** 6545,6556 ****
* ----------
*/
static void
! exec_simple_check_plan(PLpgSQL_expr *expr)
{
List *plansources;
CachedPlanSource *plansource;
Query *query;
CachedPlan *cplan;
/*
* Initialize to "not simple", and remember the plan generation number we
--- 6745,6757 ----
* ----------
*/
static void
! exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
{
List *plansources;
CachedPlanSource *plansource;
Query *query;
CachedPlan *cplan;
+ MemoryContext oldcontext;
/*
* Initialize to "not simple", and remember the plan generation number we
*************** exec_simple_check_plan(PLpgSQL_expr *exp
*** 6621,6630 ****
/*
* OK, it seems worth constructing a plan for more careful checking.
*/
!
! /* Get the generic plan for the query */
cplan = SPI_plan_get_cached_plan(expr->plan);
/* Can't fail, because we checked for a single CachedPlanSource above */
Assert(cplan != NULL);
--- 6822,6834 ----
/*
* OK, it seems worth constructing a plan for more careful checking.
+ *
+ * Get the generic plan for the query. If replanning is needed, do that
+ * work in the eval_mcontext.
*/
! oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
cplan = SPI_plan_get_cached_plan(expr->plan);
+ MemoryContextSwitchTo(oldcontext);
/* Can't fail, because we checked for a single CachedPlanSource above */
Assert(cplan != NULL);
*************** assign_text_var(PLpgSQL_execstate *estat
*** 7008,7030 ****
/*
* exec_eval_using_params --- evaluate params of USING clause
*/
static PreparedParamsData *
exec_eval_using_params(PLpgSQL_execstate *estate, List *params)
{
PreparedParamsData *ppd;
int nargs;
int i;
ListCell *lc;
! ppd = (PreparedParamsData *) palloc(sizeof(PreparedParamsData));
nargs = list_length(params);
ppd->nargs = nargs;
! ppd->types = (Oid *) palloc(nargs * sizeof(Oid));
! ppd->values = (Datum *) palloc(nargs * sizeof(Datum));
! ppd->nulls = (char *) palloc(nargs * sizeof(char));
! ppd->freevals = (bool *) palloc(nargs * sizeof(bool));
i = 0;
foreach(lc, params)
--- 7212,7241 ----
/*
* exec_eval_using_params --- evaluate params of USING clause
+ *
+ * The result data structure is created in the stmt_mcontext, and should
+ * be freed by resetting that context.
*/
static PreparedParamsData *
exec_eval_using_params(PLpgSQL_execstate *estate, List *params)
{
PreparedParamsData *ppd;
+ MemoryContext stmt_mcontext = get_stmt_mcontext(estate);
int nargs;
int i;
ListCell *lc;
! ppd = (PreparedParamsData *)
! MemoryContextAlloc(stmt_mcontext, sizeof(PreparedParamsData));
nargs = list_length(params);
ppd->nargs = nargs;
! ppd->types = (Oid *)
! MemoryContextAlloc(stmt_mcontext, nargs * sizeof(Oid));
! ppd->values = (Datum *)
! MemoryContextAlloc(stmt_mcontext, nargs * sizeof(Datum));
! ppd->nulls = (char *)
! MemoryContextAlloc(stmt_mcontext, nargs * sizeof(char));
i = 0;
foreach(lc, params)
*************** exec_eval_using_params(PLpgSQL_execstate
*** 7032,7044 ****
PLpgSQL_expr *param = (PLpgSQL_expr *) lfirst(lc);
bool isnull;
int32 ppdtypmod;
ppd->values[i] = exec_eval_expr(estate, param,
&isnull,
&ppd->types[i],
&ppdtypmod);
ppd->nulls[i] = isnull ? 'n' : ' ';
! ppd->freevals[i] = false;
if (ppd->types[i] == UNKNOWNOID)
{
--- 7243,7257 ----
PLpgSQL_expr *param = (PLpgSQL_expr *) lfirst(lc);
bool isnull;
int32 ppdtypmod;
+ MemoryContext oldcontext;
ppd->values[i] = exec_eval_expr(estate, param,
&isnull,
&ppd->types[i],
&ppdtypmod);
ppd->nulls[i] = isnull ? 'n' : ' ';
!
! oldcontext = MemoryContextSwitchTo(stmt_mcontext);
if (ppd->types[i] == UNKNOWNOID)
{
*************** exec_eval_using_params(PLpgSQL_execstate
*** 7051,7062 ****
*/
ppd->types[i] = TEXTOID;
if (!isnull)
- {
ppd->values[i] = CStringGetTextDatum(DatumGetCString(ppd->values[i]));
- ppd->freevals[i] = true;
- }
}
! /* pass-by-ref non null values must be copied into plpgsql context */
else if (!isnull)
{
int16 typLen;
--- 7264,7272 ----
*/
ppd->types[i] = TEXTOID;
if (!isnull)
ppd->values[i] = CStringGetTextDatum(DatumGetCString(ppd->values[i]));
}
! /* pass-by-ref non null values must be copied into stmt_mcontext */
else if (!isnull)
{
int16 typLen;
*************** exec_eval_using_params(PLpgSQL_execstate
*** 7064,7075 ****
get_typlenbyval(ppd->types[i], &typLen, &typByVal);
if (!typByVal)
- {
ppd->values[i] = datumCopy(ppd->values[i], typByVal, typLen);
- ppd->freevals[i] = true;
- }
}
exec_eval_cleanup(estate);
i++;
--- 7274,7284 ----
get_typlenbyval(ppd->types[i], &typLen, &typByVal);
if (!typByVal)
ppd->values[i] = datumCopy(ppd->values[i], typByVal, typLen);
}
+ MemoryContextSwitchTo(oldcontext);
+
exec_eval_cleanup(estate);
i++;
*************** exec_eval_using_params(PLpgSQL_execstate
*** 7079,7107 ****
}
/*
- * free_params_data --- pfree all pass-by-reference values used in USING clause
- */
- static void
- free_params_data(PreparedParamsData *ppd)
- {
- int i;
-
- for (i = 0; i < ppd->nargs; i++)
- {
- if (ppd->freevals[i])
- pfree(DatumGetPointer(ppd->values[i]));
- }
-
- pfree(ppd->types);
- pfree(ppd->values);
- pfree(ppd->nulls);
- pfree(ppd->freevals);
-
- pfree(ppd);
- }
-
- /*
* Open portal for dynamic query
*/
static Portal
exec_dynquery_with_params(PLpgSQL_execstate *estate,
--- 7288,7299 ----
}
/*
* Open portal for dynamic query
+ *
+ * Caution: this resets the stmt_mcontext at exit. We might eventually need
+ * to move that responsibility to the callers, but currently no caller needs
+ * to have statement-lifetime temp data that survives past this, so it's
+ * simpler to do it here.
*/
static Portal
exec_dynquery_with_params(PLpgSQL_execstate *estate,
*************** exec_dynquery_with_params(PLpgSQL_execst
*** 7116,7121 ****
--- 7308,7314 ----
Oid restype;
int32 restypmod;
char *querystr;
+ MemoryContext stmt_mcontext = get_stmt_mcontext(estate);
/*
* Evaluate the string expression after the EXECUTE keyword. Its result is
*************** exec_dynquery_with_params(PLpgSQL_execst
*** 7130,7137 ****
/* Get the C-String representation */
querystr = convert_value_to_string(estate, query, restype);
! /* copy it out of the temporary context before we clean up */
! querystr = pstrdup(querystr);
exec_eval_cleanup(estate);
--- 7323,7330 ----
/* Get the C-String representation */
querystr = convert_value_to_string(estate, query, restype);
! /* copy it into the stmt_mcontext before we clean up */
! querystr = MemoryContextStrdup(stmt_mcontext, querystr);
exec_eval_cleanup(estate);
*************** exec_dynquery_with_params(PLpgSQL_execst
*** 7151,7157 ****
ppd->values, ppd->nulls,
estate->readonly_func,
cursorOptions);
- free_params_data(ppd);
}
else
{
--- 7344,7349 ----
*************** exec_dynquery_with_params(PLpgSQL_execst
*** 7166,7172 ****
if (portal == NULL)
elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
querystr, SPI_result_code_string(SPI_result));
! pfree(querystr);
return portal;
}
--- 7358,7366 ----
if (portal == NULL)
elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
querystr, SPI_result_code_string(SPI_result));
!
! /* Release transient data */
! MemoryContextReset(stmt_mcontext);
return portal;
}
*************** exec_dynquery_with_params(PLpgSQL_execst
*** 7174,7179 ****
--- 7368,7374 ----
/*
* Return a formatted string with information about an expression's parameters,
* or NULL if the expression does not take any parameters.
+ * The result is in the eval_mcontext.
*/
static char *
format_expr_params(PLpgSQL_execstate *estate,
*************** format_expr_params(PLpgSQL_execstate *es
*** 7182,7191 ****
--- 7377,7389 ----
int paramno;
int dno;
StringInfoData paramstr;
+ MemoryContext oldcontext;
if (!expr->paramnos)
return NULL;
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+
initStringInfo(¶mstr);
paramno = 0;
dno = -1;
*************** format_expr_params(PLpgSQL_execstate *es
*** 7227,7238 ****
--- 7425,7439 ----
paramno++;
}
+ MemoryContextSwitchTo(oldcontext);
+
return paramstr.data;
}
/*
* Return a formatted string with information about PreparedParamsData, or NULL
* if there are no parameters.
+ * The result is in the eval_mcontext.
*/
static char *
format_preparedparamsdata(PLpgSQL_execstate *estate,
*************** format_preparedparamsdata(PLpgSQL_execst
*** 7240,7249 ****
--- 7441,7453 ----
{
int paramno;
StringInfoData paramstr;
+ MemoryContext oldcontext;
if (!ppd)
return NULL;
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+
initStringInfo(¶mstr);
for (paramno = 0; paramno < ppd->nargs; paramno++)
{
*************** format_preparedparamsdata(PLpgSQL_execst
*** 7269,7273 ****
--- 7473,7479 ----
}
}
+ MemoryContextSwitchTo(oldcontext);
+
return paramstr.data;
}
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 140bf4b..e729d3e 100644
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
*************** typedef struct PLpgSQL_execstate
*** 818,823 ****
--- 818,827 ----
HTAB *cast_hash;
MemoryContext cast_hash_context;
+ /* Memory context for statement-lifespan temporary values */
+ MemoryContext stmt_mcontext; /* current stmt context, or NULL if none */
+ MemoryContext stmt_mcontext_parent; /* parent of current context */
+
/* temporary state for results from evaluation of query or expr */
SPITupleTable *eval_tuptable;
uint64 eval_processed;
diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index d26991e..65a60b2 100644
*** a/src/backend/utils/mmgr/aset.c
--- b/src/backend/utils/mmgr/aset.c
***************
*** 86,91 ****
--- 86,93 ----
#include "postgres.h"
+ #include <execinfo.h>
+
#include "utils/memdebug.h"
#include "utils/memutils.h"
*************** AllocSetAlloc(MemoryContext context, Siz
*** 667,672 ****
--- 669,703 ----
AssertArg(AllocSetIsValid(set));
+ if (strcmp(context->name, "SPI Proc") == 0)
+ {
+ void *bt[10];
+ int n,
+ j;
+ char **strings;
+
+ n = backtrace(bt, lengthof(bt));
+ if (n > 0)
+ {
+ strings = backtrace_symbols(bt, n);
+ if (strings == NULL)
+ {
+ perror("backtrace_symbols");
+ exit(EXIT_FAILURE);
+ }
+ for (j = 0; j < n; j++)
+ {
+ if (strstr(strings[j], "plpgsql.so"))
+ {
+ fflush(NULL);
+ fprintf(stderr, "alloc from: %s\n", strings[j]);
+ break;
+ }
+ }
+ free(strings);
+ }
+ }
+
/*
* If requested size exceeds maximum for chunks, allocate an entire block
* for this request.
*************** AllocSetFree(MemoryContext context, void
*** 943,948 ****
--- 974,1008 ----
set->header.name, chunk);
#endif
+ if (strcmp(context->name, "SPI Proc") == 0)
+ {
+ void *bt[10];
+ int n,
+ j;
+ char **strings;
+
+ n = backtrace(bt, lengthof(bt));
+ if (n > 0)
+ {
+ strings = backtrace_symbols(bt, n);
+ if (strings == NULL)
+ {
+ perror("backtrace_symbols");
+ exit(EXIT_FAILURE);
+ }
+ for (j = 0; j < n; j++)
+ {
+ if (strstr(strings[j], "plpgsql.so"))
+ {
+ fflush(NULL);
+ fprintf(stderr, "free from: %s\n", strings[j]);
+ break;
+ }
+ }
+ free(strings);
+ }
+ }
+
if (chunk->size > set->allocChunkLimit)
{
/*
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index b628c28..1cdb82a 100644
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
*************** do_compile(FunctionCallInfo fcinfo,
*** 288,293 ****
--- 288,294 ----
int *in_arg_varnos = NULL;
PLpgSQL_variable **out_arg_variables;
MemoryContext func_cxt;
+ MemoryContext oldcontext = CurrentMemoryContext;
/*
* Setup the scanner input and error info. We assume that this function
*************** do_compile(FunctionCallInfo fcinfo,
*** 334,339 ****
--- 335,347 ----
}
plpgsql_curr_compile = function;
+ plpgsql_compile_tmp_cxt =
+ AllocSetContextCreate(CurrentMemoryContext,
+ "PL/pgSQL compile tmp context",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
/*
* All the permanent output of compilation (e.g. parse tree) is kept in a
* per-function memory context, so it can be reclaimed easily.
*************** do_compile(FunctionCallInfo fcinfo,
*** 343,349 ****
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
! plpgsql_compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
function->fn_signature = format_procedure(fcinfo->flinfo->fn_oid);
function->fn_oid = fcinfo->flinfo->fn_oid;
--- 351,357 ----
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
! MemoryContextSwitchTo(func_cxt);
function->fn_signature = format_procedure(fcinfo->flinfo->fn_oid);
function->fn_oid = fcinfo->flinfo->fn_oid;
*************** do_compile(FunctionCallInfo fcinfo,
*** 774,781 ****
plpgsql_check_syntax = false;
- MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
plpgsql_compile_tmp_cxt = NULL;
return function;
}
--- 782,790 ----
plpgsql_check_syntax = false;
plpgsql_compile_tmp_cxt = NULL;
+
+ MemoryContextSwitchTo(oldcontext);
return function;
}
*************** plpgsql_compile_inline(char *proc_source
*** 798,803 ****
--- 807,813 ----
PLpgSQL_variable *var;
int parse_rc;
MemoryContext func_cxt;
+ MemoryContext oldcontext = CurrentMemoryContext;
/*
* Setup the scanner input and error info. We assume that this function
*************** plpgsql_compile_inline(char *proc_source
*** 824,829 ****
--- 834,846 ----
plpgsql_curr_compile = function;
+ plpgsql_compile_tmp_cxt =
+ AllocSetContextCreate(CurrentMemoryContext,
+ "PL/pgSQL compile tmp context",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
/*
* All the rest of the compile-time storage (e.g. parse tree) is kept in
* its own memory context, so it can be reclaimed easily.
*************** plpgsql_compile_inline(char *proc_source
*** 833,839 ****
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
! plpgsql_compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
function->fn_signature = pstrdup(func_name);
function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
--- 850,856 ----
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
! MemoryContextSwitchTo(func_cxt);
function->fn_signature = pstrdup(func_name);
function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
*************** plpgsql_compile_inline(char *proc_source
*** 911,918 ****
plpgsql_check_syntax = false;
- MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
plpgsql_compile_tmp_cxt = NULL;
return function;
}
--- 928,936 ----
plpgsql_check_syntax = false;
plpgsql_compile_tmp_cxt = NULL;
+
+ MemoryContextSwitchTo(oldcontext);
return function;
}
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers