-/**********************************************************************
+/*-------------------------------------------------------------------------
+ *
* pl_exec.c - Executor for the PL/pgSQL
* procedural language
*
- * IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.149 2005/06/26 22:05:42 tgl Exp $
- *
- * This software is copyrighted by Jan Wieck - Hamburg.
- *
- * The author hereby grants permission to use, copy, modify,
- * distribute, and license this software and its documentation
- * for any purpose, provided that existing copyright notices are
- * retained in all copies and that this notice is included
- * verbatim in any distributions. No written agreement, license,
- * or royalty fee is required for any of the authorized uses.
- * Modifications to this software may be copyrighted by their
- * author and need not follow the licensing terms described
- * here, provided that the new terms are clearly indicated on
- * the first page of each file where they apply.
+ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
*
- * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY
- * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
- * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS
- * SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN
- * IF THE AUTHOR HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
- * DAMAGE.
*
- * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
- * PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON
- * AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAVE NO
- * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
- * ENHANCEMENTS, OR MODIFICATIONS.
+ * IDENTIFICATION
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.242 2009/06/04 18:33:07 tgl Exp $
*
- **********************************************************************/
+ *-------------------------------------------------------------------------
+ */
#include "plpgsql.h"
-#include "pl.tab.h"
#include <ctype.h>
-#include "access/heapam.h"
+#include "access/transam.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "executor/spi_priv.h"
#include "funcapi.h"
-#include "optimizer/clauses.h"
-#include "parser/parse_expr.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/scansup.h"
+#include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/array.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/snapmgr.h"
#include "utils/typcache.h"
static const char *const raise_skip_msg = "RAISE";
+typedef struct
+{
+ int nargs; /* number of arguments */
+ Oid *types; /* types of arguments */
+ Datum *values; /* evaluated argument values */
+ char *nulls; /* null markers (' '/'n' style) */
+ bool *freevals; /* which arguments are pfree-able */
+} PreparedParamsData;
/*
- * All plpgsql function executions within a single transaction share
- * the same executor EState for evaluating "simple" expressions. Each
- * function call creates its own "eval_econtext" ExprContext within this
- * estate. We destroy the estate at transaction shutdown to ensure there
- * is no permanent leakage of memory (especially for xact abort case).
+ * All plpgsql function executions within a single transaction share the same
+ * executor EState for evaluating "simple" expressions. Each function call
+ * creates its own "eval_econtext" ExprContext within this estate for
+ * per-evaluation workspace. eval_econtext is freed at normal function exit,
+ * and the EState is freed at transaction end (in case of error, we assume
+ * that the abort mechanisms clean it all up). Furthermore, any exception
+ * block within a function has to have its own eval_econtext separate from
+ * the containing function's, so that we can clean up ExprContext callbacks
+ * properly at subtransaction exit. We maintain a stack that tracks the
+ * individual econtexts so that we can clean up correctly at subxact exit.
*
- * If a simple PLpgSQL_expr has been used in the current xact, it is
- * linked into the active_simple_exprs list.
+ * This arrangement is a bit tedious to maintain, but it's worth the trouble
+ * so that we don't have to re-prepare simple expressions on each trip through
+ * a function. (We assume the case to optimize is many repetitions of a
+ * function within a transaction.)
*/
+typedef struct SimpleEcontextStackEntry
+{
+ ExprContext *stack_econtext; /* a stacked econtext */
+ SubTransactionId xact_subxid; /* ID for current subxact */
+ struct SimpleEcontextStackEntry *next; /* next stack entry up */
+} SimpleEcontextStackEntry;
+
static EState *simple_eval_estate = NULL;
-static PLpgSQL_expr *active_simple_exprs = NULL;
+static SimpleEcontextStackEntry *simple_econtext_stack = NULL;
/************************************************************
* Local function forward declarations
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,
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,
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,
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,
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);
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,
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);
/* ----------
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = td;
exec_move_row(&estate, NULL, row, &tmptup, tupdesc);
+ ReleaseTupleDesc(tupdesc);
}
else
{
}
}
+ 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;
/*
- * Provide a more helpful message if a CONTINUE has been used
- * outside a loop.
+ * Provide a more helpful message if a CONTINUE or RAISE has been used
+ * outside the context it can work in.
*/
if (rc == PLPGSQL_RC_CONTINUE)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CONTINUE cannot be used outside a loop")));
+ else if (rc == PLPGSQL_RC_RERAISE)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE without parameters cannot be used outside an exception handler")));
else
ereport(ERROR,
- (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
- errmsg("control reached end of function without RETURN")));
+ (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
+ errmsg("control reached end of function without RETURN")));
}
/*
{
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
{
&(func->fn_retinput),
func->fn_rettypioparam,
-1,
- &fcinfo->isnull);
+ fcinfo->isnull);
/*
* If the function's return type isn't by value, copy the value
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);
/*
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;
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;
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;
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;
{
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;
/*
- * Provide a more helpful message if a CONTINUE has been used
- * outside a loop.
+ * Provide a more helpful message if a CONTINUE or RAISE has been used
+ * outside the context it can work in.
*/
if (rc == PLPGSQL_RC_CONTINUE)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CONTINUE cannot be used outside a loop")));
+ else if (rc == PLPGSQL_RC_RERAISE)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE without parameters cannot be used outside an exception handler")));
else
ereport(ERROR,
- (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
- errmsg("control reached end of trigger procedure without RETURN")));
+ (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);
/*
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",
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);
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;
/*
* 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];
{
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)
+ /*
+ * 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)
{
- 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
- {
- 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;
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 */
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);
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;
}
}
/*
* 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:
- case PLPGSQL_RC_CONTINUE:
case PLPGSQL_RC_RETURN:
+ case PLPGSQL_RC_CONTINUE:
+ case PLPGSQL_RC_RERAISE:
return rc;
case PLPGSQL_RC_EXIT:
+ /*
+ * This is intentionally different from the handling of RC_EXIT
+ * for loops: to match a block, we require a match by label.
+ */
if (estate->exitlabel == NULL)
- return PLPGSQL_RC_OK;
+ return PLPGSQL_RC_EXIT;
if (block->label == NULL)
return PLPGSQL_RC_EXIT;
- if (strcmp(block->label, estate->exitlabel))
+ if (strcmp(block->label, estate->exitlabel) != 0)
return PLPGSQL_RC_EXIT;
estate->exitlabel = NULL;
return PLPGSQL_RC_OK;
-
+
default:
elog(ERROR, "unrecognized rc: %d", rc);
}
{
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;
}
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);
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;
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:
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;
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;
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;
+ PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc);
+ PLpgSQL_datum *var;
+ bool isnull = false;
if (diag_item->target <= 0)
continue;
}
+/*-----------
+ * 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.
{
for (;;)
{
- int rc = exec_stmts(estate, stmt->body);
+ int rc = exec_stmts(estate, stmt->body);
switch (rc)
{
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;
case PLPGSQL_RC_RETURN:
- return PLPGSQL_RC_RETURN;
+ case PLPGSQL_RC_RERAISE:
+ return rc;
default:
elog(ERROR, "unrecognized rc: %d", rc);
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;
break;
case PLPGSQL_RC_RETURN:
- return PLPGSQL_RC_RETURN;
+ case PLPGSQL_RC_RERAISE:
+ return rc;
default:
elog(ERROR, "unrecognized rc: %d", rc);
/* ----------
* 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
{
PLpgSQL_var *var;
Datum value;
- Oid valtype;
bool isnull;
+ Oid valtype;
+ 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);
/*
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)
}
/*
- * 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.
+ * 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)
- /* anonymous continue, so re-run the current loop */
+ /* unlabelled continue, so re-run the current loop */
rc = PLPGSQL_RC_OK;
else if (stmt->label != NULL &&
strcmp(stmt->label, estate->exitlabel) == 0)
}
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.
+ /*
+ * 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;
+ 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);
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;
+ int rc;
/*
- * Determine if we assign to a record or a row
+ * Open the implicit cursor for the statement using exec_run_select
*/
- 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");
+ exec_run_select(estate, stmt->query, 0, &portal);
/*
- * Open the implicit cursor for the statement and fetch the initial 10
- * rows.
+ * Execute the loop
*/
- exec_run_select(estate, stmt->query, 0, &portal);
-
- SPI_cursor_fetch(portal, true, 10);
- tuptab = SPI_tuptable;
- n = SPI_processed;
+ rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, true);
/*
- * If the query didn't return any rows, set the target to NULL and
- * return with FOUND = false.
+ * Close the implicit cursor
*/
- if (n == 0)
- exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
- else
- found = true; /* processed at least one tuple */
+ SPI_cursor_close(portal);
- /*
- * Now do the loop
+ return rc;
+}
+
+
+/* ----------
+ * exec_stmt_forc Execute a loop for each row from a cursor.
+ * ----------
+ */
+static int
+exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
+{
+ PLpgSQL_var *curvar;
+ char *curname = NULL;
+ PLpgSQL_expr *query;
+ Portal portal;
+ int rc;
+ Datum *values;
+ char *nulls;
+
+ /* ----------
+ * Get the cursor variable and if it has an assigned name, check
+ * that it's not in use currently.
+ * ----------
*/
- while (n > 0)
+ curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
+ if (!curvar->isnull)
{
- 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, 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.
- */
- }
- else if (rc == PLPGSQL_RC_CONTINUE)
- {
- if (estate->exitlabel == NULL)
- {
- /* anonymous 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 named continue, so re-run loop */
- rc = PLPGSQL_RC_OK;
- estate->exitlabel = NULL;
- continue;
- }
-
- /*
- * otherwise, we processed 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.
- */
- }
-
- /*
- * 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);
-
- return rc;
- }
- }
-
- SPI_freetuptable(tuptab);
+ curname = TextDatumGetCString(curvar->value);
+ if (SPI_cursor_find(curname) != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_CURSOR),
+ errmsg("cursor \"%s\" already in use", curname)));
+ }
- /*
- * Fetch the next 50 tuples
+ /* ----------
+ * 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->argquery != NULL)
+ {
+ /* ----------
+ * OPEN CURSOR with args. We fake a SELECT ... INTO ...
+ * statement to evaluate the args and put 'em into the
+ * internal row.
+ * ----------
*/
- SPI_cursor_fetch(portal, true, 50);
- n = SPI_processed;
- tuptab = SPI_tuptable;
+ 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
+ {
+ if (curvar->cursor_explicit_argrow >= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("arguments required for cursor")));
}
- /*
- * Release last group of tuples
- */
- SPI_freetuptable(tuptab);
+ query = curvar->cursor_explicit_expr;
+ Assert(query);
- /*
- * Close the implicit cursor
- */
- SPI_cursor_close(portal);
+ if (query->plan == NULL)
+ exec_prepare_plan(estate, query, curvar->cursor_options);
/*
- * 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.
+ * Now build up the values and nulls arguments for SPI_execute_plan()
*/
- exec_set_found(estate, found);
-
- return rc;
-}
-
-
-/* ----------
- * exec_stmt_select Run a query and assign the first
- * row to a record or rowtype.
- * ----------
- */
-static int
-exec_stmt_select(PLpgSQL_execstate *estate, PLpgSQL_stmt_select *stmt)
-{
- PLpgSQL_rec *rec = NULL;
- PLpgSQL_row *row = NULL;
- SPITupleTable *tuptab;
- uint32 n;
+ eval_expr_params(estate, query, &values, &nulls);
/*
- * Initialize the global found variable to false
+ * Open the cursor
*/
- exec_set_found(estate, false);
+ 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));
/*
- * Determine if we assign to a record or a row
+ * If cursor variable was NULL, store the generated portal name in it
*/
- 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");
+ if (curname == NULL)
+ assign_text_var(curvar, portal->name);
/*
- * Run the query
+ * 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.
*/
- exec_run_select(estate, stmt->query, 1, NULL);
- tuptab = estate->eval_tuptable;
- n = estate->eval_processed;
+ rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, false);
- /*
- * If the query didn't return any rows, set the target to NULL and
- * return.
+ /* ----------
+ * Close portal, and restore cursor variable if it was initially NULL.
+ * ----------
*/
- if (n == 0)
+ SPI_cursor_close(portal);
+
+ if (curname == NULL)
{
- exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
- exec_eval_cleanup(estate);
- return PLPGSQL_RC_OK;
+ free_var(curvar);
+ curvar->value = (Datum) 0;
+ curvar->isnull = true;
}
- /*
- * Put the result into the target and set found to true
- */
- exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
- exec_set_found(estate, true);
-
- exec_eval_cleanup(estate);
+ pfree(values);
+ pfree(nulls);
+ if (curname)
+ pfree(curname);
- return PLPGSQL_RC_OK;
+ return rc;
}
{
/*
* 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;
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);
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;
}
/*
* 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)
{
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);
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);
+ isNull);
- tuple = heap_form_tuple(tupdesc, &retval, &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;
}
}
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,
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);
}
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);
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)
{
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;
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;
+ }
+
+ if (current_param == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("too few parameters specified for RAISE")));
- paramvalue = exec_eval_expr(estate,
- (PLpgSQL_expr *) lfirst(current_param),
- ¶misnull,
- ¶mtypeid);
+ paramvalue = exec_eval_expr(estate,
+ (PLpgSQL_expr *) lfirst(current_param),
+ ¶misnull,
+ ¶mtypeid);
- if (paramisnull)
- extval = "<NULL>";
+ if (paramisnull)
+ extval = "<NULL>";
+ else
+ extval = convert_value_to_string(paramvalue, paramtypeid);
+ plpgsql_dstring_append(&ds, extval);
+ current_param = lnext(current_param);
+ exec_eval_cleanup(estate);
+ }
else
- extval = convert_value_to_string(paramvalue, paramtypeid);
- plpgsql_dstring_append(&ds, extval);
- current_param = lnext(current_param);
- exec_eval_cleanup(estate);
- continue;
+ plpgsql_dstring_append_char(&ds, cp[0]);
}
- plpgsql_dstring_append_char(&ds, cp[0]);
+ /*
+ * If more parameters were specified than were required to process the
+ * format string, throw an error
+ */
+ if (current_param != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("too many parameters specified for RAISE")));
+
+ err_message = plpgsql_dstring_get(&ds);
+ /* No dstring_free here, the pfree(err_message) does it */
}
- /*
- * If more parameters were specified than were required to process
- * the format string, throw an error
- */
- if (current_param != NULL)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("too many parameters specified for RAISE")));
+ foreach(lc, stmt->options)
+ {
+ PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
+ Datum optionvalue;
+ bool optionisnull;
+ Oid optiontypeid;
+ char *extval;
+
+ optionvalue = exec_eval_expr(estate, opt->expr,
+ &optionisnull,
+ &optiontypeid);
+ if (optionisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("RAISE statement option cannot be null")));
+
+ extval = convert_value_to_string(optionvalue, optiontypeid);
+
+ switch (opt->opt_type)
+ {
+ case PLPGSQL_RAISEOPTION_ERRCODE:
+ if (err_code)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "ERRCODE")));
+ err_code = plpgsql_recognize_err_condition(extval, true);
+ condname = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_MESSAGE:
+ if (err_message)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "MESSAGE")));
+ err_message = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_DETAIL:
+ if (err_detail)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "DETAIL")));
+ err_detail = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_HINT:
+ if (err_hint)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "HINT")));
+ err_hint = pstrdup(extval);
+ break;
+ default:
+ elog(ERROR, "unrecognized raise option: %d", opt->opt_type);
+ }
+
+ exec_eval_cleanup(estate);
+ }
+
+ /* Default code if nothing specified */
+ if (err_code == 0 && stmt->elog_level >= ERROR)
+ err_code = ERRCODE_RAISE_EXCEPTION;
+
+ /* Default error message if nothing specified */
+ if (err_message == NULL)
+ {
+ if (condname)
+ {
+ err_message = condname;
+ condname = NULL;
+ }
+ else
+ err_message = pstrdup(unpack_sql_state(err_code));
+ }
/*
* Throw the error (may or may not come back)
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;
}
estate->err_text = NULL;
/*
- * 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.
+ * Create an EState and ExprContext for evaluation of simple expressions.
*/
- if (simple_eval_estate == NULL)
- {
- MemoryContext oldcontext;
-
- oldcontext = MemoryContextSwitchTo(TopTransactionContext);
- simple_eval_estate = CreateExecutorState();
- MemoryContextSwitchTo(oldcontext);
- }
+ plpgsql_create_econtext(estate);
/*
- * Create an expression context for simple expressions.
- * This must be a child of simple_eval_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
*/
- estate->eval_econtext = CreateExprContext(simple_eval_estate);
+ 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);
+ }
}
/* ----------
*/
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;
/*
/*
* 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 */
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++)
- {
- 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 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)
+ {
+ 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);
/* ----------
- * 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
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);
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)
{
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:
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;
}
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",
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;
}
static int
exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
{
- Datum query;
- bool isnull;
- Oid restype;
- char *querystr;
- PLpgSQL_rec *rec = NULL;
- PLpgSQL_row *row = NULL;
- SPITupleTable *tuptab;
- 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)
- {
- int i;
-
- for (i = 0; i < n; i++)
- {
- int rc;
-
- /*
- * 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, 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.
- */
- }
- else if (rc == PLPGSQL_RC_CONTINUE)
- {
- if (estate->exitlabel == NULL)
- /* unlabelled continue, continue the current loop */
- continue;
- else if (stmt->label != NULL &&
- strcmp(stmt->label, estate->exitlabel) == 0)
- {
- /* labelled continue, matches the current stmt's label */
- estate->exitlabel = NULL;
- continue;
- }
-
- /*
- * otherwise, we process a labelled continue that
- * does not match the current statement's label,
- * so propagate RC_CONTINUE upward in the stack.
- */
- }
-
- /*
- * 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);
-
- 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;
}
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.
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),
*/
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)
{
Datum queryD;
Oid restype;
char *querystr;
- void *curplan;
+ SPIPlanPtr curplan;
/* ----------
* We evaluate the string expression after the
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);
* 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);
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;
}
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
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("arguments required for cursor")));
- }
-
- query = curvar->cursor_explicit_expr;
- if (query->plan == NULL)
- exec_prepare_plan(estate, query);
- }
-
- /* ----------
- * 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 ...;
- * ----------
- */
- 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] = ' ';
+ }
+
+ query = curvar->cursor_explicit_expr;
+ if (query->plan == NULL)
+ exec_prepare_plan(estate, query, curvar->cursor_options);
}
- /* ----------
+ /*
+ * Now build up the values and nulls arguments for SPI_execute_plan()
+ */
+ eval_expr_params(estate, query, &values, &nulls);
+
+ /*
* Open the cursor
- * ----------
*/
portal = SPI_cursor_open(curname, query->plan, values, nulls,
estate->readonly_func);
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;
-
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
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
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)
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
* ----------
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)
&(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)
{
}
/*
- * 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);
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")));
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = td;
exec_move_row(estate, NULL, row, &tmptup, tupdesc);
+ ReleaseTupleDesc(tupdesc);
}
break;
}
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")));
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = td;
exec_move_row(estate, rec, NULL, &tmptup, tupdesc);
+ ReleaseTupleDesc(tupdesc);
}
break;
}
int fno;
HeapTuple newtup;
int natts;
- int i;
Datum *values;
- char *nulls;
+ bool *nulls;
+ bool *replaces;
void *mustfree;
bool attisnull;
Oid atttype;
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\"",
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;
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]);
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);
pfree(values);
pfree(nulls);
+ pfree(replaces);
if (mustfree)
pfree(mustfree);
int i;
PLpgSQL_expr *subscripts[MAXDIM];
int subscriptvals[MAXDIM];
- bool havenullsubscript,
- oldarrayisnull;
+ bool oldarrayisnull;
Oid arraytypeid,
arrayelemtypeid;
int16 arraytyplen,
* 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
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,
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;
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.
*/
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.
*
* 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,
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);
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,
exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid);
exprdatum = exec_simple_cast_value(exprdatum, exprtypeid,
INT4OID, -1,
- isNull);
+ *isNull);
return DatumGetInt32(exprdatum);
}
exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid);
exprdatum = exec_simple_cast_value(exprdatum, exprtypeid,
BOOLOID, -1,
- isNull);
+ *isNull);
return DatumGetBool(exprdatum);
}
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.
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
exec_run_select(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, long maxtuples, Portal *portalP)
{
- int i;
Datum *values;
char *nulls;
int rc;
* 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
}
+/*
+ * 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 *econtext = estate->eval_econtext;
+ LocalTransactionId curlxid = MyProc->lxid;
+ CachedPlanSource *plansource;
+ CachedPlan *cplan;
ParamListInfo paramLI;
int i;
- Snapshot saveActiveSnapshot;
+ MemoryContext oldcontext;
+
+ /*
+ * Forget it if expression wasn't simple before.
+ */
+ if (expr->expr_simple_expr == NULL)
+ return false;
+
+ /*
+ * 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.
+ */
+ 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)
+ {
+ /* 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;
+ }
+ }
/*
* Pass back previously-determined result type.
*rettype = expr->expr_simple_type;
/*
- * Prepare the expression for execution, if it's not been done already
- * in the current transaction.
+ * 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.)
*/
- if (expr->expr_simple_state == NULL)
+ if (expr->expr_simple_lxid != curlxid)
{
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;
+ 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.
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();
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ if (!estate->readonly_func)
{
- MemoryContext oldcontext;
+ CommandCounterIncrement();
+ PushActiveSnapshot(GetTransactionSnapshot());
+ }
- oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
- if (!estate->readonly_func)
- {
- CommandCounterIncrement();
- ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
- }
+ /*
+ * Finally we can call the executor to evaluate the expression
+ */
+ *result = ExecEvalExpr(expr->expr_simple_state,
+ econtext,
+ isNull,
+ NULL);
+ MemoryContextSwitchTo(oldcontext);
- /*
- * Finally we can call the executor to evaluate the expression
- */
- retval = ExecEvalExpr(expr->expr_simple_state,
- econtext,
- isNull,
- NULL);
- MemoryContextSwitchTo(oldcontext);
- }
- PG_CATCH();
- {
- /* Restore global vars and propagate error */
- ActiveSnapshot = saveActiveSnapshot;
- PG_RE_THROW();
- }
- PG_END_TRY();
+ if (!estate->readonly_func)
+ PopActiveSnapshot();
- ActiveSnapshot = saveActiveSnapshot;
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] = ' ';
+ }
}
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);
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;
* 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;
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++;
}
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);
*
* 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 *
bool typIsVarlena;
getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
-
- return DatumGetCString(OidFunctionCall1(typoutput, value));
+ return OidOutputFunctionCall(typoutput, value);
}
/* ----------
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;
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;
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);
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;
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;
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;
/*
* 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))));
}
/* ----------
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)
{
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;
+}