From 9bedd128d6ed83798004b3c7ddc33f33703ccf23 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 4 Nov 2009 22:26:08 +0000 Subject: [PATCH] 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. --- doc/src/sgml/spi.sgml | 290 +++++++++++++++++- src/backend/commands/explain.c | 13 +- src/backend/commands/prepare.c | 7 +- src/backend/executor/execCurrent.c | 16 +- src/backend/executor/execQual.c | 16 +- src/backend/executor/functions.c | 7 +- src/backend/executor/spi.c | 242 ++++++++++----- src/backend/nodes/params.c | 82 ++++-- src/backend/tcop/postgres.c | 53 +++- src/backend/utils/cache/plancache.c | 53 +++- src/include/executor/spi.h | 11 +- src/include/executor/spi_priv.h | 4 +- src/include/nodes/params.h | 31 +- src/include/tcop/tcopprot.h | 6 +- src/include/utils/plancache.h | 8 +- src/pl/plpgsql/src/gram.y | 124 ++------ src/pl/plpgsql/src/pl_comp.c | 40 +-- src/pl/plpgsql/src/pl_exec.c | 538 +++++++++++++++++++++------------- src/pl/plpgsql/src/pl_funcs.c | 23 +- src/pl/plpgsql/src/pl_handler.c | 11 +- src/pl/plpgsql/src/plpgsql.h | 40 ++- src/test/regress/expected/plpgsql.out | 8 +- src/test/regress/sql/plpgsql.sql | 8 +- 23 files changed, 1104 insertions(+), 527 deletions(-) diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml index 07fab82bad..8d40c60c8e 100644 --- a/doc/src/sgml/spi.sgml +++ b/doc/src/sgml/spi.sgml @@ -1,4 +1,4 @@ - + Server Programming Interface @@ -861,7 +861,7 @@ SPIPlanPtr SPI_prepare(const char * command, int SPI_prepare creates and returns an execution - plan for the specified command but doesn't execute the command. + plan for the specified command, but doesn't execute the command. This function should only be called from a connected procedure. @@ -990,7 +990,7 @@ SPIPlanPtr SPI_prepare_cursor(const char * command, int < of the planner's cursor options parameter. This is a bitmask having the values shown in nodes/parsenodes.h for the options field of DeclareCursorStmt. - SPI_prepare always takes these options as zero. + SPI_prepare always takes the cursor options as zero. @@ -1061,6 +1061,94 @@ SPIPlanPtr SPI_prepare_cursor(const char * command, int < + + + SPI_prepare_params + 3 + + + + SPI_prepare_params + prepare a plan for a command, without executing it yet + + + SPI_prepare_params + + + +SPIPlanPtr SPI_prepare_params(const char * command, + ParserSetupHook parserSetup, + void * parserSetupArg, + int cursorOptions) + + + + + Description + + + SPI_prepare_params creates and returns an execution + plan for the specified command, but doesn't execute the command. + This function is equivalent to SPI_prepare_cursor, + with the addition that the caller can specify parser hook functions + to control the parsing of external parameter references. + + + + + Arguments + + + + const char * command + + + command string + + + + + + ParserSetupHook parserSetup + + + Parser hook setup function + + + + + + void * parserSetupArg + + + passthrough argument for parserSetup + + + + + + int cursorOptions + + + integer bitmask of cursor options; zero produces default behavior + + + + + + + + Return Value + + + SPI_prepare_params has the same return conventions as + SPI_prepare. + + + + + + SPI_getargcount @@ -1386,14 +1474,100 @@ int SPI_execute_plan(SPIPlanPtr plan, Datum * SPI_execute if successful. + + + + + + + SPI_execute_plan_with_paramlist + 3 + + + + SPI_execute_plan_with_paramlist + execute a plan prepared by SPI_prepare + + + SPI_execute_plan_with_paramlist + + + +int SPI_execute_plan_with_paramlist(SPIPlanPtr plan, + ParamListInfo params, + bool read_only, + long count) + + - Notes + Description - If one of the objects (a table, function, etc.) referenced by the - prepared plan is dropped during the session then the result of - SPI_execute_plan for this plan will be unpredictable. + SPI_execute_plan_with_paramlist executes a plan + prepared by SPI_prepare. + This function is equivalent to SPI_execute_plan + except that information about the parameter values to be passed to the + query is presented differently. The ParamListInfo + representation can be convenient for passing down values that are + already available in that format. It also supports use of dynamic + parameter sets via hook functions specified in ParamListInfo. + + + + + Arguments + + + + SPIPlanPtr plan + + + execution plan (returned by SPI_prepare) + + + + + + ParamListInfo params + + + data structure containing parameter types and values; NULL if none + + + + + + bool read_only + + + true for read-only execution + + + + + + long count + + + maximum number of rows to process or return + + + + + + + + Return Value + + + The return value is the same as for SPI_execute_plan. + + + + SPI_processed and + SPI_tuptable are set as in + SPI_execute_plan if successful. @@ -1543,7 +1717,7 @@ Portal SPI_cursor_open(const char * name, SPIPlanPtr - The passed-in data will be copied into the cursor's portal, so it + The passed-in parameter data will be copied into the cursor's portal, so it can be freed while the cursor still exists. @@ -1667,7 +1841,7 @@ Portal SPI_cursor_open_with_args(const char *name, - The passed-in data will be copied into the cursor's portal, so it + The passed-in parameter data will be copied into the cursor's portal, so it can be freed while the cursor still exists. @@ -1770,6 +1944,104 @@ Portal SPI_cursor_open_with_args(const char *name, + + + SPI_cursor_open_with_paramlist + 3 + + + + SPI_cursor_open_with_paramlist + set up a cursor using parameters + + + SPI_cursor_open_with_paramlist + + + +Portal SPI_cursor_open_with_paramlist(const char *name, + SPIPlanPtr plan, + ParamListInfo params, + bool read_only) + + + + + Description + + + SPI_cursor_open_with_paramlist sets up a cursor + (internally, a portal) that will execute a plan prepared by + SPI_prepare. + This function is equivalent to SPI_cursor_open + except that information about the parameter values to be passed to the + query is presented differently. The ParamListInfo + representation can be convenient for passing down values that are + already available in that format. It also supports use of dynamic + parameter sets via hook functions specified in ParamListInfo. + + + + The passed-in parameter data will be copied into the cursor's portal, so it + can be freed while the cursor still exists. + + + + + Arguments + + + + const char * name + + + name for portal, or NULL to let the system + select a name + + + + + + SPIPlanPtr plan + + + execution plan (returned by SPI_prepare) + + + + + + ParamListInfo params + + + data structure containing parameter types and values; NULL if none + + + + + + bool read_only + + + true for read-only execution + + + + + + + + Return Value + + + Pointer to portal containing the cursor. Note there is no error + return convention; any error will be reported via elog. + + + + + + SPI_cursor_find diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 1260ca00c2..21fa3add4f 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994-5, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.192 2009/10/12 18:10:41 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.193 2009/11/04 22:26:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -107,8 +107,6 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString, ParamListInfo params, DestReceiver *dest) { ExplainState es; - Oid *param_types; - int num_params; TupOutputState *tstate; List *rewritten; ListCell *lc; @@ -150,9 +148,6 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString, opt->defname))); } - /* Convert parameter type data to the form parser wants */ - getParamListTypes(params, ¶m_types, &num_params); - /* * Run parse analysis and rewrite. Note this also acquires sufficient * locks on the source table(s). @@ -163,8 +158,10 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString, * executed repeatedly. (See also the same hack in DECLARE CURSOR and * PREPARE.) XXX FIXME someday. */ - rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query), - queryString, param_types, num_params); + rewritten = pg_analyze_and_rewrite_params((Node *) copyObject(stmt->query), + queryString, + (ParserSetupHook) setupParserWithParamList, + params); /* emit opening boilerplate */ ExplainBeginOutput(&es); diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 56a16401f3..021c2daf26 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -10,7 +10,7 @@ * Copyright (c) 2002-2009, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.99 2009/08/10 05:46:50 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.100 2009/11/04 22:26:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -379,6 +379,11 @@ EvaluateParams(PreparedStatement *pstmt, List *params, paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) + (num_params - 1) *sizeof(ParamExternData)); + /* we have static list of params, so no hooks needed */ + paramLI->paramFetch = NULL; + paramLI->paramFetchArg = NULL; + paramLI->parserSetup = NULL; + paramLI->parserSetupArg = NULL; paramLI->numParams = num_params; i = 0; diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c index a4103332c4..35dc05a52b 100644 --- a/src/backend/executor/execCurrent.c +++ b/src/backend/executor/execCurrent.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.12 2009/10/26 02:26:29 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.13 2009/11/04 22:26:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -217,9 +217,21 @@ fetch_param_value(ExprContext *econtext, int paramId) { ParamExternData *prm = ¶mInfo->params[paramId - 1]; + /* give hook a chance in case parameter is dynamic */ + if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL) + (*paramInfo->paramFetch) (paramInfo, paramId); + if (OidIsValid(prm->ptype) && !prm->isnull) { - Assert(prm->ptype == REFCURSOROID); + /* safety check in case hook did something unexpected */ + if (prm->ptype != REFCURSOROID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)", + paramId, + format_type_be(prm->ptype), + format_type_be(REFCURSOROID)))); + /* We know that refcursor uses text's I/O routines */ return TextDatumGetCString(prm->value); } diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index fdfbd999f4..226e15546f 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.253 2009/10/26 02:26:29 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.254 2009/11/04 22:26:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -882,9 +882,21 @@ ExecEvalParam(ExprState *exprstate, ExprContext *econtext, { ParamExternData *prm = ¶mInfo->params[thisParamId - 1]; + /* give hook a chance in case parameter is dynamic */ + if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL) + (*paramInfo->paramFetch) (paramInfo, thisParamId); + if (OidIsValid(prm->ptype)) { - Assert(prm->ptype == expression->paramtype); + /* safety check in case hook did something unexpected */ + if (prm->ptype != expression->paramtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)", + thisParamId, + format_type_be(prm->ptype), + format_type_be(expression->paramtype)))); + *isNull = prm->isnull; return prm->value; } diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index fe25798a21..2934e51161 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.135 2009/06/11 17:25:38 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.136 2009/11/04 22:26:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -526,6 +526,11 @@ postquel_sub_params(SQLFunctionCachePtr fcache, /* sizeof(ParamListInfoData) includes the first array element */ paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) + (nargs - 1) *sizeof(ParamExternData)); + /* we have static list of params, so no hooks needed */ + paramLI->paramFetch = NULL; + paramLI->paramFetchArg = NULL; + paramLI->parserSetup = NULL; + paramLI->parserSetupArg = NULL; paramLI->numParams = nargs; fcache->paramLI = paramLI; } diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index e6bb04bc8a..fcea0a1e62 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.210 2009/10/10 01:43:47 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.211 2009/11/04 22:26:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -45,8 +45,7 @@ static int _SPI_connected = -1; static int _SPI_curid = -1; static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, - Datum *Values, const char *Nulls, - bool read_only, int pflags); + ParamListInfo paramLI, bool read_only); static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams); @@ -407,6 +406,28 @@ SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls, long tcount) return SPI_execute_plan(plan, Values, Nulls, false, tcount); } +/* Execute a previously prepared plan */ +int +SPI_execute_plan_with_paramlist(SPIPlanPtr plan, ParamListInfo params, + bool read_only, long tcount) +{ + int res; + + if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0) + return SPI_ERROR_ARGUMENT; + + res = _SPI_begin_call(true); + if (res < 0) + return res; + + res = _SPI_execute_plan(plan, params, + InvalidSnapshot, InvalidSnapshot, + read_only, true, tcount); + + _SPI_end_call(true); + return res; +} + /* * SPI_execute_snapshot -- identical to SPI_execute_plan, except that we allow * the caller to specify exactly which snapshots to use, which will be @@ -483,6 +504,8 @@ SPI_execute_with_args(const char *src, plan.cursor_options = 0; plan.nargs = nargs; plan.argtypes = argtypes; + plan.parserSetup = NULL; + plan.parserSetupArg = NULL; paramLI = _SPI_convert_params(nargs, argtypes, Values, Nulls, @@ -528,6 +551,45 @@ SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes, plan.cursor_options = cursorOptions; plan.nargs = nargs; plan.argtypes = argtypes; + plan.parserSetup = NULL; + plan.parserSetupArg = NULL; + + _SPI_prepare_plan(src, &plan, NULL); + + /* copy plan to procedure context */ + result = _SPI_copy_plan(&plan, _SPI_current->procCxt); + + _SPI_end_call(true); + + return result; +} + +SPIPlanPtr +SPI_prepare_params(const char *src, + ParserSetupHook parserSetup, + void *parserSetupArg, + int cursorOptions) +{ + _SPI_plan plan; + SPIPlanPtr result; + + if (src == NULL) + { + SPI_result = SPI_ERROR_ARGUMENT; + return NULL; + } + + SPI_result = _SPI_begin_call(true); + if (SPI_result < 0) + return NULL; + + memset(&plan, 0, sizeof(_SPI_plan)); + plan.magic = _SPI_PLAN_MAGIC; + plan.cursor_options = cursorOptions; + plan.nargs = 0; + plan.argtypes = NULL; + plan.parserSetup = parserSetup; + plan.parserSetupArg = parserSetupArg; _SPI_prepare_plan(src, &plan, NULL); @@ -954,8 +1016,21 @@ SPI_cursor_open(const char *name, SPIPlanPtr plan, Datum *Values, const char *Nulls, bool read_only) { - return SPI_cursor_open_internal(name, plan, Values, Nulls, - read_only, 0); + Portal portal; + ParamListInfo paramLI; + + /* build transient ParamListInfo in caller's context */ + paramLI = _SPI_convert_params(plan->nargs, plan->argtypes, + Values, Nulls, + 0); + + portal = SPI_cursor_open_internal(name, plan, paramLI, read_only); + + /* done with the transient ParamListInfo */ + if (paramLI) + pfree(paramLI); + + return portal; } @@ -992,7 +1067,10 @@ SPI_cursor_open_with_args(const char *name, plan.cursor_options = cursorOptions; plan.nargs = nargs; plan.argtypes = argtypes; + plan.parserSetup = NULL; + plan.parserSetupArg = NULL; + /* build transient ParamListInfo in executor context */ paramLI = _SPI_convert_params(nargs, argtypes, Values, Nulls, PARAM_FLAG_CONST); @@ -1007,8 +1085,7 @@ SPI_cursor_open_with_args(const char *name, /* SPI_cursor_open_internal must be called in procedure memory context */ _SPI_procmem(); - result = SPI_cursor_open_internal(name, &plan, Values, Nulls, - read_only, PARAM_FLAG_CONST); + result = SPI_cursor_open_internal(name, &plan, paramLI, read_only); /* And clean up */ _SPI_curid++; @@ -1019,24 +1096,35 @@ SPI_cursor_open_with_args(const char *name, /* + * SPI_cursor_open_with_paramlist() + * + * Same as SPI_cursor_open except that parameters (if any) are passed + * as a ParamListInfo, which supports dynamic parameter set determination + */ +Portal +SPI_cursor_open_with_paramlist(const char *name, SPIPlanPtr plan, + ParamListInfo params, bool read_only) +{ + return SPI_cursor_open_internal(name, plan, params, read_only); +} + + +/* * SPI_cursor_open_internal() * - * Common code for SPI_cursor_open and SPI_cursor_open_with_args + * Common code for SPI_cursor_open variants */ static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, - Datum *Values, const char *Nulls, - bool read_only, int pflags) + ParamListInfo paramLI, bool read_only) { CachedPlanSource *plansource; CachedPlan *cplan; List *stmt_list; char *query_string; - ParamListInfo paramLI; Snapshot snapshot; MemoryContext oldcontext; Portal portal; - int k; /* * Check that the plan is something the Portal code will special-case as @@ -1082,54 +1170,15 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, portal = CreatePortal(name, false, false); } - /* - * Prepare to copy stuff into the portal's memory context. We do all this - * copying first, because it could possibly fail (out-of-memory) and we - * don't want a failure to occur between RevalidateCachedPlan and - * PortalDefineQuery; that would result in leaking our plancache refcount. - */ - oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); - /* Copy the plan's query string into the portal */ - query_string = pstrdup(plansource->query_string); - - /* If the plan has parameters, copy them into the portal */ - if (plan->nargs > 0) - { - /* sizeof(ParamListInfoData) includes the first array element */ - paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) + - (plan->nargs - 1) *sizeof(ParamExternData)); - paramLI->numParams = plan->nargs; - - for (k = 0; k < plan->nargs; k++) - { - ParamExternData *prm = ¶mLI->params[k]; - - prm->ptype = plan->argtypes[k]; - prm->pflags = pflags; - prm->isnull = (Nulls && Nulls[k] == 'n'); - if (prm->isnull) - { - /* nulls just copy */ - prm->value = Values[k]; - } - else - { - /* pass-by-ref values must be copied into portal context */ - int16 paramTypLen; - bool paramTypByVal; - - get_typlenbyval(prm->ptype, ¶mTypLen, ¶mTypByVal); - prm->value = datumCopy(Values[k], - paramTypByVal, paramTypLen); - } - } - } - else - paramLI = NULL; - - MemoryContextSwitchTo(oldcontext); + query_string = MemoryContextStrdup(PortalGetHeapMemory(portal), + plansource->query_string); + /* + * Note: we mustn't have any failure occur between RevalidateCachedPlan + * and PortalDefineQuery; that would result in leaking our plancache + * refcount. + */ if (plan->saved) { /* Replan if needed, and increment plan refcount for portal */ @@ -1221,6 +1270,19 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, } /* + * If the plan has parameters, copy them into the portal. Note that + * this must be done after revalidating the plan, because in dynamic + * parameter cases the set of parameters could have changed during + * re-parsing. + */ + if (paramLI) + { + oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); + paramLI = copyParamList(paramLI); + MemoryContextSwitchTo(oldcontext); + } + + /* * Start portal execution. */ PortalStart(portal, paramLI, snapshot); @@ -1588,11 +1650,12 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self) /* * Parse and plan a querystring. * - * At entry, plan->argtypes, plan->nargs, and plan->cursor_options must be - * valid. If boundParams isn't NULL then it represents parameter values - * that are made available to the planner (as either estimates or hard values - * depending on their PARAM_FLAG_CONST marking). The boundParams had better - * match the param types embedded in the plan! + * At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup + * and plan->parserSetupArg) must be valid, as must plan->cursor_options. + * If boundParams isn't NULL then it represents parameter values that are made + * available to the planner (as either estimates or hard values depending on + * their PARAM_FLAG_CONST marking). The boundParams had better match the + * param type information embedded in the plan! * * Results are stored into *plan (specifically, plan->plancache_list). * Note however that the result trees are all in CurrentMemoryContext @@ -1605,8 +1668,6 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) List *plancache_list; ListCell *list_item; ErrorContextCallback spierrcontext; - Oid *argtypes = plan->argtypes; - int nargs = plan->nargs; int cursor_options = plan->cursor_options; /* @@ -1623,8 +1684,8 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) raw_parsetree_list = pg_parse_query(src); /* - * Do parse analysis and rule rewrite for each raw parsetree, then cons up - * a phony plancache entry for each one. + * Do parse analysis, rule rewrite, and planning for each raw parsetree, + * then cons up a phony plancache entry for each one. */ plancache_list = NIL; @@ -1635,9 +1696,27 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) CachedPlanSource *plansource; CachedPlan *cplan; - /* Need a copyObject here to keep parser from modifying raw tree */ - stmt_list = pg_analyze_and_rewrite(copyObject(parsetree), - src, argtypes, nargs); + /* + * Parameter datatypes are driven by parserSetup hook if provided, + * otherwise we use the fixed parameter list. + */ + if (plan->parserSetup != NULL) + { + Assert(plan->nargs == 0); + /* Need a copyObject here to keep parser from modifying raw tree */ + stmt_list = pg_analyze_and_rewrite_params(copyObject(parsetree), + src, + plan->parserSetup, + plan->parserSetupArg); + } + else + { + /* Need a copyObject here to keep parser from modifying raw tree */ + stmt_list = pg_analyze_and_rewrite(copyObject(parsetree), + src, + plan->argtypes, + plan->nargs); + } stmt_list = pg_plan_queries(stmt_list, cursor_options, boundParams); plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); @@ -1647,8 +1726,10 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) /* cast-away-const here is a bit ugly, but there's no reason to copy */ plansource->query_string = (char *) src; plansource->commandTag = CreateCommandTag(parsetree); - plansource->param_types = argtypes; - plansource->num_params = nargs; + plansource->param_types = plan->argtypes; + plansource->num_params = plan->nargs; + plansource->parserSetup = plan->parserSetup; + plansource->parserSetupArg = plan->parserSetupArg; plansource->fully_planned = true; plansource->fixed_result = false; /* no need to set search_path, generation or saved_xmin */ @@ -1921,7 +2002,7 @@ fail: } /* - * Convert query parameters to form wanted by planner and executor + * Convert arrays of query parameters to form wanted by planner and executor */ static ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes, @@ -1937,6 +2018,11 @@ _SPI_convert_params(int nargs, Oid *argtypes, /* sizeof(ParamListInfoData) includes the first array element */ paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) + (nargs - 1) *sizeof(ParamExternData)); + /* we have static list of params, so no hooks needed */ + paramLI->paramFetch = NULL; + paramLI->paramFetchArg = NULL; + paramLI->parserSetup = NULL; + paramLI->parserSetupArg = NULL; paramLI->numParams = nargs; for (i = 0; i < nargs; i++) @@ -2222,6 +2308,8 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt) } else newplan->argtypes = NULL; + newplan->parserSetup = plan->parserSetup; + newplan->parserSetupArg = plan->parserSetupArg; foreach(lc, plan->plancache_list) { @@ -2241,6 +2329,8 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt) newsource->commandTag = plansource->commandTag; newsource->param_types = newplan->argtypes; newsource->num_params = newplan->nargs; + newsource->parserSetup = newplan->parserSetup; + newsource->parserSetupArg = newplan->parserSetupArg; newsource->fully_planned = plansource->fully_planned; newsource->fixed_result = plansource->fixed_result; /* no need to worry about seach_path, generation or saved_xmin */ @@ -2298,6 +2388,8 @@ _SPI_save_plan(SPIPlanPtr plan) } else newplan->argtypes = NULL; + newplan->parserSetup = plan->parserSetup; + newplan->parserSetupArg = plan->parserSetupArg; foreach(lc, plan->plancache_list) { @@ -2317,6 +2409,10 @@ _SPI_save_plan(SPIPlanPtr plan) cplan->stmt_list, true, false); + if (newplan->parserSetup != NULL) + CachedPlanSetParserHook(newsource, + newplan->parserSetup, + newplan->parserSetupArg); newplan->plancache_list = lappend(newplan->plancache_list, newsource); } diff --git a/src/backend/nodes/params.c b/src/backend/nodes/params.c index e7eeb2df01..111276c348 100644 --- a/src/backend/nodes/params.c +++ b/src/backend/nodes/params.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.11 2009/01/01 17:23:43 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.12 2009/11/04 22:26:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -16,6 +16,7 @@ #include "postgres.h" #include "nodes/params.h" +#include "parser/parse_param.h" #include "utils/datum.h" #include "utils/lsyscache.h" @@ -24,6 +25,11 @@ * Copy a ParamListInfo structure. * * The result is allocated in CurrentMemoryContext. + * + * Note: the intent of this function is to make a static, self-contained + * set of parameter values. If dynamic parameter hooks are present, we + * intentionally do not copy them into the result. Rather, we forcibly + * instantiate all available parameter values and copy the datum values. */ ParamListInfo copyParamList(ParamListInfo from) @@ -40,54 +46,76 @@ copyParamList(ParamListInfo from) (from->numParams - 1) *sizeof(ParamExternData); retval = (ParamListInfo) palloc(size); - memcpy(retval, from, size); + retval->paramFetch = NULL; + retval->paramFetchArg = NULL; + retval->parserSetup = NULL; + retval->parserSetupArg = NULL; + retval->numParams = from->numParams; - /* - * Flat-copy is not good enough for pass-by-ref data values, so make a - * pass over the array to copy those. - */ - for (i = 0; i < retval->numParams; i++) + for (i = 0; i < from->numParams; i++) { - ParamExternData *prm = &retval->params[i]; + ParamExternData *oprm = &from->params[i]; + ParamExternData *nprm = &retval->params[i]; int16 typLen; bool typByVal; - if (prm->isnull || !OidIsValid(prm->ptype)) + /* give hook a chance in case parameter is dynamic */ + if (!OidIsValid(oprm->ptype) && from->paramFetch != NULL) + (*from->paramFetch) (from, i+1); + + /* flat-copy the parameter info */ + *nprm = *oprm; + + /* need datumCopy in case it's a pass-by-reference datatype */ + if (nprm->isnull || !OidIsValid(nprm->ptype)) continue; - get_typlenbyval(prm->ptype, &typLen, &typByVal); - prm->value = datumCopy(prm->value, typByVal, typLen); + get_typlenbyval(nprm->ptype, &typLen, &typByVal); + nprm->value = datumCopy(nprm->value, typByVal, typLen); } return retval; } /* - * Extract an array of parameter type OIDs from a ParamListInfo. + * Set up the parser to treat the given list of run-time parameters + * as available external parameters during parsing of a new query. * - * The result is allocated in CurrentMemoryContext. + * Note that the parser doesn't actually care about the *values* of the given + * parameters, only about their *types*. Also, the code that originally + * provided the ParamListInfo may have provided a setupHook, which should + * override applying parse_fixed_parameters(). */ void -getParamListTypes(ParamListInfo params, - Oid **param_types, int *num_params) +setupParserWithParamList(struct ParseState *pstate, + ParamListInfo params) { - Oid *ptypes; - int i; + if (params == NULL) /* no params, nothing to do */ + return; - if (params == NULL || params->numParams <= 0) + /* If there is a parserSetup hook, it gets to do this */ + if (params->parserSetup != NULL) { - *param_types = NULL; - *num_params = 0; + (*params->parserSetup) (pstate, params->parserSetupArg); return; } - ptypes = (Oid *) palloc(params->numParams * sizeof(Oid)); - *param_types = ptypes; - *num_params = params->numParams; - - for (i = 0; i < params->numParams; i++) + /* Else, treat any available parameters as being of fixed type */ + if (params->numParams > 0) { - ParamExternData *prm = ¶ms->params[i]; + Oid *ptypes; + int i; + + ptypes = (Oid *) palloc(params->numParams * sizeof(Oid)); + for (i = 0; i < params->numParams; i++) + { + ParamExternData *prm = ¶ms->params[i]; + + /* give hook a chance in case parameter is dynamic */ + if (!OidIsValid(prm->ptype) && params->paramFetch != NULL) + (*params->paramFetch) (params, i+1); - ptypes[i] = prm->ptype; + ptypes[i] = prm->ptype; + } + parse_fixed_parameters(pstate, ptypes, params->numParams); } } diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 67deea7962..b6a892a30f 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.574 2009/10/08 22:34:57 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.575 2009/11/04 22:26:06 tgl Exp $ * * NOTES * this is the "main" module of the postgres backend and @@ -628,6 +628,52 @@ pg_analyze_and_rewrite(Node *parsetree, const char *query_string, } /* + * Do parse analysis and rewriting. This is the same as pg_analyze_and_rewrite + * except that external-parameter resolution is determined by parser callback + * hooks instead of a fixed list of parameter datatypes. + */ +List * +pg_analyze_and_rewrite_params(Node *parsetree, + const char *query_string, + ParserSetupHook parserSetup, + void *parserSetupArg) +{ + ParseState *pstate; + Query *query; + List *querytree_list; + + Assert(query_string != NULL); /* required as of 8.4 */ + + TRACE_POSTGRESQL_QUERY_REWRITE_START(query_string); + + /* + * (1) Perform parse analysis. + */ + if (log_parser_stats) + ResetUsage(); + + pstate = make_parsestate(NULL); + pstate->p_sourcetext = query_string; + (*parserSetup) (pstate, parserSetupArg); + + query = transformStmt(pstate, parsetree); + + free_parsestate(pstate); + + if (log_parser_stats) + ShowUsage("PARSE ANALYSIS STATISTICS"); + + /* + * (2) Rewrite the queries, as necessary + */ + querytree_list = pg_rewrite_query(query); + + TRACE_POSTGRESQL_QUERY_REWRITE_DONE(query_string); + + return querytree_list; +} + +/* * Perform rewriting of a query produced by parse analysis. * * Note: query must just have come from the parser, because we do not do @@ -1536,6 +1582,11 @@ exec_bind_message(StringInfo input_message) /* sizeof(ParamListInfoData) includes the first array element */ params = (ParamListInfo) palloc(sizeof(ParamListInfoData) + (numParams - 1) *sizeof(ParamExternData)); + /* we have static list of params, so no hooks needed */ + params->paramFetch = NULL; + params->paramFetchArg = NULL; + params->parserSetup = NULL; + params->parserSetupArg = NULL; params->numParams = numParams; for (paramno = 0; paramno < numParams; paramno++) diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index eb196ebb56..b473b7a159 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -35,7 +35,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.30 2009/10/26 02:26:41 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.31 2009/11/04 22:26:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -99,8 +99,8 @@ InitPlanCache(void) * raw_parse_tree: output of raw_parser() * query_string: original query text (as of PG 8.4, must not be NULL) * commandTag: compile-time-constant tag for query, or NULL if empty query - * param_types: array of parameter type OIDs, or NULL if none - * num_params: number of parameters + * param_types: array of fixed parameter type OIDs, or NULL if none + * num_params: number of fixed parameters * cursor_options: options bitmask that was/will be passed to planner * stmt_list: list of PlannedStmts/utility stmts, or list of Query trees * fully_planned: are we caching planner or rewriter output? @@ -156,6 +156,9 @@ CreateCachedPlan(Node *raw_parse_tree, else plansource->param_types = NULL; plansource->num_params = num_params; + /* these can be set later with CachedPlanSetParserHook: */ + plansource->parserSetup = NULL; + plansource->parserSetupArg = NULL; plansource->cursor_options = cursor_options; plansource->fully_planned = fully_planned; plansource->fixed_result = fixed_result; @@ -240,6 +243,9 @@ FastCreateCachedPlan(Node *raw_parse_tree, plansource->commandTag = commandTag; /* no copying needed */ plansource->param_types = param_types; plansource->num_params = num_params; + /* these can be set later with CachedPlanSetParserHook: */ + plansource->parserSetup = NULL; + plansource->parserSetupArg = NULL; plansource->cursor_options = cursor_options; plansource->fully_planned = fully_planned; plansource->fixed_result = fixed_result; @@ -275,6 +281,27 @@ FastCreateCachedPlan(Node *raw_parse_tree, } /* + * CachedPlanSetParserHook: set up to use parser callback hooks + * + * Use this when a caller wants to manage parameter information via parser + * callbacks rather than a fixed parameter-types list. Beware that the + * information pointed to by parserSetupArg must be valid for as long as + * the cached plan might be replanned! + */ +void +CachedPlanSetParserHook(CachedPlanSource *plansource, + ParserSetupHook parserSetup, + void *parserSetupArg) +{ + /* Must not have specified a fixed parameter-types list */ + Assert(plansource->param_types == NULL); + Assert(plansource->num_params == 0); + /* OK, save hook info */ + plansource->parserSetup = parserSetup; + plansource->parserSetupArg = parserSetupArg; +} + +/* * StoreCachedPlan: store a built or rebuilt plan into a plancache entry. * * Common subroutine for CreateCachedPlan and RevalidateCachedPlan. @@ -466,6 +493,7 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner) if (!plan) { bool snapshot_set = false; + Node *rawtree; List *slist; TupleDesc resultDesc; @@ -491,14 +519,19 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner) /* * Run parse analysis and rule rewriting. The parser tends to * scribble on its input, so we must copy the raw parse tree to - * prevent corruption of the cache. Note that we do not use - * parse_analyze_varparams(), assuming that the caller never wants the - * parameter types to change from the original values. + * prevent corruption of the cache. */ - slist = pg_analyze_and_rewrite(copyObject(plansource->raw_parse_tree), - plansource->query_string, - plansource->param_types, - plansource->num_params); + rawtree = copyObject(plansource->raw_parse_tree); + if (plansource->parserSetup != NULL) + slist = pg_analyze_and_rewrite_params(rawtree, + plansource->query_string, + plansource->parserSetup, + plansource->parserSetupArg); + else + slist = pg_analyze_and_rewrite(rawtree, + plansource->query_string, + plansource->param_types, + plansource->num_params); if (plansource->fully_planned) { diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h index 5bdde2d524..42ba4e464d 100644 --- a/src/include/executor/spi.h +++ b/src/include/executor/spi.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.72 2009/06/11 14:49:11 momjian Exp $ + * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.73 2009/11/04 22:26:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -73,6 +73,9 @@ extern void SPI_restore_connection(void); extern int SPI_execute(const char *src, bool read_only, long tcount); extern int SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls, bool read_only, long tcount); +extern int SPI_execute_plan_with_paramlist(SPIPlanPtr plan, + ParamListInfo params, + bool read_only, long tcount); extern int SPI_exec(const char *src, long tcount); extern int SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls, long tcount); @@ -88,6 +91,10 @@ extern int SPI_execute_with_args(const char *src, extern SPIPlanPtr SPI_prepare(const char *src, int nargs, Oid *argtypes); extern SPIPlanPtr SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes, int cursorOptions); +extern SPIPlanPtr SPI_prepare_params(const char *src, + ParserSetupHook parserSetup, + void *parserSetupArg, + int cursorOptions); extern SPIPlanPtr SPI_saveplan(SPIPlanPtr plan); extern int SPI_freeplan(SPIPlanPtr plan); @@ -122,6 +129,8 @@ extern Portal SPI_cursor_open_with_args(const char *name, int nargs, Oid *argtypes, Datum *Values, const char *Nulls, bool read_only, int cursorOptions); +extern Portal SPI_cursor_open_with_paramlist(const char *name, SPIPlanPtr plan, + ParamListInfo params, bool read_only); extern Portal SPI_cursor_find(const char *name); extern void SPI_cursor_fetch(Portal portal, bool forward, long count); extern void SPI_cursor_move(Portal portal, bool forward, long count); diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h index ef50a9013e..a0dee12695 100644 --- a/src/include/executor/spi_priv.h +++ b/src/include/executor/spi_priv.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.32 2009/01/01 17:23:59 momjian Exp $ + * $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.33 2009/11/04 22:26:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -68,6 +68,8 @@ typedef struct _SPI_plan int cursor_options; /* Cursor options used for planning */ int nargs; /* number of plan arguments */ Oid *argtypes; /* Argument types (NULL if nargs is 0) */ + ParserSetupHook parserSetup; /* alternative parameter spec method */ + void *parserSetupArg; } _SPI_plan; #endif /* SPI_PRIV_H */ diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h index adc5871055..96cbf5267a 100644 --- a/src/include/nodes/params.h +++ b/src/include/nodes/params.h @@ -7,13 +7,16 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.38 2009/01/01 17:24:00 momjian Exp $ + * $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.39 2009/11/04 22:26:06 tgl Exp $ * *------------------------------------------------------------------------- */ #ifndef PARAMS_H #define PARAMS_H +/* To avoid including a pile of parser headers, reference ParseState thus: */ +struct ParseState; + /* ---------------- * ParamListInfo @@ -26,10 +29,20 @@ * Although parameter numbers are normally consecutive, we allow * ptype == InvalidOid to signal an unused array entry. * + * pflags is a flags field. Currently the only used bit is: * PARAM_FLAG_CONST signals the planner that it may treat this parameter * as a constant (i.e., generate a plan that works only for this value * of the parameter). * + * There are two hook functions that can be associated with a ParamListInfo + * array to support dynamic parameter handling. First, if paramFetch + * isn't null and the executor requires a value for an invalid parameter + * (one with ptype == InvalidOid), the paramFetch hook is called to give + * it a chance to fill in the parameter value. Second, a parserSetup + * hook can be supplied to re-instantiate the original parsing hooks if + * a query needs to be re-parsed/planned (as a substitute for supposing + * that the current ptype values represent a fixed set of parameter types). + * Although the data structure is really an array, not a list, we keep * the old typedef name to avoid unnecessary code changes. * ---------------- @@ -45,14 +58,22 @@ typedef struct ParamExternData Oid ptype; /* parameter's datatype, or 0 */ } ParamExternData; +typedef struct ParamListInfoData *ParamListInfo; + +typedef void (*ParamFetchHook) (ParamListInfo params, int paramid); + +typedef void (*ParserSetupHook) (struct ParseState *pstate, void *arg); + typedef struct ParamListInfoData { + ParamFetchHook paramFetch; /* parameter fetch hook */ + void *paramFetchArg; + ParserSetupHook parserSetup; /* parser setup hook */ + void *parserSetupArg; int numParams; /* number of ParamExternDatas following */ ParamExternData params[1]; /* VARIABLE LENGTH ARRAY */ } ParamListInfoData; -typedef ParamListInfoData *ParamListInfo; - /* ---------------- * ParamExecData @@ -82,7 +103,7 @@ typedef struct ParamExecData /* Functions found in src/backend/nodes/params.c */ extern ParamListInfo copyParamList(ParamListInfo from); -extern void getParamListTypes(ParamListInfo params, - Oid **param_types, int *num_params); +extern void setupParserWithParamList(struct ParseState *pstate, + ParamListInfo params); #endif /* PARAMS_H */ diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h index b1c8b77bd5..103a22fbc5 100644 --- a/src/include/tcop/tcopprot.h +++ b/src/include/tcop/tcopprot.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.100 2009/09/01 00:09:42 tgl Exp $ + * $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.101 2009/11/04 22:26:07 tgl Exp $ * * OLD COMMENTS * This file was created so that other c files could get the two @@ -49,6 +49,10 @@ extern List *pg_parse_and_rewrite(const char *query_string, extern List *pg_parse_query(const char *query_string); extern List *pg_analyze_and_rewrite(Node *parsetree, const char *query_string, Oid *paramTypes, int numParams); +extern List *pg_analyze_and_rewrite_params(Node *parsetree, + const char *query_string, + ParserSetupHook parserSetup, + void *parserSetupArg); extern PlannedStmt *pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams); extern List *pg_plan_queries(List *querytrees, int cursorOptions, diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index ea919bd456..68e3f72b19 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/plancache.h,v 1.15 2009/01/01 17:24:02 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/plancache.h,v 1.16 2009/11/04 22:26:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -16,6 +16,7 @@ #define PLANCACHE_H #include "access/tupdesc.h" +#include "nodes/params.h" /* * CachedPlanSource represents the portion of a cached plan that persists @@ -50,6 +51,8 @@ typedef struct CachedPlanSource const char *commandTag; /* command tag (a constant!), or NULL */ Oid *param_types; /* array of parameter type OIDs, or NULL */ int num_params; /* length of param_types array */ + ParserSetupHook parserSetup; /* alternative parameter spec method */ + void *parserSetupArg; int cursor_options; /* cursor options used for planning */ bool fully_planned; /* do we cache planner or rewriter output? */ bool fixed_result; /* disallow change in result tupdesc? */ @@ -105,6 +108,9 @@ extern CachedPlanSource *FastCreateCachedPlan(Node *raw_parse_tree, bool fully_planned, bool fixed_result, MemoryContext context); +extern void CachedPlanSetParserHook(CachedPlanSource *plansource, + ParserSetupHook parserSetup, + void *parserSetupArg); extern void DropCachedPlan(CachedPlanSource *plansource); extern CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner); diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index c876b875e6..ec6b285bf2 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 604b7dbf08..ef0dcb0f8d 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 a3a39e8e2d..f792020638 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], ¶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) { @@ -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 = ¶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. * 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], - ¶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_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 = ¶ms->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 d814e8f4f3..274d027114 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 4f506eb97c..7741308f28 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 55ddf54ab2..3d0f155a88 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; diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index a362fb5370..e3cacf589e 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -285,11 +285,9 @@ begin if new.slotno < 1 or new.slotno > hubrec.nslots then raise exception ''no manual manipulation of HSlot''; end if; - if tg_op = ''UPDATE'' then - if new.hubname != old.hubname then - if count(*) > 0 from Hub where name = old.hubname then - raise exception ''no manual manipulation of HSlot''; - end if; + if tg_op = ''UPDATE'' and new.hubname != old.hubname then + if count(*) > 0 from Hub where name = old.hubname then + raise exception ''no manual manipulation of HSlot''; end if; end if; sname := ''HS.'' || trim(new.hubname); diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index 80de8eb72f..3e6b6de539 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -347,11 +347,9 @@ begin if new.slotno < 1 or new.slotno > hubrec.nslots then raise exception ''no manual manipulation of HSlot''; end if; - if tg_op = ''UPDATE'' then - if new.hubname != old.hubname then - if count(*) > 0 from Hub where name = old.hubname then - raise exception ''no manual manipulation of HSlot''; - end if; + if tg_op = ''UPDATE'' and new.hubname != old.hubname then + if count(*) > 0 from Hub where name = old.hubname then + raise exception ''no manual manipulation of HSlot''; end if; end if; sname := ''HS.'' || trim(new.hubname); -- 2.11.0