/*
  * exec_stmt_call
+ *
+ * NOTE: this is used for both CALL and DO statements.
  */
 static int
 exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
 {
    PLpgSQL_expr *expr = stmt->expr;
+   SPIPlanPtr  orig_plan = expr->plan;
+   bool        local_plan;
+   PLpgSQL_variable *volatile cur_target = stmt->target;
    volatile LocalTransactionId before_lxid;
    LocalTransactionId after_lxid;
    volatile bool pushed_active_snap = false;
    volatile int rc;
 
+   /*
+    * If not in atomic context, we make a local plan that we'll just use for
+    * this invocation, and will free at the end.  Otherwise, transaction ends
+    * would cause errors about plancache leaks.
+    *
+    * XXX This would be fixable with some plancache/resowner surgery
+    * elsewhere, but for now we'll just work around this here.
+    */
+   local_plan = !estate->atomic;
+
    /* PG_TRY to ensure we clear the plan link, if needed, on failure */
    PG_TRY();
    {
        SPIPlanPtr  plan = expr->plan;
        ParamListInfo paramLI;
 
-       if (plan == NULL)
+       /*
+        * Make a plan if we don't have one, or if we need a local one.  Note
+        * that we'll overwrite expr->plan either way; the PG_TRY block will
+        * ensure we undo that on the way out, if the plan is local.
+        */
+       if (plan == NULL || local_plan)
        {
+           /* Don't let SPI save the plan if it's going to be local */
+           exec_prepare_plan(estate, expr, 0, !local_plan);
+           plan = expr->plan;
 
            /*
-            * Don't save the plan if not in atomic context.  Otherwise,
-            * transaction ends would cause errors about plancache leaks.
-            *
-            * XXX This would be fixable with some plancache/resowner surgery
-            * elsewhere, but for now we'll just work around this here.
+            * A CALL or DO can never be a simple expression.  (If it could
+            * be, we'd have to worry about saving/restoring the previous
+            * values of the related expr fields, not just expr->plan.)
             */
-           exec_prepare_plan(estate, expr, 0, estate->atomic);
+           Assert(!expr->expr_simple_expr);
 
            /*
             * The procedure call could end transactions, which would upset
             * the snapshot management in SPI_execute*, so don't let it do it.
             * Instead, we set the snapshots ourselves below.
             */
-           plan = expr->plan;
            plan->no_snapshots = true;
 
            /*
             * case the procedure's argument list has changed.
             */
            stmt->target = NULL;
+           cur_target = NULL;
        }
 
        /*
         * We construct a DTYPE_ROW datum representing the plpgsql variables
         * associated with the procedure's output arguments.  Then we can use
         * exec_move_row() to do the assignments.
+        *
+        * If we're using a local plan, also make a local target; otherwise,
+        * since the above code will force a new plan each time through, we'd
+        * repeatedly leak the memory for the target.  (Note: we also leak the
+        * target when a plan change is forced, but that isn't so likely to
+        * cause excessive memory leaks.)
         */
-       if (stmt->is_call && stmt->target == NULL)
+       if (stmt->is_call && cur_target == NULL)
        {
            Node       *node;
            FuncExpr   *funcexpr;
            int         i;
            ListCell   *lc;
 
+           /* Use eval_mcontext for any cruft accumulated here */
+           oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+
            /*
             * Get the parsed CallStmt, and look up the called procedure
             */
            ReleaseSysCache(func_tuple);
 
            /*
-            * Begin constructing row Datum
+            * Begin constructing row Datum; keep it in fn_cxt if it's to be
+            * long-lived.
             */
-           oldcontext = MemoryContextSwitchTo(estate->func->fn_cxt);
+           if (!local_plan)
+               MemoryContextSwitchTo(estate->func->fn_cxt);
 
            row = (PLpgSQL_row *) palloc0(sizeof(PLpgSQL_row));
            row->dtype = PLPGSQL_DTYPE_ROW;
            row->lineno = -1;
            row->varnos = (int *) palloc(sizeof(int) * list_length(funcargs));
 
-           MemoryContextSwitchTo(oldcontext);
+           if (!local_plan)
+               MemoryContextSwitchTo(get_eval_mcontext(estate));
 
            /*
             * Examine procedure's argument list.  Each output arg position
 
            row->nfields = nfields;
 
-           stmt->target = (PLpgSQL_variable *) row;
+           cur_target = (PLpgSQL_variable *) row;
+
+           /* We can save and re-use the target datum, if it's not local */
+           if (!local_plan)
+               stmt->target = cur_target;
+
+           MemoryContextSwitchTo(oldcontext);
        }
 
        paramLI = setup_param_list(estate, expr);
    PG_CATCH();
    {
        /*
-        * If we aren't saving the plan, unset the pointer.  Note that it
-        * could have been unset already, in case of a recursive call.
+        * If we are using a local plan, restore the old plan link.
         */
-       if (expr->plan && !expr->plan->saved)
-           expr->plan = NULL;
+       if (local_plan)
+           expr->plan = orig_plan;
        PG_RE_THROW();
    }
    PG_END_TRY();
 
-   if (expr->plan && !expr->plan->saved)
-       expr->plan = NULL;
+   /*
+    * If we are using a local plan, restore the old plan link; then free the
+    * local plan to avoid memory leaks.  (Note that the error exit path above
+    * just clears the link without risking calling SPI_freeplan; we expect
+    * that xact cleanup will take care of the mess in that case.)
+    */
+   if (local_plan)
+   {
+       SPIPlanPtr  plan = expr->plan;
+
+       expr->plan = orig_plan;
+       SPI_freeplan(plan);
+   }
 
    if (rc < 0)
        elog(ERROR, "SPI_execute_plan_with_paramlist failed executing query \"%s\": %s",
    {
        SPITupleTable *tuptab = SPI_tuptable;
 
-       if (!stmt->target)
+       if (!cur_target)
            elog(ERROR, "DO statement returned a row");
 
-       exec_move_row(estate, stmt->target, tuptab->vals[0], tuptab->tupdesc);
+       exec_move_row(estate, cur_target, tuptab->vals[0], tuptab->tupdesc);
    }
    else if (SPI_processed > 1)
        elog(ERROR, "procedure call returned more than one row");