Re: Curing plpgsql's memory leaks for statement-lifespan values

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: Curing plpgsql's memory leaks for statement-lifespan values
Дата
Msg-id 5916.1469472648@sss.pgh.pa.us
обсуждение исходный текст
Ответ на Re: Curing plpgsql's memory leaks for statement-lifespan values  (Amit Kapila <amit.kapila16@gmail.com>)
Ответы Re: Curing plpgsql's memory leaks for statement-lifespan values  (Alvaro Herrera <alvherre@2ndquadrant.com>)
Re: Curing plpgsql's memory leaks for statement-lifespan values  (Jim Nasby <Jim.Nasby@BlueTreble.com>)
Список pgsql-hackers
Attached is a draft patch for creating statement-level temporary memory
contexts in plpgsql.  It creates such contexts only as needed, and there
are a lot of simpler plpgsql statements that don't need them, so I think
the performance impact should be pretty minimal --- though I've not tried
to measure it, since I don't have a credible benchmark for overall plpgsql
performance.

This fixes the originally complained-of leak and several others besides,
including at least one path that leaked function-lifespan memory even
without an error :-(.

In addition to the patch proper, I attach for amusement's sake some
additional hacking I did for testing purposes, to make sure I'd found and
accounted for all the places that were allocating memory in the SPI Proc
context.  There's a glibc-dependent hack in aset.c that reports any
plpgsql-driven palloc or pfree against a context named "SPI Proc", as
well as changes in pl_comp.c so that transient junk created during initial
parsing of a plpgsql function body doesn't end up in the SPI Proc context.
(I did that just to cut the amount of noise I had to chase down.  I suppose
in large functions it might be worth adopting such a change so that that
junk can be released immediately after parsing; but I suspect for most
functions it'd just be an extra context without much gain.)

Although this is in principle a bug fix, it's invasive enough that I'd
be pretty hesitant to back-patch it, or even to stick it into HEAD
post-beta.  I'm inclined to sign it up for the next commitfest instead.

            regards, tom lane

diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 586ff1f..e23ca96 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** typedef struct
*** 48,54 ****
      Oid           *types;            /* types of arguments */
      Datum       *values;            /* evaluated argument values */
      char       *nulls;            /* null markers (' '/'n' style) */
-     bool       *freevals;        /* which arguments are pfree-able */
  } PreparedParamsData;

  /*
--- 48,53 ----
*************** static EState *shared_simple_eval_estate
*** 88,93 ****
--- 87,122 ----
  static SimpleEcontextStackEntry *simple_econtext_stack = NULL;

  /*
+  * Memory management within a plpgsql function generally works with three
+  * contexts:
+  *
+  * 1. Function-call-lifespan data, such as variable values, is kept in the
+  * "main" context, a/k/a the "SPI Proc" context established by SPI_connect().
+  * This is usually the CurrentMemoryContext while running code in this module
+  * (which is not good, because careless coding can easily cause
+  * function-lifespan memory leaks, but we live with it for now).
+  *
+  * 2. Some statement-execution routines need statement-lifespan workspace.
+  * A suitable context is created on-demand by get_stmt_mcontext(), and must
+  * be reset at the end of the requesting routine.  Error recovery will clean
+  * it up automatically.  Nested statements requiring statement-lifespan
+  * workspace will result in a stack of such contexts, see push_stmt_mcontext().
+  *
+  * 3. We use the eval_econtext's per-tuple memory context for expression
+  * evaluation, and as a general-purpose workspace for short-lived allocations.
+  * Such allocations usually aren't explicitly freed, but are left to be
+  * cleaned up by a context reset, typically done by exec_eval_cleanup().
+  *
+  * These macros are for use in making short-lived allocations:
+  */
+ #define get_eval_mcontext(estate) \
+     ((estate)->eval_econtext->ecxt_per_tuple_memory)
+ #define eval_mcontext_alloc(estate, sz) \
+     MemoryContextAlloc(get_eval_mcontext(estate), sz)
+ #define eval_mcontext_alloc0(estate, sz) \
+     MemoryContextAllocZero(get_eval_mcontext(estate), sz)
+
+ /*
   * We use a session-wide hash table for caching cast information.
   *
   * Once built, the compiled expression trees (cast_expr fields) survive for
*************** static HTAB *shared_cast_hash = NULL;
*** 128,133 ****
--- 157,165 ----
   ************************************************************/
  static void plpgsql_exec_error_callback(void *arg);
  static PLpgSQL_datum *copy_plpgsql_datum(PLpgSQL_datum *datum);
+ static MemoryContext get_stmt_mcontext(PLpgSQL_execstate *estate);
+ static void push_stmt_mcontext(PLpgSQL_execstate *estate);
+ static void pop_stmt_mcontext(PLpgSQL_execstate *estate);

  static int exec_stmt_block(PLpgSQL_execstate *estate,
                  PLpgSQL_stmt_block *block);
*************** static void exec_eval_cleanup(PLpgSQL_ex
*** 191,197 ****
  static void exec_prepare_plan(PLpgSQL_execstate *estate,
                    PLpgSQL_expr *expr, int cursorOptions);
  static bool exec_simple_check_node(Node *node);
! static void exec_simple_check_plan(PLpgSQL_expr *expr);
  static void exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan);
  static void exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno);
  static bool contains_target_param(Node *node, int *target_dno);
--- 223,229 ----
  static void exec_prepare_plan(PLpgSQL_execstate *estate,
                    PLpgSQL_expr *expr, int cursorOptions);
  static bool exec_simple_check_node(Node *node);
! static void exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
  static void exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan);
  static void exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno);
  static bool contains_target_param(Node *node, int *target_dno);
*************** static void assign_text_var(PLpgSQL_exec
*** 271,281 ****
                  const char *str);
  static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate,
                         List *params);
- static void free_params_data(PreparedParamsData *ppd);
  static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
                            PLpgSQL_expr *dynquery, List *params,
                            const char *portalname, int cursorOptions);
-
  static char *format_expr_params(PLpgSQL_execstate *estate,
                     const PLpgSQL_expr *expr);
  static char *format_preparedparamsdata(PLpgSQL_execstate *estate,
--- 303,311 ----
*************** plpgsql_exec_function(PLpgSQL_function *
*** 562,567 ****
--- 592,598 ----
      /* Clean up any leftover temporary memory */
      plpgsql_destroy_econtext(&estate);
      exec_eval_cleanup(&estate);
+     /* stmt_mcontext will be destroyed when function's main context is */

      /*
       * Pop the error context stack
*************** plpgsql_exec_trigger(PLpgSQL_function *f
*** 832,837 ****
--- 863,869 ----
      /* Clean up any leftover temporary memory */
      plpgsql_destroy_econtext(&estate);
      exec_eval_cleanup(&estate);
+     /* stmt_mcontext will be destroyed when function's main context is */

      /*
       * Pop the error context stack
*************** plpgsql_exec_trigger(PLpgSQL_function *f
*** 844,849 ****
--- 876,886 ----
      return rettup;
  }

+ /* ----------
+  * plpgsql_exec_event_trigger        Called by the call handler for
+  *                event trigger execution.
+  * ----------
+  */
  void
  plpgsql_exec_event_trigger(PLpgSQL_function *func, EventTriggerData *trigdata)
  {
*************** plpgsql_exec_event_trigger(PLpgSQL_funct
*** 915,920 ****
--- 952,958 ----
      /* Clean up any leftover temporary memory */
      plpgsql_destroy_econtext(&estate);
      exec_eval_cleanup(&estate);
+     /* stmt_mcontext will be destroyed when function's main context is */

      /*
       * Pop the error context stack
*************** copy_plpgsql_datum(PLpgSQL_datum *datum)
*** 1041,1047 ****
--- 1079,1142 ----
      return result;
  }

+ /*
+  * Create a memory context for statement-lifespan variables, if we don't
+  * have one already.  It will be a child of stmt_mcontext_parent, which is
+  * either the function's main context or a pushed-down outer stmt_mcontext.
+  */
+ static MemoryContext
+ get_stmt_mcontext(PLpgSQL_execstate *estate)
+ {
+     if (estate->stmt_mcontext == NULL)
+     {
+         estate->stmt_mcontext =
+             AllocSetContextCreate(estate->stmt_mcontext_parent,
+                                   "PLpgSQL per-statement data",
+                                   ALLOCSET_DEFAULT_MINSIZE,
+                                   ALLOCSET_DEFAULT_INITSIZE,
+                                   ALLOCSET_DEFAULT_MAXSIZE);
+     }
+     return estate->stmt_mcontext;
+ }
+
+ /*
+  * Push down the current stmt_mcontext so that called statements won't use it.
+  * This is needed by statements that have statement-lifespan data and need to
+  * preserve it across some inner statements.  The caller should eventually do
+  * pop_stmt_mcontext().
+  */
+ static void
+ push_stmt_mcontext(PLpgSQL_execstate *estate)
+ {
+     /* Should have done get_stmt_mcontext() first */
+     Assert(estate->stmt_mcontext != NULL);
+     /* Assert we've not messed up the stack linkage */
+     Assert(MemoryContextGetParent(estate->stmt_mcontext) == estate->stmt_mcontext_parent);
+     /* Push it down to become the parent of any nested stmt mcontext */
+     estate->stmt_mcontext_parent = estate->stmt_mcontext;
+     /* And make it not available for use directly */
+     estate->stmt_mcontext = NULL;
+ }
+
+ /*
+  * Undo push_stmt_mcontext().  We assume this is done just before or after
+  * resetting the caller's stmt_mcontext; since that action will also delete
+  * any child contexts, there's no need to explicitly delete whatever context
+  * might currently be estate->stmt_mcontext.
+  */
+ static void
+ pop_stmt_mcontext(PLpgSQL_execstate *estate)
+ {
+     /* We need only pop the stack */
+     estate->stmt_mcontext = estate->stmt_mcontext_parent;
+     estate->stmt_mcontext_parent = MemoryContextGetParent(estate->stmt_mcontext);
+ }

+
+ /*
+  * Subroutine for exec_stmt_block: does any condition in the condition list
+  * match the current exception?
+  */
  static bool
  exception_matches_conditions(ErrorData *edata, PLpgSQL_condition *cond)
  {
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1174,1182 ****
--- 1269,1289 ----
          ResourceOwner oldowner = CurrentResourceOwner;
          ExprContext *old_eval_econtext = estate->eval_econtext;
          ErrorData  *save_cur_error = estate->cur_error;
+         MemoryContext stmt_mcontext;

          estate->err_text = gettext_noop("during statement block entry");

+         /*
+          * We will need a stmt_mcontext to hold the error data if an error
+          * occurs.  It seems best to force it to exist before entering the
+          * subtransaction, so that we reduce the risk of out-of-memory during
+          * error recovery, and because this greatly simplifies restoring the
+          * stmt_mcontext stack to the correct state after an error.  We can
+          * ameliorate the cost of this by allowing the called statements to
+          * use this mcontext too; so we don't push it down here.
+          */
+         stmt_mcontext = get_stmt_mcontext(estate);
+
          BeginInternalSubTransaction(NULL);
          /* Want to run statements inside function's memory context */
          MemoryContextSwitchTo(oldcontext);
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1202,1208 ****
               * If the block ended with RETURN, we may need to copy the return
               * value out of the subtransaction eval_context.  This is
               * currently only needed for scalar result types --- rowtype
!              * values will always exist in the function's own memory context.
               */
              if (rc == PLPGSQL_RC_RETURN &&
                  !estate->retisset &&
--- 1309,1317 ----
               * If the block ended with RETURN, we may need to copy the return
               * value out of the subtransaction eval_context.  This is
               * currently only needed for scalar result types --- rowtype
!              * values will always exist in the function's main memory context,
!              * cf. exec_stmt_return().  We can avoid a physical copy if the
!              * value happens to be a R/W expanded object.
               */
              if (rc == PLPGSQL_RC_RETURN &&
                  !estate->retisset &&
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1213,1220 ****
                  bool        resTypByVal;

                  get_typlenbyval(estate->rettype, &resTypLen, &resTypByVal);
!                 estate->retval = datumCopy(estate->retval,
!                                            resTypByVal, resTypLen);
              }

              /* Commit the inner transaction, return to outer xact context */
--- 1322,1329 ----
                  bool        resTypByVal;

                  get_typlenbyval(estate->rettype, &resTypLen, &resTypByVal);
!                 estate->retval = datumTransfer(estate->retval,
!                                                resTypByVal, resTypLen);
              }

              /* Commit the inner transaction, return to outer xact context */
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1222,1227 ****
--- 1331,1339 ----
              MemoryContextSwitchTo(oldcontext);
              CurrentResourceOwner = oldowner;

+             /* Assert that the stmt_mcontext stack is unchanged */
+             Assert(stmt_mcontext == estate->stmt_mcontext);
+
              /*
               * Revert to outer eval_econtext.  (The inner one was
               * automatically cleaned up during subxact exit.)
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1241,1248 ****

              estate->err_text = gettext_noop("during exception cleanup");

!             /* Save error info */
!             MemoryContextSwitchTo(oldcontext);
              edata = CopyErrorData();
              FlushErrorState();

--- 1353,1360 ----

              estate->err_text = gettext_noop("during exception cleanup");

!             /* Save error info in our stmt_mcontext */
!             MemoryContextSwitchTo(stmt_mcontext);
              edata = CopyErrorData();
              FlushErrorState();

*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1251,1256 ****
--- 1363,1388 ----
              MemoryContextSwitchTo(oldcontext);
              CurrentResourceOwner = oldowner;

+             /*
+              * Set up the stmt_mcontext stack as though we had restored our
+              * previous state and then done push_stmt_mcontext().  The push is
+              * needed so that statements in the exception handler won't
+              * clobber the error data that's in our stmt_mcontext.
+              */
+             estate->stmt_mcontext_parent = stmt_mcontext;
+             estate->stmt_mcontext = NULL;
+
+             /*
+              * Now we can delete any nested stmt_mcontexts that might have
+              * been created as children of ours.  (Note: we do not immediately
+              * release any statement-lifespan data that might have been left
+              * behind in stmt_mcontext itself.  We could attempt that by doing
+              * a MemoryContextReset on it before collecting the error data
+              * above, but it seems too risky to do any significant amount of
+              * work before collecting the error.)
+              */
+             MemoryContextDeleteChildren(stmt_mcontext);
+
              /* Revert to outer eval_econtext */
              estate->eval_econtext = old_eval_econtext;

*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1319,1326 ****
              /* If no match found, re-throw the error */
              if (e == NULL)
                  ReThrowError(edata);
!             else
!                 FreeErrorData(edata);
          }
          PG_END_TRY();

--- 1451,1460 ----
              /* If no match found, re-throw the error */
              if (e == NULL)
                  ReThrowError(edata);
!
!             /* Restore stmt_mcontext stack and release the error data */
!             pop_stmt_mcontext(estate);
!             MemoryContextReset(stmt_mcontext);
          }
          PG_END_TRY();

*************** exec_stmt_getdiag(PLpgSQL_execstate *est
*** 1663,1673 ****

              case PLPGSQL_GETDIAG_CONTEXT:
                  {
!                     char       *contextstackstr = GetErrorContextStack();

!                     exec_assign_c_string(estate, var, contextstackstr);

!                     pfree(contextstackstr);
                  }
                  break;

--- 1797,1811 ----

              case PLPGSQL_GETDIAG_CONTEXT:
                  {
!                     char       *contextstackstr;
!                     MemoryContext oldcontext;

!                     /* Use eval_mcontext for short-lived string */
!                     oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
!                     contextstackstr = GetErrorContextStack();
!                     MemoryContextSwitchTo(oldcontext);

!                     exec_assign_c_string(estate, var, contextstackstr);
                  }
                  break;

*************** exec_stmt_getdiag(PLpgSQL_execstate *est
*** 1677,1682 ****
--- 1815,1822 ----
          }
      }

+     exec_eval_cleanup(estate);
+
      return PLPGSQL_RC_OK;
  }

*************** exec_stmt_case(PLpgSQL_execstate *estate
*** 1738,1744 ****
          /*
           * When expected datatype is different from real, change it. Note that
           * what we're modifying here is an execution copy of the datum, so
!          * this doesn't affect the originally stored function parse tree.
           */
          if (t_var->datatype->typoid != t_typoid ||
              t_var->datatype->atttypmod != t_typmod)
--- 1878,1887 ----
          /*
           * When expected datatype is different from real, change it. Note that
           * what we're modifying here is an execution copy of the datum, so
!          * this doesn't affect the originally stored function parse tree. (In
!          * theory, if the expression datatype keeps changing during execution,
!          * this could cause a function-lifespan memory leak.  Doesn't seem
!          * worth worrying about though.)
           */
          if (t_var->datatype->typoid != t_typoid ||
              t_var->datatype->atttypmod != t_typmod)
*************** static int
*** 2132,2137 ****
--- 2275,2281 ----
  exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
  {
      PLpgSQL_var *curvar;
+     MemoryContext stmt_mcontext = NULL;
      char       *curname = NULL;
      PLpgSQL_expr *query;
      ParamListInfo paramLI;
*************** exec_stmt_forc(PLpgSQL_execstate *estate
*** 2146,2152 ****
--- 2290,2303 ----
      curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
      if (!curvar->isnull)
      {
+         MemoryContext oldcontext;
+
+         /* We only need stmt_mcontext to hold the cursor name string */
+         stmt_mcontext = get_stmt_mcontext(estate);
+         oldcontext = MemoryContextSwitchTo(stmt_mcontext);
          curname = TextDatumGetCString(curvar->value);
+         MemoryContextSwitchTo(oldcontext);
+
          if (SPI_cursor_find(curname) != NULL)
              ereport(ERROR,
                      (errcode(ERRCODE_DUPLICATE_CURSOR),
*************** exec_stmt_forc(PLpgSQL_execstate *estate
*** 2216,2225 ****
          elog(ERROR, "could not open cursor: %s",
               SPI_result_code_string(SPI_result));

-     /* don't need paramlist any more */
-     if (paramLI)
-         pfree(paramLI);
-
      /*
       * If cursor variable was NULL, store the generated portal name in it
       */
--- 2367,2372 ----
*************** exec_stmt_forc(PLpgSQL_execstate *estate
*** 2227,2232 ****
--- 2374,2386 ----
          assign_text_var(estate, curvar, portal->name);

      /*
+      * Clean up before entering exec_for_query
+      */
+     exec_eval_cleanup(estate);
+     if (stmt_mcontext)
+         MemoryContextReset(stmt_mcontext);
+
+     /*
       * Execute the loop.  We can't prefetch because the cursor is accessible
       * to the user, for instance via UPDATE WHERE CURRENT OF within the loop.
       */
*************** exec_stmt_forc(PLpgSQL_execstate *estate
*** 2241,2249 ****
      if (curname == NULL)
          assign_simple_var(estate, curvar, (Datum) 0, true, false);

-     if (curname)
-         pfree(curname);
-
      return rc;
  }

--- 2395,2400 ----
*************** exec_stmt_foreach_a(PLpgSQL_execstate *e
*** 2266,2271 ****
--- 2417,2424 ----
      Oid            loop_var_elem_type;
      bool        found = false;
      int            rc = PLPGSQL_RC_OK;
+     MemoryContext stmt_mcontext;
+     MemoryContext oldcontext;
      ArrayIterator array_iterator;
      Oid            iterator_result_type;
      int32        iterator_result_typmod;
*************** exec_stmt_foreach_a(PLpgSQL_execstate *e
*** 2279,2284 ****
--- 2432,2446 ----
                  (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
                   errmsg("FOREACH expression must not be null")));

+     /*
+      * Do as much as possible of the code below in stmt_mcontext, to avoid any
+      * leaks from called subroutines.  We need a private stmt_mcontext since
+      * we'll be calling arbitrary statement code.
+      */
+     stmt_mcontext = get_stmt_mcontext(estate);
+     push_stmt_mcontext(estate);
+     oldcontext = MemoryContextSwitchTo(stmt_mcontext);
+
      /* check the type of the expression - must be an array */
      if (!OidIsValid(get_element_type(arrtype)))
          ereport(ERROR,
*************** exec_stmt_foreach_a(PLpgSQL_execstate *e
*** 2287,2295 ****
                          format_type_be(arrtype))));

      /*
!      * We must copy the array, else it will disappear in exec_eval_cleanup.
!      * This is annoying, but cleanup will certainly happen while running the
!      * loop body, so we have little choice.
       */
      arr = DatumGetArrayTypePCopy(value);

--- 2449,2457 ----
                          format_type_be(arrtype))));

      /*
!      * We must copy the array into stmt_mcontext, else it will disappear in
!      * exec_eval_cleanup.  This is annoying, but cleanup will certainly happen
!      * while running the loop body, so we have little choice.
       */
      arr = DatumGetArrayTypePCopy(value);

*************** exec_stmt_foreach_a(PLpgSQL_execstate *e
*** 2355,2360 ****
--- 2517,2525 ----
      {
          found = true;            /* looped at least once */

+         /* exec_assign_value and exec_stmts must run in the main context */
+         MemoryContextSwitchTo(oldcontext);
+
          /* Assign current element/slice to the loop variable */
          exec_assign_value(estate, loop_var, value, isnull,
                            iterator_result_type, iterator_result_typmod);
*************** exec_stmt_foreach_a(PLpgSQL_execstate *e
*** 2413,2423 ****
                  break;
              }
          }
      }

      /* Release temporary memory, including the array value */
!     array_free_iterator(array_iterator);
!     pfree(arr);

      /*
       * Set the FOUND variable to indicate the result of executing the loop
--- 2578,2593 ----
                  break;
              }
          }
+
+         MemoryContextSwitchTo(stmt_mcontext);
      }

+     /* Restore memory context state */
+     MemoryContextSwitchTo(oldcontext);
+     pop_stmt_mcontext(estate);
+
      /* Release temporary memory, including the array value */
!     MemoryContextReset(stmt_mcontext);

      /*
       * Set the FOUND variable to indicate the result of executing the loop
*************** exec_stmt_exit(PLpgSQL_execstate *estate
*** 2465,2470 ****
--- 2635,2647 ----
  /* ----------
   * exec_stmt_return            Evaluate an expression and start
   *                    returning from the function.
+  *
+  * Note: in the retistuple code paths, the returned tuple is always in the
+  * function's main context, whereas for non-tuple data types the result may
+  * be in the eval_mcontext.  The former case is not a memory leak since we're
+  * about to exit the function anyway.  (If you want to change it, note that
+  * exec_stmt_block() knows about this behavior.)  The latter case means that
+  * we must not do exec_eval_cleanup while unwinding the control stack.
   * ----------
   */
  static int
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2639,2646 ****
  {
      TupleDesc    tupdesc;
      int            natts;
!     HeapTuple    tuple = NULL;
!     bool        free_tuple = false;

      if (!estate->retisset)
          ereport(ERROR,
--- 2816,2823 ----
  {
      TupleDesc    tupdesc;
      int            natts;
!     HeapTuple    tuple;
!     MemoryContext oldcontext;

      if (!estate->retisset)
          ereport(ERROR,
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2712,2728 ****
                                    rec->refname),
                          errdetail("The tuple structure of a not-yet-assigned"
                                    " record is indeterminate.")));
                      tupmap = convert_tuples_by_position(rec->tupdesc,
                                                          tupdesc,
                                                          gettext_noop("wrong record type supplied in RETURN NEXT"));
                      tuple = rec->tup;
-                     /* it might need conversion */
                      if (tupmap)
-                     {
                          tuple = do_convert_tuple(tuple, tupmap);
!                         free_conversion_map(tupmap);
!                         free_tuple = true;
!                     }
                  }
                  break;

--- 2889,2905 ----
                                    rec->refname),
                          errdetail("The tuple structure of a not-yet-assigned"
                                    " record is indeterminate.")));
+
+                     /* Use eval_mcontext for tuple conversion work */
+                     oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
                      tupmap = convert_tuples_by_position(rec->tupdesc,
                                                          tupdesc,
                                                          gettext_noop("wrong record type supplied in RETURN NEXT"));
                      tuple = rec->tup;
                      if (tupmap)
                          tuple = do_convert_tuple(tuple, tupmap);
!                     tuplestore_puttuple(estate->tuple_store, tuple);
!                     MemoryContextSwitchTo(oldcontext);
                  }
                  break;

*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2730,2741 ****
                  {
                      PLpgSQL_row *row = (PLpgSQL_row *) retvar;

                      tuple = make_tuple_from_row(estate, row, tupdesc);
                      if (tuple == NULL)
                          ereport(ERROR,
                                  (errcode(ERRCODE_DATATYPE_MISMATCH),
                          errmsg("wrong record type supplied in RETURN NEXT")));
!                     free_tuple = true;
                  }
                  break;

--- 2907,2921 ----
                  {
                      PLpgSQL_row *row = (PLpgSQL_row *) retvar;

+                     /* Use eval_mcontext for tuple conversion work */
+                     oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
                      tuple = make_tuple_from_row(estate, row, tupdesc);
                      if (tuple == NULL)
                          ereport(ERROR,
                                  (errcode(ERRCODE_DATATYPE_MISMATCH),
                          errmsg("wrong record type supplied in RETURN NEXT")));
!                     tuplestore_puttuple(estate->tuple_store, tuple);
!                     MemoryContextSwitchTo(oldcontext);
                  }
                  break;

*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2770,2793 ****
                              (errcode(ERRCODE_DATATYPE_MISMATCH),
                               errmsg("cannot return non-composite value from function returning composite type")));

                  tuple = get_tuple_from_datum(retval);
-                 free_tuple = true;        /* tuple is always freshly palloc'd */
-
-                 /* it might need conversion */
                  retvaldesc = get_tupdesc_from_datum(retval);
                  tupmap = convert_tuples_by_position(retvaldesc, tupdesc,
                                                      gettext_noop("returned record type does not match expected record
type"));
                  if (tupmap)
!                 {
!                     HeapTuple    newtuple;
!
!                     newtuple = do_convert_tuple(tuple, tupmap);
!                     free_conversion_map(tupmap);
!                     heap_freetuple(tuple);
!                     tuple = newtuple;
!                 }
                  ReleaseTupleDesc(retvaldesc);
!                 /* tuple will be stored into tuplestore below */
              }
              else
              {
--- 2950,2966 ----
                              (errcode(ERRCODE_DATATYPE_MISMATCH),
                               errmsg("cannot return non-composite value from function returning composite type")));

+                 /* Use eval_mcontext for tuple conversion work */
+                 oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
                  tuple = get_tuple_from_datum(retval);
                  retvaldesc = get_tupdesc_from_datum(retval);
                  tupmap = convert_tuples_by_position(retvaldesc, tupdesc,
                                                      gettext_noop("returned record type does not match expected record
type"));
                  if (tupmap)
!                     tuple = do_convert_tuple(tuple, tupmap);
!                 tuplestore_puttuple(estate->tuple_store, tuple);
                  ReleaseTupleDesc(retvaldesc);
!                 MemoryContextSwitchTo(oldcontext);
              }
              else
              {
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2795,2807 ****
                  Datum       *nulldatums;
                  bool       *nullflags;

!                 nulldatums = (Datum *) palloc0(natts * sizeof(Datum));
!                 nullflags = (bool *) palloc(natts * sizeof(bool));
                  memset(nullflags, true, natts * sizeof(bool));
                  tuplestore_putvalues(estate->tuple_store, tupdesc,
                                       nulldatums, nullflags);
-                 pfree(nulldatums);
-                 pfree(nullflags);
              }
          }
          else
--- 2968,2980 ----
                  Datum       *nulldatums;
                  bool       *nullflags;

!                 nulldatums = (Datum *)
!                     eval_mcontext_alloc0(estate, natts * sizeof(Datum));
!                 nullflags = (bool *)
!                     eval_mcontext_alloc(estate, natts * sizeof(bool));
                  memset(nullflags, true, natts * sizeof(bool));
                  tuplestore_putvalues(estate->tuple_store, tupdesc,
                                       nulldatums, nullflags);
              }
          }
          else
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2832,2845 ****
                   errmsg("RETURN NEXT must have a parameter")));
      }

-     if (HeapTupleIsValid(tuple))
-     {
-         tuplestore_puttuple(estate->tuple_store, tuple);
-
-         if (free_tuple)
-             heap_freetuple(tuple);
-     }
-
      exec_eval_cleanup(estate);

      return PLPGSQL_RC_OK;
--- 3005,3010 ----
*************** exec_stmt_return_query(PLpgSQL_execstate
*** 2858,2863 ****
--- 3023,3029 ----
      Portal        portal;
      uint64        processed = 0;
      TupleConversionMap *tupmap;
+     MemoryContext oldcontext;

      if (!estate->retisset)
          ereport(ERROR,
*************** exec_stmt_return_query(PLpgSQL_execstate
*** 2881,2886 ****
--- 3047,3055 ----
                                             CURSOR_OPT_PARALLEL_OK);
      }

+     /* Use eval_mcontext for tuple conversion work */
+     oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+
      tupmap = convert_tuples_by_position(portal->tupDesc,
                                          estate->rettupdesc,
       gettext_noop("structure of query does not match function result type"));
*************** exec_stmt_return_query(PLpgSQL_execstate
*** 2890,2895 ****
--- 3059,3068 ----
          uint64        i;

          SPI_cursor_fetch(portal, true, 50);
+
+         /* SPI will have reset CurrentMemoryContext */
+         MemoryContextSwitchTo(get_eval_mcontext(estate));
+
          if (SPI_processed == 0)
              break;

*************** exec_stmt_return_query(PLpgSQL_execstate
*** 2908,2919 ****
          SPI_freetuptable(SPI_tuptable);
      }

-     if (tupmap)
-         free_conversion_map(tupmap);
-
      SPI_freetuptable(SPI_tuptable);
      SPI_cursor_close(portal);

      estate->eval_processed = processed;
      exec_set_found(estate, processed != 0);

--- 3081,3092 ----
          SPI_freetuptable(SPI_tuptable);
      }

      SPI_freetuptable(SPI_tuptable);
      SPI_cursor_close(portal);

+     MemoryContextSwitchTo(oldcontext);
+     exec_eval_cleanup(estate);
+
      estate->eval_processed = processed;
      exec_set_found(estate, processed != 0);

*************** do { \
*** 2965,2971 ****
                  (errcode(ERRCODE_SYNTAX_ERROR), \
                   errmsg("RAISE option already specified: %s", \
                          name))); \
!     opt = pstrdup(extval); \
  } while (0)

  /* ----------
--- 3138,3144 ----
                  (errcode(ERRCODE_SYNTAX_ERROR), \
                   errmsg("RAISE option already specified: %s", \
                          name))); \
!     opt = MemoryContextStrdup(stmt_mcontext, extval); \
  } while (0)

  /* ----------
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 2985,2990 ****
--- 3158,3164 ----
      char       *err_datatype = NULL;
      char       *err_table = NULL;
      char       *err_schema = NULL;
+     MemoryContext stmt_mcontext;
      ListCell   *lc;

      /* RAISE with no parameters: re-throw current exception */
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 2999,3008 ****
           errmsg("RAISE without parameters cannot be used outside an exception handler")));
      }

      if (stmt->condname)
      {
          err_code = plpgsql_recognize_err_condition(stmt->condname, true);
!         condname = pstrdup(stmt->condname);
      }

      if (stmt->message)
--- 3173,3185 ----
           errmsg("RAISE without parameters cannot be used outside an exception handler")));
      }

+     /* We'll need to accumulate the various strings in stmt_mcontext */
+     stmt_mcontext = get_stmt_mcontext(estate);
+
      if (stmt->condname)
      {
          err_code = plpgsql_recognize_err_condition(stmt->condname, true);
!         condname = MemoryContextStrdup(stmt_mcontext, stmt->condname);
      }

      if (stmt->message)
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 3010,3017 ****
--- 3187,3199 ----
          StringInfoData ds;
          ListCell   *current_param;
          char       *cp;
+         MemoryContext oldcontext;

+         /* build string in stmt_mcontext */
+         oldcontext = MemoryContextSwitchTo(stmt_mcontext);
          initStringInfo(&ds);
+         MemoryContextSwitchTo(oldcontext);
+
          current_param = list_head(stmt->params);

          for (cp = stmt->message; *cp; cp++)
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 3064,3070 ****
              elog(ERROR, "unexpected RAISE parameter list length");

          err_message = ds.data;
-         /* No pfree(ds.data), the pfree(err_message) does it */
      }

      foreach(lc, stmt->options)
--- 3246,3251 ----
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 3096,3102 ****
                               errmsg("RAISE option already specified: %s",
                                      "ERRCODE")));
                  err_code = plpgsql_recognize_err_condition(extval, true);
!                 condname = pstrdup(extval);
                  break;
              case PLPGSQL_RAISEOPTION_MESSAGE:
                  SET_RAISE_OPTION_TEXT(err_message, "MESSAGE");
--- 3277,3283 ----
                               errmsg("RAISE option already specified: %s",
                                      "ERRCODE")));
                  err_code = plpgsql_recognize_err_condition(extval, true);
!                 condname = MemoryContextStrdup(stmt_mcontext, extval);
                  break;
              case PLPGSQL_RAISEOPTION_MESSAGE:
                  SET_RAISE_OPTION_TEXT(err_message, "MESSAGE");
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 3142,3148 ****
              condname = NULL;
          }
          else
!             err_message = pstrdup(unpack_sql_state(err_code));
      }

      /*
--- 3323,3330 ----
              condname = NULL;
          }
          else
!             err_message = MemoryContextStrdup(stmt_mcontext,
!                                               unpack_sql_state(err_code));
      }

      /*
*************** exec_stmt_raise(PLpgSQL_execstate *estat
*** 3164,3187 ****
               (err_schema != NULL) ?
               err_generic_string(PG_DIAG_SCHEMA_NAME, err_schema) : 0));

!     if (condname != NULL)
!         pfree(condname);
!     if (err_message != NULL)
!         pfree(err_message);
!     if (err_detail != NULL)
!         pfree(err_detail);
!     if (err_hint != NULL)
!         pfree(err_hint);
!     if (err_column != NULL)
!         pfree(err_column);
!     if (err_constraint != NULL)
!         pfree(err_constraint);
!     if (err_datatype != NULL)
!         pfree(err_datatype);
!     if (err_table != NULL)
!         pfree(err_table);
!     if (err_schema != NULL)
!         pfree(err_schema);

      return PLPGSQL_RC_OK;
  }
--- 3346,3353 ----
               (err_schema != NULL) ?
               err_generic_string(PG_DIAG_SCHEMA_NAME, err_schema) : 0));

!     /* Clean up transient strings */
!     MemoryContextReset(stmt_mcontext);

      return PLPGSQL_RC_OK;
  }
*************** plpgsql_estate_setup(PLpgSQL_execstate *
*** 3329,3334 ****
--- 3495,3508 ----
          estate->cast_hash_context = shared_cast_context;
      }

+     /*
+      * We start with no stmt_mcontext; one will be created only if needed.
+      * That context will be a direct child of the function's main execution
+      * context.  Additional stmt_mcontexts might be created as children of it.
+      */
+     estate->stmt_mcontext = NULL;
+     estate->stmt_mcontext_parent = CurrentMemoryContext;
+
      estate->eval_tuptable = NULL;
      estate->eval_processed = 0;
      estate->eval_lastoid = InvalidOid;
*************** exec_eval_cleanup(PLpgSQL_execstate *est
*** 3378,3384 ****
          SPI_freetuptable(estate->eval_tuptable);
      estate->eval_tuptable = NULL;

!     /* Clear result of exec_eval_simple_expr (but keep the econtext) */
      if (estate->eval_econtext != NULL)
          ResetExprContext(estate->eval_econtext);
  }
--- 3552,3561 ----
          SPI_freetuptable(estate->eval_tuptable);
      estate->eval_tuptable = NULL;

!     /*
!      * Clear result of exec_eval_simple_expr (but keep the econtext).  This
!      * also clears any short-lived allocations done via get_eval_mcontext.
!      */
      if (estate->eval_econtext != NULL)
          ResetExprContext(estate->eval_econtext);
  }
*************** exec_prepare_plan(PLpgSQL_execstate *est
*** 3430,3436 ****
      expr->plan = plan;

      /* Check to see if it's a simple expression */
!     exec_simple_check_plan(expr);

      /*
       * Mark expression as not using a read-write param.  exec_assign_value has
--- 3607,3613 ----
      expr->plan = plan;

      /* Check to see if it's a simple expression */
!     exec_simple_check_plan(estate, expr);

      /*
       * Mark expression as not using a read-write param.  exec_assign_value has
*************** exec_prepare_plan(PLpgSQL_execstate *est
*** 3443,3448 ****
--- 3620,3628 ----

  /* ----------
   * exec_stmt_execsql            Execute an SQL statement (possibly with INTO).
+  *
+  * Note: some callers rely on this not touching stmt_mcontext.  If it ever
+  * needs to use that, fix those callers to push/pop stmt_mcontext.
   * ----------
   */
  static int
*************** exec_stmt_dynexecute(PLpgSQL_execstate *
*** 3675,3680 ****
--- 3855,3861 ----
      char       *querystr;
      int            exec_res;
      PreparedParamsData *ppd = NULL;
+     MemoryContext stmt_mcontext = get_stmt_mcontext(estate);

      /*
       * First we evaluate the string expression after the EXECUTE keyword. Its
*************** exec_stmt_dynexecute(PLpgSQL_execstate *
*** 3689,3696 ****
      /* Get the C-String representation */
      querystr = convert_value_to_string(estate, query, restype);

!     /* copy it out of the temporary context before we clean up */
!     querystr = pstrdup(querystr);

      exec_eval_cleanup(estate);

--- 3870,3877 ----
      /* Get the C-String representation */
      querystr = convert_value_to_string(estate, query, restype);

!     /* copy it into the stmt_mcontext before we clean up */
!     querystr = MemoryContextStrdup(stmt_mcontext, querystr);

      exec_eval_cleanup(estate);

*************** exec_stmt_dynexecute(PLpgSQL_execstate *
*** 3843,3854 ****
           */
      }

!     if (ppd)
!         free_params_data(ppd);
!
!     /* Release any result from SPI_execute, as well as the querystring */
      SPI_freetuptable(SPI_tuptable);
!     pfree(querystr);

      return PLPGSQL_RC_OK;
  }
--- 4024,4032 ----
           */
      }

!     /* Release any result from SPI_execute, as well as transient data */
      SPI_freetuptable(SPI_tuptable);
!     MemoryContextReset(stmt_mcontext);

      return PLPGSQL_RC_OK;
  }
*************** static int
*** 3892,3897 ****
--- 4070,4076 ----
  exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
  {
      PLpgSQL_var *curvar;
+     MemoryContext stmt_mcontext = NULL;
      char       *curname = NULL;
      PLpgSQL_expr *query;
      Portal        portal;
*************** exec_stmt_open(PLpgSQL_execstate *estate
*** 3905,3911 ****
--- 4084,4097 ----
      curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
      if (!curvar->isnull)
      {
+         MemoryContext oldcontext;
+
+         /* We only need stmt_mcontext to hold the cursor name string */
+         stmt_mcontext = get_stmt_mcontext(estate);
+         oldcontext = MemoryContextSwitchTo(stmt_mcontext);
          curname = TextDatumGetCString(curvar->value);
+         MemoryContextSwitchTo(oldcontext);
+
          if (SPI_cursor_find(curname) != NULL)
              ereport(ERROR,
                      (errcode(ERRCODE_DUPLICATE_CURSOR),
*************** exec_stmt_open(PLpgSQL_execstate *estate
*** 3942,3948 ****
                                             stmt->cursor_options);

          /*
!          * If cursor variable was NULL, store the generated portal name in it
           */
          if (curname == NULL)
              assign_text_var(estate, curvar, portal->name);
--- 4128,4137 ----
                                             stmt->cursor_options);

          /*
!          * If cursor variable was NULL, store the generated portal name in it.
!          * Note: exec_dynquery_with_params already reset the stmt_mcontext, so
!          * curname is a dangling pointer here; but testing it for nullness is
!          * OK.
           */
          if (curname == NULL)
              assign_text_var(estate, curvar, portal->name);
*************** exec_stmt_open(PLpgSQL_execstate *estate
*** 4019,4028 ****
      if (curname == NULL)
          assign_text_var(estate, curvar, portal->name);

!     if (curname)
!         pfree(curname);
!     if (paramLI)
!         pfree(paramLI);

      return PLPGSQL_RC_OK;
  }
--- 4208,4217 ----
      if (curname == NULL)
          assign_text_var(estate, curvar, portal->name);

!     /* If we had any transient data, clean it up */
!     exec_eval_cleanup(estate);
!     if (stmt_mcontext)
!         MemoryContextReset(stmt_mcontext);

      return PLPGSQL_RC_OK;
  }
*************** exec_stmt_open(PLpgSQL_execstate *estate
*** 4036,4042 ****
  static int
  exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
  {
!     PLpgSQL_var *curvar = NULL;
      PLpgSQL_rec *rec = NULL;
      PLpgSQL_row *row = NULL;
      long        how_many = stmt->how_many;
--- 4225,4231 ----
  static int
  exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
  {
!     PLpgSQL_var *curvar;
      PLpgSQL_rec *rec = NULL;
      PLpgSQL_row *row = NULL;
      long        how_many = stmt->how_many;
*************** exec_stmt_fetch(PLpgSQL_execstate *estat
*** 4044,4049 ****
--- 4233,4239 ----
      Portal        portal;
      char       *curname;
      uint64        n;
+     MemoryContext oldcontext;

      /* ----------
       * Get the portal of the cursor by name
*************** exec_stmt_fetch(PLpgSQL_execstate *estat
*** 4054,4067 ****
          ereport(ERROR,
                  (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
                   errmsg("cursor variable \"%s\" is null", curvar->refname)));
      curname = TextDatumGetCString(curvar->value);

      portal = SPI_cursor_find(curname);
      if (portal == NULL)
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_CURSOR),
                   errmsg("cursor \"%s\" does not exist", curname)));
-     pfree(curname);

      /* Calculate position for FETCH_RELATIVE or FETCH_ABSOLUTE */
      if (stmt->expr)
--- 4244,4260 ----
          ereport(ERROR,
                  (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
                   errmsg("cursor variable \"%s\" is null", curvar->refname)));
+
+     /* Use eval_mcontext for short-lived string */
+     oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
      curname = TextDatumGetCString(curvar->value);
+     MemoryContextSwitchTo(oldcontext);

      portal = SPI_cursor_find(curname);
      if (portal == NULL)
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_CURSOR),
                   errmsg("cursor \"%s\" does not exist", curname)));

      /* Calculate position for FETCH_RELATIVE or FETCH_ABSOLUTE */
      if (stmt->expr)
*************** exec_stmt_fetch(PLpgSQL_execstate *estat
*** 4133,4141 ****
  static int
  exec_stmt_close(PLpgSQL_execstate *estate, PLpgSQL_stmt_close *stmt)
  {
!     PLpgSQL_var *curvar = NULL;
      Portal        portal;
      char       *curname;

      /* ----------
       * Get the portal of the cursor by name
--- 4326,4335 ----
  static int
  exec_stmt_close(PLpgSQL_execstate *estate, PLpgSQL_stmt_close *stmt)
  {
!     PLpgSQL_var *curvar;
      Portal        portal;
      char       *curname;
+     MemoryContext oldcontext;

      /* ----------
       * Get the portal of the cursor by name
*************** exec_stmt_close(PLpgSQL_execstate *estat
*** 4146,4159 ****
          ereport(ERROR,
                  (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
                   errmsg("cursor variable \"%s\" is null", curvar->refname)));
      curname = TextDatumGetCString(curvar->value);

      portal = SPI_cursor_find(curname);
      if (portal == NULL)
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_CURSOR),
                   errmsg("cursor \"%s\" does not exist", curname)));
-     pfree(curname);

      /* ----------
       * And close it.
--- 4340,4356 ----
          ereport(ERROR,
                  (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
                   errmsg("cursor variable \"%s\" is null", curvar->refname)));
+
+     /* Use eval_mcontext for short-lived string */
+     oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
      curname = TextDatumGetCString(curvar->value);
+     MemoryContextSwitchTo(oldcontext);

      portal = SPI_cursor_find(curname);
      if (portal == NULL)
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_CURSOR),
                   errmsg("cursor \"%s\" does not exist", curname)));

      /* ----------
       * And close it.
*************** exec_assign_expr(PLpgSQL_execstate *esta
*** 4201,4206 ****
--- 4398,4406 ----
   * exec_assign_c_string        Put a C string into a text variable.
   *
   * We take a NULL pointer as signifying empty string, not SQL null.
+  *
+  * As with the underlying exec_assign_value, caller is expected to do
+  * exec_eval_cleanup later.
   * ----------
   */
  static void
*************** exec_assign_c_string(PLpgSQL_execstate *
*** 4208,4228 ****
                       const char *str)
  {
      text       *value;

      if (str != NULL)
          value = cstring_to_text(str);
      else
          value = cstring_to_text("");
      exec_assign_value(estate, target, PointerGetDatum(value), false,
                        TEXTOID, -1);
-     pfree(value);
  }


  /* ----------
   * exec_assign_value            Put a value into a target datum
   *
!  * Note: in some code paths, this will leak memory in the eval_econtext;
   * we assume that will be cleaned up later by exec_eval_cleanup.  We cannot
   * call exec_eval_cleanup here for fear of destroying the input Datum value.
   * ----------
--- 4408,4432 ----
                       const char *str)
  {
      text       *value;
+     MemoryContext oldcontext;

+     /* Use eval_mcontext for short-lived text value */
+     oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
      if (str != NULL)
          value = cstring_to_text(str);
      else
          value = cstring_to_text("");
+     MemoryContextSwitchTo(oldcontext);
+
      exec_assign_value(estate, target, PointerGetDatum(value), false,
                        TEXTOID, -1);
  }


  /* ----------
   * exec_assign_value            Put a value into a target datum
   *
!  * Note: in some code paths, this will leak memory in the eval_mcontext;
   * we assume that will be cleaned up later by exec_eval_cleanup.  We cannot
   * call exec_eval_cleanup here for fear of destroying the input Datum value.
   * ----------
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4259,4268 ****

                  /*
                   * If type is by-reference, copy the new value (which is
!                  * probably in the eval_econtext) into the procedure's memory
!                  * context.  But if it's a read/write reference to an expanded
!                  * object, no physical copy needs to happen; at most we need
!                  * to reparent the object's memory context.
                   *
                   * If it's an array, we force the value to be stored in R/W
                   * expanded form.  This wins if the function later does, say,
--- 4463,4472 ----

                  /*
                   * If type is by-reference, copy the new value (which is
!                  * probably in the eval_mcontext) into the procedure's main
!                  * memory context.  But if it's a read/write reference to an
!                  * expanded object, no physical copy needs to happen; at most
!                  * we need to reparent the object's memory context.
                   *
                   * If it's an array, we force the value to be stored in R/W
                   * expanded form.  This wins if the function later does, say,
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4402,4410 ****
                   * the attributes except the one we want to replace, use the
                   * value that's in the old tuple.
                   */
!                 values = palloc(sizeof(Datum) * natts);
!                 nulls = palloc(sizeof(bool) * natts);
!                 replaces = palloc(sizeof(bool) * natts);

                  memset(replaces, false, sizeof(bool) * natts);
                  replaces[fno] = true;
--- 4606,4614 ----
                   * the attributes except the one we want to replace, use the
                   * value that's in the old tuple.
                   */
!                 values = eval_mcontext_alloc(estate, sizeof(Datum) * natts);
!                 nulls = eval_mcontext_alloc(estate, sizeof(bool) * natts);
!                 replaces = eval_mcontext_alloc(estate, sizeof(bool) * natts);

                  memset(replaces, false, sizeof(bool) * natts);
                  replaces[fno] = true;
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4437,4446 ****
                  rec->tup = newtup;
                  rec->freetup = true;

-                 pfree(values);
-                 pfree(nulls);
-                 pfree(replaces);
-
                  break;
              }

--- 4641,4646 ----
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4601,4607 ****
                      return;

                  /* empty array, if any, and newarraydatum are short-lived */
!                 oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);

                  if (oldarrayisnull)
                      oldarraydatum = PointerGetDatum(construct_empty_array(arrayelem->elemtypoid));
--- 4801,4807 ----
                      return;

                  /* empty array, if any, and newarraydatum are short-lived */
!                 oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));

                  if (oldarrayisnull)
                      oldarraydatum = PointerGetDatum(construct_empty_array(arrayelem->elemtypoid));
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4655,4661 ****
   * responsibility that the results are semantically OK.
   *
   * In some cases we have to palloc a return value, and in such cases we put
!  * it into the estate's short-term memory context.
   */
  static void
  exec_eval_datum(PLpgSQL_execstate *estate,
--- 4855,4861 ----
   * responsibility that the results are semantically OK.
   *
   * In some cases we have to palloc a return value, and in such cases we put
!  * it into the estate's eval_mcontext.
   */
  static void
  exec_eval_datum(PLpgSQL_execstate *estate,
*************** exec_eval_datum(PLpgSQL_execstate *estat
*** 4689,4695 ****
                      elog(ERROR, "row variable has no tupdesc");
                  /* Make sure we have a valid type/typmod setting */
                  BlessTupleDesc(row->rowtupdesc);
!                 oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
                  tup = make_tuple_from_row(estate, row, row->rowtupdesc);
                  if (tup == NULL)    /* should not happen */
                      elog(ERROR, "row not compatible with its own tupdesc");
--- 4889,4895 ----
                      elog(ERROR, "row variable has no tupdesc");
                  /* Make sure we have a valid type/typmod setting */
                  BlessTupleDesc(row->rowtupdesc);
!                 oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
                  tup = make_tuple_from_row(estate, row, row->rowtupdesc);
                  if (tup == NULL)    /* should not happen */
                      elog(ERROR, "row not compatible with its own tupdesc");
*************** exec_eval_datum(PLpgSQL_execstate *estat
*** 4715,4721 ****
                  /* Make sure we have a valid type/typmod setting */
                  BlessTupleDesc(rec->tupdesc);

!                 oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
                  *typeid = rec->tupdesc->tdtypeid;
                  *typetypmod = rec->tupdesc->tdtypmod;
                  *value = heap_copy_tuple_as_datum(rec->tup, rec->tupdesc);
--- 4915,4921 ----
                  /* Make sure we have a valid type/typmod setting */
                  BlessTupleDesc(rec->tupdesc);

!                 oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
                  *typeid = rec->tupdesc->tdtypeid;
                  *typetypmod = rec->tupdesc->tdtypmod;
                  *value = heap_copy_tuple_as_datum(rec->tup, rec->tupdesc);
*************** exec_run_select(PLpgSQL_execstate *estat
*** 5107,5114 ****
          if (*portalP == NULL)
              elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
                   expr->query, SPI_result_code_string(SPI_result));
!         if (paramLI)
!             pfree(paramLI);
          return SPI_OK_CURSOR;
      }

--- 5307,5313 ----
          if (*portalP == NULL)
              elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
                   expr->query, SPI_result_code_string(SPI_result));
!         exec_eval_cleanup(estate);
          return SPI_OK_CURSOR;
      }

*************** loop_exit:
*** 5323,5331 ****
   * give correct results if that happens, and it's unlikely to be worth the
   * cycles to check.
   *
!  * Note: if pass-by-reference, the result is in the eval_econtext's
!  * temporary memory context.  It will be freed when exec_eval_cleanup
!  * is done.
   * ----------
   */
  static bool
--- 5522,5529 ----
   * give correct results if that happens, and it's unlikely to be worth the
   * cycles to check.
   *
!  * Note: if pass-by-reference, the result is in the eval_mcontext.
!  * It will be freed when exec_eval_cleanup is done.
   * ----------
   */
  static bool
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 5357,5365 ****

      /*
       * Revalidate cached plan, so that we will notice if it became stale. (We
!      * need to hold a refcount while using the plan, anyway.)
       */
      cplan = SPI_plan_get_cached_plan(expr->plan);

      /*
       * We can't get a failure here, because the number of CachedPlanSources in
--- 5555,5566 ----

      /*
       * Revalidate cached plan, so that we will notice if it became stale. (We
!      * need to hold a refcount while using the plan, anyway.)  If replanning
!      * is needed, do that work in the eval_mcontext.
       */
+     oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
      cplan = SPI_plan_get_cached_plan(expr->plan);
+     MemoryContextSwitchTo(oldcontext);

      /*
       * We can't get a failure here, because the number of CachedPlanSources in
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 5411,5417 ****
       */
      SPI_push();

!     oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
      if (!estate->readonly_func)
      {
          CommandCounterIncrement();
--- 5612,5618 ----
       */
      SPI_push();

!     oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
      if (!estate->readonly_func)
      {
          CommandCounterIncrement();
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 5449,5456 ****
      /* Assorted cleanup */
      expr->expr_simple_in_use = false;

!     if (paramLI && paramLI != estate->paramLI)
!         pfree(paramLI);

      estate->paramLI->parserSetupArg = save_setup_arg;

--- 5650,5656 ----
      /* Assorted cleanup */
      expr->expr_simple_in_use = false;

!     econtext->ecxt_param_list_info = NULL;

      estate->paramLI->parserSetupArg = save_setup_arg;

*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 5498,5505 ****
   * throw errors (for example "no such record field") and we do not want that
   * to happen in a part of the expression that might never be evaluated at
   * runtime.  For another thing, exec_eval_datum() may return short-lived
!  * values stored in the estate's short-term memory context, which will not
!  * necessarily survive to the next SPI operation.  And for a third thing, ROW
   * and RECFIELD datums' values depend on other datums, and we don't have a
   * cheap way to track that.  Therefore, param slots for non-VAR datum types
   * are always reset here and then filled on-demand by plpgsql_param_fetch().
--- 5698,5705 ----
   * throw errors (for example "no such record field") and we do not want that
   * to happen in a part of the expression that might never be evaluated at
   * runtime.  For another thing, exec_eval_datum() may return short-lived
!  * values stored in the estate's eval_mcontext, which will not necessarily
!  * survive to the next SPI operation.  And for a third thing, ROW
   * and RECFIELD datums' values depend on other datums, and we don't have a
   * cheap way to track that.  Therefore, param slots for non-VAR datum types
   * are always reset here and then filled on-demand by plpgsql_param_fetch().
*************** setup_param_list(PLpgSQL_execstate *esta
*** 5598,5604 ****
   * to some trusted function.  We don't want the R/W pointer to get into the
   * shared param list, where it could get passed to some less-trusted function.
   *
!  * Caller should pfree the result after use, if it's not NULL.
   *
   * XXX. Could we use ParamListInfo's new paramMask to avoid creating unshared
   * parameter lists?
--- 5798,5804 ----
   * to some trusted function.  We don't want the R/W pointer to get into the
   * shared param list, where it could get passed to some less-trusted function.
   *
!  * The result, if not NULL, is in the estate's eval_mcontext.
   *
   * XXX. Could we use ParamListInfo's new paramMask to avoid creating unshared
   * parameter lists?
*************** setup_unshared_param_list(PLpgSQL_execst
*** 5626,5633 ****

          /* initialize ParamListInfo with one entry per datum, all invalid */
          paramLI = (ParamListInfo)
!             palloc0(offsetof(ParamListInfoData, params) +
!                     estate->ndatums * sizeof(ParamExternData));
          paramLI->paramFetch = plpgsql_param_fetch;
          paramLI->paramFetchArg = (void *) estate;
          paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
--- 5826,5834 ----

          /* initialize ParamListInfo with one entry per datum, all invalid */
          paramLI = (ParamListInfo)
!             eval_mcontext_alloc0(estate,
!                                  offsetof(ParamListInfoData, params) +
!                                  estate->ndatums * sizeof(ParamExternData));
          paramLI->paramFetch = plpgsql_param_fetch;
          paramLI->paramFetchArg = (void *) estate;
          paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
*************** exec_move_row(PLpgSQL_execstate *estate,
*** 5784,5795 ****
              /* If we have a tupdesc but no data, form an all-nulls tuple */
              bool       *nulls;

!             nulls = (bool *) palloc(tupdesc->natts * sizeof(bool));
              memset(nulls, true, tupdesc->natts * sizeof(bool));

              tup = heap_form_tuple(tupdesc, NULL, nulls);
-
-             pfree(nulls);
          }

          if (tupdesc)
--- 5985,5995 ----
              /* If we have a tupdesc but no data, form an all-nulls tuple */
              bool       *nulls;

!             nulls = (bool *)
!                 eval_mcontext_alloc(estate, tupdesc->natts * sizeof(bool));
              memset(nulls, true, tupdesc->natts * sizeof(bool));

              tup = heap_form_tuple(tupdesc, NULL, nulls);
          }

          if (tupdesc)
*************** exec_move_row(PLpgSQL_execstate *estate,
*** 5907,5912 ****
--- 6107,6115 ----
   * make_tuple_from_row        Make a tuple from the values of a row object
   *
   * A NULL return indicates rowtype mismatch; caller must raise suitable error
+  *
+  * The result tuple is freshly palloc'd in caller's context.  Some junk
+  * may be left behind in eval_mcontext, too.
   * ----------
   */
  static HeapTuple
*************** make_tuple_from_row(PLpgSQL_execstate *e
*** 5923,5930 ****
      if (natts != row->nfields)
          return NULL;

!     dvalues = (Datum *) palloc0(natts * sizeof(Datum));
!     nulls = (bool *) palloc(natts * sizeof(bool));

      for (i = 0; i < natts; i++)
      {
--- 6126,6133 ----
      if (natts != row->nfields)
          return NULL;

!     dvalues = (Datum *) eval_mcontext_alloc0(estate, natts * sizeof(Datum));
!     nulls = (bool *) eval_mcontext_alloc(estate, natts * sizeof(bool));

      for (i = 0; i < natts; i++)
      {
*************** make_tuple_from_row(PLpgSQL_execstate *e
*** 5949,5964 ****

      tuple = heap_form_tuple(tupdesc, dvalues, nulls);

-     pfree(dvalues);
-     pfree(nulls);
-
      return tuple;
  }

  /* ----------
   * get_tuple_from_datum        extract a tuple from a composite Datum
   *
!  * Returns a freshly palloc'd HeapTuple.
   *
   * Note: it's caller's responsibility to be sure value is of composite type.
   * ----------
--- 6152,6164 ----

      tuple = heap_form_tuple(tupdesc, dvalues, nulls);

      return tuple;
  }

  /* ----------
   * get_tuple_from_datum        extract a tuple from a composite Datum
   *
!  * Returns a HeapTuple, freshly palloc'd in caller's context.
   *
   * Note: it's caller's responsibility to be sure value is of composite type.
   * ----------
*************** exec_move_row_from_datum(PLpgSQL_execsta
*** 6041,6047 ****
  /* ----------
   * convert_value_to_string            Convert a non-null Datum to C string
   *
!  * Note: the result is in the estate's eval_econtext, and will be cleared
   * by the next exec_eval_cleanup() call.  The invoked output function might
   * leave additional cruft there as well, so just pfree'ing the result string
   * would not be enough to avoid memory leaks if we did not do it like this.
--- 6241,6247 ----
  /* ----------
   * convert_value_to_string            Convert a non-null Datum to C string
   *
!  * Note: the result is in the estate's eval_mcontext, and will be cleared
   * by the next exec_eval_cleanup() call.  The invoked output function might
   * leave additional cruft there as well, so just pfree'ing the result string
   * would not be enough to avoid memory leaks if we did not do it like this.
*************** convert_value_to_string(PLpgSQL_execstat
*** 6061,6067 ****
      Oid            typoutput;
      bool        typIsVarlena;

!     oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
      getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
      result = OidOutputFunctionCall(typoutput, value);
      MemoryContextSwitchTo(oldcontext);
--- 6261,6267 ----
      Oid            typoutput;
      bool        typIsVarlena;

!     oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
      getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
      result = OidOutputFunctionCall(typoutput, value);
      MemoryContextSwitchTo(oldcontext);
*************** convert_value_to_string(PLpgSQL_execstat
*** 6076,6082 ****
   * unlikely that a cast operation would produce null from non-null or vice
   * versa, that could happen in principle.
   *
!  * Note: the estate's eval_econtext is used for temporary storage, and may
   * also contain the result Datum if we have to do a conversion to a pass-
   * by-reference data type.  Be sure to do an exec_eval_cleanup() call when
   * done with the result.
--- 6276,6282 ----
   * unlikely that a cast operation would produce null from non-null or vice
   * versa, that could happen in principle.
   *
!  * Note: the estate's eval_mcontext is used for temporary storage, and may
   * also contain the result Datum if we have to do a conversion to a pass-
   * by-reference data type.  Be sure to do an exec_eval_cleanup() call when
   * done with the result.
*************** exec_cast_value(PLpgSQL_execstate *estat
*** 6104,6110 ****
              ExprContext *econtext = estate->eval_econtext;
              MemoryContext oldcontext;

!             oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);

              econtext->caseValue_datum = value;
              econtext->caseValue_isNull = *isnull;
--- 6304,6310 ----
              ExprContext *econtext = estate->eval_econtext;
              MemoryContext oldcontext;

!             oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));

              econtext->caseValue_datum = value;
              econtext->caseValue_isNull = *isnull;
*************** get_cast_hashentry(PLpgSQL_execstate *es
*** 6161,6170 ****

          /*
           * Since we could easily fail (no such coercion), construct a
!          * temporary coercion expression tree in a short-lived context, then
!          * if successful copy it to cast_hash_context.
           */
!         oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);

          /*
           * We use a CaseTestExpr as the base of the coercion tree, since it's
--- 6361,6370 ----

          /*
           * Since we could easily fail (no such coercion), construct a
!          * temporary coercion expression tree in the short-lived
!          * eval_mcontext, then if successful copy it to cast_hash_context.
           */
!         oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));

          /*
           * We use a CaseTestExpr as the base of the coercion tree, since it's
*************** exec_simple_check_node(Node *node)
*** 6545,6556 ****
   * ----------
   */
  static void
! exec_simple_check_plan(PLpgSQL_expr *expr)
  {
      List       *plansources;
      CachedPlanSource *plansource;
      Query       *query;
      CachedPlan *cplan;

      /*
       * Initialize to "not simple", and remember the plan generation number we
--- 6745,6757 ----
   * ----------
   */
  static void
! exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
  {
      List       *plansources;
      CachedPlanSource *plansource;
      Query       *query;
      CachedPlan *cplan;
+     MemoryContext oldcontext;

      /*
       * Initialize to "not simple", and remember the plan generation number we
*************** exec_simple_check_plan(PLpgSQL_expr *exp
*** 6621,6630 ****

      /*
       * OK, it seems worth constructing a plan for more careful checking.
       */
!
!     /* Get the generic plan for the query */
      cplan = SPI_plan_get_cached_plan(expr->plan);

      /* Can't fail, because we checked for a single CachedPlanSource above */
      Assert(cplan != NULL);
--- 6822,6834 ----

      /*
       * OK, it seems worth constructing a plan for more careful checking.
+      *
+      * Get the generic plan for the query.  If replanning is needed, do that
+      * work in the eval_mcontext.
       */
!     oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
      cplan = SPI_plan_get_cached_plan(expr->plan);
+     MemoryContextSwitchTo(oldcontext);

      /* Can't fail, because we checked for a single CachedPlanSource above */
      Assert(cplan != NULL);
*************** assign_text_var(PLpgSQL_execstate *estat
*** 7008,7030 ****

  /*
   * exec_eval_using_params --- evaluate params of USING clause
   */
  static PreparedParamsData *
  exec_eval_using_params(PLpgSQL_execstate *estate, List *params)
  {
      PreparedParamsData *ppd;
      int            nargs;
      int            i;
      ListCell   *lc;

!     ppd = (PreparedParamsData *) palloc(sizeof(PreparedParamsData));
      nargs = list_length(params);

      ppd->nargs = nargs;
!     ppd->types = (Oid *) palloc(nargs * sizeof(Oid));
!     ppd->values = (Datum *) palloc(nargs * sizeof(Datum));
!     ppd->nulls = (char *) palloc(nargs * sizeof(char));
!     ppd->freevals = (bool *) palloc(nargs * sizeof(bool));

      i = 0;
      foreach(lc, params)
--- 7212,7241 ----

  /*
   * exec_eval_using_params --- evaluate params of USING clause
+  *
+  * The result data structure is created in the stmt_mcontext, and should
+  * be freed by resetting that context.
   */
  static PreparedParamsData *
  exec_eval_using_params(PLpgSQL_execstate *estate, List *params)
  {
      PreparedParamsData *ppd;
+     MemoryContext stmt_mcontext = get_stmt_mcontext(estate);
      int            nargs;
      int            i;
      ListCell   *lc;

!     ppd = (PreparedParamsData *)
!         MemoryContextAlloc(stmt_mcontext, sizeof(PreparedParamsData));
      nargs = list_length(params);

      ppd->nargs = nargs;
!     ppd->types = (Oid *)
!         MemoryContextAlloc(stmt_mcontext, nargs * sizeof(Oid));
!     ppd->values = (Datum *)
!         MemoryContextAlloc(stmt_mcontext, nargs * sizeof(Datum));
!     ppd->nulls = (char *)
!         MemoryContextAlloc(stmt_mcontext, nargs * sizeof(char));

      i = 0;
      foreach(lc, params)
*************** exec_eval_using_params(PLpgSQL_execstate
*** 7032,7044 ****
          PLpgSQL_expr *param = (PLpgSQL_expr *) lfirst(lc);
          bool        isnull;
          int32        ppdtypmod;

          ppd->values[i] = exec_eval_expr(estate, param,
                                          &isnull,
                                          &ppd->types[i],
                                          &ppdtypmod);
          ppd->nulls[i] = isnull ? 'n' : ' ';
!         ppd->freevals[i] = false;

          if (ppd->types[i] == UNKNOWNOID)
          {
--- 7243,7257 ----
          PLpgSQL_expr *param = (PLpgSQL_expr *) lfirst(lc);
          bool        isnull;
          int32        ppdtypmod;
+         MemoryContext oldcontext;

          ppd->values[i] = exec_eval_expr(estate, param,
                                          &isnull,
                                          &ppd->types[i],
                                          &ppdtypmod);
          ppd->nulls[i] = isnull ? 'n' : ' ';
!
!         oldcontext = MemoryContextSwitchTo(stmt_mcontext);

          if (ppd->types[i] == UNKNOWNOID)
          {
*************** exec_eval_using_params(PLpgSQL_execstate
*** 7051,7062 ****
               */
              ppd->types[i] = TEXTOID;
              if (!isnull)
-             {
                  ppd->values[i] = CStringGetTextDatum(DatumGetCString(ppd->values[i]));
-                 ppd->freevals[i] = true;
-             }
          }
!         /* pass-by-ref non null values must be copied into plpgsql context */
          else if (!isnull)
          {
              int16        typLen;
--- 7264,7272 ----
               */
              ppd->types[i] = TEXTOID;
              if (!isnull)
                  ppd->values[i] = CStringGetTextDatum(DatumGetCString(ppd->values[i]));
          }
!         /* pass-by-ref non null values must be copied into stmt_mcontext */
          else if (!isnull)
          {
              int16        typLen;
*************** exec_eval_using_params(PLpgSQL_execstate
*** 7064,7075 ****

              get_typlenbyval(ppd->types[i], &typLen, &typByVal);
              if (!typByVal)
-             {
                  ppd->values[i] = datumCopy(ppd->values[i], typByVal, typLen);
-                 ppd->freevals[i] = true;
-             }
          }

          exec_eval_cleanup(estate);

          i++;
--- 7274,7284 ----

              get_typlenbyval(ppd->types[i], &typLen, &typByVal);
              if (!typByVal)
                  ppd->values[i] = datumCopy(ppd->values[i], typByVal, typLen);
          }

+         MemoryContextSwitchTo(oldcontext);
+
          exec_eval_cleanup(estate);

          i++;
*************** exec_eval_using_params(PLpgSQL_execstate
*** 7079,7107 ****
  }

  /*
-  * free_params_data --- pfree all pass-by-reference values used in USING clause
-  */
- static void
- free_params_data(PreparedParamsData *ppd)
- {
-     int            i;
-
-     for (i = 0; i < ppd->nargs; i++)
-     {
-         if (ppd->freevals[i])
-             pfree(DatumGetPointer(ppd->values[i]));
-     }
-
-     pfree(ppd->types);
-     pfree(ppd->values);
-     pfree(ppd->nulls);
-     pfree(ppd->freevals);
-
-     pfree(ppd);
- }
-
- /*
   * Open portal for dynamic query
   */
  static Portal
  exec_dynquery_with_params(PLpgSQL_execstate *estate,
--- 7288,7299 ----
  }

  /*
   * Open portal for dynamic query
+  *
+  * Caution: this resets the stmt_mcontext at exit.  We might eventually need
+  * to move that responsibility to the callers, but currently no caller needs
+  * to have statement-lifetime temp data that survives past this, so it's
+  * simpler to do it here.
   */
  static Portal
  exec_dynquery_with_params(PLpgSQL_execstate *estate,
*************** exec_dynquery_with_params(PLpgSQL_execst
*** 7116,7121 ****
--- 7308,7314 ----
      Oid            restype;
      int32        restypmod;
      char       *querystr;
+     MemoryContext stmt_mcontext = get_stmt_mcontext(estate);

      /*
       * Evaluate the string expression after the EXECUTE keyword. Its result is
*************** exec_dynquery_with_params(PLpgSQL_execst
*** 7130,7137 ****
      /* Get the C-String representation */
      querystr = convert_value_to_string(estate, query, restype);

!     /* copy it out of the temporary context before we clean up */
!     querystr = pstrdup(querystr);

      exec_eval_cleanup(estate);

--- 7323,7330 ----
      /* Get the C-String representation */
      querystr = convert_value_to_string(estate, query, restype);

!     /* copy it into the stmt_mcontext before we clean up */
!     querystr = MemoryContextStrdup(stmt_mcontext, querystr);

      exec_eval_cleanup(estate);

*************** exec_dynquery_with_params(PLpgSQL_execst
*** 7151,7157 ****
                                             ppd->values, ppd->nulls,
                                             estate->readonly_func,
                                             cursorOptions);
-         free_params_data(ppd);
      }
      else
      {
--- 7344,7349 ----
*************** exec_dynquery_with_params(PLpgSQL_execst
*** 7166,7172 ****
      if (portal == NULL)
          elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
               querystr, SPI_result_code_string(SPI_result));
!     pfree(querystr);

      return portal;
  }
--- 7358,7366 ----
      if (portal == NULL)
          elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
               querystr, SPI_result_code_string(SPI_result));
!
!     /* Release transient data */
!     MemoryContextReset(stmt_mcontext);

      return portal;
  }
*************** exec_dynquery_with_params(PLpgSQL_execst
*** 7174,7179 ****
--- 7368,7374 ----
  /*
   * Return a formatted string with information about an expression's parameters,
   * or NULL if the expression does not take any parameters.
+  * The result is in the eval_mcontext.
   */
  static char *
  format_expr_params(PLpgSQL_execstate *estate,
*************** format_expr_params(PLpgSQL_execstate *es
*** 7182,7191 ****
--- 7377,7389 ----
      int            paramno;
      int            dno;
      StringInfoData paramstr;
+     MemoryContext oldcontext;

      if (!expr->paramnos)
          return NULL;

+     oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+
      initStringInfo(¶mstr);
      paramno = 0;
      dno = -1;
*************** format_expr_params(PLpgSQL_execstate *es
*** 7227,7238 ****
--- 7425,7439 ----
          paramno++;
      }

+     MemoryContextSwitchTo(oldcontext);
+
      return paramstr.data;
  }

  /*
   * Return a formatted string with information about PreparedParamsData, or NULL
   * if there are no parameters.
+  * The result is in the eval_mcontext.
   */
  static char *
  format_preparedparamsdata(PLpgSQL_execstate *estate,
*************** format_preparedparamsdata(PLpgSQL_execst
*** 7240,7249 ****
--- 7441,7453 ----
  {
      int            paramno;
      StringInfoData paramstr;
+     MemoryContext oldcontext;

      if (!ppd)
          return NULL;

+     oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+
      initStringInfo(¶mstr);
      for (paramno = 0; paramno < ppd->nargs; paramno++)
      {
*************** format_preparedparamsdata(PLpgSQL_execst
*** 7269,7273 ****
--- 7473,7479 ----
          }
      }

+     MemoryContextSwitchTo(oldcontext);
+
      return paramstr.data;
  }
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 140bf4b..e729d3e 100644
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
*************** typedef struct PLpgSQL_execstate
*** 818,823 ****
--- 818,827 ----
      HTAB       *cast_hash;
      MemoryContext cast_hash_context;

+     /* Memory context for statement-lifespan temporary values */
+     MemoryContext stmt_mcontext;    /* current stmt context, or NULL if none */
+     MemoryContext stmt_mcontext_parent; /* parent of current context */
+
      /* temporary state for results from evaluation of query or expr */
      SPITupleTable *eval_tuptable;
      uint64        eval_processed;
diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index d26991e..65a60b2 100644
*** a/src/backend/utils/mmgr/aset.c
--- b/src/backend/utils/mmgr/aset.c
***************
*** 86,91 ****
--- 86,93 ----

  #include "postgres.h"

+ #include <execinfo.h>
+
  #include "utils/memdebug.h"
  #include "utils/memutils.h"

*************** AllocSetAlloc(MemoryContext context, Siz
*** 667,672 ****
--- 669,703 ----

      AssertArg(AllocSetIsValid(set));

+     if (strcmp(context->name, "SPI Proc") == 0)
+     {
+         void       *bt[10];
+         int            n,
+                     j;
+         char      **strings;
+
+         n = backtrace(bt, lengthof(bt));
+         if (n > 0)
+         {
+             strings = backtrace_symbols(bt, n);
+             if (strings == NULL)
+             {
+                 perror("backtrace_symbols");
+                 exit(EXIT_FAILURE);
+             }
+             for (j = 0; j < n; j++)
+             {
+                 if (strstr(strings[j], "plpgsql.so"))
+                 {
+                     fflush(NULL);
+                     fprintf(stderr, "alloc from: %s\n", strings[j]);
+                     break;
+                 }
+             }
+             free(strings);
+         }
+     }
+
      /*
       * If requested size exceeds maximum for chunks, allocate an entire block
       * for this request.
*************** AllocSetFree(MemoryContext context, void
*** 943,948 ****
--- 974,1008 ----
                   set->header.name, chunk);
  #endif

+     if (strcmp(context->name, "SPI Proc") == 0)
+     {
+         void       *bt[10];
+         int            n,
+                     j;
+         char      **strings;
+
+         n = backtrace(bt, lengthof(bt));
+         if (n > 0)
+         {
+             strings = backtrace_symbols(bt, n);
+             if (strings == NULL)
+             {
+                 perror("backtrace_symbols");
+                 exit(EXIT_FAILURE);
+             }
+             for (j = 0; j < n; j++)
+             {
+                 if (strstr(strings[j], "plpgsql.so"))
+                 {
+                     fflush(NULL);
+                     fprintf(stderr, "free from: %s\n", strings[j]);
+                     break;
+                 }
+             }
+             free(strings);
+         }
+     }
+
      if (chunk->size > set->allocChunkLimit)
      {
          /*
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index b628c28..1cdb82a 100644
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
*************** do_compile(FunctionCallInfo fcinfo,
*** 288,293 ****
--- 288,294 ----
      int           *in_arg_varnos = NULL;
      PLpgSQL_variable **out_arg_variables;
      MemoryContext func_cxt;
+     MemoryContext oldcontext = CurrentMemoryContext;

      /*
       * Setup the scanner input and error info.  We assume that this function
*************** do_compile(FunctionCallInfo fcinfo,
*** 334,339 ****
--- 335,347 ----
      }
      plpgsql_curr_compile = function;

+     plpgsql_compile_tmp_cxt =
+         AllocSetContextCreate(CurrentMemoryContext,
+                               "PL/pgSQL compile tmp context",
+                               ALLOCSET_DEFAULT_MINSIZE,
+                               ALLOCSET_DEFAULT_INITSIZE,
+                               ALLOCSET_DEFAULT_MAXSIZE);
+
      /*
       * All the permanent output of compilation (e.g. parse tree) is kept in a
       * per-function memory context, so it can be reclaimed easily.
*************** do_compile(FunctionCallInfo fcinfo,
*** 343,349 ****
                                       ALLOCSET_DEFAULT_MINSIZE,
                                       ALLOCSET_DEFAULT_INITSIZE,
                                       ALLOCSET_DEFAULT_MAXSIZE);
!     plpgsql_compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);

      function->fn_signature = format_procedure(fcinfo->flinfo->fn_oid);
      function->fn_oid = fcinfo->flinfo->fn_oid;
--- 351,357 ----
                                       ALLOCSET_DEFAULT_MINSIZE,
                                       ALLOCSET_DEFAULT_INITSIZE,
                                       ALLOCSET_DEFAULT_MAXSIZE);
!     MemoryContextSwitchTo(func_cxt);

      function->fn_signature = format_procedure(fcinfo->flinfo->fn_oid);
      function->fn_oid = fcinfo->flinfo->fn_oid;
*************** do_compile(FunctionCallInfo fcinfo,
*** 774,781 ****

      plpgsql_check_syntax = false;

-     MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
      plpgsql_compile_tmp_cxt = NULL;
      return function;
  }

--- 782,790 ----

      plpgsql_check_syntax = false;

      plpgsql_compile_tmp_cxt = NULL;
+
+     MemoryContextSwitchTo(oldcontext);
      return function;
  }

*************** plpgsql_compile_inline(char *proc_source
*** 798,803 ****
--- 807,813 ----
      PLpgSQL_variable *var;
      int            parse_rc;
      MemoryContext func_cxt;
+     MemoryContext oldcontext = CurrentMemoryContext;

      /*
       * Setup the scanner input and error info.  We assume that this function
*************** plpgsql_compile_inline(char *proc_source
*** 824,829 ****
--- 834,846 ----

      plpgsql_curr_compile = function;

+     plpgsql_compile_tmp_cxt =
+         AllocSetContextCreate(CurrentMemoryContext,
+                               "PL/pgSQL compile tmp context",
+                               ALLOCSET_DEFAULT_MINSIZE,
+                               ALLOCSET_DEFAULT_INITSIZE,
+                               ALLOCSET_DEFAULT_MAXSIZE);
+
      /*
       * All the rest of the compile-time storage (e.g. parse tree) is kept in
       * its own memory context, so it can be reclaimed easily.
*************** plpgsql_compile_inline(char *proc_source
*** 833,839 ****
                                       ALLOCSET_DEFAULT_MINSIZE,
                                       ALLOCSET_DEFAULT_INITSIZE,
                                       ALLOCSET_DEFAULT_MAXSIZE);
!     plpgsql_compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);

      function->fn_signature = pstrdup(func_name);
      function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
--- 850,856 ----
                                       ALLOCSET_DEFAULT_MINSIZE,
                                       ALLOCSET_DEFAULT_INITSIZE,
                                       ALLOCSET_DEFAULT_MAXSIZE);
!     MemoryContextSwitchTo(func_cxt);

      function->fn_signature = pstrdup(func_name);
      function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
*************** plpgsql_compile_inline(char *proc_source
*** 911,918 ****

      plpgsql_check_syntax = false;

-     MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
      plpgsql_compile_tmp_cxt = NULL;
      return function;
  }

--- 928,936 ----

      plpgsql_check_syntax = false;

      plpgsql_compile_tmp_cxt = NULL;
+
+     MemoryContextSwitchTo(oldcontext);
      return function;
  }


В списке pgsql-hackers по дате отправления:

Предыдущее
От: Andres Freund
Дата:
Сообщение: Re: LWLocks in DSM memory
Следующее
От: Tom Lane
Дата:
Сообщение: Re: No longer possible to query catalogs for index capabilities?