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
This commit is contained in:
Tom Lane 2021-01-26 16:37:12 -05:00
parent 7292fd8f1c
commit d5a83d79c9
4 changed files with 314 additions and 244 deletions

View File

@ -632,6 +632,172 @@ int SPI_exec(const char * <parameter>command</parameter>, long <parameter>count<
<!-- *********************************************** -->
<refentry id="spi-spi-execute-extended">
<indexterm><primary>SPI_execute_extended</primary></indexterm>
<refmeta>
<refentrytitle>SPI_execute_extended</refentrytitle>
<manvolnum>3</manvolnum>
</refmeta>
<refnamediv>
<refname>SPI_execute_extended</refname>
<refpurpose>execute a command with out-of-line parameters</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
int SPI_execute_extended(const char *<parameter>command</parameter>,
const SPIExecuteOptions * <parameter>options</parameter>)
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<function>SPI_execute_extended</function> executes a command that might
include references to externally supplied parameters. The command text
refers to a parameter as <literal>$<replaceable>n</replaceable></literal>,
and the <parameter>options-&gt;params</parameter> object (if supplied)
provides values and type information for each such symbol.
Various execution options can be specified
in the <parameter>options</parameter> struct, too.
</para>
<para>
The <parameter>options-&gt;params</parameter> object should normally
mark each parameter with the <literal>PARAM_FLAG_CONST</literal> flag,
since a one-shot plan is always used for the query.
</para>
<para>
If <parameter>options-&gt;dest</parameter> is not NULL, then result
tuples are passed to that object as they are generated by the executor,
instead of being accumulated in <varname>SPI_tuptable</varname>. Using
a caller-supplied <literal>DestReceiver</literal> 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.
</para>
</refsect1>
<refsect1>
<title>Arguments</title>
<variablelist>
<varlistentry>
<term><literal>const char * <parameter>command</parameter></literal></term>
<listitem>
<para>
command string
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>const SPIExecuteOptions * <parameter>options</parameter></literal></term>
<listitem>
<para>
struct containing optional arguments
</para>
</listitem>
</varlistentry>
</variablelist>
<para>
Callers should always zero out the entire <parameter>options</parameter>
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 <parameter>options</parameter> fields are:
</para>
<variablelist>
<varlistentry>
<term><literal>ParamListInfo <parameter>params</parameter></literal></term>
<listitem>
<para>
data structure containing query parameter types and values; NULL if none
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>bool <parameter>read_only</parameter></literal></term>
<listitem>
<para><literal>true</literal> for read-only execution</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>bool <parameter>no_snapshots</parameter></literal></term>
<listitem>
<para>
<literal>true</literal> prevents SPI from managing snapshots for
execution of the query; use with extreme caution
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>uint64 <parameter>tcount</parameter></literal></term>
<listitem>
<para>
maximum number of rows to return,
or <literal>0</literal> for no limit
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>DestReceiver * <parameter>dest</parameter></literal></term>
<listitem>
<para>
<literal>DestReceiver</literal> object that will receive any tuples
emitted by the query; if NULL, result tuples are accumulated into
a <varname>SPI_tuptable</varname> structure, as
in <function>SPI_execute</function>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>ResourceOwner <parameter>owner</parameter></literal></term>
<listitem>
<para>
This field is present for consistency
with <function>SPI_execute_plan_extended</function>, but it is
ignored, since the plan used
by <function>SPI_execute_extended</function> is never saved.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Return Value</title>
<para>
The return value is the same as for <function>SPI_execute</function>.
</para>
<para>
When <parameter>options-&gt;dest</parameter> is NULL,
<varname>SPI_processed</varname> and
<varname>SPI_tuptable</varname> are set as in
<function>SPI_execute</function>.
When <parameter>options-&gt;dest</parameter> is not NULL,
<varname>SPI_processed</varname> is set to zero and
<varname>SPI_tuptable</varname> is set to NULL. If a tuple count
is required, the caller's <literal>DestReceiver</literal> object must
calculate it.
</para>
</refsect1>
</refentry>
<!-- *********************************************** -->
<refentry id="spi-spi-execute-with-args">
<indexterm><primary>SPI_execute_with_args</primary></indexterm>
@ -785,133 +951,6 @@ int SPI_execute_with_args(const char *<parameter>command</parameter>,
<!-- *********************************************** -->
<refentry id="spi-spi-execute-with-receiver">
<indexterm><primary>SPI_execute_with_receiver</primary></indexterm>
<refmeta>
<refentrytitle>SPI_execute_with_receiver</refentrytitle>
<manvolnum>3</manvolnum>
</refmeta>
<refnamediv>
<refname>SPI_execute_with_receiver</refname>
<refpurpose>execute a command with out-of-line parameters</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
int SPI_execute_with_receiver(const char *<parameter>command</parameter>,
ParamListInfo <parameter>params</parameter>,
bool <parameter>read_only</parameter>,
long <parameter>count</parameter>,
DestReceiver *<parameter>dest</parameter>)
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<function>SPI_execute_with_receiver</function> executes a command that might
include references to externally supplied parameters. The command text
refers to a parameter as <literal>$<replaceable>n</replaceable></literal>,
and the <parameter>params</parameter> object provides values and type
information for each such symbol.
<parameter>read_only</parameter> and <parameter>count</parameter> have
the same interpretation as in <function>SPI_execute</function>.
</para>
<para>
If <parameter>dest</parameter> is not NULL, then result tuples are passed
to that object as they are generated by the executor, instead of being
accumulated in <varname>SPI_tuptable</varname>. Using a
caller-supplied <literal>DestReceiver</literal> 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.
</para>
<para>
The <parameter>params</parameter> object should normally mark each
parameter with the <literal>PARAM_FLAG_CONST</literal> flag, since
a one-shot plan is always used for the query.
</para>
</refsect1>
<refsect1>
<title>Arguments</title>
<variablelist>
<varlistentry>
<term><literal>const char * <parameter>command</parameter></literal></term>
<listitem>
<para>
command string
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>ParamListInfo <parameter>params</parameter></literal></term>
<listitem>
<para>
data structure containing parameter types and values; NULL if none
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>bool <parameter>read_only</parameter></literal></term>
<listitem>
<para><literal>true</literal> for read-only execution</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>long <parameter>count</parameter></literal></term>
<listitem>
<para>
maximum number of rows to return,
or <literal>0</literal> for no limit
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>DestReceiver * <parameter>dest</parameter></literal></term>
<listitem>
<para>
<literal>DestReceiver</literal> object that will receive any tuples
emitted by the query; if NULL, tuples are returned
in <varname>SPI_tuptable</varname>
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Return Value</title>
<para>
The return value is the same as for <function>SPI_execute</function>.
</para>
<para>
When <parameter>dest</parameter> is NULL,
<varname>SPI_processed</varname> and
<varname>SPI_tuptable</varname> are set as in
<function>SPI_execute</function>.
When <parameter>dest</parameter> is not NULL,
<varname>SPI_processed</varname> is set to zero and
<varname>SPI_tuptable</varname> is set to NULL. If a tuple count
is required, the caller's <literal>DestReceiver</literal> object must
calculate it.
</para>
</refsect1>
</refentry>
<!-- *********************************************** -->
<refentry id="spi-spi-prepare">
<indexterm><primary>SPI_prepare</primary></indexterm>
@ -1873,11 +1912,11 @@ int SPI_execute_plan_extended(SPIPlanPtr <parameter>plan</parameter>,
</para>
<para>
When <parameter>dest</parameter> is NULL,
When <parameter>options-&gt;dest</parameter> is NULL,
<varname>SPI_processed</varname> and
<varname>SPI_tuptable</varname> are set as in
<function>SPI_execute_plan</function>.
When <parameter>dest</parameter> is not NULL,
When <parameter>options-&gt;dest</parameter> is not NULL,
<varname>SPI_processed</varname> is set to zero and
<varname>SPI_tuptable</varname> is set to NULL. If a tuple count
is required, the caller's <literal>DestReceiver</literal> object must
@ -2263,6 +2302,12 @@ Portal SPI_cursor_open_with_args(const char *<parameter>name</parameter>,
The passed-in parameter data will be copied into the cursor's portal, so it
can be freed while the cursor still exists.
</para>
<para>
This function is now deprecated in favor
of <function>SPI_cursor_parse_open</function>, which provides equivalent
functionality using a more modern API for handling query parameters.
</para>
</refsect1>
<refsect1>
@ -2465,26 +2510,24 @@ Portal SPI_cursor_open_with_paramlist(const char *<parameter>name</parameter>,
<!-- *********************************************** -->
<refentry id="spi-spi-cursor-parse-open-with-paramlist">
<indexterm><primary>SPI_cursor_parse_open_with_paramlist</primary></indexterm>
<refentry id="spi-spi-cursor-parse-open">
<indexterm><primary>SPI_cursor_parse_open</primary></indexterm>
<refmeta>
<refentrytitle>SPI_cursor_parse_open_with_paramlist</refentrytitle>
<refentrytitle>SPI_cursor_parse_open</refentrytitle>
<manvolnum>3</manvolnum>
</refmeta>
<refnamediv>
<refname>SPI_cursor_parse_open_with_paramlist</refname>
<refpurpose>set up a cursor using a query and parameters</refpurpose>
<refname>SPI_cursor_parse_open</refname>
<refpurpose>set up a cursor using a query string and parameters</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
Portal SPI_cursor_parse_open_with_paramlist(const char *<parameter>name</parameter>,
const char *<parameter>command</parameter>,
ParamListInfo <parameter>params</parameter>,
bool <parameter>read_only</parameter>,
int <parameter>cursorOptions</parameter>)
Portal SPI_cursor_parse_open(const char *<parameter>name</parameter>,
const char *<parameter>command</parameter>,
const SPIParseOpenOptions * <parameter>options</parameter>)
</synopsis>
</refsynopsisdiv>
@ -2492,17 +2535,27 @@ Portal SPI_cursor_parse_open_with_paramlist(const char *<parameter>name</paramet
<title>Description</title>
<para>
<function>SPI_cursor_parse_open_with_paramlist</function> sets up a cursor
(internally, a portal) that will execute the specified query. This
function is equivalent to <function>SPI_cursor_open_with_args</function>
except that any parameters referenced by the query are provided by
a <literal>ParamListInfo</literal> object, rather than in ad-hoc arrays.
<function>SPI_cursor_parse_open</function> sets up a cursor
(internally, a portal) that will execute the specified query string.
This is comparable to <function>SPI_prepare_cursor</function> followed
by <function>SPI_cursor_open_with_paramlist</function>, except that
parameter references within the query string are handled entirely by
supplying a <literal>ParamListInfo</literal> object.
</para>
<para>
The <parameter>params</parameter> object should normally mark each
parameter with the <literal>PARAM_FLAG_CONST</literal> flag, since
a one-shot plan is always used for the query.
For one-time query execution, this function should be preferred
over <function>SPI_prepare_cursor</function> followed by
<function>SPI_cursor_open_with_paramlist</function>.
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.
</para>
<para>
The <parameter>options-&gt;params</parameter> object should normally
mark each parameter with the <literal>PARAM_FLAG_CONST</literal> flag,
since a one-shot plan is always used for the query.
</para>
<para>
@ -2535,18 +2588,30 @@ Portal SPI_cursor_parse_open_with_paramlist(const char *<parameter>name</paramet
</varlistentry>
<varlistentry>
<term><literal>ParamListInfo <parameter>params</parameter></literal></term>
<term><literal>const SPIParseOpenOptions * <parameter>options</parameter></literal></term>
<listitem>
<para>
data structure containing parameter types and values; NULL if none
struct containing optional arguments
</para>
</listitem>
</varlistentry>
</variablelist>
<para>
Callers should always zero out the entire <parameter>options</parameter>
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 <parameter>options</parameter> fields are:
</para>
<variablelist>
<varlistentry>
<term><literal>bool <parameter>read_only</parameter></literal></term>
<term><literal>ParamListInfo <parameter>params</parameter></literal></term>
<listitem>
<para><literal>true</literal> for read-only execution</para>
<para>
data structure containing query parameter types and values; NULL if none
</para>
</listitem>
</varlistentry>
@ -2558,6 +2623,13 @@ Portal SPI_cursor_parse_open_with_paramlist(const char *<parameter>name</paramet
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>bool <parameter>read_only</parameter></literal></term>
<listitem>
<para><literal>true</literal> for read-only execution</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>

View File

@ -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);

View File

@ -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);

View File

@ -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",