]> granicus.if.org Git - postgresql/commitdiff
Improve plpgsql's memory management to fix some function-lifespan leaks.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 17 Aug 2016 18:51:10 +0000 (14:51 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 17 Aug 2016 18:51:10 +0000 (14:51 -0400)
In some cases, exiting out of a plpgsql statement due to an error, then
catching the error in a surrounding exception block, led to leakage of
temporary data the statement was working with, because we kept all such
data in the function-lifespan SPI Proc context.  Iterating such behavior
many times within one function call thus led to noticeable memory bloat.

To fix, create an additional memory context meant to have statement
lifespan.  Since many plpgsql statements, particularly the simpler/more
common ones, don't need this, create it only on demand.  Reset this context
at the end of any statement that uses it, and arrange for exception cleanup
to reset it too, thereby fixing the memory-leak issue.  Allow a stack of
such contexts to exist to handle cases where a compound statement needs
statement-lifespan data that persists across calls of inner statements.

While at it, clean up code and improve comments referring to the existing
short-term memory context, which by plpgsql convention is the per-tuple
context of the eval_econtext ExprContext.  We now uniformly refer to that
as the eval_mcontext, whereas the new statement-lifespan memory contexts
are called stmt_mcontext.

This change adds some context-creation overhead, but on the other hand
it allows removal of some retail pfree's in favor of context resets.
On balance it seems to be about a wash performance-wise.

In principle this is a bug fix, but it seems too invasive for a back-patch,
and the infrequency of complaints weighs against taking the risk in the
back branches.  So we'll fix it only in HEAD, at least for now.

Tom Lane, reviewed by Pavel Stehule

Discussion: <17863.1469142152@sss.pgh.pa.us>

src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/plpgsql.h

index fec55e502fb8e0466e35a32fc8383bd1310394e2..f9b3b22d0811c6fe853f486e1a4dc8831d4276ee 100644 (file)
@@ -48,7 +48,6 @@ typedef struct
        Oid                *types;                      /* types of arguments */
        Datum      *values;                     /* evaluated argument values */
        char       *nulls;                      /* null markers (' '/'n' style) */
-       bool       *freevals;           /* which arguments are pfree-able */
 } PreparedParamsData;
 
 /*
@@ -87,6 +86,36 @@ typedef struct SimpleEcontextStackEntry
 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.
  *
@@ -128,6 +157,9 @@ static HTAB *shared_cast_hash = NULL;
  ************************************************************/
 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);
@@ -191,7 +223,7 @@ static void exec_eval_cleanup(PLpgSQL_execstate *estate);
 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);
@@ -271,11 +303,9 @@ static void assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
                                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,
@@ -562,6 +592,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
        /* 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
@@ -832,6 +863,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
        /* 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
@@ -844,6 +876,11 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
        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)
 {
@@ -915,6 +952,7 @@ 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
@@ -1041,7 +1079,64 @@ copy_plpgsql_datum(PLpgSQL_datum *datum)
        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)
 {
@@ -1174,9 +1269,21 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                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);
@@ -1202,7 +1309,9 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                         * 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 &&
@@ -1213,8 +1322,8 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                                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 */
@@ -1222,6 +1331,9 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                        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.)
@@ -1241,8 +1353,8 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
 
                        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();
 
@@ -1251,6 +1363,26 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                        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;
 
@@ -1319,8 +1451,10 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                        /* 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();
 
@@ -1663,11 +1797,15 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
 
                        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;
 
@@ -1677,6 +1815,8 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
                }
        }
 
+       exec_eval_cleanup(estate);
+
        return PLPGSQL_RC_OK;
 }
 
@@ -1738,7 +1878,10 @@ exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt)
                /*
                 * 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)
@@ -2132,6 +2275,7 @@ static int
 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;
@@ -2146,7 +2290,14 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
        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),
@@ -2216,16 +2367,19 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
                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.
@@ -2241,9 +2395,6 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
        if (curname == NULL)
                assign_simple_var(estate, curvar, (Datum) 0, true, false);
 
-       if (curname)
-               pfree(curname);
-
        return rc;
 }
 
@@ -2266,6 +2417,8 @@ exec_stmt_foreach_a(PLpgSQL_execstate *estate, PLpgSQL_stmt_foreach_a *stmt)
        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;
@@ -2279,6 +2432,15 @@ exec_stmt_foreach_a(PLpgSQL_execstate *estate, PLpgSQL_stmt_foreach_a *stmt)
                                (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,
@@ -2287,9 +2449,9 @@ exec_stmt_foreach_a(PLpgSQL_execstate *estate, PLpgSQL_stmt_foreach_a *stmt)
                                                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);
 
@@ -2355,6 +2517,9 @@ exec_stmt_foreach_a(PLpgSQL_execstate *estate, PLpgSQL_stmt_foreach_a *stmt)
        {
                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);
@@ -2413,11 +2578,16 @@ exec_stmt_foreach_a(PLpgSQL_execstate *estate, PLpgSQL_stmt_foreach_a *stmt)
                                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
@@ -2465,6 +2635,13 @@ exec_stmt_exit(PLpgSQL_execstate *estate, PLpgSQL_stmt_exit *stmt)
 /* ----------
  * 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
@@ -2639,8 +2816,8 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
 {
        TupleDesc       tupdesc;
        int                     natts;
-       HeapTuple       tuple = NULL;
-       bool            free_tuple = false;
+       HeapTuple       tuple;
+       MemoryContext oldcontext;
 
        if (!estate->retisset)
                ereport(ERROR,
@@ -2712,17 +2889,17 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
                                                                  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;
 
@@ -2730,12 +2907,15 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
                                {
                                        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;
 
@@ -2770,24 +2950,17 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
                                                        (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
                        {
@@ -2795,13 +2968,13 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
                                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
@@ -2832,14 +3005,6 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
                                 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;
@@ -2858,6 +3023,7 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
        Portal          portal;
        uint64          processed = 0;
        TupleConversionMap *tupmap;
+       MemoryContext oldcontext;
 
        if (!estate->retisset)
                ereport(ERROR,
@@ -2881,6 +3047,9 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
                                                                                   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"));
@@ -2890,6 +3059,10 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
                uint64          i;
 
                SPI_cursor_fetch(portal, true, 50);
+
+               /* SPI will have changed CurrentMemoryContext */
+               MemoryContextSwitchTo(get_eval_mcontext(estate));
+
                if (SPI_processed == 0)
                        break;
 
@@ -2908,12 +3081,12 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
                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);
 
@@ -2965,7 +3138,7 @@ do { \
                                (errcode(ERRCODE_SYNTAX_ERROR), \
                                 errmsg("RAISE option already specified: %s", \
                                                name))); \
-       opt = pstrdup(extval); \
+       opt = MemoryContextStrdup(stmt_mcontext, extval); \
 } while (0)
 
 /* ----------
@@ -2985,6 +3158,7 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
        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 */
@@ -2999,10 +3173,13 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
                 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)
@@ -3010,8 +3187,13 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
                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++)
@@ -3064,7 +3246,6 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
                        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)
@@ -3096,7 +3277,7 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
                                                         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");
@@ -3142,7 +3323,8 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
                        condname = NULL;
                }
                else
-                       err_message = pstrdup(unpack_sql_state(err_code));
+                       err_message = MemoryContextStrdup(stmt_mcontext,
+                                                                                         unpack_sql_state(err_code));
        }
 
        /*
@@ -3164,24 +3346,8 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
                         (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;
 }
@@ -3329,6 +3495,14 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
                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;
@@ -3378,7 +3552,10 @@ exec_eval_cleanup(PLpgSQL_execstate *estate)
                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);
 }
@@ -3430,7 +3607,7 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
        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
@@ -3443,6 +3620,9 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
 
 /* ----------
  * 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
@@ -3675,6 +3855,7 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
        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
@@ -3689,8 +3870,8 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
        /* 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);
 
@@ -3843,12 +4024,9 @@ exec_stmt_dynexecute(PLpgSQL_execstate *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;
 }
@@ -3892,6 +4070,7 @@ static int
 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;
@@ -3905,7 +4084,14 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
        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),
@@ -3942,7 +4128,10 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
                                                                                   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);
@@ -4019,10 +4208,10 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
        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;
 }
@@ -4036,7 +4225,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
 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;
@@ -4044,6 +4233,7 @@ exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
        Portal          portal;
        char       *curname;
        uint64          n;
+       MemoryContext oldcontext;
 
        /* ----------
         * Get the portal of the cursor by name
@@ -4054,14 +4244,17 @@ exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
                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)
@@ -4133,9 +4326,10 @@ exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
 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
@@ -4146,14 +4340,17 @@ exec_stmt_close(PLpgSQL_execstate *estate, PLpgSQL_stmt_close *stmt)
                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.
@@ -4201,6 +4398,9 @@ exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
  * 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
@@ -4208,21 +4408,25 @@ exec_assign_c_string(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
                                         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.
  * ----------
@@ -4259,10 +4463,10 @@ exec_assign_value(PLpgSQL_execstate *estate,
 
                                /*
                                 * 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,
@@ -4402,9 +4606,9 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                 * 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;
@@ -4437,10 +4641,6 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                rec->tup = newtup;
                                rec->freetup = true;
 
-                               pfree(values);
-                               pfree(nulls);
-                               pfree(replaces);
-
                                break;
                        }
 
@@ -4601,7 +4801,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                        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));
@@ -4655,7 +4855,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
  * 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,
@@ -4689,7 +4889,7 @@ 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");
@@ -4715,7 +4915,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
                                /* 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);
@@ -5107,8 +5307,7 @@ exec_run_select(PLpgSQL_execstate *estate,
                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;
        }
 
@@ -5323,9 +5522,8 @@ loop_exit:
  * 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
@@ -5357,9 +5555,12 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
 
        /*
         * 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
@@ -5411,7 +5612,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
         */
        SPI_push();
 
-       oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+       oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
        if (!estate->readonly_func)
        {
                CommandCounterIncrement();
@@ -5449,8 +5650,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
        /* 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;
 
@@ -5498,8 +5698,8 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
  * 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().
@@ -5598,7 +5798,7 @@ setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
  * 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?
@@ -5626,8 +5826,9 @@ setup_unshared_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
 
                /* 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;
@@ -5784,12 +5985,11 @@ exec_move_row(PLpgSQL_execstate *estate,
                        /* 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)
@@ -5907,6 +6107,9 @@ exec_move_row(PLpgSQL_execstate *estate,
  * 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
@@ -5923,8 +6126,8 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
        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++)
        {
@@ -5949,16 +6152,13 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
 
        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.
  * ----------
@@ -6041,7 +6241,7 @@ exec_move_row_from_datum(PLpgSQL_execstate *estate,
 /* ----------
  * 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.
@@ -6061,7 +6261,7 @@ convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype)
        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);
@@ -6076,7 +6276,7 @@ convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype)
  * 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.
@@ -6104,7 +6304,7 @@ exec_cast_value(PLpgSQL_execstate *estate,
                        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;
@@ -6161,10 +6361,10 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
 
                /*
                 * 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
@@ -6548,12 +6748,13 @@ exec_simple_check_node(Node *node)
  * ----------
  */
 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
@@ -6624,10 +6825,13 @@ exec_simple_check_plan(PLpgSQL_expr *expr)
 
        /*
         * 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);
@@ -7011,23 +7215,30 @@ assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str)
 
 /*
  * 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)
@@ -7035,13 +7246,15 @@ exec_eval_using_params(PLpgSQL_execstate *estate, List *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)
                {
@@ -7054,12 +7267,9 @@ exec_eval_using_params(PLpgSQL_execstate *estate, List *params)
                         */
                        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;
@@ -7067,12 +7277,11 @@ exec_eval_using_params(PLpgSQL_execstate *estate, List *params)
 
                        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++;
@@ -7081,30 +7290,13 @@ exec_eval_using_params(PLpgSQL_execstate *estate, List *params)
        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,
@@ -7119,6 +7311,7 @@ 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
@@ -7133,8 +7326,8 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate,
        /* 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);
 
@@ -7154,7 +7347,6 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate,
                                                                                   ppd->values, ppd->nulls,
                                                                                   estate->readonly_func,
                                                                                   cursorOptions);
-               free_params_data(ppd);
        }
        else
        {
@@ -7169,7 +7361,9 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate,
        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;
 }
@@ -7177,6 +7371,7 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate,
 /*
  * 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,
@@ -7185,10 +7380,13 @@ 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(&paramstr);
        paramno = 0;
        dno = -1;
@@ -7230,12 +7428,15 @@ format_expr_params(PLpgSQL_execstate *estate,
                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,
@@ -7243,10 +7444,13 @@ format_preparedparamsdata(PLpgSQL_execstate *estate,
 {
        int                     paramno;
        StringInfoData paramstr;
+       MemoryContext oldcontext;
 
        if (!ppd)
                return NULL;
 
+       oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+
        initStringInfo(&paramstr);
        for (paramno = 0; paramno < ppd->nargs; paramno++)
        {
@@ -7272,5 +7476,7 @@ format_preparedparamsdata(PLpgSQL_execstate *estate,
                }
        }
 
+       MemoryContextSwitchTo(oldcontext);
+
        return paramstr.data;
 }
index 140bf4baddbe73cf2c5fee3e8937a5312e0c0ce7..bfd52af3e79839cfce57bf8522330e9de8580ec0 100644 (file)
@@ -814,10 +814,14 @@ typedef struct PLpgSQL_execstate
        /* EState to use for "simple" expression evaluation */
        EState     *simple_eval_estate;
 
-       /* Lookup table to use for executing type casts */
+       /* lookup table to use for executing type casts */
        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;