Redesign query-snapshot timing so that volatile functions in READ COMMITTED

mode see a fresh snapshot for each command in the function, rather than
using the latest interactive command's snapshot.  Also, suppress fresh
snapshots as well as CommandCounterIncrement inside STABLE and IMMUTABLE
functions, instead using the snapshot taken for the most closely nested
regular query.  (This behavior is only sane for read-only functions, so
the patch also enforces that such functions contain only SELECT commands.)
As per my proposal of 6-Sep-2004; I note that I floated essentially the
same proposal on 19-Jun-2002, but that discussion tailed off without any
action.  Since 8.0 seems like the right place to be taking possibly
nontrivial backwards compatibility hits, let's get it done now.
This commit is contained in:
Tom Lane 2004-09-13 20:10:13 +00:00
parent d69528881a
commit b2c4071299
41 changed files with 1764 additions and 834 deletions

View File

@ -386,7 +386,7 @@ crosstab(PG_FUNCTION_ARGS)
elog(ERROR, "crosstab: SPI_connect returned %d", ret);
/* Retrieve the desired rows */
ret = SPI_exec(sql, 0);
ret = SPI_execute(sql, true, 0);
proc = SPI_processed;
/* Check for qualifying tuples */
@ -777,7 +777,7 @@ load_categories_hash(char *cats_sql, MemoryContext per_query_ctx)
elog(ERROR, "load_categories_hash: SPI_connect returned %d", ret);
/* Retrieve the category name rows */
ret = SPI_exec(cats_sql, 0);
ret = SPI_execute(cats_sql, true, 0);
num_categories = proc = SPI_processed;
/* Check for qualifying tuples */
@ -855,7 +855,7 @@ get_crosstab_tuplestore(char *sql,
elog(ERROR, "get_crosstab_tuplestore: SPI_connect returned %d", ret);
/* Now retrieve the crosstab source rows */
ret = SPI_exec(sql, 0);
ret = SPI_execute(sql, true, 0);
proc = SPI_processed;
/* Check for qualifying tuples */
@ -1376,7 +1376,7 @@ build_tuplestore_recursively(char *key_fld,
}
/* Retrieve the desired rows */
ret = SPI_exec(sql->data, 0);
ret = SPI_execute(sql->data, true, 0);
proc = SPI_processed;
/* Check for qualifying tuples */

View File

@ -446,7 +446,7 @@ ts_stat_sql(text *txt, text *ws)
/* internal error */
elog(ERROR, "SPI_prepare('%s') returns NULL", query);
if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL)) == NULL)
if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL, false)) == NULL)
/* internal error */
elog(ERROR, "SPI_cursor_open('%s') returns NULL", query);

View File

@ -1,4 +1,4 @@
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpython.sgml,v 1.23 2004/05/16 23:22:07 neilc Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpython.sgml,v 1.24 2004/09/13 20:05:18 tgl Exp $ -->
<chapter id="plpython">
<title>PL/Python - Python Procedural Language</title>
@ -175,7 +175,7 @@ def __plpython_procedure_myfunc_23456():
row number and column name. It has these additional methods:
<function>nrows</function> which returns the number of rows
returned by the query, and <function>status</function> which is the
<function>SPI_exec()</function> return value. The result object
<function>SPI_execute()</function> return value. The result object
can be modified.
</para>

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.58 2004/07/11 23:23:43 momjian Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.59 2004/09/13 20:05:38 tgl Exp $
-->
<refentry id="SQL-CREATEFUNCTION">
@ -172,7 +172,7 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
These attributes inform the system whether it is safe to
replace multiple evaluations of the function with a single
evaluation, for run-time optimization. At most one choice
should be specified. If none of these appear,
may be specified. If none of these appear,
<literal>VOLATILE</literal> is the default assumption.
</para>
@ -206,6 +206,10 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
to prevent calls from being optimized away; an example is
<literal>setval()</>.
</para>
<para>
For additional details see <xref linkend="xfunc-volatility">.
</para>
</listitem>
</varlistentry>

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.295 2004/09/10 18:39:54 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.296 2004/09/13 20:05:18 tgl Exp $
-->
<appendix id="release">
@ -337,6 +337,26 @@ $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.295 2004/09/10 18:39:54 tgl Exp
</para>
</listitem>
<listitem>
<para>
In <literal>READ COMMITTED</> serialization mode, volatile functions
now see the results of concurrent transactions committed up to the
beginning of each statement within the function, rather than up to the
beginning of the interactive command that called the function.
</para>
</listitem>
<listitem>
<para>
Functions declared <literal>STABLE</> or <literal>IMMUTABLE</> always
use the snapshot of the calling query, and therefore do not see the
effects of actions taken after the calling query starts, whether in
their own transaction or other transactions. Such a function must be
read-only, too, meaning that it cannot use any SQL commands other than
<command>SELECT</>.
</para>
</listitem>
<listitem>
<para>
Non-deferred AFTER triggers are now fired immediately after completion
@ -1434,6 +1454,26 @@ $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.295 2004/09/10 18:39:54 tgl Exp
<title>Server-Side Language Changes</title>
<itemizedlist>
<listitem>
<para>
In <literal>READ COMMITTED</> serialization mode, volatile functions
now see the results of concurrent transactions committed up to the
beginning of each statement within the function, rather than up to the
beginning of the interactive command that called the function.
</para>
</listitem>
<listitem>
<para>
Functions declared <literal>STABLE</> or <literal>IMMUTABLE</> always
use the snapshot of the calling query, and therefore do not see the
effects of actions taken after the calling query starts, whether in
their own transaction or other transactions. Such a function must be
read-only, too, meaning that it cannot use any SQL commands other than
<command>SELECT</>.
</para>
</listitem>
<listitem>
<para>
Non-deferred AFTER triggers are now fired immediately after completion

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/spi.sgml,v 1.34 2004/04/01 21:28:43 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/spi.sgml,v 1.35 2004/09/13 20:05:25 tgl Exp $
-->
<chapter id="spi">
@ -206,7 +206,7 @@ int SPI_finish(void)
<refnamediv>
<refname>SPI_push</refname>
<refpurpose>pushes SPI stack to allow recursive SPI calls</refpurpose>
<refpurpose>pushes SPI stack to allow recursive SPI usage</refpurpose>
</refnamediv>
<indexterm><primary>SPI_push</primary></indexterm>
@ -221,8 +221,24 @@ void SPI_push(void)
<title>Description</title>
<para>
<function>SPI_push</function> pushes a new environment on to the
SPI call stack, allowing recursive calls to use a new environment.
<function>SPI_push</function> should be called before executing another
procedure that might itself wish to use SPI.
After <function>SPI_push</function>, SPI is no longer in a
<quote>connected</> state, and SPI function calls will be rejected unless
a fresh <function>SPI_connect</function> is done. This ensures a clean
separation between your procedure's SPI state and that of another procedure
you call. After the other procedure returns, call
<function>SPI_pop</function> to restore access to your own SPI state.
</para>
<para>
Note that <function>SPI_execute</function> and related functions
automatically do the equivalent of <function>SPI_push</function> before
passing control back to the SQL execution engine, so it is not necessary
for you to worry about this when using those functions.
Only when you are directly calling arbitrary code that might contain
<function>SPI_connect</function> calls do you need to issue
<function>SPI_push</function> and <function>SPI_pop</function>.
</para>
</refsect1>
@ -237,7 +253,7 @@ void SPI_push(void)
<refnamediv>
<refname>SPI_pop</refname>
<refpurpose>pops SPI stack to allow recursive SPI calls</refpurpose>
<refpurpose>pops SPI stack to return from recursive SPI usage</refpurpose>
</refnamediv>
<indexterm><primary>SPI_pop</primary></indexterm>
@ -253,7 +269,7 @@ void SPI_pop(void)
<para>
<function>SPI_pop</function> pops the previous environment from the
SPI call stack. For use when returning from recursive SPI calls.
SPI call stack. See <function>SPI_push</function>.
</para>
</refsect1>
@ -261,21 +277,21 @@ void SPI_pop(void)
<!-- *********************************************** -->
<refentry id="spi-spi-exec">
<refentry id="spi-spi-execute">
<refmeta>
<refentrytitle>SPI_exec</refentrytitle>
<refentrytitle>SPI_execute</refentrytitle>
</refmeta>
<refnamediv>
<refname>SPI_exec</refname>
<refname>SPI_execute</refname>
<refpurpose>execute a command</refpurpose>
</refnamediv>
<indexterm><primary>SPI_exec</primary></indexterm>
<indexterm><primary>SPI_execute</primary></indexterm>
<refsynopsisdiv>
<synopsis>
int SPI_exec(const char * <parameter>command</parameter>, int <parameter>count</parameter>)
int SPI_execute(const char * <parameter>command</parameter>, bool <parameter>read_only</parameter>, int <parameter>count</parameter>)
</synopsis>
</refsynopsisdiv>
@ -283,27 +299,65 @@ int SPI_exec(const char * <parameter>command</parameter>, int <parameter>count</
<title>Description</title>
<para>
<function>SPI_exec</function> executes the specified SQL command
for <parameter>count</parameter> rows.
<function>SPI_execute</function> executes the specified SQL command
for <parameter>count</parameter> rows. If <parameter>read_only</parameter>
is <literal>true</>, the command must be read-only, and execution overhead
is somewhat reduced.
</para>
<para>
This function should only be called from a connected procedure. If
<parameter>count</parameter> is zero then it executes the command
This function may only be called from a connected procedure.
</para>
<para>
If <parameter>count</parameter> is zero then the command is executed
for all rows that it applies to. If <parameter>count</parameter>
is greater than 0, then the number of rows for which the command
will be executed is restricted (much like a
<literal>LIMIT</literal> clause). For example,
<programlisting>
SPI_exec("INSERT INTO tab SELECT * FROM tab", 5);
SPI_execute("INSERT INTO foo SELECT * FROM bar", false, 5);
</programlisting>
will allow at most 5 rows to be inserted into the table.
</para>
<para>
You may pass multiple commands in one string, and the command may
be rewritten by rules. <function>SPI_exec</function> returns the
result for the command executed last.
You may pass multiple commands in one string, and the commands may
be rewritten by rules. <function>SPI_execute</function> returns the
result for the command executed last. The <parameter>count</parameter>
limit applies to each command separately, but it is not applied to
hidden commands generated by rules.
</para>
<para>
When <parameter>read_only</parameter> is <literal>false</>,
<function>SPI_execute</function> increments the command
counter and computes a new <firstterm>snapshot</> before executing each
command in the string. The snapshot does not actually change if the
current transaction isolation level is <literal>SERIALIZABLE</>, but in
<literal>READ COMMITTED</> mode the snapshot update allows each command to
see the results of newly committed transactions from other sessions.
This is essential for consistent behavior when the commands are modifying
the database.
</para>
<para>
When <parameter>read_only</parameter> is <literal>true</>,
<function>SPI_execute</function> does not update either the snapshot
or the command counter, and it allows only plain <command>SELECT</>
commands to appear in the command string. The commands are executed
using the snapshot previously established for the surrounding query.
This execution mode is somewhat faster than the read/write mode due
to eliminating per-command overhead. It also allows genuinely
<firstterm>stable</> functions to be built: since successive executions
will all use the same snapshot, there will be no change in the results.
</para>
<para>
It is generally unwise to mix read-only and read-write commands within
a single function using SPI; that could result in very confusing behavior,
since the read-only queries would not see the results of any database
updates done by the read-write queries.
</para>
<para>
@ -311,7 +365,7 @@ SPI_exec("INSERT INTO tab SELECT * FROM tab", 5);
is returned in the global variable <varname>SPI_processed</varname>
(unless the return value of the function is
<symbol>SPI_OK_UTILITY</symbol>). If the return value of the
function is <symbol>SPI_OK_SELECT</symbol> then you may the use
function is <symbol>SPI_OK_SELECT</symbol> then you may use the
global pointer <literal>SPITupleTable *SPI_tuptable</literal> to
access the result rows.
</para>
@ -330,7 +384,7 @@ typedef struct
} SPITupleTable;
</programlisting>
<structfield>vals</> is an array of pointers to rows. (The number
of valid entries is given by <varname>SPI_processed</varname>).
of valid entries is given by <varname>SPI_processed</varname>.)
<structfield>tupdesc</> is a row descriptor which you may pass to
SPI functions dealing with rows. <structfield>tuptabcxt</>,
<structfield>alloced</>, and <structfield>free</> are internal
@ -358,6 +412,15 @@ typedef struct
</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>count</parameter></literal></term>
<listitem>
@ -504,14 +567,15 @@ typedef struct
<title>Notes</title>
<para>
The functions <function>SPI_exec</function>,
<function>SPI_execp</function>, and
<function>SPI_prepare</function> change both
The functions <function>SPI_execute</function>,
<function>SPI_exec</function>,
<function>SPI_execute_plan</function>, and
<function>SPI_execp</function> change both
<varname>SPI_processed</varname> and
<varname>SPI_tuptable</varname> (just the pointer, not the contents
of the structure). Save these two global variables into local
procedure variables if you need to access the result of
<function>SPI_exec</function> or <function>SPI_execp</function>
procedure variables if you need to access the result table of
<function>SPI_execute</function> or a related function
across later calls.
</para>
</refsect1>
@ -519,6 +583,70 @@ typedef struct
<!-- *********************************************** -->
<refentry id="spi-spi-exec">
<refmeta>
<refentrytitle>SPI_exec</refentrytitle>
</refmeta>
<refnamediv>
<refname>SPI_exec</refname>
<refpurpose>execute a read/write command</refpurpose>
</refnamediv>
<indexterm><primary>SPI_exec</primary></indexterm>
<refsynopsisdiv>
<synopsis>
int SPI_exec(const char * <parameter>command</parameter>, int <parameter>count</parameter>)
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<function>SPI_exec</function> is the same as
<function>SPI_execute</function>, with the latter's
<parameter>read_only</parameter> parameter always taken as
<literal>false</>.
</para>
</refsect1>
<refsect1>
<title>Arguments</title>
<variablelist>
<varlistentry>
<term><literal>const char * <parameter>command</parameter></literal></term>
<listitem>
<para>
string containing command to execute
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>int <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>
See <function>SPI_execute</function>.
</para>
</refsect1>
</refentry>
<!-- *********************************************** -->
<refentry id="spi-spi-prepare">
<refmeta>
<refentrytitle>SPI_prepare</refentrytitle>
@ -551,14 +679,14 @@ void * SPI_prepare(const char * <parameter>command</parameter>, int <parameter>n
may be advantageous to perform the planning only once.
<function>SPI_prepare</function> converts a command string into an
execution plan that can be executed repeatedly using
<function>SPI_execp</function>.
<function>SPI_execute_plan</function>.
</para>
<para>
A prepared command can be generalized by writing parameters
(<literal>$1</>, <literal>$2</>, etc.) in place of what would be
constants in a normal command. The actual values of the parameters
are then specified when <function>SPI_execp</function> is called.
are then specified when <function>SPI_execute_plan</function> is called.
This allows the prepared command to be used over a wider range of
situations than would be possible without parameters.
</para>
@ -610,10 +738,10 @@ void * SPI_prepare(const char * <parameter>command</parameter>, int <parameter>n
<title>Return Value</title>
<para>
<function>SPI_prepare</function> returns non-null pointer to an
execution plan. On error, <symbol>NULL</symbol> will be returned.
In both cases, <varname>SPI_result</varname> will be set analogous
to the value returned by <function>SPI_exec</function>, except that
<function>SPI_prepare</function> returns a non-null pointer to an
execution plan. On error, <symbol>NULL</symbol> will be returned,
and <varname>SPI_result</varname> will be set to one of the same
error codes used by <function>SPI_execute</function>, except that
it is set to <symbol>SPI_ERROR_ARGUMENT</symbol> if
<parameter>command</parameter> is <symbol>NULL</symbol>, or if
<parameter>nargs</> is less than 0, or if <parameter>nargs</> is
@ -642,7 +770,7 @@ void * SPI_prepare(const char * <parameter>command</parameter>, int <parameter>n
<refnamediv>
<refname>SPI_getargcount</refname>
<refpurpose>returns the number of arguments needed when executing a plan
<refpurpose>returns the number of arguments needed by a plan
prepared by <function>SPI_prepare</function></refpurpose>
</refnamediv>
@ -659,7 +787,7 @@ int SPI_getargcount(void * <parameter>plan</parameter>)
<para>
<function>SPI_getargcount</function> returns the number of arguments needed
when executing a plan prepared by <function>SPI_prepare</function>.
to execute a plan prepared by <function>SPI_prepare</function>.
</para>
</refsect1>
@ -681,7 +809,7 @@ int SPI_getargcount(void * <parameter>plan</parameter>)
<refsect1>
<title>Return Value</title>
<para>
The expected argument count for the <parameter>plan</parameter> or
The expected argument count for the <parameter>plan</parameter>, or
<symbol>SPI_ERROR_ARGUMENT</symbol> if the <parameter>plan
</parameter> is <symbol>NULL</symbol>
</para>
@ -697,8 +825,8 @@ int SPI_getargcount(void * <parameter>plan</parameter>)
<refnamediv>
<refname>SPI_getargtypeid</refname>
<refpurpose>returns the expected typeid for the specified argument when
executing a plan prepared by <function>SPI_prepare</function></refpurpose>
<refpurpose>returns the expected typeid for the specified argument of
a plan prepared by <function>SPI_prepare</function></refpurpose>
</refnamediv>
<indexterm><primary>SPI_getargtypeid</primary></indexterm>
@ -714,7 +842,7 @@ Oid SPI_getargtypeid(void * <parameter>plan</parameter>, int <parameter>argIndex
<para>
<function>SPI_getargtypeid</function> returns the Oid representing the type
id for argument at <parameter>argIndex</parameter> in a plan prepared by
id for the <parameter>argIndex</parameter>'th argument of a plan prepared by
<function>SPI_prepare</function>. First argument is at index zero.
</para>
</refsect1>
@ -746,11 +874,11 @@ Oid SPI_getargtypeid(void * <parameter>plan</parameter>, int <parameter>argIndex
<refsect1>
<title>Return Value</title>
<para>
The type id of the argument at the given index or <symbol>
SPI_ERROR_ARGUMENT</symbol> if the <parameter>plan</parameter> is
The type id of the argument at the given index, or
<symbol>SPI_ERROR_ARGUMENT</symbol> if the <parameter>plan</parameter> is
<symbol>NULL</symbol> or <parameter>argIndex</parameter> is less than 0 or
not less than the number of arguments declared for the <parameter>plan
</parameter>
not less than the number of arguments declared for the
<parameter>plan</parameter>
</para>
</refsect1>
</refentry>
@ -765,8 +893,8 @@ Oid SPI_getargtypeid(void * <parameter>plan</parameter>, int <parameter>argIndex
<refnamediv>
<refname>SPI_is_cursor_plan</refname>
<refpurpose>returns <symbol>true</symbol> if a plan
prepared by <function>SPI_prepare</function> can be passed
as an argument to <function>SPI_cursor_open</function></refpurpose>
prepared by <function>SPI_prepare</function> can be used with
<function>SPI_cursor_open</function></refpurpose>
</refnamediv>
<indexterm><primary>SPI_is_cursor_plan</primary></indexterm>
@ -784,7 +912,7 @@ bool SPI_is_cursor_plan(void * <parameter>plan</parameter>)
<function>SPI_is_cursor_plan</function> returns <symbol>true</symbol>
if a plan prepared by <function>SPI_prepare</function> can be passed
as an argument to <function>SPI_cursor_open</function> and <symbol>
false</symbol> if that is not the case. The criteria is that the
false</symbol> if that is not the case. The criteria are that the
<parameter>plan</parameter> represents one single command and that this
command is a <command>SELECT</command> without an <command>INTO</command>
clause.
@ -819,21 +947,22 @@ bool SPI_is_cursor_plan(void * <parameter>plan</parameter>)
<!-- *********************************************** -->
<refentry id="spi-spi-execp">
<refentry id="spi-spi-execute-plan">
<refmeta>
<refentrytitle>SPI_execp</refentrytitle>
<refentrytitle>SPI_execute_plan</refentrytitle>
</refmeta>
<refnamediv>
<refname>SPI_execp</refname>
<refname>SPI_execute_plan</refname>
<refpurpose>executes a plan prepared by <function>SPI_prepare</function></refpurpose>
</refnamediv>
<indexterm><primary>SPI_execp</primary></indexterm>
<indexterm><primary>SPI_execute_plan</primary></indexterm>
<refsynopsisdiv>
<synopsis>
int SPI_execp(void * <parameter>plan</parameter>, Datum * <parameter>values</parameter>, const char * <parameter>nulls</parameter>, int <parameter>count</parameter>)
int SPI_execute_plan(void * <parameter>plan</parameter>, Datum * <parameter>values</parameter>, const char * <parameter>nulls</parameter>,
bool <parameter>read_only</parameter>, int <parameter>count</parameter>)
</synopsis>
</refsynopsisdiv>
@ -841,9 +970,10 @@ int SPI_execp(void * <parameter>plan</parameter>, Datum * <parameter>values</par
<title>Description</title>
<para>
<function>SPI_execp</function> executes a plan prepared by
<function>SPI_prepare</function>. <parameter>tcount</parameter>
has the same interpretation as in <function>SPI_exec</function>.
<function>SPI_execute_plan</function> executes a plan prepared by
<function>SPI_prepare</function>. <parameter>read_only</parameter> and
<parameter>count</parameter> have the same interpretation as in
<function>SPI_execute</function>.
</para>
</refsect1>
@ -861,10 +991,11 @@ int SPI_execp(void * <parameter>plan</parameter>, Datum * <parameter>values</par
</varlistentry>
<varlistentry>
<term><literal>Datum *<parameter>values</parameter></literal></term>
<term><literal>Datum * <parameter>values</parameter></literal></term>
<listitem>
<para>
actual parameter values
An array of actual parameter values. Must have same length as the
plan's number of arguments.
</para>
</listitem>
</varlistentry>
@ -873,7 +1004,8 @@ int SPI_execp(void * <parameter>plan</parameter>, Datum * <parameter>values</par
<term><literal>const char * <parameter>nulls</parameter></literal></term>
<listitem>
<para>
An array describing which parameters are null.
An array describing which parameters are null. Must have same length as
the plan's number of arguments.
<literal>n</literal> indicates a null value (entry in
<parameter>values</> will be ignored); a space indicates a
nonnull value (entry in <parameter>values</> is valid).
@ -881,17 +1013,26 @@ int SPI_execp(void * <parameter>plan</parameter>, Datum * <parameter>values</par
<para>
If <parameter>nulls</parameter> is <symbol>NULL</symbol> then
<function>SPI_execp</function> assumes that no parameters are
<function>SPI_execute_plan</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>count</parameter></literal></term>
<listitem>
<para>
number of row for which plan is to be executed
maximum number of rows to process or return
</para>
</listitem>
</varlistentry>
@ -902,8 +1043,8 @@ int SPI_execp(void * <parameter>plan</parameter>, Datum * <parameter>values</par
<title>Return Value</title>
<para>
The return value is the same as for <function>SPI_exec</function>
or one of the following:
The return value is the same as for <function>SPI_execute</function>,
with the following additional possible error (negative) results:
<variablelist>
<varlistentry>
@ -931,7 +1072,7 @@ int SPI_execp(void * <parameter>plan</parameter>, Datum * <parameter>values</par
<para>
<varname>SPI_processed</varname> and
<varname>SPI_tuptable</varname> are set as in
<function>SPI_exec</function> if successful.
<function>SPI_execute</function> if successful.
</para>
</refsect1>
@ -941,7 +1082,106 @@ int SPI_execp(void * <parameter>plan</parameter>, Datum * <parameter>values</par
<para>
If one of the objects (a table, function, etc.) referenced by the
prepared plan is dropped during the session then the result of
<function>SPI_execp</function> for this plan will be unpredictable.
<function>SPI_execute_plan</function> for this plan will be unpredictable.
</para>
</refsect1>
</refentry>
<!-- *********************************************** -->
<refentry id="spi-spi-execp">
<refmeta>
<refentrytitle>SPI_execp</refentrytitle>
</refmeta>
<refnamediv>
<refname>SPI_execp</refname>
<refpurpose>executes a plan in read/write mode</refpurpose>
</refnamediv>
<indexterm><primary>SPI_execp</primary></indexterm>
<refsynopsisdiv>
<synopsis>
int SPI_execp(void * <parameter>plan</parameter>, Datum * <parameter>values</parameter>, const char * <parameter>nulls</parameter>, int <parameter>count</parameter>)
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<function>SPI_execp</function> is the same as
<function>SPI_execute_plan</function>, with the latter's
<parameter>read_only</parameter> parameter always taken as
<literal>false</>.
</para>
</refsect1>
<refsect1>
<title>Arguments</title>
<variablelist>
<varlistentry>
<term><literal>void * <parameter>plan</parameter></literal></term>
<listitem>
<para>
execution plan (returned by <function>SPI_prepare</function>)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>Datum * <parameter>values</parameter></literal></term>
<listitem>
<para>
An array of actual parameter values. Must have same length as the
plan's number of arguments.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>const char * <parameter>nulls</parameter></literal></term>
<listitem>
<para>
An array describing which parameters are null. Must have same length as
the plan's number of arguments.
<literal>n</literal> indicates a null value (entry in
<parameter>values</> will be ignored); a space indicates a
nonnull value (entry in <parameter>values</> is valid).
</para>
<para>
If <parameter>nulls</parameter> is <symbol>NULL</symbol> then
<function>SPI_execp</function> assumes that no parameters are
null.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>int <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>
See <function>SPI_execute_plan</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>
@ -962,7 +1202,9 @@ int SPI_execp(void * <parameter>plan</parameter>, Datum * <parameter>values</par
<refsynopsisdiv>
<synopsis>
Portal SPI_cursor_open(const char * <parameter>name</parameter>, void * <parameter>plan</parameter>, Datum * <parameter>values</parameter>, const char * <parameter>nulls</parameter>)
Portal SPI_cursor_open(const char * <parameter>name</parameter>, void * <parameter>plan</parameter>,
Datum * <parameter>values</parameter>, const char * <parameter>nulls</parameter>,
bool <parameter>read_only</parameter>)
</synopsis>
</refsynopsisdiv>
@ -972,7 +1214,9 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, void * <paramet
<para>
<function>SPI_cursor_open</function> sets up a cursor (internally,
a portal) that will execute a plan prepared by
<function>SPI_prepare</function>.
<function>SPI_prepare</function>. The parameters have the same
meanings as the corresponding parameters to
<function>SPI_execute_plan</function>.
</para>
<para>
@ -1013,22 +1257,36 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, void * <paramet
<term><literal>Datum * <parameter>values</parameter></literal></term>
<listitem>
<para>
actual parameter values
An array of actual parameter values. Must have same length as the
plan's number of arguments.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>const char *<parameter>nulls</parameter></literal></term>
<term><literal>const char * <parameter>nulls</parameter></literal></term>
<listitem>
<para>
An array describing which parameters are null values.
An array describing which parameters are null. Must have same length as
the plan's number of arguments.
<literal>n</literal> indicates a null value (entry in
<parameter>values</> will be ignored); a space indicates a
nonnull value (entry in <parameter>values</> is valid). If
<parameter>nulls</parameter> is <symbol>NULL</> then
<function>SPI_cursor_open</function> assumes that no parameters
are null.
nonnull value (entry in <parameter>values</> is valid).
</para>
<para>
If <parameter>nulls</parameter> is <symbol>NULL</symbol> then
<function>SPI_cursor_open</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>
@ -1168,7 +1426,7 @@ void SPI_cursor_fetch(Portal <parameter>portal</parameter>, bool <parameter>forw
<para>
<varname>SPI_processed</varname> and
<varname>SPI_tuptable</varname> are set as in
<function>SPI_exec</function> if successful.
<function>SPI_execute</function> if successful.
</para>
</refsect1>
</refentry>
@ -1320,7 +1578,7 @@ void * SPI_saveplan(void * <parameter>plan</parameter>)
your procedure in the current session. You may save the pointer
returned in a local variable. Always check if this pointer is
<symbol>NULL</symbol> or not either when preparing a plan or using
an already prepared plan in <function>SPI_execp</function>.
an already prepared plan in <function>SPI_execute_plan</function>.
</para>
</refsect1>
@ -1374,7 +1632,7 @@ void * SPI_saveplan(void * <parameter>plan</parameter>)
<para>
If one of the objects (a table, function, etc.) referenced by the
prepared plan is dropped during the session then the results of
<function>SPI_execp</function> for this plan will be unpredictable.
<function>SPI_execute_plan</function> for this plan will be unpredictable.
</para>
</refsect1>
</refentry>
@ -1386,7 +1644,7 @@ void * SPI_saveplan(void * <parameter>plan</parameter>)
<para>
The functions described here provide an interface for extracting
information from result sets returned by <function>SPI_exec</> and
information from result sets returned by <function>SPI_execute</> and
other SPI functions.
</para>
@ -2360,7 +2618,8 @@ HeapTuple SPI_modifytuple(Relation <parameter>rel</parameter>, HeapTuple <parame
<term><literal>const char * <parameter>Nulls</parameter></literal></term>
<listitem>
<para>
which new values are null, if any (see <function>SPI_execp</function> for the format)
which new values are null, if any (see
<function>SPI_execute_plan</function> for the format)
</para>
</listitem>
</varlistentry>
@ -2466,7 +2725,8 @@ void SPI_freetuple(HeapTuple <parameter>row</parameter>)
<refnamediv>
<refname>SPI_freetuptable</refname>
<refpurpose>free a row set created by <function>SPI_exec</> or a similar function</refpurpose>
<refpurpose>free a row set created by <function>SPI_execute</> or a similar
function</refpurpose>
</refnamediv>
<indexterm><primary>SPI_freetuptable</primary></indexterm>
@ -2483,7 +2743,7 @@ void SPI_freetuptable(SPITupleTable * <parameter>tuptable</parameter>)
<para>
<function>SPI_freetuptable</function> frees a row set created by a
prior SPI command execution function, such as
<function>SPI_exec</>. Therefore, this function is usually called
<function>SPI_execute</>. Therefore, this function is usually called
with the global variable <varname>SPI_tupletable</varname> as
argument.
</para>

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.86 2004/08/24 00:06:50 neilc Exp $
$PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.87 2004/09/13 20:05:25 tgl Exp $
-->
<sect1 id="xfunc">
@ -2404,14 +2404,6 @@ CREATE FUNCTION make_array(anyelement) RETURNS anyarray
number of arguments, up to a finite maximum number.
</para>
<para>
A function may also have the same name as an attribute. (Recall
that <literal>attribute(table)</literal> is equivalent to
<literal>table.attribute</literal>.) In the case that there is an
ambiguity between a function on a complex type and an attribute of
the complex type, the attribute will always be used.
</para>
<para>
When creating a family of overloaded functions, one should be
careful not to create ambiguities. For instance, given the
@ -2427,6 +2419,18 @@ CREATE FUNCTION test(smallint, double precision) RETURNS ...
relies on this behavior.
</para>
<para>
A function that takes a single argument of a composite type should
generally not have the same name as any attribute (field) of that type.
Recall that <literal>attribute(table)</literal> is considered equivalent
to <literal>table.attribute</literal>. In the case that there is an
ambiguity between a function on a composite type and an attribute of
the composite type, the attribute will always be used. It is possible
to override that choice by schema-qualifying the function name
(that is, <literal>schema.func(table)</literal>) but it's better to
avoid the problem by not choosing conflicting names.
</para>
<para>
When overloading C-language functions, there is an additional
constraint: The C name of each function in the family of
@ -2437,7 +2441,7 @@ CREATE FUNCTION test(smallint, double precision) RETURNS ...
(usually the internal one). The alternative form of the
<literal>AS</> clause for the SQL <command>CREATE
FUNCTION</command> command decouples the SQL function name from
the function name in the C source code. E.g.,
the function name in the C source code. For instance,
<programlisting>
CREATE FUNCTION test(int) RETURNS int
AS '<replaceable>filename</>', 'test_1arg'
@ -2450,6 +2454,128 @@ CREATE FUNCTION test(int, int) RETURNS int
</para>
</sect1>
<sect1 id="xfunc-volatility">
<title>Function Volatility Categories</title>
<indexterm zone="xfunc-volatility">
<primary>volatility</primary>
<secondary>functions</secondary>
</indexterm>
<para>
Every function has a <firstterm>volatility</> classification, with
the possibilities being <literal>VOLATILE</>, <literal>STABLE</>, or
<literal>IMMUTABLE</>. <literal>VOLATILE</> is the default if the
<command>CREATE FUNCTION</command> command does not specify a category.
The volatility category is a promise to the optimizer about the behavior
of the function:
<itemizedlist>
<listitem>
<para>
A <literal>VOLATILE</> function can do anything, including modifying
the database. It can return different results on successive calls with
the same arguments. The optimizer makes no assumptions about the
behavior of such functions. A query using a volatile function will
re-evaluate the function at every row where its value is needed.
</para>
</listitem>
<listitem>
<para>
A <literal>STABLE</> function cannot modify the database and is
guaranteed to return the same results given the same arguments
for all calls within a single surrounding query. This category
allows the optimizer to optimize away multiple calls of the function
within a single query. In particular, it is safe to use an expression
containing such a function in an indexscan condition. (Since an
indexscan will evaluate the comparison value only once, not once at
each row, it is not valid to use a <literal>VOLATILE</> function in
an indexscan condition.)
</para>
</listitem>
<listitem>
<para>
An <literal>IMMUTABLE</> function cannot modify the database and is
guaranteed to return the same results given the same arguments forever.
This category allows the optimizer to pre-evaluate the function when
a query calls it with constant arguments. For example, a query like
<literal>SELECT ... WHERE x = 2 + 2</> can be simplified on sight to
<literal>SELECT ... WHERE x = 4</>, because the function underlying
the integer addition operator is marked <literal>IMMUTABLE</>.
</para>
</listitem>
</itemizedlist>
</para>
<para>
For best optimization results, you should label your functions with the
strictest volatility category that is valid for them.
</para>
<para>
Any function with side-effects <emphasis>must</> be labeled
<literal>VOLATILE</>, so that calls to it cannot be optimized away.
Even a function with no side-effects needs to be labeled
<literal>VOLATILE</> if its value can change within a single query;
some examples are <literal>random()</>, <literal>currval()</>,
<literal>timeofday()</>.
</para>
<para>
There is relatively little difference between <literal>STABLE</> and
<literal>IMMUTABLE</> categories when considering simple interactive
queries that are planned and immediately executed: it doesn't matter
a lot whether a function is executed once during planning or once during
query execution startup. But there is a big difference if the plan is
saved and reused later. Labeling a function <literal>IMMUTABLE</> when
it really isn't may allow it to be prematurely folded to a constant during
planning, resulting in a stale value being re-used during subsequent uses
of the plan. This is a hazard when using prepared statements or when
using function languages that cache plans (such as
<application>PL/pgSQL</>).
</para>
<para>
Because of the snapshotting behavior of MVCC (see <xref linkend="mvcc">)
a function containing only <command>SELECT</> commands can safely be
marked <literal>STABLE</>, even if it selects from tables that might be
undergoing modifications by concurrent queries.
<productname>PostgreSQL</productname> will execute a <literal>STABLE</>
function using the snapshot established for the calling query, and so it
will see a fixed view of the database throughout that query.
Also note
that the <function>current_timestamp</> family of functions qualify
as stable, since their values do not change within a transaction.
</para>
<para>
The same snapshotting behavior is used for <command>SELECT</> commands
within <literal>IMMUTABLE</> functions. It is generally unwise to select
from database tables within an <literal>IMMUTABLE</> function at all,
since the immutability will be broken if the table contents ever change.
However, <productname>PostgreSQL</productname> does not enforce that you
do not do that.
</para>
<para>
A common error is to label a function <literal>IMMUTABLE</> when its
results depend on a configuration parameter. For example, a function
that manipulates timestamps might well have results that depend on the
<xref linkend="guc-timezone"> setting. For safety, such functions should
be labeled <literal>STABLE</> instead.
</para>
<note>
<para>
Before <productname>PostgreSQL</productname> release 8.0, the requirement
that <literal>STABLE</> and <literal>IMMUTABLE</> functions cannot modify
the database was not enforced by the system. Release 8.0 enforces it
by requiring SQL functions and procedural language functions of these
categories to contain no SQL commands other than <command>SELECT</>.
</para>
</note>
</sect1>
<!-- Keep this comment at the end of the file
Local variables:
mode:sgml

View File

@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.187 2004/09/10 18:39:55 tgl Exp $
* $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.188 2004/09/13 20:06:04 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -401,11 +401,11 @@ CommandCounterIncrement(void)
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("cannot have more than 2^32-1 commands in a transaction")));
/* Propagate new command ID into query snapshots, if set */
if (QuerySnapshot)
QuerySnapshot->curcid = s->commandId;
/* Propagate new command ID into static snapshots, if set */
if (SerializableSnapshot)
SerializableSnapshot->curcid = s->commandId;
if (LatestSnapshot)
LatestSnapshot->curcid = s->commandId;
/*
* make cache changes visible to me.
@ -3001,8 +3001,10 @@ CommitSubTransaction(void)
s->state = TRANS_COMMIT;
/* Mark subtransaction as subcommitted */
/* Must CCI to ensure commands of subtransaction are seen as done */
CommandCounterIncrement();
/* Mark subtransaction as subcommitted */
RecordSubTransactionCommit();
AtSubCommit_childXids();

View File

@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.129 2004/08/29 05:06:41 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.130 2004/09/13 20:06:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -202,8 +202,8 @@ cluster(ClusterStmt *stmt)
/* Start a new transaction for each relation. */
StartTransactionCommand();
SetQuerySnapshot(); /* might be needed for functions in
* indexes */
/* functions in indexes may want a snapshot set */
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
cluster_rel(rvtc, true);
CommitTransactionCommand();
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.231 2004/09/10 18:39:56 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.232 2004/09/13 20:06:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1182,7 +1182,6 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
Oid *typioparams;
bool *isvarlena;
char *string;
Snapshot mySnapshot;
ListCell *cur;
MemoryContext oldcontext;
MemoryContext mycontext;
@ -1260,9 +1259,7 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
strlen(null_print));
}
mySnapshot = CopyQuerySnapshot();
scandesc = heap_beginscan(rel, mySnapshot, 0, NULL);
scandesc = heap_beginscan(rel, ActiveSnapshot, 0, NULL);
while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL)
{

View File

@ -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.125 2004/09/10 18:39:56 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.126 2004/09/13 20:06:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -180,7 +180,9 @@ ExplainOneQuery(Query *query, ExplainStmt *stmt, TupOutputState *tstate)
plan = planner(query, isCursor, cursorOptions, NULL);
/* Create a QueryDesc requesting no output */
queryDesc = CreateQueryDesc(query, plan, None_Receiver, NULL,
queryDesc = CreateQueryDesc(query, plan,
ActiveSnapshot, InvalidSnapshot,
None_Receiver, NULL,
stmt->analyze);
ExplainOnePlan(queryDesc, stmt, tstate);
@ -212,7 +214,7 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
AfterTriggerBeginQuery();
/* call ExecutorStart to prepare the plan for execution */
ExecutorStart(queryDesc, false, !stmt->analyze);
ExecutorStart(queryDesc, !stmt->analyze);
/* Execute the plan for statistics if asked for */
if (stmt->analyze)
@ -272,7 +274,9 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
FreeQueryDesc(queryDesc);
CommandCounterIncrement();
/* We need a CCI just in case query expanded to multiple plans */
if (stmt->analyze)
CommandCounterIncrement();
totaltime += elapsed_time(&starttime);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.125 2004/08/29 05:06:41 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.126 2004/09/13 20:06:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1060,8 +1060,8 @@ ReindexDatabase(const char *dbname, bool force /* currently unused */ ,
Oid relid = lfirst_oid(l);
StartTransactionCommand();
SetQuerySnapshot(); /* might be needed for functions in
* indexes */
/* functions in indexes may want a snapshot set */
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
if (reindex_relation(relid, true))
ereport(NOTICE,
(errmsg("table \"%s\" was reindexed",

View File

@ -14,7 +14,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.34 2004/09/10 18:39:56 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.35 2004/09/13 20:06:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -135,7 +135,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
/*
* Start execution, inserting parameters if any.
*/
PortalStart(portal, params);
PortalStart(portal, params, ActiveSnapshot);
Assert(portal->strategy == PORTAL_ONE_SELECT);

View File

@ -10,7 +10,7 @@
* Copyright (c) 2002-2004, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.31 2004/08/29 05:06:41 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.32 2004/09/13 20:06:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -186,7 +186,7 @@ ExecuteQuery(ExecuteStmt *stmt, DestReceiver *dest, char *completionTag)
/*
* Run the portal to completion.
*/
PortalStart(portal, paramLI);
PortalStart(portal, paramLI, ActiveSnapshot);
(void) PortalRun(portal, FETCH_ALL, dest, dest, completionTag);
@ -544,7 +544,9 @@ ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate)
}
/* Create a QueryDesc requesting no output */
qdesc = CreateQueryDesc(query, plan, None_Receiver,
qdesc = CreateQueryDesc(query, plan,
ActiveSnapshot, InvalidSnapshot,
None_Receiver,
paramLI, stmt->analyze);
ExplainOnePlan(qdesc, stmt, tstate);

View File

@ -13,7 +13,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.290 2004/08/30 02:54:38 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.291 2004/09/13 20:06:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -402,8 +402,8 @@ vacuum(VacuumStmt *vacstmt)
if (use_own_xacts)
{
StartTransactionCommand();
SetQuerySnapshot(); /* might be needed for functions
* in indexes */
/* functions in indexes may want a snapshot set */
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
}
else
old_context = MemoryContextSwitchTo(anl_context);
@ -865,8 +865,8 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind)
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
SetQuerySnapshot(); /* might be needed for functions in
* indexes */
/* functions in indexes may want a snapshot set */
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
/*
* Tell the cache replacement strategy that vacuum is causing all

View File

@ -26,7 +26,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.237 2004/09/11 18:28:34 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.238 2004/09/13 20:06:46 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -106,15 +106,6 @@ static void EvalPlanQualStop(evalPlanQual *epq);
* field of the QueryDesc is filled in to describe the tuples that will be
* returned, and the internal fields (estate and planstate) are set up.
*
* If useCurrentSnapshot is true, run the query with the latest available
* snapshot, instead of the normal QuerySnapshot. Also, if it's an update
* or delete query, check that the rows to be updated or deleted would be
* visible to the normal QuerySnapshot. (This is a special-case behavior
* needed for referential integrity updates in serializable transactions.
* We must check all currently-committed rows, but we want to throw a
* can't-serialize error if any rows that would need updates would not be
* visible under the normal serializable snapshot.)
*
* If explainOnly is true, we are not actually intending to run the plan,
* only to set up for EXPLAIN; so skip unwanted side-effects.
*
@ -123,7 +114,7 @@ static void EvalPlanQualStop(evalPlanQual *epq);
* ----------------------------------------------------------------
*/
void
ExecutorStart(QueryDesc *queryDesc, bool useCurrentSnapshot, bool explainOnly)
ExecutorStart(QueryDesc *queryDesc, bool explainOnly)
{
EState *estate;
MemoryContext oldcontext;
@ -156,28 +147,12 @@ ExecutorStart(QueryDesc *queryDesc, bool useCurrentSnapshot, bool explainOnly)
estate->es_param_exec_vals = (ParamExecData *)
palloc0(queryDesc->plantree->nParamExec * sizeof(ParamExecData));
estate->es_instrument = queryDesc->doInstrument;
/*
* Make our own private copy of the current query snapshot data.
*
* This "freezes" our idea of which tuples are good and which are not for
* the life of this query, even if it outlives the current command and
* current snapshot.
* Copy other important information into the EState
*/
if (useCurrentSnapshot)
{
/* RI update/delete query --- must use an up-to-date snapshot */
estate->es_snapshot = CopyCurrentSnapshot();
/* crosscheck updates/deletes against transaction snapshot */
estate->es_crosscheck_snapshot = CopyQuerySnapshot();
}
else
{
/* normal query --- use query snapshot, no crosscheck */
estate->es_snapshot = CopyQuerySnapshot();
estate->es_crosscheck_snapshot = InvalidSnapshot;
}
estate->es_snapshot = queryDesc->snapshot;
estate->es_crosscheck_snapshot = queryDesc->crosscheck_snapshot;
estate->es_instrument = queryDesc->doInstrument;
/*
* Initialize the plan state tree
@ -1454,6 +1429,11 @@ ExecDelete(TupleTableSlot *slot,
/*
* delete the tuple
*
* Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
* the row to be deleted is visible to that snapshot, and throw a can't-
* serialize error if not. This is a special-case behavior needed for
* referential integrity updates in serializable transactions.
*/
ldelete:;
result = heap_delete(resultRelationDesc, tupleid,
@ -1591,6 +1571,11 @@ lreplace:;
/*
* replace the heap tuple
*
* Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
* the row to be updated is visible to that snapshot, and throw a can't-
* serialize error if not. This is a special-case behavior needed for
* referential integrity updates in serializable transactions.
*/
result = heap_update(resultRelationDesc, tupleid, tuple,
&ctid,

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.88 2004/09/10 18:39:57 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.89 2004/09/13 20:06:46 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -65,6 +65,7 @@ typedef struct
bool typbyval; /* true if return type is pass by value */
bool returnsTuple; /* true if returning whole tuple result */
bool shutdown_reg; /* true if registered shutdown callback */
bool readonly_func; /* true to run in "read only" mode */
ParamListInfo paramLI; /* Param list representing current args */
@ -76,11 +77,12 @@ typedef SQLFunctionCache *SQLFunctionCachePtr;
/* non-export function prototypes */
static execution_state *init_execution_state(List *queryTree_list);
static execution_state *init_execution_state(List *queryTree_list,
bool readonly_func);
static void init_sql_fcache(FmgrInfo *finfo);
static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache);
static TupleTableSlot *postquel_getnext(execution_state *es);
static void postquel_end(execution_state *es);
static void postquel_end(execution_state *es, SQLFunctionCachePtr fcache);
static void postquel_sub_params(SQLFunctionCachePtr fcache,
FunctionCallInfo fcinfo);
static Datum postquel_execute(execution_state *es,
@ -91,7 +93,7 @@ static void ShutdownSQLFunction(Datum arg);
static execution_state *
init_execution_state(List *queryTree_list)
init_execution_state(List *queryTree_list, bool readonly_func)
{
execution_state *firstes = NULL;
execution_state *preves = NULL;
@ -103,6 +105,22 @@ init_execution_state(List *queryTree_list)
Plan *planTree;
execution_state *newes;
/* Precheck all commands for validity in a function */
if (queryTree->commandType == CMD_UTILITY &&
IsA(queryTree->utilityStmt, TransactionStmt))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s is a SQL statement name */
errmsg("%s is not allowed in a SQL function",
CreateQueryTag(queryTree))));
if (readonly_func && !QueryIsReadOnly(queryTree))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s is a SQL statement name */
errmsg("%s is not allowed in a non-volatile function",
CreateQueryTag(queryTree))));
planTree = pg_plan_query(queryTree, NULL);
newes = (execution_state *) palloc(sizeof(execution_state));
@ -172,6 +190,10 @@ init_sql_fcache(FmgrInfo *finfo)
fcache->rettype = rettype;
/* Remember if function is STABLE/IMMUTABLE */
fcache->readonly_func =
(procedureStruct->provolatile != PROVOLATILE_VOLATILE);
/* Now look up the actual result type */
typeTuple = SearchSysCache(TYPEOID,
ObjectIdGetDatum(rettype),
@ -253,7 +275,8 @@ init_sql_fcache(FmgrInfo *finfo)
queryTree_list);
/* Finally, plan the queries */
fcache->func_state = init_execution_state(queryTree_list);
fcache->func_state = init_execution_state(queryTree_list,
fcache->readonly_func);
pfree(src);
@ -267,16 +290,37 @@ init_sql_fcache(FmgrInfo *finfo)
static void
postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
{
Snapshot snapshot;
Assert(es->qd == NULL);
/*
* In a read-only function, use the surrounding query's snapshot;
* otherwise take a new snapshot for each query. The snapshot should
* include a fresh command ID so that all work to date in this
* transaction is visible. We copy in both cases so that postquel_end
* can unconditionally do FreeSnapshot.
*/
if (fcache->readonly_func)
snapshot = CopySnapshot(ActiveSnapshot);
else
{
CommandCounterIncrement();
snapshot = CopySnapshot(GetTransactionSnapshot());
}
es->qd = CreateQueryDesc(es->query, es->plan,
snapshot, InvalidSnapshot,
None_Receiver,
fcache->paramLI, false);
/* We assume we don't need to set up ActiveSnapshot for ExecutorStart */
/* Utility commands don't need Executor. */
if (es->qd->operation != CMD_UTILITY)
{
AfterTriggerBeginQuery();
ExecutorStart(es->qd, false, false);
ExecutorStart(es->qd, false);
}
es->status = F_EXEC_RUN;
@ -285,46 +329,82 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
static TupleTableSlot *
postquel_getnext(execution_state *es)
{
TupleTableSlot *result;
Snapshot saveActiveSnapshot;
long count;
if (es->qd->operation == CMD_UTILITY)
/* Make our snapshot the active one for any called functions */
saveActiveSnapshot = ActiveSnapshot;
PG_TRY();
{
/* Can't handle starting or committing a transaction */
if (IsA(es->qd->parsetree->utilityStmt, TransactionStmt))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot begin/end transactions in SQL functions")));
ProcessUtility(es->qd->parsetree->utilityStmt, es->qd->params,
es->qd->dest, NULL);
return NULL;
ActiveSnapshot = es->qd->snapshot;
if (es->qd->operation == CMD_UTILITY)
{
ProcessUtility(es->qd->parsetree->utilityStmt, es->qd->params,
es->qd->dest, NULL);
result = NULL;
}
else
{
/*
* If it's the function's last command, and it's a SELECT, fetch
* one row at a time so we can return the results. Otherwise just
* run it to completion. (If we run to completion then
* ExecutorRun is guaranteed to return NULL.)
*/
if (LAST_POSTQUEL_COMMAND(es) && es->qd->operation == CMD_SELECT)
count = 1L;
else
count = 0L;
result = ExecutorRun(es->qd, ForwardScanDirection, count);
}
}
PG_CATCH();
{
/* Restore global vars and propagate error */
ActiveSnapshot = saveActiveSnapshot;
PG_RE_THROW();
}
PG_END_TRY();
/*
* If it's the function's last command, and it's a SELECT, fetch one
* row at a time so we can return the results. Otherwise just run it
* to completion.
*/
if (LAST_POSTQUEL_COMMAND(es) && es->qd->operation == CMD_SELECT)
count = 1L;
else
count = 0L;
ActiveSnapshot = saveActiveSnapshot;
return ExecutorRun(es->qd, ForwardScanDirection, count);
return result;
}
static void
postquel_end(execution_state *es)
postquel_end(execution_state *es, SQLFunctionCachePtr fcache)
{
Snapshot saveActiveSnapshot;
/* mark status done to ensure we don't do ExecutorEnd twice */
es->status = F_EXEC_DONE;
/* Utility commands don't need Executor. */
if (es->qd->operation != CMD_UTILITY)
{
ExecutorEnd(es->qd);
AfterTriggerEndQuery();
/* Make our snapshot the active one for any called functions */
saveActiveSnapshot = ActiveSnapshot;
PG_TRY();
{
ActiveSnapshot = es->qd->snapshot;
ExecutorEnd(es->qd);
AfterTriggerEndQuery();
}
PG_CATCH();
{
/* Restore global vars and propagate error */
ActiveSnapshot = saveActiveSnapshot;
PG_RE_THROW();
}
PG_END_TRY();
ActiveSnapshot = saveActiveSnapshot;
}
FreeSnapshot(es->qd->snapshot);
FreeQueryDesc(es->qd);
es->qd = NULL;
}
@ -368,6 +448,8 @@ postquel_execute(execution_state *es,
SQLFunctionCachePtr fcache)
{
TupleTableSlot *slot;
HeapTuple tup;
TupleDesc tupDesc;
Datum value;
if (es->status == F_EXEC_START)
@ -377,101 +459,92 @@ postquel_execute(execution_state *es,
if (TupIsNull(slot))
{
postquel_end(es);
fcinfo->isnull = true;
/*
* If this isn't the last command for the function we have to
* increment the command counter so that subsequent commands can
* see changes made by previous ones.
* We fall out here for all cases except where we have obtained
* a row from a function's final SELECT.
*/
if (!LAST_POSTQUEL_COMMAND(es))
CommandCounterIncrement();
postquel_end(es, fcache);
fcinfo->isnull = true;
return (Datum) NULL;
}
if (LAST_POSTQUEL_COMMAND(es))
/*
* If we got a row from a command within the function it has to be
* the final command. All others shouldn't be returning anything.
*/
Assert(LAST_POSTQUEL_COMMAND(es));
/*
* Set up to return the function value.
*/
tup = slot->val;
tupDesc = slot->ttc_tupleDescriptor;
if (fcache->returnsTuple)
{
/*
* Set up to return the function value.
* We are returning the whole tuple, so copy it into current
* execution context and make sure it is a valid Datum.
*
* XXX do we need to remove junk attrs from the result tuple?
* Probably OK to leave them, as long as they are at the end.
*/
HeapTuple tup = slot->val;
TupleDesc tupDesc = slot->ttc_tupleDescriptor;
HeapTupleHeader dtup;
Oid dtuptype;
int32 dtuptypmod;
if (fcache->returnsTuple)
dtup = (HeapTupleHeader) palloc(tup->t_len);
memcpy((char *) dtup, (char *) tup->t_data, tup->t_len);
/*
* Use the declared return type if it's not RECORD; else take
* the type from the computed result, making sure a typmod has
* been assigned.
*/
if (fcache->rettype != RECORDOID)
{
/*
* We are returning the whole tuple, so copy it into current
* execution context and make sure it is a valid Datum.
*
* XXX do we need to remove junk attrs from the result tuple?
* Probably OK to leave them, as long as they are at the end.
*/
HeapTupleHeader dtup;
Oid dtuptype;
int32 dtuptypmod;
dtup = (HeapTupleHeader) palloc(tup->t_len);
memcpy((char *) dtup, (char *) tup->t_data, tup->t_len);
/*
* Use the declared return type if it's not RECORD; else take
* the type from the computed result, making sure a typmod has
* been assigned.
*/
if (fcache->rettype != RECORDOID)
{
/* function has a named composite return type */
dtuptype = fcache->rettype;
dtuptypmod = -1;
}
else
{
/* function is declared to return RECORD */
if (tupDesc->tdtypeid == RECORDOID &&
tupDesc->tdtypmod < 0)
assign_record_type_typmod(tupDesc);
dtuptype = tupDesc->tdtypeid;
dtuptypmod = tupDesc->tdtypmod;
}
HeapTupleHeaderSetDatumLength(dtup, tup->t_len);
HeapTupleHeaderSetTypeId(dtup, dtuptype);
HeapTupleHeaderSetTypMod(dtup, dtuptypmod);
value = PointerGetDatum(dtup);
fcinfo->isnull = false;
/* function has a named composite return type */
dtuptype = fcache->rettype;
dtuptypmod = -1;
}
else
{
/*
* Returning a scalar, which we have to extract from the first
* column of the SELECT result, and then copy into current
* execution context if needed.
*/
value = heap_getattr(tup, 1, tupDesc, &(fcinfo->isnull));
if (!fcinfo->isnull)
value = datumCopy(value, fcache->typbyval, fcache->typlen);
/* function is declared to return RECORD */
if (tupDesc->tdtypeid == RECORDOID &&
tupDesc->tdtypmod < 0)
assign_record_type_typmod(tupDesc);
dtuptype = tupDesc->tdtypeid;
dtuptypmod = tupDesc->tdtypmod;
}
/*
* If this is a single valued function we have to end the function
* execution now.
*/
if (!fcinfo->flinfo->fn_retset)
postquel_end(es);
HeapTupleHeaderSetDatumLength(dtup, tup->t_len);
HeapTupleHeaderSetTypeId(dtup, dtuptype);
HeapTupleHeaderSetTypMod(dtup, dtuptypmod);
return value;
value = PointerGetDatum(dtup);
fcinfo->isnull = false;
}
else
{
/*
* Returning a scalar, which we have to extract from the first
* column of the SELECT result, and then copy into current
* execution context if needed.
*/
value = heap_getattr(tup, 1, tupDesc, &(fcinfo->isnull));
if (!fcinfo->isnull)
value = datumCopy(value, fcache->typbyval, fcache->typlen);
}
/*
* If this isn't the last command for the function, we don't return
* any results, but we have to increment the command counter so that
* subsequent commands can see changes made by previous ones.
* If this is a single valued function we have to end the function
* execution now.
*/
CommandCounterIncrement();
return (Datum) NULL;
if (!fcinfo->flinfo->fn_retset)
postquel_end(es, fcache);
return value;
}
Datum
@ -726,7 +799,7 @@ ShutdownSQLFunction(Datum arg)
{
/* Shut down anything still running */
if (es->status == F_EXEC_RUN)
postquel_end(es);
postquel_end(es, fcache);
/* Reset states to START in case we're called again */
es->status = F_EXEC_START;
es = es->next;

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.126 2004/09/10 18:39:57 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.127 2004/09/13 20:06:46 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -34,13 +34,14 @@ static int _SPI_stack_depth = 0; /* allocated size of _SPI_stack */
static int _SPI_connected = -1;
static int _SPI_curid = -1;
static int _SPI_execute(const char *src, int tcount, _SPI_plan *plan);
static int _SPI_pquery(QueryDesc *queryDesc, bool runit,
bool useCurrentSnapshot, int tcount);
static void _SPI_prepare_plan(const char *src, _SPI_plan *plan);
static int _SPI_execute_plan(_SPI_plan *plan,
Datum *Values, const char *Nulls,
bool useCurrentSnapshot, int tcount);
Datum *Values, const char *Nulls,
Snapshot snapshot, Snapshot crosscheck_snapshot,
bool read_only, int tcount);
static int _SPI_pquery(QueryDesc *queryDesc, int tcount);
static void _SPI_error_callback(void *arg);
@ -252,9 +253,11 @@ SPI_pop(void)
_SPI_curid--;
}
/* Parse, plan, and execute a querystring */
int
SPI_exec(const char *src, int tcount)
SPI_execute(const char *src, bool read_only, int tcount)
{
_SPI_plan plan;
int res;
if (src == NULL || tcount < 0)
@ -264,42 +267,75 @@ SPI_exec(const char *src, int tcount)
if (res < 0)
return res;
res = _SPI_execute(src, tcount, NULL);
plan.plancxt = NULL; /* doesn't have own context */
plan.query = src;
plan.nargs = 0;
plan.argtypes = NULL;
_SPI_prepare_plan(src, &plan);
res = _SPI_execute_plan(&plan, NULL, NULL,
InvalidSnapshot, InvalidSnapshot,
read_only, tcount);
_SPI_end_call(true);
return res;
}
/* Obsolete version of SPI_execute */
int
SPI_exec(const char *src, int tcount)
{
return SPI_execute(src, false, tcount);
}
/* Execute a previously prepared plan */
int
SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
bool read_only, int tcount)
{
int res;
if (plan == NULL || tcount < 0)
return SPI_ERROR_ARGUMENT;
if (((_SPI_plan *) plan)->nargs > 0 && Values == NULL)
return SPI_ERROR_PARAM;
res = _SPI_begin_call(true);
if (res < 0)
return res;
res = _SPI_execute_plan((_SPI_plan *) plan,
Values, Nulls,
InvalidSnapshot, InvalidSnapshot,
read_only, tcount);
_SPI_end_call(true);
return res;
}
/* Obsolete version of SPI_execute_plan */
int
SPI_execp(void *plan, Datum *Values, const char *Nulls, int tcount)
{
int res;
if (plan == NULL || tcount < 0)
return SPI_ERROR_ARGUMENT;
if (((_SPI_plan *) plan)->nargs > 0 && Values == NULL)
return SPI_ERROR_PARAM;
res = _SPI_begin_call(true);
if (res < 0)
return res;
res = _SPI_execute_plan((_SPI_plan *) plan, Values, Nulls, false, tcount);
_SPI_end_call(true);
return res;
return SPI_execute_plan(plan, Values, Nulls, false, tcount);
}
/*
* SPI_execp_current -- identical to SPI_execp, except that we expose the
* Executor option to use a current snapshot instead of the normal
* QuerySnapshot. This is currently not documented in spi.sgml because
* it is only intended for use by RI triggers.
* SPI_execute_snapshot -- identical to SPI_execute_plan, except that we allow
* the caller to specify exactly which snapshots to use. This is currently
* not documented in spi.sgml because it is only intended for use by RI
* triggers.
*
* Passing snapshot == InvalidSnapshot will select the normal behavior of
* fetching a new snapshot for each query.
*/
int
SPI_execp_current(void *plan, Datum *Values, const char *Nulls,
bool useCurrentSnapshot, int tcount)
extern int
SPI_execute_snapshot(void *plan,
Datum *Values, const char *Nulls,
Snapshot snapshot, Snapshot crosscheck_snapshot,
bool read_only, int tcount)
{
int res;
@ -313,8 +349,10 @@ SPI_execp_current(void *plan, Datum *Values, const char *Nulls,
if (res < 0)
return res;
res = _SPI_execute_plan((_SPI_plan *) plan, Values, Nulls,
useCurrentSnapshot, tcount);
res = _SPI_execute_plan((_SPI_plan *) plan,
Values, Nulls,
snapshot, crosscheck_snapshot,
read_only, tcount);
_SPI_end_call(true);
return res;
@ -341,12 +379,10 @@ SPI_prepare(const char *src, int nargs, Oid *argtypes)
plan.nargs = nargs;
plan.argtypes = argtypes;
SPI_result = _SPI_execute(src, 0, &plan);
_SPI_prepare_plan(src, &plan);
if (SPI_result >= 0) /* copy plan to procedure context */
result = _SPI_copy_plan(&plan, _SPI_CPLAN_PROCXT);
else
result = NULL;
/* copy plan to procedure context */
result = _SPI_copy_plan(&plan, _SPI_CPLAN_PROCXT);
_SPI_end_call(true);
@ -756,7 +792,9 @@ SPI_freetuptable(SPITupleTable *tuptable)
* Open a prepared SPI plan as a portal
*/
Portal
SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
SPI_cursor_open(const char *name, void *plan,
Datum *Values, const char *Nulls,
bool read_only)
{
_SPI_plan *spiplan = (_SPI_plan *) plan;
List *qtlist = spiplan->qtlist;
@ -764,6 +802,7 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
Query *queryTree;
Plan *planTree;
ParamListInfo paramLI;
Snapshot snapshot;
MemoryContext oldcontext;
Portal portal;
int k;
@ -785,9 +824,6 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("cannot open SELECT INTO query as cursor")));
/* Increment CommandCounter to see changes made by now */
CommandCounterIncrement();
/* Reset SPI result */
SPI_processed = 0;
SPI_tuptable = NULL;
@ -866,10 +902,22 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
else
portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
/*
* Set up the snapshot to use. (PortalStart will do CopySnapshot,
* so we skip that here.)
*/
if (read_only)
snapshot = ActiveSnapshot;
else
{
CommandCounterIncrement();
snapshot = GetTransactionSnapshot();
}
/*
* Start portal execution.
*/
PortalStart(portal, paramLI);
PortalStart(portal, paramLI, snapshot);
Assert(portal->strategy == PORTAL_ONE_SELECT);
@ -1143,38 +1191,31 @@ spi_printtup(HeapTuple tuple, TupleDesc tupdesc, DestReceiver *self)
*/
/*
* Plan and optionally execute a querystring.
* Parse and plan a querystring.
*
* If plan != NULL, just prepare plan trees and save them in *plan;
* else execute immediately.
* At entry, plan->argtypes and plan->nargs must be valid.
*
* Query and plan lists are stored into *plan.
*/
static int
_SPI_execute(const char *src, int tcount, _SPI_plan *plan)
static void
_SPI_prepare_plan(const char *src, _SPI_plan *plan)
{
List *raw_parsetree_list;
List *query_list_list;
List *plan_list;
ListCell *list_item;
ErrorContextCallback spierrcontext;
int nargs = 0;
Oid *argtypes = NULL;
int res = 0;
Oid *argtypes = plan->argtypes;
int nargs = plan->nargs;
if (plan)
{
nargs = plan->nargs;
argtypes = plan->argtypes;
}
/* Increment CommandCounter to see changes made by now */
/*
* Increment CommandCounter to see changes made by now. We must do
* this to be sure of seeing any schema changes made by a just-preceding
* SPI command. (But we don't bother advancing the snapshot, since the
* planner generally operates under SnapshotNow rules anyway.)
*/
CommandCounterIncrement();
/* Reset state (only needed in case string is empty) */
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
_SPI_current->tuptable = NULL;
/*
* Setup error traceback support for ereport()
*/
@ -1191,9 +1232,9 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
/*
* Do parse analysis and rule rewrite for each raw parsetree.
*
* We save the querytrees from each raw parsetree as a separate sublist.
* This allows _SPI_execute_plan() to know where the boundaries
* between original queries fall.
* We save the querytrees from each raw parsetree as a separate
* sublist. This allows _SPI_execute_plan() to know where the
* boundaries between original queries fall.
*/
query_list_list = NIL;
plan_list = NIL;
@ -1202,203 +1243,221 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
{
Node *parsetree = (Node *) lfirst(list_item);
List *query_list;
ListCell *query_list_item;
query_list = pg_analyze_and_rewrite(parsetree, argtypes, nargs);
query_list_list = lappend(query_list_list, query_list);
/* Reset state for each original parsetree */
/* (at most one of its querytrees will be marked canSetTag) */
plan_list = list_concat(plan_list,
pg_plan_queries(query_list, NULL, false));
}
plan->qtlist = query_list_list;
plan->ptlist = plan_list;
/*
* Pop the error context stack
*/
error_context_stack = spierrcontext.previous;
}
/*
* Execute the given plan with the given parameter values
*
* snapshot: query snapshot to use, or InvalidSnapshot for the normal
* behavior of taking a new snapshot for each query.
* crosscheck_snapshot: for RI use, all others pass InvalidSnapshot
* read_only: TRUE for read-only execution (no CommandCounterIncrement)
* tcount: execution tuple-count limit, or 0 for none
*/
static int
_SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
Snapshot snapshot, Snapshot crosscheck_snapshot,
bool read_only, int tcount)
{
volatile int res = 0;
Snapshot saveActiveSnapshot;
/* Be sure to restore ActiveSnapshot on error exit */
saveActiveSnapshot = ActiveSnapshot;
PG_TRY();
{
List *query_list_list = plan->qtlist;
ListCell *plan_list_item = list_head(plan->ptlist);
ListCell *query_list_list_item;
ErrorContextCallback spierrcontext;
int nargs = plan->nargs;
ParamListInfo paramLI;
/* Convert parameters to form wanted by executor */
if (nargs > 0)
{
int k;
paramLI = (ParamListInfo)
palloc0((nargs + 1) * sizeof(ParamListInfoData));
for (k = 0; k < nargs; k++)
{
paramLI[k].kind = PARAM_NUM;
paramLI[k].id = k + 1;
paramLI[k].ptype = plan->argtypes[k];
paramLI[k].isnull = (Nulls && Nulls[k] == 'n');
paramLI[k].value = Values[k];
}
paramLI[k].kind = PARAM_INVALID;
}
else
paramLI = NULL;
/* Reset state (only needed in case string is empty) */
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
_SPI_current->tuptable = NULL;
foreach(query_list_item, query_list)
/*
* Setup error traceback support for ereport()
*/
spierrcontext.callback = _SPI_error_callback;
spierrcontext.arg = (void *) plan->query;
spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext;
foreach(query_list_list_item, query_list_list)
{
Query *queryTree = (Query *) lfirst(query_list_item);
Plan *planTree;
QueryDesc *qdesc;
DestReceiver *dest;
List *query_list = lfirst(query_list_list_item);
ListCell *query_list_item;
planTree = pg_plan_query(queryTree, NULL);
plan_list = lappend(plan_list, planTree);
/* Reset state for each original parsetree */
/* (at most one of its querytrees will be marked canSetTag) */
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
_SPI_current->tuptable = NULL;
dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None, NULL);
if (queryTree->commandType == CMD_UTILITY)
foreach(query_list_item, query_list)
{
if (IsA(queryTree->utilityStmt, CopyStmt))
{
CopyStmt *stmt = (CopyStmt *) queryTree->utilityStmt;
Query *queryTree = (Query *) lfirst(query_list_item);
Plan *planTree;
QueryDesc *qdesc;
DestReceiver *dest;
if (stmt->filename == NULL)
planTree = lfirst(plan_list_item);
plan_list_item = lnext(plan_list_item);
if (queryTree->commandType == CMD_UTILITY)
{
if (IsA(queryTree->utilityStmt, CopyStmt))
{
res = SPI_ERROR_COPY;
CopyStmt *stmt = (CopyStmt *) queryTree->utilityStmt;
if (stmt->filename == NULL)
{
res = SPI_ERROR_COPY;
goto fail;
}
}
else if (IsA(queryTree->utilityStmt, DeclareCursorStmt) ||
IsA(queryTree->utilityStmt, ClosePortalStmt) ||
IsA(queryTree->utilityStmt, FetchStmt))
{
res = SPI_ERROR_CURSOR;
goto fail;
}
else if (IsA(queryTree->utilityStmt, TransactionStmt))
{
res = SPI_ERROR_TRANSACTION;
goto fail;
}
}
else if (IsA(queryTree->utilityStmt, DeclareCursorStmt) ||
IsA(queryTree->utilityStmt, ClosePortalStmt) ||
IsA(queryTree->utilityStmt, FetchStmt))
{
res = SPI_ERROR_CURSOR;
goto fail;
}
else if (IsA(queryTree->utilityStmt, TransactionStmt))
{
res = SPI_ERROR_TRANSACTION;
goto fail;
}
res = SPI_OK_UTILITY;
if (plan == NULL)
{
ProcessUtility(queryTree->utilityStmt, NULL, dest, NULL);
if (read_only && !QueryIsReadOnly(queryTree))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s is a SQL statement name */
errmsg("%s is not allowed in a non-volatile function",
CreateQueryTag(queryTree))));
/*
* If not read-only mode, advance the command counter before
* each command.
*/
if (!read_only)
CommandCounterIncrement();
dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None,
NULL);
if (snapshot == InvalidSnapshot)
{
/*
* Default read_only behavior is to use the entry-time
* ActiveSnapshot; if read-write, grab a full new snap.
*/
if (read_only)
ActiveSnapshot = CopySnapshot(saveActiveSnapshot);
else
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
}
}
else if (plan == NULL)
{
qdesc = CreateQueryDesc(queryTree, planTree, dest,
NULL, false);
res = _SPI_pquery(qdesc, true, false,
queryTree->canSetTag ? tcount : 0);
if (res < 0)
goto fail;
CommandCounterIncrement();
}
else
{
qdesc = CreateQueryDesc(queryTree, planTree, dest,
NULL, false);
res = _SPI_pquery(qdesc, false, false, 0);
else
{
/*
* We interpret read_only with a specified snapshot to be
* exactly that snapshot, but read-write means use the
* snap with advancing of command ID.
*/
ActiveSnapshot = CopySnapshot(snapshot);
if (!read_only)
ActiveSnapshot->curcid = GetCurrentCommandId();
}
if (queryTree->commandType == CMD_UTILITY)
{
ProcessUtility(queryTree->utilityStmt, paramLI,
dest, NULL);
res = SPI_OK_UTILITY;
}
else
{
qdesc = CreateQueryDesc(queryTree, planTree,
ActiveSnapshot,
crosscheck_snapshot,
dest,
paramLI, false);
res = _SPI_pquery(qdesc,
queryTree->canSetTag ? tcount : 0);
FreeQueryDesc(qdesc);
}
FreeSnapshot(ActiveSnapshot);
ActiveSnapshot = NULL;
/* we know that the receiver doesn't need a destroy call */
if (res < 0)
goto fail;
}
}
}
if (plan)
{
plan->qtlist = query_list_list;
plan->ptlist = plan_list;
}
fail:
/*
* Pop the error context stack
*/
error_context_stack = spierrcontext.previous;
/*
* Pop the error context stack
*/
error_context_stack = spierrcontext.previous;
}
PG_CATCH();
{
/* Restore global vars and propagate error */
ActiveSnapshot = saveActiveSnapshot;
PG_RE_THROW();
}
PG_END_TRY();
ActiveSnapshot = saveActiveSnapshot;
return res;
}
static int
_SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
bool useCurrentSnapshot, int tcount)
{
List *query_list_list = plan->qtlist;
ListCell *plan_list_item = list_head(plan->ptlist);
ListCell *query_list_list_item;
ErrorContextCallback spierrcontext;
int nargs = plan->nargs;
int res = 0;
ParamListInfo paramLI;
/* Increment CommandCounter to see changes made by now */
CommandCounterIncrement();
/* Convert parameters to form wanted by executor */
if (nargs > 0)
{
int k;
paramLI = (ParamListInfo)
palloc0((nargs + 1) * sizeof(ParamListInfoData));
for (k = 0; k < nargs; k++)
{
paramLI[k].kind = PARAM_NUM;
paramLI[k].id = k + 1;
paramLI[k].ptype = plan->argtypes[k];
paramLI[k].isnull = (Nulls && Nulls[k] == 'n');
paramLI[k].value = Values[k];
}
paramLI[k].kind = PARAM_INVALID;
}
else
paramLI = NULL;
/* Reset state (only needed in case string is empty) */
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
_SPI_current->tuptable = NULL;
/*
* Setup error traceback support for ereport()
*/
spierrcontext.callback = _SPI_error_callback;
spierrcontext.arg = (void *) plan->query;
spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext;
foreach(query_list_list_item, query_list_list)
{
List *query_list = lfirst(query_list_list_item);
ListCell *query_list_item;
/* Reset state for each original parsetree */
/* (at most one of its querytrees will be marked canSetTag) */
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
_SPI_current->tuptable = NULL;
foreach(query_list_item, query_list)
{
Query *queryTree = (Query *) lfirst(query_list_item);
Plan *planTree;
QueryDesc *qdesc;
DestReceiver *dest;
planTree = lfirst(plan_list_item);
plan_list_item = lnext(plan_list_item);
dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None, NULL);
if (queryTree->commandType == CMD_UTILITY)
{
ProcessUtility(queryTree->utilityStmt, paramLI, dest, NULL);
res = SPI_OK_UTILITY;
CommandCounterIncrement();
}
else
{
qdesc = CreateQueryDesc(queryTree, planTree, dest,
paramLI, false);
res = _SPI_pquery(qdesc, true, useCurrentSnapshot,
queryTree->canSetTag ? tcount : 0);
if (res < 0)
goto fail;
CommandCounterIncrement();
}
}
}
fail:
/*
* Pop the error context stack
*/
error_context_stack = spierrcontext.previous;
return res;
}
static int
_SPI_pquery(QueryDesc *queryDesc, bool runit,
bool useCurrentSnapshot, int tcount)
_SPI_pquery(QueryDesc *queryDesc, int tcount)
{
int operation = queryDesc->operation;
int res;
@ -1427,9 +1486,6 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit,
return SPI_ERROR_OPUNKNOWN;
}
if (!runit) /* plan preparation, don't execute */
return res;
#ifdef SPI_EXECUTOR_STATS
if (ShowExecutorStats)
ResetUsage();
@ -1437,7 +1493,7 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit,
AfterTriggerBeginQuery();
ExecutorStart(queryDesc, useCurrentSnapshot, false);
ExecutorStart(queryDesc, false);
ExecutorRun(queryDesc, ForwardScanDirection, (long) tcount);
@ -1467,8 +1523,6 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit,
res = SPI_OK_UTILITY;
}
FreeQueryDesc(queryDesc);
#ifdef SPI_EXECUTOR_STATS
if (ShowExecutorStats)
ShowUsage("SPI EXECUTOR STATS");

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/fastpath.c,v 1.75 2004/08/29 05:06:49 momjian Exp $
* $PostgreSQL: pgsql/src/backend/tcop/fastpath.c,v 1.76 2004/09/13 20:07:05 tgl Exp $
*
* NOTES
* This cruft is the server side of PQfn.
@ -333,11 +333,6 @@ HandleFunctionRequest(StringInfo msgBuf)
aclcheck_error(aclresult, ACL_KIND_PROC,
get_func_name(fid));
/*
* Set up a query snapshot in case function needs one.
*/
SetQuerySnapshot();
/*
* Prepare function call info block and insert arguments.
*/

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.431 2004/09/10 18:39:59 tgl Exp $
* $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.432 2004/09/13 20:07:05 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
@ -700,7 +700,7 @@ pg_plan_queries(List *querytrees, ParamListInfo boundParams,
{
if (needSnapshot)
{
SetQuerySnapshot();
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
needSnapshot = false;
}
plan = pg_plan_query(query, boundParams);
@ -883,7 +883,7 @@ exec_simple_query(const char *query_string)
/*
* Start the portal. No parameters here.
*/
PortalStart(portal, NULL);
PortalStart(portal, NULL, InvalidSnapshot);
/*
* Select the appropriate output format: text unless we are doing
@ -1539,7 +1539,7 @@ exec_bind_message(StringInfo input_message)
pstmt->plan_list,
pstmt->context);
PortalStart(portal, params);
PortalStart(portal, params, InvalidSnapshot);
/*
* Apply the result format requests to the portal.
@ -3027,6 +3027,9 @@ PostgresMain(int argc, char *argv[], const char *username)
/* switch back to message context */
MemoryContextSwitchTo(MessageContext);
/* set snapshot in case function needs one */
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
if (HandleFunctionRequest(&input_message) == EOF)
{
/* lost frontend connection during F message input */

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.86 2004/09/10 18:40:00 tgl Exp $
* $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.87 2004/09/13 20:07:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -32,6 +32,11 @@
Portal ActivePortal = NULL;
static void ProcessQuery(Query *parsetree,
Plan *plan,
ParamListInfo params,
DestReceiver *dest,
char *completionTag);
static uint32 RunFromStore(Portal portal, ScanDirection direction, long count,
DestReceiver *dest);
static long PortalRunSelect(Portal portal, bool forward, long count,
@ -54,6 +59,8 @@ static void DoPortalRewind(Portal portal);
QueryDesc *
CreateQueryDesc(Query *parsetree,
Plan *plantree,
Snapshot snapshot,
Snapshot crosscheck_snapshot,
DestReceiver *dest,
ParamListInfo params,
bool doInstrument)
@ -63,6 +70,8 @@ CreateQueryDesc(Query *parsetree,
qd->operation = parsetree->commandType; /* operation */
qd->parsetree = parsetree; /* parse tree */
qd->plantree = plantree; /* plan */
qd->snapshot = snapshot; /* snapshot */
qd->crosscheck_snapshot = crosscheck_snapshot; /* RI check snapshot */
qd->dest = dest; /* output dest */
qd->params = params; /* parameter values passed into query */
qd->doInstrument = doInstrument; /* instrumentation wanted? */
@ -90,7 +99,7 @@ FreeQueryDesc(QueryDesc *qdesc)
/*
* ProcessQuery
* Execute a single query
* Execute a single plannable query within a PORTAL_MULTI_QUERY portal
*
* parsetree: the query tree
* plan: the plan tree for the query
@ -104,7 +113,7 @@ FreeQueryDesc(QueryDesc *qdesc)
* Must be called in a memory context that will be reset or deleted on
* error; otherwise the executor's memory usage will be leaked.
*/
void
static void
ProcessQuery(Query *parsetree,
Plan *plan,
ParamListInfo params,
@ -114,6 +123,9 @@ ProcessQuery(Query *parsetree,
int operation = parsetree->commandType;
QueryDesc *queryDesc;
ereport(DEBUG3,
(errmsg_internal("ProcessQuery")));
/*
* Check for special-case destinations
*/
@ -132,10 +144,18 @@ ProcessQuery(Query *parsetree,
}
}
/*
* Must always set snapshot for plannable queries. Note we assume
* that caller will take care of restoring ActiveSnapshot on exit/error.
*/
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
/*
* Create the QueryDesc object
*/
queryDesc = CreateQueryDesc(parsetree, plan, dest, params, false);
queryDesc = CreateQueryDesc(parsetree, plan,
ActiveSnapshot, InvalidSnapshot,
dest, params, false);
/*
* Set up to collect AFTER triggers
@ -145,7 +165,7 @@ ProcessQuery(Query *parsetree,
/*
* Call ExecStart to prepare the plan for execution
*/
ExecutorStart(queryDesc, false, false);
ExecutorStart(queryDesc, false);
/*
* Run the plan to completion.
@ -195,6 +215,9 @@ ProcessQuery(Query *parsetree,
AfterTriggerEndQuery();
FreeQueryDesc(queryDesc);
FreeSnapshot(ActiveSnapshot);
ActiveSnapshot = NULL;
}
/*
@ -238,13 +261,19 @@ ChoosePortalStrategy(List *parseTrees)
* the query, they must be passed in here (caller is responsible for
* giving them appropriate lifetime).
*
* The caller can optionally pass a snapshot to be used; pass InvalidSnapshot
* for the normal behavior of setting a new snapshot. This parameter is
* presently ignored for non-PORTAL_ONE_SELECT portals (it's only intended
* to be used for cursors).
*
* On return, portal is ready to accept PortalRun() calls, and the result
* tupdesc (if any) is known.
*/
void
PortalStart(Portal portal, ParamListInfo params)
PortalStart(Portal portal, ParamListInfo params, Snapshot snapshot)
{
Portal saveActivePortal;
Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext oldContext;
@ -259,11 +288,13 @@ PortalStart(Portal portal, ParamListInfo params)
* QueryContext?)
*/
saveActivePortal = ActivePortal;
saveActiveSnapshot = ActiveSnapshot;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
PG_TRY();
{
ActivePortal = portal;
ActiveSnapshot = NULL; /* will be set later */
CurrentResourceOwner = portal->resowner;
PortalContext = PortalGetHeapMemory(portal);
@ -285,9 +316,13 @@ PortalStart(Portal portal, ParamListInfo params)
case PORTAL_ONE_SELECT:
/*
* Must set query snapshot before starting executor.
* Must set snapshot before starting executor. Be sure to
* copy it into the portal's context.
*/
SetQuerySnapshot();
if (snapshot)
ActiveSnapshot = CopySnapshot(snapshot);
else
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
/*
* Create QueryDesc in portal's context; for the moment,
@ -295,6 +330,8 @@ PortalStart(Portal portal, ParamListInfo params)
*/
queryDesc = CreateQueryDesc((Query *) linitial(portal->parseTrees),
(Plan *) linitial(portal->planTrees),
ActiveSnapshot,
InvalidSnapshot,
None_Receiver,
params,
false);
@ -309,7 +346,7 @@ PortalStart(Portal portal, ParamListInfo params)
/*
* Call ExecStart to prepare the plan for execution
*/
ExecutorStart(queryDesc, false, false);
ExecutorStart(queryDesc, false);
/*
* This tells PortalCleanup to shut down the executor
@ -333,8 +370,8 @@ PortalStart(Portal portal, ParamListInfo params)
case PORTAL_UTIL_SELECT:
/*
* We don't set query snapshot here, because
* PortalRunUtility will take care of it.
* We don't set snapshot here, because
* PortalRunUtility will take care of it if needed.
*/
portal->tupDesc =
UtilityTupleDescriptor(((Query *) linitial(portal->parseTrees))->utilityStmt);
@ -361,6 +398,7 @@ PortalStart(Portal portal, ParamListInfo params)
/* Restore global vars and propagate error */
ActivePortal = saveActivePortal;
ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
@ -371,6 +409,7 @@ PortalStart(Portal portal, ParamListInfo params)
MemoryContextSwitchTo(oldContext);
ActivePortal = saveActivePortal;
ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
@ -453,6 +492,7 @@ PortalRun(Portal portal, long count,
{
bool result;
Portal saveActivePortal;
Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext saveQueryContext;
@ -485,12 +525,14 @@ PortalRun(Portal portal, long count,
* Set up global portal context pointers.
*/
saveActivePortal = ActivePortal;
saveActiveSnapshot = ActiveSnapshot;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
saveQueryContext = QueryContext;
PG_TRY();
{
ActivePortal = portal;
ActiveSnapshot = NULL; /* will be set later */
CurrentResourceOwner = portal->resowner;
PortalContext = PortalGetHeapMemory(portal);
QueryContext = portal->queryContext;
@ -579,6 +621,7 @@ PortalRun(Portal portal, long count,
/* Restore global vars and propagate error */
ActivePortal = saveActivePortal;
ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
@ -590,6 +633,7 @@ PortalRun(Portal portal, long count,
MemoryContextSwitchTo(oldContext);
ActivePortal = saveActivePortal;
ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
@ -670,6 +714,7 @@ PortalRunSelect(Portal portal,
nprocessed = RunFromStore(portal, direction, count, dest);
else
{
ActiveSnapshot = queryDesc->snapshot;
ExecutorRun(queryDesc, direction, count);
nprocessed = queryDesc->estate->es_processed;
}
@ -711,6 +756,7 @@ PortalRunSelect(Portal portal,
nprocessed = RunFromStore(portal, direction, count, dest);
else
{
ActiveSnapshot = queryDesc->snapshot;
ExecutorRun(queryDesc, direction, count);
nprocessed = queryDesc->estate->es_processed;
}
@ -834,6 +880,9 @@ PortalRunUtility(Portal portal, Query *query,
* the database --- if, say, it has to update an index with
* expressions that invoke user-defined functions, then it had better
* have a snapshot.
*
* Note we assume that caller will take care of restoring ActiveSnapshot
* on exit/error.
*/
if (!(IsA(utilityStmt, TransactionStmt) ||
IsA(utilityStmt, LockStmt) ||
@ -847,7 +896,9 @@ PortalRunUtility(Portal portal, Query *query,
IsA(utilityStmt, NotifyStmt) ||
IsA(utilityStmt, UnlistenStmt) ||
IsA(utilityStmt, CheckPointStmt)))
SetQuerySnapshot();
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
else
ActiveSnapshot = NULL;
if (query->canSetTag)
{
@ -864,6 +915,10 @@ PortalRunUtility(Portal portal, Query *query,
/* Some utility statements may change context on us */
MemoryContextSwitchTo(PortalGetHeapMemory(portal));
if (ActiveSnapshot)
FreeSnapshot(ActiveSnapshot);
ActiveSnapshot = NULL;
}
/*
@ -924,15 +979,6 @@ PortalRunMulti(Portal portal,
/*
* process a plannable query.
*/
ereport(DEBUG3,
(errmsg_internal("ProcessQuery")));
/* Must always set snapshot for plannable queries */
SetQuerySnapshot();
/*
* execute the plan
*/
if (log_executor_stats)
ResetUsage();
@ -1005,6 +1051,7 @@ PortalRunFetch(Portal portal,
{
long result;
Portal saveActivePortal;
Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext saveQueryContext;
@ -1025,12 +1072,14 @@ PortalRunFetch(Portal portal,
* Set up global portal context pointers.
*/
saveActivePortal = ActivePortal;
saveActiveSnapshot = ActiveSnapshot;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
saveQueryContext = QueryContext;
PG_TRY();
{
ActivePortal = portal;
ActiveSnapshot = NULL; /* will be set later */
CurrentResourceOwner = portal->resowner;
PortalContext = PortalGetHeapMemory(portal);
QueryContext = portal->queryContext;
@ -1056,6 +1105,7 @@ PortalRunFetch(Portal portal,
/* Restore global vars and propagate error */
ActivePortal = saveActivePortal;
ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
@ -1070,6 +1120,7 @@ PortalRunFetch(Portal portal,
portal->status = PORTAL_READY;
ActivePortal = saveActivePortal;
ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;

View File

@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.229 2004/09/10 18:40:00 tgl Exp $
* $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.230 2004/09/13 20:07:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -222,6 +222,46 @@ CheckRelationOwnership(RangeVar *rel, bool noCatalogs)
}
/*
* QueryIsReadOnly: is an analyzed/rewritten query read-only?
*
* This is a much stricter test than we apply for XactReadOnly mode;
* the query must be *in truth* read-only, because the caller wishes
* not to do CommandCounterIncrement for it.
*/
bool
QueryIsReadOnly(Query *parsetree)
{
switch (parsetree->commandType)
{
case CMD_SELECT:
if (parsetree->into != NULL)
return false; /* SELECT INTO */
else if (parsetree->rowMarks != NIL)
return false; /* SELECT FOR UPDATE */
else
return true;
case CMD_UPDATE:
case CMD_INSERT:
case CMD_DELETE:
return false;
case CMD_UTILITY:
/* For now, treat all utility commands as read/write */
return false;
default:
elog(WARNING, "unrecognized commandType: %d",
(int) parsetree->commandType);
break;
}
return false;
}
/*
* check_xact_readonly: is a utility command read-only?
*
* Here we use the loose rules of XactReadOnly mode: no permanent effects
* on the database are allowed.
*/
static void
check_xact_readonly(Node *parsetree)
{
@ -299,8 +339,7 @@ check_xact_readonly(Node *parsetree)
* completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
* in which to store a command completion status string.
*
* completionTag is only set nonempty if we want to return a nondefault
* status (currently, only used for MOVE/FETCH).
* completionTag is only set nonempty if we want to return a nondefault status.
*
* completionTag may be NULL if caller doesn't want a status string.
*/
@ -1586,3 +1625,51 @@ CreateCommandTag(Node *parsetree)
return tag;
}
/*
* CreateQueryTag
* utility to get a string representation of a Query operation.
*
* This is exactly like CreateCommandTag, except it works on a Query
* that has already been through parse analysis (and possibly further).
*/
const char *
CreateQueryTag(Query *parsetree)
{
const char *tag;
switch (parsetree->commandType)
{
case CMD_SELECT:
/*
* We take a little extra care here so that the result will
* be useful for complaints about read-only statements
*/
if (parsetree->into != NULL)
tag = "SELECT INTO";
else if (parsetree->rowMarks != NIL)
tag = "SELECT FOR UPDATE";
else
tag = "SELECT";
break;
case CMD_UPDATE:
tag = "UPDATE";
break;
case CMD_INSERT:
tag = "INSERT";
break;
case CMD_DELETE:
tag = "DELETE";
break;
case CMD_UTILITY:
tag = CreateCommandTag(parsetree->utilityStmt);
break;
default:
elog(WARNING, "unrecognized commandType: %d",
(int) parsetree->commandType);
tag = "???";
break;
}
return tag;
}

View File

@ -17,7 +17,7 @@
*
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.72 2004/09/10 18:40:04 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.73 2004/09/13 20:07:13 tgl Exp $
*
* ----------
*/
@ -2698,16 +2698,20 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel)
elog(ERROR, "SPI_prepare returned %d for %s", SPI_result, querystr);
/*
* Run the plan. For safety we force a current query snapshot to be
* used. (In serializable mode, this arguably violates
* serializability, but we really haven't got much choice.) We need
* at most one tuple returned, so pass limit = 1.
* Run the plan. For safety we force a current snapshot to be used.
* (In serializable mode, this arguably violates serializability, but we
* really haven't got much choice.) We need at most one tuple returned,
* so pass limit = 1.
*/
spi_result = SPI_execp_current(qplan, NULL, NULL, true, 1);
spi_result = SPI_execute_snapshot(qplan,
NULL, NULL,
CopySnapshot(GetLatestSnapshot()),
InvalidSnapshot,
true, 1);
/* Check result */
if (spi_result != SPI_OK_SELECT)
elog(ERROR, "SPI_execp_current returned %d", spi_result);
elog(ERROR, "SPI_execute_snapshot returned %d", spi_result);
/* Did we find a tuple violating the constraint? */
if (SPI_processed > 0)
@ -3043,7 +3047,8 @@ ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
Relation query_rel,
source_rel;
int key_idx;
bool useCurrentSnapshot;
Snapshot test_snapshot;
Snapshot crosscheck_snapshot;
int limit;
int spi_result;
AclId save_uid;
@ -3094,21 +3099,26 @@ ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
}
/*
* In READ COMMITTED mode, we just need to make sure the regular query
* snapshot is up-to-date, and we will see all rows that could be
* interesting. In SERIALIZABLE mode, we can't update the regular
* query snapshot. If the caller passes detectNewRows == false then
* it's okay to do the query with the transaction snapshot; otherwise
* we tell the executor to force a current snapshot (and error out if
* it finds any rows under current snapshot that wouldn't be visible
* per the transaction snapshot).
* In READ COMMITTED mode, we just need to use an up-to-date regular
* snapshot, and we will see all rows that could be interesting.
* But in SERIALIZABLE mode, we can't change the transaction snapshot.
* If the caller passes detectNewRows == false then it's okay to do the
* query with the transaction snapshot; otherwise we use a current
* snapshot, and tell the executor to error out if it finds any rows under
* the current snapshot that wouldn't be visible per the transaction
* snapshot.
*/
if (IsXactIsoLevelSerializable)
useCurrentSnapshot = detectNewRows;
if (IsXactIsoLevelSerializable && detectNewRows)
{
CommandCounterIncrement(); /* be sure all my own work is visible */
test_snapshot = CopySnapshot(GetLatestSnapshot());
crosscheck_snapshot = CopySnapshot(GetTransactionSnapshot());
}
else
{
SetQuerySnapshot();
useCurrentSnapshot = false;
/* the default SPI behavior is okay */
test_snapshot = InvalidSnapshot;
crosscheck_snapshot = InvalidSnapshot;
}
/*
@ -3124,15 +3134,17 @@ ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
SetUserId(RelationGetForm(query_rel)->relowner);
/* Finally we can run the query. */
spi_result = SPI_execp_current(qplan, vals, nulls,
useCurrentSnapshot, limit);
spi_result = SPI_execute_snapshot(qplan,
vals, nulls,
test_snapshot, crosscheck_snapshot,
false, limit);
/* Restore UID */
SetUserId(save_uid);
/* Check result */
if (spi_result < 0)
elog(ERROR, "SPI_execp_current returned %d", spi_result);
elog(ERROR, "SPI_execute_snapshot returned %d", spi_result);
if (expect_OK >= 0 && spi_result != expect_OK)
ri_ReportViolation(qkey, constrname ? constrname : "",

View File

@ -3,7 +3,7 @@
* back to source text
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.180 2004/09/01 23:58:38 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.181 2004/09/13 20:07:13 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -290,7 +290,7 @@ pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
*/
args[0] = ObjectIdGetDatum(ruleoid);
nulls[0] = ' ';
spirc = SPI_execp(plan_getrulebyoid, args, nulls, 1);
spirc = SPI_execute_plan(plan_getrulebyoid, args, nulls, true, 1);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for rule %u", ruleoid);
if (SPI_processed != 1)
@ -425,7 +425,7 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags)
args[1] = PointerGetDatum(ViewSelectRuleName);
nulls[0] = ' ';
nulls[1] = ' ';
spirc = SPI_execp(plan_getviewrule, args, nulls, 2);
spirc = SPI_execute_plan(plan_getviewrule, args, nulls, true, 2);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for view %u", viewoid);
if (SPI_processed != 1)

View File

@ -16,7 +16,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/time/tqual.c,v 1.77 2004/08/29 05:06:52 momjian Exp $
* $PostgreSQL: pgsql/src/backend/utils/time/tqual.c,v 1.78 2004/09/13 20:07:36 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -28,18 +28,24 @@
#include "utils/tqual.h"
/*
* The SnapshotData structs are static to simplify memory allocation
* These SnapshotData structs are static to simplify memory allocation
* (see the hack in GetSnapshotData to avoid repeated malloc/free).
*/
static SnapshotData QuerySnapshotData;
static SnapshotData SerializableSnapshotData;
static SnapshotData CurrentSnapshotData;
static SnapshotData SnapshotDirtyData;
static SnapshotData SerializableSnapshotData;
static SnapshotData LatestSnapshotData;
/* Externally visible pointers to valid snapshots: */
Snapshot QuerySnapshot = NULL;
Snapshot SerializableSnapshot = NULL;
Snapshot SnapshotDirty = &SnapshotDirtyData;
Snapshot SerializableSnapshot = NULL;
Snapshot LatestSnapshot = NULL;
/*
* This pointer is not maintained by this module, but it's convenient
* to declare it here anyway. Callers typically assign a copy of
* GetTransactionSnapshot's result to ActiveSnapshot.
*/
Snapshot ActiveSnapshot = NULL;
/* These are updated by GetSnapshotData: */
TransactionId RecentXmin = InvalidTransactionId;
@ -1028,101 +1034,94 @@ HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin)
/*
* SetQuerySnapshot
* Initialize query snapshot for a new query
* GetTransactionSnapshot
* Get the appropriate snapshot for a new query in a transaction.
*
* The SerializableSnapshot is the first one taken in a transaction.
* In serializable mode we just use that one throughout the transaction.
* In read-committed mode, we take a new snapshot at the start of each query.
* In read-committed mode, we take a new snapshot each time we are called.
*
* Note that the return value points at static storage that will be modified
* by future calls and by CommandCounterIncrement(). Callers should copy
* the result with CopySnapshot() if it is to be used very long.
*/
void
SetQuerySnapshot(void)
Snapshot
GetTransactionSnapshot(void)
{
/* 1st call in xaction? */
/* First call in transaction? */
if (SerializableSnapshot == NULL)
{
SerializableSnapshot = GetSnapshotData(&SerializableSnapshotData, true);
QuerySnapshot = SerializableSnapshot;
Assert(QuerySnapshot != NULL);
return;
return SerializableSnapshot;
}
if (IsXactIsoLevelSerializable)
QuerySnapshot = SerializableSnapshot;
else
QuerySnapshot = GetSnapshotData(&QuerySnapshotData, false);
return SerializableSnapshot;
Assert(QuerySnapshot != NULL);
LatestSnapshot = GetSnapshotData(&LatestSnapshotData, false);
return LatestSnapshot;
}
/*
* CopyQuerySnapshot
* Copy the current query snapshot.
*
* Copying the snapshot is done so that a query is guaranteed to use a
* consistent snapshot for its entire execution life, even if the command
* counter is incremented or SetQuerySnapshot() is called while it runs
* (as could easily happen, due to triggers etc. executing queries).
*
* The copy is palloc'd in the current memory context.
* GetLatestSnapshot
* Get a snapshot that is up-to-date as of the current instant,
* even if we are executing in SERIALIZABLE mode.
*/
Snapshot
CopyQuerySnapshot(void)
GetLatestSnapshot(void)
{
Snapshot snapshot;
if (QuerySnapshot == NULL) /* should be set beforehand */
/* Should not be first call in transaction */
if (SerializableSnapshot == NULL)
elog(ERROR, "no snapshot has been set");
snapshot = (Snapshot) palloc(sizeof(SnapshotData));
memcpy(snapshot, QuerySnapshot, sizeof(SnapshotData));
if (snapshot->xcnt > 0)
{
snapshot->xip = (TransactionId *)
palloc(snapshot->xcnt * sizeof(TransactionId));
memcpy(snapshot->xip, QuerySnapshot->xip,
snapshot->xcnt * sizeof(TransactionId));
}
else
snapshot->xip = NULL;
LatestSnapshot = GetSnapshotData(&LatestSnapshotData, false);
return snapshot;
return LatestSnapshot;
}
/*
* CopyCurrentSnapshot
* Make a snapshot that is up-to-date as of the current instant,
* and return a copy.
* CopySnapshot
* Copy the given snapshot.
*
* The copy is palloc'd in the current memory context.
*
* Note that this will not work on "special" snapshots.
*/
Snapshot
CopyCurrentSnapshot(void)
CopySnapshot(Snapshot snapshot)
{
Snapshot currentSnapshot;
Snapshot snapshot;
Snapshot newsnap;
if (QuerySnapshot == NULL) /* should not be first call in xact */
elog(ERROR, "no snapshot has been set");
/* Update the static struct */
currentSnapshot = GetSnapshotData(&CurrentSnapshotData, false);
currentSnapshot->curcid = GetCurrentCommandId();
/* Make a copy */
snapshot = (Snapshot) palloc(sizeof(SnapshotData));
memcpy(snapshot, currentSnapshot, sizeof(SnapshotData));
/* We allocate any XID array needed in the same palloc block. */
newsnap = (Snapshot) palloc(sizeof(SnapshotData) +
snapshot->xcnt * sizeof(TransactionId));
memcpy(newsnap, snapshot, sizeof(SnapshotData));
if (snapshot->xcnt > 0)
{
snapshot->xip = (TransactionId *)
palloc(snapshot->xcnt * sizeof(TransactionId));
memcpy(snapshot->xip, currentSnapshot->xip,
newsnap->xip = (TransactionId *) (newsnap + 1);
memcpy(newsnap->xip, snapshot->xip,
snapshot->xcnt * sizeof(TransactionId));
}
else
snapshot->xip = NULL;
newsnap->xip = NULL;
return snapshot;
return newsnap;
}
/*
* FreeSnapshot
* Free a snapshot previously copied with CopySnapshot.
*
* This is currently identical to pfree, but is provided for cleanliness.
*
* Do *not* apply this to the results of GetTransactionSnapshot or
* GetLatestSnapshot.
*/
void
FreeSnapshot(Snapshot snapshot)
{
pfree(snapshot);
}
/*
@ -1133,10 +1132,11 @@ void
FreeXactSnapshot(void)
{
/*
* We do not free the xip arrays for the snapshot structs; they will
* be reused soon. So this is now just a state change to prevent
* We do not free the xip arrays for the static snapshot structs; they
* will be reused soon. So this is now just a state change to prevent
* outside callers from accessing the snapshots.
*/
QuerySnapshot = NULL;
SerializableSnapshot = NULL;
LatestSnapshot = NULL;
ActiveSnapshot = NULL; /* just for cleanliness */
}

View File

@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/executor/execdesc.h,v 1.28 2004/08/29 04:13:06 momjian Exp $
* $PostgreSQL: pgsql/src/include/executor/execdesc.h,v 1.29 2004/09/13 20:07:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -33,6 +33,8 @@ typedef struct QueryDesc
CmdType operation; /* CMD_SELECT, CMD_UPDATE, etc. */
Query *parsetree; /* rewritten parsetree */
Plan *plantree; /* planner's output */
Snapshot snapshot; /* snapshot to use for query */
Snapshot crosscheck_snapshot; /* crosscheck for RI update/delete */
DestReceiver *dest; /* the destination for tuple output */
ParamListInfo params; /* param values being passed in */
bool doInstrument; /* TRUE requests runtime instrumentation */
@ -45,6 +47,8 @@ typedef struct QueryDesc
/* in pquery.c */
extern QueryDesc *CreateQueryDesc(Query *parsetree, Plan *plantree,
Snapshot snapshot,
Snapshot crosscheck_snapshot,
DestReceiver *dest,
ParamListInfo params,
bool doInstrument);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.112 2004/08/29 05:06:56 momjian Exp $
* $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.113 2004/09/13 20:07:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -95,8 +95,7 @@ extern HeapTuple ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot);
/*
* prototypes from functions in execMain.c
*/
extern void ExecutorStart(QueryDesc *queryDesc, bool useCurrentSnapshot,
bool explainOnly);
extern void ExecutorStart(QueryDesc *queryDesc, bool explainOnly);
extern TupleTableSlot *ExecutorRun(QueryDesc *queryDesc,
ScanDirection direction, long count);
extern void ExecutorEnd(QueryDesc *queryDesc);

View File

@ -2,7 +2,7 @@
*
* spi.h
*
* $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.47 2004/08/29 05:06:56 momjian Exp $
* $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.48 2004/09/13 20:07:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -81,11 +81,17 @@ extern int SPI_connect(void);
extern int SPI_finish(void);
extern void SPI_push(void);
extern void SPI_pop(void);
extern int SPI_execute(const char *src, bool read_only, int tcount);
extern int SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
bool read_only, int tcount);
extern int SPI_exec(const char *src, int tcount);
extern int SPI_execp(void *plan, Datum *values, const char *Nulls,
int tcount);
extern int SPI_execp_current(void *plan, Datum *values, const char *Nulls,
bool useCurrentSnapshot, int tcount);
extern int SPI_execp(void *plan, Datum *Values, const char *Nulls,
int tcount);
extern int SPI_execute_snapshot(void *plan,
Datum *Values, const char *Nulls,
Snapshot snapshot,
Snapshot crosscheck_snapshot,
bool read_only, int tcount);
extern void *SPI_prepare(const char *src, int nargs, Oid *argtypes);
extern void *SPI_saveplan(void *plan);
extern int SPI_freeplan(void *plan);
@ -113,7 +119,7 @@ extern void SPI_freetuple(HeapTuple pointer);
extern void SPI_freetuptable(SPITupleTable *tuptable);
extern Portal SPI_cursor_open(const char *name, void *plan,
Datum *Values, const char *Nulls);
Datum *Values, const char *Nulls, bool read_only);
extern Portal SPI_cursor_find(const char *name);
extern void SPI_cursor_fetch(Portal portal, bool forward, int count);
extern void SPI_cursor_move(Portal portal, bool forward, int count);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/tcop/pquery.h,v 1.32 2004/08/29 04:13:10 momjian Exp $
* $PostgreSQL: pgsql/src/include/tcop/pquery.h,v 1.33 2004/09/13 20:08:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -20,15 +20,10 @@
extern DLLIMPORT Portal ActivePortal;
extern void ProcessQuery(Query *parsetree,
Plan *plan,
ParamListInfo params,
DestReceiver *dest,
char *completionTag);
extern PortalStrategy ChoosePortalStrategy(List *parseTrees);
extern void PortalStart(Portal portal, ParamListInfo params);
extern void PortalStart(Portal portal, ParamListInfo params,
Snapshot snapshot);
extern void PortalSetResultFormat(Portal portal, int nFormats,
int16 *formats);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/tcop/utility.h,v 1.24 2004/08/29 05:06:58 momjian Exp $
* $PostgreSQL: pgsql/src/include/tcop/utility.h,v 1.25 2004/09/13 20:08:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -26,6 +26,10 @@ extern TupleDesc UtilityTupleDescriptor(Node *parsetree);
extern const char *CreateCommandTag(Node *parsetree);
extern const char *CreateQueryTag(Query *parsetree);
extern bool QueryIsReadOnly(Query *parsetree);
extern void CheckRelationOwnership(RangeVar *rel, bool noCatalogs);
#endif /* UTILITY_H */

View File

@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/tqual.h,v 1.51 2004/09/11 18:28:34 tgl Exp $
* $PostgreSQL: pgsql/src/include/utils/tqual.h,v 1.52 2004/09/13 20:08:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -54,8 +54,10 @@ typedef SnapshotData *Snapshot;
#define SnapshotToast ((Snapshot) 0x4)
extern DLLIMPORT Snapshot SnapshotDirty;
extern DLLIMPORT Snapshot QuerySnapshot;
extern DLLIMPORT Snapshot SerializableSnapshot;
extern DLLIMPORT Snapshot LatestSnapshot;
extern DLLIMPORT Snapshot ActiveSnapshot;
extern TransactionId RecentXmin;
extern TransactionId RecentGlobalXmin;
@ -121,10 +123,13 @@ extern int HeapTupleSatisfiesUpdate(HeapTupleHeader tuple,
extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTupleHeader tuple,
TransactionId OldestXmin);
extern Snapshot GetSnapshotData(Snapshot snapshot, bool serializable);
extern void SetQuerySnapshot(void);
extern Snapshot CopyQuerySnapshot(void);
extern Snapshot CopyCurrentSnapshot(void);
extern Snapshot GetTransactionSnapshot(void);
extern Snapshot GetLatestSnapshot(void);
extern Snapshot CopySnapshot(Snapshot snapshot);
extern void FreeSnapshot(Snapshot snapshot);
extern void FreeXactSnapshot(void);
/* in sinval.c; declared here to avoid including tqual.h in sinval.h: */
extern Snapshot GetSnapshotData(Snapshot snapshot, bool serializable);
#endif /* TQUAL_H */

View File

@ -33,7 +33,7 @@
* ENHANCEMENTS, OR MODIFICATIONS.
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.50 2004/08/30 02:54:41 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.51 2004/09/13 20:08:59 tgl Exp $
*
**********************************************************************/
@ -53,6 +53,7 @@
#include "executor/spi.h"
#include "fmgr.h"
#include "tcop/tcopprot.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@ -77,6 +78,7 @@ typedef struct plperl_proc_desc
char *proname;
TransactionId fn_xmin;
CommandId fn_cmin;
bool fn_readonly;
bool lanpltrusted;
bool fn_retistuple; /* true, if function returns tuple */
bool fn_retisset; /* true, if function returns set */
@ -98,11 +100,13 @@ static int plperl_firstcall = 1;
static bool plperl_safe_init_done = false;
static PerlInterpreter *plperl_interp = NULL;
static HV *plperl_proc_hash = NULL;
static AV *g_row_keys = NULL;
static AV *g_column_keys = NULL;
static SV *srf_perlret = NULL; /* keep returned value */
static int g_attr_num = 0;
/* this is saved and restored by plperl_call_handler */
static plperl_proc_desc *plperl_current_prodesc = NULL;
/**********************************************************************
* Forward declarations
**********************************************************************/
@ -119,6 +123,7 @@ static plperl_proc_desc *compile_plperl_function(Oid fn_oid, bool is_trigger);
static SV *plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc);
static void plperl_init_shared_libs(pTHX);
static HV *plperl_spi_execute_fetch_result(SPITupleTable *, int, int);
/*
@ -435,7 +440,6 @@ static AV *
plperl_get_keys(HV *hv)
{
AV *ret;
SV **svp;
int key_count;
SV *val;
char *key;
@ -445,7 +449,7 @@ plperl_get_keys(HV *hv)
ret = newAV();
hv_iterinit(hv);
while (val = hv_iternextsv(hv, (char **) &key, &klen))
while ((val = hv_iternextsv(hv, (char **) &key, &klen)))
{
av_store(ret, key_count, eval_pv(key, TRUE));
key_count++;
@ -592,26 +596,43 @@ Datum
plperl_call_handler(PG_FUNCTION_ARGS)
{
Datum retval;
plperl_proc_desc *save_prodesc;
/************************************************************
* Initialize interpreter
************************************************************/
/*
* Initialize interpreter if first time through
*/
plperl_init_all();
/************************************************************
* Connect to SPI manager
************************************************************/
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "could not connect to SPI manager");
/*
* Ensure that static pointers are saved/restored properly
*/
save_prodesc = plperl_current_prodesc;
/************************************************************
* Determine if called as function or trigger and
* call appropriate subhandler
************************************************************/
if (CALLED_AS_TRIGGER(fcinfo))
retval = PointerGetDatum(plperl_trigger_handler(fcinfo));
else
retval = plperl_func_handler(fcinfo);
PG_TRY();
{
/************************************************************
* Connect to SPI manager
************************************************************/
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "could not connect to SPI manager");
/************************************************************
* Determine if called as function or trigger and
* call appropriate subhandler
************************************************************/
if (CALLED_AS_TRIGGER(fcinfo))
retval = PointerGetDatum(plperl_trigger_handler(fcinfo));
else
retval = plperl_func_handler(fcinfo);
}
PG_CATCH();
{
plperl_current_prodesc = save_prodesc;
PG_RE_THROW();
}
PG_END_TRY();
plperl_current_prodesc = save_prodesc;
return retval;
}
@ -821,7 +842,6 @@ plperl_call_perl_trigger_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo, S
SV *retval;
int i;
int count;
char *ret_test;
ENTER;
SAVETMPS;
@ -874,6 +894,9 @@ plperl_func_handler(PG_FUNCTION_ARGS)
/* Find or compile the function */
prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false);
plperl_current_prodesc = prodesc;
/************************************************************
* Call the Perl function if not returning set
************************************************************/
@ -1002,7 +1025,6 @@ plperl_func_handler(PG_FUNCTION_ARGS)
{
HV *row_hv;
SV **svp;
char *row_key;
svp = av_fetch(ret_av, call_cntr, FALSE);
@ -1052,7 +1074,6 @@ plperl_func_handler(PG_FUNCTION_ARGS)
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcontext;
int i;
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
@ -1067,7 +1088,6 @@ plperl_func_handler(PG_FUNCTION_ARGS)
Datum result;
AV *array;
SV **svp;
int i;
array = (AV *) SvRV(perlret);
svp = av_fetch(array, funcctx->call_cntr, FALSE);
@ -1158,6 +1178,8 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
/* Find or compile the function */
prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true);
plperl_current_prodesc = prodesc;
/************************************************************
* Call the Perl function
************************************************************/
@ -1323,6 +1345,10 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
prodesc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
/* Remember if function is STABLE/IMMUTABLE */
prodesc->fn_readonly =
(procStruct->provolatile != PROVOLATILE_VOLATILE);
/************************************************************
* Lookup the pg_language tuple by Oid
************************************************************/
@ -1560,3 +1586,82 @@ plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)
output = perl_eval_pv(SvPV(output, PL_na), TRUE);
return output;
}
HV *
plperl_spi_exec(char *query, int limit)
{
HV *ret_hv;
int spi_rv;
spi_rv = SPI_execute(query, plperl_current_prodesc->fn_readonly, limit);
ret_hv = plperl_spi_execute_fetch_result(SPI_tuptable, SPI_processed, spi_rv);
return ret_hv;
}
static HV *
plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
{
int i;
char *attname;
char *attdata;
HV *array;
array = newHV();
for (i = 0; i < tupdesc->natts; i++)
{
/************************************************************
* Get the attribute name
************************************************************/
attname = tupdesc->attrs[i]->attname.data;
/************************************************************
* Get the attributes value
************************************************************/
attdata = SPI_getvalue(tuple, tupdesc, i + 1);
if (attdata)
hv_store(array, attname, strlen(attname), newSVpv(attdata, 0), 0);
else
hv_store(array, attname, strlen(attname), newSVpv("undef", 0), 0);
}
return array;
}
static HV *
plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed, int status)
{
HV *result;
result = newHV();
hv_store(result, "status", strlen("status"),
newSVpv((char *) SPI_result_code_string(status), 0), 0);
hv_store(result, "processed", strlen("processed"),
newSViv(processed), 0);
if (status == SPI_OK_SELECT)
{
if (processed)
{
AV *rows;
HV *row;
int i;
rows = newAV();
for (i = 0; i < processed; i++)
{
row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc);
av_store(rows, i, newRV_noinc((SV *) row));
}
hv_store(result, "rows", strlen("rows"),
newRV_noinc((SV *) rows), 0);
}
}
SPI_freetuptable(tuptable);
return result;
}

View File

@ -1,15 +1,12 @@
#include "postgres.h"
#include "executor/spi.h"
#include "utils/syscache.h"
/*
* This kludge is necessary because of the conflicting
* definitions of 'DEBUG' between postgres and perl.
* we'll live.
*/
#include "spi_internal.h"
#include "postgres.h"
static HV *plperl_spi_execute_fetch_result(SPITupleTable *, int, int);
#include "spi_internal.h"
int
@ -47,81 +44,3 @@ spi_ERROR(void)
{
return ERROR;
}
HV *
plperl_spi_exec(char *query, int limit)
{
HV *ret_hv;
int spi_rv;
spi_rv = SPI_exec(query, limit);
ret_hv = plperl_spi_execute_fetch_result(SPI_tuptable, SPI_processed, spi_rv);
return ret_hv;
}
static HV *
plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
{
int i;
char *attname;
char *attdata;
HV *array;
array = newHV();
for (i = 0; i < tupdesc->natts; i++)
{
/************************************************************
* Get the attribute name
************************************************************/
attname = tupdesc->attrs[i]->attname.data;
/************************************************************
* Get the attributes value
************************************************************/
attdata = SPI_getvalue(tuple, tupdesc, i + 1);
if (attdata)
hv_store(array, attname, strlen(attname), newSVpv(attdata, 0), 0);
else
hv_store(array, attname, strlen(attname), newSVpv("undef", 0), 0);
}
return array;
}
static HV *
plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed, int status)
{
HV *result;
result = newHV();
hv_store(result, "status", strlen("status"),
newSVpv((char *) SPI_result_code_string(status), 0), 0);
hv_store(result, "processed", strlen("processed"),
newSViv(processed), 0);
if (status == SPI_OK_SELECT)
{
if (processed)
{
AV *rows;
HV *row;
int i;
rows = newAV();
for (i = 0; i < processed; i++)
{
row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc);
av_store(rows, i, newRV_noinc((SV *) row));
}
hv_store(result, "rows", strlen("rows"),
newRV_noinc((SV *) rows), 0);
}
}
SPI_freetuptable(tuptable);
return result;
}

View File

@ -15,4 +15,5 @@ int spi_WARNING(void);
int spi_ERROR(void);
/* this is actually in plperl.c */
HV *plperl_spi_exec(char *, int);

View File

@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.81 2004/08/30 02:54:42 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.82 2004/09/13 20:09:20 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -578,6 +578,9 @@ do_compile(FunctionCallInfo fcinfo,
break;
}
/* Remember if function is STABLE/IMMUTABLE */
function->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
/*
* Create the magic FOUND variable.
*/

View File

@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.118 2004/08/30 02:54:42 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.119 2004/09/13 20:09:20 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -897,6 +897,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
* sub-transaction
*/
MemoryContext oldcontext = CurrentMemoryContext;
ResourceOwner oldowner = CurrentResourceOwner;
volatile bool caught = false;
int xrc;
@ -907,12 +908,15 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
BeginInternalSubTransaction(NULL);
/* Want to run statements inside function's memory context */
MemoryContextSwitchTo(oldcontext);
if ((xrc = SPI_connect()) != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed: %s",
SPI_result_code_string(xrc));
PG_TRY();
rc = exec_stmts(estate, block->body);
{
rc = exec_stmts(estate, block->body);
}
PG_CATCH();
{
ErrorData *edata;
@ -927,6 +931,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
/* Abort the inner transaction (and inner SPI connection) */
RollbackAndReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
SPI_pop();
@ -958,8 +963,11 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
if ((xrc = SPI_finish()) != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed: %s",
SPI_result_code_string(xrc));
ReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
SPI_pop();
}
}
@ -1984,6 +1992,8 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
estate->retistuple = func->fn_retistuple;
estate->retisset = func->fn_retset;
estate->readonly_func = func->fn_readonly;
estate->rettupdesc = NULL;
estate->exitlabel = NULL;
@ -2019,7 +2029,7 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
static void
exec_eval_cleanup(PLpgSQL_execstate *estate)
{
/* Clear result of a full SPI_exec */
/* Clear result of a full SPI_execute */
if (estate->eval_tuptable != NULL)
SPI_freetuptable(estate->eval_tuptable);
estate->eval_tuptable = NULL;
@ -2120,7 +2130,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
exec_prepare_plan(estate, expr);
/*
* Now build up the values and nulls arguments for SPI_execp()
* Now build up the values and nulls arguments for SPI_execute_plan()
*/
values = (Datum *) palloc(expr->nparams * sizeof(Datum));
nulls = (char *) palloc(expr->nparams * sizeof(char));
@ -2142,7 +2152,8 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
/*
* Execute the plan
*/
rc = SPI_execp(expr->plan, values, nulls, 0);
rc = SPI_execute_plan(expr->plan, values, nulls,
estate->readonly_func, 0);
switch (rc)
{
case SPI_OK_UTILITY:
@ -2168,12 +2179,12 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
errhint("If you want to discard the results, use PERFORM instead.")));
default:
elog(ERROR, "SPI_execp failed executing query \"%s\": %s",
elog(ERROR, "SPI_execute_plan failed executing query \"%s\": %s",
expr->query, SPI_result_code_string(rc));
}
/*
* Release any result tuples from SPI_execp (probably shouldn't be
* Release any result tuples from SPI_execute_plan (probably shouldn't be
* any)
*/
SPI_freetuptable(SPI_tuptable);
@ -2220,11 +2231,11 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
exec_eval_cleanup(estate);
/*
* Call SPI_exec() without preparing a saved plan. The returncode can
* Call SPI_execute() without preparing a saved plan. The returncode can
* be any standard OK. Note that while a SELECT is allowed, its
* results will be discarded.
*/
exec_res = SPI_exec(querystr, 0);
exec_res = SPI_execute(querystr, estate->readonly_func, 0);
switch (exec_res)
{
case SPI_OK_SELECT:
@ -2249,7 +2260,7 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
* behavior is not consistent with SELECT INTO in a normal
* plpgsql context. (We need to reimplement EXECUTE to parse
* the string as a plpgsql command, not just feed it to
* SPI_exec.) However, CREATE AS should be allowed ... and
* SPI_execute.) However, CREATE AS should be allowed ... and
* since it produces the same parsetree as SELECT INTO,
* there's no way to tell the difference except to look at the
* source text. Wotta kluge!
@ -2284,12 +2295,12 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
default:
elog(ERROR, "SPI_exec failed executing query \"%s\": %s",
elog(ERROR, "SPI_execute failed executing query \"%s\": %s",
querystr, SPI_result_code_string(exec_res));
break;
}
/* Release any result from SPI_exec, as well as the querystring */
/* Release any result from SPI_execute, as well as the querystring */
SPI_freetuptable(SPI_tuptable);
pfree(querystr);
@ -2357,7 +2368,8 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
if (plan == NULL)
elog(ERROR, "SPI_prepare failed for \"%s\": %s",
querystr, SPI_result_code_string(SPI_result));
portal = SPI_cursor_open(NULL, plan, NULL, NULL);
portal = SPI_cursor_open(NULL, plan, NULL, NULL,
estate->readonly_func);
if (portal == NULL)
elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
querystr, SPI_result_code_string(SPI_result));
@ -2549,7 +2561,8 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
if (curplan == NULL)
elog(ERROR, "SPI_prepare failed for \"%s\": %s",
querystr, SPI_result_code_string(SPI_result));
portal = SPI_cursor_open(curname, curplan, NULL, NULL);
portal = SPI_cursor_open(curname, curplan, NULL, NULL,
estate->readonly_func);
if (portal == NULL)
elog(ERROR, "could not open cursor for query \"%s\": %s",
querystr, SPI_result_code_string(SPI_result));
@ -2643,7 +2656,8 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
* Open the cursor
* ----------
*/
portal = SPI_cursor_open(curname, query->plan, values, nulls);
portal = SPI_cursor_open(curname, query->plan, values, nulls,
estate->readonly_func);
if (portal == NULL)
elog(ERROR, "could not open cursor: %s",
SPI_result_code_string(SPI_result));
@ -3470,7 +3484,7 @@ exec_run_select(PLpgSQL_execstate *estate,
exec_prepare_plan(estate, expr);
/*
* Now build up the values and nulls arguments for SPI_execp()
* Now build up the values and nulls arguments for SPI_execute_plan()
*/
values = (Datum *) palloc(expr->nparams * sizeof(Datum));
nulls = (char *) palloc(expr->nparams * sizeof(char));
@ -3494,7 +3508,8 @@ exec_run_select(PLpgSQL_execstate *estate,
*/
if (portalP != NULL)
{
*portalP = SPI_cursor_open(NULL, expr->plan, values, nulls);
*portalP = SPI_cursor_open(NULL, expr->plan, values, nulls,
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));
@ -3506,7 +3521,8 @@ exec_run_select(PLpgSQL_execstate *estate,
/*
* Execute the query
*/
rc = SPI_execp(expr->plan, values, nulls, maxtuples);
rc = SPI_execute_plan(expr->plan, values, nulls,
estate->readonly_func, maxtuples);
if (rc != SPI_OK_SELECT)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),

View File

@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.53 2004/08/30 02:54:42 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.54 2004/09/13 20:09:21 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -585,6 +585,7 @@ typedef struct PLpgSQL_function
Oid fn_rettypioparam;
bool fn_retistuple;
bool fn_retset;
bool fn_readonly;
int fn_nargs;
int fn_argvarnos[FUNC_MAX_ARGS];
@ -615,6 +616,8 @@ typedef struct
bool retistuple;
bool retisset;
bool readonly_func;
TupleDesc rettupdesc;
char *exitlabel;

View File

@ -29,7 +29,7 @@
* MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.55 2004/08/30 02:54:42 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.56 2004/09/13 20:09:30 tgl Exp $
*
*********************************************************************
*/
@ -131,6 +131,7 @@ typedef struct PLyProcedure
char *pyname; /* Python name of procedure */
TransactionId fn_xmin;
CommandId fn_cmin;
bool fn_readonly;
PLyTypeInfo result; /* also used to store info for trigger
* tuple type */
PLyTypeInfo args[FUNC_MAX_ARGS];
@ -257,11 +258,9 @@ static PyObject *PLyString_FromString(const char *);
static int PLy_first_call = 1;
/*
* Last function called by postgres backend
*
* XXX replace this with errcontext mechanism
* Currently active plpython function
*/
static PLyProcedure *PLy_last_procedure = NULL;
static PLyProcedure *PLy_curr_procedure = NULL;
/*
* When a callback from Python into PG incurs an error, we temporarily store
@ -322,6 +321,7 @@ Datum
plpython_call_handler(PG_FUNCTION_ARGS)
{
Datum retval;
PLyProcedure *save_curr_proc;
PLyProcedure *volatile proc = NULL;
PLy_init_all();
@ -329,6 +329,8 @@ plpython_call_handler(PG_FUNCTION_ARGS)
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "could not connect to SPI manager");
save_curr_proc = PLy_curr_procedure;
PG_TRY();
{
if (CALLED_AS_TRIGGER(fcinfo))
@ -338,17 +340,20 @@ plpython_call_handler(PG_FUNCTION_ARGS)
proc = PLy_procedure_get(fcinfo,
RelationGetRelid(tdata->tg_relation));
PLy_curr_procedure = proc;
trv = PLy_trigger_handler(fcinfo, proc);
retval = PointerGetDatum(trv);
}
else
{
proc = PLy_procedure_get(fcinfo, InvalidOid);
PLy_curr_procedure = proc;
retval = PLy_function_handler(fcinfo, proc);
}
}
PG_CATCH();
{
PLy_curr_procedure = save_curr_proc;
if (proc)
{
/* note: Py_DECREF needs braces around it, as of 2003/08 */
@ -359,6 +364,8 @@ plpython_call_handler(PG_FUNCTION_ARGS)
}
PG_END_TRY();
PLy_curr_procedure = save_curr_proc;
Py_DECREF(proc->me);
return retval;
@ -795,14 +802,10 @@ static PyObject *
PLy_procedure_call(PLyProcedure * proc, char *kargs, PyObject * vargs)
{
PyObject *rv;
PLyProcedure *current;
current = PLy_last_procedure;
PLy_last_procedure = proc;
PyDict_SetItemString(proc->globals, kargs, vargs);
rv = PyEval_EvalCode((PyCodeObject *) proc->code,
proc->globals, proc->globals);
PLy_last_procedure = current;
/*
* If there was an error in a PG callback, propagate that no matter
@ -1005,6 +1008,9 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid,
strcpy(proc->pyname, procName);
proc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
proc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
/* Remember if function is STABLE/IMMUTABLE */
proc->fn_readonly =
(procStruct->provolatile != PROVOLATILE_VOLATILE);
PLy_typeinfo_init(&proc->result);
for (i = 0; i < FUNC_MAX_ARGS; i++)
PLy_typeinfo_init(&proc->args[i]);
@ -1935,7 +1941,8 @@ PLy_spi_prepare(PyObject * self, PyObject * args)
PyErr_SetString(PLy_exc_spi_error,
"Unknown error in PLy_spi_prepare");
/* XXX this oughta be replaced with errcontext mechanism */
PLy_elog(WARNING, "in function %s:", PLy_procedure_name(PLy_last_procedure));
PLy_elog(WARNING, "in function %s:",
PLy_procedure_name(PLy_curr_procedure));
return NULL;
}
PG_END_TRY();
@ -2054,7 +2061,8 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit)
}
}
rv = SPI_execp(plan->plan, plan->values, nulls, limit);
rv = SPI_execute_plan(plan->plan, plan->values, nulls,
PLy_curr_procedure->fn_readonly, limit);
pfree(nulls);
}
@ -2080,7 +2088,9 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit)
if (!PyErr_Occurred())
PyErr_SetString(PLy_exc_error,
"Unknown error in PLy_spi_execute_plan");
PLy_elog(WARNING, "in function %s:", PLy_procedure_name(PLy_last_procedure));
/* XXX this oughta be replaced with errcontext mechanism */
PLy_elog(WARNING, "in function %s:",
PLy_procedure_name(PLy_curr_procedure));
return NULL;
}
PG_END_TRY();
@ -2098,7 +2108,7 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit)
if (rv < 0)
{
PLy_exception_set(PLy_exc_spi_error,
"SPI_execp failed: %s",
"SPI_execute_plan failed: %s",
SPI_result_code_string(rv));
return NULL;
}
@ -2114,7 +2124,9 @@ PLy_spi_execute_query(char *query, int limit)
oldcontext = CurrentMemoryContext;
PG_TRY();
rv = SPI_exec(query, limit);
{
rv = SPI_execute(query, PLy_curr_procedure->fn_readonly, limit);
}
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);
@ -2123,7 +2135,9 @@ PLy_spi_execute_query(char *query, int limit)
if (!PyErr_Occurred())
PyErr_SetString(PLy_exc_spi_error,
"Unknown error in PLy_spi_execute_query");
PLy_elog(WARNING, "in function %s:", PLy_procedure_name(PLy_last_procedure));
/* XXX this oughta be replaced with errcontext mechanism */
PLy_elog(WARNING, "in function %s:",
PLy_procedure_name(PLy_curr_procedure));
return NULL;
}
PG_END_TRY();
@ -2131,7 +2145,7 @@ PLy_spi_execute_query(char *query, int limit)
if (rv < 0)
{
PLy_exception_set(PLy_exc_spi_error,
"SPI_exec failed: %s",
"SPI_execute failed: %s",
SPI_result_code_string(rv));
return NULL;
}
@ -2375,7 +2389,9 @@ PLy_output(volatile int level, PyObject * self, PyObject * args)
oldcontext = CurrentMemoryContext;
PG_TRY();
elog(level, "%s", sv);
{
elog(level, "%s", sv);
}
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);

View File

@ -31,7 +31,7 @@
* ENHANCEMENTS, OR MODIFICATIONS.
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.91 2004/08/30 02:54:42 momjian Exp $
* $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.92 2004/09/13 20:09:39 tgl Exp $
*
**********************************************************************/
@ -87,13 +87,17 @@ utf_e2u(unsigned char *src)
pfree(_pltcl_utf_dst); } while (0)
#define UTF_U2E(x) (_pltcl_utf_dst=utf_u2e(_pltcl_utf_src=(x)))
#define UTF_E2U(x) (_pltcl_utf_dst=utf_e2u(_pltcl_utf_src=(x)))
#else /* PLTCL_UTF */
#else /* !PLTCL_UTF */
#define UTF_BEGIN
#define UTF_END
#define UTF_U2E(x) (x)
#define UTF_E2U(x) (x)
#endif /* PLTCL_UTF */
/**********************************************************************
* The information we cache about loaded procedures
**********************************************************************/
@ -102,6 +106,7 @@ typedef struct pltcl_proc_desc
char *proname;
TransactionId fn_xmin;
CommandId fn_cmin;
bool fn_readonly;
bool lanpltrusted;
FmgrInfo result_in_func;
Oid result_typioparam;
@ -137,7 +142,10 @@ static Tcl_Interp *pltcl_safe_interp = NULL;
static Tcl_HashTable *pltcl_proc_hash = NULL;
static Tcl_HashTable *pltcl_norm_query_hash = NULL;
static Tcl_HashTable *pltcl_safe_query_hash = NULL;
/* these are saved and restored by pltcl_call_handler */
static FunctionCallInfo pltcl_current_fcinfo = NULL;
static pltcl_proc_desc *pltcl_current_prodesc = NULL;
/*
* When a callback from Tcl into PG incurs an error, we temporarily store
@ -179,11 +187,11 @@ static int pltcl_argisnull(ClientData cdata, Tcl_Interp *interp,
static int pltcl_returnnull(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
static int pltcl_SPI_exec(ClientData cdata, Tcl_Interp *interp,
static int pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
static int pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
static int pltcl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
static int pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
static int pltcl_SPI_lastoid(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
@ -307,11 +315,11 @@ pltcl_init_interp(Tcl_Interp *interp)
pltcl_returnnull, NULL, NULL);
Tcl_CreateCommand(interp, "spi_exec",
pltcl_SPI_exec, NULL, NULL);
pltcl_SPI_execute, NULL, NULL);
Tcl_CreateCommand(interp, "spi_prepare",
pltcl_SPI_prepare, NULL, NULL);
Tcl_CreateCommand(interp, "spi_execp",
pltcl_SPI_execp, NULL, NULL);
pltcl_SPI_execute_plan, NULL, NULL);
Tcl_CreateCommand(interp, "spi_lastoid",
pltcl_SPI_lastoid, NULL, NULL);
}
@ -334,8 +342,9 @@ pltcl_init_load_unknown(Tcl_Interp *interp)
/************************************************************
* Check if table pltcl_modules exists
************************************************************/
spi_rc = SPI_exec("select 1 from pg_catalog.pg_class "
"where relname = 'pltcl_modules'", 1);
spi_rc = SPI_execute("select 1 from pg_catalog.pg_class "
"where relname = 'pltcl_modules'",
false, 1);
SPI_freetuptable(SPI_tuptable);
if (spi_rc != SPI_OK_SELECT)
elog(ERROR, "select from pg_class failed");
@ -348,9 +357,10 @@ pltcl_init_load_unknown(Tcl_Interp *interp)
************************************************************/
Tcl_DStringInit(&unknown_src);
spi_rc = SPI_exec("select modseq, modsrc from pltcl_modules "
"where modname = 'unknown' "
"order by modseq", 0);
spi_rc = SPI_execute("select modseq, modsrc from pltcl_modules "
"where modname = 'unknown' "
"order by modseq",
false, 0);
if (spi_rc != SPI_OK_SELECT)
elog(ERROR, "select from pltcl_modules failed");
@ -405,30 +415,46 @@ pltcl_call_handler(PG_FUNCTION_ARGS)
{
Datum retval;
FunctionCallInfo save_fcinfo;
pltcl_proc_desc *save_prodesc;
/************************************************************
/*
* Initialize interpreters if first time through
************************************************************/
*/
pltcl_init_all();
/************************************************************
* Determine if called as function or trigger and
* call appropriate subhandler
************************************************************/
/*
* Ensure that static pointers are saved/restored properly
*/
save_fcinfo = pltcl_current_fcinfo;
save_prodesc = pltcl_current_prodesc;
if (CALLED_AS_TRIGGER(fcinfo))
PG_TRY();
{
pltcl_current_fcinfo = NULL;
retval = PointerGetDatum(pltcl_trigger_handler(fcinfo));
/*
* Determine if called as function or trigger and
* call appropriate subhandler
*/
if (CALLED_AS_TRIGGER(fcinfo))
{
pltcl_current_fcinfo = NULL;
retval = PointerGetDatum(pltcl_trigger_handler(fcinfo));
}
else
{
pltcl_current_fcinfo = fcinfo;
retval = pltcl_func_handler(fcinfo);
}
}
else
PG_CATCH();
{
pltcl_current_fcinfo = fcinfo;
retval = pltcl_func_handler(fcinfo);
pltcl_current_fcinfo = save_fcinfo;
pltcl_current_prodesc = save_prodesc;
PG_RE_THROW();
}
PG_END_TRY();
pltcl_current_fcinfo = save_fcinfo;
pltcl_current_prodesc = save_prodesc;
return retval;
}
@ -467,6 +493,8 @@ pltcl_func_handler(PG_FUNCTION_ARGS)
/* Find or compile the function */
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid);
pltcl_current_prodesc = prodesc;
if (prodesc->lanpltrusted)
interp = pltcl_safe_interp;
else
@ -643,6 +671,8 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS)
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
RelationGetRelid(trigdata->tg_relation));
pltcl_current_prodesc = prodesc;
if (prodesc->lanpltrusted)
interp = pltcl_safe_interp;
else
@ -1030,6 +1060,10 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid)
prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
prodesc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
/* Remember if function is STABLE/IMMUTABLE */
prodesc->fn_readonly =
(procStruct->provolatile != PROVOLATILE_VOLATILE);
/************************************************************
* Lookup the pg_language tuple by Oid
************************************************************/
@ -1336,7 +1370,7 @@ pltcl_elog(ClientData cdata, Tcl_Interp *interp,
/**********************************************************************
* pltcl_quote() - quote literal strings that are to
* be used in SPI_exec query strings
* be used in SPI_execute query strings
**********************************************************************/
static int
pltcl_quote(ClientData cdata, Tcl_Interp *interp,
@ -1484,12 +1518,12 @@ pltcl_returnnull(ClientData cdata, Tcl_Interp *interp,
/**********************************************************************
* pltcl_SPI_exec() - The builtin SPI_exec command
* pltcl_SPI_execute() - The builtin SPI_execute command
* for the Tcl interpreter
**********************************************************************/
static int
pltcl_SPI_exec(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[])
pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[])
{
volatile int my_rc;
int spi_rc;
@ -1570,7 +1604,8 @@ pltcl_SPI_exec(ClientData cdata, Tcl_Interp *interp,
PG_TRY();
{
UTF_BEGIN;
spi_rc = SPI_exec(UTF_U2E(argv[query_idx]), count);
spi_rc = SPI_execute(UTF_U2E(argv[query_idx]),
pltcl_current_prodesc->fn_readonly, count);
UTF_END;
}
PG_CATCH();
@ -1603,7 +1638,7 @@ pltcl_SPI_exec(ClientData cdata, Tcl_Interp *interp,
break;
default:
Tcl_AppendResult(interp, "pltcl: SPI_exec failed: ",
Tcl_AppendResult(interp, "pltcl: SPI_execute failed: ",
SPI_result_code_string(spi_rc), NULL);
SPI_freetuptable(SPI_tuptable);
return TCL_ERROR;
@ -1840,11 +1875,11 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
/**********************************************************************
* pltcl_SPI_execp() - Execute a prepared plan
* pltcl_SPI_execute_plan() - Execute a prepared plan
**********************************************************************/
static int
pltcl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[])
pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[])
{
volatile int my_rc;
int spi_rc;
@ -1992,7 +2027,7 @@ pltcl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
}
/************************************************************
* Setup the value array for the SPI_execp() using
* Setup the value array for SPI_execute_plan() using
* the type specific input functions
************************************************************/
oldcontext = CurrentMemoryContext;
@ -2046,7 +2081,10 @@ pltcl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
************************************************************/
oldcontext = CurrentMemoryContext;
PG_TRY();
spi_rc = SPI_execp(qdesc->plan, argvalues, nulls, count);
{
spi_rc = SPI_execute_plan(qdesc->plan, argvalues, nulls,
pltcl_current_prodesc->fn_readonly, count);
}
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);
@ -2058,7 +2096,7 @@ pltcl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
PG_END_TRY();
/************************************************************
* Check the return code from SPI_execp()
* Check the return code from SPI_execute_plan()
************************************************************/
switch (spi_rc)
{
@ -2080,7 +2118,7 @@ pltcl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
break;
default:
Tcl_AppendResult(interp, "pltcl: SPI_execp failed: ",
Tcl_AppendResult(interp, "pltcl: SPI_execute_plan failed: ",
SPI_result_code_string(spi_rc), NULL);
SPI_freetuptable(SPI_tuptable);
return TCL_ERROR;

View File

@ -374,6 +374,84 @@ ERROR: portal "c" cannot be run
FETCH 10 FROM c;
ERROR: portal "c" cannot be run
COMMIT;
--
-- Check that "stable" functions are really stable. They should not be
-- able to see the partial results of the calling query. (Ideally we would
-- also check that they don't see commits of concurrent transactions, but
-- that's a mite hard to do within the limitations of pg_regress.)
--
select * from xacttest;
a | b
-----+---------
56 | 7.8
100 | 99.097
0 | 0.09561
42 | 324.78
777 | 777.777
(5 rows)
create or replace function max_xacttest() returns smallint language sql as
'select max(a) from xacttest' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
a | b
-----+---------
0 | 0.09561
787 | 7.8
787 | 99.097
787 | 324.78
787 | 777.777
(5 rows)
rollback;
-- But a volatile function can see the partial results of the calling query
create or replace function max_xacttest() returns smallint language sql as
'select max(a) from xacttest' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
a | b
-----+---------
0 | 0.09561
787 | 7.8
797 | 99.097
807 | 324.78
817 | 777.777
(5 rows)
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
a | b
-----+---------
0 | 0.09561
787 | 7.8
787 | 99.097
787 | 324.78
787 | 777.777
(5 rows)
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
a | b
-----+---------
0 | 0.09561
787 | 7.8
797 | 99.097
807 | 324.78
817 | 777.777
(5 rows)
rollback;
-- test case for problems with dropping an open relation during abort
BEGIN;
savepoint x;

View File

@ -231,6 +231,49 @@ BEGIN;
FETCH 10 FROM c;
COMMIT;
--
-- Check that "stable" functions are really stable. They should not be
-- able to see the partial results of the calling query. (Ideally we would
-- also check that they don't see commits of concurrent transactions, but
-- that's a mite hard to do within the limitations of pg_regress.)
--
select * from xacttest;
create or replace function max_xacttest() returns smallint language sql as
'select max(a) from xacttest' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
rollback;
-- But a volatile function can see the partial results of the calling query
create or replace function max_xacttest() returns smallint language sql as
'select max(a) from xacttest' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
rollback;
-- Now the same test with plpgsql (since it depends on SPI which is different)
create or replace function max_xacttest() returns smallint language plpgsql as
'begin return max(a) from xacttest; end' stable;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
rollback;
create or replace function max_xacttest() returns smallint language plpgsql as
'begin return max(a) from xacttest; end' volatile;
begin;
update xacttest set a = max_xacttest() + 10 where a > 0;
select * from xacttest;
rollback;
-- test case for problems with dropping an open relation during abort
BEGIN;
savepoint x;