summaryrefslogtreecommitdiff
path: root/src/pl
diff options
context:
space:
mode:
authorTom Lane2009-11-04 22:26:08 +0000
committerTom Lane2009-11-04 22:26:08 +0000
commit9bedd128d6ed83798004b3c7ddc33f33703ccf23 (patch)
tree95a475a5da180f19c69b5bcf2f6e764b1bc69ea7 /src/pl
parent48912acc089a6148529f12ab0a75b1bf026f231d (diff)
Add support for invoking parser callback hooks via SPI and in cached plans.
As proof of concept, modify plpgsql to use the hooks. plpgsql is still inserting $n symbols textually, but the "back end" of the parsing process now goes through the ParamRef hook instead of using a fixed parameter-type array, and then execution only fetches actually-referenced parameters, using a hook added to ParamListInfo. Although there's a lot left to be done in plpgsql, this already cures the "if (TG_OP = 'INSERT' and NEW.foo ...)" problem, as illustrated by the changed regression test.
Diffstat (limited to 'src/pl')
-rw-r--r--src/pl/plpgsql/src/gram.y124
-rw-r--r--src/pl/plpgsql/src/pl_comp.c40
-rw-r--r--src/pl/plpgsql/src/pl_exec.c538
-rw-r--r--src/pl/plpgsql/src/pl_funcs.c23
-rw-r--r--src/pl/plpgsql/src/pl_handler.c11
-rw-r--r--src/pl/plpgsql/src/plpgsql.h40
6 files changed, 402 insertions, 374 deletions
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index c876b875e62..ec6b285bf2f 100644
--- a/src/pl/plpgsql/src/gram.y
+++ b/src/pl/plpgsql/src/gram.y
@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.128 2009/09/29 20:05:29 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.129 2009/11/04 22:26:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -109,7 +109,7 @@ static List *read_raise_options(void);
} loop_body;
List *list;
PLpgSQL_type *dtype;
- PLpgSQL_datum *scalar; /* a VAR, RECFIELD, or TRIGARG */
+ PLpgSQL_datum *scalar; /* a VAR or RECFIELD */
PLpgSQL_variable *variable; /* a VAR, REC, or ROW */
PLpgSQL_var *var;
PLpgSQL_row *row;
@@ -236,7 +236,7 @@ static List *read_raise_options(void);
*/
%token T_STRING
%token T_NUMBER
-%token T_SCALAR /* a VAR, RECFIELD, or TRIGARG */
+%token T_SCALAR /* a VAR or RECFIELD */
%token T_ROW
%token T_RECORD
%token T_DTYPE
@@ -1903,44 +1903,6 @@ lno :
%%
-#define MAX_EXPR_PARAMS 1024
-
-/*
- * determine the expression parameter position to use for a plpgsql datum
- *
- * It is important that any given plpgsql datum map to just one parameter.
- * We used to be sloppy and assign a separate parameter for each occurrence
- * of a datum reference, but that fails for situations such as "select DATUM
- * from ... group by DATUM".
- *
- * The params[] array must be of size MAX_EXPR_PARAMS.
- */
-static int
-assign_expr_param(int dno, int *params, int *nparams)
-{
- int i;
-
- /* already have an instance of this dno? */
- for (i = 0; i < *nparams; i++)
- {
- if (params[i] == dno)
- return i+1;
- }
- /* check for array overflow */
- if (*nparams >= MAX_EXPR_PARAMS)
- {
- plpgsql_error_lineno = plpgsql_scanner_lineno();
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("too many variables specified in SQL statement")));
- }
- /* add new parameter dno to array */
- params[*nparams] = dno;
- (*nparams)++;
- return *nparams;
-}
-
-
/* Convenience routine to read an expression with one possible terminator */
PLpgSQL_expr *
plpgsql_read_expression(int until, const char *expected)
@@ -1993,8 +1955,7 @@ read_sql_construct(int until,
int lno;
StringInfoData ds;
int parenlevel = 0;
- int nparams = 0;
- int params[MAX_EXPR_PARAMS];
+ Bitmapset *paramnos = NULL;
char buf[32];
PLpgSQL_expr *expr;
@@ -2047,24 +2008,21 @@ read_sql_construct(int until,
switch (tok)
{
case T_SCALAR:
- snprintf(buf, sizeof(buf), " $%d ",
- assign_expr_param(yylval.scalar->dno,
- params, &nparams));
+ snprintf(buf, sizeof(buf), " $%d ", yylval.scalar->dno + 1);
appendStringInfoString(&ds, buf);
+ paramnos = bms_add_member(paramnos, yylval.scalar->dno);
break;
case T_ROW:
- snprintf(buf, sizeof(buf), " $%d ",
- assign_expr_param(yylval.row->dno,
- params, &nparams));
+ snprintf(buf, sizeof(buf), " $%d ", yylval.row->dno + 1);
appendStringInfoString(&ds, buf);
+ paramnos = bms_add_member(paramnos, yylval.row->dno);
break;
case T_RECORD:
- snprintf(buf, sizeof(buf), " $%d ",
- assign_expr_param(yylval.rec->dno,
- params, &nparams));
+ snprintf(buf, sizeof(buf), " $%d ", yylval.rec->dno + 1);
appendStringInfoString(&ds, buf);
+ paramnos = bms_add_member(paramnos, yylval.rec->dno);
break;
default:
@@ -2076,13 +2034,11 @@ read_sql_construct(int until,
if (endtoken)
*endtoken = tok;
- expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
+ expr = palloc0(sizeof(PLpgSQL_expr));
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = pstrdup(ds.data);
expr->plan = NULL;
- expr->nparams = nparams;
- while(nparams-- > 0)
- expr->params[nparams] = params[nparams];
+ expr->paramnos = paramnos;
pfree(ds.data);
if (valid_sql)
@@ -2162,8 +2118,7 @@ static PLpgSQL_stmt *
make_execsql_stmt(const char *sqlstart, int lineno)
{
StringInfoData ds;
- int nparams = 0;
- int params[MAX_EXPR_PARAMS];
+ Bitmapset *paramnos = NULL;
char buf[32];
PLpgSQL_stmt_execsql *execsql;
PLpgSQL_expr *expr;
@@ -2214,24 +2169,21 @@ make_execsql_stmt(const char *sqlstart, int lineno)
switch (tok)
{
case T_SCALAR:
- snprintf(buf, sizeof(buf), " $%d ",
- assign_expr_param(yylval.scalar->dno,
- params, &nparams));
+ snprintf(buf, sizeof(buf), " $%d ", yylval.scalar->dno + 1);
appendStringInfoString(&ds, buf);
+ paramnos = bms_add_member(paramnos, yylval.scalar->dno);
break;
case T_ROW:
- snprintf(buf, sizeof(buf), " $%d ",
- assign_expr_param(yylval.row->dno,
- params, &nparams));
+ snprintf(buf, sizeof(buf), " $%d ", yylval.row->dno + 1);
appendStringInfoString(&ds, buf);
+ paramnos = bms_add_member(paramnos, yylval.row->dno);
break;
case T_RECORD:
- snprintf(buf, sizeof(buf), " $%d ",
- assign_expr_param(yylval.rec->dno,
- params, &nparams));
+ snprintf(buf, sizeof(buf), " $%d ", yylval.rec->dno + 1);
appendStringInfoString(&ds, buf);
+ paramnos = bms_add_member(paramnos, yylval.rec->dno);
break;
default:
@@ -2240,13 +2192,11 @@ make_execsql_stmt(const char *sqlstart, int lineno)
}
}
- expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
+ expr = palloc0(sizeof(PLpgSQL_expr));
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = pstrdup(ds.data);
expr->plan = NULL;
- expr->nparams = nparams;
- while(nparams-- > 0)
- expr->params[nparams] = params[nparams];
+ expr->paramnos = paramnos;
pfree(ds.data);
check_sql_expr(expr->query);
@@ -2600,9 +2550,6 @@ check_assignable(PLpgSQL_datum *datum)
case PLPGSQL_DTYPE_ARRAYELEM:
/* always assignable? */
break;
- case PLPGSQL_DTYPE_TRIGARG:
- yyerror("cannot assign to tg_argv");
- break;
default:
elog(ERROR, "unrecognized dtype: %d", datum->dtype);
break;
@@ -3095,24 +3042,10 @@ make_case(int lineno, PLpgSQL_expr *t_expr,
{
PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
PLpgSQL_expr *expr = cwt->expr;
- int nparams = expr->nparams;
- PLpgSQL_expr *new_expr;
StringInfoData ds;
/* Must add the CASE variable as an extra param to expression */
- if (nparams >= MAX_EXPR_PARAMS)
- {
- plpgsql_error_lineno = cwt->lineno;
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("too many variables specified in SQL statement")));
- }
-
- new_expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * (nparams + 1) - sizeof(int));
- memcpy(new_expr, expr,
- sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
- new_expr->nparams = nparams + 1;
- new_expr->params[nparams] = t_varno;
+ expr->paramnos = bms_add_member(expr->paramnos, t_varno);
/* copy expression query without SELECT keyword (expr->query + 7) */
Assert(strncmp(expr->query, "SELECT ", 7) == 0);
@@ -3120,17 +3053,14 @@ make_case(int lineno, PLpgSQL_expr *t_expr,
/* And do the string hacking */
initStringInfo(&ds);
- appendStringInfo(&ds, "SELECT $%d IN(%s)",
- nparams + 1,
- expr->query + 7);
+ appendStringInfo(&ds, "SELECT $%d IN (%s)",
+ t_varno + 1,
+ expr->query + 7);
- new_expr->query = pstrdup(ds.data);
-
- pfree(ds.data);
pfree(expr->query);
- pfree(expr);
+ expr->query = pstrdup(ds.data);
- cwt->expr = new_expr;
+ pfree(ds.data);
}
}
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 604b7dbf086..ef0dcb0f8df 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.139 2009/09/22 23:43:42 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.140 2009/11/04 22:26:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -624,20 +624,24 @@ do_compile(FunctionCallInfo fcinfo,
true);
function->tg_table_name_varno = var->dno;
-
- /* add variable tg_table_schema */
+ /* add the variable tg_table_schema */
var = plpgsql_build_variable("tg_table_schema", 0,
plpgsql_build_datatype(NAMEOID, -1),
true);
function->tg_table_schema_varno = var->dno;
-
/* Add the variable tg_nargs */
var = plpgsql_build_variable("tg_nargs", 0,
plpgsql_build_datatype(INT4OID, -1),
true);
function->tg_nargs_varno = var->dno;
+ /* Add the variable tg_argv */
+ var = plpgsql_build_variable("tg_argv", 0,
+ plpgsql_build_datatype(TEXTARRAYOID, -1),
+ true);
+ function->tg_argv_varno = var->dno;
+
break;
default:
@@ -932,34 +936,6 @@ plpgsql_parse_word(const char *word)
plpgsql_convert_ident(word, cp, 1);
/*
- * Recognize tg_argv when compiling triggers (XXX this sucks, it should be
- * a regular variable in the namestack)
- */
- if (plpgsql_curr_compile->fn_is_trigger)
- {
- if (strcmp(cp[0], "tg_argv") == 0)
- {
- bool save_spacescanned = plpgsql_SpaceScanned;
- PLpgSQL_trigarg *trigarg;
-
- trigarg = palloc0(sizeof(PLpgSQL_trigarg));
- trigarg->dtype = PLPGSQL_DTYPE_TRIGARG;
-
- if (plpgsql_yylex() != '[')
- plpgsql_yyerror("expected \"[\"");
-
- trigarg->argnum = plpgsql_read_expression(']', "]");
-
- plpgsql_adddatum((PLpgSQL_datum *) trigarg);
- plpgsql_yylval.scalar = (PLpgSQL_datum *) trigarg;
-
- plpgsql_SpaceScanned = save_spacescanned;
- pfree(cp[0]);
- return T_SCALAR;
- }
- }
-
- /*
* Do a lookup on the compiler's namestack
*/
nse = plpgsql_ns_lookup(cp[0], NULL, NULL, NULL);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index a3a39e8e2d8..f7920206382 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.248 2009/08/06 20:44:31 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.249 2009/11/04 22:26:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -26,6 +26,7 @@
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "parser/parse_node.h"
#include "parser/scansup.h"
#include "storage/proc.h"
#include "tcop/tcopprot.h"
@@ -154,10 +155,11 @@ static void exec_assign_value(PLpgSQL_execstate *estate,
Datum value, Oid valtype, bool *isNull);
static void exec_eval_datum(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum,
- Oid expectedtypeid,
Oid *typeid,
Datum *value,
bool *isnull);
+static Oid exec_get_datum_type(PLpgSQL_execstate *estate,
+ PLpgSQL_datum *datum);
static int exec_eval_integer(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr,
bool *isNull);
@@ -172,8 +174,11 @@ 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 ParamListInfo setup_param_list(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr);
+static void plpgsql_parser_setup(ParseState *pstate, PLpgSQL_expr *expr);
+static Node *plpgsql_param_ref(ParseState *pstate, ParamRef *pref);
+static void plpgsql_param_fetch(ParamListInfo params, int paramid);
static void exec_move_row(PLpgSQL_execstate *estate,
PLpgSQL_rec *rec,
PLpgSQL_row *row,
@@ -514,12 +519,20 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
/*
* Put the OLD and NEW tuples into record variables
+ *
+ * We make the tupdescs available in both records even though only one
+ * may have a value. This allows parsing of record references to succeed
+ * in functions that are used for multiple trigger types. For example,
+ * we might have a test like "if (TG_OP = 'INSERT' and NEW.foo = 'xyz')",
+ * which should parse regardless of the current trigger type.
*/
rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]);
rec_new->freetup = false;
+ rec_new->tupdesc = trigdata->tg_relation->rd_att;
rec_new->freetupdesc = false;
rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
rec_old->freetup = false;
+ rec_old->tupdesc = trigdata->tg_relation->rd_att;
rec_old->freetupdesc = false;
if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
@@ -528,30 +541,22 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
* Per-statement triggers don't use OLD/NEW variables
*/
rec_new->tup = NULL;
- rec_new->tupdesc = NULL;
rec_old->tup = NULL;
- rec_old->tupdesc = NULL;
}
else if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
{
rec_new->tup = trigdata->tg_trigtuple;
- rec_new->tupdesc = trigdata->tg_relation->rd_att;
rec_old->tup = NULL;
- rec_old->tupdesc = NULL;
}
else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
{
rec_new->tup = trigdata->tg_newtuple;
- rec_new->tupdesc = trigdata->tg_relation->rd_att;
rec_old->tup = trigdata->tg_trigtuple;
- rec_old->tupdesc = trigdata->tg_relation->rd_att;
}
else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
{
rec_new->tup = NULL;
- rec_new->tupdesc = NULL;
rec_old->tup = trigdata->tg_trigtuple;
- rec_old->tupdesc = trigdata->tg_relation->rd_att;
}
else
elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
@@ -631,19 +636,36 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
var->isnull = false;
var->freeval = false;
- /*
- * Store the trigger argument values into the special execution state
- * variables
- */
- estate.err_text = gettext_noop("while storing call arguments into local variables");
- estate.trig_nargs = trigdata->tg_trigger->tgnargs;
- if (estate.trig_nargs == 0)
- estate.trig_argv = NULL;
+ var = (PLpgSQL_var *) (estate.datums[func->tg_argv_varno]);
+ if (trigdata->tg_trigger->tgnargs > 0)
+ {
+ /*
+ * For historical reasons, tg_argv[] subscripts start at zero not one.
+ * So we can't use construct_array().
+ */
+ int nelems = trigdata->tg_trigger->tgnargs;
+ Datum *elems;
+ int dims[1];
+ int lbs[1];
+
+ elems = palloc(sizeof(Datum) * nelems);
+ for (i = 0; i < nelems; i++)
+ elems[i] = CStringGetTextDatum(trigdata->tg_trigger->tgargs[i]);
+ dims[0] = nelems;
+ lbs[0] = 0;
+
+ var->value = PointerGetDatum(construct_md_array(elems, NULL,
+ 1, dims, lbs,
+ TEXTOID,
+ -1, false, 'i'));
+ var->isnull = false;
+ var->freeval = true;
+ }
else
{
- estate.trig_argv = palloc(sizeof(Datum) * estate.trig_nargs);
- for (i = 0; i < trigdata->tg_trigger->tgnargs; i++)
- estate.trig_argv[i] = CStringGetTextDatum(trigdata->tg_trigger->tgargs[i]);
+ var->value = (Datum) 0;
+ var->isnull = true;
+ var->freeval = false;
}
estate.err_text = gettext_noop("during function entry");
@@ -756,10 +778,6 @@ plpgsql_exec_error_callback(void *arg)
{
PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
- /* safety check, shouldn't happen */
- if (estate->err_func == NULL)
- return;
-
/* if we are doing RAISE, don't report its location */
if (estate->err_text == raise_skip_msg)
return;
@@ -784,7 +802,7 @@ plpgsql_exec_error_callback(void *arg)
* 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));
}
@@ -795,7 +813,7 @@ plpgsql_exec_error_callback(void *arg)
* arguments into local variables"
*/
errcontext("PL/pgSQL function \"%s\" %s",
- estate->err_func->fn_name,
+ estate->func->fn_name,
_(estate->err_text));
}
}
@@ -803,13 +821,13 @@ plpgsql_exec_error_callback(void *arg)
{
/* translator: last %s is a plpgsql statement type name */
errcontext("PL/pgSQL function \"%s\" line %d at %s",
- estate->err_func->fn_name,
+ estate->func->fn_name,
estate->err_stmt->lineno,
plpgsql_stmt_typename(estate->err_stmt));
}
else
errcontext("PL/pgSQL function \"%s\"",
- estate->err_func->fn_name);
+ estate->func->fn_name);
}
@@ -856,7 +874,6 @@ copy_plpgsql_datum(PLpgSQL_datum *datum)
case PLPGSQL_DTYPE_ROW:
case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
- case PLPGSQL_DTYPE_TRIGARG:
/*
* These datum records are read-only at runtime, so no need to
@@ -977,10 +994,13 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
if (rec->freetup)
{
heap_freetuple(rec->tup);
- FreeTupleDesc(rec->tupdesc);
rec->freetup = false;
}
-
+ if (rec->freetupdesc)
+ {
+ FreeTupleDesc(rec->tupdesc);
+ rec->freetupdesc = false;
+ }
rec->tup = NULL;
rec->tupdesc = NULL;
}
@@ -1885,10 +1905,9 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
PLpgSQL_var *curvar;
char *curname = NULL;
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
@@ -1954,19 +1973,25 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
exec_prepare_plan(estate, query, curvar->cursor_options);
/*
- * Now build up the values and nulls arguments for SPI_execute_plan()
+ * Set up ParamListInfo (note this is only carrying a hook function,
+ * not any actual data values, at this point)
*/
- eval_expr_params(estate, query, &values, &nulls);
+ paramLI = setup_param_list(estate, query);
/*
- * Open the cursor
+ * Open the cursor (the paramlist will get copied into the portal)
*/
- portal = SPI_cursor_open(curname, query->plan, values, nulls,
- estate->readonly_func);
+ portal = SPI_cursor_open_with_paramlist(curname, query->plan,
+ paramLI,
+ estate->readonly_func);
if (portal == NULL)
elog(ERROR, "could not open cursor: %s",
SPI_result_code_string(SPI_result));
+ /* don't need paramlist any more */
+ if (paramLI)
+ pfree(paramLI);
+
/*
* If cursor variable was NULL, store the generated portal name in it
*/
@@ -1992,8 +2017,6 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
curvar->isnull = true;
}
- pfree(values);
- pfree(nulls);
if (curname)
pfree(curname);
@@ -2599,6 +2622,11 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
PLpgSQL_function *func,
ReturnSetInfo *rsi)
{
+ /* this link will be restored at exit from plpgsql_call_handler */
+ func->cur_estate = estate;
+
+ estate->func = func;
+
estate->retval = (Datum) 0;
estate->retisnull = true;
estate->rettype = InvalidOid;
@@ -2616,9 +2644,6 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
estate->tuple_store_cxt = 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);
@@ -2627,11 +2652,14 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
estate->eval_tuptable = NULL;
estate->eval_processed = 0;
estate->eval_lastoid = InvalidOid;
+ estate->eval_econtext = NULL;
+ estate->cur_expr = NULL;
- estate->err_func = func;
estate->err_stmt = NULL;
estate->err_text = NULL;
+ estate->plugin_info = NULL;
+
/*
* Create an EState and ExprContext for evaluation of simple expressions.
*/
@@ -2682,30 +2710,20 @@ static void
exec_prepare_plan(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, int cursorOptions)
{
- int i;
SPIPlanPtr plan;
- Oid *argtypes;
/*
- * We need a temporary argtypes array to load with data. (The finished
- * plan structure will contain a copy of it.)
+ * The grammar can't conveniently set expr->func while building the
+ * parse tree, so make sure it's set before parser hooks need it.
*/
- argtypes = (Oid *) palloc(expr->nparams * sizeof(Oid));
-
- for (i = 0; i < expr->nparams; i++)
- {
- Datum paramval;
- bool paramisnull;
-
- exec_eval_datum(estate, estate->datums[expr->params[i]],
- InvalidOid,
- &argtypes[i], &paramval, &paramisnull);
- }
+ expr->func = estate->func;
/*
* Generate and save the plan
*/
- plan = SPI_prepare_cursor(expr->query, expr->nparams, argtypes,
+ plan = SPI_prepare_params(expr->query,
+ (ParserSetupHook) plpgsql_parser_setup,
+ (void *) expr,
cursorOptions);
if (plan == NULL)
{
@@ -2722,17 +2740,13 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
errmsg("cannot begin/end transactions in PL/pgSQL"),
errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
default:
- elog(ERROR, "SPI_prepare_cursor failed for \"%s\": %s",
+ elog(ERROR, "SPI_prepare_params failed for \"%s\": %s",
expr->query, SPI_result_code_string(SPI_result));
}
}
expr->plan = SPI_saveplan(plan);
SPI_freeplan(plan);
- plan = expr->plan;
- expr->plan_argtypes = plan->argtypes;
exec_simple_check_plan(expr);
-
- pfree(argtypes);
}
@@ -2744,8 +2758,7 @@ static int
exec_stmt_execsql(PLpgSQL_execstate *estate,
PLpgSQL_stmt_execsql *stmt)
{
- Datum *values;
- char *nulls;
+ ParamListInfo paramLI;
long tcount;
int rc;
PLpgSQL_expr *expr = stmt->sqlstmt;
@@ -2782,9 +2795,10 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
}
/*
- * Now build up the values and nulls arguments for SPI_execute_plan()
+ * Set up ParamListInfo (note this is only carrying a hook function,
+ * not any actual data values, at this point)
*/
- eval_expr_params(estate, expr, &values, &nulls);
+ paramLI = setup_param_list(estate, expr);
/*
* If we have INTO, then we only need one row back ... but if we have INTO
@@ -2810,8 +2824,8 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
/*
* Execute the plan
*/
- rc = SPI_execute_plan(expr->plan, values, nulls,
- estate->readonly_func, tcount);
+ rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
+ estate->readonly_func, tcount);
/*
* Check for error, and set FOUND if appropriate (for historical reasons
@@ -2852,7 +2866,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
break;
default:
- elog(ERROR, "SPI_execute_plan failed executing query \"%s\": %s",
+ elog(ERROR, "SPI_execute_plan_with_paramlist failed executing query \"%s\": %s",
expr->query, SPI_result_code_string(rc));
}
@@ -2919,8 +2933,8 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
(rc == SPI_OK_SELECT) ? errhint("If you want to discard the results of a SELECT, use PERFORM instead.") : 0));
}
- pfree(values);
- pfree(nulls);
+ if (paramLI)
+ pfree(paramLI);
return PLPGSQL_RC_OK;
}
@@ -3142,8 +3156,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
char *curname = NULL;
PLpgSQL_expr *query;
Portal portal;
- Datum *values;
- char *nulls;
+ ParamListInfo paramLI;
bool isnull;
/* ----------
@@ -3280,15 +3293,17 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
}
/*
- * Now build up the values and nulls arguments for SPI_execute_plan()
+ * Set up ParamListInfo (note this is only carrying a hook function,
+ * not any actual data values, at this point)
*/
- eval_expr_params(estate, query, &values, &nulls);
+ paramLI = setup_param_list(estate, query);
/*
* Open the cursor
*/
- portal = SPI_cursor_open(curname, query->plan, values, nulls,
- estate->readonly_func);
+ portal = SPI_cursor_open_with_paramlist(curname, query->plan,
+ paramLI,
+ estate->readonly_func);
if (portal == NULL)
elog(ERROR, "could not open cursor: %s",
SPI_result_code_string(SPI_result));
@@ -3299,10 +3314,10 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
if (curname == NULL)
assign_text_var(curvar, portal->name);
- pfree(values);
- pfree(nulls);
if (curname)
pfree(curname);
+ if (paramLI)
+ pfree(paramLI);
return PLPGSQL_RC_OK;
}
@@ -3755,7 +3770,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
} while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
/* Fetch current value of array datum */
- exec_eval_datum(estate, target, InvalidOid,
+ exec_eval_datum(estate, target,
&arraytypeid, &oldarraydatum, &oldarrayisnull);
arrayelemtypeid = get_element_type(arraytypeid);
@@ -3860,8 +3875,6 @@ exec_assign_value(PLpgSQL_execstate *estate,
*
* The type oid, value in Datum format, and null flag are returned.
*
- * If expectedtypeid isn't InvalidOid, it is checked against the actual type.
- *
* At present this doesn't handle PLpgSQL_expr or PLpgSQL_arrayelem datums.
*
* NOTE: caller must not modify the returned value, since it points right
@@ -3872,7 +3885,6 @@ exec_assign_value(PLpgSQL_execstate *estate,
static void
exec_eval_datum(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum,
- Oid expectedtypeid,
Oid *typeid,
Datum *value,
bool *isnull)
@@ -3888,11 +3900,6 @@ exec_eval_datum(PLpgSQL_execstate *estate,
*typeid = var->datatype->typoid;
*value = var->value;
*isnull = var->isnull;
- if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("type of \"%s\" does not match that when preparing the plan",
- var->refname)));
break;
}
@@ -3913,11 +3920,6 @@ exec_eval_datum(PLpgSQL_execstate *estate,
*typeid = row->rowtupdesc->tdtypeid;
*value = HeapTupleGetDatum(tup);
*isnull = false;
- if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("type of \"%s\" does not match that when preparing the plan",
- row->refname)));
break;
}
@@ -3950,11 +3952,6 @@ exec_eval_datum(PLpgSQL_execstate *estate,
*typeid = rec->tupdesc->tdtypeid;
*value = HeapTupleGetDatum(&worktup);
*isnull = false;
- if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("type of \"%s\" does not match that when preparing the plan",
- rec->refname)));
break;
}
@@ -3979,42 +3976,96 @@ exec_eval_datum(PLpgSQL_execstate *estate,
rec->refname, recfield->fieldname)));
*typeid = SPI_gettypeid(rec->tupdesc, fno);
*value = SPI_getbinval(rec->tup, rec->tupdesc, fno, isnull);
- if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
+ break;
+ }
+
+ default:
+ elog(ERROR, "unrecognized dtype: %d", datum->dtype);
+ }
+}
+
+/*
+ * exec_get_datum_type Get datatype of a PLpgSQL_datum
+ *
+ * This is the same logic as in exec_eval_datum, except that it can handle
+ * some cases where exec_eval_datum has to fail; specifically, we may have
+ * a tupdesc but no row value for a record variable. (This currently can
+ * happen only for a trigger's NEW/OLD records.)
+ */
+static 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 tg_argv[%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;
}
/* ----------
@@ -4145,8 +4196,7 @@ static int
exec_run_select(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, long maxtuples, Portal *portalP)
{
- Datum *values;
- char *nulls;
+ ParamListInfo paramLI;
int rc;
/*
@@ -4156,30 +4206,32 @@ exec_run_select(PLpgSQL_execstate *estate,
exec_prepare_plan(estate, expr, 0);
/*
- * Now build up the values and nulls arguments for SPI_execute_plan()
+ * Set up ParamListInfo (note this is only carrying a hook function,
+ * not any actual data values, at this point)
*/
- eval_expr_params(estate, expr, &values, &nulls);
+ paramLI = setup_param_list(estate, expr);
/*
* If a portal was requested, put the query into the portal
*/
if (portalP != NULL)
{
- *portalP = SPI_cursor_open(NULL, expr->plan, values, nulls,
- estate->readonly_func);
+ *portalP = SPI_cursor_open_with_paramlist(NULL, expr->plan,
+ paramLI,
+ estate->readonly_func);
if (*portalP == NULL)
elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
expr->query, SPI_result_code_string(SPI_result));
- pfree(values);
- pfree(nulls);
+ if (paramLI)
+ pfree(paramLI);
return SPI_OK_CURSOR;
}
/*
* Execute the query
*/
- rc = SPI_execute_plan(expr->plan, values, nulls,
- estate->readonly_func, maxtuples);
+ rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
+ estate->readonly_func, maxtuples);
if (rc != SPI_OK_SELECT)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -4191,8 +4243,8 @@ exec_run_select(PLpgSQL_execstate *estate,
estate->eval_processed = SPI_processed;
estate->eval_lastoid = SPI_lastoid;
- pfree(values);
- pfree(nulls);
+ if (paramLI)
+ pfree(paramLI);
return rc;
}
@@ -4378,7 +4430,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
CachedPlanSource *plansource;
CachedPlan *cplan;
ParamListInfo paramLI;
- int i;
+ PLpgSQL_expr *save_cur_expr;
MemoryContext oldcontext;
/*
@@ -4426,42 +4478,6 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
}
/*
- * Param list can live in econtext's temporary memory context.
- *
- * XXX think about avoiding repeated palloc's for param lists? Beware
- * however that this routine is re-entrant: exec_eval_datum() can call it
- * back for subscript evaluation, and so there can be a need to have more
- * than one active param list.
- */
- if (expr->nparams > 0)
- {
- /* sizeof(ParamListInfoData) includes the first array element */
- paramLI = (ParamListInfo)
- MemoryContextAlloc(econtext->ecxt_per_tuple_memory,
- sizeof(ParamListInfoData) +
- (expr->nparams - 1) *sizeof(ParamExternData));
- paramLI->numParams = expr->nparams;
-
- for (i = 0; i < expr->nparams; i++)
- {
- ParamExternData *prm = &paramLI->params[i];
- PLpgSQL_datum *datum = estate->datums[expr->params[i]];
-
- prm->pflags = 0;
- exec_eval_datum(estate, datum, expr->plan_argtypes[i],
- &prm->ptype,
- &prm->value, &prm->isnull);
- }
- }
- else
- paramLI = NULL;
-
- /*
- * Now we can safely make the econtext point to the param list.
- */
- econtext->ecxt_param_list_info = paramLI;
-
- /*
* We have to do some of the things SPI_execute_plan would do, in
* particular advance the snapshot if we are in a non-read-only function.
* Without this, stable functions within the expression would fail to see
@@ -4477,17 +4493,37 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
}
/*
+ * 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
*/
*result = ExecEvalExpr(expr->expr_simple_state,
econtext,
isNull,
NULL);
- MemoryContextSwitchTo(oldcontext);
+
+ /* Assorted cleanup */
+ estate->cur_expr = save_cur_expr;
if (!estate->readonly_func)
PopActiveSnapshot();
+ MemoryContextSwitchTo(oldcontext);
+
SPI_pop();
/*
@@ -4503,32 +4539,136 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
/*
- * Build up the values and nulls arguments for SPI_execute_plan()
+ * Create a ParamListInfo to pass to SPI
+ *
+ * The ParamListInfo array is initially all zeroes, in particular the
+ * ptype values are all InvalidOid. This causes the executor to call the
+ * paramFetch hook each time it wants a value. We thus evaluate only the
+ * parameters actually demanded.
+ *
+ * The result is a locally palloc'd array that should be pfree'd after use;
+ * but note it can be NULL.
*/
-static void
-eval_expr_params(PLpgSQL_execstate *estate,
- PLpgSQL_expr *expr, Datum **p_values, char **p_nulls)
+static ParamListInfo
+setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
{
- Datum *values;
- char *nulls;
- int i;
-
- *p_values = values = (Datum *) palloc(expr->nparams * sizeof(Datum));
- *p_nulls = nulls = (char *) palloc(expr->nparams * sizeof(char));
+ ParamListInfo paramLI;
- for (i = 0; i < expr->nparams; i++)
+ /*
+ * Could we re-use these arrays instead of palloc'ing a new one each
+ * time? However, we'd have to zero the array each time anyway,
+ * since new values might have been assigned to the variables.
+ */
+ if (estate->ndatums > 0)
{
- PLpgSQL_datum *datum = estate->datums[expr->params[i]];
- Oid paramtypeid;
- bool paramisnull;
-
- exec_eval_datum(estate, datum, expr->plan_argtypes[i],
- &paramtypeid, &values[i], &paramisnull);
- if (paramisnull)
- nulls[i] = 'n';
- else
- nulls[i] = ' ';
+ /* sizeof(ParamListInfoData) includes the first array element */
+ paramLI = (ParamListInfo)
+ palloc0(sizeof(ParamListInfoData) +
+ (estate->ndatums - 1) * sizeof(ParamExternData));
+ paramLI->paramFetch = plpgsql_param_fetch;
+ paramLI->paramFetchArg = (void *) estate;
+ paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
+ paramLI->parserSetupArg = (void *) expr;
+ paramLI->numParams = estate->ndatums;
+
+ /*
+ * Set up link to active expr where the hook functions can find it.
+ * Callers must save and restore cur_expr if there is any chance
+ * that they are interrupting an active use of parameters.
+ */
+ estate->cur_expr = expr;
+
+ /*
+ * Also make sure this is set before parser hooks need it. There
+ * is no need to save and restore, since the value is always correct
+ * once set.
+ */
+ expr->func = estate->func;
}
+ else
+ paramLI = NULL;
+ return paramLI;
+}
+
+/*
+ * plpgsql_parser_setup set up parser hooks for dynamic parameters
+ */
+static void
+plpgsql_parser_setup(ParseState *pstate, PLpgSQL_expr *expr)
+{
+ pstate->p_ref_hook_state = (void *) expr;
+ pstate->p_paramref_hook = plpgsql_param_ref;
+ /* no need to use p_coerce_param_hook */
+}
+
+/*
+ * plpgsql_param_ref parser callback for ParamRefs ($n symbols)
+ */
+static Node *
+plpgsql_param_ref(ParseState *pstate, ParamRef *pref)
+{
+ int paramno = pref->number;
+ PLpgSQL_expr *expr = (PLpgSQL_expr *) pstate->p_ref_hook_state;
+ PLpgSQL_execstate *estate;
+ Param *param;
+
+ /* Let's just check parameter number is in range */
+ if (!bms_is_member(paramno-1, expr->paramnos))
+ return NULL;
+
+ /*
+ * We use the function's current estate to resolve parameter data types.
+ * This is really pretty bogus because there is no provision for updating
+ * plans when those types change ...
+ */
+ estate = expr->func->cur_estate;
+ Assert(paramno <= estate->ndatums);
+
+ param = makeNode(Param);
+ param->paramkind = PARAM_EXTERN;
+ param->paramid = paramno;
+ param->paramtype = exec_get_datum_type(estate,
+ estate->datums[paramno-1]);
+ param->paramtypmod = -1;
+ param->location = pref->location;
+
+ return (Node *) param;
+}
+
+/*
+ * plpgsql_param_fetch paramFetch callback for dynamic parameter fetch
+ */
+static void
+plpgsql_param_fetch(ParamListInfo params, int paramid)
+{
+ int dno;
+ PLpgSQL_execstate *estate;
+ PLpgSQL_expr *expr;
+ PLpgSQL_datum *datum;
+ ParamExternData *prm;
+
+ /* paramid's are 1-based, but dnos are 0-based */
+ dno = paramid - 1;
+ Assert(dno >= 0 && dno < params->numParams);
+
+ /* fetch back the hook data */
+ estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ expr = estate->cur_expr;
+ Assert(params->numParams == estate->ndatums);
+
+ /*
+ * Do nothing if asked for a value that's not supposed to be used by
+ * this SQL expression. This avoids unwanted evaluations when functions
+ * such as copyParamList try to materialize all the values.
+ */
+ if (!bms_is_member(dno, expr->paramnos))
+ return;
+
+ /* OK, evaluate the value and store into the appropriate paramlist slot */
+ datum = estate->datums[dno];
+ prm = &params->params[dno];
+ exec_eval_datum(estate, datum,
+ &prm->ptype, &prm->value, &prm->isnull);
}
@@ -4710,7 +4850,7 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
elog(ERROR, "dropped rowtype entry for non-dropped column");
exec_eval_datum(estate, estate->datums[row->varnos[i]],
- InvalidOid, &fieldtypeid, &dvalues[i], &nulls[i]);
+ &fieldtypeid, &dvalues[i], &nulls[i]);
if (fieldtypeid != tupdesc->attrs[i]->atttypid)
return NULL;
}
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index d814e8f4f3c..274d0271141 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.81 2009/09/29 20:05:29 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.82 2009/11/04 22:26:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1148,21 +1148,7 @@ dump_getdiag(PLpgSQL_stmt_getdiag *stmt)
static void
dump_expr(PLpgSQL_expr *expr)
{
- int i;
-
- printf("'%s", expr->query);
- if (expr->nparams > 0)
- {
- printf(" {");
- for (i = 0; i < expr->nparams; i++)
- {
- if (i > 0)
- printf(", ");
- printf("$%d=%d", i + 1, expr->params[i]);
- }
- printf("}");
- }
- printf("'");
+ printf("'%s'", expr->query);
}
void
@@ -1240,11 +1226,6 @@ plpgsql_dumptree(PLpgSQL_function *func)
dump_expr(((PLpgSQL_arrayelem *) d)->subscript);
printf("\n");
break;
- case PLPGSQL_DTYPE_TRIGARG:
- printf("TRIGARG ");
- dump_expr(((PLpgSQL_trigarg *) d)->argnum);
- printf("\n");
- break;
default:
printf("??? unknown data type %d\n", d->dtype);
}
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 4f506eb97ca..7741308f287 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.46 2009/09/22 23:43:42 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.47 2009/11/04 22:26:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -68,6 +68,7 @@ Datum
plpgsql_call_handler(PG_FUNCTION_ARGS)
{
PLpgSQL_function *func;
+ PLpgSQL_execstate *save_cur_estate;
Datum retval;
int rc;
@@ -80,6 +81,9 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
/* Find or compile the function */
func = plpgsql_compile(fcinfo, false);
+ /* Must save and restore prior value of cur_estate */
+ save_cur_estate = func->cur_estate;
+
/* Mark the function as busy, so it can't be deleted from under us */
func->use_count++;
@@ -97,14 +101,17 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
}
PG_CATCH();
{
- /* Decrement use-count and propagate error */
+ /* Decrement use-count, restore cur_estate, and propagate error */
func->use_count--;
+ func->cur_estate = save_cur_estate;
PG_RE_THROW();
}
PG_END_TRY();
func->use_count--;
+ func->cur_estate = save_cur_estate;
+
/*
* Disconnect from SPI manager
*/
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 55ddf54ab2b..3d0f155a884 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.117 2009/09/29 20:05:29 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.118 2009/11/04 22:26:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -22,6 +22,7 @@
#include "fmgr.h"
#include "commands/trigger.h"
#include "executor/spi.h"
+#include "nodes/bitmapset.h"
#include "utils/tuplestore.h"
/**********************************************************************
@@ -58,8 +59,7 @@ enum
PLPGSQL_DTYPE_REC,
PLPGSQL_DTYPE_RECFIELD,
PLPGSQL_DTYPE_ARRAYELEM,
- PLPGSQL_DTYPE_EXPR,
- PLPGSQL_DTYPE_TRIGARG
+ PLPGSQL_DTYPE_EXPR
};
/* ----------
@@ -162,8 +162,7 @@ typedef struct
/*
* PLpgSQL_datum is the common supertype for PLpgSQL_expr, PLpgSQL_var,
- * PLpgSQL_row, PLpgSQL_rec, PLpgSQL_recfield, PLpgSQL_arrayelem, and
- * PLpgSQL_trigarg
+ * PLpgSQL_row, PLpgSQL_rec, PLpgSQL_recfield, and PLpgSQL_arrayelem
*/
typedef struct
{ /* Generic datum array item */
@@ -189,7 +188,11 @@ typedef struct PLpgSQL_expr
int dno;
char *query;
SPIPlanPtr plan;
- Oid *plan_argtypes;
+ Bitmapset *paramnos; /* all dnos referenced by this query */
+
+ /* function containing this expr (not set until we first parse query) */
+ struct PLpgSQL_function *func;
+
/* fields for "simple expression" fast-path execution: */
Expr *expr_simple_expr; /* NULL means not a simple expr */
int expr_simple_generation; /* plancache generation we checked */
@@ -202,10 +205,6 @@ typedef struct PLpgSQL_expr
*/
ExprState *expr_simple_state;
LocalTransactionId expr_simple_lxid;
-
- /* params to pass to expr */
- int nparams;
- int params[1]; /* VARIABLE SIZE ARRAY ... must be last */
} PLpgSQL_expr;
@@ -285,14 +284,6 @@ typedef struct
typedef struct
-{ /* Positional argument to trigger */
- int dtype;
- int dno;
- PLpgSQL_expr *argnum;
-} PLpgSQL_trigarg;
-
-
-typedef struct
{ /* Item in the compilers namestack */
int itemtype;
int itemno;
@@ -670,17 +661,22 @@ typedef struct PLpgSQL_function
int tg_table_name_varno;
int tg_table_schema_varno;
int tg_nargs_varno;
+ int tg_argv_varno;
int ndatums;
PLpgSQL_datum **datums;
PLpgSQL_stmt_block *action;
+ /* these fields change when the function is used */
+ struct PLpgSQL_execstate *cur_estate;
unsigned long use_count;
} PLpgSQL_function;
-typedef struct
+typedef struct PLpgSQL_execstate
{ /* Runtime execution data */
+ PLpgSQL_function *func; /* function being executed */
+
Datum retval;
bool retisnull;
Oid rettype; /* type of current retval */
@@ -699,9 +695,6 @@ typedef struct
MemoryContext tuple_store_cxt;
ReturnSetInfo *rsi;
- int trig_nargs;
- Datum *trig_argv;
-
int found_varno;
int ndatums;
PLpgSQL_datum **datums;
@@ -711,11 +704,12 @@ typedef struct
uint32 eval_processed;
Oid eval_lastoid;
ExprContext *eval_econtext; /* for executing simple expressions */
+ PLpgSQL_expr *cur_expr; /* current query/expr being evaluated */
/* status information for error context reporting */
- PLpgSQL_function *err_func; /* current func */
PLpgSQL_stmt *err_stmt; /* current stmt */
const char *err_text; /* additional state info */
+
void *plugin_info; /* reserved for use by optional plugin */
} PLpgSQL_execstate;