* pl_exec.c - Executor for the PL/pgSQL
* procedural language
*
- * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.235 2009/02/23 10:03:22 petere Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.261 2010/07/06 19:19:01 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include <ctype.h>
#include "access/transam.h"
+#include "access/tupconvert.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "executor/spi_priv.h"
#include "funcapi.h"
+#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "parser/scansup.h"
+#include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/array.h"
#include "utils/builtins.h"
* creates its own "eval_econtext" ExprContext within this estate for
* per-evaluation workspace. eval_econtext is freed at normal function exit,
* and the EState is freed at transaction end (in case of error, we assume
- * that the abort mechanisms clean it all up). In order to be sure
- * ExprContext callbacks are handled properly, each subtransaction has to have
- * its own such EState; hence we need a stack. We use a simple counter to
- * distinguish different instantiations of the EState, so that we can tell
- * whether we have a current copy of a prepared expression.
+ * that the abort mechanisms clean it all up). Furthermore, any exception
+ * block within a function has to have its own eval_econtext separate from
+ * the containing function's, so that we can clean up ExprContext callbacks
+ * properly at subtransaction exit. We maintain a stack that tracks the
+ * individual econtexts so that we can clean up correctly at subxact exit.
*
* This arrangement is a bit tedious to maintain, but it's worth the trouble
* so that we don't have to re-prepare simple expressions on each trip through
* a function. (We assume the case to optimize is many repetitions of a
* function within a transaction.)
*/
-typedef struct SimpleEstateStackEntry
+typedef struct SimpleEcontextStackEntry
{
- EState *xact_eval_estate; /* EState for current xact level */
- long int xact_estate_simple_id; /* ID for xact_eval_estate */
+ ExprContext *stack_econtext; /* a stacked econtext */
SubTransactionId xact_subxid; /* ID for current subxact */
- struct SimpleEstateStackEntry *next; /* next stack entry up */
-} SimpleEstateStackEntry;
+ struct SimpleEcontextStackEntry *next; /* next stack entry up */
+} SimpleEcontextStackEntry;
-static SimpleEstateStackEntry *simple_estate_stack = NULL;
-static long int simple_estate_id_counter = 0;
+static EState *simple_eval_estate = NULL;
+static SimpleEcontextStackEntry *simple_econtext_stack = NULL;
/************************************************************
* Local function forward declarations
static int exec_stmt_if(PLpgSQL_execstate *estate,
PLpgSQL_stmt_if *stmt);
static int exec_stmt_case(PLpgSQL_execstate *estate,
- PLpgSQL_stmt_case *stmt);
+ PLpgSQL_stmt_case *stmt);
static int exec_stmt_loop(PLpgSQL_execstate *estate,
PLpgSQL_stmt_loop *stmt);
static int exec_stmt_while(PLpgSQL_execstate *estate,
Datum value, Oid valtype, bool *isNull);
static void exec_eval_datum(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum,
- Oid expectedtypeid,
Oid *typeid,
Datum *value,
bool *isnull);
static int exec_run_select(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, long maxtuples, Portal *portalP);
static int exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
- Portal portal, bool prefetch_ok);
-static void eval_expr_params(PLpgSQL_execstate *estate,
- PLpgSQL_expr *expr, Datum **p_values, char **p_nulls);
+ Portal portal, bool prefetch_ok);
+static ParamListInfo setup_param_list(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr);
+static void plpgsql_param_fetch(ParamListInfo params, int paramid);
static void exec_move_row(PLpgSQL_execstate *estate,
PLpgSQL_rec *rec,
PLpgSQL_row *row,
Oid reqtype, int32 reqtypmod,
bool isnull);
static void exec_init_tuple_store(PLpgSQL_execstate *estate);
-static void validate_tupdesc_compat(TupleDesc expected, TupleDesc returned,
- const char *msg);
static void exec_set_found(PLpgSQL_execstate *estate, bool state);
static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
+static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate);
static void free_var(PLpgSQL_var *var);
static void assign_text_var(PLpgSQL_var *var, const char *str);
static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate,
- List *params);
+ List *params);
static void free_params_data(PreparedParamsData *ppd);
static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
- PLpgSQL_expr *query, List *params);
+ PLpgSQL_expr *dynquery, List *params,
+ const char *portalname, int cursorOptions);
/* ----------
* expected result type. XXX would be better to cache the tupdesc
* instead of repeating get_call_result_type()
*/
+ HeapTuple rettup = (HeapTuple) DatumGetPointer(estate.retval);
TupleDesc tupdesc;
+ TupleConversionMap *tupmap;
switch (get_call_result_type(fcinfo, NULL, &tupdesc))
{
case TYPEFUNC_COMPOSITE:
/* got the expected result rowtype, now check it */
- validate_tupdesc_compat(tupdesc, estate.rettupdesc,
- "returned record type does not match expected record type");
+ tupmap = convert_tuples_by_position(estate.rettupdesc,
+ tupdesc,
+ gettext_noop("returned record type does not match expected record type"));
+ /* it might need conversion */
+ if (tupmap)
+ rettup = do_convert_tuple(rettup, tupmap);
+ /* no need to free map, we're about to return anyway */
break;
case TYPEFUNC_RECORD:
* Copy tuple to upper executor memory, as a tuple Datum. Make
* sure it is labeled with the caller-supplied tuple type.
*/
- estate.retval =
- PointerGetDatum(SPI_returntuple((HeapTuple)DatumGetPointer(estate.retval),
- tupdesc));
+ estate.retval = PointerGetDatum(SPI_returntuple(rettup, tupdesc));
}
else
{
((*plugin_ptr)->func_end) (&estate, func);
/* Clean up any leftover temporary memory */
- FreeExprContext(estate.eval_econtext);
- estate.eval_econtext = NULL;
+ plpgsql_destroy_econtext(&estate);
exec_eval_cleanup(&estate);
/*
/*
* Put the OLD and NEW tuples into record variables
+ *
+ * We make the tupdescs available in both records even though only one may
+ * have a value. This allows parsing of record references to succeed in
+ * functions that are used for multiple trigger types. For example, we
+ * might have a test like "if (TG_OP = 'INSERT' and NEW.foo = 'xyz')",
+ * which should parse regardless of the current trigger type.
*/
rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]);
rec_new->freetup = false;
+ rec_new->tupdesc = trigdata->tg_relation->rd_att;
rec_new->freetupdesc = false;
rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
rec_old->freetup = false;
+ rec_old->tupdesc = trigdata->tg_relation->rd_att;
rec_old->freetupdesc = false;
if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
* Per-statement triggers don't use OLD/NEW variables
*/
rec_new->tup = NULL;
- rec_new->tupdesc = NULL;
rec_old->tup = NULL;
- rec_old->tupdesc = NULL;
}
else if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
{
rec_new->tup = trigdata->tg_trigtuple;
- rec_new->tupdesc = trigdata->tg_relation->rd_att;
rec_old->tup = NULL;
- rec_old->tupdesc = NULL;
}
else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
{
rec_new->tup = trigdata->tg_newtuple;
- rec_new->tupdesc = trigdata->tg_relation->rd_att;
rec_old->tup = trigdata->tg_trigtuple;
- rec_old->tupdesc = trigdata->tg_relation->rd_att;
}
else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
{
rec_new->tup = NULL;
- rec_new->tupdesc = NULL;
rec_old->tup = trigdata->tg_trigtuple;
- rec_old->tupdesc = trigdata->tg_relation->rd_att;
}
else
elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
var->isnull = false;
var->freeval = false;
- /*
- * Store the trigger argument values into the special execution state
- * variables
- */
- estate.err_text = gettext_noop("while storing call arguments into local variables");
- estate.trig_nargs = trigdata->tg_trigger->tgnargs;
- if (estate.trig_nargs == 0)
- estate.trig_argv = NULL;
+ var = (PLpgSQL_var *) (estate.datums[func->tg_argv_varno]);
+ if (trigdata->tg_trigger->tgnargs > 0)
+ {
+ /*
+ * For historical reasons, tg_argv[] subscripts start at zero not one.
+ * So we can't use construct_array().
+ */
+ int nelems = trigdata->tg_trigger->tgnargs;
+ Datum *elems;
+ int dims[1];
+ int lbs[1];
+
+ elems = palloc(sizeof(Datum) * nelems);
+ for (i = 0; i < nelems; i++)
+ elems[i] = CStringGetTextDatum(trigdata->tg_trigger->tgargs[i]);
+ dims[0] = nelems;
+ lbs[0] = 0;
+
+ var->value = PointerGetDatum(construct_md_array(elems, NULL,
+ 1, dims, lbs,
+ TEXTOID,
+ -1, false, 'i'));
+ var->isnull = false;
+ var->freeval = true;
+ }
else
{
- estate.trig_argv = palloc(sizeof(Datum) * estate.trig_nargs);
- for (i = 0; i < trigdata->tg_trigger->tgnargs; i++)
- estate.trig_argv[i] = CStringGetTextDatum(trigdata->tg_trigger->tgargs[i]);
+ var->value = (Datum) 0;
+ var->isnull = true;
+ var->freeval = false;
}
estate.err_text = gettext_noop("during function entry");
rettup = NULL;
else
{
- validate_tupdesc_compat(trigdata->tg_relation->rd_att,
- estate.rettupdesc,
- "returned row structure does not match the structure of the triggering table");
+ TupleConversionMap *tupmap;
+
+ rettup = (HeapTuple) DatumGetPointer(estate.retval);
+ /* check rowtype compatibility */
+ tupmap = convert_tuples_by_position(estate.rettupdesc,
+ trigdata->tg_relation->rd_att,
+ gettext_noop("returned row structure does not match the structure of the triggering table"));
+ /* it might need conversion */
+ if (tupmap)
+ rettup = do_convert_tuple(rettup, tupmap);
+ /* no need to free map, we're about to return anyway */
+
/* Copy tuple to upper executor memory */
- rettup = SPI_copytuple((HeapTuple) DatumGetPointer(estate.retval));
+ rettup = SPI_copytuple(rettup);
}
/*
((*plugin_ptr)->func_end) (&estate, func);
/* Clean up any leftover temporary memory */
- FreeExprContext(estate.eval_econtext);
- estate.eval_econtext = NULL;
+ plpgsql_destroy_econtext(&estate);
exec_eval_cleanup(&estate);
/*
{
PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
- /* safety check, shouldn't happen */
- if (estate->err_func == NULL)
- return;
-
/* if we are doing RAISE, don't report its location */
if (estate->err_text == raise_skip_msg)
return;
*/
if (estate->err_stmt != NULL)
{
- /* translator: last %s is a phrase such as "during statement block local variable initialization" */
+ /*
+ * translator: last %s is a phrase such as "during statement block
+ * local variable initialization"
+ */
errcontext("PL/pgSQL function \"%s\" line %d %s",
- estate->err_func->fn_name,
+ estate->func->fn_name,
estate->err_stmt->lineno,
_(estate->err_text));
}
else
{
- /* translator: last %s is a phrase such as "while storing call arguments into local variables" */
+ /*
+ * translator: last %s is a phrase such as "while storing call
+ * arguments into local variables"
+ */
errcontext("PL/pgSQL function \"%s\" %s",
- estate->err_func->fn_name,
+ estate->func->fn_name,
_(estate->err_text));
}
}
{
/* translator: last %s is a plpgsql statement type name */
errcontext("PL/pgSQL function \"%s\" line %d at %s",
- estate->err_func->fn_name,
+ estate->func->fn_name,
estate->err_stmt->lineno,
plpgsql_stmt_typename(estate->err_stmt));
}
else
errcontext("PL/pgSQL function \"%s\"",
- estate->err_func->fn_name);
+ estate->func->fn_name);
}
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
if (rec->freetup)
{
heap_freetuple(rec->tup);
- FreeTupleDesc(rec->tupdesc);
rec->freetup = false;
}
-
+ if (rec->freetupdesc)
+ {
+ FreeTupleDesc(rec->tupdesc);
+ rec->freetupdesc = false;
+ }
rec->tup = NULL;
rec->tupdesc = NULL;
}
MemoryContext oldcontext = CurrentMemoryContext;
ResourceOwner oldowner = CurrentResourceOwner;
ExprContext *old_eval_econtext = estate->eval_econtext;
- EState *old_eval_estate = estate->eval_estate;
- long int old_eval_estate_simple_id = estate->eval_estate_simple_id;
estate->err_text = gettext_noop("during statement block entry");
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
- /* Revert to outer eval_econtext */
+ /*
+ * Revert to outer eval_econtext. (The inner one was
+ * automatically cleaned up during subxact exit.)
+ */
estate->eval_econtext = old_eval_econtext;
- estate->eval_estate = old_eval_estate;
- estate->eval_estate_simple_id = old_eval_estate_simple_id;
/*
* AtEOSubXact_SPI() should not have popped any SPI context, but
/* Revert to outer eval_econtext */
estate->eval_econtext = old_eval_econtext;
- estate->eval_estate = old_eval_estate;
- estate->eval_estate_simple_id = old_eval_estate_simple_id;
/*
* If AtEOSubXact_SPI() popped any SPI context of the subxact, it
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;
if (stmt->t_expr != NULL)
{
/* simple case */
- Datum t_val;
- Oid t_oid;
+ Datum t_val;
+ Oid t_oid;
t_val = exec_eval_expr(estate, stmt->t_expr, &isnull, &t_oid);
t_var = (PLpgSQL_var *) estate->datums[stmt->t_varno];
/*
- * When expected datatype is different from real, change it.
- * Note that what we're modifying here is an execution copy
- * of the datum, so this doesn't affect the originally stored
- * function parse tree.
+ * When expected datatype is different from real, change it. Note that
+ * what we're modifying here is an execution copy of the datum, so
+ * this doesn't affect the originally stored function parse tree.
*/
if (t_var->datatype->typoid != t_oid)
t_var->datatype = plpgsql_build_datatype(t_oid, -1);
foreach(l, stmt->case_when_list)
{
PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
- bool value;
+ bool value;
value = exec_eval_boolean(estate, cwt->expr, &isnull);
exec_eval_cleanup(estate);
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;
{
PLpgSQL_var *curvar;
char *curname = NULL;
+ const char *portalname;
PLpgSQL_expr *query;
+ ParamListInfo paramLI;
Portal portal;
int rc;
- Datum *values;
- char *nulls;
/* ----------
* Get the cursor variable and if it has an assigned name, check
if (curvar->cursor_explicit_argrow < 0)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("arguments given for cursor without arguments")));
+ errmsg("arguments given for cursor without arguments")));
memset(&set_args, 0, sizeof(set_args));
set_args.cmd_type = PLPGSQL_STMT_EXECSQL;
exec_prepare_plan(estate, query, curvar->cursor_options);
/*
- * Now build up the values and nulls arguments for SPI_execute_plan()
+ * Set up ParamListInfo (note this is only carrying a hook function, not
+ * any actual data values, at this point)
*/
- eval_expr_params(estate, query, &values, &nulls);
+ paramLI = setup_param_list(estate, query);
/*
- * Open the cursor
+ * Open the cursor (the paramlist will get copied into the portal)
*/
- portal = SPI_cursor_open(curname, query->plan, values, nulls,
- estate->readonly_func);
+ portal = SPI_cursor_open_with_paramlist(curname, query->plan,
+ paramLI,
+ estate->readonly_func);
if (portal == NULL)
elog(ERROR, "could not open cursor: %s",
SPI_result_code_string(SPI_result));
+ portalname = portal->name;
+
+ /* don't need paramlist any more */
+ if (paramLI)
+ pfree(paramLI);
/*
* If cursor variable was NULL, store the generated portal name in it
curvar->isnull = true;
}
- pfree(values);
- pfree(nulls);
if (curname)
pfree(curname);
estate->retval =
PointerGetDatum(make_tuple_from_row(estate, row,
row->rowtupdesc));
- if (DatumGetPointer(estate->retval) == NULL) /* should not happen */
+ if (DatumGetPointer(estate->retval) == NULL) /* should not happen */
elog(ERROR, "row not compatible with its own tupdesc");
estate->rettupdesc = row->rowtupdesc;
estate->retisnull = false;
exec_stmt_return_next(PLpgSQL_execstate *estate,
PLpgSQL_stmt_return_next *stmt)
{
- TupleDesc tupdesc;
- int natts;
- MemoryContext oldcxt;
- HeapTuple tuple = NULL;
- bool free_tuple = false;
+ TupleDesc tupdesc;
+ int natts;
+ HeapTuple tuple = NULL;
+ bool free_tuple = false;
if (!estate->retisset)
ereport(ERROR,
tupdesc->attrs[0]->atttypmod,
isNull);
- oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
tuplestore_putvalues(estate->tuple_store, tupdesc,
&retval, &isNull);
- MemoryContextSwitchTo(oldcxt);
}
break;
case PLPGSQL_DTYPE_REC:
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
+ TupleConversionMap *tupmap;
if (!HeapTupleIsValid(rec->tup))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("record \"%s\" is not assigned yet",
rec->refname),
- errdetail("The tuple structure of a not-yet-assigned"
- " record is indeterminate.")));
- validate_tupdesc_compat(tupdesc, rec->tupdesc,
- "wrong record type supplied in RETURN NEXT");
+ errdetail("The tuple structure of a not-yet-assigned"
+ " record is indeterminate.")));
+ tupmap = convert_tuples_by_position(rec->tupdesc,
+ tupdesc,
+ gettext_noop("wrong record type supplied in RETURN NEXT"));
tuple = rec->tup;
+ /* it might need conversion */
+ if (tupmap)
+ {
+ tuple = do_convert_tuple(tuple, tupmap);
+ free_conversion_map(tupmap);
+ }
}
break;
tupdesc->attrs[0]->atttypmod,
isNull);
- oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
tuplestore_putvalues(estate->tuple_store, tupdesc,
&retval, &isNull);
- MemoryContextSwitchTo(oldcxt);
exec_eval_cleanup(estate);
}
if (HeapTupleIsValid(tuple))
{
- oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
tuplestore_puttuple(estate->tuple_store, tuple);
- MemoryContextSwitchTo(oldcxt);
if (free_tuple)
heap_freetuple(tuple);
PLpgSQL_stmt_return_query *stmt)
{
Portal portal;
- uint32 processed = 0;
+ uint32 processed = 0;
+ TupleConversionMap *tupmap;
if (!estate->retisset)
ereport(ERROR,
/* RETURN QUERY EXECUTE */
Assert(stmt->dynquery != NULL);
portal = exec_dynquery_with_params(estate, stmt->dynquery,
- stmt->params);
+ stmt->params, NULL, 0);
}
- validate_tupdesc_compat(estate->rettupdesc, portal->tupDesc,
- "structure of query does not match function result type");
+ tupmap = convert_tuples_by_position(portal->tupDesc,
+ estate->rettupdesc,
+ gettext_noop("structure of query does not match function result type"));
while (true)
{
- MemoryContext old_cxt;
int i;
SPI_cursor_fetch(portal, true, 50);
if (SPI_processed == 0)
break;
- old_cxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
for (i = 0; i < SPI_processed; i++)
{
HeapTuple tuple = SPI_tuptable->vals[i];
+ if (tupmap)
+ tuple = do_convert_tuple(tuple, tupmap);
tuplestore_puttuple(estate->tuple_store, tuple);
+ if (tupmap)
+ heap_freetuple(tuple);
processed++;
}
- MemoryContextSwitchTo(old_cxt);
SPI_freetuptable(SPI_tuptable);
}
+ if (tupmap)
+ free_conversion_map(tupmap);
+
SPI_freetuptable(SPI_tuptable);
SPI_cursor_close(portal);
{
ReturnSetInfo *rsi = estate->rsi;
MemoryContext oldcxt;
+ ResourceOwner oldowner;
/*
* Check caller can handle a set result in the way we want
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set")));
- estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
-
+ /*
+ * Switch to the right memory context and resource owner for storing the
+ * tuplestore for return set. If we're within a subtransaction opened for
+ * an exception-block, for example, we must still create the tuplestore in
+ * the resource owner that was active when this function was entered, and
+ * not in the subtransaction resource owner.
+ */
oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
+ oldowner = CurrentResourceOwner;
+ CurrentResourceOwner = estate->tuple_store_owner;
+
estate->tuple_store =
tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
false, work_mem);
+
+ CurrentResourceOwner = oldowner;
MemoryContextSwitchTo(oldcxt);
estate->rettupdesc = rsi->expectedDesc;
if (stmt->message)
{
- PLpgSQL_dstring ds;
+ StringInfoData ds;
ListCell *current_param;
char *cp;
- plpgsql_dstring_init(&ds);
+ initStringInfo(&ds);
current_param = list_head(stmt->params);
for (cp = stmt->message; *cp; cp++)
if (cp[1] == '%')
{
- plpgsql_dstring_append_char(&ds, cp[1]);
+ appendStringInfoChar(&ds, '%');
cp++;
continue;
}
if (current_param == NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("too few parameters specified for RAISE")));
+ errmsg("too few parameters specified for RAISE")));
paramvalue = exec_eval_expr(estate,
- (PLpgSQL_expr *) lfirst(current_param),
+ (PLpgSQL_expr *) lfirst(current_param),
¶misnull,
¶mtypeid);
extval = "<NULL>";
else
extval = convert_value_to_string(paramvalue, paramtypeid);
- plpgsql_dstring_append(&ds, extval);
+ appendStringInfoString(&ds, extval);
current_param = lnext(current_param);
exec_eval_cleanup(estate);
}
else
- plpgsql_dstring_append_char(&ds, cp[0]);
+ appendStringInfoChar(&ds, cp[0]);
}
/*
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("too many parameters specified for RAISE")));
- err_message = plpgsql_dstring_get(&ds);
- /* No dstring_free here, the pfree(err_message) does it */
+ err_message = ds.data;
+ /* No pfree(ds.data), the pfree(err_message) does it */
}
foreach(lc, stmt->options)
PLpgSQL_function *func,
ReturnSetInfo *rsi)
{
+ /* this link will be restored at exit from plpgsql_call_handler */
+ func->cur_estate = estate;
+
+ estate->func = func;
+
estate->retval = (Datum) 0;
estate->retisnull = true;
estate->rettype = InvalidOid;
estate->exitlabel = NULL;
estate->tuple_store = NULL;
- estate->tuple_store_cxt = NULL;
+ if (rsi)
+ {
+ estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
+ estate->tuple_store_owner = CurrentResourceOwner;
+ }
+ else
+ {
+ estate->tuple_store_cxt = NULL;
+ estate->tuple_store_owner = NULL;
+ }
estate->rsi = rsi;
- estate->trig_nargs = 0;
- estate->trig_argv = NULL;
-
estate->found_varno = func->found_varno;
estate->ndatums = func->ndatums;
estate->datums = palloc(sizeof(PLpgSQL_datum *) * estate->ndatums);
estate->eval_tuptable = NULL;
estate->eval_processed = 0;
estate->eval_lastoid = InvalidOid;
+ estate->eval_econtext = NULL;
+ estate->cur_expr = NULL;
- estate->err_func = func;
estate->err_stmt = NULL;
estate->err_text = NULL;
+ estate->plugin_info = NULL;
+
/*
* Create an EState and ExprContext for evaluation of simple expressions.
*/
exec_prepare_plan(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, int cursorOptions)
{
- int i;
SPIPlanPtr plan;
- Oid *argtypes;
/*
- * We need a temporary argtypes array to load with data. (The finished
- * plan structure will contain a copy of it.)
+ * The grammar can't conveniently set expr->func while building the parse
+ * tree, so make sure it's set before parser hooks need it.
*/
- argtypes = (Oid *) palloc(expr->nparams * sizeof(Oid));
-
- for (i = 0; i < expr->nparams; i++)
- {
- Datum paramval;
- bool paramisnull;
-
- exec_eval_datum(estate, estate->datums[expr->params[i]],
- InvalidOid,
- &argtypes[i], ¶mval, ¶misnull);
- }
+ expr->func = estate->func;
/*
* Generate and save the plan
*/
- plan = SPI_prepare_cursor(expr->query, expr->nparams, argtypes,
+ plan = SPI_prepare_params(expr->query,
+ (ParserSetupHook) plpgsql_parser_setup,
+ (void *) expr,
cursorOptions);
if (plan == NULL)
{
errmsg("cannot begin/end transactions in PL/pgSQL"),
errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
default:
- elog(ERROR, "SPI_prepare_cursor failed for \"%s\": %s",
+ elog(ERROR, "SPI_prepare_params failed for \"%s\": %s",
expr->query, SPI_result_code_string(SPI_result));
}
}
expr->plan = SPI_saveplan(plan);
SPI_freeplan(plan);
- plan = expr->plan;
- expr->plan_argtypes = plan->argtypes;
exec_simple_check_plan(expr);
-
- pfree(argtypes);
}
exec_stmt_execsql(PLpgSQL_execstate *estate,
PLpgSQL_stmt_execsql *stmt)
{
- Datum *values;
- char *nulls;
+ ParamListInfo paramLI;
long tcount;
int rc;
PLpgSQL_expr *expr = stmt->sqlstmt;
}
/*
- * Now build up the values and nulls arguments for SPI_execute_plan()
+ * Set up ParamListInfo (note this is only carrying a hook function, not
+ * any actual data values, at this point)
*/
- eval_expr_params(estate, expr, &values, &nulls);
+ paramLI = setup_param_list(estate, expr);
/*
* If we have INTO, then we only need one row back ... but if we have INTO
/*
* Execute the plan
*/
- rc = SPI_execute_plan(expr->plan, values, nulls,
- estate->readonly_func, tcount);
+ rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
+ estate->readonly_func, tcount);
/*
* Check for error, and set FOUND if appropriate (for historical reasons
case SPI_OK_REWRITTEN:
Assert(!stmt->mod_stmt);
+
/*
* The command was rewritten into another kind of command. It's
* not clear what FOUND would mean in that case (and SPI doesn't
break;
default:
- elog(ERROR, "SPI_execute_plan failed executing query \"%s\": %s",
+ elog(ERROR, "SPI_execute_plan_with_paramlist failed executing query \"%s\": %s",
expr->query, SPI_result_code_string(rc));
}
(rc == SPI_OK_SELECT) ? errhint("If you want to discard the results of a SELECT, use PERFORM instead.") : 0));
}
- pfree(values);
- pfree(nulls);
+ if (paramLI)
+ pfree(paramLI);
return PLPGSQL_RC_OK;
}
if (*ptr == 'S' || *ptr == 's')
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("EXECUTE of SELECT ... INTO is not implemented")));
+ errmsg("EXECUTE of SELECT ... INTO is not implemented"),
+ errhint("You might want to use EXECUTE ... INTO instead.")));
break;
}
Portal portal;
int rc;
- portal = exec_dynquery_with_params(estate, stmt->query, stmt->params);
+ portal = exec_dynquery_with_params(estate, stmt->query, stmt->params,
+ NULL, 0);
/*
* Execute the loop
char *curname = NULL;
PLpgSQL_expr *query;
Portal portal;
- Datum *values;
- char *nulls;
- bool isnull;
+ ParamListInfo paramLI;
/* ----------
* Get the cursor variable and if it has an assigned name, check
* This is an OPEN refcursor FOR EXECUTE ...
* ----------
*/
- Datum queryD;
- Oid restype;
- char *querystr;
- SPIPlanPtr curplan;
-
- /* ----------
- * We evaluate the string expression after the
- * EXECUTE keyword. It's result is the querystring we have
- * to execute.
- * ----------
- */
- queryD = exec_eval_expr(estate, stmt->dynquery, &isnull, &restype);
- if (isnull)
- ereport(ERROR,
- (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("query string argument of EXECUTE is null")));
-
- /* Get the C-String representation */
- querystr = convert_value_to_string(queryD, restype);
-
- exec_eval_cleanup(estate);
-
- /* ----------
- * Now we prepare a query plan for it and open a cursor
- * ----------
- */
- curplan = SPI_prepare_cursor(querystr, 0, NULL, stmt->cursor_options);
- if (curplan == NULL)
- elog(ERROR, "SPI_prepare_cursor failed for \"%s\": %s",
- querystr, SPI_result_code_string(SPI_result));
- portal = SPI_cursor_open(curname, curplan, NULL, NULL,
- estate->readonly_func);
- if (portal == NULL)
- elog(ERROR, "could not open cursor for query \"%s\": %s",
- querystr, SPI_result_code_string(SPI_result));
- pfree(querystr);
- SPI_freeplan(curplan);
+ portal = exec_dynquery_with_params(estate,
+ stmt->dynquery,
+ stmt->params,
+ curname,
+ stmt->cursor_options);
/*
* If cursor variable was NULL, store the generated portal name in it
}
/*
- * Now build up the values and nulls arguments for SPI_execute_plan()
+ * Set up ParamListInfo (note this is only carrying a hook function, not
+ * any actual data values, at this point)
*/
- eval_expr_params(estate, query, &values, &nulls);
+ paramLI = setup_param_list(estate, query);
/*
* Open the cursor
*/
- portal = SPI_cursor_open(curname, query->plan, values, nulls,
- estate->readonly_func);
+ portal = SPI_cursor_open_with_paramlist(curname, query->plan,
+ paramLI,
+ estate->readonly_func);
if (portal == NULL)
elog(ERROR, "could not open cursor: %s",
SPI_result_code_string(SPI_result));
if (curname == NULL)
assign_text_var(curvar, portal->name);
- pfree(values);
- pfree(nulls);
if (curname)
pfree(curname);
+ if (paramLI)
+ pfree(paramLI);
return PLPGSQL_RC_OK;
}
SPITupleTable *tuptab;
Portal portal;
char *curname;
- int n;
+ uint32 n;
/* ----------
* Get the portal of the cursor by name
n = SPI_processed;
/* ----------
- * Set the target and the global FOUND variable appropriately.
+ * Set the target appropriately.
* ----------
*/
if (n == 0)
- {
exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
- exec_set_found(estate, false);
- }
else
- {
exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
- exec_set_found(estate, true);
- }
SPI_freetuptable(tuptab);
}
/* Move the cursor */
SPI_scroll_cursor_move(portal, stmt->direction, how_many);
n = SPI_processed;
-
- /* Set the global FOUND variable appropriately. */
- exec_set_found(estate, n != 0);
}
+ /* Set the ROW_COUNT and the global FOUND variable appropriately. */
+ estate->eval_processed = n;
+ exec_set_found(estate, n != 0);
+
return PLPGSQL_RC_OK;
}
*/
PLpgSQL_row *row = (PLpgSQL_row *) target;
- /* Source must be of RECORD or composite type */
- if (!type_is_rowtype(valtype))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("cannot assign non-composite value to a row variable")));
if (*isNull)
{
/* If source is null, just assign nulls to the row */
TupleDesc tupdesc;
HeapTupleData tmptup;
- /* Else source is a tuple Datum, safe to do this: */
+ /* Source must be of RECORD or composite type */
+ if (!type_is_rowtype(valtype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot assign non-composite value to a row variable")));
+ /* Source is a tuple Datum, so safe to do this: */
td = DatumGetHeapTupleHeader(value);
/* Extract rowtype info and find a tupdesc */
tupType = HeapTupleHeaderGetTypeId(td);
*/
PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
- /* Source must be of RECORD or composite type */
- if (!type_is_rowtype(valtype))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("cannot assign non-composite value to a record variable")));
if (*isNull)
{
/* If source is null, just assign nulls to the record */
TupleDesc tupdesc;
HeapTupleData tmptup;
- /* Else source is a tuple Datum, safe to do this: */
+ /* Source must be of RECORD or composite type */
+ if (!type_is_rowtype(valtype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot assign non-composite value to a record variable")));
+
+ /* Source is a tuple Datum, so safe to do this: */
td = DatumGetHeapTupleHeader(value);
/* Extract rowtype info and find a tupdesc */
tupType = HeapTupleHeaderGetTypeId(td);
/*
* Get the number of the records field to change and the
- * number of attributes in the tuple. Note: disallow
- * system column names because the code below won't cope.
+ * number of attributes in the tuple. Note: disallow system
+ * column names because the code below won't cope.
*/
fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
if (fno <= 0)
} while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
/* Fetch current value of array datum */
- exec_eval_datum(estate, target, InvalidOid,
+ exec_eval_datum(estate, target,
&arraytypeid, &oldarraydatum, &oldarrayisnull);
arrayelemtypeid = get_element_type(arraytypeid);
*
* The type oid, value in Datum format, and null flag are returned.
*
- * If expectedtypeid isn't InvalidOid, it is checked against the actual type.
- *
* At present this doesn't handle PLpgSQL_expr or PLpgSQL_arrayelem datums.
*
* NOTE: caller must not modify the returned value, since it points right
static void
exec_eval_datum(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum,
- Oid expectedtypeid,
Oid *typeid,
Datum *value,
bool *isnull)
*typeid = var->datatype->typoid;
*value = var->value;
*isnull = var->isnull;
- if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("type of \"%s\" does not match that when preparing the plan",
- var->refname)));
break;
}
*typeid = row->rowtupdesc->tdtypeid;
*value = HeapTupleGetDatum(tup);
*isnull = false;
- if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("type of \"%s\" does not match that when preparing the plan",
- row->refname)));
break;
}
*typeid = rec->tupdesc->tdtypeid;
*value = HeapTupleGetDatum(&worktup);
*isnull = false;
- if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("type of \"%s\" does not match that when preparing the plan",
- rec->refname)));
break;
}
rec->refname, recfield->fieldname)));
*typeid = SPI_gettypeid(rec->tupdesc, fno);
*value = SPI_getbinval(rec->tup, rec->tupdesc, fno, isnull);
- if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
+ break;
+ }
+
+ default:
+ elog(ERROR, "unrecognized dtype: %d", datum->dtype);
+ }
+}
+
+/*
+ * exec_get_datum_type Get datatype of a PLpgSQL_datum
+ *
+ * This is the same logic as in exec_eval_datum, except that it can handle
+ * some cases where exec_eval_datum has to fail; specifically, we may have
+ * a tupdesc but no row value for a record variable. (This currently can
+ * happen only for a trigger's NEW/OLD records.)
+ */
+Oid
+exec_get_datum_type(PLpgSQL_execstate *estate,
+ PLpgSQL_datum *datum)
+{
+ Oid typeid;
+
+ switch (datum->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *) datum;
+
+ typeid = var->datatype->typoid;
+ break;
+ }
+
+ case PLPGSQL_DTYPE_ROW:
+ {
+ PLpgSQL_row *row = (PLpgSQL_row *) datum;
+
+ if (!row->rowtupdesc) /* should not happen */
+ elog(ERROR, "row variable has no tupdesc");
+ /* Make sure we have a valid type/typmod setting */
+ BlessTupleDesc(row->rowtupdesc);
+ typeid = row->rowtupdesc->tdtypeid;
+ break;
+ }
+
+ case PLPGSQL_DTYPE_REC:
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
+
+ if (rec->tupdesc == NULL)
ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("type of \"%s.%s\" does not match that when preparing the plan",
- rec->refname, recfield->fieldname)));
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("record \"%s\" is not assigned yet",
+ rec->refname),
+ errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
+ /* Make sure we have a valid type/typmod setting */
+ BlessTupleDesc(rec->tupdesc);
+ typeid = rec->tupdesc->tdtypeid;
break;
}
- case PLPGSQL_DTYPE_TRIGARG:
+ case PLPGSQL_DTYPE_RECFIELD:
{
- PLpgSQL_trigarg *trigarg = (PLpgSQL_trigarg *) datum;
- int tgargno;
+ PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
+ PLpgSQL_rec *rec;
+ int fno;
- *typeid = TEXTOID;
- tgargno = exec_eval_integer(estate, trigarg->argnum, isnull);
- if (*isnull || tgargno < 0 || tgargno >= estate->trig_nargs)
- {
- *value = (Datum) 0;
- *isnull = true;
- }
- else
- {
- *value = estate->trig_argv[tgargno];
- *isnull = false;
- }
- if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
+ rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
+ if (rec->tupdesc == NULL)
ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("type of tgargv[%d] does not match that when preparing the plan",
- tgargno)));
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("record \"%s\" is not assigned yet",
+ rec->refname),
+ errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
+ fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
+ if (fno == SPI_ERROR_NOATTRIBUTE)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("record \"%s\" has no field \"%s\"",
+ rec->refname, recfield->fieldname)));
+ typeid = SPI_gettypeid(rec->tupdesc, fno);
break;
}
default:
elog(ERROR, "unrecognized dtype: %d", datum->dtype);
+ typeid = InvalidOid; /* keep compiler quiet */
+ break;
}
+
+ return typeid;
+}
+
+/*
+ * exec_get_rec_fieldtype Get datatype of a PLpgSQL record field
+ *
+ * Also returns the field number to *fieldno.
+ */
+Oid
+exec_get_rec_fieldtype(PLpgSQL_rec *rec, const char *fieldname,
+ int *fieldno)
+{
+ Oid typeid;
+ int fno;
+
+ if (rec->tupdesc == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("record \"%s\" is not assigned yet",
+ rec->refname),
+ errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
+ fno = SPI_fnumber(rec->tupdesc, fieldname);
+ if (fno == SPI_ERROR_NOATTRIBUTE)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("record \"%s\" has no field \"%s\"",
+ rec->refname, fieldname)));
+ typeid = SPI_gettypeid(rec->tupdesc, fno);
+
+ *fieldno = fno;
+ return typeid;
}
/* ----------
errmsg("query \"%s\" did not return data", expr->query)));
/*
- * If there are no rows selected, the result is NULL.
+ * Check that the expression returns exactly one column...
+ */
+ if (estate->eval_tuptable->tupdesc->natts != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg_plural("query \"%s\" returned %d column",
+ "query \"%s\" returned %d columns",
+ estate->eval_tuptable->tupdesc->natts,
+ expr->query,
+ estate->eval_tuptable->tupdesc->natts)));
+
+ /*
+ * ... and get the column's datatype.
+ */
+ *rettype = SPI_gettypeid(estate->eval_tuptable->tupdesc, 1);
+
+ /*
+ * If there are no rows selected, the result is a NULL of that type.
*/
if (estate->eval_processed == 0)
{
}
/*
- * Check that the expression returned one single Datum
+ * Check that the expression returned no more than one row.
*/
- if (estate->eval_processed > 1)
+ if (estate->eval_processed != 1)
ereport(ERROR,
(errcode(ERRCODE_CARDINALITY_VIOLATION),
errmsg("query \"%s\" returned more than one row",
expr->query)));
- if (estate->eval_tuptable->tupdesc->natts != 1)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("query \"%s\" returned %d columns", expr->query,
- estate->eval_tuptable->tupdesc->natts)));
/*
- * Return the result and its type
+ * Return the single result Datum.
*/
- *rettype = SPI_gettypeid(estate->eval_tuptable->tupdesc, 1);
return SPI_getbinval(estate->eval_tuptable->vals[0],
estate->eval_tuptable->tupdesc, 1, isNull);
}
exec_run_select(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, long maxtuples, Portal *portalP)
{
- Datum *values;
- char *nulls;
+ ParamListInfo paramLI;
int rc;
/*
exec_prepare_plan(estate, expr, 0);
/*
- * Now build up the values and nulls arguments for SPI_execute_plan()
+ * Set up ParamListInfo (note this is only carrying a hook function, not
+ * any actual data values, at this point)
*/
- eval_expr_params(estate, expr, &values, &nulls);
+ paramLI = setup_param_list(estate, expr);
/*
* If a portal was requested, put the query into the portal
*/
if (portalP != NULL)
{
- *portalP = SPI_cursor_open(NULL, expr->plan, values, nulls,
- estate->readonly_func);
+ *portalP = SPI_cursor_open_with_paramlist(NULL, expr->plan,
+ paramLI,
+ estate->readonly_func);
if (*portalP == NULL)
elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
expr->query, SPI_result_code_string(SPI_result));
- pfree(values);
- pfree(nulls);
+ if (paramLI)
+ pfree(paramLI);
return SPI_OK_CURSOR;
}
/*
* Execute the query
*/
- rc = SPI_execute_plan(expr->plan, values, nulls,
- estate->readonly_func, maxtuples);
+ rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
+ estate->readonly_func, maxtuples);
if (rc != SPI_OK_SELECT)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
estate->eval_processed = SPI_processed;
estate->eval_lastoid = SPI_lastoid;
- pfree(values);
- pfree(nulls);
+ if (paramLI)
+ pfree(paramLI);
return rc;
}
elog(ERROR, "unsupported target");
/*
- * Fetch the initial tuple(s). If prefetching is allowed then we grab
- * a few more rows to avoid multiple trips through executor startup
+ * Make sure the portal doesn't get closed by the user statements we
+ * execute.
+ */
+ PinPortal(portal);
+
+ /*
+ * Fetch the initial tuple(s). If prefetching is allowed then we grab a
+ * few more rows to avoid multiple trips through executor startup
* overhead.
*/
SPI_cursor_fetch(portal, true, prefetch_ok ? 10 : 1);
n = SPI_processed;
/*
- * If the query didn't return any rows, set the target to NULL and
- * fall through with found = false.
+ * If the query didn't return any rows, set the target to NULL and fall
+ * through with found = false.
*/
if (n <= 0)
exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
*/
SPI_freetuptable(tuptab);
+ UnpinPortal(portal);
+
/*
* Set the FOUND variable to indicate the result of executing the loop
* (namely, whether we looped one or more times). This must be set last so
Oid *rettype)
{
ExprContext *econtext = estate->eval_econtext;
+ LocalTransactionId curlxid = MyProc->lxid;
CachedPlanSource *plansource;
CachedPlan *cplan;
ParamListInfo paramLI;
- int i;
+ PLpgSQL_expr *save_cur_expr;
MemoryContext oldcontext;
/*
/*
* Prepare the expression for execution, if it's not been done already in
- * the current eval_estate. (This will be forced to happen if we called
+ * the current transaction. (This will be forced to happen if we called
* exec_simple_check_plan above.)
*/
- if (expr->expr_simple_id != estate->eval_estate_simple_id)
+ if (expr->expr_simple_lxid != curlxid)
{
expr->expr_simple_state = ExecPrepareExpr(expr->expr_simple_expr,
- estate->eval_estate);
- expr->expr_simple_id = estate->eval_estate_simple_id;
+ simple_eval_estate);
+ expr->expr_simple_lxid = curlxid;
}
- /*
- * Param list can live in econtext's temporary memory context.
- *
- * XXX think about avoiding repeated palloc's for param lists? Beware
- * however that this routine is re-entrant: exec_eval_datum() can call it
- * back for subscript evaluation, and so there can be a need to have more
- * than one active param list.
- */
- if (expr->nparams > 0)
- {
- /* sizeof(ParamListInfoData) includes the first array element */
- paramLI = (ParamListInfo)
- MemoryContextAlloc(econtext->ecxt_per_tuple_memory,
- sizeof(ParamListInfoData) +
- (expr->nparams - 1) *sizeof(ParamExternData));
- paramLI->numParams = expr->nparams;
-
- for (i = 0; i < expr->nparams; i++)
- {
- ParamExternData *prm = ¶mLI->params[i];
- PLpgSQL_datum *datum = estate->datums[expr->params[i]];
-
- prm->pflags = 0;
- exec_eval_datum(estate, datum, expr->plan_argtypes[i],
- &prm->ptype,
- &prm->value, &prm->isnull);
- }
- }
- else
- paramLI = NULL;
-
- /*
- * Now we can safely make the econtext point to the param list.
- */
- econtext->ecxt_param_list_info = paramLI;
-
/*
* We have to do some of the things SPI_execute_plan would do, in
* particular advance the snapshot if we are in a non-read-only function.
PushActiveSnapshot(GetTransactionSnapshot());
}
+ /*
+ * Create the param list in econtext's temporary memory context. We won't
+ * need to free it explicitly, since it will go away at the next reset of
+ * that context.
+ *
+ * XXX think about avoiding repeated palloc's for param lists? It should
+ * be possible --- this routine isn't re-entrant anymore.
+ *
+ * Just for paranoia's sake, save and restore the prior value of
+ * estate->cur_expr, which setup_param_list() sets.
+ */
+ save_cur_expr = estate->cur_expr;
+
+ paramLI = setup_param_list(estate, expr);
+ econtext->ecxt_param_list_info = paramLI;
+
/*
* Finally we can call the executor to evaluate the expression
*/
econtext,
isNull,
NULL);
- MemoryContextSwitchTo(oldcontext);
+
+ /* Assorted cleanup */
+ estate->cur_expr = save_cur_expr;
if (!estate->readonly_func)
PopActiveSnapshot();
+ MemoryContextSwitchTo(oldcontext);
+
SPI_pop();
/*
/*
- * Build up the values and nulls arguments for SPI_execute_plan()
+ * Create a ParamListInfo to pass to SPI
+ *
+ * The ParamListInfo array is initially all zeroes, in particular the
+ * ptype values are all InvalidOid. This causes the executor to call the
+ * paramFetch hook each time it wants a value. We thus evaluate only the
+ * parameters actually demanded.
+ *
+ * The result is a locally palloc'd array that should be pfree'd after use;
+ * but note it can be NULL.
*/
-static void
-eval_expr_params(PLpgSQL_execstate *estate,
- PLpgSQL_expr *expr, Datum **p_values, char **p_nulls)
+static ParamListInfo
+setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
{
- Datum *values;
- char *nulls;
- int i;
-
- *p_values = values = (Datum *) palloc(expr->nparams * sizeof(Datum));
- *p_nulls = nulls = (char *) palloc(expr->nparams * sizeof(char));
+ ParamListInfo paramLI;
- for (i = 0; i < expr->nparams; i++)
+ /*
+ * Could we re-use these arrays instead of palloc'ing a new one each time?
+ * However, we'd have to zero the array each time anyway, since new values
+ * might have been assigned to the variables.
+ */
+ if (estate->ndatums > 0)
{
- PLpgSQL_datum *datum = estate->datums[expr->params[i]];
- Oid paramtypeid;
- bool paramisnull;
-
- exec_eval_datum(estate, datum, expr->plan_argtypes[i],
- ¶mtypeid, &values[i], ¶misnull);
- if (paramisnull)
- nulls[i] = 'n';
- else
- nulls[i] = ' ';
+ /* sizeof(ParamListInfoData) includes the first array element */
+ paramLI = (ParamListInfo)
+ palloc0(sizeof(ParamListInfoData) +
+ (estate->ndatums - 1) *sizeof(ParamExternData));
+ paramLI->paramFetch = plpgsql_param_fetch;
+ paramLI->paramFetchArg = (void *) estate;
+ paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
+ paramLI->parserSetupArg = (void *) expr;
+ paramLI->numParams = estate->ndatums;
+
+ /*
+ * Set up link to active expr where the hook functions can find it.
+ * Callers must save and restore cur_expr if there is any chance that
+ * they are interrupting an active use of parameters.
+ */
+ estate->cur_expr = expr;
+
+ /*
+ * Also make sure this is set before parser hooks need it. There is
+ * no need to save and restore, since the value is always correct once
+ * set.
+ */
+ expr->func = estate->func;
}
+ else
+ paramLI = NULL;
+ return paramLI;
+}
+
+/*
+ * plpgsql_param_fetch paramFetch callback for dynamic parameter fetch
+ */
+static void
+plpgsql_param_fetch(ParamListInfo params, int paramid)
+{
+ int dno;
+ PLpgSQL_execstate *estate;
+ PLpgSQL_expr *expr;
+ PLpgSQL_datum *datum;
+ ParamExternData *prm;
+
+ /* paramid's are 1-based, but dnos are 0-based */
+ dno = paramid - 1;
+ Assert(dno >= 0 && dno < params->numParams);
+
+ /* fetch back the hook data */
+ estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ expr = estate->cur_expr;
+ Assert(params->numParams == estate->ndatums);
+
+ /*
+ * Do nothing if asked for a value that's not supposed to be used by this
+ * SQL expression. This avoids unwanted evaluations when functions such
+ * as copyParamList try to materialize all the values.
+ */
+ if (!bms_is_member(dno, expr->paramnos))
+ return;
+
+ /* OK, evaluate the value and store into the appropriate paramlist slot */
+ datum = estate->datums[dno];
+ prm = ¶ms->params[dno];
+ exec_eval_datum(estate, datum,
+ &prm->ptype, &prm->value, &prm->isnull);
}
* attributes of the tuple to the variables the row points to.
*
* NOTE: this code used to demand row->nfields ==
- * HeapTupleHeaderGetNatts(tup->t_data), but that's wrong. The tuple might
- * have more fields than we expected if it's from an inheritance-child
- * table of the current table, or it might have fewer if the table has had
- * columns added by ALTER TABLE. Ignore extra columns and assume NULL for
- * missing columns, the same as heap_getattr would do. We also have to
- * skip over dropped columns in either the source or destination.
+ * HeapTupleHeaderGetNatts(tup->t_data), but that's wrong. The tuple
+ * might have more fields than we expected if it's from an
+ * inheritance-child table of the current table, or it might have fewer if
+ * the table has had columns added by ALTER TABLE. Ignore extra columns
+ * and assume NULL for missing columns, the same as heap_getattr would do.
+ * We also have to skip over dropped columns in either the source or
+ * destination.
*
* If we have no tuple data at all, we'll assign NULL to all columns of
* the row variable.
{
value = (Datum) 0;
isnull = true;
+
+ /*
+ * InvalidOid is OK because exec_assign_value doesn't care
+ * about the type of a source NULL
+ */
valtype = InvalidOid;
}
elog(ERROR, "dropped rowtype entry for non-dropped column");
exec_eval_datum(estate, estate->datums[row->varnos[i]],
- InvalidOid, &fieldtypeid, &dvalues[i], &nulls[i]);
+ &fieldtypeid, &dvalues[i], &nulls[i]);
if (fieldtypeid != tupdesc->attrs[i]->atttypid)
return NULL;
}
Oid reqtype, int32 reqtypmod,
bool isnull)
{
- if (!isnull)
+ if (valtype != reqtype || reqtypmod != -1)
{
- if (valtype != reqtype || reqtypmod != -1)
- {
- Oid typinput;
- Oid typioparam;
- FmgrInfo finfo_input;
+ Oid typinput;
+ Oid typioparam;
+ FmgrInfo finfo_input;
- getTypeInputInfo(reqtype, &typinput, &typioparam);
+ getTypeInputInfo(reqtype, &typinput, &typioparam);
- fmgr_info(typinput, &finfo_input);
+ fmgr_info(typinput, &finfo_input);
- value = exec_cast_value(value,
- valtype,
- reqtype,
- &finfo_input,
- typioparam,
- reqtypmod,
- isnull);
- }
+ value = exec_cast_value(value,
+ valtype,
+ reqtype,
+ &finfo_input,
+ typioparam,
+ reqtypmod,
+ isnull);
}
return value;
*/
expr->expr_simple_expr = tle->expr;
expr->expr_simple_state = NULL;
- expr->expr_simple_id = -1;
+ expr->expr_simple_lxid = InvalidLocalTransactionId;
/* Also stash away the expression result type */
expr->expr_simple_type = exprType((Node *) tle->expr);
}
-/*
- * Validates compatibility of supplied TupleDesc pair by checking number and type
- * of attributes.
- */
-static void
-validate_tupdesc_compat(TupleDesc expected, TupleDesc returned, const char *msg)
-{
- int i;
- const char *dropped_column_type = gettext_noop("N/A (dropped column)");
-
- if (!expected || !returned)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("%s", _(msg))));
-
- if (expected->natts != returned->natts)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("%s", _(msg)),
- errdetail("Number of returned columns (%d) does not match "
- "expected column count (%d).",
- returned->natts, expected->natts)));
-
- for (i = 0; i < expected->natts; i++)
- if (expected->attrs[i]->atttypid != returned->attrs[i]->atttypid)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("%s", _(msg)),
- errdetail("Returned type %s does not match expected type "
- "%s in column \"%s\".",
- OidIsValid(returned->attrs[i]->atttypid) ?
- format_type_be(returned->attrs[i]->atttypid) :
- _(dropped_column_type),
- OidIsValid(expected->attrs[i]->atttypid) ?
- format_type_be(expected->attrs[i]->atttypid) :
- _(dropped_column_type),
- NameStr(expected->attrs[i]->attname))));
-}
-
/* ----------
* exec_set_found Set the global found variable
* to true/false
/*
* plpgsql_create_econtext --- create an eval_econtext for the current function
*
- * We may need to create a new eval_estate too, if there's not one already
- * for the current (sub) transaction. The EState will be cleaned up at
- * (sub) transaction end.
+ * We may need to create a new simple_eval_estate too, if there's not one
+ * already for the current transaction. The EState will be cleaned up at
+ * transaction end.
*/
static void
plpgsql_create_econtext(PLpgSQL_execstate *estate)
{
- SubTransactionId my_subxid = GetCurrentSubTransactionId();
- SimpleEstateStackEntry *entry = simple_estate_stack;
+ SimpleEcontextStackEntry *entry;
- /* Create new EState if not one for current subxact */
- if (entry == NULL ||
- entry->xact_subxid != my_subxid)
+ /*
+ * Create an EState for evaluation of simple expressions, if there's not
+ * one already in the current transaction. The EState is made a child of
+ * TopTransactionContext so it will have the right lifespan.
+ */
+ if (simple_eval_estate == NULL)
{
MemoryContext oldcontext;
- /* Stack entries are kept in TopTransactionContext for simplicity */
- entry = (SimpleEstateStackEntry *)
- MemoryContextAlloc(TopTransactionContext,
- sizeof(SimpleEstateStackEntry));
-
- /* But each EState should be a child of its CurTransactionContext */
- oldcontext = MemoryContextSwitchTo(CurTransactionContext);
- entry->xact_eval_estate = CreateExecutorState();
+ oldcontext = MemoryContextSwitchTo(TopTransactionContext);
+ simple_eval_estate = CreateExecutorState();
MemoryContextSwitchTo(oldcontext);
+ }
- /* Assign a reasonably-unique ID to this EState */
- entry->xact_estate_simple_id = simple_estate_id_counter++;
- entry->xact_subxid = my_subxid;
+ /*
+ * Create a child econtext for the current function.
+ */
+ estate->eval_econtext = CreateExprContext(simple_eval_estate);
- entry->next = simple_estate_stack;
- simple_estate_stack = entry;
- }
+ /*
+ * Make a stack entry so we can clean up the econtext at subxact end.
+ * Stack entries are kept in TopTransactionContext for simplicity.
+ */
+ entry = (SimpleEcontextStackEntry *)
+ MemoryContextAlloc(TopTransactionContext,
+ sizeof(SimpleEcontextStackEntry));
- /* Link plpgsql estate to it */
- estate->eval_estate = entry->xact_eval_estate;
- estate->eval_estate_simple_id = entry->xact_estate_simple_id;
+ entry->stack_econtext = estate->eval_econtext;
+ entry->xact_subxid = GetCurrentSubTransactionId();
- /* And create a child econtext for the current function */
- estate->eval_econtext = CreateExprContext(estate->eval_estate);
+ entry->next = simple_econtext_stack;
+ simple_econtext_stack = entry;
+}
+
+/*
+ * plpgsql_destroy_econtext --- destroy function's econtext
+ *
+ * We check that it matches the top stack entry, and destroy the stack
+ * entry along with the context.
+ */
+static void
+plpgsql_destroy_econtext(PLpgSQL_execstate *estate)
+{
+ SimpleEcontextStackEntry *next;
+
+ Assert(simple_econtext_stack != NULL);
+ Assert(simple_econtext_stack->stack_econtext == estate->eval_econtext);
+
+ next = simple_econtext_stack->next;
+ pfree(simple_econtext_stack);
+ simple_econtext_stack = next;
+
+ FreeExprContext(estate->eval_econtext, true);
+ estate->eval_econtext = NULL;
}
/*
* If we are doing a clean transaction shutdown, free the EState (so that
* any remaining resources will be released correctly). In an abort, we
* expect the regular abort recovery procedures to release everything of
- * interest. We don't need to free the individual stack entries since
- * TopTransactionContext is about to go away anyway.
- *
- * Note: if plpgsql_subxact_cb is doing its job, there should be at most
- * one stack entry, but we may as well code this as a loop.
+ * interest.
*/
if (event != XACT_EVENT_ABORT)
{
- while (simple_estate_stack != NULL)
- {
- FreeExecutorState(simple_estate_stack->xact_eval_estate);
- simple_estate_stack = simple_estate_stack->next;
- }
+ /* Shouldn't be any econtext stack entries left at commit */
+ Assert(simple_econtext_stack == NULL);
+
+ if (simple_eval_estate)
+ FreeExecutorState(simple_eval_estate);
+ simple_eval_estate = NULL;
}
else
- simple_estate_stack = NULL;
+ {
+ simple_econtext_stack = NULL;
+ simple_eval_estate = NULL;
+ }
}
/*
* plpgsql_subxact_cb --- post-subtransaction-commit-or-abort cleanup
*
- * If a simple-expression EState was created in the current subtransaction,
- * it has to be cleaned up.
+ * Make sure any simple-expression econtexts created in the current
+ * subtransaction get cleaned up. We have to do this explicitly because
+ * no other code knows which child econtexts of simple_eval_estate belong
+ * to which level of subxact.
*/
void
plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
if (event == SUBXACT_EVENT_START_SUB)
return;
- if (simple_estate_stack != NULL &&
- simple_estate_stack->xact_subxid == mySubid)
+ while (simple_econtext_stack != NULL &&
+ simple_econtext_stack->xact_subxid == mySubid)
{
- SimpleEstateStackEntry *next;
+ SimpleEcontextStackEntry *next;
- if (event == SUBXACT_EVENT_COMMIT_SUB)
- FreeExecutorState(simple_estate_stack->xact_eval_estate);
- next = simple_estate_stack->next;
- pfree(simple_estate_stack);
- simple_estate_stack = next;
+ FreeExprContext(simple_econtext_stack->stack_econtext,
+ (event == SUBXACT_EVENT_COMMIT_SUB));
+ next = simple_econtext_stack->next;
+ pfree(simple_econtext_stack);
+ simple_econtext_stack = next;
}
}
foreach(lc, params)
{
PLpgSQL_expr *param = (PLpgSQL_expr *) lfirst(lc);
- bool isnull;
+ bool isnull;
ppd->values[i] = exec_eval_expr(estate, param,
&isnull,
/* pass-by-ref non null values must be copied into plpgsql context */
if (!isnull)
{
- int16 typLen;
- bool typByVal;
+ int16 typLen;
+ bool typByVal;
get_typlenbyval(ppd->types[i], &typLen, &typByVal);
if (!typByVal)
static void
free_params_data(PreparedParamsData *ppd)
{
- int i;
+ int i;
for (i = 0; i < ppd->nargs; i++)
{
* Open portal for dynamic query
*/
static Portal
-exec_dynquery_with_params(PLpgSQL_execstate *estate, PLpgSQL_expr *dynquery,
- List *params)
+exec_dynquery_with_params(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *dynquery,
+ List *params,
+ const char *portalname,
+ int cursorOptions)
{
Portal portal;
Datum query;
char *querystr;
/*
- * Evaluate the string expression after the EXECUTE keyword. Its result
- * is the querystring we have to execute.
+ * Evaluate the string expression after the EXECUTE keyword. Its result is
+ * the querystring we have to execute.
*/
query = exec_eval_expr(estate, dynquery, &isnull, &restype);
if (isnull)
exec_eval_cleanup(estate);
/*
- * Open an implicit cursor for the query. We use SPI_cursor_open_with_args
- * even when there are no params, because this avoids making and freeing
- * one copy of the plan.
+ * Open an implicit cursor for the query. We use
+ * SPI_cursor_open_with_args even when there are no params, because this
+ * avoids making and freeing one copy of the plan.
*/
if (params)
{
PreparedParamsData *ppd;
ppd = exec_eval_using_params(estate, params);
- portal = SPI_cursor_open_with_args(NULL,
+ portal = SPI_cursor_open_with_args(portalname,
querystr,
ppd->nargs, ppd->types,
ppd->values, ppd->nulls,
- estate->readonly_func, 0);
+ estate->readonly_func,
+ cursorOptions);
free_params_data(ppd);
}
else
{
- portal = SPI_cursor_open_with_args(NULL,
+ portal = SPI_cursor_open_with_args(portalname,
querystr,
0, NULL,
NULL, NULL,
- estate->readonly_func, 0);
+ estate->readonly_func,
+ cursorOptions);
}
if (portal == NULL)