Oid *types; /* types of arguments */
Datum *values; /* evaluated argument values */
char *nulls; /* null markers (' '/'n' style) */
- bool *freevals; /* which arguments are pfree-able */
} PreparedParamsData;
/*
static EState *shared_simple_eval_estate = NULL;
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.
*
************************************************************/
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_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_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);
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,
/* 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
/* 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
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)
{
/* 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
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)
{
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);
* 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.
+ * 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 &&
bool resTypByVal;
get_typlenbyval(estate->rettype, &resTypLen, &resTypByVal);
- estate->retval = datumCopy(estate->retval,
- resTypByVal, resTypLen);
+ estate->retval = datumTransfer(estate->retval,
+ resTypByVal, resTypLen);
}
/* Commit the inner transaction, return to outer xact context */
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.)
estate->err_text = gettext_noop("during exception cleanup");
- /* Save error info */
- MemoryContextSwitchTo(oldcontext);
+ /* Save error info in our stmt_mcontext */
+ MemoryContextSwitchTo(stmt_mcontext);
edata = CopyErrorData();
FlushErrorState();
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;
/* If no match found, re-throw the error */
if (e == NULL)
ReThrowError(edata);
- else
- FreeErrorData(edata);
+
+ /* Restore stmt_mcontext stack and release the error data */
+ pop_stmt_mcontext(estate);
+ MemoryContextReset(stmt_mcontext);
}
PG_END_TRY();
case PLPGSQL_GETDIAG_CONTEXT:
{
- char *contextstackstr = GetErrorContextStack();
+ char *contextstackstr;
+ MemoryContext oldcontext;
- exec_assign_c_string(estate, var, contextstackstr);
+ /* Use eval_mcontext for short-lived string */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ contextstackstr = GetErrorContextStack();
+ MemoryContextSwitchTo(oldcontext);
- pfree(contextstackstr);
+ exec_assign_c_string(estate, var, contextstackstr);
}
break;
}
}
+ exec_eval_cleanup(estate);
+
return PLPGSQL_RC_OK;
}
/*
* 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.
+ * 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)
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;
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),
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
*/
if (curname == NULL)
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.
if (curname == NULL)
assign_simple_var(estate, curvar, (Datum) 0, true, false);
- if (curname)
- pfree(curname);
-
return rc;
}
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;
(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,
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.
+ * 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);
{
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);
break;
}
}
+
+ MemoryContextSwitchTo(stmt_mcontext);
}
+ /* Restore memory context state */
+ MemoryContextSwitchTo(oldcontext);
+ pop_stmt_mcontext(estate);
+
/* Release temporary memory, including the array value */
- array_free_iterator(array_iterator);
- pfree(arr);
+ MemoryContextReset(stmt_mcontext);
/*
* Set the FOUND variable to indicate the result of executing the loop
/* ----------
* 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
{
TupleDesc tupdesc;
int natts;
- HeapTuple tuple = NULL;
- bool free_tuple = false;
+ HeapTuple tuple;
+ MemoryContext oldcontext;
if (!estate->retisset)
ereport(ERROR,
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;
- /* it might need conversion */
if (tupmap)
- {
tuple = do_convert_tuple(tuple, tupmap);
- free_conversion_map(tupmap);
- free_tuple = true;
- }
+ tuplestore_puttuple(estate->tuple_store, tuple);
+ MemoryContextSwitchTo(oldcontext);
}
break;
{
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")));
- free_tuple = true;
+ tuplestore_puttuple(estate->tuple_store, tuple);
+ MemoryContextSwitchTo(oldcontext);
}
break;
(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);
- 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;
- }
+ tuple = do_convert_tuple(tuple, tupmap);
+ tuplestore_puttuple(estate->tuple_store, tuple);
ReleaseTupleDesc(retvaldesc);
- /* tuple will be stored into tuplestore below */
+ MemoryContextSwitchTo(oldcontext);
}
else
{
Datum *nulldatums;
bool *nullflags;
- nulldatums = (Datum *) palloc0(natts * sizeof(Datum));
- nullflags = (bool *) palloc(natts * sizeof(bool));
+ 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);
- pfree(nulldatums);
- pfree(nullflags);
}
}
else
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;
Portal portal;
uint64 processed = 0;
TupleConversionMap *tupmap;
+ MemoryContext oldcontext;
if (!estate->retisset)
ereport(ERROR,
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"));
uint64 i;
SPI_cursor_fetch(portal, true, 50);
+
+ /* SPI will have changed CurrentMemoryContext */
+ MemoryContextSwitchTo(get_eval_mcontext(estate));
+
if (SPI_processed == 0)
break;
SPI_freetuptable(SPI_tuptable);
}
- if (tupmap)
- free_conversion_map(tupmap);
-
SPI_freetuptable(SPI_tuptable);
SPI_cursor_close(portal);
+ MemoryContextSwitchTo(oldcontext);
+ exec_eval_cleanup(estate);
+
estate->eval_processed = processed;
exec_set_found(estate, processed != 0);
(errcode(ERRCODE_SYNTAX_ERROR), \
errmsg("RAISE option already specified: %s", \
name))); \
- opt = pstrdup(extval); \
+ opt = MemoryContextStrdup(stmt_mcontext, extval); \
} while (0)
/* ----------
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 */
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 = pstrdup(stmt->condname);
+ condname = MemoryContextStrdup(stmt_mcontext, stmt->condname);
}
if (stmt->message)
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++)
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)
errmsg("RAISE option already specified: %s",
"ERRCODE")));
err_code = plpgsql_recognize_err_condition(extval, true);
- condname = pstrdup(extval);
+ condname = MemoryContextStrdup(stmt_mcontext, extval);
break;
case PLPGSQL_RAISEOPTION_MESSAGE:
SET_RAISE_OPTION_TEXT(err_message, "MESSAGE");
condname = NULL;
}
else
- err_message = pstrdup(unpack_sql_state(err_code));
+ err_message = MemoryContextStrdup(stmt_mcontext,
+ unpack_sql_state(err_code));
}
/*
(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);
+ /* Clean up transient strings */
+ MemoryContextReset(stmt_mcontext);
return PLPGSQL_RC_OK;
}
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;
SPI_freetuptable(estate->eval_tuptable);
estate->eval_tuptable = NULL;
- /* Clear result of exec_eval_simple_expr (but keep the econtext) */
+ /*
+ * 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);
}
expr->plan = plan;
/* Check to see if it's a simple expression */
- exec_simple_check_plan(expr);
+ exec_simple_check_plan(estate, expr);
/*
* Mark expression as not using a read-write param. exec_assign_value has
/* ----------
* 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
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
/* 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);
+ /* copy it into the stmt_mcontext before we clean up */
+ querystr = MemoryContextStrdup(stmt_mcontext, querystr);
exec_eval_cleanup(estate);
*/
}
- if (ppd)
- free_params_data(ppd);
-
- /* Release any result from SPI_execute, as well as the querystring */
+ /* Release any result from SPI_execute, as well as transient data */
SPI_freetuptable(SPI_tuptable);
- pfree(querystr);
+ MemoryContextReset(stmt_mcontext);
return PLPGSQL_RC_OK;
}
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;
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),
stmt->cursor_options);
/*
- * If cursor variable was NULL, store the generated portal name in it
+ * 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);
if (curname == NULL)
assign_text_var(estate, curvar, portal->name);
- if (curname)
- pfree(curname);
- if (paramLI)
- pfree(paramLI);
+ /* If we had any transient data, clean it up */
+ exec_eval_cleanup(estate);
+ if (stmt_mcontext)
+ MemoryContextReset(stmt_mcontext);
return PLPGSQL_RC_OK;
}
static int
exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
{
- PLpgSQL_var *curvar = NULL;
+ PLpgSQL_var *curvar;
PLpgSQL_rec *rec = NULL;
PLpgSQL_row *row = NULL;
long how_many = stmt->how_many;
Portal portal;
char *curname;
uint64 n;
+ MemoryContext oldcontext;
/* ----------
* Get the portal of the cursor by name
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)));
- pfree(curname);
/* Calculate position for FETCH_RELATIVE or FETCH_ABSOLUTE */
if (stmt->expr)
static int
exec_stmt_close(PLpgSQL_execstate *estate, PLpgSQL_stmt_close *stmt)
{
- PLpgSQL_var *curvar = NULL;
+ PLpgSQL_var *curvar;
Portal portal;
char *curname;
+ MemoryContext oldcontext;
/* ----------
* Get the portal of the cursor by name
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)));
- pfree(curname);
/* ----------
* And close it.
* 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
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);
- 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;
+ * 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.
* ----------
/*
* 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.
+ * 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,
* 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);
+ 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;
rec->tup = newtup;
rec->freetup = true;
- pfree(values);
- pfree(nulls);
- pfree(replaces);
-
break;
}
return;
/* empty array, if any, and newarraydatum are short-lived */
- oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
if (oldarrayisnull)
oldarraydatum = PointerGetDatum(construct_empty_array(arrayelem->elemtypoid));
* 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.
+ * it into the estate's eval_mcontext.
*/
static void
exec_eval_datum(PLpgSQL_execstate *estate,
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);
+ 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");
/* Make sure we have a valid type/typmod setting */
BlessTupleDesc(rec->tupdesc);
- oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
*typeid = rec->tupdesc->tdtypeid;
*typetypmod = rec->tupdesc->tdtypmod;
*value = heap_copy_tuple_as_datum(rec->tup, rec->tupdesc);
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);
+ exec_eval_cleanup(estate);
return SPI_OK_CURSOR;
}
* 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.
+ * Note: if pass-by-reference, the result is in the eval_mcontext.
+ * It will be freed when exec_eval_cleanup is done.
* ----------
*/
static bool
/*
* Revalidate cached plan, so that we will notice if it became stale. (We
- * need to hold a refcount while using the plan, anyway.)
+ * 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
*/
SPI_push();
- oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
if (!estate->readonly_func)
{
CommandCounterIncrement();
/* Assorted cleanup */
expr->expr_simple_in_use = false;
- if (paramLI && paramLI != estate->paramLI)
- pfree(paramLI);
+ econtext->ecxt_param_list_info = NULL;
estate->paramLI->parserSetupArg = save_setup_arg;
* 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
+ * 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().
* 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.
+ * 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?
/* initialize ParamListInfo with one entry per datum, all invalid */
paramLI = (ParamListInfo)
- palloc0(offsetof(ParamListInfoData, params) +
- estate->ndatums * sizeof(ParamExternData));
+ 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;
/* If we have a tupdesc but no data, form an all-nulls tuple */
bool *nulls;
- nulls = (bool *) palloc(tupdesc->natts * sizeof(bool));
+ nulls = (bool *)
+ eval_mcontext_alloc(estate, tupdesc->natts * sizeof(bool));
memset(nulls, true, tupdesc->natts * sizeof(bool));
tup = heap_form_tuple(tupdesc, NULL, nulls);
-
- pfree(nulls);
}
if (tupdesc)
* 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
if (natts != row->nfields)
return NULL;
- dvalues = (Datum *) palloc0(natts * sizeof(Datum));
- nulls = (bool *) palloc(natts * sizeof(bool));
+ dvalues = (Datum *) eval_mcontext_alloc0(estate, natts * sizeof(Datum));
+ nulls = (bool *) eval_mcontext_alloc(estate, natts * sizeof(bool));
for (i = 0; i < natts; i++)
{
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.
+ * Returns a HeapTuple, freshly palloc'd in caller's context.
*
* Note: it's caller's responsibility to be sure value is of composite type.
* ----------
/* ----------
* 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
+ * 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.
Oid typoutput;
bool typIsVarlena;
- oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
result = OidOutputFunctionCall(typoutput, value);
MemoryContextSwitchTo(oldcontext);
* 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
+ * 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.
ExprContext *econtext = estate->eval_econtext;
MemoryContext oldcontext;
- oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
econtext->caseValue_datum = value;
econtext->caseValue_isNull = *isnull;
/*
* 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.
+ * temporary coercion expression tree in the short-lived
+ * eval_mcontext, then if successful copy it to cast_hash_context.
*/
- oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
/*
* We use a CaseTestExpr as the base of the coercion tree, since it's
* ----------
*/
static void
-exec_simple_check_plan(PLpgSQL_expr *expr)
+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
/*
* 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.
*/
-
- /* Get the generic plan for the query */
+ 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);
/*
* 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 *) palloc(sizeof(PreparedParamsData));
+ ppd = (PreparedParamsData *)
+ MemoryContextAlloc(stmt_mcontext, 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));
+ 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)
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' : ' ';
- ppd->freevals[i] = false;
+
+ oldcontext = MemoryContextSwitchTo(stmt_mcontext);
if (ppd->types[i] == UNKNOWNOID)
{
*/
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 */
+ /* pass-by-ref non null values must be copied into stmt_mcontext */
else if (!isnull)
{
int16 typLen;
get_typlenbyval(ppd->types[i], &typLen, &typByVal);
if (!typByVal)
- {
ppd->values[i] = datumCopy(ppd->values[i], typByVal, typLen);
- ppd->freevals[i] = true;
- }
}
+ MemoryContextSwitchTo(oldcontext);
+
exec_eval_cleanup(estate);
i++;
return ppd;
}
-/*
- * 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
+ *
+ * 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,
Oid restype;
int32 restypmod;
char *querystr;
+ MemoryContext stmt_mcontext = get_stmt_mcontext(estate);
/*
* Evaluate the string expression after the EXECUTE keyword. Its result is
/* 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);
+ /* copy it into the stmt_mcontext before we clean up */
+ querystr = MemoryContextStrdup(stmt_mcontext, querystr);
exec_eval_cleanup(estate);
ppd->values, ppd->nulls,
estate->readonly_func,
cursorOptions);
- free_params_data(ppd);
}
else
{
if (portal == NULL)
elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
querystr, SPI_result_code_string(SPI_result));
- pfree(querystr);
+
+ /* Release transient data */
+ MemoryContextReset(stmt_mcontext);
return portal;
}
/*
* 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,
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;
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,
{
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++)
{
}
}
+ MemoryContextSwitchTo(oldcontext);
+
return paramstr.data;
}