Add SPI-level support for executing SQL commands with one-time-use plans,

that is commands that have out-of-line parameters but the plan is prepared
assuming that the parameter values are constants.  This is needed for the
plpgsql EXECUTE USING patch, but will probably have use elsewhere.

This commit includes the SPI functions and documentation, but no callers
nor regression tests.  The upcoming EXECUTE USING patch will provide
regression-test coverage.  I thought committing this separately made
sense since it's logically a distinct feature.
This commit is contained in:
Tom Lane 2008-04-01 03:09:30 +00:00
parent 6b73d7e567
commit d5466e38f0
3 changed files with 480 additions and 51 deletions

View File

@ -1,4 +1,4 @@
<!-- $PostgreSQL: pgsql/doc/src/sgml/spi.sgml,v 1.61 2008/03/25 22:42:42 tgl Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/spi.sgml,v 1.62 2008/04/01 03:09:30 tgl Exp $ -->
<chapter id="spi">
<title>Server Programming Interface</title>
@ -267,7 +267,7 @@ void SPI_pop(void)
<title>Description</title>
<para>
<function>SPI_pop</function> pops the previous environment from the
<function>SPI_pop</function> pops the previous environment from the
SPI call stack. See <function>SPI_push</function>.
</para>
</refsect1>
@ -371,7 +371,7 @@ SPI_execute("INSERT INTO foo SELECT * FROM bar", false, 5);
then you can use the
global pointer <literal>SPITupleTable *SPI_tuptable</literal> to
access the result rows. Some utility commands (such as
<command>EXPLAIN</>) also return row sets, and <literal>SPI_tuptable</>
<command>EXPLAIN</>) also return row sets, and <literal>SPI_tuptable</>
will contain the result in these cases too.
</para>
@ -676,6 +676,150 @@ int SPI_exec(const char * <parameter>command</parameter>, long <parameter>count<
<!-- *********************************************** -->
<refentry id="spi-spi-execute-with-args">
<refmeta>
<refentrytitle>SPI_execute_with_args</refentrytitle>
</refmeta>
<refnamediv>
<refname>SPI_execute_with_args</refname>
<refpurpose>execute a command with out-of-line parameters</refpurpose>
</refnamediv>
<indexterm><primary>SPI_execute_with_args</primary></indexterm>
<refsynopsisdiv>
<synopsis>
int SPI_execute_with_args(const char *<parameter>command</parameter>,
int <parameter>nargs</parameter>, Oid *<parameter>argtypes</parameter>,
Datum *<parameter>values</parameter>, const char *<parameter>nulls</parameter>,
bool <parameter>read_only</parameter>, long <parameter>count</parameter>)
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<function>SPI_execute_with_args</function> executes a command that might
include references to externally supplied parameters. The command text
refers to a parameter as <literal>$<replaceable>n</></literal>, and
the call specifies data types and values for each such symbol.
<parameter>read_only</parameter> and <parameter>count</parameter> have
the same interpretation as in <function>SPI_execute</function>.
</para>
<para>
The main advantage of this routine compared to
<function>SPI_execute</function> is that data values can be inserted
into the command without tedious quoting/escaping, and thus with much
less risk of SQL-injection attacks.
</para>
<para>
Similar results can be achieved with <function>SPI_prepare</> followed by
<function>SPI_execute_plan</function>; 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.
</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>int <parameter>nargs</parameter></literal></term>
<listitem>
<para>
number of input parameters (<literal>$1</>, <literal>$2</>, etc.)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>Oid * <parameter>argtypes</parameter></literal></term>
<listitem>
<para>
an array containing the <acronym>OID</acronym>s of
the data types of the parameters
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>Datum * <parameter>values</parameter></literal></term>
<listitem>
<para>
an array of actual parameter values
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>const char * <parameter>nulls</parameter></literal></term>
<listitem>
<para>
an array describing which parameters are null
</para>
<para>
If <parameter>nulls</parameter> is <symbol>NULL</symbol> then
<function>SPI_execute_with_args</function> assumes that no parameters are
null.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>bool <parameter>read_only</parameter></literal></term>
<listitem>
<para>
<literal>true</> for read-only execution
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>long <parameter>count</parameter></literal></term>
<listitem>
<para>
maximum number of rows to process or return
</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>
<varname>SPI_processed</varname> and
<varname>SPI_tuptable</varname> are set as in
<function>SPI_execute</function> if successful.
</para>
</refsect1>
</refentry>
<!-- *********************************************** -->
<refentry id="spi-spi-prepare">
<refmeta>
<refentrytitle>SPI_prepare</refentrytitle>
@ -861,7 +1005,7 @@ SPIPlanPtr SPI_prepare_cursor(const char * <parameter>command</parameter>, int <
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>int <parameter>cursorOptions</parameter></literal></term>
<listitem>
@ -1453,6 +1597,152 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, SPIPlanPtr <par
<!-- *********************************************** -->
<refentry id="spi-spi-cursor-open-with-args">
<refmeta>
<refentrytitle>SPI_cursor_open_with_args</refentrytitle>
</refmeta>
<refnamediv>
<refname>SPI_cursor_open_with_args</refname>
<refpurpose>set up a cursor using a query and parameters</refpurpose>
</refnamediv>
<indexterm><primary>SPI_cursor_open_with_args</primary></indexterm>
<refsynopsisdiv>
<synopsis>
Portal SPI_cursor_open_with_args(const char *<parameter>name</parameter>,
const char *<parameter>command</parameter>,
int <parameter>nargs</parameter>, Oid *<parameter>argtypes</parameter>,
Datum *<parameter>values</parameter>, const char *<parameter>nulls</parameter>,
bool <parameter>read_only</parameter>, int <parameter>cursorOptions</parameter>)
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<function>SPI_cursor_open_with_args</function> 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 <function>SPI_prepare_cursor</function>
and <function>SPI_cursor_open</function>.
</para>
<para>
For one-time query execution, this function should be preferred
over <function>SPI_prepare_cursor</function> followed by
<function>SPI_cursor_open</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 passed-in data will be copied into the cursor's portal, so it
can be freed while the cursor still exists.
</para>
</refsect1>
<refsect1>
<title>Arguments</title>
<variablelist>
<varlistentry>
<term><literal>const char * <parameter>name</parameter></literal></term>
<listitem>
<para>
name for portal, or <symbol>NULL</symbol> to let the system
select a name
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>const char * <parameter>command</parameter></literal></term>
<listitem>
<para>
command string
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>int <parameter>nargs</parameter></literal></term>
<listitem>
<para>
number of input parameters (<literal>$1</>, <literal>$2</>, etc.)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>Oid * <parameter>argtypes</parameter></literal></term>
<listitem>
<para>
an array containing the <acronym>OID</acronym>s of
the data types of the parameters
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>Datum * <parameter>values</parameter></literal></term>
<listitem>
<para>
an array of actual parameter values
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>const char * <parameter>nulls</parameter></literal></term>
<listitem>
<para>
an array describing which parameters are null
</para>
<para>
If <parameter>nulls</parameter> is <symbol>NULL</symbol> then
<function>SPI_cursor_open_with_args</function> assumes that no
parameters are null.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>bool <parameter>read_only</parameter></literal></term>
<listitem>
<para>
<literal>true</> for read-only execution
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>int <parameter>cursorOptions</parameter></literal></term>
<listitem>
<para>
integer bitmask of cursor options; zero produces default behavior
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Return Value</title>
<para>
Pointer to portal containing the cursor. Note there is no error
return convention; any error will be reported via <function>elog</>.
</para>
</refsect1>
</refentry>
<!-- *********************************************** -->
<refentry id="spi-spi-cursor-find">
<refmeta>
<refentrytitle>SPI_cursor_find</refentrytitle>
@ -1748,7 +2038,7 @@ void SPI_scroll_cursor_fetch(Portal <parameter>portal</parameter>, FetchDirectio
<para>
See the SQL <xref linkend="sql-fetch" endterm="sql-fetch-title"> command
for details of the interpretation of the
for details of the interpretation of the
<parameter>direction</parameter> and
<parameter>count</parameter> parameters.
</para>
@ -1847,7 +2137,7 @@ void SPI_scroll_cursor_move(Portal <parameter>portal</parameter>, FetchDirection
<para>
See the SQL <xref linkend="sql-fetch" endterm="sql-fetch-title"> command
for details of the interpretation of the
for details of the interpretation of the
<parameter>direction</parameter> and
<parameter>count</parameter> parameters.
</para>
@ -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 &lt; proc; j++)
{
HeapTuple tuple = tuptable-&gt;vals[j];
for (i = 1, buf[0] = 0; i &lt;= tupdesc-&gt;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
</programlisting>
</para>

View File

@ -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 = &paramLI->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 = &paramLI->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)
{

View File

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