]> granicus.if.org Git - postgresql/blobdiff - src/pl/plpgsql/src/pl_exec.c
Improve the recently-added support for properly pluralized error messages
[postgresql] / src / pl / plpgsql / src / pl_exec.c
index b248384af4162817f1984714ca4c7b3ae326dcdd..b5f000691886414b9673f9ab3f3f32f4537ea64f 100644 (file)
@@ -3,18 +3,17 @@
  * pl_exec.c           - Executor for the PL/pgSQL
  *                       procedural language
  *
- * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2009, 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.213 2008/05/12 20:02:02 alvherre Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.242 2009/06/04 18:33:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "plpgsql.h"
-#include "pl.tab.h"
 
 #include <ctype.h>
 
 #include "catalog/pg_type.h"
 #include "executor/spi_priv.h"
 #include "funcapi.h"
-#include "optimizer/clauses.h"
-#include "parser/parse_expr.h"
+#include "nodes/nodeFuncs.h"
 #include "parser/scansup.h"
+#include "storage/proc.h"
 #include "tcop/tcopprot.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
@@ -52,27 +52,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 */
-       SubTransactionId xact_subxid;           /* ID for current subxact */
-       struct SimpleEstateStackEntry *next;            /* next stack entry up */
-} SimpleEstateStackEntry;
+       ExprContext *stack_econtext;                    /* a stacked econtext */
+       SubTransactionId xact_subxid;                   /* ID for current subxact */
+       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,6 +93,8 @@ static int exec_stmt_getdiag(PLpgSQL_execstate *estate,
                                  PLpgSQL_stmt_getdiag *stmt);
 static int exec_stmt_if(PLpgSQL_execstate *estate,
                         PLpgSQL_stmt_if *stmt);
+static int exec_stmt_case(PLpgSQL_execstate *estate,
+                                                 PLpgSQL_stmt_case *stmt);
 static int exec_stmt_loop(PLpgSQL_execstate *estate,
                           PLpgSQL_stmt_loop *stmt);
 static int exec_stmt_while(PLpgSQL_execstate *estate,
@@ -188,9 +189,11 @@ 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 bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
+static void validate_tupdesc_compat(TupleDesc expected, TupleDesc returned,
+                                               const char *msg);
 static void exec_set_found(PLpgSQL_execstate *estate, bool state);
 static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
+static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate);
 static void free_var(PLpgSQL_var *var);
 static void assign_text_var(PLpgSQL_var *var, const char *str);
 static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate,
@@ -316,13 +319,17 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
                estate.err_text = NULL;
 
                /*
-                * Provide a more helpful message if a CONTINUE has been used outside
-                * a loop.
+                * Provide a more helpful message if a CONTINUE or RAISE has been used
+                * outside the context it can work in.
                 */
                if (rc == PLPGSQL_RC_CONTINUE)
                        ereport(ERROR,
                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                         errmsg("CONTINUE cannot be used outside a loop")));
+               else if (rc == PLPGSQL_RC_RERAISE)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("RAISE without parameters cannot be used outside an exception handler")));
                else
                        ereport(ERROR,
                           (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
@@ -380,11 +387,8 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
                        {
                                case TYPEFUNC_COMPOSITE:
                                        /* got the expected result rowtype, now check it */
-                                       if (estate.rettupdesc == NULL ||
-                                               !compatible_tupdesc(estate.rettupdesc, tupdesc))
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                                errmsg("returned record type does not match expected record type")));
+                                       validate_tupdesc_compat(tupdesc, estate.rettupdesc,
+                                                                                       "returned record type does not match expected record type");
                                        break;
                                case TYPEFUNC_RECORD:
 
@@ -449,8 +453,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);
 
        /*
@@ -662,13 +665,17 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
                estate.err_text = NULL;
 
                /*
-                * Provide a more helpful message if a CONTINUE has been used outside
-                * a loop.
+                * Provide a more helpful message if a CONTINUE or RAISE has been used
+                * outside the context it can work in.
                 */
                if (rc == PLPGSQL_RC_CONTINUE)
                        ereport(ERROR,
                                        (errcode(ERRCODE_SYNTAX_ERROR),
                                         errmsg("CONTINUE cannot be used outside a loop")));
+               else if (rc == PLPGSQL_RC_RERAISE)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("RAISE without parameters cannot be used outside an exception handler")));
                else
                        ereport(ERROR,
                           (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
@@ -697,11 +704,9 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
                rettup = NULL;
        else
        {
-               if (!compatible_tupdesc(estate.rettupdesc,
-                                                               trigdata->tg_relation->rd_att))
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                        errmsg("returned tuple structure does not match table of trigger event")));
+               validate_tupdesc_compat(trigdata->tg_relation->rd_att,
+                                                               estate.rettupdesc,
+                                                               "returned row structure does not match the structure of the triggering table");
                /* Copy tuple to upper executor memory */
                rettup = SPI_copytuple((HeapTuple) DatumGetPointer(estate.retval));
        }
@@ -713,8 +718,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);
 
        /*
@@ -760,24 +764,18 @@ 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->err_stmt->lineno,
-                                          gettext(estate->err_text));
+                                          _(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,
-                                          gettext(estate->err_text));
+                                          _(estate->err_text));
                }
        }
        else if (estate->err_stmt != NULL)
@@ -985,8 +983,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");
 
@@ -1035,10 +1031,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
@@ -1065,8 +1062,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
@@ -1109,6 +1104,11 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
                                        free_var(errm_var);
                                        errm_var->value = (Datum) 0;
                                        errm_var->isnull = true;
+
+                                       /* re-throw error if requested by handler */
+                                       if (rc == PLPGSQL_RC_RERAISE)
+                                               ReThrowError(edata);
+
                                        break;
                                }
                        }
@@ -1139,16 +1139,21 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
        switch (rc)
        {
                case PLPGSQL_RC_OK:
-               case PLPGSQL_RC_CONTINUE:
                case PLPGSQL_RC_RETURN:
+               case PLPGSQL_RC_CONTINUE:
+               case PLPGSQL_RC_RERAISE:
                        return rc;
 
                case PLPGSQL_RC_EXIT:
+                       /*
+                        * This is intentionally different from the handling of RC_EXIT
+                        * for loops: to match a block, we require a match by label.
+                        */
                        if (estate->exitlabel == NULL)
-                               return PLPGSQL_RC_OK;
+                               return PLPGSQL_RC_EXIT;
                        if (block->label == NULL)
                                return PLPGSQL_RC_EXIT;
-                       if (strcmp(block->label, estate->exitlabel))
+                       if (strcmp(block->label, estate->exitlabel) != 0)
                                return PLPGSQL_RC_EXIT;
                        estate->exitlabel = NULL;
                        return PLPGSQL_RC_OK;
@@ -1215,7 +1220,7 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
 
        CHECK_FOR_INTERRUPTS();
 
-       switch (stmt->cmd_type)
+       switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
        {
                case PLPGSQL_STMT_BLOCK:
                        rc = exec_stmt_block(estate, (PLpgSQL_stmt_block *) stmt);
@@ -1237,6 +1242,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
                        rc = exec_stmt_if(estate, (PLpgSQL_stmt_if *) stmt);
                        break;
 
+               case PLPGSQL_STMT_CASE:
+                       rc = exec_stmt_case(estate, (PLpgSQL_stmt_case *) stmt);
+                       break;
+
                case PLPGSQL_STMT_LOOP:
                        rc = exec_stmt_loop(estate, (PLpgSQL_stmt_loop *) stmt);
                        break;
@@ -1428,6 +1437,91 @@ exec_stmt_if(PLpgSQL_execstate *estate, PLpgSQL_stmt_if *stmt)
 }
 
 
+/*-----------
+ * exec_stmt_case
+ *-----------
+ */
+static int
+exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt)
+{
+       PLpgSQL_var *t_var = NULL;
+       bool            isnull;
+       ListCell   *l;
+
+       if (stmt->t_expr != NULL)
+       {
+               /* simple case */
+               Datum   t_val;
+               Oid             t_oid;
+
+               t_val = exec_eval_expr(estate, stmt->t_expr, &isnull, &t_oid);
+
+               t_var = (PLpgSQL_var *) estate->datums[stmt->t_varno];
+
+               /*
+                * When expected datatype is different from real, change it.
+                * Note that what we're modifying here is an execution copy
+                * of the datum, so this doesn't affect the originally stored
+                * function parse tree.
+                */
+               if (t_var->datatype->typoid != t_oid)
+                       t_var->datatype = plpgsql_build_datatype(t_oid, -1);
+
+               /* now we can assign to the variable */
+               exec_assign_value(estate,
+                                                 (PLpgSQL_datum *) t_var,
+                                                 t_val,
+                                                 t_oid,
+                                                 &isnull);
+
+               exec_eval_cleanup(estate);
+       }
+
+       /* Now search for a successful WHEN clause */
+       foreach(l, stmt->case_when_list)
+       {
+               PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+               bool    value;
+
+               value = exec_eval_boolean(estate, cwt->expr, &isnull);
+               exec_eval_cleanup(estate);
+               if (!isnull && value)
+               {
+                       /* Found it */
+
+                       /* We can now discard any value we had for the temp variable */
+                       if (t_var != NULL)
+                       {
+                               free_var(t_var);
+                               t_var->value = (Datum) 0;
+                               t_var->isnull = true;
+                       }
+
+                       /* Evaluate the statement(s), and we're done */
+                       return exec_stmts(estate, cwt->stmts);
+               }
+       }
+
+       /* We can now discard any value we had for the temp variable */
+       if (t_var != NULL)
+       {
+               free_var(t_var);
+               t_var->value = (Datum) 0;
+               t_var->isnull = true;
+       }
+
+       /* SQL2003 mandates this error if there was no ELSE clause */
+       if (!stmt->have_else)
+               ereport(ERROR,
+                               (errcode(ERRCODE_CASE_NOT_FOUND),
+                                errmsg("case not found"),
+                                errhint("CASE statement is missing ELSE part.")));
+
+       /* Evaluate the ELSE statements, and we're done */
+       return exec_stmts(estate, stmt->else_stmts);
+}
+
+
 /* ----------
  * exec_stmt_loop                      Loop over statements until
  *                                     an exit occurs.
@@ -1469,7 +1563,8 @@ exec_stmt_loop(PLpgSQL_execstate *estate, PLpgSQL_stmt_loop *stmt)
                                break;
 
                        case PLPGSQL_RC_RETURN:
-                               return PLPGSQL_RC_RETURN;
+                       case PLPGSQL_RC_RERAISE:
+                               return rc;
 
                        default:
                                elog(ERROR, "unrecognized rc: %d", rc);
@@ -1513,7 +1608,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;
@@ -1532,7 +1627,8 @@ exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
                                break;
 
                        case PLPGSQL_RC_RETURN:
-                               return PLPGSQL_RC_RETURN;
+                       case PLPGSQL_RC_RERAISE:
+                               return rc;
 
                        default:
                                elog(ERROR, "unrecognized rc: %d", rc);
@@ -1562,7 +1658,7 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
        bool            found = false;
        int                     rc = PLPGSQL_RC_OK;
 
-       var = (PLpgSQL_var *) (estate->datums[stmt->var->varno]);
+       var = (PLpgSQL_var *) (estate->datums[stmt->var->dno]);
 
        /*
         * Get the value of the lower bound
@@ -1575,7 +1671,7 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
        if (isnull)
                ereport(ERROR,
                                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                errmsg("lower bound of FOR loop cannot be NULL")));
+                                errmsg("lower bound of FOR loop cannot be null")));
        loop_value = DatumGetInt32(value);
        exec_eval_cleanup(estate);
 
@@ -1590,7 +1686,7 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
        if (isnull)
                ereport(ERROR,
                                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                errmsg("upper bound of FOR loop cannot be NULL")));
+                                errmsg("upper bound of FOR loop cannot be null")));
        end_value = DatumGetInt32(value);
        exec_eval_cleanup(estate);
 
@@ -1607,7 +1703,7 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
                if (isnull)
                        ereport(ERROR,
                                        (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                        errmsg("BY value of FOR loop cannot be NULL")));
+                                        errmsg("BY value of FOR loop cannot be null")));
                step_value = DatumGetInt32(value);
                exec_eval_cleanup(estate);
                if (step_value <= 0)
@@ -1650,8 +1746,9 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
                 */
                rc = exec_stmts(estate, stmt->body);
 
-               if (rc == PLPGSQL_RC_RETURN)
-                       break;                          /* return from function */
+               if (rc == PLPGSQL_RC_RETURN ||
+                       rc == PLPGSQL_RC_RERAISE)
+                       break;                          /* break out of the loop */
                else if (rc == PLPGSQL_RC_EXIT)
                {
                        if (estate->exitlabel == NULL)
@@ -2093,11 +2190,10 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
                                                  (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                                                   errmsg("record \"%s\" is not assigned yet",
                                                                  rec->refname),
-                                                  errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
-                                       if (!compatible_tupdesc(tupdesc, rec->tupdesc))
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                               errmsg("wrong record type supplied in RETURN NEXT")));
+                                                  errdetail("The tuple structure of a not-yet-assigned"
+                                                                        " record is indeterminate.")));
+                                       validate_tupdesc_compat(tupdesc, rec->tupdesc,
+                                                                   "wrong record type supplied in RETURN NEXT");
                                        tuple = rec->tup;
                                }
                                break;
@@ -2181,6 +2277,7 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
                                           PLpgSQL_stmt_return_query *stmt)
 {
        Portal          portal;
+       uint32                  processed = 0;
 
        if (!estate->retisset)
                ereport(ERROR,
@@ -2203,10 +2300,8 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
                                                                                   stmt->params);
        }
 
-       if (!compatible_tupdesc(estate->rettupdesc, portal->tupDesc))
-               ereport(ERROR,
-                               (errcode(ERRCODE_DATATYPE_MISMATCH),
-                 errmsg("structure of query does not match function result type")));
+       validate_tupdesc_compat(estate->rettupdesc, portal->tupDesc,
+                                                       "structure of query does not match function result type");
 
        while (true)
        {
@@ -2223,6 +2318,7 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
                        HeapTuple       tuple = SPI_tuptable->vals[i];
 
                        tuplestore_puttuple(estate->tuple_store, tuple);
+                       processed++;
                }
                MemoryContextSwitchTo(old_cxt);
 
@@ -2232,6 +2328,9 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
        SPI_freetuptable(SPI_tuptable);
        SPI_cursor_close(portal);
 
+       estate->eval_processed = processed;
+       exec_set_found(estate, processed != 0);
+
        return PLPGSQL_RC_OK;
 }
 
@@ -2254,7 +2353,9 @@ exec_init_tuple_store(PLpgSQL_execstate *estate)
        estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
 
        oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
-       estate->tuple_store = tuplestore_begin_heap(true, false, work_mem);
+       estate->tuple_store =
+               tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+                                                         false, work_mem);
        MemoryContextSwitchTo(oldcxt);
 
        estate->rettupdesc = rsi->expectedDesc;
@@ -2267,64 +2368,163 @@ exec_init_tuple_store(PLpgSQL_execstate *estate)
 static int
 exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
 {
-       char       *cp;
-       PLpgSQL_dstring ds;
-       ListCell   *current_param;
+       int                     err_code = 0;
+       char       *condname = NULL;
+       char       *err_message = NULL;
+       char       *err_detail = NULL;
+       char       *err_hint = NULL;
+       ListCell   *lc;
 
-       plpgsql_dstring_init(&ds);
-       current_param = list_head(stmt->params);
+       /* RAISE with no parameters: re-throw current exception */
+       if (stmt->condname == NULL && stmt->message == NULL &&
+               stmt->options == NIL)
+               return PLPGSQL_RC_RERAISE;
 
-       for (cp = stmt->message; *cp; cp++)
+       if (stmt->condname)
        {
-               /*
-                * Occurrences of a single % are replaced by the next parameter's
-                * external representation. Double %'s are converted to one %.
-                */
-               if (cp[0] == '%')
-               {
-                       Oid                     paramtypeid;
-                       Datum           paramvalue;
-                       bool            paramisnull;
-                       char       *extval;
+               err_code = plpgsql_recognize_err_condition(stmt->condname, true);
+               condname = pstrdup(stmt->condname);
+       }
+
+       if (stmt->message)
+       {
+               PLpgSQL_dstring ds;
+               ListCell   *current_param;
+               char       *cp;
+
+               plpgsql_dstring_init(&ds);
+               current_param = list_head(stmt->params);
 
-                       if (cp[1] == '%')
+               for (cp = stmt->message; *cp; cp++)
+               {
+                       /*
+                        * Occurrences of a single % are replaced by the next parameter's
+                        * external representation. Double %'s are converted to one %.
+                        */
+                       if (cp[0] == '%')
                        {
-                               plpgsql_dstring_append_char(&ds, cp[1]);
-                               cp++;
-                               continue;
-                       }
+                               Oid                     paramtypeid;
+                               Datum           paramvalue;
+                               bool            paramisnull;
+                               char       *extval;
 
-                       if (current_param == NULL)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                                errmsg("too few parameters specified for RAISE")));
+                               if (cp[1] == '%')
+                               {
+                                       plpgsql_dstring_append_char(&ds, cp[1]);
+                                       cp++;
+                                       continue;
+                               }
+
+                               if (current_param == NULL)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                        errmsg("too few parameters specified for RAISE")));
 
-                       paramvalue = exec_eval_expr(estate,
-                                                                         (PLpgSQL_expr *) lfirst(current_param),
-                                                                               &paramisnull,
-                                                                               &paramtypeid);
+                               paramvalue = exec_eval_expr(estate,
+                                                                                       (PLpgSQL_expr *) lfirst(current_param),
+                                                                                       &paramisnull,
+                                                                                       &paramtypeid);
 
-                       if (paramisnull)
-                               extval = "<NULL>";
+                               if (paramisnull)
+                                       extval = "<NULL>";
+                               else
+                                       extval = convert_value_to_string(paramvalue, paramtypeid);
+                               plpgsql_dstring_append(&ds, extval);
+                               current_param = lnext(current_param);
+                               exec_eval_cleanup(estate);
+                       }
                        else
-                               extval = convert_value_to_string(paramvalue, paramtypeid);
-                       plpgsql_dstring_append(&ds, extval);
-                       current_param = lnext(current_param);
-                       exec_eval_cleanup(estate);
-                       continue;
+                               plpgsql_dstring_append_char(&ds, cp[0]);
                }
 
-               plpgsql_dstring_append_char(&ds, cp[0]);
+               /*
+                * If more parameters were specified than were required to process the
+                * format string, throw an error
+                */
+               if (current_param != NULL)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("too many parameters specified for RAISE")));
+
+               err_message = plpgsql_dstring_get(&ds);
+               /* No dstring_free here, the pfree(err_message) does it */
        }
 
-       /*
-        * If more parameters were specified than were required to process the
-        * format string, throw an error
-        */
-       if (current_param != NULL)
-               ereport(ERROR,
-                               (errcode(ERRCODE_SYNTAX_ERROR),
-                                errmsg("too many parameters specified for RAISE")));
+       foreach(lc, stmt->options)
+       {
+               PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
+               Datum           optionvalue;
+               bool            optionisnull;
+               Oid                     optiontypeid;
+               char       *extval;
+
+               optionvalue = exec_eval_expr(estate, opt->expr,
+                                                                        &optionisnull,
+                                                                        &optiontypeid);
+               if (optionisnull)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                        errmsg("RAISE statement option cannot be null")));
+
+               extval = convert_value_to_string(optionvalue, optiontypeid);
+
+               switch (opt->opt_type)
+               {
+                       case PLPGSQL_RAISEOPTION_ERRCODE:
+                               if (err_code)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                        errmsg("RAISE option already specified: %s",
+                                                                       "ERRCODE")));
+                               err_code = plpgsql_recognize_err_condition(extval, true);
+                               condname = pstrdup(extval);
+                               break;
+                       case PLPGSQL_RAISEOPTION_MESSAGE:
+                               if (err_message)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                        errmsg("RAISE option already specified: %s",
+                                                                       "MESSAGE")));
+                               err_message = pstrdup(extval);
+                               break;
+                       case PLPGSQL_RAISEOPTION_DETAIL:
+                               if (err_detail)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                        errmsg("RAISE option already specified: %s",
+                                                                       "DETAIL")));
+                               err_detail = pstrdup(extval);
+                               break;
+                       case PLPGSQL_RAISEOPTION_HINT:
+                               if (err_hint)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                                        errmsg("RAISE option already specified: %s",
+                                                                       "HINT")));
+                               err_hint = pstrdup(extval);
+                               break;
+                       default:
+                               elog(ERROR, "unrecognized raise option: %d", opt->opt_type);
+               }
+
+               exec_eval_cleanup(estate);
+       }
+
+       /* Default code if nothing specified */
+       if (err_code == 0 && stmt->elog_level >= ERROR)
+               err_code = ERRCODE_RAISE_EXCEPTION;
+
+       /* Default error message if nothing specified */
+       if (err_message == NULL)
+       {
+               if (condname)
+               {
+                       err_message = condname;
+                       condname = NULL;
+               }
+               else
+                       err_message = pstrdup(unpack_sql_state(err_code));
+       }
 
        /*
         * Throw the error (may or may not come back)
@@ -2332,12 +2532,21 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
        estate->err_text = raise_skip_msg;      /* suppress traceback of raise */
 
        ereport(stmt->elog_level,
-                ((stmt->elog_level >= ERROR) ? errcode(ERRCODE_RAISE_EXCEPTION) : 0,
-                 errmsg_internal("%s", plpgsql_dstring_get(&ds))));
+                       (err_code ? errcode(err_code) : 0,
+                        errmsg_internal("%s", err_message),
+                        (err_detail != NULL) ? errdetail("%s", err_detail) : 0,
+                        (err_hint != NULL) ? errhint("%s", err_hint) : 0));
 
        estate->err_text = NULL;        /* un-suppress... */
 
-       plpgsql_dstring_free(&ds);
+       if (condname != NULL)
+               pfree(condname);
+       if (err_message != NULL)
+               pfree(err_message);
+       if (err_detail != NULL)
+               pfree(err_detail);
+       if (err_hint != NULL)
+               pfree(err_hint);
 
        return PLPGSQL_RC_OK;
 }
@@ -2593,6 +2802,16 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
                        Assert(!stmt->mod_stmt);
                        break;
 
+               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
+                        * return the row count either), so just set it to false.
+                        */
+                       exec_set_found(estate, false);
+                       break;
+
                default:
                        elog(ERROR, "SPI_execute_plan failed executing query \"%s\": %s",
                                 expr->query, SPI_result_code_string(rc));
@@ -2618,9 +2837,9 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 
                /* Determine if we assign to a record or a row */
                if (stmt->rec != NULL)
-                       rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
+                       rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->dno]);
                else if (stmt->row != NULL)
-                       row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
+                       row = (PLpgSQL_row *) (estate->datums[stmt->row->dno]);
                else
                        elog(ERROR, "unsupported target");
 
@@ -2691,7 +2910,7 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
        if (isnull)
                ereport(ERROR,
                                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                errmsg("cannot EXECUTE a null querystring")));
+                                errmsg("query string argument of EXECUTE is null")));
 
        /* Get the C-String representation */
        querystr = convert_value_to_string(query, restype);
@@ -2725,6 +2944,7 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
                case SPI_OK_UPDATE_RETURNING:
                case SPI_OK_DELETE_RETURNING:
                case SPI_OK_UTILITY:
+               case SPI_OK_REWRITTEN:
                        break;
 
                case 0:
@@ -2755,7 +2975,7 @@ 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 yet")));
+                                                        errmsg("EXECUTE of SELECT ... INTO is not implemented")));
                                break;
                        }
 
@@ -2796,9 +3016,9 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
 
                /* Determine if we assign to a record or a row */
                if (stmt->rec != NULL)
-                       rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
+                       rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->dno]);
                else if (stmt->row != NULL)
-                       row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
+                       row = (PLpgSQL_row *) (estate->datums[stmt->row->dno]);
                else
                        elog(ERROR, "unsupported target");
 
@@ -2940,7 +3160,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
                if (isnull)
                        ereport(ERROR,
                                        (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                        errmsg("cannot EXECUTE a null querystring")));
+                                        errmsg("query string argument of EXECUTE is null")));
 
                /* Get the C-String representation */
                querystr = convert_value_to_string(queryD, restype);
@@ -3064,7 +3284,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
@@ -3074,7 +3294,7 @@ exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
        if (curvar->isnull)
                ereport(ERROR,
                                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                errmsg("cursor variable \"%s\" is NULL", curvar->refname)));
+                                errmsg("cursor variable \"%s\" is null", curvar->refname)));
        curname = TextDatumGetCString(curvar->value);
 
        portal = SPI_cursor_find(curname);
@@ -3095,7 +3315,7 @@ exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
                if (isnull)
                        ereport(ERROR,
                                        (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                        errmsg("relative or absolute cursor position is NULL")));
+                                        errmsg("relative or absolute cursor position is null")));
 
                exec_eval_cleanup(estate);
        }
@@ -3107,9 +3327,9 @@ exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
                 * ----------
                 */
                if (stmt->rec != NULL)
-                       rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
+                       rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->dno]);
                else if (stmt->row != NULL)
-                       row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
+                       row = (PLpgSQL_row *) (estate->datums[stmt->row->dno]);
                else
                        elog(ERROR, "unsupported target");
 
@@ -3122,19 +3342,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);
        }
@@ -3143,11 +3357,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;
 }
 
@@ -3170,7 +3385,7 @@ exec_stmt_close(PLpgSQL_execstate *estate, PLpgSQL_stmt_close *stmt)
        if (curvar->isnull)
                ereport(ERROR,
                                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                errmsg("cursor variable \"%s\" is NULL", curvar->refname)));
+                                errmsg("cursor variable \"%s\" is null", curvar->refname)));
        curname = TextDatumGetCString(curvar->value);
 
        portal = SPI_cursor_find(curname);
@@ -3237,7 +3452,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                if (*isNull && var->notnull)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                                        errmsg("NULL cannot be assigned to variable \"%s\" declared NOT NULL",
+                                                        errmsg("null value cannot be assigned to variable \"%s\" declared NOT NULL",
                                                                        var->refname)));
 
                                /*
@@ -3365,9 +3580,9 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                int                     fno;
                                HeapTuple       newtup;
                                int                     natts;
-                               int                     i;
                                Datum      *values;
-                               char       *nulls;
+                               bool       *nulls;
+                               bool       *replaces;
                                void       *mustfree;
                                bool            attisnull;
                                Oid                     atttype;
@@ -3389,10 +3604,11 @@ exec_assign_value(PLpgSQL_execstate *estate,
 
                                /*
                                 * Get the number of the records field to change and the
-                                * number of attributes in the tuple.
+                                * number of attributes in the tuple.  Note: disallow
+                                * system column names because the code below won't cope.
                                 */
                                fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
-                               if (fno == SPI_ERROR_NOATTRIBUTE)
+                               if (fno <= 0)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_UNDEFINED_COLUMN),
                                                         errmsg("record \"%s\" has no field \"%s\"",
@@ -3401,24 +3617,16 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                natts = rec->tupdesc->natts;
 
                                /*
-                                * Set up values/datums arrays for heap_formtuple.      For all
+                                * Set up values/control arrays for heap_modify_tuple. For all
                                 * the attributes except the one we want to replace, use the
                                 * value that's in the old tuple.
                                 */
                                values = palloc(sizeof(Datum) * natts);
-                               nulls = palloc(natts);
+                               nulls = palloc(sizeof(bool) * natts);
+                               replaces = palloc(sizeof(bool) * natts);
 
-                               for (i = 0; i < natts; i++)
-                               {
-                                       if (i == fno)
-                                               continue;
-                                       values[i] = SPI_getbinval(rec->tup, rec->tupdesc,
-                                                                                         i + 1, &attisnull);
-                                       if (attisnull)
-                                               nulls[i] = 'n';
-                                       else
-                                               nulls[i] = ' ';
-                               }
+                               memset(replaces, false, sizeof(bool) * natts);
+                               replaces[fno] = true;
 
                                /*
                                 * Now insert the new value, being careful to cast it to the
@@ -3432,10 +3640,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                                                                                         atttype,
                                                                                                         atttypmod,
                                                                                                         attisnull);
-                               if (attisnull)
-                                       nulls[fno] = 'n';
-                               else
-                                       nulls[fno] = ' ';
+                               nulls[fno] = attisnull;
 
                                /*
                                 * Avoid leaking the result of exec_simple_cast_value, if it
@@ -3447,10 +3652,11 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                        mustfree = NULL;
 
                                /*
-                                * Now call heap_formtuple() to create a new tuple that
+                                * Now call heap_modify_tuple() to create a new tuple that
                                 * replaces the old one in the record.
                                 */
-                               newtup = heap_formtuple(rec->tupdesc, values, nulls);
+                               newtup = heap_modify_tuple(rec->tup, rec->tupdesc,
+                                                                                  values, nulls, replaces);
 
                                if (rec->freetup)
                                        heap_freetuple(rec->tup);
@@ -3460,6 +3666,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
 
                                pfree(values);
                                pfree(nulls);
+                               pfree(replaces);
                                if (mustfree)
                                        pfree(mustfree);
 
@@ -3502,8 +3709,8 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                        if (nsubscripts >= MAXDIM)
                                                ereport(ERROR,
                                                                (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-                                                                errmsg("number of array dimensions exceeds the maximum allowed, %d",
-                                                                               MAXDIM)));
+                                                                errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+                                                                               nsubscripts, MAXDIM)));
                                        subscripts[nsubscripts++] = arrayelem->subscript;
                                        target = estate->datums[arrayelem->arrayparentno];
                                } while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
@@ -3539,7 +3746,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                        if (subisnull)
                                                ereport(ERROR,
                                                                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                                                errmsg("array subscript in assignment must not be NULL")));
+                                                                errmsg("array subscript in assignment must not be null")));
                                }
 
                                /* Coerce source value to match array element type. */
@@ -3831,7 +4038,7 @@ exec_eval_expr(PLpgSQL_execstate *estate,
                           bool *isNull,
                           Oid *rettype)
 {
-       Datum           result;
+       Datum           result = 0;
        int                     rc;
 
        /*
@@ -3876,8 +4083,11 @@ exec_eval_expr(PLpgSQL_execstate *estate,
        if (estate->eval_tuptable->tupdesc->natts != 1)
                ereport(ERROR,
                                (errcode(ERRCODE_SYNTAX_ERROR),
-                                errmsg("query \"%s\" returned %d columns", expr->query,
-                                               estate->eval_tuptable->tupdesc->natts)));
+                                errmsg_plural("query \"%s\" returned %d column",
+                                                          "query \"%s\" returned %d columns",
+                                                          estate->eval_tuptable->tupdesc->natts,
+                                                          expr->query,
+                                                          estate->eval_tuptable->tupdesc->natts)));
 
        /*
         * Return the result and its type
@@ -3969,9 +4179,9 @@ exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
         * Determine if we assign to a record or a row
         */
        if (stmt->rec != NULL)
-               rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
+               rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->dno]);
        else if (stmt->row != NULL)
-               row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
+               row = (PLpgSQL_row *) (estate->datums[stmt->row->dno]);
        else
                elog(ERROR, "unsupported target");
 
@@ -4125,6 +4335,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
                                          Oid *rettype)
 {
        ExprContext *econtext = estate->eval_econtext;
+       LocalTransactionId curlxid = MyProc->lxid;
        CachedPlanSource *plansource;
        CachedPlan *cplan;
        ParamListInfo paramLI;
@@ -4165,14 +4376,14 @@ 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;
        }
 
        /*
@@ -4299,13 +4510,27 @@ exec_move_row(PLpgSQL_execstate *estate,
        if (rec != NULL)
        {
                /*
-                * copy input first, just in case it is pointing at variable's value
+                * Copy input first, just in case it is pointing at variable's value
                 */
                if (HeapTupleIsValid(tup))
                        tup = heap_copytuple(tup);
+               else if (tupdesc)
+               {
+                       /* If we have a tupdesc but no data, form an all-nulls tuple */
+                       bool       *nulls;
+
+                       nulls = (bool *) palloc(tupdesc->natts * sizeof(bool));
+                       memset(nulls, true, tupdesc->natts * sizeof(bool));
+
+                       tup = heap_form_tuple(tupdesc, NULL, nulls);
+
+                       pfree(nulls);
+               }
+
                if (tupdesc)
                        tupdesc = CreateTupleDescCopy(tupdesc);
 
+               /* Free the old value ... */
                if (rec->freetup)
                {
                        heap_freetuple(rec->tup);
@@ -4317,24 +4542,12 @@ exec_move_row(PLpgSQL_execstate *estate,
                        rec->freetupdesc = false;
                }
 
+               /* ... and install the new */
                if (HeapTupleIsValid(tup))
                {
                        rec->tup = tup;
                        rec->freetup = true;
                }
-               else if (tupdesc)
-               {
-                       /* If we have a tupdesc but no data, form an all-nulls tuple */
-                       char       *nulls;
-
-                       nulls = (char *) palloc(tupdesc->natts * sizeof(char));
-                       memset(nulls, 'n', tupdesc->natts * sizeof(char));
-
-                       rec->tup = heap_formtuple(tupdesc, NULL, nulls);
-                       rec->freetup = true;
-
-                       pfree(nulls);
-               }
                else
                        rec->tup = NULL;
 
@@ -4354,7 +4567,7 @@ 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
+        * 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
@@ -4366,6 +4579,7 @@ exec_move_row(PLpgSQL_execstate *estate,
         */
        if (row != NULL)
        {
+               int                     td_natts = tupdesc ? tupdesc->natts : 0;
                int                     t_natts;
                int                     fnum;
                int                     anum;
@@ -4388,12 +4602,18 @@ exec_move_row(PLpgSQL_execstate *estate,
 
                        var = (PLpgSQL_var *) (estate->datums[row->varnos[fnum]]);
 
-                       while (anum < t_natts && tupdesc->attrs[anum]->attisdropped)
+                       while (anum < td_natts && tupdesc->attrs[anum]->attisdropped)
                                anum++;                 /* skip dropped column in tuple */
 
-                       if (anum < t_natts)
+                       if (anum < td_natts)
                        {
-                               value = SPI_getbinval(tup, tupdesc, anum + 1, &isnull);
+                               if (anum < t_natts)
+                                       value = SPI_getbinval(tup, tupdesc, anum + 1, &isnull);
+                               else
+                               {
+                                       value = (Datum) 0;
+                                       isnull = true;
+                               }
                                valtype = SPI_gettypeid(tupdesc, anum + 1);
                                anum++;
                        }
@@ -4475,27 +4695,11 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
 static char *
 convert_value_to_string(Datum value, Oid valtype)
 {
-       char       *str;
        Oid                     typoutput;
        bool            typIsVarlena;
 
        getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
-
-       /*
-        * We do SPI_push to allow the datatype output function to use SPI.
-        * However we do not mess around with CommandCounterIncrement or advancing
-        * the snapshot, which means that a stable output function would not see
-        * updates made so far by our own function.  The use-case for such
-        * scenarios seems too narrow to justify the cycles that would be
-        * expended.
-        */
-       SPI_push();
-
-       str = OidOutputFunctionCall(typoutput, value);
-
-       SPI_pop();
-
-       return str;
+       return OidOutputFunctionCall(typoutput, value);
 }
 
 /* ----------
@@ -4521,25 +4725,14 @@ exec_cast_value(Datum value, Oid valtype,
                        char       *extval;
 
                        extval = convert_value_to_string(value, valtype);
-
-                       /* Allow input function to use SPI ... see notes above */
-                       SPI_push();
-
                        value = InputFunctionCall(reqinput, extval,
                                                                          reqtypioparam, reqtypmod);
-
-                       SPI_pop();
-
                        pfree(extval);
                }
                else
                {
-                       SPI_push();
-
                        value = InputFunctionCall(reqinput, NULL,
                                                                          reqtypioparam, reqtypmod);
-
-                       SPI_pop();
                }
        }
 
@@ -4559,26 +4752,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;
@@ -4916,29 +5106,48 @@ 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);
 }
 
 /*
- * Check two tupledescs have matching number and types of attributes
+ * Validates compatibility of supplied TupleDesc pair by checking number and type
+ * of attributes.
  */
-static bool
-compatible_tupdesc(TupleDesc td1, TupleDesc td2)
+static void
+validate_tupdesc_compat(TupleDesc expected, TupleDesc returned, const char *msg)
 {
-       int                     i;
+       int                i;
+       const char *dropped_column_type = gettext_noop("N/A (dropped column)");
 
-       if (td1->natts != td2->natts)
-               return false;
+       if (!expected || !returned)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("%s", _(msg))));
 
-       for (i = 0; i < td1->natts; i++)
-       {
-               if (td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)
-                       return false;
-       }
+       if (expected->natts != returned->natts)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("%s", _(msg)),
+                                errdetail("Number of returned columns (%d) does not match "
+                                                  "expected column count (%d).",
+                                                  returned->natts, expected->natts)));
 
-       return true;
+       for (i = 0; i < expected->natts; i++)
+               if (expected->attrs[i]->atttypid != returned->attrs[i]->atttypid)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                        errmsg("%s", _(msg)),
+                                        errdetail("Returned type %s does not match expected type "
+                                                          "%s in column \"%s\".",
+                                                          OidIsValid(returned->attrs[i]->atttypid) ?
+                                                          format_type_be(returned->attrs[i]->atttypid) :
+                                                          _(dropped_column_type),
+                                                          OidIsValid(expected->attrs[i]->atttypid) ?
+                                                          format_type_be(expected->attrs[i]->atttypid) :
+                                                          _(dropped_column_type),
+                                                          NameStr(expected->attrs[i]->attname))));
 }
 
 /* ----------
@@ -4959,46 +5168,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);
+       }
+
+       /*
+        * Create a child econtext for the current function.
+        */
+       estate->eval_econtext = CreateExprContext(simple_eval_estate);
 
-               /* Assign a reasonably-unique ID to this EState */
-               entry->xact_estate_simple_id = simple_estate_id_counter++;
-               entry->xact_subxid = my_subxid;
+       /*
+        * Make a stack entry so we can clean up the econtext at subxact end.
+        * Stack entries are kept in TopTransactionContext for simplicity.
+        */
+       entry = (SimpleEcontextStackEntry *)
+               MemoryContextAlloc(TopTransactionContext,
+                                                  sizeof(SimpleEcontextStackEntry));
 
-               entry->next = simple_estate_stack;
-               simple_estate_stack = entry;
-       }
+       entry->stack_econtext = estate->eval_econtext;
+       entry->xact_subxid = GetCurrentSubTransactionId();
+
+       entry->next = simple_econtext_stack;
+       simple_econtext_stack = entry;
+}
 
-       /* Link plpgsql estate to it */
-       estate->eval_estate = entry->xact_eval_estate;
-       estate->eval_estate_simple_id = entry->xact_estate_simple_id;
+/*
+ * 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;
 
-       /* And create a child econtext for the current function */
-       estate->eval_econtext = CreateExprContext(estate->eval_estate);
+       FreeExprContext(estate->eval_econtext);
+       estate->eval_econtext = NULL;
 }
 
 /*
@@ -5014,29 +5246,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,
@@ -5045,16 +5279,15 @@ 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);
+               next = simple_econtext_stack->next;
+               pfree(simple_econtext_stack);
+               simple_econtext_stack = next;
        }
 }
 
@@ -5183,7 +5416,7 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate, PLpgSQL_expr *dynquery,
        if (isnull)
                ereport(ERROR,
                                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                errmsg("cannot EXECUTE a null querystring")));
+                                errmsg("query string argument of EXECUTE is null")));
 
        /* Get the C-String representation */
        querystr = convert_value_to_string(query, restype);