]> granicus.if.org Git - postgresql/blobdiff - src/pl/plpgsql/src/pl_exec.c
Improve the recently-added support for properly pluralized error messages
[postgresql] / src / pl / plpgsql / src / pl_exec.c
index d09c9fc234270af3eaf652b04970d538eb15e666..b5f000691886414b9673f9ab3f3f32f4537ea64f 100644 (file)
@@ -1,75 +1,77 @@
-/**********************************************************************
+/*-------------------------------------------------------------------------
+ *
  * pl_exec.c           - Executor for the PL/pgSQL
  *                       procedural language
  *
- * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.145 2005/06/20 20:44:44 tgl Exp $
- *
- *       This software is copyrighted by Jan Wieck - Hamburg.
- *
- *       The author hereby grants permission  to  use,  copy,  modify,
- *       distribute,  and      license this software and its documentation
- *       for any purpose, provided that existing copyright notices are
- *       retained      in      all  copies  and  that  this notice is included
- *       verbatim in any distributions. No written agreement, license,
- *       or  royalty  fee      is required for any of the authorized uses.
- *       Modifications to this software may be  copyrighted  by  their
- *       author  and  need  not  follow  the licensing terms described
- *       here, provided that the new terms are  clearly  indicated  on
- *       the first page of each file where they apply.
+ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
  *
- *       IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY
- *       PARTY  FOR  DIRECT,   INDIRECT,       SPECIAL,   INCIDENTAL,   OR
- *       CONSEQUENTIAL   DAMAGES  ARISING      OUT  OF  THE  USE  OF  THIS
- *       SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN
- *       IF  THE  AUTHOR  HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
- *       DAMAGE.
  *
- *       THE  AUTHOR  AND      DISTRIBUTORS  SPECIFICALLY       DISCLAIM       ANY
- *       WARRANTIES,  INCLUDING,  BUT  NOT  LIMITED  TO,  THE  IMPLIED
- *       WARRANTIES  OF  MERCHANTABILITY,      FITNESS  FOR  A  PARTICULAR
- *       PURPOSE,      AND NON-INFRINGEMENT.  THIS SOFTWARE IS PROVIDED ON
- *       AN "AS IS" BASIS, AND THE AUTHOR      AND  DISTRIBUTORS  HAVE  NO
- *       OBLIGATION   TO       PROVIDE   MAINTENANCE,   SUPPORT,  UPDATES,
- *       ENHANCEMENTS, OR MODIFICATIONS.
+ * IDENTIFICATION
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.242 2009/06/04 18:33:07 tgl Exp $
  *
- **********************************************************************/
+ *-------------------------------------------------------------------------
+ */
 
 #include "plpgsql.h"
-#include "pl.tab.h"
 
 #include <ctype.h>
 
-#include "access/heapam.h"
+#include "access/transam.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "executor/spi_priv.h"
 #include "funcapi.h"
-#include "optimizer/clauses.h"
-#include "parser/parse_expr.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/scansup.h"
+#include "storage/proc.h"
 #include "tcop/tcopprot.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/snapmgr.h"
 #include "utils/typcache.h"
 
 
 static const char *const raise_skip_msg = "RAISE";
 
+typedef struct
+{
+       int                     nargs;                  /* number of arguments */
+       Oid                *types;                      /* types of arguments */
+       Datum      *values;                     /* evaluated argument values */
+       char       *nulls;                      /* null markers (' '/'n' style) */
+       bool       *freevals;           /* which arguments are pfree-able */
+} PreparedParamsData;
 
 /*
- * All plpgsql function executions within a single transaction share
- * the same executor EState for evaluating "simple" expressions.  Each
- * function call creates its own "eval_econtext" ExprContext within this
- * estate.     We destroy the estate at transaction shutdown to ensure there
- * is no permanent leakage of memory (especially for xact abort case).
+ * All plpgsql function executions within a single transaction share the same
+ * executor EState for evaluating "simple" expressions.  Each function call
+ * creates its own "eval_econtext" ExprContext within this estate for
+ * per-evaluation workspace.  eval_econtext is freed at normal function exit,
+ * and the EState is freed at transaction end (in case of error, we assume
+ * that the abort mechanisms clean it all up).  Furthermore, any exception
+ * block within a function has to have its own eval_econtext separate from
+ * the containing function's, so that we can clean up ExprContext callbacks
+ * properly at subtransaction exit.  We maintain a stack that tracks the
+ * individual econtexts so that we can clean up correctly at subxact exit.
  *
- * If a simple PLpgSQL_expr has been used in the current xact, it is
- * linked into the active_simple_exprs list.
+ * This arrangement is a bit tedious to maintain, but it's worth the trouble
+ * so that we don't have to re-prepare simple expressions on each trip through
+ * a function. (We assume the case to optimize is many repetitions of a
+ * function within a transaction.)
  */
+typedef struct SimpleEcontextStackEntry
+{
+       ExprContext *stack_econtext;                    /* a stacked econtext */
+       SubTransactionId xact_subxid;                   /* ID for current subxact */
+       struct SimpleEcontextStackEntry *next;  /* next stack entry up */
+} SimpleEcontextStackEntry;
+
 static EState *simple_eval_estate = NULL;
-static PLpgSQL_expr *active_simple_exprs = NULL;
+static SimpleEcontextStackEntry *simple_econtext_stack = NULL;
 
 /************************************************************
  * Local function forward declarations
@@ -80,7 +82,7 @@ static PLpgSQL_datum *copy_plpgsql_datum(PLpgSQL_datum *datum);
 static int exec_stmt_block(PLpgSQL_execstate *estate,
                                PLpgSQL_stmt_block *block);
 static int exec_stmts(PLpgSQL_execstate *estate,
-                                         List *stmts);
+                  List *stmts);
 static int exec_stmt(PLpgSQL_execstate *estate,
                  PLpgSQL_stmt *stmt);
 static int exec_stmt_assign(PLpgSQL_execstate *estate,
@@ -91,6 +93,8 @@ static int exec_stmt_getdiag(PLpgSQL_execstate *estate,
                                  PLpgSQL_stmt_getdiag *stmt);
 static int exec_stmt_if(PLpgSQL_execstate *estate,
                         PLpgSQL_stmt_if *stmt);
+static int exec_stmt_case(PLpgSQL_execstate *estate,
+                                                 PLpgSQL_stmt_case *stmt);
 static int exec_stmt_loop(PLpgSQL_execstate *estate,
                           PLpgSQL_stmt_loop *stmt);
 static int exec_stmt_while(PLpgSQL_execstate *estate,
@@ -99,8 +103,8 @@ static int exec_stmt_fori(PLpgSQL_execstate *estate,
                           PLpgSQL_stmt_fori *stmt);
 static int exec_stmt_fors(PLpgSQL_execstate *estate,
                           PLpgSQL_stmt_fors *stmt);
-static int exec_stmt_select(PLpgSQL_execstate *estate,
-                                PLpgSQL_stmt_select *stmt);
+static int exec_stmt_forc(PLpgSQL_execstate *estate,
+                          PLpgSQL_stmt_forc *stmt);
 static int exec_stmt_open(PLpgSQL_execstate *estate,
                           PLpgSQL_stmt_open *stmt);
 static int exec_stmt_fetch(PLpgSQL_execstate *estate,
@@ -113,6 +117,8 @@ static int exec_stmt_return(PLpgSQL_execstate *estate,
                                 PLpgSQL_stmt_return *stmt);
 static int exec_stmt_return_next(PLpgSQL_execstate *estate,
                                          PLpgSQL_stmt_return_next *stmt);
+static int exec_stmt_return_query(PLpgSQL_execstate *estate,
+                                          PLpgSQL_stmt_return_query *stmt);
 static int exec_stmt_raise(PLpgSQL_execstate *estate,
                                PLpgSQL_stmt_raise *stmt);
 static int exec_stmt_execsql(PLpgSQL_execstate *estate,
@@ -128,11 +134,12 @@ static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
 static void exec_eval_cleanup(PLpgSQL_execstate *estate);
 
 static void exec_prepare_plan(PLpgSQL_execstate *estate,
-                                 PLpgSQL_expr *expr);
+                                 PLpgSQL_expr *expr, int cursorOptions);
 static bool exec_simple_check_node(Node *node);
 static void exec_simple_check_plan(PLpgSQL_expr *expr);
-static Datum exec_eval_simple_expr(PLpgSQL_execstate *estate,
+static bool exec_eval_simple_expr(PLpgSQL_execstate *estate,
                                          PLpgSQL_expr *expr,
+                                         Datum *result,
                                          bool *isNull,
                                          Oid *rettype);
 
@@ -160,6 +167,10 @@ static Datum exec_eval_expr(PLpgSQL_execstate *estate,
                           Oid *rettype);
 static int exec_run_select(PLpgSQL_execstate *estate,
                                PLpgSQL_expr *expr, long maxtuples, Portal *portalP);
+static int exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
+                                                 Portal portal, bool prefetch_ok);
+static void eval_expr_params(PLpgSQL_execstate *estate,
+                                PLpgSQL_expr *expr, Datum **p_values, char **p_nulls);
 static void exec_move_row(PLpgSQL_execstate *estate,
                          PLpgSQL_rec *rec,
                          PLpgSQL_row *row,
@@ -173,14 +184,23 @@ static Datum exec_cast_value(Datum value, Oid valtype,
                                FmgrInfo *reqinput,
                                Oid reqtypioparam,
                                int32 reqtypmod,
-                               bool *isnull);
+                               bool isnull);
 static Datum exec_simple_cast_value(Datum value, Oid valtype,
                                           Oid reqtype, int32 reqtypmod,
-                                          bool *isnull);
+                                          bool isnull);
 static void exec_init_tuple_store(PLpgSQL_execstate *estate);
-static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
+static void validate_tupdesc_compat(TupleDesc expected, TupleDesc returned,
+                                               const char *msg);
 static void exec_set_found(PLpgSQL_execstate *estate, bool state);
+static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
+static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate);
 static void free_var(PLpgSQL_var *var);
+static void assign_text_var(PLpgSQL_var *var, const char *str);
+static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate,
+                                                                                                 List *params);
+static void free_params_data(PreparedParamsData *ppd);
+static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
+                                                                               PLpgSQL_expr *query, List *params);
 
 
 /* ----------
@@ -194,6 +214,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
        PLpgSQL_execstate estate;
        ErrorContextCallback plerrcontext;
        int                     i;
+       int                     rc;
 
        /*
         * Setup the execution state
@@ -258,6 +279,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
                                                tmptup.t_tableOid = InvalidOid;
                                                tmptup.t_data = td;
                                                exec_move_row(&estate, NULL, row, &tmptup, tupdesc);
+                                               ReleaseTupleDesc(tupdesc);
                                        }
                                        else
                                        {
@@ -272,23 +294,46 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
                }
        }
 
+       estate.err_text = gettext_noop("during function entry");
+
        /*
         * Set the magic variable FOUND to false
         */
        exec_set_found(&estate, false);
 
+       /*
+        * Let the instrumentation plugin peek at this function
+        */
+       if (*plugin_ptr && (*plugin_ptr)->func_beg)
+               ((*plugin_ptr)->func_beg) (&estate, func);
+
        /*
         * Now call the toplevel block of statements
         */
        estate.err_text = NULL;
        estate.err_stmt = (PLpgSQL_stmt *) (func->action);
-       if (exec_stmt_block(&estate, func->action) != PLPGSQL_RC_RETURN)
+       rc = exec_stmt_block(&estate, func->action);
+       if (rc != PLPGSQL_RC_RETURN)
        {
                estate.err_stmt = NULL;
                estate.err_text = NULL;
-               ereport(ERROR,
-                  (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
-                       errmsg("control reached end of function without RETURN")));
+
+               /*
+                * Provide a more helpful message if a CONTINUE or RAISE has been used
+                * outside the context it can work in.
+                */
+               if (rc == PLPGSQL_RC_CONTINUE)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("CONTINUE cannot be used outside a loop")));
+               else if (rc == PLPGSQL_RC_RERAISE)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("RAISE without parameters cannot be used outside an exception handler")));
+               else
+                       ereport(ERROR,
+                          (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
+                               errmsg("control reached end of function without RETURN")));
        }
 
        /*
@@ -331,10 +376,46 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
        {
                if (estate.retistuple)
                {
-                       /* Copy tuple to upper executor memory, as a tuple Datum */
+                       /*
+                        * We have to check that the returned tuple actually matches the
+                        * expected result type.  XXX would be better to cache the tupdesc
+                        * instead of repeating get_call_result_type()
+                        */
+                       TupleDesc       tupdesc;
+
+                       switch (get_call_result_type(fcinfo, NULL, &tupdesc))
+                       {
+                               case TYPEFUNC_COMPOSITE:
+                                       /* got the expected result rowtype, now check it */
+                                       validate_tupdesc_compat(tupdesc, estate.rettupdesc,
+                                                                                       "returned record type does not match expected record type");
+                                       break;
+                               case TYPEFUNC_RECORD:
+
+                                       /*
+                                        * Failed to determine actual type of RECORD.  We could
+                                        * raise an error here, but what this means in practice is
+                                        * that the caller is expecting any old generic rowtype,
+                                        * so we don't really need to be restrictive. Pass back
+                                        * the generated result type, instead.
+                                        */
+                                       tupdesc = estate.rettupdesc;
+                                       if (tupdesc == NULL)            /* shouldn't happen */
+                                               elog(ERROR, "return type must be a row type");
+                                       break;
+                               default:
+                                       /* shouldn't get here if retistuple is true ... */
+                                       elog(ERROR, "return type must be a row type");
+                                       break;
+                       }
+
+                       /*
+                        * Copy tuple to upper executor memory, as a tuple Datum. Make
+                        * sure it is labeled with the caller-supplied tuple type.
+                        */
                        estate.retval =
-                               PointerGetDatum(SPI_returntuple((HeapTuple) (estate.retval),
-                                                                                               estate.rettupdesc));
+                               PointerGetDatum(SPI_returntuple((HeapTuple)DatumGetPointer(estate.retval),
+                                                                                               tupdesc));
                }
                else
                {
@@ -344,7 +425,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
                                                                                        &(func->fn_retinput),
                                                                                        func->fn_rettypioparam,
                                                                                        -1,
-                                                                                       &fcinfo->isnull);
+                                                                                       fcinfo->isnull);
 
                        /*
                         * If the function's return type isn't by value, copy the value
@@ -356,17 +437,23 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
                                void       *tmp;
 
                                len = datumGetSize(estate.retval, false, func->fn_rettyplen);
-                               tmp = (void *) SPI_palloc(len);
+                               tmp = SPI_palloc(len);
                                memcpy(tmp, DatumGetPointer(estate.retval), len);
                                estate.retval = PointerGetDatum(tmp);
                        }
                }
        }
 
+       estate.err_text = gettext_noop("during function exit");
+
+       /*
+        * Let the instrumentation plugin peek at this function
+        */
+       if (*plugin_ptr && (*plugin_ptr)->func_end)
+               ((*plugin_ptr)->func_end) (&estate, func);
+
        /* Clean up any leftover temporary memory */
-       if (estate.eval_econtext != NULL)
-               FreeExprContext(estate.eval_econtext);
-       estate.eval_econtext = NULL;
+       plpgsql_destroy_econtext(&estate);
        exec_eval_cleanup(&estate);
 
        /*
@@ -393,6 +480,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
        PLpgSQL_execstate estate;
        ErrorContextCallback plerrcontext;
        int                     i;
+       int                     rc;
        PLpgSQL_var *var;
        PLpgSQL_rec *rec_new,
                           *rec_old;
@@ -468,27 +556,29 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
 
        var = (PLpgSQL_var *) (estate.datums[func->tg_op_varno]);
        if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
-               var->value = DirectFunctionCall1(textin, CStringGetDatum("INSERT"));
+               var->value = CStringGetTextDatum("INSERT");
        else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-               var->value = DirectFunctionCall1(textin, CStringGetDatum("UPDATE"));
+               var->value = CStringGetTextDatum("UPDATE");
        else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
-               var->value = DirectFunctionCall1(textin, CStringGetDatum("DELETE"));
+               var->value = CStringGetTextDatum("DELETE");
+       else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
+               var->value = CStringGetTextDatum("TRUNCATE");
        else
-               elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
+               elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE");
        var->isnull = false;
        var->freeval = true;
 
        var = (PLpgSQL_var *) (estate.datums[func->tg_name_varno]);
        var->value = DirectFunctionCall1(namein,
-                                                 CStringGetDatum(trigdata->tg_trigger->tgname));
+                                                         CStringGetDatum(trigdata->tg_trigger->tgname));
        var->isnull = false;
        var->freeval = true;
 
        var = (PLpgSQL_var *) (estate.datums[func->tg_when_varno]);
        if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
-               var->value = DirectFunctionCall1(textin, CStringGetDatum("BEFORE"));
+               var->value = CStringGetTextDatum("BEFORE");
        else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
-               var->value = DirectFunctionCall1(textin, CStringGetDatum("AFTER"));
+               var->value = CStringGetTextDatum("AFTER");
        else
                elog(ERROR, "unrecognized trigger execution time: not BEFORE or AFTER");
        var->isnull = false;
@@ -496,9 +586,9 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
 
        var = (PLpgSQL_var *) (estate.datums[func->tg_level_varno]);
        if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-               var->value = DirectFunctionCall1(textin, CStringGetDatum("ROW"));
+               var->value = CStringGetTextDatum("ROW");
        else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
-               var->value = DirectFunctionCall1(textin, CStringGetDatum("STATEMENT"));
+               var->value = CStringGetTextDatum("STATEMENT");
        else
                elog(ERROR, "unrecognized trigger event type: not ROW or STATEMENT");
        var->isnull = false;
@@ -511,7 +601,22 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
 
        var = (PLpgSQL_var *) (estate.datums[func->tg_relname_varno]);
        var->value = DirectFunctionCall1(namein,
-               CStringGetDatum(RelationGetRelationName(trigdata->tg_relation)));
+                       CStringGetDatum(RelationGetRelationName(trigdata->tg_relation)));
+       var->isnull = false;
+       var->freeval = true;
+
+       var = (PLpgSQL_var *) (estate.datums[func->tg_table_name_varno]);
+       var->value = DirectFunctionCall1(namein,
+                       CStringGetDatum(RelationGetRelationName(trigdata->tg_relation)));
+       var->isnull = false;
+       var->freeval = true;
+
+       var = (PLpgSQL_var *) (estate.datums[func->tg_table_schema_varno]);
+       var->value = DirectFunctionCall1(namein,
+                                                                        CStringGetDatum(
+                                                                                                        get_namespace_name(
+                                                                                                               RelationGetNamespace(
+                                                                                                  trigdata->tg_relation))));
        var->isnull = false;
        var->freeval = true;
 
@@ -521,8 +626,8 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
        var->freeval = false;
 
        /*
-        * Store the trigger argument values into the special execution
-        * state variables
+        * Store the trigger argument values into the special execution state
+        * variables
         */
        estate.err_text = gettext_noop("while storing call arguments into local variables");
        estate.trig_nargs = trigdata->tg_trigger->tgnargs;
@@ -532,61 +637,88 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
        {
                estate.trig_argv = palloc(sizeof(Datum) * estate.trig_nargs);
                for (i = 0; i < trigdata->tg_trigger->tgnargs; i++)
-                       estate.trig_argv[i] = DirectFunctionCall1(textin,
-                                          CStringGetDatum(trigdata->tg_trigger->tgargs[i]));
+                       estate.trig_argv[i] = CStringGetTextDatum(trigdata->tg_trigger->tgargs[i]);
        }
 
+       estate.err_text = gettext_noop("during function entry");
+
        /*
         * Set the magic variable FOUND to false
         */
        exec_set_found(&estate, false);
 
+       /*
+        * Let the instrumentation plugin peek at this function
+        */
+       if (*plugin_ptr && (*plugin_ptr)->func_beg)
+               ((*plugin_ptr)->func_beg) (&estate, func);
+
        /*
         * Now call the toplevel block of statements
         */
        estate.err_text = NULL;
        estate.err_stmt = (PLpgSQL_stmt *) (func->action);
-       if (exec_stmt_block(&estate, func->action) != PLPGSQL_RC_RETURN)
+       rc = exec_stmt_block(&estate, func->action);
+       if (rc != PLPGSQL_RC_RETURN)
        {
                estate.err_stmt = NULL;
                estate.err_text = NULL;
-               ereport(ERROR,
-                  (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
-                       errmsg("control reached end of trigger procedure without RETURN")));
+
+               /*
+                * Provide a more helpful message if a CONTINUE or RAISE has been used
+                * outside the context it can work in.
+                */
+               if (rc == PLPGSQL_RC_CONTINUE)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("CONTINUE cannot be used outside a loop")));
+               else if (rc == PLPGSQL_RC_RERAISE)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("RAISE without parameters cannot be used outside an exception handler")));
+               else
+                       ereport(ERROR,
+                          (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
+                               errmsg("control reached end of trigger procedure without RETURN")));
        }
 
+       estate.err_stmt = NULL;
+       estate.err_text = gettext_noop("during function exit");
+
        if (estate.retisset)
                ereport(ERROR,
                                (errcode(ERRCODE_DATATYPE_MISMATCH),
                                 errmsg("trigger procedure cannot return a set")));
 
        /*
-        * Check that the returned tuple structure has the same attributes,
-        * the relation that fired the trigger has. A per-statement trigger
-        * always needs to return NULL, so we ignore any return value the
-        * function itself produces (XXX: is this a good idea?)
+        * Check that the returned tuple structure has the same attributes, the
+        * relation that fired the trigger has. A per-statement trigger always
+        * needs to return NULL, so we ignore any return value the function itself
+        * produces (XXX: is this a good idea?)
         *
         * XXX This way it is possible, that the trigger returns a tuple where
-        * attributes don't have the correct atttypmod's length. It's up to
-        * the trigger's programmer to ensure that this doesn't happen. Jan
+        * attributes don't have the correct atttypmod's length. It's up to the
+        * trigger's programmer to ensure that this doesn't happen. Jan
         */
        if (estate.retisnull || TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
                rettup = NULL;
        else
        {
-               if (!compatible_tupdesc(estate.rettupdesc,
-                                                               trigdata->tg_relation->rd_att))
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                        errmsg("returned tuple structure does not match table of trigger event")));
+               validate_tupdesc_compat(trigdata->tg_relation->rd_att,
+                                                               estate.rettupdesc,
+                                                               "returned row structure does not match the structure of the triggering table");
                /* Copy tuple to upper executor memory */
-               rettup = SPI_copytuple((HeapTuple) (estate.retval));
+               rettup = SPI_copytuple((HeapTuple) DatumGetPointer(estate.retval));
        }
 
+       /*
+        * Let the instrumentation plugin peek at this function
+        */
+       if (*plugin_ptr && (*plugin_ptr)->func_end)
+               ((*plugin_ptr)->func_end) (&estate, func);
+
        /* Clean up any leftover temporary memory */
-       if (estate.eval_econtext != NULL)
-               FreeExprContext(estate.eval_econtext);
-       estate.eval_econtext = NULL;
+       plpgsql_destroy_econtext(&estate);
        exec_eval_cleanup(&estate);
 
        /*
@@ -617,7 +749,36 @@ plpgsql_exec_error_callback(void *arg)
        if (estate->err_text == raise_skip_msg)
                return;
 
-       if (estate->err_stmt != NULL)
+       if (estate->err_text != NULL)
+       {
+               /*
+                * We don't expend the cycles to run gettext() on err_text unless we
+                * actually need it.  Therefore, places that set up err_text should
+                * use gettext_noop() to ensure the strings get recorded in the
+                * message dictionary.
+                *
+                * If both err_text and err_stmt are set, use the err_text as
+                * description, but report the err_stmt's line number.  When err_stmt
+                * is not set, we're in function entry/exit, or some such place not
+                * attached to a specific line number.
+                */
+               if (estate->err_stmt != NULL)
+               {
+                       /* translator: last %s is a phrase such as "during statement block local variable initialization" */
+                       errcontext("PL/pgSQL function \"%s\" line %d %s",
+                                          estate->err_func->fn_name,
+                                          estate->err_stmt->lineno,
+                                          _(estate->err_text));
+               }
+               else
+               {
+                       /* translator: last %s is a phrase such as "while storing call arguments into local variables" */
+                       errcontext("PL/pgSQL function \"%s\" %s",
+                                          estate->err_func->fn_name,
+                                          _(estate->err_text));
+               }
+       }
+       else if (estate->err_stmt != NULL)
        {
                /* translator: last %s is a plpgsql statement type name */
                errcontext("PL/pgSQL function \"%s\" line %d at %s",
@@ -625,23 +786,6 @@ plpgsql_exec_error_callback(void *arg)
                                   estate->err_stmt->lineno,
                                   plpgsql_stmt_typename(estate->err_stmt));
        }
-       else if (estate->err_text != NULL)
-       {
-               /*
-                * We don't expend the cycles to run gettext() on err_text unless
-                * we actually need it.  Therefore, places that set up err_text
-                * should use gettext_noop() to ensure the strings get recorded in
-                * the message dictionary.
-                */
-
-               /*
-                * translator: last %s is a phrase such as "while storing call
-                * arguments into local variables"
-                */
-               errcontext("PL/pgSQL function \"%s\" %s",
-                                  estate->err_func->fn_name,
-                                  gettext(estate->err_text));
-       }
        else
                errcontext("PL/pgSQL function \"%s\"",
                                   estate->err_func->fn_name);
@@ -660,41 +804,42 @@ copy_plpgsql_datum(PLpgSQL_datum *datum)
        switch (datum->dtype)
        {
                case PLPGSQL_DTYPE_VAR:
-               {
-                       PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var));
+                       {
+                               PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var));
 
-                       memcpy(new, datum, sizeof(PLpgSQL_var));
-                       /* Ensure the value is null (possibly not needed?) */
-                       new->value = 0;
-                       new->isnull = true;
-                       new->freeval = false;
+                               memcpy(new, datum, sizeof(PLpgSQL_var));
+                               /* Ensure the value is null (possibly not needed?) */
+                               new->value = 0;
+                               new->isnull = true;
+                               new->freeval = false;
 
-                       result = (PLpgSQL_datum *) new;
-               }
-               break;
+                               result = (PLpgSQL_datum *) new;
+                       }
+                       break;
 
                case PLPGSQL_DTYPE_REC:
-               {
-                       PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec));
+                       {
+                               PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec));
 
-                       memcpy(new, datum, sizeof(PLpgSQL_rec));
-                       /* Ensure the value is null (possibly not needed?) */
-                       new->tup = NULL;
-                       new->tupdesc = NULL;
-                       new->freetup = false;
-                       new->freetupdesc = false;
+                               memcpy(new, datum, sizeof(PLpgSQL_rec));
+                               /* Ensure the value is null (possibly not needed?) */
+                               new->tup = NULL;
+                               new->tupdesc = NULL;
+                               new->freetup = false;
+                               new->freetupdesc = false;
 
-                       result = (PLpgSQL_datum *) new;
-               }
-               break;
+                               result = (PLpgSQL_datum *) new;
+                       }
+                       break;
 
                case PLPGSQL_DTYPE_ROW:
                case PLPGSQL_DTYPE_RECFIELD:
                case PLPGSQL_DTYPE_ARRAYELEM:
                case PLPGSQL_DTYPE_TRIGARG:
+
                        /*
-                        * These datum records are read-only at runtime, so no need
-                        * to copy them
+                        * These datum records are read-only at runtime, so no need to
+                        * copy them
                         */
                        result = datum;
                        break;
@@ -751,6 +896,8 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
        /*
         * First initialize all variables declared in this block
         */
+       estate->err_text = gettext_noop("during statement block local variable initialization");
+
        for (i = 0; i < block->n_initvars; i++)
        {
                n = block->initvarnos[i];
@@ -761,24 +908,43 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                                {
                                        PLpgSQL_var *var = (PLpgSQL_var *) (estate->datums[n]);
 
+                                       /* free any old value, in case re-entering block */
                                        free_var(var);
-                                       if (!var->isconst || var->isnull)
+
+                                       /* Initially it contains a NULL */
+                                       var->value = (Datum) 0;
+                                       var->isnull = true;
+
+                                       if (var->default_val == NULL)
                                        {
-                                               if (var->default_val == NULL)
-                                               {
-                                                       var->value = (Datum) 0;
-                                                       var->isnull = true;
-                                                       if (var->notnull)
-                                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                                                errmsg("variable \"%s\" declared NOT NULL cannot default to NULL",
-                                                                               var->refname)));
-                                               }
-                                               else
+                                               /*
+                                                * If needed, give the datatype a chance to reject
+                                                * NULLs, by assigning a NULL to the variable. We
+                                                * claim the value is of type UNKNOWN, not the var's
+                                                * datatype, else coercion will be skipped. (Do this
+                                                * before the notnull check to be consistent with
+                                                * exec_assign_value.)
+                                                */
+                                               if (!var->datatype->typinput.fn_strict)
                                                {
-                                                       exec_assign_expr(estate, (PLpgSQL_datum *) var,
-                                                                                        var->default_val);
+                                                       bool            valIsNull = true;
+
+                                                       exec_assign_value(estate,
+                                                                                         (PLpgSQL_datum *) var,
+                                                                                         (Datum) 0,
+                                                                                         UNKNOWNOID,
+                                                                                         &valIsNull);
                                                }
+                                               if (var->notnull)
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                                                        errmsg("variable \"%s\" declared NOT NULL cannot default to NULL",
+                                                                                       var->refname)));
+                                       }
+                                       else
+                                       {
+                                               exec_assign_expr(estate, (PLpgSQL_datum *) var,
+                                                                                var->default_val);
                                        }
                                }
                                break;
@@ -812,11 +978,13 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
        if (block->exceptions)
        {
                /*
-                * Execute the statements in the block's body inside a
-                * sub-transaction
+                * Execute the statements in the block's body inside a sub-transaction
                 */
                MemoryContext oldcontext = CurrentMemoryContext;
                ResourceOwner oldowner = CurrentResourceOwner;
+               ExprContext *old_eval_econtext = estate->eval_econtext;
+
+               estate->err_text = gettext_noop("during statement block entry");
 
                BeginInternalSubTransaction(NULL);
                /* Want to run statements inside function's memory context */
@@ -824,23 +992,63 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
 
                PG_TRY();
                {
+                       /*
+                        * We need to run the block's statements with a new eval_econtext
+                        * that belongs to the current subtransaction; if we try to use
+                        * the outer econtext then ExprContext shutdown callbacks will be
+                        * called at the wrong times.
+                        */
+                       plpgsql_create_econtext(estate);
+
+                       estate->err_text = NULL;
+
+                       /* Run the block's statements */
                        rc = exec_stmts(estate, block->body);
 
+                       estate->err_text = gettext_noop("during statement block exit");
+
+                       /*
+                        * 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 &&
+                               !estate->retisnull &&
+                               estate->rettupdesc == NULL)
+                       {
+                               int16           resTypLen;
+                               bool            resTypByVal;
+
+                               get_typlenbyval(estate->rettype, &resTypLen, &resTypByVal);
+                               estate->retval = datumCopy(estate->retval,
+                                                                                  resTypByVal, resTypLen);
+                       }
+
                        /* Commit the inner transaction, return to outer xact context */
                        ReleaseCurrentSubTransaction();
                        MemoryContextSwitchTo(oldcontext);
                        CurrentResourceOwner = oldowner;
 
                        /*
-                        * AtEOSubXact_SPI() should not have popped any SPI context,
-                        * but just in case it did, make sure we remain connected.
+                        * Revert to outer eval_econtext.  (The inner one was automatically
+                        * cleaned up during subxact exit.)
+                        */
+                       estate->eval_econtext = old_eval_econtext;
+
+                       /*
+                        * AtEOSubXact_SPI() should not have popped any SPI context, but
+                        * just in case it did, make sure we remain connected.
                         */
                        SPI_restore_connection();
                }
                PG_CATCH();
                {
-                       ErrorData       *edata;
-                       ListCell        *e;
+                       ErrorData  *edata;
+                       ListCell   *e;
+
+                       estate->err_text = gettext_noop("during exception cleanup");
 
                        /* Save error info */
                        MemoryContextSwitchTo(oldcontext);
@@ -852,47 +1060,55 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                        MemoryContextSwitchTo(oldcontext);
                        CurrentResourceOwner = oldowner;
 
+                       /* Revert to outer eval_econtext */
+                       estate->eval_econtext = old_eval_econtext;
+
                        /*
-                        * If AtEOSubXact_SPI() popped any SPI context of the subxact,
-                        * it will have left us in a disconnected state.  We need this
-                        * hack to return to connected state.
+                        * If AtEOSubXact_SPI() popped any SPI context of the subxact, it
+                        * will have left us in a disconnected state.  We need this hack
+                        * to return to connected state.
                         */
                        SPI_restore_connection();
 
                        /* Look for a matching exception handler */
-                       foreach (e, block->exceptions->exc_list)
+                       foreach(e, block->exceptions->exc_list)
                        {
                                PLpgSQL_exception *exception = (PLpgSQL_exception *) lfirst(e);
 
                                if (exception_matches_conditions(edata, exception->conditions))
                                {
                                        /*
-                                        * Initialize the magic SQLSTATE and SQLERRM
-                                        * variables for the exception block. We needn't
-                                        * do this until we have found a matching
-                                        * exception.
+                                        * Initialize the magic SQLSTATE and SQLERRM variables for
+                                        * the exception block. We needn't do this until we have
+                                        * found a matching exception.
                                         */
                                        PLpgSQL_var *state_var;
                                        PLpgSQL_var *errm_var;
 
                                        state_var = (PLpgSQL_var *)
                                                estate->datums[block->exceptions->sqlstate_varno];
-                                       state_var->value = DirectFunctionCall1(textin,
-                                                                                                                  CStringGetDatum(unpack_sql_state(edata->sqlerrcode)));
-                                       state_var->freeval = true;
-                                       state_var->isnull = false;
-
                                        errm_var = (PLpgSQL_var *)
                                                estate->datums[block->exceptions->sqlerrm_varno];
-                                       errm_var->value = DirectFunctionCall1(textin,
-                                                                                                                 CStringGetDatum(edata->message));
-                                       errm_var->freeval = true;
-                                       errm_var->isnull = false;
+
+                                       assign_text_var(state_var,
+                                                                       unpack_sql_state(edata->sqlerrcode));
+                                       assign_text_var(errm_var, edata->message);
+
+                                       estate->err_text = NULL;
 
                                        rc = exec_stmts(estate, exception->action);
 
                                        free_var(state_var);
+                                       state_var->value = (Datum) 0;
+                                       state_var->isnull = true;
                                        free_var(errm_var);
+                                       errm_var->value = (Datum) 0;
+                                       errm_var->isnull = true;
+
+                                       /* re-throw error if requested by handler */
+                                       if (rc == PLPGSQL_RC_RERAISE)
+                                               ReThrowError(edata);
+
                                        break;
                                }
                        }
@@ -910,30 +1126,38 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                /*
                 * Just execute the statements in the block's body
                 */
+               estate->err_text = NULL;
+
                rc = exec_stmts(estate, block->body);
        }
 
+       estate->err_text = NULL;
+
        /*
         * Handle the return code.
         */
        switch (rc)
        {
                case PLPGSQL_RC_OK:
-                       return PLPGSQL_RC_OK;
+               case PLPGSQL_RC_RETURN:
+               case PLPGSQL_RC_CONTINUE:
+               case PLPGSQL_RC_RERAISE:
+                       return rc;
 
                case PLPGSQL_RC_EXIT:
+                       /*
+                        * This is intentionally different from the handling of RC_EXIT
+                        * for loops: to match a block, we require a match by label.
+                        */
                        if (estate->exitlabel == NULL)
-                               return PLPGSQL_RC_OK;
+                               return PLPGSQL_RC_EXIT;
                        if (block->label == NULL)
                                return PLPGSQL_RC_EXIT;
-                       if (strcmp(block->label, estate->exitlabel))
+                       if (strcmp(block->label, estate->exitlabel) != 0)
                                return PLPGSQL_RC_EXIT;
                        estate->exitlabel = NULL;
                        return PLPGSQL_RC_OK;
 
-               case PLPGSQL_RC_RETURN:
-                       return PLPGSQL_RC_RETURN;
-
                default:
                        elog(ERROR, "unrecognized rc: %d", rc);
        }
@@ -952,10 +1176,22 @@ exec_stmts(PLpgSQL_execstate *estate, List *stmts)
 {
        ListCell   *s;
 
-       foreach (s, stmts)
+       if (stmts == NIL)
+       {
+               /*
+                * Ensure we do a CHECK_FOR_INTERRUPTS() even though there is no
+                * statement.  This prevents hangup in a tight loop if, for instance,
+                * there is a LOOP construct with an empty body.
+                */
+               CHECK_FOR_INTERRUPTS();
+               return PLPGSQL_RC_OK;
+       }
+
+       foreach(s, stmts)
        {
                PLpgSQL_stmt *stmt = (PLpgSQL_stmt *) lfirst(s);
-               int rc = exec_stmt(estate, stmt);
+               int                     rc = exec_stmt(estate, stmt);
+
                if (rc != PLPGSQL_RC_OK)
                        return rc;
        }
@@ -978,9 +1214,13 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
        save_estmt = estate->err_stmt;
        estate->err_stmt = stmt;
 
+       /* Let the plugin know that we are about to execute this statement */
+       if (*plugin_ptr && (*plugin_ptr)->stmt_beg)
+               ((*plugin_ptr)->stmt_beg) (estate, stmt);
+
        CHECK_FOR_INTERRUPTS();
 
-       switch (stmt->cmd_type)
+       switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
        {
                case PLPGSQL_STMT_BLOCK:
                        rc = exec_stmt_block(estate, (PLpgSQL_stmt_block *) stmt);
@@ -1002,6 +1242,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
                        rc = exec_stmt_if(estate, (PLpgSQL_stmt_if *) stmt);
                        break;
 
+               case PLPGSQL_STMT_CASE:
+                       rc = exec_stmt_case(estate, (PLpgSQL_stmt_case *) stmt);
+                       break;
+
                case PLPGSQL_STMT_LOOP:
                        rc = exec_stmt_loop(estate, (PLpgSQL_stmt_loop *) stmt);
                        break;
@@ -1018,8 +1262,8 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
                        rc = exec_stmt_fors(estate, (PLpgSQL_stmt_fors *) stmt);
                        break;
 
-               case PLPGSQL_STMT_SELECT:
-                       rc = exec_stmt_select(estate, (PLpgSQL_stmt_select *) stmt);
+               case PLPGSQL_STMT_FORC:
+                       rc = exec_stmt_forc(estate, (PLpgSQL_stmt_forc *) stmt);
                        break;
 
                case PLPGSQL_STMT_EXIT:
@@ -1034,6 +1278,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
                        rc = exec_stmt_return_next(estate, (PLpgSQL_stmt_return_next *) stmt);
                        break;
 
+               case PLPGSQL_STMT_RETURN_QUERY:
+                       rc = exec_stmt_return_query(estate, (PLpgSQL_stmt_return_query *) stmt);
+                       break;
+
                case PLPGSQL_STMT_RAISE:
                        rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt);
                        break;
@@ -1067,6 +1315,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
                        elog(ERROR, "unrecognized cmdtype: %d", stmt->cmd_type);
        }
 
+       /* Let the plugin know that we have finished executing this statement */
+       if (*plugin_ptr && (*plugin_ptr)->stmt_end)
+               ((*plugin_ptr)->stmt_end) (estate, stmt);
+
        estate->err_stmt = save_estmt;
 
        return rc;
@@ -1114,13 +1366,13 @@ exec_stmt_perform(PLpgSQL_execstate *estate, PLpgSQL_stmt_perform *stmt)
 static int
 exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
 {
-       ListCell *lc;
+       ListCell   *lc;
 
-       foreach (lc, stmt->diag_items)
+       foreach(lc, stmt->diag_items)
        {
-               PLpgSQL_diag_item       *diag_item = (PLpgSQL_diag_item *) lfirst(lc);
-               PLpgSQL_datum           *var;
-               bool                             isnull = false;
+               PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc);
+               PLpgSQL_datum *var;
+               bool            isnull = false;
 
                if (diag_item->target <= 0)
                        continue;
@@ -1165,7 +1417,7 @@ static int
 exec_stmt_if(PLpgSQL_execstate *estate, PLpgSQL_stmt_if *stmt)
 {
        bool            value;
-       bool            isnull = false;
+       bool            isnull;
 
        value = exec_eval_boolean(estate, stmt->cond, &isnull);
        exec_eval_cleanup(estate);
@@ -1185,6 +1437,91 @@ exec_stmt_if(PLpgSQL_execstate *estate, PLpgSQL_stmt_if *stmt)
 }
 
 
+/*-----------
+ * exec_stmt_case
+ *-----------
+ */
+static int
+exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt)
+{
+       PLpgSQL_var *t_var = NULL;
+       bool            isnull;
+       ListCell   *l;
+
+       if (stmt->t_expr != NULL)
+       {
+               /* simple case */
+               Datum   t_val;
+               Oid             t_oid;
+
+               t_val = exec_eval_expr(estate, stmt->t_expr, &isnull, &t_oid);
+
+               t_var = (PLpgSQL_var *) estate->datums[stmt->t_varno];
+
+               /*
+                * 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_oid)
+                       t_var->datatype = plpgsql_build_datatype(t_oid, -1);
+
+               /* now we can assign to the variable */
+               exec_assign_value(estate,
+                                                 (PLpgSQL_datum *) t_var,
+                                                 t_val,
+                                                 t_oid,
+                                                 &isnull);
+
+               exec_eval_cleanup(estate);
+       }
+
+       /* Now search for a successful WHEN clause */
+       foreach(l, stmt->case_when_list)
+       {
+               PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+               bool    value;
+
+               value = exec_eval_boolean(estate, cwt->expr, &isnull);
+               exec_eval_cleanup(estate);
+               if (!isnull && value)
+               {
+                       /* Found it */
+
+                       /* We can now discard any value we had for the temp variable */
+                       if (t_var != NULL)
+                       {
+                               free_var(t_var);
+                               t_var->value = (Datum) 0;
+                               t_var->isnull = true;
+                       }
+
+                       /* Evaluate the statement(s), and we're done */
+                       return exec_stmts(estate, cwt->stmts);
+               }
+       }
+
+       /* We can now discard any value we had for the temp variable */
+       if (t_var != NULL)
+       {
+               free_var(t_var);
+               t_var->value = (Datum) 0;
+               t_var->isnull = true;
+       }
+
+       /* SQL2003 mandates this error if there was no ELSE clause */
+       if (!stmt->have_else)
+               ereport(ERROR,
+                               (errcode(ERRCODE_CASE_NOT_FOUND),
+                                errmsg("case not found"),
+                                errhint("CASE statement is missing ELSE part.")));
+
+       /* Evaluate the ELSE statements, and we're done */
+       return exec_stmts(estate, stmt->else_stmts);
+}
+
+
 /* ----------
  * exec_stmt_loop                      Loop over statements until
  *                                     an exit occurs.
@@ -1193,11 +1530,9 @@ exec_stmt_if(PLpgSQL_execstate *estate, PLpgSQL_stmt_if *stmt)
 static int
 exec_stmt_loop(PLpgSQL_execstate *estate, PLpgSQL_stmt_loop *stmt)
 {
-       int                     rc;
-
        for (;;)
        {
-               rc = exec_stmts(estate, stmt->body);
+               int                     rc = exec_stmts(estate, stmt->body);
 
                switch (rc)
                {
@@ -1209,13 +1544,27 @@ exec_stmt_loop(PLpgSQL_execstate *estate, PLpgSQL_stmt_loop *stmt)
                                        return PLPGSQL_RC_OK;
                                if (stmt->label == NULL)
                                        return PLPGSQL_RC_EXIT;
-                               if (strcmp(stmt->label, estate->exitlabel))
+                               if (strcmp(stmt->label, estate->exitlabel) != 0)
                                        return PLPGSQL_RC_EXIT;
                                estate->exitlabel = NULL;
                                return PLPGSQL_RC_OK;
 
+                       case PLPGSQL_RC_CONTINUE:
+                               if (estate->exitlabel == NULL)
+                                       /* anonymous continue, so re-run the loop */
+                                       break;
+                               else if (stmt->label != NULL &&
+                                                strcmp(stmt->label, estate->exitlabel) == 0)
+                                       /* label matches named continue, so re-run loop */
+                                       estate->exitlabel = NULL;
+                               else
+                                       /* label doesn't match named continue, so propagate upward */
+                                       return PLPGSQL_RC_CONTINUE;
+                               break;
+
                        case PLPGSQL_RC_RETURN:
-                               return PLPGSQL_RC_RETURN;
+                       case PLPGSQL_RC_RERAISE:
+                               return rc;
 
                        default:
                                elog(ERROR, "unrecognized rc: %d", rc);
@@ -1235,12 +1584,12 @@ exec_stmt_loop(PLpgSQL_execstate *estate, PLpgSQL_stmt_loop *stmt)
 static int
 exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
 {
-       bool            value;
-       bool            isnull = false;
-       int                     rc;
-
        for (;;)
        {
+               int                     rc;
+               bool            value;
+               bool            isnull;
+
                value = exec_eval_boolean(estate, stmt->cond, &isnull);
                exec_eval_cleanup(estate);
 
@@ -1259,13 +1608,27 @@ exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
                                        return PLPGSQL_RC_OK;
                                if (stmt->label == NULL)
                                        return PLPGSQL_RC_EXIT;
-                               if (strcmp(stmt->label, estate->exitlabel))
+                               if (strcmp(stmt->label, estate->exitlabel) != 0)
                                        return PLPGSQL_RC_EXIT;
                                estate->exitlabel = NULL;
                                return PLPGSQL_RC_OK;
 
+                       case PLPGSQL_RC_CONTINUE:
+                               if (estate->exitlabel == NULL)
+                                       /* anonymous continue, so re-run loop */
+                                       break;
+                               else if (stmt->label != NULL &&
+                                                strcmp(stmt->label, estate->exitlabel) == 0)
+                                       /* label matches named continue, so re-run loop */
+                                       estate->exitlabel = NULL;
+                               else
+                                       /* label doesn't match named continue, propagate upward */
+                                       return PLPGSQL_RC_CONTINUE;
+                               break;
+
                        case PLPGSQL_RC_RETURN:
-                               return PLPGSQL_RC_RETURN;
+                       case PLPGSQL_RC_RERAISE:
+                               return rc;
 
                        default:
                                elog(ERROR, "unrecognized rc: %d", rc);
@@ -1278,8 +1641,8 @@ exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
 
 /* ----------
  * exec_stmt_fori                      Iterate an integer variable
- *                                     from a lower to an upper value.
- *                                     Loop can be left with exit.
+ *                                     from a lower to an upper value
+ *                                     incrementing or decrementing by the BY value
  * ----------
  */
 static int
@@ -1287,27 +1650,29 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
 {
        PLpgSQL_var *var;
        Datum           value;
+       bool            isnull;
        Oid                     valtype;
-       bool            isnull = false;
+       int32           loop_value;
+       int32           end_value;
+       int32           step_value;
        bool            found = false;
        int                     rc = PLPGSQL_RC_OK;
 
-       var = (PLpgSQL_var *) (estate->datums[stmt->var->varno]);
+       var = (PLpgSQL_var *) (estate->datums[stmt->var->dno]);
 
        /*
-        * Get the value of the lower bound into the loop var
+        * Get the value of the lower bound
         */
        value = exec_eval_expr(estate, stmt->lower, &isnull, &valtype);
        value = exec_cast_value(value, valtype, var->datatype->typoid,
                                                        &(var->datatype->typinput),
                                                        var->datatype->typioparam,
-                                                       var->datatype->atttypmod, &isnull);
+                                                       var->datatype->atttypmod, isnull);
        if (isnull)
                ereport(ERROR,
                                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                errmsg("lower bound of FOR loop cannot be NULL")));
-       var->value = value;
-       var->isnull = false;
+                                errmsg("lower bound of FOR loop cannot be null")));
+       loop_value = DatumGetInt32(value);
        exec_eval_cleanup(estate);
 
        /*
@@ -1317,41 +1682,73 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
        value = exec_cast_value(value, valtype, var->datatype->typoid,
                                                        &(var->datatype->typinput),
                                                        var->datatype->typioparam,
-                                                       var->datatype->atttypmod, &isnull);
+                                                       var->datatype->atttypmod, isnull);
        if (isnull)
                ereport(ERROR,
                                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                errmsg("upper bound of FOR loop cannot be NULL")));
+                                errmsg("upper bound of FOR loop cannot be null")));
+       end_value = DatumGetInt32(value);
        exec_eval_cleanup(estate);
 
+       /*
+        * Get the step value
+        */
+       if (stmt->step)
+       {
+               value = exec_eval_expr(estate, stmt->step, &isnull, &valtype);
+               value = exec_cast_value(value, valtype, var->datatype->typoid,
+                                                               &(var->datatype->typinput),
+                                                               var->datatype->typioparam,
+                                                               var->datatype->atttypmod, isnull);
+               if (isnull)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                        errmsg("BY value of FOR loop cannot be null")));
+               step_value = DatumGetInt32(value);
+               exec_eval_cleanup(estate);
+               if (step_value <= 0)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                 errmsg("BY value of FOR loop must be greater than zero")));
+       }
+       else
+               step_value = 1;
+
        /*
         * Now do the loop
         */
        for (;;)
        {
                /*
-                * Check bounds
+                * Check against upper bound
                 */
                if (stmt->reverse)
                {
-                       if ((int4) (var->value) < (int4) value)
+                       if (loop_value < end_value)
                                break;
                }
                else
                {
-                       if ((int4) (var->value) > (int4) value)
+                       if (loop_value > end_value)
                                break;
                }
 
                found = true;                   /* looped at least once */
 
+               /*
+                * Assign current value to loop var
+                */
+               var->value = Int32GetDatum(loop_value);
+               var->isnull = false;
+
                /*
                 * Execute the statements
                 */
                rc = exec_stmts(estate, stmt->body);
 
-               if (rc == PLPGSQL_RC_RETURN)
-                       break;                          /* return from function */
+               if (rc == PLPGSQL_RC_RETURN ||
+                       rc == PLPGSQL_RC_RERAISE)
+                       break;                          /* break out of the loop */
                else if (rc == PLPGSQL_RC_EXIT)
                {
                        if (estate->exitlabel == NULL)
@@ -1366,28 +1763,58 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
                        }
 
                        /*
-                        * otherwise, we processed a labelled exit that does not match
-                        * the current statement's label, if any: return RC_EXIT so
-                        * that the EXIT continues to recurse upward.
+                        * otherwise, this is a labelled exit that does not match the
+                        * current statement's label, if any: return RC_EXIT so that the
+                        * EXIT continues to propagate up the stack.
                         */
-
                        break;
                }
+               else if (rc == PLPGSQL_RC_CONTINUE)
+               {
+                       if (estate->exitlabel == NULL)
+                               /* unlabelled continue, so re-run the current loop */
+                               rc = PLPGSQL_RC_OK;
+                       else if (stmt->label != NULL &&
+                                        strcmp(stmt->label, estate->exitlabel) == 0)
+                       {
+                               /* label matches named continue, so re-run loop */
+                               estate->exitlabel = NULL;
+                               rc = PLPGSQL_RC_OK;
+                       }
+                       else
+                       {
+                               /*
+                                * otherwise, this is a named continue that does not match the
+                                * current statement's label, if any: return RC_CONTINUE so
+                                * that the CONTINUE will propagate up the stack.
+                                */
+                               break;
+                       }
+               }
 
                /*
-                * Increase/decrease loop var
+                * Increase/decrease loop value, unless it would overflow, in which
+                * case exit the loop.
                 */
                if (stmt->reverse)
-                       var->value--;
+               {
+                       if ((int32) (loop_value - step_value) > loop_value)
+                               break;
+                       loop_value -= step_value;
+               }
                else
-                       var->value++;
+               {
+                       if ((int32) (loop_value + step_value) < loop_value)
+                               break;
+                       loop_value += step_value;
+               }
        }
 
        /*
         * Set the FOUND variable to indicate the result of executing the loop
-        * (namely, whether we looped one or more times). This must be set
-        * here so that it does not interfere with the value of the FOUND
-        * variable inside the loop processing itself.
+        * (namely, whether we looped one or more times). This must be set here so
+        * that it does not interfere with the value of the FOUND variable inside
+        * the loop processing itself.
         */
        exec_set_found(estate, found);
 
@@ -1405,209 +1832,182 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
 static int
 exec_stmt_fors(PLpgSQL_execstate *estate, PLpgSQL_stmt_fors *stmt)
 {
-       PLpgSQL_rec *rec = NULL;
-       PLpgSQL_row *row = NULL;
-       SPITupleTable *tuptab;
        Portal          portal;
-       bool            found = false;
-       int                     rc = PLPGSQL_RC_OK;
-       int                     i;
-       int                     n;
-
-       /*
-        * Determine if we assign to a record or a row
-        */
-       if (stmt->rec != NULL)
-               rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
-       else if (stmt->row != NULL)
-               row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
-       else
-               elog(ERROR, "unsupported target");
+       int                     rc;
 
        /*
-        * Open the implicit cursor for the statement and fetch the initial 10
-        * rows.
+        * Open the implicit cursor for the statement using exec_run_select
         */
        exec_run_select(estate, stmt->query, 0, &portal);
 
-       SPI_cursor_fetch(portal, true, 10);
-       tuptab = SPI_tuptable;
-       n = SPI_processed;
-
-       /*
-        * If the query didn't return any rows, set the target to NULL and
-        * return with FOUND = false.
-        */
-       if (n == 0)
-               exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
-       else
-               found = true;                   /* processed at least one tuple */
-
-       /*
-        * Now do the loop
-        */
-       while (n > 0)
-       {
-               for (i = 0; i < n; i++)
-               {
-                       /*
-                        * Assign the tuple to the target
-                        */
-                       exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
-
-                       /*
-                        * Execute the statements
-                        */
-                       rc = exec_stmts(estate, stmt->body);
-
-                       if (rc != PLPGSQL_RC_OK)
-                       {
-                               /*
-                                * We're aborting the loop, so cleanup and set FOUND.
-                                * (This code should match the code after the loop.)
-                                */
-                               SPI_freetuptable(tuptab);
-                               SPI_cursor_close(portal);
-                               exec_set_found(estate, found);
-
-                               if (rc == PLPGSQL_RC_EXIT)
-                               {
-                                       if (estate->exitlabel == NULL)
-                                               /* unlabelled exit, finish the current loop */
-                                               rc = PLPGSQL_RC_OK;
-                                       else if (stmt->label != NULL &&
-                                                        strcmp(stmt->label, estate->exitlabel) == 0)
-                                       {
-                                               /* labelled exit, matches the current stmt's label */
-                                               estate->exitlabel = NULL;
-                                               rc = PLPGSQL_RC_OK;
-                                       }
-
-                                       /*
-                                        * otherwise, we processed a labelled exit that does
-                                        * not match the current statement's label, if any:
-                                        * return RC_EXIT so that the EXIT continues to
-                                        * recurse upward.
-                                        */
-                               }
-
-                               return rc;
-                       }
-               }
-
-               SPI_freetuptable(tuptab);
-
-               /*
-                * Fetch the next 50 tuples
-                */
-               SPI_cursor_fetch(portal, true, 50);
-               n = SPI_processed;
-               tuptab = SPI_tuptable;
-       }
-
        /*
-        * Release last group of tuples
+        * Execute the loop
         */
-       SPI_freetuptable(tuptab);
+       rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, true);
 
        /*
         * Close the implicit cursor
         */
        SPI_cursor_close(portal);
 
-       /*
-        * Set the FOUND variable to indicate the result of executing the loop
-        * (namely, whether we looped one or more times). This must be set
-        * here so that it does not interfere with the value of the FOUND
-        * variable inside the loop processing itself.
-        */
-       exec_set_found(estate, found);
-
        return rc;
 }
 
 
 /* ----------
- * exec_stmt_select                    Run a query and assign the first
- *                                     row to a record or rowtype.
+ * exec_stmt_forc                      Execute a loop for each row from a cursor.
  * ----------
  */
 static int
-exec_stmt_select(PLpgSQL_execstate *estate, PLpgSQL_stmt_select *stmt)
+exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
 {
-       PLpgSQL_rec *rec = NULL;
-       PLpgSQL_row *row = NULL;
-       SPITupleTable *tuptab;
-       uint32          n;
+       PLpgSQL_var *curvar;
+       char       *curname = NULL;
+       PLpgSQL_expr *query;
+       Portal          portal;
+       int                     rc;
+       Datum      *values;
+       char       *nulls;
 
-       /*
-        * Initialize the global found variable to false
+       /* ----------
+        * Get the cursor variable and if it has an assigned name, check
+        * that it's not in use currently.
+        * ----------
         */
-       exec_set_found(estate, false);
+       curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
+       if (!curvar->isnull)
+       {
+               curname = TextDatumGetCString(curvar->value);
+               if (SPI_cursor_find(curname) != NULL)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DUPLICATE_CURSOR),
+                                        errmsg("cursor \"%s\" already in use", curname)));
+       }
 
-       /*
-        * Determine if we assign to a record or a row
+       /* ----------
+        * Open the cursor just like an OPEN command
+        *
+        * Note: parser should already have checked that statement supplies
+        * args iff cursor needs them, but we check again to be safe.
+        * ----------
         */
-       if (stmt->rec != NULL)
-               rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
-       else if (stmt->row != NULL)
-               row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
+       if (stmt->argquery != NULL)
+       {
+               /* ----------
+                * OPEN CURSOR with args.  We fake a SELECT ... INTO ...
+                * statement to evaluate the args and put 'em into the
+                * internal row.
+                * ----------
+                */
+               PLpgSQL_stmt_execsql set_args;
+
+               if (curvar->cursor_explicit_argrow < 0)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                       errmsg("arguments given for cursor without arguments")));
+
+               memset(&set_args, 0, sizeof(set_args));
+               set_args.cmd_type = PLPGSQL_STMT_EXECSQL;
+               set_args.lineno = stmt->lineno;
+               set_args.sqlstmt = stmt->argquery;
+               set_args.into = true;
+               /* XXX historically this has not been STRICT */
+               set_args.row = (PLpgSQL_row *)
+                       (estate->datums[curvar->cursor_explicit_argrow]);
+
+               if (exec_stmt_execsql(estate, &set_args) != PLPGSQL_RC_OK)
+                       elog(ERROR, "open cursor failed during argument processing");
+       }
        else
-               elog(ERROR, "unsupported target");
+       {
+               if (curvar->cursor_explicit_argrow >= 0)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("arguments required for cursor")));
+       }
+
+       query = curvar->cursor_explicit_expr;
+       Assert(query);
+
+       if (query->plan == NULL)
+               exec_prepare_plan(estate, query, curvar->cursor_options);
 
        /*
-        * Run the query
+        * Now build up the values and nulls arguments for SPI_execute_plan()
         */
-       exec_run_select(estate, stmt->query, 1, NULL);
-       tuptab = estate->eval_tuptable;
-       n = estate->eval_processed;
+       eval_expr_params(estate, query, &values, &nulls);
 
        /*
-        * If the query didn't return any row, set the target to NULL and
-        * return.
+        * Open the cursor
         */
-       if (n == 0)
-       {
-               exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
-               exec_eval_cleanup(estate);
-               return PLPGSQL_RC_OK;
-       }
+       portal = SPI_cursor_open(curname, query->plan, values, nulls,
+                                                        estate->readonly_func);
+       if (portal == NULL)
+               elog(ERROR, "could not open cursor: %s",
+                        SPI_result_code_string(SPI_result));
 
        /*
-        * Put the result into the target and set found to true
+        * If cursor variable was NULL, store the generated portal name in it
         */
-       exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
-       exec_set_found(estate, true);
+       if (curname == NULL)
+               assign_text_var(curvar, portal->name);
 
-       exec_eval_cleanup(estate);
+       /*
+        * 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.
+        */
+       rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, false);
 
-       return PLPGSQL_RC_OK;
+       /* ----------
+        * Close portal, and restore cursor variable if it was initially NULL.
+        * ----------
+        */
+       SPI_cursor_close(portal);
+
+       if (curname == NULL)
+       {
+               free_var(curvar);
+               curvar->value = (Datum) 0;
+               curvar->isnull = true;
+       }
+
+       pfree(values);
+       pfree(nulls);
+       if (curname)
+               pfree(curname);
+
+       return rc;
 }
 
 
 /* ----------
- * exec_stmt_exit                      Start exiting loop(s) or blocks
+ * exec_stmt_exit                      Implements EXIT and CONTINUE
+ *
+ * This begins the process of exiting / restarting a loop.
  * ----------
  */
 static int
 exec_stmt_exit(PLpgSQL_execstate *estate, PLpgSQL_stmt_exit *stmt)
 {
        /*
-        * If the exit has a condition, check that it's true
+        * If the exit / continue has a condition, evaluate it
         */
        if (stmt->cond != NULL)
        {
                bool            value;
-               bool            isnull = false;
+               bool            isnull;
 
                value = exec_eval_boolean(estate, stmt->cond, &isnull);
                exec_eval_cleanup(estate);
-               if (isnull || !value)
+               if (isnull || value == false)
                        return PLPGSQL_RC_OK;
        }
 
        estate->exitlabel = stmt->label;
-       return PLPGSQL_RC_EXIT;
+       if (stmt->is_exit)
+               return PLPGSQL_RC_EXIT;
+       else
+               return PLPGSQL_RC_CONTINUE;
 }
 
 
@@ -1621,8 +2021,8 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
 {
        /*
         * If processing a set-returning PL/PgSQL function, the final RETURN
-        * indicates that the function is finished producing tuples.  The rest
-        * of the work will be done at the top level.
+        * indicates that the function is finished producing tuples.  The rest of
+        * the work will be done at the top level.
         */
        if (estate->retisset)
                return PLPGSQL_RC_RETURN;
@@ -1639,41 +2039,42 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
                switch (retvar->dtype)
                {
                        case PLPGSQL_DTYPE_VAR:
-                       {
-                               PLpgSQL_var *var = (PLpgSQL_var *) retvar;
+                               {
+                                       PLpgSQL_var *var = (PLpgSQL_var *) retvar;
 
-                               estate->retval = var->value;
-                               estate->retisnull = var->isnull;
-                               estate->rettype = var->datatype->typoid;
-                       }
-                       break;
+                                       estate->retval = var->value;
+                                       estate->retisnull = var->isnull;
+                                       estate->rettype = var->datatype->typoid;
+                               }
+                               break;
 
                        case PLPGSQL_DTYPE_REC:
-                       {
-                               PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
-
-                               if (HeapTupleIsValid(rec->tup))
                                {
-                                       estate->retval = (Datum) rec->tup;
-                                       estate->rettupdesc = rec->tupdesc;
-                                       estate->retisnull = false;
+                                       PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
+
+                                       if (HeapTupleIsValid(rec->tup))
+                                       {
+                                               estate->retval = PointerGetDatum(rec->tup);
+                                               estate->rettupdesc = rec->tupdesc;
+                                               estate->retisnull = false;
+                                       }
                                }
-                       }
-                       break;
+                               break;
 
                        case PLPGSQL_DTYPE_ROW:
-                       {
-                               PLpgSQL_row *row = (PLpgSQL_row *) retvar;
-
-                               Assert(row->rowtupdesc);
-                               estate->retval = (Datum) make_tuple_from_row(estate, row,
-                                                                                                                        row->rowtupdesc);
-                               if (estate->retval == (Datum) NULL)     /* should not happen */
-                                       elog(ERROR, "row not compatible with its own tupdesc");
-                               estate->rettupdesc = row->rowtupdesc;
-                               estate->retisnull = false;
-                       }
-                       break;
+                               {
+                                       PLpgSQL_row *row = (PLpgSQL_row *) retvar;
+
+                                       Assert(row->rowtupdesc);
+                                       estate->retval =
+                                               PointerGetDatum(make_tuple_from_row(estate, row,
+                                                                                                                       row->rowtupdesc));
+                                       if (DatumGetPointer(estate->retval) == NULL) /* should not happen */
+                                               elog(ERROR, "row not compatible with its own tupdesc");
+                                       estate->rettupdesc = row->rowtupdesc;
+                                       estate->retisnull = false;
+                               }
+                               break;
 
                        default:
                                elog(ERROR, "unrecognized dtype: %d", retvar->dtype);
@@ -1689,7 +2090,7 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
                        exec_run_select(estate, stmt->expr, 1, NULL);
                        if (estate->eval_processed > 0)
                        {
-                               estate->retval = (Datum) estate->eval_tuptable->vals[0];
+                               estate->retval = PointerGetDatum(estate->eval_tuptable->vals[0]);
                                estate->rettupdesc = estate->eval_tuptable->tupdesc;
                                estate->retisnull = false;
                        }
@@ -1708,8 +2109,8 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
        /*
         * Special hack for function returning VOID: instead of NULL, return a
         * non-null VOID value.  This is of dubious importance but is kept for
-        * backwards compatibility.  Note that the only other way to get here
-        * is to have written "RETURN NULL" in a function returning tuple.
+        * backwards compatibility.  Note that the only other way to get here is
+        * to have written "RETURN NULL" in a function returning tuple.
         */
        if (estate->fn_rettype == VOIDOID)
        {
@@ -1731,15 +2132,16 @@ static int
 exec_stmt_return_next(PLpgSQL_execstate *estate,
                                          PLpgSQL_stmt_return_next *stmt)
 {
-       TupleDesc       tupdesc;
-       int                     natts;
-       HeapTuple       tuple;
-       bool            free_tuple = false;
+       TupleDesc               tupdesc;
+       int                             natts;
+       MemoryContext   oldcxt;
+       HeapTuple               tuple = NULL;
+       bool                    free_tuple = false;
 
        if (!estate->retisset)
                ereport(ERROR,
                                (errcode(ERRCODE_SYNTAX_ERROR),
-                         errmsg("cannot use RETURN NEXT in a non-SETOF function")));
+                                errmsg("cannot use RETURN NEXT in a non-SETOF function")));
 
        if (estate->tuple_store == NULL)
                exec_init_tuple_store(estate);
@@ -1755,63 +2157,62 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
                switch (retvar->dtype)
                {
                        case PLPGSQL_DTYPE_VAR:
-                       {
-                               PLpgSQL_var *var = (PLpgSQL_var *) retvar;
-                               Datum           retval = var->value;
-                               bool            isNull = var->isnull;
+                               {
+                                       PLpgSQL_var *var = (PLpgSQL_var *) retvar;
+                                       Datum           retval = var->value;
+                                       bool            isNull = var->isnull;
 
-                               if (natts != 1)
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                        errmsg("wrong result type supplied in RETURN NEXT")));
+                                       if (natts != 1)
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                               errmsg("wrong result type supplied in RETURN NEXT")));
 
-                               /* coerce type if needed */
-                               retval = exec_simple_cast_value(retval,
-                                                                                               var->datatype->typoid,
-                                                                                               tupdesc->attrs[0]->atttypid,
+                                       /* coerce type if needed */
+                                       retval = exec_simple_cast_value(retval,
+                                                                                                       var->datatype->typoid,
+                                                                                                tupdesc->attrs[0]->atttypid,
                                                                                                tupdesc->attrs[0]->atttypmod,
-                                                                                               &isNull);
-
-                               tuple = heap_form_tuple(tupdesc, &retval, &isNull);
+                                                                                                       isNull);
 
-                               free_tuple = true;
-                       }
-                       break;
+                                       oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
+                                       tuplestore_putvalues(estate->tuple_store, tupdesc,
+                                                                                &retval, &isNull);
+                                       MemoryContextSwitchTo(oldcxt);
+                               }
+                               break;
 
                        case PLPGSQL_DTYPE_REC:
-                       {
-                               PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
+                               {
+                                       PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
 
-                               if (!HeapTupleIsValid(rec->tup))
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                                        errmsg("record \"%s\" is not assigned yet",
-                                                                       rec->refname),
-                                                        errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
-                               if (!compatible_tupdesc(tupdesc, rec->tupdesc))
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                        errmsg("wrong record type supplied in RETURN NEXT")));
-                               tuple = rec->tup;
-                       }
-                       break;
+                                       if (!HeapTupleIsValid(rec->tup))
+                                               ereport(ERROR,
+                                                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                                  errmsg("record \"%s\" is not assigned yet",
+                                                                 rec->refname),
+                                                  errdetail("The tuple structure of a not-yet-assigned"
+                                                                        " record is indeterminate.")));
+                                       validate_tupdesc_compat(tupdesc, rec->tupdesc,
+                                                                   "wrong record type supplied in RETURN NEXT");
+                                       tuple = rec->tup;
+                               }
+                               break;
 
                        case PLPGSQL_DTYPE_ROW:
-                       {
-                               PLpgSQL_row *row = (PLpgSQL_row *) retvar;
+                               {
+                                       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;
+                                       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;
 
                        default:
                                elog(ERROR, "unrecognized dtype: %d", retvar->dtype);
-                               tuple = NULL;   /* keep compiler quiet */
                                break;
                }
        }
@@ -1824,7 +2225,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
                if (natts != 1)
                        ereport(ERROR,
                                        (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                  errmsg("wrong result type supplied in RETURN NEXT")));
+                                        errmsg("wrong result type supplied in RETURN NEXT")));
 
                retval = exec_eval_expr(estate,
                                                                stmt->expr,
@@ -1836,11 +2237,12 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
                                                                                rettype,
                                                                                tupdesc->attrs[0]->atttypid,
                                                                                tupdesc->attrs[0]->atttypmod,
-                                                                               &isNull);
-
-               tuple = heap_form_tuple(tupdesc, &retval, &isNull);
+                                                                               isNull);
 
-               free_tuple = true;
+               oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
+               tuplestore_putvalues(estate->tuple_store, tupdesc,
+                                                        &retval, &isNull);
+               MemoryContextSwitchTo(oldcxt);
 
                exec_eval_cleanup(estate);
        }
@@ -1849,13 +2251,10 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
                ereport(ERROR,
                                (errcode(ERRCODE_SYNTAX_ERROR),
                                 errmsg("RETURN NEXT must have a parameter")));
-               tuple = NULL;                   /* keep compiler quiet */
        }
 
        if (HeapTupleIsValid(tuple))
        {
-               MemoryContext oldcxt;
-
                oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
                tuplestore_puttuple(estate->tuple_store, tuple);
                MemoryContextSwitchTo(oldcxt);
@@ -1867,6 +2266,74 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
        return PLPGSQL_RC_OK;
 }
 
+/* ----------
+ * exec_stmt_return_query              Evaluate a query and add it to the
+ *                                                             list of tuples returned by the current
+ *                                                             SRF.
+ * ----------
+ */
+static int
+exec_stmt_return_query(PLpgSQL_execstate *estate,
+                                          PLpgSQL_stmt_return_query *stmt)
+{
+       Portal          portal;
+       uint32                  processed = 0;
+
+       if (!estate->retisset)
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("cannot use RETURN QUERY in a non-SETOF function")));
+
+       if (estate->tuple_store == NULL)
+               exec_init_tuple_store(estate);
+
+       if (stmt->query != NULL)
+       {
+               /* static query */
+               exec_run_select(estate, stmt->query, 0, &portal);
+       }
+       else
+       {
+               /* RETURN QUERY EXECUTE */
+               Assert(stmt->dynquery != NULL);
+               portal = exec_dynquery_with_params(estate, stmt->dynquery,
+                                                                                  stmt->params);
+       }
+
+       validate_tupdesc_compat(estate->rettupdesc, portal->tupDesc,
+                                                       "structure of query does not match function result type");
+
+       while (true)
+       {
+               MemoryContext old_cxt;
+               int                     i;
+
+               SPI_cursor_fetch(portal, true, 50);
+               if (SPI_processed == 0)
+                       break;
+
+               old_cxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
+               for (i = 0; i < SPI_processed; i++)
+               {
+                       HeapTuple       tuple = SPI_tuptable->vals[i];
+
+                       tuplestore_puttuple(estate->tuple_store, tuple);
+                       processed++;
+               }
+               MemoryContextSwitchTo(old_cxt);
+
+               SPI_freetuptable(SPI_tuptable);
+       }
+
+       SPI_freetuptable(SPI_tuptable);
+       SPI_cursor_close(portal);
+
+       estate->eval_processed = processed;
+       exec_set_found(estate, processed != 0);
+
+       return PLPGSQL_RC_OK;
+}
+
 static void
 exec_init_tuple_store(PLpgSQL_execstate *estate)
 {
@@ -1886,7 +2353,9 @@ exec_init_tuple_store(PLpgSQL_execstate *estate)
        estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
 
        oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
-       estate->tuple_store = tuplestore_begin_heap(true, false, work_mem);
+       estate->tuple_store =
+               tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+                                                         false, work_mem);
        MemoryContextSwitchTo(oldcxt);
 
        estate->rettupdesc = rsi->expectedDesc;
@@ -1899,64 +2368,163 @@ exec_init_tuple_store(PLpgSQL_execstate *estate)
 static int
 exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
 {
-       char       *cp;
-       PLpgSQL_dstring ds;
-       ListCell   *current_param;
-
-       plpgsql_dstring_init(&ds);
-       current_param = list_head(stmt->params);
+       int                     err_code = 0;
+       char       *condname = NULL;
+       char       *err_message = NULL;
+       char       *err_detail = NULL;
+       char       *err_hint = NULL;
+       ListCell   *lc;
+
+       /* RAISE with no parameters: re-throw current exception */
+       if (stmt->condname == NULL && stmt->message == NULL &&
+               stmt->options == NIL)
+               return PLPGSQL_RC_RERAISE;
+
+       if (stmt->condname)
+       {
+               err_code = plpgsql_recognize_err_condition(stmt->condname, true);
+               condname = pstrdup(stmt->condname);
+       }
 
-       for (cp = stmt->message; *cp; cp++)
+       if (stmt->message)
        {
-               /*
-                * Occurrences of a single % are replaced by the next parameter's
-                * external representation. Double %'s are converted to one %.
-                */
-               if (cp[0] == '%')
-               {
-                       Oid                     paramtypeid;
-                       Datum           paramvalue;
-                       bool            paramisnull;
-                       char       *extval;
+               PLpgSQL_dstring ds;
+               ListCell   *current_param;
+               char       *cp;
+
+               plpgsql_dstring_init(&ds);
+               current_param = list_head(stmt->params);
 
-                       if (cp[1] == '%')
+               for (cp = stmt->message; *cp; cp++)
+               {
+                       /*
+                        * Occurrences of a single % are replaced by the next parameter's
+                        * external representation. Double %'s are converted to one %.
+                        */
+                       if (cp[0] == '%')
                        {
-                               plpgsql_dstring_append_char(&ds, cp[1]);
-                               cp++;
-                               continue;
-                       }
+                               Oid                     paramtypeid;
+                               Datum           paramvalue;
+                               bool            paramisnull;
+                               char       *extval;
 
-                       if (current_param == NULL)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                errmsg("too few parameters specified for RAISE")));
+                               if (cp[1] == '%')
+                               {
+                                       plpgsql_dstring_append_char(&ds, cp[1]);
+                                       cp++;
+                                       continue;
+                               }
 
-                       paramvalue = exec_eval_expr(estate,
-                                                                               (PLpgSQL_expr *) lfirst(current_param),
-                                                                               &paramisnull,
-                                                                               &paramtypeid);
+                               if (current_param == NULL)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                        errmsg("too few parameters specified for RAISE")));
 
-                       if (paramisnull)
-                               extval = "<NULL>";
+                               paramvalue = exec_eval_expr(estate,
+                                                                                       (PLpgSQL_expr *) lfirst(current_param),
+                                                                                       &paramisnull,
+                                                                                       &paramtypeid);
+
+                               if (paramisnull)
+                                       extval = "<NULL>";
+                               else
+                                       extval = convert_value_to_string(paramvalue, paramtypeid);
+                               plpgsql_dstring_append(&ds, extval);
+                               current_param = lnext(current_param);
+                               exec_eval_cleanup(estate);
+                       }
                        else
-                               extval = convert_value_to_string(paramvalue, paramtypeid);
-                       plpgsql_dstring_append(&ds, extval);
-                       current_param = lnext(current_param);
-                       exec_eval_cleanup(estate);
-                       continue;
+                               plpgsql_dstring_append_char(&ds, cp[0]);
                }
 
-               plpgsql_dstring_append_char(&ds, cp[0]);
+               /*
+                * If more parameters were specified than were required to process the
+                * format string, throw an error
+                */
+               if (current_param != NULL)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("too many parameters specified for RAISE")));
+
+               err_message = plpgsql_dstring_get(&ds);
+               /* No dstring_free here, the pfree(err_message) does it */
        }
 
-       /*
-        * If more parameters were specified than were required to process
-        * the format string, throw an error
-        */
-       if (current_param != NULL)
-               ereport(ERROR,
-                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                errmsg("too many parameters specified for RAISE")));
+       foreach(lc, stmt->options)
+       {
+               PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
+               Datum           optionvalue;
+               bool            optionisnull;
+               Oid                     optiontypeid;
+               char       *extval;
+
+               optionvalue = exec_eval_expr(estate, opt->expr,
+                                                                        &optionisnull,
+                                                                        &optiontypeid);
+               if (optionisnull)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                        errmsg("RAISE statement option cannot be null")));
+
+               extval = convert_value_to_string(optionvalue, optiontypeid);
+
+               switch (opt->opt_type)
+               {
+                       case PLPGSQL_RAISEOPTION_ERRCODE:
+                               if (err_code)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                        errmsg("RAISE option already specified: %s",
+                                                                       "ERRCODE")));
+                               err_code = plpgsql_recognize_err_condition(extval, true);
+                               condname = pstrdup(extval);
+                               break;
+                       case PLPGSQL_RAISEOPTION_MESSAGE:
+                               if (err_message)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                        errmsg("RAISE option already specified: %s",
+                                                                       "MESSAGE")));
+                               err_message = pstrdup(extval);
+                               break;
+                       case PLPGSQL_RAISEOPTION_DETAIL:
+                               if (err_detail)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                        errmsg("RAISE option already specified: %s",
+                                                                       "DETAIL")));
+                               err_detail = pstrdup(extval);
+                               break;
+                       case PLPGSQL_RAISEOPTION_HINT:
+                               if (err_hint)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                        errmsg("RAISE option already specified: %s",
+                                                                       "HINT")));
+                               err_hint = pstrdup(extval);
+                               break;
+                       default:
+                               elog(ERROR, "unrecognized raise option: %d", opt->opt_type);
+               }
+
+               exec_eval_cleanup(estate);
+       }
+
+       /* Default code if nothing specified */
+       if (err_code == 0 && stmt->elog_level >= ERROR)
+               err_code = ERRCODE_RAISE_EXCEPTION;
+
+       /* Default error message if nothing specified */
+       if (err_message == NULL)
+       {
+               if (condname)
+               {
+                       err_message = condname;
+                       condname = NULL;
+               }
+               else
+                       err_message = pstrdup(unpack_sql_state(err_code));
+       }
 
        /*
         * Throw the error (may or may not come back)
@@ -1964,12 +2532,21 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
        estate->err_text = raise_skip_msg;      /* suppress traceback of raise */
 
        ereport(stmt->elog_level,
-        ((stmt->elog_level >= ERROR) ? errcode(ERRCODE_RAISE_EXCEPTION) : 0,
-         errmsg_internal("%s", plpgsql_dstring_get(&ds))));
+                       (err_code ? errcode(err_code) : 0,
+                        errmsg_internal("%s", err_message),
+                        (err_detail != NULL) ? errdetail("%s", err_detail) : 0,
+                        (err_hint != NULL) ? errhint("%s", err_hint) : 0));
 
        estate->err_text = NULL;        /* un-suppress... */
 
-       plpgsql_dstring_free(&ds);
+       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);
 
        return PLPGSQL_RC_OK;
 }
@@ -2012,11 +2589,30 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
        estate->eval_tuptable = NULL;
        estate->eval_processed = 0;
        estate->eval_lastoid = InvalidOid;
-       estate->eval_econtext = NULL;
 
        estate->err_func = func;
        estate->err_stmt = NULL;
        estate->err_text = NULL;
+
+       /*
+        * Create an EState and ExprContext for evaluation of simple expressions.
+        */
+       plpgsql_create_econtext(estate);
+
+       /*
+        * Let the plugin see this function before we initialize any local
+        * PL/pgSQL variables - note that we also give the plugin a few function
+        * pointers so it can call back into PL/pgSQL for doing things like
+        * variable assignments and stack traces
+        */
+       if (*plugin_ptr)
+       {
+               (*plugin_ptr)->error_callback = plpgsql_exec_error_callback;
+               (*plugin_ptr)->assign_expr = exec_assign_expr;
+
+               if ((*plugin_ptr)->func_setup)
+                       ((*plugin_ptr)->func_setup) (estate, func);
+       }
 }
 
 /* ----------
@@ -2046,11 +2642,10 @@ exec_eval_cleanup(PLpgSQL_execstate *estate)
  */
 static void
 exec_prepare_plan(PLpgSQL_execstate *estate,
-                                 PLpgSQL_expr *expr)
+                                 PLpgSQL_expr *expr, int cursorOptions)
 {
        int                     i;
-       _SPI_plan  *spi_plan;
-       void       *plan;
+       SPIPlanPtr      plan;
        Oid                *argtypes;
 
        /*
@@ -2072,7 +2667,8 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
        /*
         * Generate and save the plan
         */
-       plan = SPI_prepare(expr->query, expr->nparams, argtypes);
+       plan = SPI_prepare_cursor(expr->query, expr->nparams, argtypes,
+                                                         cursorOptions);
        if (plan == NULL)
        {
                /* Some SPI errors deserve specific error messages */
@@ -2081,118 +2677,209 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
                        case SPI_ERROR_COPY:
                                ereport(ERROR,
                                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                         errmsg("cannot COPY to/from client in PL/pgSQL")));
-                       case SPI_ERROR_CURSOR:
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                               errmsg("cannot manipulate cursors directly in PL/pgSQL"),
-                                       errhint("Use PL/pgSQL's cursor features instead.")));
+                                                errmsg("cannot COPY to/from client in PL/pgSQL")));
                        case SPI_ERROR_TRANSACTION:
                                ereport(ERROR,
                                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                        errmsg("cannot begin/end transactions in PL/pgSQL"),
+                                                errmsg("cannot begin/end transactions in PL/pgSQL"),
                                                 errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
                        default:
-                               elog(ERROR, "SPI_prepare failed for \"%s\": %s",
+                               elog(ERROR, "SPI_prepare_cursor failed for \"%s\": %s",
                                         expr->query, SPI_result_code_string(SPI_result));
                }
        }
        expr->plan = SPI_saveplan(plan);
-       spi_plan = (_SPI_plan *) expr->plan;
-       expr->plan_argtypes = spi_plan->argtypes;
-       expr->expr_simple_expr = NULL;
+       SPI_freeplan(plan);
+       plan = expr->plan;
+       expr->plan_argtypes = plan->argtypes;
        exec_simple_check_plan(expr);
 
-       SPI_freeplan(plan);
        pfree(argtypes);
 }
 
 
 /* ----------
- * exec_stmt_execsql                   Execute an SQL statement not
- *                                     returning any data.
+ * exec_stmt_execsql                   Execute an SQL statement (possibly with INTO).
  * ----------
  */
 static int
 exec_stmt_execsql(PLpgSQL_execstate *estate,
                                  PLpgSQL_stmt_execsql *stmt)
 {
-       int                     i;
        Datum      *values;
        char       *nulls;
+       long            tcount;
        int                     rc;
        PLpgSQL_expr *expr = stmt->sqlstmt;
 
        /*
-        * On the first call for this expression generate the plan
+        * On the first call for this statement generate the plan, and detect
+        * whether the statement is INSERT/UPDATE/DELETE
         */
        if (expr->plan == NULL)
-               exec_prepare_plan(estate, expr);
+       {
+               ListCell   *l;
+
+               exec_prepare_plan(estate, expr, 0);
+               stmt->mod_stmt = false;
+               foreach(l, expr->plan->plancache_list)
+               {
+                       CachedPlanSource *plansource = (CachedPlanSource *) lfirst(l);
+                       ListCell   *l2;
+
+                       foreach(l2, plansource->plan->stmt_list)
+                       {
+                               PlannedStmt *p = (PlannedStmt *) lfirst(l2);
+
+                               if (IsA(p, PlannedStmt) &&
+                                       p->canSetTag)
+                               {
+                                       if (p->commandType == CMD_INSERT ||
+                                               p->commandType == CMD_UPDATE ||
+                                               p->commandType == CMD_DELETE)
+                                               stmt->mod_stmt = true;
+                               }
+                       }
+               }
+       }
 
        /*
         * Now build up the values and nulls arguments for SPI_execute_plan()
         */
-       values = (Datum *) palloc(expr->nparams * sizeof(Datum));
-       nulls = (char *) palloc(expr->nparams * sizeof(char));
+       eval_expr_params(estate, expr, &values, &nulls);
 
-       for (i = 0; i < expr->nparams; i++)
+       /*
+        * If we have INTO, then we only need one row back ... but if we have INTO
+        * STRICT, ask for two rows, so that we can verify the statement returns
+        * only one.  INSERT/UPDATE/DELETE are always treated strictly. Without
+        * INTO, just run the statement to completion (tcount = 0).
+        *
+        * We could just ask for two rows always when using INTO, but there are
+        * some cases where demanding the extra row costs significant time, eg by
+        * forcing completion of a sequential scan.  So don't do it unless we need
+        * to enforce strictness.
+        */
+       if (stmt->into)
        {
-               PLpgSQL_datum *datum = estate->datums[expr->params[i]];
-               Oid                     paramtypeid;
-               bool            paramisnull;
-
-               exec_eval_datum(estate, datum, expr->plan_argtypes[i],
-                                               &paramtypeid, &values[i], &paramisnull);
-               if (paramisnull)
-                       nulls[i] = 'n';
+               if (stmt->strict || stmt->mod_stmt)
+                       tcount = 2;
                else
-                       nulls[i] = ' ';
+                       tcount = 1;
        }
+       else
+               tcount = 0;
 
        /*
         * Execute the plan
         */
        rc = SPI_execute_plan(expr->plan, values, nulls,
-                                                 estate->readonly_func, 0);
+                                                 estate->readonly_func, tcount);
+
+       /*
+        * Check for error, and set FOUND if appropriate (for historical reasons
+        * we set FOUND only for certain query types).  Also Assert that we
+        * identified the statement type the same as SPI did.
+        */
        switch (rc)
        {
-               case SPI_OK_UTILITY:
-               case SPI_OK_SELINTO:
+               case SPI_OK_SELECT:
+                       Assert(!stmt->mod_stmt);
+                       exec_set_found(estate, (SPI_processed != 0));
                        break;
 
                case SPI_OK_INSERT:
-               case SPI_OK_DELETE:
                case SPI_OK_UPDATE:
+               case SPI_OK_DELETE:
+               case SPI_OK_INSERT_RETURNING:
+               case SPI_OK_UPDATE_RETURNING:
+               case SPI_OK_DELETE_RETURNING:
+                       Assert(stmt->mod_stmt);
+                       exec_set_found(estate, (SPI_processed != 0));
+                       break;
+
+               case SPI_OK_SELINTO:
+               case SPI_OK_UTILITY:
+                       Assert(!stmt->mod_stmt);
+                       break;
 
+               case SPI_OK_REWRITTEN:
+                       Assert(!stmt->mod_stmt);
                        /*
-                        * If the INSERT, DELETE, or UPDATE query affected at least
-                        * one tuple, set the magic 'FOUND' variable to true. This
-                        * conforms with the behavior of PL/SQL.
+                        * The command was rewritten into another kind of command. It's
+                        * not clear what FOUND would mean in that case (and SPI doesn't
+                        * return the row count either), so just set it to false.
                         */
-                       exec_set_found(estate, (SPI_processed != 0));
+                       exec_set_found(estate, false);
                        break;
 
-               case SPI_OK_SELECT:
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_SYNTAX_ERROR),
-                          errmsg("SELECT query has no destination for result data"),
-                                        errhint("If you want to discard the results, use PERFORM instead.")));
-
                default:
                        elog(ERROR, "SPI_execute_plan failed executing query \"%s\": %s",
                                 expr->query, SPI_result_code_string(rc));
        }
 
-       /*
-        * Release any result tuples from SPI_execute_plan (probably shouldn't be
-        * any)
-        */
-       SPI_freetuptable(SPI_tuptable);
-
-       /* Save result info for GET DIAGNOSTICS */
+       /* All variants should save result info for GET DIAGNOSTICS */
        estate->eval_processed = SPI_processed;
        estate->eval_lastoid = SPI_lastoid;
 
+       /* Process INTO if present */
+       if (stmt->into)
+       {
+               SPITupleTable *tuptab = SPI_tuptable;
+               uint32          n = SPI_processed;
+               PLpgSQL_rec *rec = NULL;
+               PLpgSQL_row *row = NULL;
+
+               /* If the statement did not return a tuple table, complain */
+               if (tuptab == NULL)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                               errmsg("INTO used with a command that cannot return data")));
+
+               /* Determine if we assign to a record or a row */
+               if (stmt->rec != NULL)
+                       rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->dno]);
+               else if (stmt->row != NULL)
+                       row = (PLpgSQL_row *) (estate->datums[stmt->row->dno]);
+               else
+                       elog(ERROR, "unsupported target");
+
+               /*
+                * If SELECT ... INTO specified STRICT, and the query didn't find
+                * exactly one row, throw an error.  If STRICT was not specified, then
+                * allow the query to find any number of rows.
+                */
+               if (n == 0)
+               {
+                       if (stmt->strict)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_NO_DATA_FOUND),
+                                                errmsg("query returned no rows")));
+                       /* set the target to NULL(s) */
+                       exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
+               }
+               else
+               {
+                       if (n > 1 && (stmt->strict || stmt->mod_stmt))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_TOO_MANY_ROWS),
+                                                errmsg("query returned more than one row")));
+                       /* Put the first result row into the target */
+                       exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
+               }
+
+               /* Clean up */
+               SPI_freetuptable(SPI_tuptable);
+       }
+       else
+       {
+               /* If the statement returned a tuple table, complain */
+               if (SPI_tuptable != NULL)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("query has no destination for result data"),
+                                        (rc == SPI_OK_SELECT) ? errhint("If you want to discard the results of a SELECT, use PERFORM instead.") : 0));
+       }
+
        pfree(values);
        pfree(nulls);
 
@@ -2201,8 +2888,8 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 
 
 /* ----------
- * exec_stmt_dynexecute                        Execute a dynamic SQL query not
- *                                     returning any data.
+ * exec_stmt_dynexecute                        Execute a dynamic SQL query
+ *                                     (possibly with INTO).
  * ----------
  */
 static int
@@ -2214,23 +2901,16 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
        Oid                     restype;
        char       *querystr;
        int                     exec_res;
-       PLpgSQL_rec *rec = NULL;
-       PLpgSQL_row *row = NULL;
-
-       if (stmt->rec != NULL)
-               rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
-       else if (stmt->row != NULL)
-               row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
 
        /*
-        * First we evaluate the string expression after the EXECUTE keyword.
-        * It's result is the querystring we have to execute.
+        * First we evaluate the string expression after the EXECUTE keyword. Its
+        * result is the querystring we have to execute.
         */
        query = exec_eval_expr(estate, stmt->query, &isnull, &restype);
        if (isnull)
                ereport(ERROR,
                                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                errmsg("cannot EXECUTE a null querystring")));
+                                errmsg("query string argument of EXECUTE is null")));
 
        /* Get the C-String representation */
        querystr = convert_value_to_string(query, restype);
@@ -2238,28 +2918,21 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
        exec_eval_cleanup(estate);
 
        /*
-        * Call SPI_execute() without preparing a saved plan. The returncode can
-        * be any standard OK.  Note that while a SELECT is allowed, its
-        * results will be discarded unless an INTO clause is specified.
+        * Execute the query without preparing a saved plan.
         */
-       exec_res = SPI_execute(querystr, estate->readonly_func, 0);
-
-       /* Assign to INTO variable */
-       if (rec || row)
+       if (stmt->params)
        {
-               if (exec_res != SPI_OK_SELECT)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                        errmsg("EXECUTE ... INTO is only for SELECT")));
-               else
-               {
-                       if (SPI_processed == 0)
-                               exec_move_row(estate, rec, row, NULL, SPI_tuptable->tupdesc);
-                       else
-                               exec_move_row(estate, rec, row,
-                                                         SPI_tuptable->vals[0], SPI_tuptable->tupdesc);
-               }
+               PreparedParamsData *ppd;
+
+               ppd = exec_eval_using_params(estate, stmt->params);
+               exec_res = SPI_execute_with_args(querystr,
+                                                                                ppd->nargs, ppd->types,
+                                                                                ppd->values, ppd->nulls,
+                                                                                estate->readonly_func, 0);
+               free_params_data(ppd);
        }
+       else
+               exec_res = SPI_execute(querystr, estate->readonly_func, 0);
 
        switch (exec_res)
        {
@@ -2267,7 +2940,11 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
                case SPI_OK_INSERT:
                case SPI_OK_UPDATE:
                case SPI_OK_DELETE:
+               case SPI_OK_INSERT_RETURNING:
+               case SPI_OK_UPDATE_RETURNING:
+               case SPI_OK_DELETE_RETURNING:
                case SPI_OK_UTILITY:
+               case SPI_OK_REWRITTEN:
                        break;
 
                case 0:
@@ -2281,25 +2958,24 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
                case SPI_OK_SELINTO:
 
                        /*
-                        * We want to disallow SELECT INTO for now, because its
-                        * behavior is not consistent with SELECT INTO in a normal
-                        * plpgsql context. (We need to reimplement EXECUTE to parse
-                        * the string as a plpgsql command, not just feed it to
-                        * SPI_execute.) However, CREATE AS should be allowed ... and
-                        * since it produces the same parsetree as SELECT INTO,
-                        * there's no way to tell the difference except to look at the
-                        * source text.  Wotta kluge!
+                        * We want to disallow SELECT INTO for now, because its behavior
+                        * is not consistent with SELECT INTO in a normal plpgsql context.
+                        * (We need to reimplement EXECUTE to parse the string as a
+                        * plpgsql command, not just feed it to SPI_execute.) However,
+                        * CREATE AS should be allowed ... and since it produces the same
+                        * parsetree as SELECT INTO, there's no way to tell the difference
+                        * except to look at the source text.  Wotta kluge!
                         */
                        {
                                char       *ptr;
 
                                for (ptr = querystr; *ptr; ptr++)
-                                       if (!isspace((unsigned char) *ptr))
+                                       if (!scanner_isspace(*ptr))
                                                break;
                                if (*ptr == 'S' || *ptr == 's')
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                        errmsg("EXECUTE of SELECT ... INTO is not implemented yet")));
+                                                        errmsg("EXECUTE of SELECT ... INTO is not implemented")));
                                break;
                        }
 
@@ -2308,16 +2984,11 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
                        ereport(ERROR,
                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                         errmsg("cannot COPY to/from client in PL/pgSQL")));
-               case SPI_ERROR_CURSOR:
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                               errmsg("cannot manipulate cursors directly in PL/pgSQL"),
-                                        errhint("Use PL/pgSQL's cursor features instead.")));
                case SPI_ERROR_TRANSACTION:
                        ereport(ERROR,
                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                         errmsg("cannot begin/end transactions in PL/pgSQL"),
-                                        errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
+                       errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
 
                default:
                        elog(ERROR, "SPI_execute failed executing query \"%s\": %s",
@@ -2325,14 +2996,69 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
                        break;
        }
 
-       /* Release any result from SPI_execute, as well as the querystring */
-       SPI_freetuptable(SPI_tuptable);
-       pfree(querystr);
-
        /* Save result info for GET DIAGNOSTICS */
        estate->eval_processed = SPI_processed;
        estate->eval_lastoid = SPI_lastoid;
 
+       /* Process INTO if present */
+       if (stmt->into)
+       {
+               SPITupleTable *tuptab = SPI_tuptable;
+               uint32          n = SPI_processed;
+               PLpgSQL_rec *rec = NULL;
+               PLpgSQL_row *row = NULL;
+
+               /* If the statement did not return a tuple table, complain */
+               if (tuptab == NULL)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                               errmsg("INTO used with a command that cannot return data")));
+
+               /* Determine if we assign to a record or a row */
+               if (stmt->rec != NULL)
+                       rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->dno]);
+               else if (stmt->row != NULL)
+                       row = (PLpgSQL_row *) (estate->datums[stmt->row->dno]);
+               else
+                       elog(ERROR, "unsupported target");
+
+               /*
+                * If SELECT ... INTO specified STRICT, and the query didn't find
+                * exactly one row, throw an error.  If STRICT was not specified, then
+                * allow the query to find any number of rows.
+                */
+               if (n == 0)
+               {
+                       if (stmt->strict)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_NO_DATA_FOUND),
+                                                errmsg("query returned no rows")));
+                       /* set the target to NULL(s) */
+                       exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
+               }
+               else
+               {
+                       if (n > 1 && stmt->strict)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_TOO_MANY_ROWS),
+                                                errmsg("query returned more than one row")));
+                       /* Put the first result row into the target */
+                       exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
+               }
+       }
+       else
+       {
+               /*
+                * It might be a good idea to raise an error if the query returned
+                * tuples that are being ignored, but historically we have not done
+                * that.
+                */
+       }
+
+       /* Release any result from SPI_execute, as well as the querystring */
+       SPI_freetuptable(SPI_tuptable);
+       pfree(querystr);
+
        return PLPGSQL_RC_OK;
 }
 
@@ -2347,157 +3073,22 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
 static int
 exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
 {
-       Datum           query;
-       bool            isnull = false;
-       Oid                     restype;
-       char       *querystr;
-       PLpgSQL_rec *rec = NULL;
-       PLpgSQL_row *row = NULL;
-       SPITupleTable *tuptab;
-       int                     rc = PLPGSQL_RC_OK;
-       int                     i;
-       int                     n;
-       void       *plan;
        Portal          portal;
-       bool            found = false;
-
-       /*
-        * Determine if we assign to a record or a row
-        */
-       if (stmt->rec != NULL)
-               rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
-       else if (stmt->row != NULL)
-               row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
-       else
-               elog(ERROR, "unsupported target");
-
-       /*
-        * Evaluate the string expression after the EXECUTE keyword. It's
-        * result is the querystring we have to execute.
-        */
-       query = exec_eval_expr(estate, stmt->query, &isnull, &restype);
-       if (isnull)
-               ereport(ERROR,
-                               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                errmsg("cannot EXECUTE a null querystring")));
-
-       /* Get the C-String representation */
-       querystr = convert_value_to_string(query, restype);
-
-       exec_eval_cleanup(estate);
-
-       /*
-        * Prepare a plan and open an implicit cursor for the query
-        */
-       plan = SPI_prepare(querystr, 0, NULL);
-       if (plan == NULL)
-               elog(ERROR, "SPI_prepare failed for \"%s\": %s",
-                        querystr, SPI_result_code_string(SPI_result));
-       portal = SPI_cursor_open(NULL, plan, NULL, NULL,
-                                                        estate->readonly_func);
-       if (portal == NULL)
-               elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
-                        querystr, SPI_result_code_string(SPI_result));
-       pfree(querystr);
-       SPI_freeplan(plan);
-
-       /*
-        * Fetch the initial 10 tuples
-        */
-       SPI_cursor_fetch(portal, true, 10);
-       tuptab = SPI_tuptable;
-       n = SPI_processed;
-
-       /*
-        * If the query didn't return any rows, set the target to NULL and
-        * return with FOUND = false.
-        */
-       if (n == 0)
-               exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
-       else
-               found = true;                   /* processed at least one tuple */
-
-       /*
-        * Now do the loop
-        */
-       while (n > 0)
-       {
-               for (i = 0; i < n; i++)
-               {
-                       /*
-                        * Assign the tuple to the target
-                        */
-                       exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
-
-                       /*
-                        * Execute the statements
-                        */
-                       rc = exec_stmts(estate, stmt->body);
-
-                       if (rc != PLPGSQL_RC_OK)
-                       {
-                               /*
-                                * We're aborting the loop, so cleanup and set FOUND.
-                                * (This code should match the code after the loop.)
-                                */
-                               SPI_freetuptable(tuptab);
-                               SPI_cursor_close(portal);
-                               exec_set_found(estate, found);
-
-                               if (rc == PLPGSQL_RC_EXIT)
-                               {
-                                       if (estate->exitlabel == NULL)
-                                               /* unlabelled exit, finish the current loop */
-                                               rc = PLPGSQL_RC_OK;
-                                       else if (stmt->label != NULL &&
-                                                        strcmp(stmt->label, estate->exitlabel) == 0)
-                                       {
-                                               /* labelled exit, matches the current stmt's label */
-                                               estate->exitlabel = NULL;
-                                               rc = PLPGSQL_RC_OK;
-                                       }
-
-                                       /*
-                                        * otherwise, we processed a labelled exit that does
-                                        * not match the current statement's label, if any:
-                                        * return RC_EXIT so that the EXIT continues to
-                                        * recurse upward.
-                                        */
-                               }
-
-                               return rc;
-                       }
-               }
-
-               SPI_freetuptable(tuptab);
+       int                     rc;
 
-               /*
-                * Fetch the next 50 tuples
-                */
-               SPI_cursor_fetch(portal, true, 50);
-               n = SPI_processed;
-               tuptab = SPI_tuptable;
-       }
+       portal = exec_dynquery_with_params(estate, stmt->query, stmt->params);
 
        /*
-        * Release last group of tuples
+        * Execute the loop
         */
-       SPI_freetuptable(tuptab);
+       rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, true);
 
        /*
         * Close the implicit cursor
         */
        SPI_cursor_close(portal);
 
-       /*
-        * Set the FOUND variable to indicate the result of executing the loop
-        * (namely, whether we looped one or more times). This must be set
-        * here so that it does not interfere with the value of the FOUND
-        * variable inside the loop processing itself.
-        */
-       exec_set_found(estate, found);
-
-       return PLPGSQL_RC_OK;
+       return rc;
 }
 
 
@@ -2508,16 +3099,14 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
 static int
 exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
 {
-       PLpgSQL_var *curvar = NULL;
+       PLpgSQL_var *curvar;
        char       *curname = NULL;
-       PLpgSQL_expr *query = NULL;
+       PLpgSQL_expr *query;
        Portal          portal;
-       int                     i;
        Datum      *values;
        char       *nulls;
        bool            isnull;
 
-
        /* ----------
         * Get the cursor variable and if it has an assigned name, check
         * that it's not in use currently.
@@ -2526,7 +3115,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
        curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
        if (!curvar->isnull)
        {
-               curname = DatumGetCString(DirectFunctionCall1(textout, curvar->value));
+               curname = TextDatumGetCString(curvar->value);
                if (SPI_cursor_find(curname) != NULL)
                        ereport(ERROR,
                                        (errcode(ERRCODE_DUPLICATE_CURSOR),
@@ -2548,7 +3137,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
                 */
                query = stmt->query;
                if (query->plan == NULL)
-                       exec_prepare_plan(estate, query);
+                       exec_prepare_plan(estate, query, stmt->cursor_options);
        }
        else if (stmt->dynquery != NULL)
        {
@@ -2559,7 +3148,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
                Datum           queryD;
                Oid                     restype;
                char       *querystr;
-               void       *curplan;
+               SPIPlanPtr      curplan;
 
                /* ----------
                 * We evaluate the string expression after the
@@ -2571,7 +3160,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
                if (isnull)
                        ereport(ERROR,
                                        (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                        errmsg("cannot EXECUTE a null querystring")));
+                                        errmsg("query string argument of EXECUTE is null")));
 
                /* Get the C-String representation */
                querystr = convert_value_to_string(queryD, restype);
@@ -2582,9 +3171,9 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
                 * Now we prepare a query plan for it and open a cursor
                 * ----------
                 */
-               curplan = SPI_prepare(querystr, 0, NULL);
+               curplan = SPI_prepare_cursor(querystr, 0, NULL, stmt->cursor_options);
                if (curplan == NULL)
-                       elog(ERROR, "SPI_prepare failed for \"%s\": %s",
+                       elog(ERROR, "SPI_prepare_cursor failed for \"%s\": %s",
                                 querystr, SPI_result_code_string(SPI_result));
                portal = SPI_cursor_open(curname, curplan, NULL, NULL,
                                                                 estate->readonly_func);
@@ -2594,14 +3183,11 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
                pfree(querystr);
                SPI_freeplan(curplan);
 
-               /* ----------
-                * Store the eventually assigned cursor name in the cursor variable
-                * ----------
+               /*
+                * If cursor variable was NULL, store the generated portal name in it
                 */
-               free_var(curvar);
-               curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name));
-               curvar->isnull = false;
-               curvar->freeval = true;
+               if (curname == NULL)
+                       assign_text_var(curvar, portal->name);
 
                return PLPGSQL_RC_OK;
        }
@@ -2617,26 +3203,28 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
                if (stmt->argquery != NULL)
                {
                        /* ----------
-                        * Er - OPEN CURSOR (args). We fake a SELECT ... INTO ...
+                        * OPEN CURSOR with args.  We fake a SELECT ... INTO ...
                         * statement to evaluate the args and put 'em into the
                         * internal row.
                         * ----------
                         */
-                       PLpgSQL_stmt_select set_args;
+                       PLpgSQL_stmt_execsql set_args;
 
                        if (curvar->cursor_explicit_argrow < 0)
                                ereport(ERROR,
                                                (errcode(ERRCODE_SYNTAX_ERROR),
-                               errmsg("arguments given for cursor without arguments")));
+                                       errmsg("arguments given for cursor without arguments")));
 
                        memset(&set_args, 0, sizeof(set_args));
-                       set_args.cmd_type = PLPGSQL_STMT_SELECT;
+                       set_args.cmd_type = PLPGSQL_STMT_EXECSQL;
                        set_args.lineno = stmt->lineno;
+                       set_args.sqlstmt = stmt->argquery;
+                       set_args.into = true;
+                       /* XXX historically this has not been STRICT */
                        set_args.row = (PLpgSQL_row *)
                                (estate->datums[curvar->cursor_explicit_argrow]);
-                       set_args.query = stmt->argquery;
 
-                       if (exec_stmt_select(estate, &set_args) != PLPGSQL_RC_OK)
+                       if (exec_stmt_execsql(estate, &set_args) != PLPGSQL_RC_OK)
                                elog(ERROR, "open cursor failed during argument processing");
                }
                else
@@ -2649,35 +3237,16 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
 
                query = curvar->cursor_explicit_expr;
                if (query->plan == NULL)
-                       exec_prepare_plan(estate, query);
+                       exec_prepare_plan(estate, query, curvar->cursor_options);
        }
 
-       /* ----------
-        * Here we go if we have a saved plan where we have to put
-        * values into, either from an explicit cursor or from a
-        * refcursor opened with OPEN ... FOR SELECT ...;
-        * ----------
+       /*
+        * Now build up the values and nulls arguments for SPI_execute_plan()
         */
-       values = (Datum *) palloc(query->nparams * sizeof(Datum));
-       nulls = (char *) palloc(query->nparams * sizeof(char));
-
-       for (i = 0; i < query->nparams; i++)
-       {
-               PLpgSQL_datum *datum = estate->datums[query->params[i]];
-               Oid                     paramtypeid;
-               bool            paramisnull;
-
-               exec_eval_datum(estate, datum, query->plan_argtypes[i],
-                                               &paramtypeid, &values[i], &paramisnull);
-               if (paramisnull)
-                       nulls[i] = 'n';
-               else
-                       nulls[i] = ' ';
-       }
+       eval_expr_params(estate, query, &values, &nulls);
 
-       /* ----------
+       /*
         * Open the cursor
-        * ----------
         */
        portal = SPI_cursor_open(curname, query->plan, values, nulls,
                                                         estate->readonly_func);
@@ -2685,26 +3254,24 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
                elog(ERROR, "could not open cursor: %s",
                         SPI_result_code_string(SPI_result));
 
+       /*
+        * If cursor variable was NULL, store the generated portal name in it
+        */
+       if (curname == NULL)
+               assign_text_var(curvar, portal->name);
+
        pfree(values);
        pfree(nulls);
        if (curname)
-               pfree(curname);
-
-       /* ----------
-        * Store the eventually assigned portal name in the cursor variable
-        * ----------
-        */
-       free_var(curvar);
-       curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name));
-       curvar->isnull = false;
-       curvar->freeval = true;
+               pfree(curname);
 
        return PLPGSQL_RC_OK;
 }
 
 
 /* ----------
- * exec_stmt_fetch                     Fetch from a cursor into a target
+ * exec_stmt_fetch                     Fetch from a cursor into a target, or just
+ *                                                     move the current position of the cursor
  * ----------
  */
 static int
@@ -2713,10 +3280,11 @@ 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;
        SPITupleTable *tuptab;
        Portal          portal;
        char       *curname;
-       int                     n;
+       uint32          n;
 
        /* ----------
         * Get the portal of the cursor by name
@@ -2726,8 +3294,8 @@ exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
        if (curvar->isnull)
                ereport(ERROR,
                                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                        errmsg("cursor variable \"%s\" is NULL", curvar->refname)));
-       curname = DatumGetCString(DirectFunctionCall1(textout, curvar->value));
+                                errmsg("cursor variable \"%s\" is null", curvar->refname)));
+       curname = TextDatumGetCString(curvar->value);
 
        portal = SPI_cursor_find(curname);
        if (portal == NULL)
@@ -2736,46 +3304,68 @@ exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
                                 errmsg("cursor \"%s\" does not exist", curname)));
        pfree(curname);
 
-       /* ----------
-        * Determine if we fetch into a record or a row
-        * ----------
-        */
-       if (stmt->rec != NULL)
-               rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
-       else if (stmt->row != NULL)
-               row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
-       else
-               elog(ERROR, "unsupported target");
+       /* Calculate position for FETCH_RELATIVE or FETCH_ABSOLUTE */
+       if (stmt->expr)
+       {
+               bool            isnull;
 
-       /* ----------
-        * Fetch 1 tuple from the cursor
-        * ----------
-        */
-       SPI_cursor_fetch(portal, true, 1);
-       tuptab = SPI_tuptable;
-       n = SPI_processed;
+               /* XXX should be doing this in LONG not INT width */
+               how_many = exec_eval_integer(estate, stmt->expr, &isnull);
 
-       /* ----------
-        * Set the target and the global FOUND variable appropriately.
-        * ----------
-        */
-       if (n == 0)
+               if (isnull)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                        errmsg("relative or absolute cursor position is null")));
+
+               exec_eval_cleanup(estate);
+       }
+
+       if (!stmt->is_move)
        {
-               exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
-               exec_set_found(estate, false);
+               /* ----------
+                * Determine if we fetch into a record or a row
+                * ----------
+                */
+               if (stmt->rec != NULL)
+                       rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->dno]);
+               else if (stmt->row != NULL)
+                       row = (PLpgSQL_row *) (estate->datums[stmt->row->dno]);
+               else
+                       elog(ERROR, "unsupported target");
+
+               /* ----------
+                * Fetch 1 tuple from the cursor
+                * ----------
+                */
+               SPI_scroll_cursor_fetch(portal, stmt->direction, how_many);
+               tuptab = SPI_tuptable;
+               n = SPI_processed;
+
+               /* ----------
+                * Set the target appropriately.
+                * ----------
+                */
+               if (n == 0)
+                       exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
+               else
+                       exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
+
+               SPI_freetuptable(tuptab);
        }
        else
        {
-               exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
-               exec_set_found(estate, true);
+               /* Move the cursor */
+               SPI_scroll_cursor_move(portal, stmt->direction, how_many);
+               n = SPI_processed;
        }
 
-       SPI_freetuptable(tuptab);
+       /* Set the ROW_COUNT and the global FOUND variable appropriately. */
+       estate->eval_processed = n;
+       exec_set_found(estate, n != 0);
 
        return PLPGSQL_RC_OK;
 }
 
-
 /* ----------
  * exec_stmt_close                     Close a cursor
  * ----------
@@ -2795,8 +3385,8 @@ exec_stmt_close(PLpgSQL_execstate *estate, PLpgSQL_stmt_close *stmt)
        if (curvar->isnull)
                ereport(ERROR,
                                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                        errmsg("cursor variable \"%s\" is NULL", curvar->refname)));
-       curname = DatumGetCString(DirectFunctionCall1(textout, curvar->value));
+                                errmsg("cursor variable \"%s\" is null", curvar->refname)));
+       curname = TextDatumGetCString(curvar->value);
 
        portal = SPI_cursor_find(curname);
        if (portal == NULL)
@@ -2857,20 +3447,20 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                                                                   &(var->datatype->typinput),
                                                                                   var->datatype->typioparam,
                                                                                   var->datatype->atttypmod,
-                                                                                  isNull);
+                                                                                  *isNull);
 
                                if (*isNull && var->notnull)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                                        errmsg("NULL cannot be assigned to variable \"%s\" declared NOT NULL",
+                                                        errmsg("null value cannot be assigned to variable \"%s\" declared NOT NULL",
                                                                        var->refname)));
 
                                /*
                                 * If type is by-reference, make sure we have a freshly
-                                * palloc'd copy; the originally passed value may not live
-                                * as long as the variable!  But we don't need to re-copy
-                                * if exec_cast_value performed a conversion; its output
-                                * must already be palloc'd.
+                                * palloc'd copy; the originally passed value may not live as
+                                * long as the variable!  But we don't need to re-copy if
+                                * exec_cast_value performed a conversion; its output must
+                                * already be palloc'd.
                                 */
                                if (!var->datatype->typbyval && !*isNull)
                                {
@@ -2881,10 +3471,10 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                }
 
                                /*
-                                * Now free the old value.  (We can't do this any earlier
-                                * because of the possibility that we are assigning the
-                                * var's old value to it, eg "foo := foo".  We could optimize
-                                * out the assignment altogether in such cases, but it's too
+                                * Now free the old value.      (We can't do this any earlier
+                                * because of the possibility that we are assigning the var's
+                                * old value to it, eg "foo := foo".  We could optimize out
+                                * the assignment altogether in such cases, but it's too
                                 * infrequent to be worth testing for.)
                                 */
                                free_var(var);
@@ -2904,8 +3494,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                PLpgSQL_row *row = (PLpgSQL_row *) target;
 
                                /* Source must be of RECORD or composite type */
-                               if (!(valtype == RECORDOID ||
-                                         get_typtype(valtype) == 'c'))
+                               if (!type_is_rowtype(valtype))
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_DATATYPE_MISMATCH),
                                                         errmsg("cannot assign non-composite value to a row variable")));
@@ -2934,6 +3523,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                        tmptup.t_tableOid = InvalidOid;
                                        tmptup.t_data = td;
                                        exec_move_row(estate, NULL, row, &tmptup, tupdesc);
+                                       ReleaseTupleDesc(tupdesc);
                                }
                                break;
                        }
@@ -2946,8 +3536,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
 
                                /* Source must be of RECORD or composite type */
-                               if (!(valtype == RECORDOID ||
-                                         get_typtype(valtype) == 'c'))
+                               if (!type_is_rowtype(valtype))
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_DATATYPE_MISMATCH),
                                                         errmsg("cannot assign non-composite value to a record variable")));
@@ -2976,6 +3565,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                        tmptup.t_tableOid = InvalidOid;
                                        tmptup.t_data = td;
                                        exec_move_row(estate, rec, NULL, &tmptup, tupdesc);
+                                       ReleaseTupleDesc(tupdesc);
                                }
                                break;
                        }
@@ -2990,9 +3580,9 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                int                     fno;
                                HeapTuple       newtup;
                                int                     natts;
-                               int                     i;
                                Datum      *values;
-                               char       *nulls;
+                               bool       *nulls;
+                               bool       *replaces;
                                void       *mustfree;
                                bool            attisnull;
                                Oid                     atttype;
@@ -3001,23 +3591,24 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
 
                                /*
-                                * Check that there is already a tuple in the record. We
-                                * need that because records don't have any predefined
-                                * field structure.
+                                * Check that there is already a tuple in the record. We need
+                                * that because records don't have any predefined field
+                                * structure.
                                 */
                                if (!HeapTupleIsValid(rec->tup))
                                        ereport(ERROR,
-                                         (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                          errmsg("record \"%s\" is not assigned yet",
-                                                         rec->refname),
-                                          errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
+                                                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                                  errmsg("record \"%s\" is not assigned yet",
+                                                                 rec->refname),
+                                                  errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
 
                                /*
                                 * Get the number of the records field to change and the
-                                * number of attributes in the tuple.
+                                * number of attributes in the tuple.  Note: disallow
+                                * system column names because the code below won't cope.
                                 */
                                fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
-                               if (fno == SPI_ERROR_NOATTRIBUTE)
+                               if (fno <= 0)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_UNDEFINED_COLUMN),
                                                         errmsg("record \"%s\" has no field \"%s\"",
@@ -3026,28 +3617,20 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                natts = rec->tupdesc->natts;
 
                                /*
-                                * Set up values/datums arrays for heap_formtuple.      For
-                                * all the attributes except the one we want to replace,
-                                * use the value that's in the old tuple.
+                                * Set up values/control arrays for heap_modify_tuple. For all
+                                * 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(natts);
+                               nulls = palloc(sizeof(bool) * natts);
+                               replaces = palloc(sizeof(bool) * natts);
 
-                               for (i = 0; i < natts; i++)
-                               {
-                                       if (i == fno)
-                                               continue;
-                                       values[i] = SPI_getbinval(rec->tup, rec->tupdesc,
-                                                                                         i + 1, &attisnull);
-                                       if (attisnull)
-                                               nulls[i] = 'n';
-                                       else
-                                               nulls[i] = ' ';
-                               }
+                               memset(replaces, false, sizeof(bool) * natts);
+                               replaces[fno] = true;
 
                                /*
-                                * Now insert the new value, being careful to cast it to
-                                * the right type.
+                                * Now insert the new value, being careful to cast it to the
+                                * right type.
                                 */
                                atttype = SPI_gettypeid(rec->tupdesc, fno + 1);
                                atttypmod = rec->tupdesc->attrs[fno]->atttypmod;
@@ -3056,15 +3639,12 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                                                                                         valtype,
                                                                                                         atttype,
                                                                                                         atttypmod,
-                                                                                                        &attisnull);
-                               if (attisnull)
-                                       nulls[fno] = 'n';
-                               else
-                                       nulls[fno] = ' ';
+                                                                                                        attisnull);
+                               nulls[fno] = attisnull;
 
                                /*
-                                * Avoid leaking the result of exec_simple_cast_value, if
-                                * it performed a conversion to a pass-by-ref type.
+                                * Avoid leaking the result of exec_simple_cast_value, if it
+                                * performed a conversion to a pass-by-ref type.
                                 */
                                if (!attisnull && values[fno] != value && !get_typbyval(atttype))
                                        mustfree = DatumGetPointer(values[fno]);
@@ -3072,10 +3652,11 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                        mustfree = NULL;
 
                                /*
-                                * Now call heap_formtuple() to create a new tuple that
+                                * Now call heap_modify_tuple() to create a new tuple that
                                 * replaces the old one in the record.
                                 */
-                               newtup = heap_formtuple(rec->tupdesc, values, nulls);
+                               newtup = heap_modify_tuple(rec->tup, rec->tupdesc,
+                                                                                  values, nulls, replaces);
 
                                if (rec->freetup)
                                        heap_freetuple(rec->tup);
@@ -3085,6 +3666,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
 
                                pfree(values);
                                pfree(nulls);
+                               pfree(replaces);
                                if (mustfree)
                                        pfree(mustfree);
 
@@ -3097,8 +3679,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                int                     i;
                                PLpgSQL_expr *subscripts[MAXDIM];
                                int                     subscriptvals[MAXDIM];
-                               bool            havenullsubscript,
-                                                       oldarrayisnull;
+                               bool            oldarrayisnull;
                                Oid                     arraytypeid,
                                                        arrayelemtypeid;
                                int16           arraytyplen,
@@ -3114,11 +3695,11 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                 * Target is an element of an array
                                 *
                                 * To handle constructs like x[1][2] := something, we have to
-                                * be prepared to deal with a chain of arrayelem datums.
-                                * Chase back to find the base array datum, and save the
-                                * subscript expressions as we go.      (We are scanning right
-                                * to left here, but want to evaluate the subscripts
-                                * left-to-right to minimize surprises.)
+                                * be prepared to deal with a chain of arrayelem datums. Chase
+                                * back to find the base array datum, and save the subscript
+                                * expressions as we go.  (We are scanning right to left here,
+                                * but want to evaluate the subscripts left-to-right to
+                                * minimize surprises.)
                                 */
                                nsubscripts = 0;
                                do
@@ -3128,21 +3709,21 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                        if (nsubscripts >= MAXDIM)
                                                ereport(ERROR,
                                                                (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-                                                                errmsg("number of array dimensions exceeds the maximum allowed, %d",
-                                                                               MAXDIM)));
+                                                                errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+                                                                               nsubscripts, MAXDIM)));
                                        subscripts[nsubscripts++] = arrayelem->subscript;
                                        target = estate->datums[arrayelem->arrayparentno];
                                } while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
 
                                /* Fetch current value of array datum */
                                exec_eval_datum(estate, target, InvalidOid,
-                                                               &arraytypeid, &oldarraydatum, &oldarrayisnull);
+                                                         &arraytypeid, &oldarraydatum, &oldarrayisnull);
 
                                arrayelemtypeid = get_element_type(arraytypeid);
                                if (!OidIsValid(arrayelemtypeid))
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                 errmsg("subscripted object is not an array")));
+                                                        errmsg("subscripted object is not an array")));
 
                                get_typlenbyvalalign(arrayelemtypeid,
                                                                         &elemtyplen,
@@ -3151,10 +3732,9 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                arraytyplen = get_typlen(arraytypeid);
 
                                /*
-                                * Evaluate the subscripts, switch into left-to-right
-                                * order
+                                * Evaluate the subscripts, switch into left-to-right order.
+                                * Like ExecEvalArrayRef(), complain if any subscript is null.
                                 */
-                               havenullsubscript = false;
                                for (i = 0; i < nsubscripts; i++)
                                {
                                        bool            subisnull;
@@ -3163,47 +3743,38 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                                exec_eval_integer(estate,
                                                                                  subscripts[nsubscripts - 1 - i],
                                                                                  &subisnull);
-                                       havenullsubscript |= subisnull;
+                                       if (subisnull)
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                                                errmsg("array subscript in assignment must not be null")));
                                }
 
+                               /* Coerce source value to match array element type. */
+                               coerced_value = exec_simple_cast_value(value,
+                                                                                                          valtype,
+                                                                                                          arrayelemtypeid,
+                                                                                                          -1,
+                                                                                                          *isNull);
+
                                /*
-                                * Skip the assignment if we have any nulls in the subscripts
-                                * or the righthand side. This is pretty bogus but it
+                                * If the original array is null, cons up an empty array so
+                                * that the assignment can proceed; we'll end with a
+                                * one-element array containing just the assigned-to
+                                * subscript.  This only works for varlena arrays, though; for
+                                * fixed-length array types we skip the assignment.  We can't
+                                * support assignment of a null entry into a fixed-length
+                                * array, either, so that's a no-op too.  This is all ugly but
                                 * corresponds to the current behavior of ExecEvalArrayRef().
                                 */
-                               if (havenullsubscript || *isNull)
+                               if (arraytyplen > 0 &&  /* fixed-length array? */
+                                       (oldarrayisnull || *isNull))
                                        return;
 
-                               /*
-                                * If the original array is null, cons up an empty array
-                                * so that the assignment can proceed; we'll end with a
-                                * one-element array containing just the assigned-to
-                                * subscript.  This only works for varlena arrays, though;
-                                * for fixed-length array types we skip the assignment.
-                                * Again, this corresponds to the current behavior of
-                                * ExecEvalArrayRef().
-                                */
                                if (oldarrayisnull)
-                               {
-                                       if (arraytyplen > 0)            /* fixed-length array? */
-                                               return;
-
-                                       oldarrayval = construct_md_array(NULL, 0, NULL, NULL,
-                                                                                                        arrayelemtypeid,
-                                                                                                        elemtyplen,
-                                                                                                        elemtypbyval,
-                                                                                                        elemtypalign);
-                               }
+                                       oldarrayval = construct_empty_array(arrayelemtypeid);
                                else
                                        oldarrayval = (ArrayType *) DatumGetPointer(oldarraydatum);
 
-                               /* Coerce source value to match array element type. */
-                               coerced_value = exec_simple_cast_value(value,
-                                                                                                          valtype,
-                                                                                                          arrayelemtypeid,
-                                                                                                          -1,
-                                                                                                          isNull);
-
                                /*
                                 * Build the modified array value.
                                 */
@@ -3211,25 +3782,27 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                                                                nsubscripts,
                                                                                subscriptvals,
                                                                                coerced_value,
+                                                                               *isNull,
                                                                                arraytyplen,
                                                                                elemtyplen,
                                                                                elemtypbyval,
-                                                                               elemtypalign,
-                                                                               isNull);
+                                                                               elemtypalign);
 
                                /*
-                                * Assign it to the base variable.
+                                * Avoid leaking the result of exec_simple_cast_value, if it
+                                * performed a conversion to a pass-by-ref type.
                                 */
-                               exec_assign_value(estate, target,
-                                                                 PointerGetDatum(newarrayval),
-                                                                 arraytypeid, isNull);
+                               if (!*isNull && coerced_value != value && !elemtypbyval)
+                                       pfree(DatumGetPointer(coerced_value));
 
                                /*
-                                * Avoid leaking the result of exec_simple_cast_value, if
-                                * it performed a conversion to a pass-by-ref type.
+                                * Assign the new array to the base variable.  It's never NULL
+                                * at this point.
                                 */
-                               if (!*isNull && coerced_value != value && !elemtypbyval)
-                                       pfree(DatumGetPointer(coerced_value));
+                               *isNull = false;
+                               exec_assign_value(estate, target,
+                                                                 PointerGetDatum(newarrayval),
+                                                                 arraytypeid, isNull);
 
                                /*
                                 * Avoid leaking the modified array value, too.
@@ -3250,11 +3823,12 @@ exec_assign_value(PLpgSQL_execstate *estate,
  *
  * If expectedtypeid isn't InvalidOid, it is checked against the actual type.
  *
- * This obviously only handles scalar datums (not whole records or rows);
- * at present it doesn't need to handle PLpgSQL_expr datums, either.
+ * At present this doesn't handle PLpgSQL_expr or PLpgSQL_arrayelem datums.
  *
  * NOTE: caller must not modify the returned value, since it points right
- * at the stored value in the case of pass-by-reference datatypes.
+ * at the stored value in the case of pass-by-reference datatypes.     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,
@@ -3264,6 +3838,8 @@ exec_eval_datum(PLpgSQL_execstate *estate,
                                Datum *value,
                                bool *isnull)
 {
+       MemoryContext oldcontext;
+
        switch (datum->dtype)
        {
                case PLPGSQL_DTYPE_VAR:
@@ -3290,9 +3866,11 @@ exec_eval_datum(PLpgSQL_execstate *estate,
                                        elog(ERROR, "row variable has no tupdesc");
                                /* Make sure we have a valid type/typmod setting */
                                BlessTupleDesc(row->rowtupdesc);
+                               oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
                                tup = make_tuple_from_row(estate, row, row->rowtupdesc);
                                if (tup == NULL)        /* should not happen */
                                        elog(ERROR, "row not compatible with its own tupdesc");
+                               MemoryContextSwitchTo(oldcontext);
                                *typeid = row->rowtupdesc->tdtypeid;
                                *value = HeapTupleGetDatum(tup);
                                *isnull = false;
@@ -3311,24 +3889,25 @@ exec_eval_datum(PLpgSQL_execstate *estate,
 
                                if (!HeapTupleIsValid(rec->tup))
                                        ereport(ERROR,
-                                         (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                          errmsg("record \"%s\" is not assigned yet",
-                                                         rec->refname),
-                                          errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
+                                                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                                  errmsg("record \"%s\" is not assigned yet",
+                                                                 rec->refname),
+                                                  errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
                                Assert(rec->tupdesc != NULL);
                                /* Make sure we have a valid type/typmod setting */
                                BlessTupleDesc(rec->tupdesc);
 
                                /*
-                                * In a trigger, the NEW and OLD parameters are likely to
-                                * be on-disk tuples that don't have the desired Datum
-                                * fields. Copy the tuple body and insert the right
-                                * values.
+                                * In a trigger, the NEW and OLD parameters are likely to be
+                                * on-disk tuples that don't have the desired Datum fields.
+                                * Copy the tuple body and insert the right values.
                                 */
+                               oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
                                heap_copytuple_with_tuple(rec->tup, &worktup);
                                HeapTupleHeaderSetDatumLength(worktup.t_data, worktup.t_len);
                                HeapTupleHeaderSetTypeId(worktup.t_data, rec->tupdesc->tdtypeid);
                                HeapTupleHeaderSetTypMod(worktup.t_data, rec->tupdesc->tdtypmod);
+                               MemoryContextSwitchTo(oldcontext);
                                *typeid = rec->tupdesc->tdtypeid;
                                *value = HeapTupleGetDatum(&worktup);
                                *isnull = false;
@@ -3349,10 +3928,10 @@ exec_eval_datum(PLpgSQL_execstate *estate,
                                rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
                                if (!HeapTupleIsValid(rec->tup))
                                        ereport(ERROR,
-                                         (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                          errmsg("record \"%s\" is not assigned yet",
-                                                         rec->refname),
-                                          errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
+                                                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                                  errmsg("record \"%s\" is not assigned yet",
+                                                                 rec->refname),
+                                                  errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
                                fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
                                if (fno == SPI_ERROR_NOATTRIBUTE)
                                        ereport(ERROR,
@@ -3420,7 +3999,7 @@ exec_eval_integer(PLpgSQL_execstate *estate,
        exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid);
        exprdatum = exec_simple_cast_value(exprdatum, exprtypeid,
                                                                           INT4OID, -1,
-                                                                          isNull);
+                                                                          *isNull);
        return DatumGetInt32(exprdatum);
 }
 
@@ -3442,7 +4021,7 @@ exec_eval_boolean(PLpgSQL_execstate *estate,
        exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid);
        exprdatum = exec_simple_cast_value(exprdatum, exprtypeid,
                                                                           BOOLOID, -1,
-                                                                          isNull);
+                                                                          *isNull);
        return DatumGetBool(exprdatum);
 }
 
@@ -3459,26 +4038,30 @@ exec_eval_expr(PLpgSQL_execstate *estate,
                           bool *isNull,
                           Oid *rettype)
 {
+       Datum           result = 0;
        int                     rc;
 
        /*
-        * If not already done create a plan for this expression
+        * If first time through, create a plan for this expression.
         */
        if (expr->plan == NULL)
-               exec_prepare_plan(estate, expr);
+               exec_prepare_plan(estate, expr, 0);
 
        /*
         * If this is a simple expression, bypass SPI and use the executor
         * directly
         */
-       if (expr->expr_simple_expr != NULL)
-               return exec_eval_simple_expr(estate, expr, isNull, rettype);
+       if (exec_eval_simple_expr(estate, expr, &result, isNull, rettype))
+               return result;
 
+       /*
+        * Else do it the hard way via exec_run_select
+        */
        rc = exec_run_select(estate, expr, 2, NULL);
        if (rc != SPI_OK_SELECT)
                ereport(ERROR,
                                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                          errmsg("query \"%s\" did not return data", expr->query)));
+                                errmsg("query \"%s\" did not return data", expr->query)));
 
        /*
         * If there are no rows selected, the result is NULL.
@@ -3500,8 +4083,11 @@ exec_eval_expr(PLpgSQL_execstate *estate,
        if (estate->eval_tuptable->tupdesc->natts != 1)
                ereport(ERROR,
                                (errcode(ERRCODE_SYNTAX_ERROR),
-                                errmsg("query \"%s\" returned %d columns", expr->query,
-                                               estate->eval_tuptable->tupdesc->natts)));
+                                errmsg_plural("query \"%s\" returned %d column",
+                                                          "query \"%s\" returned %d columns",
+                                                          estate->eval_tuptable->tupdesc->natts,
+                                                          expr->query,
+                                                          estate->eval_tuptable->tupdesc->natts)));
 
        /*
         * Return the result and its type
@@ -3520,7 +4106,6 @@ static int
 exec_run_select(PLpgSQL_execstate *estate,
                                PLpgSQL_expr *expr, long maxtuples, Portal *portalP)
 {
-       int                     i;
        Datum      *values;
        char       *nulls;
        int                     rc;
@@ -3529,27 +4114,12 @@ exec_run_select(PLpgSQL_execstate *estate,
         * On the first call for this expression generate the plan
         */
        if (expr->plan == NULL)
-               exec_prepare_plan(estate, expr);
+               exec_prepare_plan(estate, expr, 0);
 
        /*
         * Now build up the values and nulls arguments for SPI_execute_plan()
         */
-       values = (Datum *) palloc(expr->nparams * sizeof(Datum));
-       nulls = (char *) palloc(expr->nparams * sizeof(char));
-
-       for (i = 0; i < expr->nparams; i++)
-       {
-               PLpgSQL_datum *datum = estate->datums[expr->params[i]];
-               Oid                     paramtypeid;
-               bool            paramisnull;
-
-               exec_eval_datum(estate, datum, expr->plan_argtypes[i],
-                                               &paramtypeid, &values[i], &paramisnull);
-               if (paramisnull)
-                       nulls[i] = 'n';
-               else
-                       nulls[i] = ' ';
-       }
+       eval_expr_params(estate, expr, &values, &nulls);
 
        /*
         * If a portal was requested, put the query into the portal
@@ -3589,97 +4159,263 @@ exec_run_select(PLpgSQL_execstate *estate,
 }
 
 
+/*
+ * exec_for_query --- execute body of FOR loop for each row from a portal
+ *
+ * Used by exec_stmt_fors, exec_stmt_forc and exec_stmt_dynfors
+ */
+static int
+exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
+                          Portal portal, bool prefetch_ok)
+{
+       PLpgSQL_rec *rec = NULL;
+       PLpgSQL_row *row = NULL;
+       SPITupleTable *tuptab;
+       bool            found = false;
+       int                     rc = PLPGSQL_RC_OK;
+       int                     n;
+
+       /*
+        * Determine if we assign to a record or a row
+        */
+       if (stmt->rec != NULL)
+               rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->dno]);
+       else if (stmt->row != NULL)
+               row = (PLpgSQL_row *) (estate->datums[stmt->row->dno]);
+       else
+               elog(ERROR, "unsupported target");
+
+       /*
+        * Fetch the initial tuple(s).  If prefetching is allowed then we grab
+        * a few more rows to avoid multiple trips through executor startup
+        * overhead.
+        */
+       SPI_cursor_fetch(portal, true, prefetch_ok ? 10 : 1);
+       tuptab = SPI_tuptable;
+       n = SPI_processed;
+
+       /*
+        * If the query didn't return any rows, set the target to NULL and
+        * fall through with found = false.
+        */
+       if (n <= 0)
+               exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
+       else
+               found = true;                   /* processed at least one tuple */
+
+       /*
+        * Now do the loop
+        */
+       while (n > 0)
+       {
+               int                     i;
+
+               for (i = 0; i < n; i++)
+               {
+                       /*
+                        * Assign the tuple to the target
+                        */
+                       exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
+
+                       /*
+                        * Execute the statements
+                        */
+                       rc = exec_stmts(estate, stmt->body);
+
+                       if (rc != PLPGSQL_RC_OK)
+                       {
+                               if (rc == PLPGSQL_RC_EXIT)
+                               {
+                                       if (estate->exitlabel == NULL)
+                                       {
+                                               /* unlabelled exit, so exit the current loop */
+                                               rc = PLPGSQL_RC_OK;
+                                       }
+                                       else if (stmt->label != NULL &&
+                                                        strcmp(stmt->label, estate->exitlabel) == 0)
+                                       {
+                                               /* label matches this loop, so exit loop */
+                                               estate->exitlabel = NULL;
+                                               rc = PLPGSQL_RC_OK;
+                                       }
+
+                                       /*
+                                        * otherwise, we processed a labelled exit that does not
+                                        * match the current statement's label, if any; return
+                                        * RC_EXIT so that the EXIT continues to recurse upward.
+                                        */
+                               }
+                               else if (rc == PLPGSQL_RC_CONTINUE)
+                               {
+                                       if (estate->exitlabel == NULL)
+                                       {
+                                               /* unlabelled continue, so re-run the current loop */
+                                               rc = PLPGSQL_RC_OK;
+                                               continue;
+                                       }
+                                       else if (stmt->label != NULL &&
+                                                        strcmp(stmt->label, estate->exitlabel) == 0)
+                                       {
+                                               /* label matches this loop, so re-run loop */
+                                               estate->exitlabel = NULL;
+                                               rc = PLPGSQL_RC_OK;
+                                               continue;
+                                       }
+
+                                       /*
+                                        * otherwise, we process a labelled continue that does not
+                                        * match the current statement's label, if any; return
+                                        * RC_CONTINUE so that the CONTINUE will propagate up the
+                                        * stack.
+                                        */
+                               }
+
+                               /*
+                                * We're aborting the loop.  Need a goto to get out of two
+                                * levels of loop...
+                                */
+                               goto loop_exit;
+                       }
+               }
+
+               SPI_freetuptable(tuptab);
+
+               /*
+                * Fetch more tuples.  If prefetching is allowed, grab 50 at a time.
+                */
+               SPI_cursor_fetch(portal, true, prefetch_ok ? 50 : 1);
+               tuptab = SPI_tuptable;
+               n = SPI_processed;
+       }
+
+loop_exit:
+
+       /*
+        * Release last group of tuples (if any)
+        */
+       SPI_freetuptable(tuptab);
+
+       /*
+        * Set the FOUND variable to indicate the result of executing the loop
+        * (namely, whether we looped one or more times). This must be set last so
+        * that it does not interfere with the value of the FOUND variable inside
+        * the loop processing itself.
+        */
+       exec_set_found(estate, found);
+
+       return rc;
+}
+
+
 /* ----------
  * exec_eval_simple_expr -             Evaluate a simple expression returning
  *                                                             a Datum by directly calling ExecEvalExpr().
  *
+ * If successful, store results into *result, *isNull, *rettype and return
+ * TRUE.  If the expression is not simple (any more), return FALSE.
+ *
+ * It is possible though unlikely for a simple expression to become non-simple
+ * (consider for example redefining a trivial view).  We must handle that for
+ * correctness; fortunately it's normally inexpensive to do
+ * RevalidateCachedPlan on a simple expression.  We do not consider the other
+ * direction (non-simple expression becoming simple) because we'll still 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 Datum
+static bool
 exec_eval_simple_expr(PLpgSQL_execstate *estate,
                                          PLpgSQL_expr *expr,
+                                         Datum *result,
                                          bool *isNull,
                                          Oid *rettype)
 {
-       Datum           retval;
-       ExprContext * volatile econtext;
+       ExprContext *econtext = estate->eval_econtext;
+       LocalTransactionId curlxid = MyProc->lxid;
+       CachedPlanSource *plansource;
+       CachedPlan *cplan;
        ParamListInfo paramLI;
        int                     i;
-       Snapshot        saveActiveSnapshot;
+       MemoryContext oldcontext;
 
        /*
-        * Pass back previously-determined result type.
+        * Forget it if expression wasn't simple before.
         */
-       *rettype = expr->expr_simple_type;
+       if (expr->expr_simple_expr == NULL)
+               return false;
 
        /*
-        * Create an EState for evaluation of simple expressions, if there's
-        * not one already in the current transaction.  The EState is made a
-        * child of TopTransactionContext so it will have the right lifespan.
+        * Revalidate cached plan, so that we will notice if it became stale. (We
+        * also need to hold a refcount while using the plan.)  Note that even if
+        * replanning occurs, the length of plancache_list can't change, since it
+        * is a property of the raw parsetree generated from the query text.
         */
-       if (simple_eval_estate == NULL)
+       Assert(list_length(expr->plan->plancache_list) == 1);
+       plansource = (CachedPlanSource *) linitial(expr->plan->plancache_list);
+       cplan = RevalidateCachedPlan(plansource, true);
+       if (cplan->generation != expr->expr_simple_generation)
        {
-               MemoryContext oldcontext;
-
-               oldcontext = MemoryContextSwitchTo(TopTransactionContext);
-               simple_eval_estate = CreateExecutorState();
-               MemoryContextSwitchTo(oldcontext);
+               /* It got replanned ... is it still simple? */
+               exec_simple_check_plan(expr);
+               if (expr->expr_simple_expr == NULL)
+               {
+                       /* Ooops, release refcount and fail */
+                       ReleaseCachedPlan(cplan, true);
+                       return false;
+               }
        }
 
        /*
-        * Prepare the expression for execution, if it's not been done already
-        * in the current transaction.
+        * Pass back previously-determined result type.
         */
-       if (expr->expr_simple_state == NULL)
-       {
-               expr->expr_simple_state = ExecPrepareExpr(expr->expr_simple_expr,
-                                                                                                 simple_eval_estate);
-               /* Add it to list for cleanup */
-               expr->expr_simple_next = active_simple_exprs;
-               active_simple_exprs = expr;
-       }
+       *rettype = expr->expr_simple_type;
 
        /*
-        * Create an expression context for simple expressions, if there's not
-        * one already in the current function call.  This must be a child of
-        * simple_eval_estate.
+        * Prepare the expression for execution, if it's not been done already in
+        * the current transaction.  (This will be forced to happen if we called
+        * exec_simple_check_plan above.)
         */
-       econtext = estate->eval_econtext;
-       if (econtext == NULL)
+       if (expr->expr_simple_lxid != curlxid)
        {
-               econtext = CreateExprContext(simple_eval_estate);
-               estate->eval_econtext = econtext;
+               expr->expr_simple_state = ExecPrepareExpr(expr->expr_simple_expr,
+                                                                                                 simple_eval_estate);
+               expr->expr_simple_lxid = curlxid;
        }
 
        /*
         * Param list can live in econtext's temporary memory context.
         *
         * XXX think about avoiding repeated palloc's for param lists? Beware
-        * however that this routine is re-entrant: exec_eval_datum() can call
-        * it back for subscript evaluation, and so there can be a need to
-        * have more than one active param list.
+        * however that this routine is re-entrant: exec_eval_datum() can call it
+        * back for subscript evaluation, and so there can be a need to have more
+        * than one active param list.
         */
-       paramLI = (ParamListInfo)
-               MemoryContextAlloc(econtext->ecxt_per_tuple_memory,
-                                               (expr->nparams + 1) * sizeof(ParamListInfoData));
-
-       /*
-        * Put the parameter values into the parameter list entries.
-        */
-       for (i = 0; i < expr->nparams; i++)
+       if (expr->nparams > 0)
        {
-               PLpgSQL_datum *datum = estate->datums[expr->params[i]];
+               /* sizeof(ParamListInfoData) includes the first array element */
+               paramLI = (ParamListInfo)
+                       MemoryContextAlloc(econtext->ecxt_per_tuple_memory,
+                                                          sizeof(ParamListInfoData) +
+                                                          (expr->nparams - 1) *sizeof(ParamExternData));
+               paramLI->numParams = expr->nparams;
+
+               for (i = 0; i < expr->nparams; i++)
+               {
+                       ParamExternData *prm = &paramLI->params[i];
+                       PLpgSQL_datum *datum = estate->datums[expr->params[i]];
 
-               paramLI[i].kind = PARAM_NUM;
-               paramLI[i].id = i + 1;
-               exec_eval_datum(estate, datum, expr->plan_argtypes[i],
-                                               &paramLI[i].ptype,
-                                               &paramLI[i].value, &paramLI[i].isnull);
+                       prm->pflags = 0;
+                       exec_eval_datum(estate, datum, expr->plan_argtypes[i],
+                                                       &prm->ptype,
+                                                       &prm->value, &prm->isnull);
+               }
        }
-       paramLI[i].kind = PARAM_INVALID;
+       else
+               paramLI = NULL;
 
        /*
         * Now we can safely make the econtext point to the param list.
@@ -3687,49 +4423,73 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
        econtext->ecxt_param_list_info = paramLI;
 
        /*
-        * We have to do some of the things SPI_execute_plan would do,
-        * in particular advance the snapshot if we are in a non-read-only
-        * function.  Without this, stable functions within the expression
-        * would fail to see updates made so far by our own function.
+        * We have to do some of the things SPI_execute_plan would do, in
+        * particular advance the snapshot if we are in a non-read-only function.
+        * Without this, stable functions within the expression would fail to see
+        * updates made so far by our own function.
         */
-       SPI_push();
-       saveActiveSnapshot = ActiveSnapshot;
-
-       PG_TRY();
-       {
-               MemoryContext oldcontext;
-
-               oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
-               if (!estate->readonly_func)
-               {
-                       CommandCounterIncrement();
-                       ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
-               }
+       SPI_push();
 
-               /*
-                * Finally we can call the executor to evaluate the expression
-                */
-               retval = ExecEvalExpr(expr->expr_simple_state,
-                                                         econtext,
-                                                         isNull,
-                                                         NULL);
-               MemoryContextSwitchTo(oldcontext);
-       }
-       PG_CATCH();
+       oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+       if (!estate->readonly_func)
        {
-               /* Restore global vars and propagate error */
-               ActiveSnapshot = saveActiveSnapshot;
-               PG_RE_THROW();
+               CommandCounterIncrement();
+               PushActiveSnapshot(GetTransactionSnapshot());
        }
-       PG_END_TRY();
 
-       ActiveSnapshot = saveActiveSnapshot;
+       /*
+        * Finally we can call the executor to evaluate the expression
+        */
+       *result = ExecEvalExpr(expr->expr_simple_state,
+                                                  econtext,
+                                                  isNull,
+                                                  NULL);
+       MemoryContextSwitchTo(oldcontext);
+
+       if (!estate->readonly_func)
+               PopActiveSnapshot();
+
        SPI_pop();
 
+       /*
+        * Now we can release our refcount on the cached plan.
+        */
+       ReleaseCachedPlan(cplan, true);
+
        /*
         * That's it.
         */
-       return retval;
+       return true;
+}
+
+
+/*
+ * Build up the values and nulls arguments for SPI_execute_plan()
+ */
+static void
+eval_expr_params(PLpgSQL_execstate *estate,
+                                PLpgSQL_expr *expr, Datum **p_values, char **p_nulls)
+{
+       Datum      *values;
+       char       *nulls;
+       int                     i;
+
+       *p_values = values = (Datum *) palloc(expr->nparams * sizeof(Datum));
+       *p_nulls = nulls = (char *) palloc(expr->nparams * sizeof(char));
+
+       for (i = 0; i < expr->nparams; i++)
+       {
+               PLpgSQL_datum *datum = estate->datums[expr->params[i]];
+               Oid                     paramtypeid;
+               bool            paramisnull;
+
+               exec_eval_datum(estate, datum, expr->plan_argtypes[i],
+                                               &paramtypeid, &values[i], &paramisnull);
+               if (paramisnull)
+                       nulls[i] = 'n';
+               else
+                       nulls[i] = ' ';
+       }
 }
 
 
@@ -3750,13 +4510,27 @@ exec_move_row(PLpgSQL_execstate *estate,
        if (rec != NULL)
        {
                /*
-                * copy input first, just in case it is pointing at variable's value
+                * Copy input first, just in case it is pointing at variable's value
                 */
                if (HeapTupleIsValid(tup))
                        tup = heap_copytuple(tup);
+               else if (tupdesc)
+               {
+                       /* 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)
                        tupdesc = CreateTupleDescCopy(tupdesc);
 
+               /* Free the old value ... */
                if (rec->freetup)
                {
                        heap_freetuple(rec->tup);
@@ -3768,24 +4542,12 @@ exec_move_row(PLpgSQL_execstate *estate,
                        rec->freetupdesc = false;
                }
 
+               /* ... and install the new */
                if (HeapTupleIsValid(tup))
                {
                        rec->tup = tup;
                        rec->freetup = true;
                }
-               else if (tupdesc)
-               {
-                       /* If we have a tupdesc but no data, form an all-nulls tuple */
-                       char       *nulls;
-
-                       nulls = (char *) palloc(tupdesc->natts * sizeof(char));
-                       memset(nulls, 'n', tupdesc->natts * sizeof(char));
-
-                       rec->tup = heap_formtuple(tupdesc, NULL, nulls);
-                       rec->freetup = true;
-
-                       pfree(nulls);
-               }
                else
                        rec->tup = NULL;
 
@@ -3804,25 +4566,26 @@ exec_move_row(PLpgSQL_execstate *estate,
         * Row is a bit more complicated in that we assign the individual
         * attributes of the tuple to the variables the row points to.
         *
-        * NOTE: this code used to demand row->nfields == tup->t_data->t_natts,
-        * but that's wrong.  The tuple might have more fields than we
-        * expected if it's from an inheritance-child table of the current
-        * table, or it might have fewer if the table has had columns added by
-        * ALTER TABLE. Ignore extra columns and assume NULL for missing
-        * columns, the same as heap_getattr would do.  We also have to skip
-        * over dropped columns in either the source or destination.
+        * NOTE: this code used to demand row->nfields ==
+        * HeapTupleHeaderGetNatts(tup->t_data), but that's wrong.  The tuple might
+        * have more fields than we expected if it's from an inheritance-child
+        * table of the current table, or it might have fewer if the table has had
+        * columns added by ALTER TABLE. Ignore extra columns and assume NULL for
+        * missing columns, the same as heap_getattr would do.  We also have to
+        * skip over dropped columns in either the source or destination.
         *
         * If we have no tuple data at all, we'll assign NULL to all columns of
         * the row variable.
         */
        if (row != NULL)
        {
+               int                     td_natts = tupdesc ? tupdesc->natts : 0;
                int                     t_natts;
                int                     fnum;
                int                     anum;
 
                if (HeapTupleIsValid(tup))
-                       t_natts = tup->t_data->t_natts;
+                       t_natts = HeapTupleHeaderGetNatts(tup->t_data);
                else
                        t_natts = 0;
 
@@ -3839,12 +4602,18 @@ exec_move_row(PLpgSQL_execstate *estate,
 
                        var = (PLpgSQL_var *) (estate->datums[row->varnos[fnum]]);
 
-                       while (anum < t_natts && tupdesc->attrs[anum]->attisdropped)
+                       while (anum < td_natts && tupdesc->attrs[anum]->attisdropped)
                                anum++;                 /* skip dropped column in tuple */
 
-                       if (anum < t_natts)
+                       if (anum < td_natts)
                        {
-                               value = SPI_getbinval(tup, tupdesc, anum + 1, &isnull);
+                               if (anum < t_natts)
+                                       value = SPI_getbinval(tup, tupdesc, anum + 1, &isnull);
+                               else
+                               {
+                                       value = (Datum) 0;
+                                       isnull = true;
+                               }
                                valtype = SPI_gettypeid(tupdesc, anum + 1);
                                anum++;
                        }
@@ -3879,34 +4648,34 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
        int                     natts = tupdesc->natts;
        HeapTuple       tuple;
        Datum      *dvalues;
-       char       *nulls;
+       bool       *nulls;
        int                     i;
 
        if (natts != row->nfields)
                return NULL;
 
        dvalues = (Datum *) palloc0(natts * sizeof(Datum));
-       nulls = (char *) palloc(natts * sizeof(char));
-       MemSet(nulls, 'n', natts);
+       nulls = (bool *) palloc(natts * sizeof(bool));
 
        for (i = 0; i < natts; i++)
        {
-               PLpgSQL_var *var;
+               Oid                     fieldtypeid;
 
                if (tupdesc->attrs[i]->attisdropped)
-                       continue;                       /* leave the column as null */
+               {
+                       nulls[i] = true;        /* leave the column as null */
+                       continue;
+               }
                if (row->varnos[i] < 0) /* should not happen */
                        elog(ERROR, "dropped rowtype entry for non-dropped column");
 
-               var = (PLpgSQL_var *) (estate->datums[row->varnos[i]]);
-               if (var->datatype->typoid != tupdesc->attrs[i]->atttypid)
+               exec_eval_datum(estate, estate->datums[row->varnos[i]],
+                                               InvalidOid, &fieldtypeid, &dvalues[i], &nulls[i]);
+               if (fieldtypeid != tupdesc->attrs[i]->atttypid)
                        return NULL;
-               dvalues[i] = var->value;
-               if (!var->isnull)
-                       nulls[i] = ' ';
        }
 
-       tuple = heap_formtuple(tupdesc, dvalues, nulls);
+       tuple = heap_form_tuple(tupdesc, dvalues, nulls);
 
        pfree(dvalues);
        pfree(nulls);
@@ -3919,6 +4688,8 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
  *
  * Note: callers generally assume that the result is a palloc'd string and
  * should be pfree'd.  This is not all that safe an assumption ...
+ *
+ * Note: not caching the conversion function lookup is bad for performance.
  * ----------
  */
 static char *
@@ -3928,8 +4699,7 @@ convert_value_to_string(Datum value, Oid valtype)
        bool            typIsVarlena;
 
        getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
-
-       return DatumGetCString(OidFunctionCall1(typoutput, value));
+       return OidOutputFunctionCall(typoutput, value);
 }
 
 /* ----------
@@ -3942,25 +4712,28 @@ exec_cast_value(Datum value, Oid valtype,
                                FmgrInfo *reqinput,
                                Oid reqtypioparam,
                                int32 reqtypmod,
-                               bool *isnull)
+                               bool isnull)
 {
-       if (!*isnull)
+       /*
+        * If the type of the queries return value isn't that of the variable,
+        * convert it.
+        */
+       if (valtype != reqtype || reqtypmod != -1)
        {
-               /*
-                * If the type of the queries return value isn't that of the
-                * variable, convert it.
-                */
-               if (valtype != reqtype || reqtypmod != -1)
+               if (!isnull)
                {
                        char       *extval;
 
                        extval = convert_value_to_string(value, valtype);
-                       value = FunctionCall3(reqinput,
-                                                                 CStringGetDatum(extval),
-                                                                 ObjectIdGetDatum(reqtypioparam),
-                                                                 Int32GetDatum(reqtypmod));
+                       value = InputFunctionCall(reqinput, extval,
+                                                                         reqtypioparam, reqtypmod);
                        pfree(extval);
                }
+               else
+               {
+                       value = InputFunctionCall(reqinput, NULL,
+                                                                         reqtypioparam, reqtypmod);
+               }
        }
 
        return value;
@@ -3977,28 +4750,25 @@ exec_cast_value(Datum value, Oid valtype,
 static Datum
 exec_simple_cast_value(Datum value, Oid valtype,
                                           Oid reqtype, int32 reqtypmod,
-                                          bool *isnull)
+                                          bool isnull)
 {
-       if (!*isnull)
+       if (valtype != reqtype || reqtypmod != -1)
        {
-               if (valtype != reqtype || reqtypmod != -1)
-               {
-                       Oid                     typinput;
-                       Oid                     typioparam;
-                       FmgrInfo        finfo_input;
+               Oid                     typinput;
+               Oid                     typioparam;
+               FmgrInfo        finfo_input;
 
-                       getTypeInputInfo(reqtype, &typinput, &typioparam);
+               getTypeInputInfo(reqtype, &typinput, &typioparam);
 
-                       fmgr_info(typinput, &finfo_input);
+               fmgr_info(typinput, &finfo_input);
 
-                       value = exec_cast_value(value,
-                                                                       valtype,
-                                                                       reqtype,
-                                                                       &finfo_input,
-                                                                       typioparam,
-                                                                       reqtypmod,
-                                                                       isnull);
-               }
+               value = exec_cast_value(value,
+                                                               valtype,
+                                                               reqtype,
+                                                               &finfo_input,
+                                                               typioparam,
+                                                               reqtypmod,
+                                                               isnull);
        }
 
        return value;
@@ -4116,6 +4886,12 @@ exec_simple_check_node(Node *node)
                case T_RelabelType:
                        return exec_simple_check_node((Node *) ((RelabelType *) node)->arg);
 
+               case T_CoerceViaIO:
+                       return exec_simple_check_node((Node *) ((CoerceViaIO *) node)->arg);
+
+               case T_ArrayCoerceExpr:
+                       return exec_simple_check_node((Node *) ((ArrayCoerceExpr *) node)->arg);
+
                case T_ConvertRowtypeExpr:
                        return exec_simple_check_node((Node *) ((ConvertRowtypeExpr *) node)->arg);
 
@@ -4168,6 +4944,18 @@ exec_simple_check_node(Node *node)
                                return TRUE;
                        }
 
+               case T_RowCompareExpr:
+                       {
+                               RowCompareExpr *expr = (RowCompareExpr *) node;
+
+                               if (!exec_simple_check_node((Node *) expr->largs))
+                                       return FALSE;
+                               if (!exec_simple_check_node((Node *) expr->rargs))
+                                       return FALSE;
+
+                               return TRUE;
+                       }
+
                case T_CoalesceExpr:
                        {
                                CoalesceExpr *expr = (CoalesceExpr *) node;
@@ -4178,6 +4966,28 @@ exec_simple_check_node(Node *node)
                                return TRUE;
                        }
 
+               case T_MinMaxExpr:
+                       {
+                               MinMaxExpr *expr = (MinMaxExpr *) node;
+
+                               if (!exec_simple_check_node((Node *) expr->args))
+                                       return FALSE;
+
+                               return TRUE;
+                       }
+
+               case T_XmlExpr:
+                       {
+                               XmlExpr    *expr = (XmlExpr *) node;
+
+                               if (!exec_simple_check_node((Node *) expr->named_args))
+                                       return FALSE;
+                               if (!exec_simple_check_node((Node *) expr->args))
+                                       return FALSE;
+
+                               return TRUE;
+                       }
+
                case T_NullIfExpr:
                        {
                                NullIfExpr *expr = (NullIfExpr *) node;
@@ -4231,27 +5041,38 @@ exec_simple_check_node(Node *node)
 static void
 exec_simple_check_plan(PLpgSQL_expr *expr)
 {
-       _SPI_plan  *spi_plan = (_SPI_plan *) expr->plan;
+       CachedPlanSource *plansource;
+       PlannedStmt *stmt;
        Plan       *plan;
        TargetEntry *tle;
 
+       /*
+        * Initialize to "not simple", and remember the plan generation number we
+        * last checked.  (If the query produces more or less than one parsetree
+        * we just leave expr_simple_generation set to 0.)
+        */
        expr->expr_simple_expr = NULL;
+       expr->expr_simple_generation = 0;
 
        /*
-        * 1. We can only evaluate queries that resulted in one single
-        * execution plan
+        * 1. We can only evaluate queries that resulted in one single execution
+        * plan
         */
-       if (list_length(spi_plan->ptlist) != 1)
+       if (list_length(expr->plan->plancache_list) != 1)
+               return;
+       plansource = (CachedPlanSource *) linitial(expr->plan->plancache_list);
+       expr->expr_simple_generation = plansource->generation;
+       if (list_length(plansource->plan->stmt_list) != 1)
                return;
 
-       plan = (Plan *) linitial(spi_plan->ptlist);
+       stmt = (PlannedStmt *) linitial(plansource->plan->stmt_list);
 
        /*
         * 2. It must be a RESULT plan --> no scan's required
         */
-       if (plan == NULL)                       /* utility statement produces this */
+       if (!IsA(stmt, PlannedStmt))
                return;
-
+       plan = stmt->planTree;
        if (!IsA(plan, Result))
                return;
 
@@ -4281,33 +5102,52 @@ exec_simple_check_plan(PLpgSQL_expr *expr)
 
        /*
         * Yes - this is a simple expression.  Mark it as such, and initialize
-        * state to "not executing".
+        * state to "not valid in current transaction".
         */
        expr->expr_simple_expr = tle->expr;
        expr->expr_simple_state = NULL;
-       expr->expr_simple_next = NULL;
+       expr->expr_simple_lxid = InvalidLocalTransactionId;
        /* Also stash away the expression result type */
        expr->expr_simple_type = exprType((Node *) tle->expr);
 }
 
 /*
- * Check two tupledescs have matching number and types of attributes
+ * Validates compatibility of supplied TupleDesc pair by checking number and type
+ * of attributes.
  */
-static bool
-compatible_tupdesc(TupleDesc td1, TupleDesc td2)
+static void
+validate_tupdesc_compat(TupleDesc expected, TupleDesc returned, const char *msg)
 {
-       int                     i;
+       int                i;
+       const char *dropped_column_type = gettext_noop("N/A (dropped column)");
 
-       if (td1->natts != td2->natts)
-               return false;
+       if (!expected || !returned)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("%s", _(msg))));
 
-       for (i = 0; i < td1->natts; i++)
-       {
-               if (td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)
-                       return false;
-       }
+       if (expected->natts != returned->natts)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("%s", _(msg)),
+                                errdetail("Number of returned columns (%d) does not match "
+                                                  "expected column count (%d).",
+                                                  returned->natts, expected->natts)));
 
-       return true;
+       for (i = 0; i < expected->natts; i++)
+               if (expected->attrs[i]->atttypid != returned->attrs[i]->atttypid)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                        errmsg("%s", _(msg)),
+                                        errdetail("Returned type %s does not match expected type "
+                                                          "%s in column \"%s\".",
+                                                          OidIsValid(returned->attrs[i]->atttypid) ?
+                                                          format_type_be(returned->attrs[i]->atttypid) :
+                                                          _(dropped_column_type),
+                                                          OidIsValid(expected->attrs[i]->atttypid) ?
+                                                          format_type_be(expected->attrs[i]->atttypid) :
+                                                          _(dropped_column_type),
+                                                          NameStr(expected->attrs[i]->attname))));
 }
 
 /* ----------
@@ -4321,47 +5161,142 @@ exec_set_found(PLpgSQL_execstate *estate, bool state)
        PLpgSQL_var *var;
 
        var = (PLpgSQL_var *) (estate->datums[estate->found_varno]);
-       var->value = (Datum) state;
+       var->value = PointerGetDatum(state);
        var->isnull = false;
 }
 
 /*
- * plpgsql_xact_cb --- post-transaction-commit-or-abort cleanup
+ * plpgsql_create_econtext --- create an eval_econtext for the current function
+ *
+ * We may need to create a new simple_eval_estate too, if there's not one
+ * already for the current transaction.  The EState will be cleaned up at
+ * transaction end.
+ */
+static void
+plpgsql_create_econtext(PLpgSQL_execstate *estate)
+{
+       SimpleEcontextStackEntry *entry;
+
+       /*
+        * Create an EState for evaluation of simple expressions, if there's not
+        * one already in the current transaction.      The EState is made a child of
+        * TopTransactionContext so it will have the right lifespan.
+        */
+       if (simple_eval_estate == NULL)
+       {
+               MemoryContext oldcontext;
+
+               oldcontext = MemoryContextSwitchTo(TopTransactionContext);
+               simple_eval_estate = CreateExecutorState();
+               MemoryContextSwitchTo(oldcontext);
+       }
+
+       /*
+        * Create a child econtext for the current function.
+        */
+       estate->eval_econtext = CreateExprContext(simple_eval_estate);
+
+       /*
+        * Make a stack entry so we can clean up the econtext at subxact end.
+        * Stack entries are kept in TopTransactionContext for simplicity.
+        */
+       entry = (SimpleEcontextStackEntry *)
+               MemoryContextAlloc(TopTransactionContext,
+                                                  sizeof(SimpleEcontextStackEntry));
+
+       entry->stack_econtext = estate->eval_econtext;
+       entry->xact_subxid = GetCurrentSubTransactionId();
+
+       entry->next = simple_econtext_stack;
+       simple_econtext_stack = entry;
+}
+
+/*
+ * plpgsql_destroy_econtext --- destroy function's econtext
  *
- * If a simple_eval_estate was created in the current transaction,
- * it has to be cleaned up, and we have to mark all active PLpgSQL_expr
- * structs that are using it as no longer active.
+ * We check that it matches the top stack entry, and destroy the stack
+ * entry along with the context.
+ */
+static void
+plpgsql_destroy_econtext(PLpgSQL_execstate *estate)
+{
+       SimpleEcontextStackEntry *next;
+
+       Assert(simple_econtext_stack != NULL);
+       Assert(simple_econtext_stack->stack_econtext == estate->eval_econtext);
+
+       next = simple_econtext_stack->next;
+       pfree(simple_econtext_stack);
+       simple_econtext_stack = next;
+
+       FreeExprContext(estate->eval_econtext);
+       estate->eval_econtext = NULL;
+}
+
+/*
+ * plpgsql_xact_cb --- post-transaction-commit-or-abort cleanup
  *
- * XXX Do we need to do anything at subtransaction events?
- * Maybe subtransactions need to have their own simple_eval_estate?
- * It would get a lot messier, so for now let's assume we don't need that.
+ * If a simple-expression EState was created in the current transaction,
+ * it has to be cleaned up.
  */
 void
 plpgsql_xact_cb(XactEvent event, void *arg)
 {
-       PLpgSQL_expr *expr;
-       PLpgSQL_expr *enext;
+       /*
+        * If we are doing a clean transaction shutdown, free the EState (so that
+        * any remaining resources will be released correctly). In an abort, we
+        * expect the regular abort recovery procedures to release everything of
+        * interest.
+        */
+       if (event != XACT_EVENT_ABORT)
+       {
+               /* Shouldn't be any econtext stack entries left at commit */
+               Assert(simple_econtext_stack == NULL);
 
-       /* Mark all active exprs as inactive */
-       for (expr = active_simple_exprs; expr; expr = enext)
+               if (simple_eval_estate)
+                       FreeExecutorState(simple_eval_estate);
+               simple_eval_estate = NULL;
+       }
+       else
        {
-               enext = expr->expr_simple_next;
-               expr->expr_simple_state = NULL;
-               expr->expr_simple_next = NULL;
+               simple_econtext_stack = NULL;
+               simple_eval_estate = NULL;
        }
-       active_simple_exprs = NULL;
+}
 
-       /*
-        * If we are doing a clean transaction shutdown, free the
-        * EState (so that any remaining resources will be released
-        * correctly). In an abort, we expect the regular abort
-        * recovery procedures to release everything of interest.
-        */
-       if (event == XACT_EVENT_COMMIT && simple_eval_estate)
-               FreeExecutorState(simple_eval_estate);
-       simple_eval_estate = NULL;
+/*
+ * plpgsql_subxact_cb --- post-subtransaction-commit-or-abort cleanup
+ *
+ * Make sure any simple-expression econtexts created in the current
+ * subtransaction get cleaned up.  We have to do this explicitly because
+ * no other code knows which child econtexts of simple_eval_estate belong
+ * to which level of subxact.
+ */
+void
+plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
+                                  SubTransactionId parentSubid, void *arg)
+{
+       if (event == SUBXACT_EVENT_START_SUB)
+               return;
+
+       while (simple_econtext_stack != NULL &&
+                  simple_econtext_stack->xact_subxid == mySubid)
+       {
+               SimpleEcontextStackEntry *next;
+
+               FreeExprContext(simple_econtext_stack->stack_econtext);
+               next = simple_econtext_stack->next;
+               pfree(simple_econtext_stack);
+               simple_econtext_stack = next;
+       }
 }
 
+/*
+ * free_var --- pfree any pass-by-reference value of the variable.
+ *
+ * This should always be followed by some assignment to var->value,
+ * as it leaves a dangling pointer.
+ */
 static void
 free_var(PLpgSQL_var *var)
 {
@@ -4371,3 +5306,153 @@ free_var(PLpgSQL_var *var)
                var->freeval = false;
        }
 }
+
+/*
+ * free old value of a text variable and assign new value from C string
+ */
+static void
+assign_text_var(PLpgSQL_var *var, const char *str)
+{
+       free_var(var);
+       var->value = CStringGetTextDatum(str);
+       var->isnull = false;
+       var->freeval = true;
+}
+
+/*
+ * 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)
+       {
+               PLpgSQL_expr *param = (PLpgSQL_expr *) lfirst(lc);
+               bool    isnull;
+
+               ppd->values[i] = exec_eval_expr(estate, param,
+                                                                               &isnull,
+                                                                               &ppd->types[i]);
+               ppd->nulls[i] = isnull ? 'n' : ' ';
+               ppd->freevals[i] = false;
+
+               /* pass-by-ref non null values must be copied into plpgsql context */
+               if (!isnull)
+               {
+                       int16   typLen;
+                       bool    typByVal;
+
+                       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++;
+       }
+
+       return ppd;
+}
+
+/*
+ * free_params_data --- pfree all pass-by-reference values used in USING clause
+ */
+static void
+free_params_data(PreparedParamsData *ppd)
+{
+       int     i;
+
+       for (i = 0; i < ppd->nargs; i++)
+       {
+               if (ppd->freevals[i])
+                       pfree(DatumGetPointer(ppd->values[i]));
+       }
+
+       pfree(ppd->types);
+       pfree(ppd->values);
+       pfree(ppd->nulls);
+       pfree(ppd->freevals);
+
+       pfree(ppd);
+}
+
+/*
+ * Open portal for dynamic query
+ */
+static Portal
+exec_dynquery_with_params(PLpgSQL_execstate *estate, PLpgSQL_expr *dynquery,
+                                                 List *params)
+{
+       Portal          portal;
+       Datum           query;
+       bool            isnull;
+       Oid                     restype;
+       char       *querystr;
+
+       /*
+        * Evaluate the string expression after the EXECUTE keyword. Its result
+        * is the querystring we have to execute.
+        */
+       query = exec_eval_expr(estate, dynquery, &isnull, &restype);
+       if (isnull)
+               ereport(ERROR,
+                               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                errmsg("query string argument of EXECUTE is null")));
+
+       /* Get the C-String representation */
+       querystr = convert_value_to_string(query, restype);
+
+       exec_eval_cleanup(estate);
+
+       /*
+        * Open an implicit cursor for the query.  We use SPI_cursor_open_with_args
+        * even when there are no params, because this avoids making and freeing
+        * one copy of the plan.
+        */
+       if (params)
+       {
+               PreparedParamsData *ppd;
+
+               ppd = exec_eval_using_params(estate, params);
+               portal = SPI_cursor_open_with_args(NULL,
+                                                                                  querystr,
+                                                                                  ppd->nargs, ppd->types,
+                                                                                  ppd->values, ppd->nulls,
+                                                                                  estate->readonly_func, 0);
+               free_params_data(ppd);
+       }
+       else
+       {
+               portal = SPI_cursor_open_with_args(NULL,
+                                                                                  querystr,
+                                                                                  0, NULL,
+                                                                                  NULL, NULL,
+                                                                                  estate->readonly_func, 0);
+       }
+
+       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;
+}