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