diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml index a28d8170a4c..04cd0ca1791 100644 --- a/doc/src/sgml/spi.sgml +++ b/doc/src/sgml/spi.sgml @@ -1,4 +1,4 @@ - + Server Programming Interface @@ -267,7 +267,7 @@ void SPI_pop(void) Description - SPI_pop pops the previous environment from the + SPI_pop pops the previous environment from the SPI call stack. See SPI_push. @@ -371,7 +371,7 @@ SPI_execute("INSERT INTO foo SELECT * FROM bar", false, 5); then you can use the global pointer SPITupleTable *SPI_tuptable to access the result rows. Some utility commands (such as - EXPLAIN) also return row sets, and SPI_tuptable + EXPLAIN) also return row sets, and SPI_tuptable will contain the result in these cases too. @@ -676,6 +676,150 @@ int SPI_exec(const char * command, long count< + + + SPI_execute_with_args + + + + SPI_execute_with_args + execute a command with out-of-line parameters + + + SPI_execute_with_args + + + +int SPI_execute_with_args(const char *command, + int nargs, Oid *argtypes, + Datum *values, const char *nulls, + bool read_only, long count) + + + + + Description + + + SPI_execute_with_args executes a command that might + include references to externally supplied parameters. The command text + refers to a parameter as $n, and + the call specifies data types and values for each such symbol. + read_only and count have + the same interpretation as in SPI_execute. + + + + The main advantage of this routine compared to + SPI_execute is that data values can be inserted + into the command without tedious quoting/escaping, and thus with much + less risk of SQL-injection attacks. + + + + Similar results can be achieved with SPI_prepare followed by + SPI_execute_plan; however, when using this function + the query plan is customized to the specific parameter values provided. + For one-time query execution, this function should be preferred. + If the same command is to be executed with many different parameters, + either method might be faster, depending on the cost of re-planning + versus the benefit of custom plans. + + + + + Arguments + + + + const char * command + + + command string + + + + + + int nargs + + + number of input parameters ($1, $2, etc.) + + + + + + Oid * argtypes + + + an array containing the OIDs of + the data types of the parameters + + + + + + Datum * values + + + an array of actual parameter values + + + + + + const char * nulls + + + an array describing which parameters are null + + + + If nulls is NULL then + SPI_execute_with_args assumes that no parameters are + null. + + + + + + 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. + + + + SPI_processed and + SPI_tuptable are set as in + SPI_execute if successful. + + + + + + SPI_prepare @@ -861,7 +1005,7 @@ SPIPlanPtr SPI_prepare_cursor(const char * command, int < - + int cursorOptions @@ -1453,6 +1597,152 @@ Portal SPI_cursor_open(const char * name, SPIPlanPtr + + + SPI_cursor_open_with_args + + + + SPI_cursor_open_with_args + set up a cursor using a query and parameters + + + SPI_cursor_open_with_args + + + +Portal SPI_cursor_open_with_args(const char *name, + const char *command, + int nargs, Oid *argtypes, + Datum *values, const char *nulls, + bool read_only, int cursorOptions) + + + + + Description + + + SPI_cursor_open_with_args sets up a cursor + (internally, a portal) that will execute the specified query. + Most of the parameters have the same meanings as the corresponding + parameters to SPI_prepare_cursor + and SPI_cursor_open. + + + + For one-time query execution, this function should be preferred + over SPI_prepare_cursor followed by + SPI_cursor_open. + If the same command is to be executed with many different parameters, + either method might be faster, depending on the cost of re-planning + versus the benefit of custom plans. + + + + The passed-in 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 + + + + + + const char * command + + + command string + + + + + + int nargs + + + number of input parameters ($1, $2, etc.) + + + + + + Oid * argtypes + + + an array containing the OIDs of + the data types of the parameters + + + + + + Datum * values + + + an array of actual parameter values + + + + + + const char * nulls + + + an array describing which parameters are null + + + + If nulls is NULL then + SPI_cursor_open_with_args assumes that no + parameters are null. + + + + + + bool read_only + + + true for read-only execution + + + + + + int cursorOptions + + + integer bitmask of cursor options; zero produces default behavior + + + + + + + + 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 @@ -1748,7 +2038,7 @@ void SPI_scroll_cursor_fetch(Portal portal, FetchDirectio See the SQL command - for details of the interpretation of the + for details of the interpretation of the direction and count parameters. @@ -1847,7 +2137,7 @@ void SPI_scroll_cursor_move(Portal portal, FetchDirection See the SQL command - for details of the interpretation of the + for details of the interpretation of the direction and count parameters. @@ -3346,9 +3636,9 @@ execq(text *sql, int cnt) command = text_to_cstring(sql); SPI_connect(); - + ret = SPI_exec(command, cnt); - + proc = SPI_processed; /* * If some rows were fetched, print them via elog(INFO). @@ -3359,11 +3649,11 @@ execq(text *sql, int cnt) SPITupleTable *tuptable = SPI_tuptable; char buf[8192]; int i, j; - + for (j = 0; j < proc; j++) { HeapTuple tuple = tuptable->vals[j]; - + for (i = 1, buf[0] = 0; i <= tupdesc->natts; i++) snprintf(buf + strlen (buf), sizeof(buf) - strlen(buf), " %s%s", SPI_getvalue(tuple, tupdesc, i), @@ -3469,7 +3759,7 @@ INSERT 0 2 2 2 -- 2 rows * 1 (x in first row) 6 -- 3 rows (2 + 1 just inserted) * 2 (x in second row) -(4 rows) ^^^^^^ +(4 rows) ^^^^^^ rows visible to execq() in different invocations diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 3cd2e574100..42187fe43ad 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.191 2008/03/26 18:48:59 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.192 2008/04/01 03:09:30 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -35,13 +35,17 @@ static int _SPI_stack_depth = 0; /* allocated size of _SPI_stack */ static int _SPI_connected = -1; static int _SPI_curid = -1; -static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan); +static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan, + ParamListInfo boundParams); -static int _SPI_execute_plan(SPIPlanPtr plan, - Datum *Values, const char *Nulls, +static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, Snapshot snapshot, Snapshot crosscheck_snapshot, bool read_only, bool fire_triggers, long tcount); +static ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes, + Datum *Values, const char *Nulls, + int pflags); + static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount); static void _SPI_error_callback(void *arg); @@ -313,9 +317,9 @@ SPI_execute(const char *src, bool read_only, long tcount) plan.magic = _SPI_PLAN_MAGIC; plan.cursor_options = 0; - _SPI_prepare_plan(src, &plan); + _SPI_prepare_plan(src, &plan, NULL); - res = _SPI_execute_plan(&plan, NULL, NULL, + res = _SPI_execute_plan(&plan, NULL, InvalidSnapshot, InvalidSnapshot, read_only, true, tcount); @@ -348,7 +352,9 @@ SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls, return res; res = _SPI_execute_plan(plan, - Values, Nulls, + _SPI_convert_params(plan->nargs, plan->argtypes, + Values, Nulls, + 0), InvalidSnapshot, InvalidSnapshot, read_only, true, tcount); @@ -394,7 +400,9 @@ SPI_execute_snapshot(SPIPlanPtr plan, return res; res = _SPI_execute_plan(plan, - Values, Nulls, + _SPI_convert_params(plan->nargs, plan->argtypes, + Values, Nulls, + 0), snapshot, crosscheck_snapshot, read_only, fire_triggers, tcount); @@ -402,6 +410,57 @@ SPI_execute_snapshot(SPIPlanPtr plan, return res; } +/* + * SPI_execute_with_args -- plan and execute a query with supplied arguments + * + * This is functionally comparable to SPI_prepare followed by + * SPI_execute_plan, except that since we know the plan will be used only + * once, we can tell the planner to rely on the parameter values as constants. + * This eliminates potential performance disadvantages compared to + * inserting the parameter values directly into the query text. + */ +int +SPI_execute_with_args(const char *src, + int nargs, Oid *argtypes, + Datum *Values, const char *Nulls, + bool read_only, long tcount) +{ + int res; + _SPI_plan plan; + ParamListInfo paramLI; + + if (src == NULL || nargs < 0 || tcount < 0) + return SPI_ERROR_ARGUMENT; + + if (nargs > 0 && (argtypes == NULL || Values == NULL)) + return SPI_ERROR_PARAM; + + res = _SPI_begin_call(true); + if (res < 0) + return res; + + memset(&plan, 0, sizeof(_SPI_plan)); + plan.magic = _SPI_PLAN_MAGIC; + plan.cursor_options = 0; + plan.nargs = nargs; + plan.argtypes = argtypes; + + paramLI = _SPI_convert_params(nargs, argtypes, + Values, Nulls, + PARAM_FLAG_CONST); + + _SPI_prepare_plan(src, &plan, paramLI); + + /* We don't need to copy the plan since it will be thrown away anyway */ + + res = _SPI_execute_plan(&plan, paramLI, + InvalidSnapshot, InvalidSnapshot, + read_only, true, tcount); + + _SPI_end_call(true); + return res; +} + SPIPlanPtr SPI_prepare(const char *src, int nargs, Oid *argtypes) { @@ -431,7 +490,7 @@ SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes, plan.nargs = nargs; plan.argtypes = argtypes; - _SPI_prepare_plan(src, &plan); + _SPI_prepare_plan(src, &plan, NULL); /* copy plan to procedure context */ result = _SPI_copy_plan(&plan, _SPI_current->procCxt); @@ -1054,6 +1113,64 @@ SPI_cursor_open(const char *name, SPIPlanPtr plan, } +/* + * SPI_cursor_open_with_args() + * + * Parse and plan a query and open it as a portal. Like SPI_execute_with_args, + * we can tell the planner to rely on the parameter values as constants, + * because the plan will only be used once. + */ +Portal +SPI_cursor_open_with_args(const char *name, + const char *src, + int nargs, Oid *argtypes, + Datum *Values, const char *Nulls, + bool read_only, int cursorOptions) +{ + Portal result; + _SPI_plan plan; + ParamListInfo paramLI; + + if (src == NULL || nargs < 0) + elog(ERROR, "SPI_cursor_open_with_args called with invalid arguments"); + + if (nargs > 0 && (argtypes == NULL || Values == NULL)) + elog(ERROR, "SPI_cursor_open_with_args called with missing parameters"); + + SPI_result = _SPI_begin_call(true); + if (SPI_result < 0) + elog(ERROR, "SPI_cursor_open_with_args called while not connected"); + + memset(&plan, 0, sizeof(_SPI_plan)); + plan.magic = _SPI_PLAN_MAGIC; + plan.cursor_options = cursorOptions; + plan.nargs = nargs; + plan.argtypes = argtypes; + + paramLI = _SPI_convert_params(nargs, argtypes, + Values, Nulls, + PARAM_FLAG_CONST); + + _SPI_prepare_plan(src, &plan, paramLI); + + /* We needn't copy the plan; SPI_cursor_open will do so */ + + /* Adjust stack so that SPI_cursor_open doesn't complain */ + _SPI_curid--; + + /* SPI_cursor_open expects to be called in procedure memory context */ + _SPI_procmem(); + + result = SPI_cursor_open(name, &plan, Values, Nulls, read_only); + + /* And clean up */ + _SPI_curid++; + _SPI_end_call(true); + + return result; +} + + /* * SPI_cursor_find() * @@ -1376,14 +1493,17 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self) * Parse and plan a querystring. * * At entry, plan->argtypes, plan->nargs, and plan->cursor_options must be - * valid. + * 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! * * Results are stored into *plan (specifically, plan->plancache_list). * Note however that the result trees are all in CurrentMemoryContext * and need to be copied somewhere to survive. */ static void -_SPI_prepare_plan(const char *src, SPIPlanPtr plan) +_SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) { List *raw_parsetree_list; List *plancache_list; @@ -1422,7 +1542,8 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan) /* Need a copyObject here to keep parser from modifying raw tree */ stmt_list = pg_analyze_and_rewrite(copyObject(parsetree), src, argtypes, nargs); - stmt_list = pg_plan_queries(stmt_list, cursor_options, NULL, false); + stmt_list = pg_plan_queries(stmt_list, cursor_options, + boundParams, false); plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); cplan = (CachedPlan *) palloc0(sizeof(CachedPlan)); @@ -1465,7 +1586,7 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan) * tcount: execution tuple-count limit, or 0 for none */ static int -_SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls, +_SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, Snapshot snapshot, Snapshot crosscheck_snapshot, bool read_only, bool fire_triggers, long tcount) { @@ -1480,34 +1601,9 @@ _SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls, saveActiveSnapshot = ActiveSnapshot; PG_TRY(); { - ListCell *lc1; ErrorContextCallback spierrcontext; - int nargs = plan->nargs; - ParamListInfo paramLI; CachedPlan *cplan = NULL; - - /* Convert parameters to form wanted by executor */ - if (nargs > 0) - { - int k; - - /* sizeof(ParamListInfoData) includes the first array element */ - paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) + - (nargs - 1) *sizeof(ParamExternData)); - paramLI->numParams = nargs; - - for (k = 0; k < nargs; k++) - { - ParamExternData *prm = ¶mLI->params[k]; - - prm->value = Values[k]; - prm->isnull = (Nulls && Nulls[k] == 'n'); - prm->pflags = 0; - prm->ptype = plan->argtypes[k]; - } - } - else - paramLI = NULL; + ListCell *lc1; /* * Setup error traceback support for ereport() @@ -1723,6 +1819,40 @@ fail: return my_res; } +/* + * Convert query parameters to form wanted by planner and executor + */ +static ParamListInfo +_SPI_convert_params(int nargs, Oid *argtypes, + Datum *Values, const char *Nulls, + int pflags) +{ + ParamListInfo paramLI; + + if (nargs > 0) + { + int i; + + /* sizeof(ParamListInfoData) includes the first array element */ + paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) + + (nargs - 1) *sizeof(ParamExternData)); + paramLI->numParams = nargs; + + for (i = 0; i < nargs; i++) + { + ParamExternData *prm = ¶mLI->params[i]; + + prm->value = Values[i]; + prm->isnull = (Nulls && Nulls[i] == 'n'); + prm->pflags = pflags; + prm->ptype = argtypes[i]; + } + } + else + paramLI = NULL; + return paramLI; +} + static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount) { diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h index e64fc39cba5..9d3f65b8cf0 100644 --- a/src/include/executor/spi.h +++ b/src/include/executor/spi.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.65 2008/01/01 19:45:57 momjian Exp $ + * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.66 2008/04/01 03:09:30 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -105,6 +105,10 @@ extern int SPI_execute_snapshot(SPIPlanPtr plan, Snapshot snapshot, Snapshot crosscheck_snapshot, bool read_only, bool fire_triggers, long tcount); +extern int SPI_execute_with_args(const char *src, + int nargs, Oid *argtypes, + Datum *Values, const char *Nulls, + bool read_only, long tcount); 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); @@ -136,6 +140,11 @@ extern void SPI_freetuptable(SPITupleTable *tuptable); extern Portal SPI_cursor_open(const char *name, SPIPlanPtr plan, Datum *Values, const char *Nulls, bool read_only); +extern Portal SPI_cursor_open_with_args(const char *name, + const char *src, + int nargs, Oid *argtypes, + Datum *Values, const char *Nulls, + bool read_only, int cursorOptions); 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);