From d5a83d79c9f9b660a6a5a77afafe146d3c8c6f46 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 26 Jan 2021 16:37:12 -0500 Subject: [PATCH] Rethink recently-added SPI interfaces. SPI_execute_with_receiver and SPI_cursor_parse_open_with_paramlist are new in v14 (cf. commit 2f48ede08). Before they can get out the door, let's change their APIs to follow the practice recently established by SPI_prepare_extended etc: shove all optional arguments into a struct that callers are supposed to pre-zero. The hope is to allow future addition of more options without either API breakage or a continuing proliferation of new SPI entry points. With that in mind, choose slightly more generic names for them: SPI_execute_extended and SPI_cursor_parse_open respectively. Discussion: https://postgr.es/m/CAFj8pRCLPdDAETvR7Po7gC5y_ibkn_-bOzbeJb39WHms01194Q@mail.gmail.com --- doc/src/sgml/spi.sgml | 374 +++++++++++++++++++++-------------- src/backend/executor/spi.c | 114 +++++------ src/include/executor/spi.h | 24 ++- src/pl/plpgsql/src/pl_exec.c | 46 +++-- 4 files changed, 314 insertions(+), 244 deletions(-) diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml index d8c121f5f3..6543eaa034 100644 --- a/doc/src/sgml/spi.sgml +++ b/doc/src/sgml/spi.sgml @@ -632,6 +632,172 @@ int SPI_exec(const char * command, long count< + + SPI_execute_extended + + + SPI_execute_extended + 3 + + + + SPI_execute_extended + execute a command with out-of-line parameters + + + + +int SPI_execute_extended(const char *command, + const SPIExecuteOptions * options) + + + + + Description + + + SPI_execute_extended executes a command that might + include references to externally supplied parameters. The command text + refers to a parameter as $n, + and the options->params object (if supplied) + provides values and type information for each such symbol. + Various execution options can be specified + in the options struct, too. + + + + The options->params object should normally + mark each parameter with the PARAM_FLAG_CONST flag, + since a one-shot plan is always used for the query. + + + + If options->dest is not NULL, then result + tuples are passed to that object as they are generated by the executor, + instead of being accumulated in SPI_tuptable. Using + a caller-supplied DestReceiver object is particularly + helpful for queries that might generate many tuples, since the data can + be processed on-the-fly instead of being accumulated in memory. + + + + + Arguments + + + + const char * command + + + command string + + + + + + const SPIExecuteOptions * options + + + struct containing optional arguments + + + + + + + Callers should always zero out the entire options + struct, then fill whichever fields they want to set. This ensures forward + compatibility of code, since any fields that are added to the struct in + future will be defined to behave backwards-compatibly if they are zero. + The currently available options fields are: + + + + + ParamListInfo params + + + data structure containing query parameter types and values; NULL if none + + + + + + bool read_only + + true for read-only execution + + + + + bool no_snapshots + + + true prevents SPI from managing snapshots for + execution of the query; use with extreme caution + + + + + + uint64 tcount + + + maximum number of rows to return, + or 0 for no limit + + + + + + DestReceiver * dest + + + DestReceiver object that will receive any tuples + emitted by the query; if NULL, result tuples are accumulated into + a SPI_tuptable structure, as + in SPI_execute + + + + + + ResourceOwner owner + + + This field is present for consistency + with SPI_execute_plan_extended, but it is + ignored, since the plan used + by SPI_execute_extended is never saved. + + + + + + + + Return Value + + + The return value is the same as for SPI_execute. + + + + When options->dest is NULL, + SPI_processed and + SPI_tuptable are set as in + SPI_execute. + When options->dest is not NULL, + SPI_processed is set to zero and + SPI_tuptable is set to NULL. If a tuple count + is required, the caller's DestReceiver object must + calculate it. + + + + + + SPI_execute_with_args @@ -785,133 +951,6 @@ int SPI_execute_with_args(const char *command, - - SPI_execute_with_receiver - - - SPI_execute_with_receiver - 3 - - - - SPI_execute_with_receiver - execute a command with out-of-line parameters - - - - - int SPI_execute_with_receiver(const char *command, - ParamListInfo params, - bool read_only, - long count, - DestReceiver *dest) - - - - - Description - - - SPI_execute_with_receiver executes a command that might - include references to externally supplied parameters. The command text - refers to a parameter as $n, - and the params object provides values and type - information for each such symbol. - read_only and count have - the same interpretation as in SPI_execute. - - - - If dest is not NULL, then result tuples are passed - to that object as they are generated by the executor, instead of being - accumulated in SPI_tuptable. Using a - caller-supplied DestReceiver object is particularly - helpful for queries that might generate many tuples, since the data can - be processed on-the-fly instead of being accumulated in memory. - - - - The params object should normally mark each - parameter with the PARAM_FLAG_CONST flag, since - a one-shot plan is always used for the query. - - - - - Arguments - - - - const char * command - - - command string - - - - - - 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 return, - or 0 for no limit - - - - - - DestReceiver * dest - - - DestReceiver object that will receive any tuples - emitted by the query; if NULL, tuples are returned - in SPI_tuptable - - - - - - - - Return Value - - - The return value is the same as for SPI_execute. - - - - When dest is NULL, - SPI_processed and - SPI_tuptable are set as in - SPI_execute. - When dest is not NULL, - SPI_processed is set to zero and - SPI_tuptable is set to NULL. If a tuple count - is required, the caller's DestReceiver object must - calculate it. - - - - - - SPI_prepare @@ -1873,11 +1912,11 @@ int SPI_execute_plan_extended(SPIPlanPtr plan, - When dest is NULL, + When options->dest is NULL, SPI_processed and SPI_tuptable are set as in SPI_execute_plan. - When dest is not NULL, + When options->dest is not NULL, SPI_processed is set to zero and SPI_tuptable is set to NULL. If a tuple count is required, the caller's DestReceiver object must @@ -2263,6 +2302,12 @@ Portal SPI_cursor_open_with_args(const char *name, The passed-in parameter data will be copied into the cursor's portal, so it can be freed while the cursor still exists. + + + This function is now deprecated in favor + of SPI_cursor_parse_open, which provides equivalent + functionality using a more modern API for handling query parameters. + @@ -2465,26 +2510,24 @@ Portal SPI_cursor_open_with_paramlist(const char *name, - - SPI_cursor_parse_open_with_paramlist + + SPI_cursor_parse_open - SPI_cursor_parse_open_with_paramlist + SPI_cursor_parse_open 3 - SPI_cursor_parse_open_with_paramlist - set up a cursor using a query and parameters + SPI_cursor_parse_open + set up a cursor using a query string and parameters -Portal SPI_cursor_parse_open_with_paramlist(const char *name, - const char *command, - ParamListInfo params, - bool read_only, - int cursorOptions) +Portal SPI_cursor_parse_open(const char *name, + const char *command, + const SPIParseOpenOptions * options) @@ -2492,17 +2535,27 @@ Portal SPI_cursor_parse_open_with_paramlist(const char *nameDescription - SPI_cursor_parse_open_with_paramlist sets up a cursor - (internally, a portal) that will execute the specified query. This - function is equivalent to SPI_cursor_open_with_args - except that any parameters referenced by the query are provided by - a ParamListInfo object, rather than in ad-hoc arrays. + SPI_cursor_parse_open sets up a cursor + (internally, a portal) that will execute the specified query string. + This is comparable to SPI_prepare_cursor followed + by SPI_cursor_open_with_paramlist, except that + parameter references within the query string are handled entirely by + supplying a ParamListInfo object. - The params object should normally mark each - parameter with the PARAM_FLAG_CONST flag, since - a one-shot plan is always used for the query. + For one-time query execution, this function should be preferred + over SPI_prepare_cursor followed by + SPI_cursor_open_with_paramlist. + 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 options->params object should normally + mark each parameter with the PARAM_FLAG_CONST flag, + since a one-shot plan is always used for the query. @@ -2535,18 +2588,30 @@ Portal SPI_cursor_parse_open_with_paramlist(const char *name - ParamListInfo params + const SPIParseOpenOptions * options - data structure containing parameter types and values; NULL if none + struct containing optional arguments + + + Callers should always zero out the entire options + struct, then fill whichever fields they want to set. This ensures forward + compatibility of code, since any fields that are added to the struct in + future will be defined to behave backwards-compatibly if they are zero. + The currently available options fields are: + + + - bool read_only + ParamListInfo params - true for read-only execution + + data structure containing query parameter types and values; NULL if none + @@ -2558,6 +2623,13 @@ Portal SPI_cursor_parse_open_with_paramlist(const char *name + + + bool read_only + + true for read-only execution + + diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 68a6bcea02..00aa78ea53 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -538,6 +538,43 @@ SPI_exec(const char *src, long tcount) return SPI_execute(src, false, tcount); } +/* Parse, plan, and execute a query string, with extensible options */ +int +SPI_execute_extended(const char *src, + const SPIExecuteOptions *options) +{ + int res; + _SPI_plan plan; + + if (src == NULL || options == NULL) + return SPI_ERROR_ARGUMENT; + + res = _SPI_begin_call(true); + if (res < 0) + return res; + + memset(&plan, 0, sizeof(_SPI_plan)); + plan.magic = _SPI_PLAN_MAGIC; + plan.parse_mode = RAW_PARSE_DEFAULT; + plan.cursor_options = CURSOR_OPT_PARALLEL_OK; + if (options->params) + { + plan.parserSetup = options->params->parserSetup; + plan.parserSetupArg = options->params->parserSetupArg; + } + + _SPI_prepare_oneshot_plan(src, &plan); + + res = _SPI_execute_plan(&plan, options->params, + InvalidSnapshot, InvalidSnapshot, + options->read_only, options->no_snapshots, + true, options->tcount, + options->dest, options->owner); + + _SPI_end_call(true); + return res; +} + /* Execute a previously prepared plan */ int SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls, @@ -715,52 +752,6 @@ SPI_execute_with_args(const char *src, return res; } -/* - * SPI_execute_with_receiver -- plan and execute a query with arguments - * - * This is the same as SPI_execute_with_args except that parameters are - * supplied through a ParamListInfo, and (if dest isn't NULL) we send - * result tuples to the caller-supplied DestReceiver rather than through - * the usual SPI output arrangements. - */ -int -SPI_execute_with_receiver(const char *src, - ParamListInfo params, - bool read_only, long tcount, - DestReceiver *dest) -{ - int res; - _SPI_plan plan; - - if (src == NULL || tcount < 0) - return SPI_ERROR_ARGUMENT; - - res = _SPI_begin_call(true); - if (res < 0) - return res; - - memset(&plan, 0, sizeof(_SPI_plan)); - plan.magic = _SPI_PLAN_MAGIC; - plan.parse_mode = RAW_PARSE_DEFAULT; - plan.cursor_options = CURSOR_OPT_PARALLEL_OK; - if (params) - { - plan.parserSetup = params->parserSetup; - plan.parserSetupArg = params->parserSetupArg; - } - - _SPI_prepare_oneshot_plan(src, &plan); - - res = _SPI_execute_plan(&plan, params, - InvalidSnapshot, InvalidSnapshot, - read_only, false, - true, tcount, - dest, NULL); - - _SPI_end_call(true); - return res; -} - SPIPlanPtr SPI_prepare(const char *src, int nargs, Oid *argtypes) { @@ -1433,43 +1424,38 @@ SPI_cursor_open_with_paramlist(const char *name, SPIPlanPtr plan, return SPI_cursor_open_internal(name, plan, params, read_only); } -/* - * SPI_cursor_parse_open_with_paramlist() - * - * Same as SPI_cursor_open_with_args except that parameters (if any) are passed - * as a ParamListInfo, which supports dynamic parameter set determination - */ +/* Parse a query and open it as a cursor */ Portal -SPI_cursor_parse_open_with_paramlist(const char *name, - const char *src, - ParamListInfo params, - bool read_only, int cursorOptions) +SPI_cursor_parse_open(const char *name, + const char *src, + const SPIParseOpenOptions *options) { Portal result; _SPI_plan plan; - if (src == NULL) - elog(ERROR, "SPI_cursor_parse_open_with_paramlist called with invalid arguments"); + if (src == NULL || options == NULL) + elog(ERROR, "SPI_cursor_parse_open called with invalid arguments"); SPI_result = _SPI_begin_call(true); if (SPI_result < 0) - elog(ERROR, "SPI_cursor_parse_open_with_paramlist called while not connected"); + elog(ERROR, "SPI_cursor_parse_open called while not connected"); memset(&plan, 0, sizeof(_SPI_plan)); plan.magic = _SPI_PLAN_MAGIC; plan.parse_mode = RAW_PARSE_DEFAULT; - plan.cursor_options = cursorOptions; - if (params) + plan.cursor_options = options->cursorOptions; + if (options->params) { - plan.parserSetup = params->parserSetup; - plan.parserSetupArg = params->parserSetupArg; + plan.parserSetup = options->params->parserSetup; + plan.parserSetupArg = options->params->parserSetupArg; } _SPI_prepare_plan(src, &plan); /* We needn't copy the plan; SPI_cursor_open_internal will do so */ - result = SPI_cursor_open_internal(name, &plan, params, read_only); + result = SPI_cursor_open_internal(name, &plan, + options->params, options->read_only); /* And clean up */ _SPI_end_call(true); diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h index 5740f8956e..6455d100f5 100644 --- a/src/include/executor/spi.h +++ b/src/include/executor/spi.h @@ -42,7 +42,7 @@ typedef struct SPIPrepareOptions int cursorOptions; } SPIPrepareOptions; -/* Optional arguments for SPI_execute_plan_extended */ +/* Optional arguments for SPI_execute[_plan]_extended */ typedef struct SPIExecuteOptions { ParamListInfo params; @@ -53,6 +53,14 @@ typedef struct SPIExecuteOptions ResourceOwner owner; } SPIExecuteOptions; +/* Optional arguments for SPI_cursor_parse_open */ +typedef struct SPIParseOpenOptions +{ + ParamListInfo params; + int cursorOptions; + bool read_only; +} SPIParseOpenOptions; + /* Plans are opaque structs for standard users of SPI */ typedef struct _SPI_plan *SPIPlanPtr; @@ -105,6 +113,8 @@ extern int SPI_connect(void); extern int SPI_connect_ext(int options); extern int SPI_finish(void); extern int SPI_execute(const char *src, bool read_only, long tcount); +extern int SPI_execute_extended(const char *src, + const SPIExecuteOptions *options); extern int SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls, bool read_only, long tcount); extern int SPI_execute_plan_extended(SPIPlanPtr plan, @@ -124,10 +134,6 @@ extern int SPI_execute_with_args(const char *src, int nargs, Oid *argtypes, Datum *Values, const char *Nulls, bool read_only, long tcount); -extern int SPI_execute_with_receiver(const char *src, - ParamListInfo params, - bool read_only, long tcount, - DestReceiver *dest); 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); @@ -178,11 +184,9 @@ extern Portal SPI_cursor_open_with_args(const char *name, 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_parse_open_with_paramlist(const char *name, - const char *src, - ParamListInfo params, - bool read_only, - int cursorOptions); +extern Portal SPI_cursor_parse_open(const char *name, + const char *src, + const SPIParseOpenOptions *options); 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/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 383d92fc1d..b4c70aaa7f 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3603,6 +3603,7 @@ exec_stmt_return_query(PLpgSQL_execstate *estate, Oid restype; int32 restypmod; char *querystr; + SPIExecuteOptions options; /* * Evaluate the string expression after the EXECUTE keyword. Its @@ -3625,14 +3626,15 @@ exec_stmt_return_query(PLpgSQL_execstate *estate, exec_eval_cleanup(estate); /* Execute query, passing params if necessary */ - rc = SPI_execute_with_receiver(querystr, - exec_eval_using_params(estate, - stmt->params), - estate->readonly_func, - 0, - treceiver); + memset(&options, 0, sizeof(options)); + options.params = exec_eval_using_params(estate, + stmt->params); + options.read_only = estate->readonly_func; + options.dest = treceiver; + + rc = SPI_execute_extended(querystr, &options); if (rc < 0) - elog(ERROR, "SPI_execute_with_receiver failed executing query \"%s\": %s", + elog(ERROR, "SPI_execute_extended failed executing query \"%s\": %s", querystr, SPI_result_code_string(rc)); } @@ -4402,6 +4404,7 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate, char *querystr; int exec_res; ParamListInfo paramLI; + SPIExecuteOptions options; MemoryContext stmt_mcontext = get_stmt_mcontext(estate); /* @@ -4426,8 +4429,12 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate, * Execute the query without preparing a saved plan. */ paramLI = exec_eval_using_params(estate, stmt->params); - exec_res = SPI_execute_with_receiver(querystr, paramLI, - estate->readonly_func, 0, NULL); + + memset(&options, 0, sizeof(options)); + options.params = paramLI; + options.read_only = estate->readonly_func; + + exec_res = SPI_execute_extended(querystr, &options); switch (exec_res) { @@ -4479,7 +4486,7 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate, break; default: - elog(ERROR, "SPI_execute_with_receiver failed executing query \"%s\": %s", + elog(ERROR, "SPI_execute_extended failed executing query \"%s\": %s", querystr, SPI_result_code_string(exec_res)); break; } @@ -8582,6 +8589,7 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate, Oid restype; int32 restypmod; char *querystr; + SPIParseOpenOptions options; MemoryContext stmt_mcontext = get_stmt_mcontext(estate); /* @@ -8603,16 +8611,16 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate, exec_eval_cleanup(estate); /* - * Open an implicit cursor for the query. We use - * SPI_cursor_parse_open_with_paramlist even when there are no params, - * because this avoids making and freeing one copy of the plan. + * Open an implicit cursor for the query. We use SPI_cursor_parse_open + * even when there are no params, because this avoids making and freeing + * one copy of the plan. */ - portal = SPI_cursor_parse_open_with_paramlist(portalname, - querystr, - exec_eval_using_params(estate, - params), - estate->readonly_func, - cursorOptions); + memset(&options, 0, sizeof(options)); + options.params = exec_eval_using_params(estate, params); + options.cursorOptions = cursorOptions; + options.read_only = estate->readonly_func; + + portal = SPI_cursor_parse_open(portalname, querystr, &options); if (portal == NULL) elog(ERROR, "could not open implicit cursor for query \"%s\": %s",