]> granicus.if.org Git - postgresql/blobdiff - src/pl/plpgsql/src/pl_exec.c
pgindent run for 9.0, second run
[postgresql] / src / pl / plpgsql / src / pl_exec.c
index cff12961ebd33b8997d24c9a4abd6dc16fd19e17..e1f48e3d75d55cdef7f700b40c0839787923d41c 100644 (file)
@@ -3,12 +3,12 @@
  * pl_exec.c           - Executor for the PL/pgSQL
  *                       procedural language
  *
- * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.235 2009/02/23 10:03:22 petere Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.261 2010/07/06 19:19:01 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include <ctype.h>
 
 #include "access/transam.h"
+#include "access/tupconvert.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "executor/spi_priv.h"
 #include "funcapi.h"
+#include "miscadmin.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"
@@ -51,27 +54,26 @@ typedef struct
  * 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). In order to be sure
- * ExprContext callbacks are handled properly, each subtransaction has to have
- * its own such EState; hence we need a stack. We use a simple counter to
- * distinguish different instantiations of the EState, so that we can tell
- * whether we have a current copy of a prepared expression.
+ * 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.
  *
  * 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 SimpleEstateStackEntry
+typedef struct SimpleEcontextStackEntry
 {
-       EState     *xact_eval_estate;           /* EState for current xact level */
-       long int        xact_estate_simple_id;  /* ID for xact_eval_estate */
+       ExprContext *stack_econtext;    /* a stacked econtext */
        SubTransactionId xact_subxid;           /* ID for current subxact */
-       struct SimpleEstateStackEntry *next;            /* next stack entry up */
-} SimpleEstateStackEntry;
+       struct SimpleEcontextStackEntry *next;          /* next stack entry up */
+} SimpleEcontextStackEntry;
 
-static SimpleEstateStackEntry *simple_estate_stack = NULL;
-static long int simple_estate_id_counter = 0;
+static EState *simple_eval_estate = NULL;
+static SimpleEcontextStackEntry *simple_econtext_stack = NULL;
 
 /************************************************************
  * Local function forward declarations
@@ -94,7 +96,7 @@ static int exec_stmt_getdiag(PLpgSQL_execstate *estate,
 static int exec_stmt_if(PLpgSQL_execstate *estate,
                         PLpgSQL_stmt_if *stmt);
 static int exec_stmt_case(PLpgSQL_execstate *estate,
-                                                 PLpgSQL_stmt_case *stmt);
+                          PLpgSQL_stmt_case *stmt);
 static int exec_stmt_loop(PLpgSQL_execstate *estate,
                           PLpgSQL_stmt_loop *stmt);
 static int exec_stmt_while(PLpgSQL_execstate *estate,
@@ -151,7 +153,6 @@ static void exec_assign_value(PLpgSQL_execstate *estate,
                                  Datum value, Oid valtype, bool *isNull);
 static void exec_eval_datum(PLpgSQL_execstate *estate,
                                PLpgSQL_datum *datum,
-                               Oid expectedtypeid,
                                Oid *typeid,
                                Datum *value,
                                bool *isnull);
@@ -168,9 +169,10 @@ static Datum exec_eval_expr(PLpgSQL_execstate *estate,
 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);
+                          Portal portal, bool prefetch_ok);
+static ParamListInfo setup_param_list(PLpgSQL_execstate *estate,
+                                PLpgSQL_expr *expr);
+static void plpgsql_param_fetch(ParamListInfo params, int paramid);
 static void exec_move_row(PLpgSQL_execstate *estate,
                          PLpgSQL_rec *rec,
                          PLpgSQL_row *row,
@@ -189,17 +191,17 @@ static Datum exec_simple_cast_value(Datum value, Oid valtype,
                                           Oid reqtype, int32 reqtypmod,
                                           bool isnull);
 static void exec_init_tuple_store(PLpgSQL_execstate *estate);
-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);
+                                          List *params);
 static void free_params_data(PreparedParamsData *ppd);
 static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
-                                                                               PLpgSQL_expr *query, List *params);
+                                                 PLpgSQL_expr *dynquery, List *params,
+                                                 const char *portalname, int cursorOptions);
 
 
 /* ----------
@@ -380,14 +382,21 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
                         * expected result type.  XXX would be better to cache the tupdesc
                         * instead of repeating get_call_result_type()
                         */
+                       HeapTuple       rettup = (HeapTuple) DatumGetPointer(estate.retval);
                        TupleDesc       tupdesc;
+                       TupleConversionMap *tupmap;
 
                        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");
+                                       tupmap = convert_tuples_by_position(estate.rettupdesc,
+                                                                                                               tupdesc,
+                                                                                                               gettext_noop("returned record type does not match expected record type"));
+                                       /* it might need conversion */
+                                       if (tupmap)
+                                               rettup = do_convert_tuple(rettup, tupmap);
+                                       /* no need to free map, we're about to return anyway */
                                        break;
                                case TYPEFUNC_RECORD:
 
@@ -412,9 +421,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
                         * 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)DatumGetPointer(estate.retval),
-                                                                                               tupdesc));
+                       estate.retval = PointerGetDatum(SPI_returntuple(rettup, tupdesc));
                }
                else
                {
@@ -452,8 +459,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
                ((*plugin_ptr)->func_end) (&estate, func);
 
        /* Clean up any leftover temporary memory */
-       FreeExprContext(estate.eval_econtext);
-       estate.eval_econtext = NULL;
+       plpgsql_destroy_econtext(&estate);
        exec_eval_cleanup(&estate);
 
        /*
@@ -508,12 +514,20 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
 
        /*
         * Put the OLD and NEW tuples into record variables
+        *
+        * We make the tupdescs available in both records even though only one may
+        * have a value.  This allows parsing of record references to succeed in
+        * functions that are used for multiple trigger types.  For example, we
+        * might have a test like "if (TG_OP = 'INSERT' and NEW.foo = 'xyz')",
+        * which should parse regardless of the current trigger type.
         */
        rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]);
        rec_new->freetup = false;
+       rec_new->tupdesc = trigdata->tg_relation->rd_att;
        rec_new->freetupdesc = false;
        rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
        rec_old->freetup = false;
+       rec_old->tupdesc = trigdata->tg_relation->rd_att;
        rec_old->freetupdesc = false;
 
        if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
@@ -522,30 +536,22 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
                 * Per-statement triggers don't use OLD/NEW variables
                 */
                rec_new->tup = NULL;
-               rec_new->tupdesc = NULL;
                rec_old->tup = NULL;
-               rec_old->tupdesc = NULL;
        }
        else if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
        {
                rec_new->tup = trigdata->tg_trigtuple;
-               rec_new->tupdesc = trigdata->tg_relation->rd_att;
                rec_old->tup = NULL;
-               rec_old->tupdesc = NULL;
        }
        else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
        {
                rec_new->tup = trigdata->tg_newtuple;
-               rec_new->tupdesc = trigdata->tg_relation->rd_att;
                rec_old->tup = trigdata->tg_trigtuple;
-               rec_old->tupdesc = trigdata->tg_relation->rd_att;
        }
        else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
        {
                rec_new->tup = NULL;
-               rec_new->tupdesc = NULL;
                rec_old->tup = trigdata->tg_trigtuple;
-               rec_old->tupdesc = trigdata->tg_relation->rd_att;
        }
        else
                elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
@@ -625,19 +631,36 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
        var->isnull = false;
        var->freeval = false;
 
-       /*
-        * 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;
-       if (estate.trig_nargs == 0)
-               estate.trig_argv = NULL;
+       var = (PLpgSQL_var *) (estate.datums[func->tg_argv_varno]);
+       if (trigdata->tg_trigger->tgnargs > 0)
+       {
+               /*
+                * For historical reasons, tg_argv[] subscripts start at zero not one.
+                * So we can't use construct_array().
+                */
+               int                     nelems = trigdata->tg_trigger->tgnargs;
+               Datum      *elems;
+               int                     dims[1];
+               int                     lbs[1];
+
+               elems = palloc(sizeof(Datum) * nelems);
+               for (i = 0; i < nelems; i++)
+                       elems[i] = CStringGetTextDatum(trigdata->tg_trigger->tgargs[i]);
+               dims[0] = nelems;
+               lbs[0] = 0;
+
+               var->value = PointerGetDatum(construct_md_array(elems, NULL,
+                                                                                                               1, dims, lbs,
+                                                                                                               TEXTOID,
+                                                                                                               -1, false, 'i'));
+               var->isnull = false;
+               var->freeval = true;
+       }
        else
        {
-               estate.trig_argv = palloc(sizeof(Datum) * estate.trig_nargs);
-               for (i = 0; i < trigdata->tg_trigger->tgnargs; i++)
-                       estate.trig_argv[i] = CStringGetTextDatum(trigdata->tg_trigger->tgargs[i]);
+               var->value = (Datum) 0;
+               var->isnull = true;
+               var->freeval = false;
        }
 
        estate.err_text = gettext_noop("during function entry");
@@ -704,11 +727,20 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
                rettup = NULL;
        else
        {
-               validate_tupdesc_compat(trigdata->tg_relation->rd_att,
-                                                               estate.rettupdesc,
-                                                               "returned row structure does not match the structure of the triggering table");
+               TupleConversionMap *tupmap;
+
+               rettup = (HeapTuple) DatumGetPointer(estate.retval);
+               /* check rowtype compatibility */
+               tupmap = convert_tuples_by_position(estate.rettupdesc,
+                                                                                       trigdata->tg_relation->rd_att,
+                                                                                       gettext_noop("returned row structure does not match the structure of the triggering table"));
+               /* it might need conversion */
+               if (tupmap)
+                       rettup = do_convert_tuple(rettup, tupmap);
+               /* no need to free map, we're about to return anyway */
+
                /* Copy tuple to upper executor memory */
-               rettup = SPI_copytuple((HeapTuple) DatumGetPointer(estate.retval));
+               rettup = SPI_copytuple(rettup);
        }
 
        /*
@@ -718,8 +750,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
                ((*plugin_ptr)->func_end) (&estate, func);
 
        /* Clean up any leftover temporary memory */
-       FreeExprContext(estate.eval_econtext);
-       estate.eval_econtext = NULL;
+       plpgsql_destroy_econtext(&estate);
        exec_eval_cleanup(&estate);
 
        /*
@@ -742,10 +773,6 @@ plpgsql_exec_error_callback(void *arg)
 {
        PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
 
-       /* safety check, shouldn't happen */
-       if (estate->err_func == NULL)
-               return;
-
        /* if we are doing RAISE, don't report its location */
        if (estate->err_text == raise_skip_msg)
                return;
@@ -765,17 +792,23 @@ plpgsql_exec_error_callback(void *arg)
                 */
                if (estate->err_stmt != NULL)
                {
-                       /* translator: last %s is a phrase such as "during statement block local variable initialization" */
+                       /*
+                        * 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->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" */
+                       /*
+                        * 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->func->fn_name,
                                           _(estate->err_text));
                }
        }
@@ -783,13 +816,13 @@ plpgsql_exec_error_callback(void *arg)
        {
                /* translator: last %s is a plpgsql statement type name */
                errcontext("PL/pgSQL function \"%s\" line %d at %s",
-                                  estate->err_func->fn_name,
+                                  estate->func->fn_name,
                                   estate->err_stmt->lineno,
                                   plpgsql_stmt_typename(estate->err_stmt));
        }
        else
                errcontext("PL/pgSQL function \"%s\"",
-                                  estate->err_func->fn_name);
+                                  estate->func->fn_name);
 }
 
 
@@ -836,7 +869,6 @@ copy_plpgsql_datum(PLpgSQL_datum *datum)
                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
@@ -957,10 +989,13 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                                        if (rec->freetup)
                                        {
                                                heap_freetuple(rec->tup);
-                                               FreeTupleDesc(rec->tupdesc);
                                                rec->freetup = false;
                                        }
-
+                                       if (rec->freetupdesc)
+                                       {
+                                               FreeTupleDesc(rec->tupdesc);
+                                               rec->freetupdesc = false;
+                                       }
                                        rec->tup = NULL;
                                        rec->tupdesc = NULL;
                                }
@@ -984,8 +1019,6 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                MemoryContext oldcontext = CurrentMemoryContext;
                ResourceOwner oldowner = CurrentResourceOwner;
                ExprContext *old_eval_econtext = estate->eval_econtext;
-               EState     *old_eval_estate = estate->eval_estate;
-               long int        old_eval_estate_simple_id = estate->eval_estate_simple_id;
 
                estate->err_text = gettext_noop("during statement block entry");
 
@@ -1034,10 +1067,11 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                        MemoryContextSwitchTo(oldcontext);
                        CurrentResourceOwner = oldowner;
 
-                       /* Revert to outer eval_econtext */
+                       /*
+                        * Revert to outer eval_econtext.  (The inner one was
+                        * automatically cleaned up during subxact exit.)
+                        */
                        estate->eval_econtext = old_eval_econtext;
-                       estate->eval_estate = old_eval_estate;
-                       estate->eval_estate_simple_id = old_eval_estate_simple_id;
 
                        /*
                         * AtEOSubXact_SPI() should not have popped any SPI context, but
@@ -1064,8 +1098,6 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
 
                        /* Revert to outer eval_econtext */
                        estate->eval_econtext = old_eval_econtext;
-                       estate->eval_estate = old_eval_estate;
-                       estate->eval_estate_simple_id = old_eval_estate_simple_id;
 
                        /*
                         * If AtEOSubXact_SPI() popped any SPI context of the subxact, it
@@ -1149,11 +1181,16 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                        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;
@@ -1451,18 +1488,17 @@ exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt)
        if (stmt->t_expr != NULL)
        {
                /* simple case */
-               Datum   t_val;
-               Oid             t_oid;
+               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.
+                * 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);
@@ -1481,7 +1517,7 @@ exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt)
        foreach(l, stmt->case_when_list)
        {
                PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
-               bool    value;
+               bool            value;
 
                value = exec_eval_boolean(estate, cwt->expr, &isnull);
                exec_eval_cleanup(estate);
@@ -1608,7 +1644,7 @@ 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;
@@ -1863,11 +1899,11 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
 {
        PLpgSQL_var *curvar;
        char       *curname = NULL;
+       const char *portalname;
        PLpgSQL_expr *query;
+       ParamListInfo paramLI;
        Portal          portal;
        int                     rc;
-       Datum      *values;
-       char       *nulls;
 
        /* ----------
         * Get the cursor variable and if it has an assigned name, check
@@ -1904,7 +1940,7 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
                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_EXECSQL;
@@ -1933,18 +1969,25 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
                exec_prepare_plan(estate, query, curvar->cursor_options);
 
        /*
-        * Now build up the values and nulls arguments for SPI_execute_plan()
+        * Set up ParamListInfo (note this is only carrying a hook function, not
+        * any actual data values, at this point)
         */
-       eval_expr_params(estate, query, &values, &nulls);
+       paramLI = setup_param_list(estate, query);
 
        /*
-        * Open the cursor
+        * Open the cursor (the paramlist will get copied into the portal)
         */
-       portal = SPI_cursor_open(curname, query->plan, values, nulls,
-                                                        estate->readonly_func);
+       portal = SPI_cursor_open_with_paramlist(curname, query->plan,
+                                                                                       paramLI,
+                                                                                       estate->readonly_func);
        if (portal == NULL)
                elog(ERROR, "could not open cursor: %s",
                         SPI_result_code_string(SPI_result));
+       portalname = portal->name;
+
+       /* don't need paramlist any more */
+       if (paramLI)
+               pfree(paramLI);
 
        /*
         * If cursor variable was NULL, store the generated portal name in it
@@ -1971,8 +2014,6 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
                curvar->isnull = true;
        }
 
-       pfree(values);
-       pfree(nulls);
        if (curname)
                pfree(curname);
 
@@ -2069,7 +2110,7 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
                                        estate->retval =
                                                PointerGetDatum(make_tuple_from_row(estate, row,
                                                                                                                        row->rowtupdesc));
-                                       if (DatumGetPointer(estate->retval) == NULL) /* should not happen */
+                                       if (DatumGetPointer(estate->retval) == NULL)            /* should not happen */
                                                elog(ERROR, "row not compatible with its own tupdesc");
                                        estate->rettupdesc = row->rowtupdesc;
                                        estate->retisnull = false;
@@ -2132,11 +2173,10 @@ static int
 exec_stmt_return_next(PLpgSQL_execstate *estate,
                                          PLpgSQL_stmt_return_next *stmt)
 {
-       TupleDesc               tupdesc;
-       int                             natts;
-       MemoryContext   oldcxt;
-       HeapTuple               tuple = NULL;
-       bool                    free_tuple = false;
+       TupleDesc       tupdesc;
+       int                     natts;
+       HeapTuple       tuple = NULL;
+       bool            free_tuple = false;
 
        if (!estate->retisset)
                ereport(ERROR,
@@ -2174,27 +2214,33 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
                                                                                                tupdesc->attrs[0]->atttypmod,
                                                                                                        isNull);
 
-                                       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;
+                                       TupleConversionMap *tupmap;
 
                                        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");
+                                               errdetail("The tuple structure of a not-yet-assigned"
+                                                                 " record is indeterminate.")));
+                                       tupmap = convert_tuples_by_position(rec->tupdesc,
+                                                                                                               tupdesc,
+                                                                                                               gettext_noop("wrong record type supplied in RETURN NEXT"));
                                        tuple = rec->tup;
+                                       /* it might need conversion */
+                                       if (tupmap)
+                                       {
+                                               tuple = do_convert_tuple(tuple, tupmap);
+                                               free_conversion_map(tupmap);
+                                       }
                                }
                                break;
 
@@ -2239,10 +2285,8 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
                                                                                tupdesc->attrs[0]->atttypmod,
                                                                                isNull);
 
-               oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
                tuplestore_putvalues(estate->tuple_store, tupdesc,
                                                         &retval, &isNull);
-               MemoryContextSwitchTo(oldcxt);
 
                exec_eval_cleanup(estate);
        }
@@ -2255,9 +2299,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
 
        if (HeapTupleIsValid(tuple))
        {
-               oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
                tuplestore_puttuple(estate->tuple_store, tuple);
-               MemoryContextSwitchTo(oldcxt);
 
                if (free_tuple)
                        heap_freetuple(tuple);
@@ -2277,7 +2319,8 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
                                           PLpgSQL_stmt_return_query *stmt)
 {
        Portal          portal;
-       uint32                  processed = 0;
+       uint32          processed = 0;
+       TupleConversionMap *tupmap;
 
        if (!estate->retisset)
                ereport(ERROR,
@@ -2297,34 +2340,39 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
                /* RETURN QUERY EXECUTE */
                Assert(stmt->dynquery != NULL);
                portal = exec_dynquery_with_params(estate, stmt->dynquery,
-                                                                                  stmt->params);
+                                                                                  stmt->params, NULL, 0);
        }
 
-       validate_tupdesc_compat(estate->rettupdesc, portal->tupDesc,
-                                                       "structure of query does not match function result type");
+       tupmap = convert_tuples_by_position(portal->tupDesc,
+                                                                               estate->rettupdesc,
+        gettext_noop("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];
 
+                       if (tupmap)
+                               tuple = do_convert_tuple(tuple, tupmap);
                        tuplestore_puttuple(estate->tuple_store, tuple);
+                       if (tupmap)
+                               heap_freetuple(tuple);
                        processed++;
                }
-               MemoryContextSwitchTo(old_cxt);
 
                SPI_freetuptable(SPI_tuptable);
        }
 
+       if (tupmap)
+               free_conversion_map(tupmap);
+
        SPI_freetuptable(SPI_tuptable);
        SPI_cursor_close(portal);
 
@@ -2339,6 +2387,7 @@ exec_init_tuple_store(PLpgSQL_execstate *estate)
 {
        ReturnSetInfo *rsi = estate->rsi;
        MemoryContext oldcxt;
+       ResourceOwner oldowner;
 
        /*
         * Check caller can handle a set result in the way we want
@@ -2350,12 +2399,22 @@ exec_init_tuple_store(PLpgSQL_execstate *estate)
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                 errmsg("set-valued function called in context that cannot accept a set")));
 
-       estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
-
+       /*
+        * Switch to the right memory context and resource owner for storing the
+        * tuplestore for return set. If we're within a subtransaction opened for
+        * an exception-block, for example, we must still create the tuplestore in
+        * the resource owner that was active when this function was entered, and
+        * not in the subtransaction resource owner.
+        */
        oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
+       oldowner = CurrentResourceOwner;
+       CurrentResourceOwner = estate->tuple_store_owner;
+
        estate->tuple_store =
                tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
                                                          false, work_mem);
+
+       CurrentResourceOwner = oldowner;
        MemoryContextSwitchTo(oldcxt);
 
        estate->rettupdesc = rsi->expectedDesc;
@@ -2388,11 +2447,11 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
 
        if (stmt->message)
        {
-               PLpgSQL_dstring ds;
+               StringInfoData ds;
                ListCell   *current_param;
                char       *cp;
 
-               plpgsql_dstring_init(&ds);
+               initStringInfo(&ds);
                current_param = list_head(stmt->params);
 
                for (cp = stmt->message; *cp; cp++)
@@ -2410,7 +2469,7 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
 
                                if (cp[1] == '%')
                                {
-                                       plpgsql_dstring_append_char(&ds, cp[1]);
+                                       appendStringInfoChar(&ds, '%');
                                        cp++;
                                        continue;
                                }
@@ -2418,10 +2477,10 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
                                if (current_param == NULL)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_SYNTAX_ERROR),
-                                                        errmsg("too few parameters specified for RAISE")));
+                                                 errmsg("too few parameters specified for RAISE")));
 
                                paramvalue = exec_eval_expr(estate,
-                                                                                       (PLpgSQL_expr *) lfirst(current_param),
+                                                                         (PLpgSQL_expr *) lfirst(current_param),
                                                                                        &paramisnull,
                                                                                        &paramtypeid);
 
@@ -2429,12 +2488,12 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
                                        extval = "<NULL>";
                                else
                                        extval = convert_value_to_string(paramvalue, paramtypeid);
-                               plpgsql_dstring_append(&ds, extval);
+                               appendStringInfoString(&ds, extval);
                                current_param = lnext(current_param);
                                exec_eval_cleanup(estate);
                        }
                        else
-                               plpgsql_dstring_append_char(&ds, cp[0]);
+                               appendStringInfoChar(&ds, cp[0]);
                }
 
                /*
@@ -2446,8 +2505,8 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
                                        (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 */
+               err_message = ds.data;
+               /* No pfree(ds.data), the pfree(err_message) does it */
        }
 
        foreach(lc, stmt->options)
@@ -2561,6 +2620,11 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
                                         PLpgSQL_function *func,
                                         ReturnSetInfo *rsi)
 {
+       /* this link will be restored at exit from plpgsql_call_handler */
+       func->cur_estate = estate;
+
+       estate->func = func;
+
        estate->retval = (Datum) 0;
        estate->retisnull = true;
        estate->rettype = InvalidOid;
@@ -2575,12 +2639,18 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
        estate->exitlabel = NULL;
 
        estate->tuple_store = NULL;
-       estate->tuple_store_cxt = NULL;
+       if (rsi)
+       {
+               estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
+               estate->tuple_store_owner = CurrentResourceOwner;
+       }
+       else
+       {
+               estate->tuple_store_cxt = NULL;
+               estate->tuple_store_owner = NULL;
+       }
        estate->rsi = rsi;
 
-       estate->trig_nargs = 0;
-       estate->trig_argv = NULL;
-
        estate->found_varno = func->found_varno;
        estate->ndatums = func->ndatums;
        estate->datums = palloc(sizeof(PLpgSQL_datum *) * estate->ndatums);
@@ -2589,11 +2659,14 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
        estate->eval_tuptable = NULL;
        estate->eval_processed = 0;
        estate->eval_lastoid = InvalidOid;
+       estate->eval_econtext = NULL;
+       estate->cur_expr = NULL;
 
-       estate->err_func = func;
        estate->err_stmt = NULL;
        estate->err_text = NULL;
 
+       estate->plugin_info = NULL;
+
        /*
         * Create an EState and ExprContext for evaluation of simple expressions.
         */
@@ -2644,30 +2717,20 @@ static void
 exec_prepare_plan(PLpgSQL_execstate *estate,
                                  PLpgSQL_expr *expr, int cursorOptions)
 {
-       int                     i;
        SPIPlanPtr      plan;
-       Oid                *argtypes;
 
        /*
-        * We need a temporary argtypes array to load with data. (The finished
-        * plan structure will contain a copy of it.)
+        * The grammar can't conveniently set expr->func while building the parse
+        * tree, so make sure it's set before parser hooks need it.
         */
-       argtypes = (Oid *) palloc(expr->nparams * sizeof(Oid));
-
-       for (i = 0; i < expr->nparams; i++)
-       {
-               Datum           paramval;
-               bool            paramisnull;
-
-               exec_eval_datum(estate, estate->datums[expr->params[i]],
-                                               InvalidOid,
-                                               &argtypes[i], &paramval, &paramisnull);
-       }
+       expr->func = estate->func;
 
        /*
         * Generate and save the plan
         */
-       plan = SPI_prepare_cursor(expr->query, expr->nparams, argtypes,
+       plan = SPI_prepare_params(expr->query,
+                                                         (ParserSetupHook) plpgsql_parser_setup,
+                                                         (void *) expr,
                                                          cursorOptions);
        if (plan == NULL)
        {
@@ -2684,17 +2747,13 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
                                                 errmsg("cannot begin/end transactions in PL/pgSQL"),
                                                 errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
                        default:
-                               elog(ERROR, "SPI_prepare_cursor failed for \"%s\": %s",
+                               elog(ERROR, "SPI_prepare_params failed for \"%s\": %s",
                                         expr->query, SPI_result_code_string(SPI_result));
                }
        }
        expr->plan = SPI_saveplan(plan);
        SPI_freeplan(plan);
-       plan = expr->plan;
-       expr->plan_argtypes = plan->argtypes;
        exec_simple_check_plan(expr);
-
-       pfree(argtypes);
 }
 
 
@@ -2706,8 +2765,7 @@ static int
 exec_stmt_execsql(PLpgSQL_execstate *estate,
                                  PLpgSQL_stmt_execsql *stmt)
 {
-       Datum      *values;
-       char       *nulls;
+       ParamListInfo paramLI;
        long            tcount;
        int                     rc;
        PLpgSQL_expr *expr = stmt->sqlstmt;
@@ -2744,9 +2802,10 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
        }
 
        /*
-        * Now build up the values and nulls arguments for SPI_execute_plan()
+        * Set up ParamListInfo (note this is only carrying a hook function, not
+        * any actual data values, at this point)
         */
-       eval_expr_params(estate, expr, &values, &nulls);
+       paramLI = setup_param_list(estate, expr);
 
        /*
         * If we have INTO, then we only need one row back ... but if we have INTO
@@ -2772,8 +2831,8 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
        /*
         * Execute the plan
         */
-       rc = SPI_execute_plan(expr->plan, values, nulls,
-                                                 estate->readonly_func, tcount);
+       rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
+                                                                                estate->readonly_func, tcount);
 
        /*
         * Check for error, and set FOUND if appropriate (for historical reasons
@@ -2804,6 +2863,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 
                case SPI_OK_REWRITTEN:
                        Assert(!stmt->mod_stmt);
+
                        /*
                         * The command was rewritten into another kind of command. It's
                         * not clear what FOUND would mean in that case (and SPI doesn't
@@ -2813,7 +2873,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
                        break;
 
                default:
-                       elog(ERROR, "SPI_execute_plan failed executing query \"%s\": %s",
+                       elog(ERROR, "SPI_execute_plan_with_paramlist failed executing query \"%s\": %s",
                                 expr->query, SPI_result_code_string(rc));
        }
 
@@ -2880,8 +2940,8 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
                                         (rc == SPI_OK_SELECT) ? errhint("If you want to discard the results of a SELECT, use PERFORM instead.") : 0));
        }
 
-       pfree(values);
-       pfree(nulls);
+       if (paramLI)
+               pfree(paramLI);
 
        return PLPGSQL_RC_OK;
 }
@@ -2975,7 +3035,8 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
                                if (*ptr == 'S' || *ptr == 's')
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                        errmsg("EXECUTE of SELECT ... INTO is not implemented")));
+                                        errmsg("EXECUTE of SELECT ... INTO is not implemented"),
+                                                        errhint("You might want to use EXECUTE ... INTO instead.")));
                                break;
                        }
 
@@ -3076,7 +3137,8 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
        Portal          portal;
        int                     rc;
 
-       portal = exec_dynquery_with_params(estate, stmt->query, stmt->params);
+       portal = exec_dynquery_with_params(estate, stmt->query, stmt->params,
+                                                                          NULL, 0);
 
        /*
         * Execute the loop
@@ -3103,9 +3165,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
        char       *curname = NULL;
        PLpgSQL_expr *query;
        Portal          portal;
-       Datum      *values;
-       char       *nulls;
-       bool            isnull;
+       ParamListInfo paramLI;
 
        /* ----------
         * Get the cursor variable and if it has an assigned name, check
@@ -3145,43 +3205,11 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
                 * This is an OPEN refcursor FOR EXECUTE ...
                 * ----------
                 */
-               Datum           queryD;
-               Oid                     restype;
-               char       *querystr;
-               SPIPlanPtr      curplan;
-
-               /* ----------
-                * We evaluate the string expression after the
-                * EXECUTE keyword. It's result is the querystring we have
-                * to execute.
-                * ----------
-                */
-               queryD = exec_eval_expr(estate, stmt->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(queryD, restype);
-
-               exec_eval_cleanup(estate);
-
-               /* ----------
-                * Now we prepare a query plan for it and open a cursor
-                * ----------
-                */
-               curplan = SPI_prepare_cursor(querystr, 0, NULL, stmt->cursor_options);
-               if (curplan == NULL)
-                       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);
-               if (portal == NULL)
-                       elog(ERROR, "could not open cursor for query \"%s\": %s",
-                                querystr, SPI_result_code_string(SPI_result));
-               pfree(querystr);
-               SPI_freeplan(curplan);
+               portal = exec_dynquery_with_params(estate,
+                                                                                  stmt->dynquery,
+                                                                                  stmt->params,
+                                                                                  curname,
+                                                                                  stmt->cursor_options);
 
                /*
                 * If cursor variable was NULL, store the generated portal name in it
@@ -3241,15 +3269,17 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
        }
 
        /*
-        * Now build up the values and nulls arguments for SPI_execute_plan()
+        * Set up ParamListInfo (note this is only carrying a hook function, not
+        * any actual data values, at this point)
         */
-       eval_expr_params(estate, query, &values, &nulls);
+       paramLI = setup_param_list(estate, query);
 
        /*
         * Open the cursor
         */
-       portal = SPI_cursor_open(curname, query->plan, values, nulls,
-                                                        estate->readonly_func);
+       portal = SPI_cursor_open_with_paramlist(curname, query->plan,
+                                                                                       paramLI,
+                                                                                       estate->readonly_func);
        if (portal == NULL)
                elog(ERROR, "could not open cursor: %s",
                         SPI_result_code_string(SPI_result));
@@ -3260,10 +3290,10 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
        if (curname == NULL)
                assign_text_var(curvar, portal->name);
 
-       pfree(values);
-       pfree(nulls);
        if (curname)
                pfree(curname);
+       if (paramLI)
+               pfree(paramLI);
 
        return PLPGSQL_RC_OK;
 }
@@ -3284,7 +3314,7 @@ exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
        SPITupleTable *tuptab;
        Portal          portal;
        char       *curname;
-       int                     n;
+       uint32          n;
 
        /* ----------
         * Get the portal of the cursor by name
@@ -3342,19 +3372,13 @@ exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
                n = SPI_processed;
 
                /* ----------
-                * Set the target and the global FOUND variable appropriately.
+                * Set the target appropriately.
                 * ----------
                 */
                if (n == 0)
-               {
                        exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
-                       exec_set_found(estate, false);
-               }
                else
-               {
                        exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
-                       exec_set_found(estate, true);
-               }
 
                SPI_freetuptable(tuptab);
        }
@@ -3363,11 +3387,12 @@ exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
                /* Move the cursor */
                SPI_scroll_cursor_move(portal, stmt->direction, how_many);
                n = SPI_processed;
-
-               /* Set the global FOUND variable appropriately. */
-               exec_set_found(estate, n != 0);
        }
 
+       /* Set the ROW_COUNT and the global FOUND variable appropriately. */
+       estate->eval_processed = n;
+       exec_set_found(estate, n != 0);
+
        return PLPGSQL_RC_OK;
 }
 
@@ -3498,11 +3523,6 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                 */
                                PLpgSQL_row *row = (PLpgSQL_row *) target;
 
-                               /* Source must be of RECORD or composite type */
-                               if (!type_is_rowtype(valtype))
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                        errmsg("cannot assign non-composite value to a row variable")));
                                if (*isNull)
                                {
                                        /* If source is null, just assign nulls to the row */
@@ -3516,7 +3536,12 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                        TupleDesc       tupdesc;
                                        HeapTupleData tmptup;
 
-                                       /* Else source is a tuple Datum, safe to do this: */
+                                       /* Source must be of RECORD or composite type */
+                                       if (!type_is_rowtype(valtype))
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                                errmsg("cannot assign non-composite value to a row variable")));
+                                       /* Source is a tuple Datum, so safe to do this: */
                                        td = DatumGetHeapTupleHeader(value);
                                        /* Extract rowtype info and find a tupdesc */
                                        tupType = HeapTupleHeaderGetTypeId(td);
@@ -3540,11 +3565,6 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                 */
                                PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
 
-                               /* Source must be of RECORD or composite type */
-                               if (!type_is_rowtype(valtype))
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                        errmsg("cannot assign non-composite value to a record variable")));
                                if (*isNull)
                                {
                                        /* If source is null, just assign nulls to the record */
@@ -3558,7 +3578,13 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                        TupleDesc       tupdesc;
                                        HeapTupleData tmptup;
 
-                                       /* Else source is a tuple Datum, safe to do this: */
+                                       /* Source must be of RECORD or composite type */
+                                       if (!type_is_rowtype(valtype))
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                                errmsg("cannot assign non-composite value to a record variable")));
+
+                                       /* Source is a tuple Datum, so safe to do this: */
                                        td = DatumGetHeapTupleHeader(value);
                                        /* Extract rowtype info and find a tupdesc */
                                        tupType = HeapTupleHeaderGetTypeId(td);
@@ -3609,8 +3635,8 @@ exec_assign_value(PLpgSQL_execstate *estate,
 
                                /*
                                 * Get the number of the records field to change and the
-                                * number of attributes in the tuple.  Note: disallow
-                                * system column names because the code below won't cope.
+                                * 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 <= 0)
@@ -3721,7 +3747,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                } while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
 
                                /* Fetch current value of array datum */
-                               exec_eval_datum(estate, target, InvalidOid,
+                               exec_eval_datum(estate, target,
                                                          &arraytypeid, &oldarraydatum, &oldarrayisnull);
 
                                arrayelemtypeid = get_element_type(arraytypeid);
@@ -3826,8 +3852,6 @@ exec_assign_value(PLpgSQL_execstate *estate,
  *
  * The type oid, value in Datum format, and null flag are returned.
  *
- * If expectedtypeid isn't InvalidOid, it is checked against the actual type.
- *
  * At present this doesn't handle PLpgSQL_expr or PLpgSQL_arrayelem datums.
  *
  * NOTE: caller must not modify the returned value, since it points right
@@ -3838,7 +3862,6 @@ exec_assign_value(PLpgSQL_execstate *estate,
 static void
 exec_eval_datum(PLpgSQL_execstate *estate,
                                PLpgSQL_datum *datum,
-                               Oid expectedtypeid,
                                Oid *typeid,
                                Datum *value,
                                bool *isnull)
@@ -3854,11 +3877,6 @@ exec_eval_datum(PLpgSQL_execstate *estate,
                                *typeid = var->datatype->typoid;
                                *value = var->value;
                                *isnull = var->isnull;
-                               if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                        errmsg("type of \"%s\" does not match that when preparing the plan",
-                                                                       var->refname)));
                                break;
                        }
 
@@ -3879,11 +3897,6 @@ exec_eval_datum(PLpgSQL_execstate *estate,
                                *typeid = row->rowtupdesc->tdtypeid;
                                *value = HeapTupleGetDatum(tup);
                                *isnull = false;
-                               if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                        errmsg("type of \"%s\" does not match that when preparing the plan",
-                                                                       row->refname)));
                                break;
                        }
 
@@ -3916,11 +3929,6 @@ exec_eval_datum(PLpgSQL_execstate *estate,
                                *typeid = rec->tupdesc->tdtypeid;
                                *value = HeapTupleGetDatum(&worktup);
                                *isnull = false;
-                               if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                        errmsg("type of \"%s\" does not match that when preparing the plan",
-                                                                       rec->refname)));
                                break;
                        }
 
@@ -3945,42 +3953,126 @@ exec_eval_datum(PLpgSQL_execstate *estate,
                                                                        rec->refname, recfield->fieldname)));
                                *typeid = SPI_gettypeid(rec->tupdesc, fno);
                                *value = SPI_getbinval(rec->tup, rec->tupdesc, fno, isnull);
-                               if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
+                               break;
+                       }
+
+               default:
+                       elog(ERROR, "unrecognized dtype: %d", datum->dtype);
+       }
+}
+
+/*
+ * exec_get_datum_type                         Get datatype of a PLpgSQL_datum
+ *
+ * This is the same logic as in exec_eval_datum, except that it can handle
+ * some cases where exec_eval_datum has to fail; specifically, we may have
+ * a tupdesc but no row value for a record variable.  (This currently can
+ * happen only for a trigger's NEW/OLD records.)
+ */
+Oid
+exec_get_datum_type(PLpgSQL_execstate *estate,
+                                       PLpgSQL_datum *datum)
+{
+       Oid                     typeid;
+
+       switch (datum->dtype)
+       {
+               case PLPGSQL_DTYPE_VAR:
+                       {
+                               PLpgSQL_var *var = (PLpgSQL_var *) datum;
+
+                               typeid = var->datatype->typoid;
+                               break;
+                       }
+
+               case PLPGSQL_DTYPE_ROW:
+                       {
+                               PLpgSQL_row *row = (PLpgSQL_row *) datum;
+
+                               if (!row->rowtupdesc)   /* should not happen */
+                                       elog(ERROR, "row variable has no tupdesc");
+                               /* Make sure we have a valid type/typmod setting */
+                               BlessTupleDesc(row->rowtupdesc);
+                               typeid = row->rowtupdesc->tdtypeid;
+                               break;
+                       }
+
+               case PLPGSQL_DTYPE_REC:
+                       {
+                               PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
+
+                               if (rec->tupdesc == NULL)
                                        ereport(ERROR,
-                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                        errmsg("type of \"%s.%s\" does not match that when preparing the plan",
-                                                                       rec->refname, recfield->fieldname)));
+                                                 (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.")));
+                               /* Make sure we have a valid type/typmod setting */
+                               BlessTupleDesc(rec->tupdesc);
+                               typeid = rec->tupdesc->tdtypeid;
                                break;
                        }
 
-               case PLPGSQL_DTYPE_TRIGARG:
+               case PLPGSQL_DTYPE_RECFIELD:
                        {
-                               PLpgSQL_trigarg *trigarg = (PLpgSQL_trigarg *) datum;
-                               int                     tgargno;
+                               PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
+                               PLpgSQL_rec *rec;
+                               int                     fno;
 
-                               *typeid = TEXTOID;
-                               tgargno = exec_eval_integer(estate, trigarg->argnum, isnull);
-                               if (*isnull || tgargno < 0 || tgargno >= estate->trig_nargs)
-                               {
-                                       *value = (Datum) 0;
-                                       *isnull = true;
-                               }
-                               else
-                               {
-                                       *value = estate->trig_argv[tgargno];
-                                       *isnull = false;
-                               }
-                               if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
+                               rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
+                               if (rec->tupdesc == NULL)
                                        ereport(ERROR,
-                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                        errmsg("type of tgargv[%d] does not match that when preparing the plan",
-                                                                       tgargno)));
+                                                 (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,
+                                                       (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                                        errmsg("record \"%s\" has no field \"%s\"",
+                                                                       rec->refname, recfield->fieldname)));
+                               typeid = SPI_gettypeid(rec->tupdesc, fno);
                                break;
                        }
 
                default:
                        elog(ERROR, "unrecognized dtype: %d", datum->dtype);
+                       typeid = InvalidOid;    /* keep compiler quiet */
+                       break;
        }
+
+       return typeid;
+}
+
+/*
+ * exec_get_rec_fieldtype                              Get datatype of a PLpgSQL record field
+ *
+ * Also returns the field number to *fieldno.
+ */
+Oid
+exec_get_rec_fieldtype(PLpgSQL_rec *rec, const char *fieldname,
+                                          int *fieldno)
+{
+       Oid                     typeid;
+       int                     fno;
+
+       if (rec->tupdesc == NULL)
+               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.")));
+       fno = SPI_fnumber(rec->tupdesc, fieldname);
+       if (fno == SPI_ERROR_NOATTRIBUTE)
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                errmsg("record \"%s\" has no field \"%s\"",
+                                               rec->refname, fieldname)));
+       typeid = SPI_gettypeid(rec->tupdesc, fno);
+
+       *fieldno = fno;
+       return typeid;
 }
 
 /* ----------
@@ -4069,7 +4161,24 @@ exec_eval_expr(PLpgSQL_execstate *estate,
                                 errmsg("query \"%s\" did not return data", expr->query)));
 
        /*
-        * If there are no rows selected, the result is NULL.
+        * Check that the expression returns exactly one column...
+        */
+       if (estate->eval_tuptable->tupdesc->natts != 1)
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg_plural("query \"%s\" returned %d column",
+                                                          "query \"%s\" returned %d columns",
+                                                          estate->eval_tuptable->tupdesc->natts,
+                                                          expr->query,
+                                                          estate->eval_tuptable->tupdesc->natts)));
+
+       /*
+        * ... and get the column's datatype.
+        */
+       *rettype = SPI_gettypeid(estate->eval_tuptable->tupdesc, 1);
+
+       /*
+        * If there are no rows selected, the result is a NULL of that type.
         */
        if (estate->eval_processed == 0)
        {
@@ -4078,23 +4187,17 @@ exec_eval_expr(PLpgSQL_execstate *estate,
        }
 
        /*
-        * Check that the expression returned one single Datum
+        * Check that the expression returned no more than one row.
         */
-       if (estate->eval_processed > 1)
+       if (estate->eval_processed != 1)
                ereport(ERROR,
                                (errcode(ERRCODE_CARDINALITY_VIOLATION),
                                 errmsg("query \"%s\" returned more than one row",
                                                expr->query)));
-       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)));
 
        /*
-        * Return the result and its type
+        * Return the single result Datum.
         */
-       *rettype = SPI_gettypeid(estate->eval_tuptable->tupdesc, 1);
        return SPI_getbinval(estate->eval_tuptable->vals[0],
                                                 estate->eval_tuptable->tupdesc, 1, isNull);
 }
@@ -4108,8 +4211,7 @@ static int
 exec_run_select(PLpgSQL_execstate *estate,
                                PLpgSQL_expr *expr, long maxtuples, Portal *portalP)
 {
-       Datum      *values;
-       char       *nulls;
+       ParamListInfo paramLI;
        int                     rc;
 
        /*
@@ -4119,30 +4221,32 @@ exec_run_select(PLpgSQL_execstate *estate,
                exec_prepare_plan(estate, expr, 0);
 
        /*
-        * Now build up the values and nulls arguments for SPI_execute_plan()
+        * Set up ParamListInfo (note this is only carrying a hook function, not
+        * any actual data values, at this point)
         */
-       eval_expr_params(estate, expr, &values, &nulls);
+       paramLI = setup_param_list(estate, expr);
 
        /*
         * If a portal was requested, put the query into the portal
         */
        if (portalP != NULL)
        {
-               *portalP = SPI_cursor_open(NULL, expr->plan, values, nulls,
-                                                                  estate->readonly_func);
+               *portalP = SPI_cursor_open_with_paramlist(NULL, expr->plan,
+                                                                                                 paramLI,
+                                                                                                 estate->readonly_func);
                if (*portalP == NULL)
                        elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
                                 expr->query, SPI_result_code_string(SPI_result));
-               pfree(values);
-               pfree(nulls);
+               if (paramLI)
+                       pfree(paramLI);
                return SPI_OK_CURSOR;
        }
 
        /*
         * Execute the query
         */
-       rc = SPI_execute_plan(expr->plan, values, nulls,
-                                                 estate->readonly_func, maxtuples);
+       rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
+                                                                                estate->readonly_func, maxtuples);
        if (rc != SPI_OK_SELECT)
                ereport(ERROR,
                                (errcode(ERRCODE_SYNTAX_ERROR),
@@ -4154,8 +4258,8 @@ exec_run_select(PLpgSQL_execstate *estate,
        estate->eval_processed = SPI_processed;
        estate->eval_lastoid = SPI_lastoid;
 
-       pfree(values);
-       pfree(nulls);
+       if (paramLI)
+               pfree(paramLI);
 
        return rc;
 }
@@ -4188,8 +4292,14 @@ exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
                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
+        * Make sure the portal doesn't get closed by the user statements we
+        * execute.
+        */
+       PinPortal(portal);
+
+       /*
+        * 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);
@@ -4197,8 +4307,8 @@ exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
        n = SPI_processed;
 
        /*
-        * If the query didn't return any rows, set the target to NULL and
-        * fall through with found = false.
+        * 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);
@@ -4297,6 +4407,8 @@ loop_exit:
         */
        SPI_freetuptable(tuptab);
 
+       UnpinPortal(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 last so
@@ -4337,10 +4449,11 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
                                          Oid *rettype)
 {
        ExprContext *econtext = estate->eval_econtext;
+       LocalTransactionId curlxid = MyProc->lxid;
        CachedPlanSource *plansource;
        CachedPlan *cplan;
        ParamListInfo paramLI;
-       int                     i;
+       PLpgSQL_expr *save_cur_expr;
        MemoryContext oldcontext;
 
        /*
@@ -4377,52 +4490,16 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
 
        /*
         * Prepare the expression for execution, if it's not been done already in
-        * the current eval_estate.  (This will be forced to happen if we called
+        * the current transaction.  (This will be forced to happen if we called
         * exec_simple_check_plan above.)
         */
-       if (expr->expr_simple_id != estate->eval_estate_simple_id)
+       if (expr->expr_simple_lxid != curlxid)
        {
                expr->expr_simple_state = ExecPrepareExpr(expr->expr_simple_expr,
-                                                                                                 estate->eval_estate);
-               expr->expr_simple_id = estate->eval_estate_simple_id;
+                                                                                                 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.
-        */
-       if (expr->nparams > 0)
-       {
-               /* 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]];
-
-                       prm->pflags = 0;
-                       exec_eval_datum(estate, datum, expr->plan_argtypes[i],
-                                                       &prm->ptype,
-                                                       &prm->value, &prm->isnull);
-               }
-       }
-       else
-               paramLI = NULL;
-
-       /*
-        * Now we can safely make the econtext point to the param list.
-        */
-       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.
@@ -4438,6 +4515,22 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
                PushActiveSnapshot(GetTransactionSnapshot());
        }
 
+       /*
+        * Create the param list in econtext's temporary memory context. We won't
+        * need to free it explicitly, since it will go away at the next reset of
+        * that context.
+        *
+        * XXX think about avoiding repeated palloc's for param lists?  It should
+        * be possible --- this routine isn't re-entrant anymore.
+        *
+        * Just for paranoia's sake, save and restore the prior value of
+        * estate->cur_expr, which setup_param_list() sets.
+        */
+       save_cur_expr = estate->cur_expr;
+
+       paramLI = setup_param_list(estate, expr);
+       econtext->ecxt_param_list_info = paramLI;
+
        /*
         * Finally we can call the executor to evaluate the expression
         */
@@ -4445,11 +4538,15 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
                                                   econtext,
                                                   isNull,
                                                   NULL);
-       MemoryContextSwitchTo(oldcontext);
+
+       /* Assorted cleanup */
+       estate->cur_expr = save_cur_expr;
 
        if (!estate->readonly_func)
                PopActiveSnapshot();
 
+       MemoryContextSwitchTo(oldcontext);
+
        SPI_pop();
 
        /*
@@ -4465,32 +4562,91 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
 
 
 /*
- * Build up the values and nulls arguments for SPI_execute_plan()
+ * Create a ParamListInfo to pass to SPI
+ *
+ * The ParamListInfo array is initially all zeroes, in particular the
+ * ptype values are all InvalidOid.  This causes the executor to call the
+ * paramFetch hook each time it wants a value. We thus evaluate only the
+ * parameters actually demanded.
+ *
+ * The result is a locally palloc'd array that should be pfree'd after use;
+ * but note it can be NULL.
  */
-static void
-eval_expr_params(PLpgSQL_execstate *estate,
-                                PLpgSQL_expr *expr, Datum **p_values, char **p_nulls)
+static ParamListInfo
+setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
 {
-       Datum      *values;
-       char       *nulls;
-       int                     i;
-
-       *p_values = values = (Datum *) palloc(expr->nparams * sizeof(Datum));
-       *p_nulls = nulls = (char *) palloc(expr->nparams * sizeof(char));
+       ParamListInfo paramLI;
 
-       for (i = 0; i < expr->nparams; i++)
+       /*
+        * Could we re-use these arrays instead of palloc'ing a new one each time?
+        * However, we'd have to zero the array each time anyway, since new values
+        * might have been assigned to the variables.
+        */
+       if (estate->ndatums > 0)
        {
-               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] = ' ';
+               /* sizeof(ParamListInfoData) includes the first array element */
+               paramLI = (ParamListInfo)
+                       palloc0(sizeof(ParamListInfoData) +
+                                       (estate->ndatums - 1) *sizeof(ParamExternData));
+               paramLI->paramFetch = plpgsql_param_fetch;
+               paramLI->paramFetchArg = (void *) estate;
+               paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
+               paramLI->parserSetupArg = (void *) expr;
+               paramLI->numParams = estate->ndatums;
+
+               /*
+                * Set up link to active expr where the hook functions can find it.
+                * Callers must save and restore cur_expr if there is any chance that
+                * they are interrupting an active use of parameters.
+                */
+               estate->cur_expr = expr;
+
+               /*
+                * Also make sure this is set before parser hooks need it.      There is
+                * no need to save and restore, since the value is always correct once
+                * set.
+                */
+               expr->func = estate->func;
        }
+       else
+               paramLI = NULL;
+       return paramLI;
+}
+
+/*
+ * plpgsql_param_fetch         paramFetch callback for dynamic parameter fetch
+ */
+static void
+plpgsql_param_fetch(ParamListInfo params, int paramid)
+{
+       int                     dno;
+       PLpgSQL_execstate *estate;
+       PLpgSQL_expr *expr;
+       PLpgSQL_datum *datum;
+       ParamExternData *prm;
+
+       /* paramid's are 1-based, but dnos are 0-based */
+       dno = paramid - 1;
+       Assert(dno >= 0 && dno < params->numParams);
+
+       /* fetch back the hook data */
+       estate = (PLpgSQL_execstate *) params->paramFetchArg;
+       expr = estate->cur_expr;
+       Assert(params->numParams == estate->ndatums);
+
+       /*
+        * Do nothing if asked for a value that's not supposed to be used by this
+        * SQL expression.      This avoids unwanted evaluations when functions such
+        * as copyParamList try to materialize all the values.
+        */
+       if (!bms_is_member(dno, expr->paramnos))
+               return;
+
+       /* OK, evaluate the value and store into the appropriate paramlist slot */
+       datum = estate->datums[dno];
+       prm = &params->params[dno];
+       exec_eval_datum(estate, datum,
+                                       &prm->ptype, &prm->value, &prm->isnull);
 }
 
 
@@ -4568,12 +4724,13 @@ exec_move_row(PLpgSQL_execstate *estate,
         * attributes of the tuple to the variables the row points to.
         *
         * 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.
+        * 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.
@@ -4622,6 +4779,11 @@ exec_move_row(PLpgSQL_execstate *estate,
                        {
                                value = (Datum) 0;
                                isnull = true;
+
+                               /*
+                                * InvalidOid is OK because exec_assign_value doesn't care
+                                * about the type of a source NULL
+                                */
                                valtype = InvalidOid;
                        }
 
@@ -4671,7 +4833,7 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
                        elog(ERROR, "dropped rowtype entry for non-dropped column");
 
                exec_eval_datum(estate, estate->datums[row->varnos[i]],
-                                               InvalidOid, &fieldtypeid, &dvalues[i], &nulls[i]);
+                                               &fieldtypeid, &dvalues[i], &nulls[i]);
                if (fieldtypeid != tupdesc->attrs[i]->atttypid)
                        return NULL;
        }
@@ -4753,26 +4915,23 @@ exec_simple_cast_value(Datum value, Oid valtype,
                                           Oid reqtype, int32 reqtypmod,
                                           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;
@@ -5110,50 +5269,11 @@ exec_simple_check_plan(PLpgSQL_expr *expr)
         */
        expr->expr_simple_expr = tle->expr;
        expr->expr_simple_state = NULL;
-       expr->expr_simple_id = -1;
+       expr->expr_simple_lxid = InvalidLocalTransactionId;
        /* Also stash away the expression result type */
        expr->expr_simple_type = exprType((Node *) tle->expr);
 }
 
-/*
- * Validates compatibility of supplied TupleDesc pair by checking number and type
- * of attributes.
- */
-static void
-validate_tupdesc_compat(TupleDesc expected, TupleDesc returned, const char *msg)
-{
-       int                i;
-       const char *dropped_column_type = gettext_noop("N/A (dropped column)");
-
-       if (!expected || !returned)
-               ereport(ERROR,
-                               (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                errmsg("%s", _(msg))));
-
-       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)));
-
-       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))));
-}
-
 /* ----------
  * exec_set_found                      Set the global found variable
  *                                     to true/false
@@ -5172,46 +5292,69 @@ exec_set_found(PLpgSQL_execstate *estate, bool state)
 /*
  * plpgsql_create_econtext --- create an eval_econtext for the current function
  *
- * We may need to create a new eval_estate too, if there's not one already
- * for the current (sub) transaction.  The EState will be cleaned up at
- * (sub) transaction end.
+ * 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)
 {
-       SubTransactionId my_subxid = GetCurrentSubTransactionId();
-       SimpleEstateStackEntry *entry = simple_estate_stack;
+       SimpleEcontextStackEntry *entry;
 
-       /* Create new EState if not one for current subxact */
-       if (entry == NULL ||
-               entry->xact_subxid != my_subxid)
+       /*
+        * 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;
 
-               /* Stack entries are kept in TopTransactionContext for simplicity */
-               entry = (SimpleEstateStackEntry *)
-                       MemoryContextAlloc(TopTransactionContext,
-                                                          sizeof(SimpleEstateStackEntry));
-
-               /* But each EState should be a child of its CurTransactionContext */
-               oldcontext = MemoryContextSwitchTo(CurTransactionContext);
-               entry->xact_eval_estate = CreateExecutorState();
+               oldcontext = MemoryContextSwitchTo(TopTransactionContext);
+               simple_eval_estate = CreateExecutorState();
                MemoryContextSwitchTo(oldcontext);
+       }
 
-               /* Assign a reasonably-unique ID to this EState */
-               entry->xact_estate_simple_id = simple_estate_id_counter++;
-               entry->xact_subxid = my_subxid;
+       /*
+        * Create a child econtext for the current function.
+        */
+       estate->eval_econtext = CreateExprContext(simple_eval_estate);
 
-               entry->next = simple_estate_stack;
-               simple_estate_stack = entry;
-       }
+       /*
+        * 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));
 
-       /* Link plpgsql estate to it */
-       estate->eval_estate = entry->xact_eval_estate;
-       estate->eval_estate_simple_id = entry->xact_estate_simple_id;
+       entry->stack_econtext = estate->eval_econtext;
+       entry->xact_subxid = GetCurrentSubTransactionId();
 
-       /* And create a child econtext for the current function */
-       estate->eval_econtext = CreateExprContext(estate->eval_estate);
+       entry->next = simple_econtext_stack;
+       simple_econtext_stack = entry;
+}
+
+/*
+ * plpgsql_destroy_econtext --- destroy function's econtext
+ *
+ * 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, true);
+       estate->eval_econtext = NULL;
 }
 
 /*
@@ -5227,29 +5370,31 @@ plpgsql_xact_cb(XactEvent event, void *arg)
         * 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.  We don't need to free the individual stack entries since
-        * TopTransactionContext is about to go away anyway.
-        *
-        * Note: if plpgsql_subxact_cb is doing its job, there should be at most
-        * one stack entry, but we may as well code this as a loop.
+        * interest.
         */
        if (event != XACT_EVENT_ABORT)
        {
-               while (simple_estate_stack != NULL)
-               {
-                       FreeExecutorState(simple_estate_stack->xact_eval_estate);
-                       simple_estate_stack = simple_estate_stack->next;
-               }
+               /* Shouldn't be any econtext stack entries left at commit */
+               Assert(simple_econtext_stack == NULL);
+
+               if (simple_eval_estate)
+                       FreeExecutorState(simple_eval_estate);
+               simple_eval_estate = NULL;
        }
        else
-               simple_estate_stack = NULL;
+       {
+               simple_econtext_stack = NULL;
+               simple_eval_estate = NULL;
+       }
 }
 
 /*
  * plpgsql_subxact_cb --- post-subtransaction-commit-or-abort cleanup
  *
- * If a simple-expression EState was created in the current subtransaction,
- * it has to be cleaned up.
+ * 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,
@@ -5258,16 +5403,16 @@ plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
        if (event == SUBXACT_EVENT_START_SUB)
                return;
 
-       if (simple_estate_stack != NULL &&
-               simple_estate_stack->xact_subxid == mySubid)
+       while (simple_econtext_stack != NULL &&
+                  simple_econtext_stack->xact_subxid == mySubid)
        {
-               SimpleEstateStackEntry *next;
+               SimpleEcontextStackEntry *next;
 
-               if (event == SUBXACT_EVENT_COMMIT_SUB)
-                       FreeExecutorState(simple_estate_stack->xact_eval_estate);
-               next = simple_estate_stack->next;
-               pfree(simple_estate_stack);
-               simple_estate_stack = next;
+               FreeExprContext(simple_econtext_stack->stack_econtext,
+                                               (event == SUBXACT_EVENT_COMMIT_SUB));
+               next = simple_econtext_stack->next;
+               pfree(simple_econtext_stack);
+               simple_econtext_stack = next;
        }
 }
 
@@ -5323,7 +5468,7 @@ exec_eval_using_params(PLpgSQL_execstate *estate, List *params)
        foreach(lc, params)
        {
                PLpgSQL_expr *param = (PLpgSQL_expr *) lfirst(lc);
-               bool    isnull;
+               bool            isnull;
 
                ppd->values[i] = exec_eval_expr(estate, param,
                                                                                &isnull,
@@ -5334,8 +5479,8 @@ exec_eval_using_params(PLpgSQL_execstate *estate, List *params)
                /* pass-by-ref non null values must be copied into plpgsql context */
                if (!isnull)
                {
-                       int16   typLen;
-                       bool    typByVal;
+                       int16           typLen;
+                       bool            typByVal;
 
                        get_typlenbyval(ppd->types[i], &typLen, &typByVal);
                        if (!typByVal)
@@ -5359,7 +5504,7 @@ exec_eval_using_params(PLpgSQL_execstate *estate, List *params)
 static void
 free_params_data(PreparedParamsData *ppd)
 {
-       int     i;
+       int                     i;
 
        for (i = 0; i < ppd->nargs; i++)
        {
@@ -5379,8 +5524,11 @@ free_params_data(PreparedParamsData *ppd)
  * Open portal for dynamic query
  */
 static Portal
-exec_dynquery_with_params(PLpgSQL_execstate *estate, PLpgSQL_expr *dynquery,
-                                                 List *params)
+exec_dynquery_with_params(PLpgSQL_execstate *estate,
+                                                 PLpgSQL_expr *dynquery,
+                                                 List *params,
+                                                 const char *portalname,
+                                                 int cursorOptions)
 {
        Portal          portal;
        Datum           query;
@@ -5389,8 +5537,8 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate, PLpgSQL_expr *dynquery,
        char       *querystr;
 
        /*
-        * Evaluate the string expression after the EXECUTE keyword. Its result
-        * is the querystring we have to execute.
+        * 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)
@@ -5404,29 +5552,31 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate, PLpgSQL_expr *dynquery,
        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.
+        * 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,
+               portal = SPI_cursor_open_with_args(portalname,
                                                                                   querystr,
                                                                                   ppd->nargs, ppd->types,
                                                                                   ppd->values, ppd->nulls,
-                                                                                  estate->readonly_func, 0);
+                                                                                  estate->readonly_func,
+                                                                                  cursorOptions);
                free_params_data(ppd);
        }
        else
        {
-               portal = SPI_cursor_open_with_args(NULL,
+               portal = SPI_cursor_open_with_args(portalname,
                                                                                   querystr,
                                                                                   0, NULL,
                                                                                   NULL, NULL,
-                                                                                  estate->readonly_func, 0);
+                                                                                  estate->readonly_func,
+                                                                                  cursorOptions);
        }
 
        if (portal == NULL)