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 */
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 */
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 */
}
/* Retrieve the desired rows */
- ret = SPI_exec(sql->data, 0);
+ ret = SPI_execute(sql->data, true, 0);
proc = SPI_processed;
/* Check for qualifying tuples */
/* 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);
-<!-- $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>
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>
<!--
-$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">
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>
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>
<!--
-$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">
<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
of the triggering query, rather than upon finishing the current
interactive command. This makes a difference when the triggering query
<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
of the triggering query, rather than upon finishing the current
interactive command. This makes a difference when the triggering query
<!--
-$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">
<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>
<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>
<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>
<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>
<!-- *********************************************** -->
-<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>
<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 may only be called from a connected procedure.
</para>
<para>
- This function should only be called from a connected procedure. If
- <parameter>count</parameter> is zero then it executes the command
+ 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>
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>
} 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
</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>
<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>
<!-- *********************************************** -->
+<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>
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>
<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
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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.
<!-- *********************************************** -->
-<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>
<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>
</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>
<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).
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
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>
<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>
<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>
<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>
<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>
<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>
<!--
-$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">
</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
functions
</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
overloaded functions must be different from the C names of all
(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'
</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
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
(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.
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();
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
/* 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();
}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
Oid *typioparams;
bool *isvarlena;
char *string;
- Snapshot mySnapshot;
ListCell *cur;
MemoryContext oldcontext;
MemoryContext mycontext;
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)
{
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
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)
FreeQueryDesc(queryDesc);
- CommandCounterIncrement();
+ /* We need a CCI just in case query expanded to multiple plans */
+ if (stmt->analyze)
+ CommandCounterIncrement();
totaltime += elapsed_time(&starttime);
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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",
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* Start execution, inserting parameters if any.
*/
- PortalStart(portal, params);
+ PortalStart(portal, params, ActiveSnapshot);
Assert(portal->strategy == PORTAL_ONE_SELECT);
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* Run the portal to completion.
*/
- PortalStart(portal, paramLI);
+ PortalStart(portal, paramLI, ActiveSnapshot);
(void) PortalRun(portal, FETCH_ALL, dest, dest, completionTag);
}
/* 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);
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
/* 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
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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.
*
* ----------------------------------------------------------------
*/
void
-ExecutorStart(QueryDesc *queryDesc, bool useCurrentSnapshot, bool explainOnly)
+ExecutorStart(QueryDesc *queryDesc, bool explainOnly)
{
EState *estate;
MemoryContext oldcontext;
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
/*
* 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,
/*
* 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,
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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 */
/* 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,
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;
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));
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),
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);
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;
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;
}
SQLFunctionCachePtr fcache)
{
TupleTableSlot *slot;
+ HeapTuple tup;
+ TupleDesc tupDesc;
Datum value;
if (es->status == F_EXEC_START)
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)
- {
- /*
- * 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);
+ 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;
+ /*
+ * 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
{
- /*
- * 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;
}
+ HeapTupleHeaderSetDatumLength(dtup, tup->t_len);
+ HeapTupleHeaderSetTypeId(dtup, dtuptype);
+ HeapTupleHeaderSetTypMod(dtup, dtuptypmod);
+
+ value = PointerGetDatum(dtup);
+ fcinfo->isnull = false;
+ }
+ else
+ {
/*
- * If this is a single valued function we have to end the function
- * execution now.
+ * 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.
*/
- if (!fcinfo->flinfo->fn_retset)
- postquel_end(es);
+ value = heap_getattr(tup, 1, tupDesc, &(fcinfo->isnull));
- return value;
+ 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
{
/* 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;
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
_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)
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_execp(void *plan, Datum *Values, const char *Nulls, int tcount)
+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 (res < 0)
return res;
- res = _SPI_execute_plan((_SPI_plan *) plan, Values, Nulls, false, tcount);
+ 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)
+{
+ 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;
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;
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);
* 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;
Query *queryTree;
Plan *planTree;
ParamListInfo paramLI;
+ Snapshot snapshot;
MemoryContext oldcontext;
Portal portal;
int k;
(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;
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);
*/
/*
- * Plan and optionally execute a querystring.
+ * Parse and plan a querystring.
+ *
+ * At entry, plan->argtypes and plan->nargs must be valid.
*
- * If plan != NULL, just prepare plan trees and save them in *plan;
- * else execute immediately.
+ * 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;
-
- if (plan)
- {
- nargs = plan->nargs;
- argtypes = plan->argtypes;
- }
+ Oid *argtypes = plan->argtypes;
+ int nargs = plan->nargs;
- /* 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()
*/
/*
* 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;
{
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) */
- 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 = pg_plan_query(queryTree, NULL);
- plan_list = lappend(plan_list, planTree);
-
- dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None, NULL);
- if (queryTree->commandType == CMD_UTILITY)
- {
- if (IsA(queryTree->utilityStmt, CopyStmt))
- {
- 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;
- }
- res = SPI_OK_UTILITY;
- if (plan == NULL)
- {
- ProcessUtility(queryTree->utilityStmt, NULL, dest, NULL);
- CommandCounterIncrement();
- }
- }
- 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);
- if (res < 0)
- goto fail;
- }
- }
+ plan_list = list_concat(plan_list,
+ pg_plan_queries(query_list, NULL, false));
}
- if (plan)
- {
- plan->qtlist = query_list_list;
- plan->ptlist = plan_list;
- }
-
-fail:
+ plan->qtlist = query_list_list;
+ plan->ptlist = plan_list;
/*
* Pop the error context stack
*/
error_context_stack = spierrcontext.previous;
-
- return res;
}
+/*
+ * 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,
- bool useCurrentSnapshot, int tcount)
+ Snapshot snapshot, Snapshot crosscheck_snapshot,
+ bool read_only, 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();
+ volatile int res = 0;
+ Snapshot saveActiveSnapshot;
- /* Convert parameters to form wanted by executor */
- if (nargs > 0)
+ /* Be sure to restore ActiveSnapshot on error exit */
+ saveActiveSnapshot = ActiveSnapshot;
+ PG_TRY();
{
- int k;
-
- paramLI = (ParamListInfo)
- palloc0((nargs + 1) * sizeof(ParamListInfoData));
-
- for (k = 0; k < nargs; k++)
+ 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)
{
- 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;
+ int k;
- /* 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;
+ paramLI = (ParamListInfo)
+ palloc0((nargs + 1) * sizeof(ParamListInfoData));
- foreach(query_list_list_item, query_list_list)
- {
- List *query_list = lfirst(query_list_list_item);
- ListCell *query_list_item;
+ 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 for each original parsetree */
- /* (at most one of its querytrees will be marked canSetTag) */
+ /* 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 = lfirst(plan_list_item);
- plan_list_item = lnext(plan_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;
- dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None, NULL);
- if (queryTree->commandType == CMD_UTILITY)
- {
- ProcessUtility(queryTree->utilityStmt, paramLI, dest, NULL);
- res = SPI_OK_UTILITY;
- CommandCounterIncrement();
- }
- else
+ foreach(query_list_item, query_list)
{
- qdesc = CreateQueryDesc(queryTree, planTree, dest,
- paramLI, false);
- res = _SPI_pquery(qdesc, true, useCurrentSnapshot,
- queryTree->canSetTag ? tcount : 0);
+ 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);
+
+ if (queryTree->commandType == CMD_UTILITY)
+ {
+ if (IsA(queryTree->utilityStmt, CopyStmt))
+ {
+ 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;
+ }
+ }
+
+ 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
+ {
+ /*
+ * 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;
- CommandCounterIncrement();
}
}
- }
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_pquery(QueryDesc *queryDesc, bool runit,
- bool useCurrentSnapshot, int tcount)
+_SPI_pquery(QueryDesc *queryDesc, int tcount)
{
int operation = queryDesc->operation;
int res;
return SPI_ERROR_OPUNKNOWN;
}
- if (!runit) /* plan preparation, don't execute */
- return res;
-
#ifdef SPI_EXECUTOR_STATS
if (ShowExecutorStats)
ResetUsage();
AfterTriggerBeginQuery();
- ExecutorStart(queryDesc, useCurrentSnapshot, false);
+ ExecutorStart(queryDesc, false);
ExecutorRun(queryDesc, ForwardScanDirection, (long) tcount);
res = SPI_OK_UTILITY;
}
- FreeQueryDesc(queryDesc);
-
#ifdef SPI_EXECUTOR_STATS
if (ShowExecutorStats)
ShowUsage("SPI EXECUTOR STATS");
*
*
* 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.
get_func_name(fid));
/*
- * Set up a query snapshot in case function needs one.
- */
- SetQuerySnapshot();
-
- /*
* Prepare function call info block and insert arguments.
*/
MemSet(&fcinfo, 0, sizeof(fcinfo));
*
*
* 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
{
if (needSnapshot)
{
- SetQuerySnapshot();
+ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
needSnapshot = false;
}
plan = pg_plan_query(query, boundParams);
/*
* Start the portal. No parameters here.
*/
- PortalStart(portal, NULL);
+ PortalStart(portal, NULL, InvalidSnapshot);
/*
* Select the appropriate output format: text unless we are doing
pstmt->plan_list,
pstmt->context);
- PortalStart(portal, params);
+ PortalStart(portal, params, InvalidSnapshot);
/*
* Apply the result format requests to the portal.
/* 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 */
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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,
QueryDesc *
CreateQueryDesc(Query *parsetree,
Plan *plantree,
+ Snapshot snapshot,
+ Snapshot crosscheck_snapshot,
DestReceiver *dest,
ParamListInfo params,
bool doInstrument)
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? */
/*
* 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
* 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,
int operation = parsetree->commandType;
QueryDesc *queryDesc;
+ ereport(DEBUG3,
+ (errmsg_internal("ProcessQuery")));
+
/*
* Check for special-case destinations
*/
}
/*
+ * 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
/*
* Call ExecStart to prepare the plan for execution
*/
- ExecutorStart(queryDesc, false, false);
+ ExecutorStart(queryDesc, false);
/*
* Run the plan to completion.
AfterTriggerEndQuery();
FreeQueryDesc(queryDesc);
+
+ FreeSnapshot(ActiveSnapshot);
+ ActiveSnapshot = NULL;
}
/*
* 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;
* 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);
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,
*/
queryDesc = CreateQueryDesc((Query *) linitial(portal->parseTrees),
(Plan *) linitial(portal->planTrees),
+ ActiveSnapshot,
+ InvalidSnapshot,
None_Receiver,
params,
false);
/*
* Call ExecStart to prepare the plan for execution
*/
- ExecutorStart(queryDesc, false, false);
+ ExecutorStart(queryDesc, false);
/*
* This tells PortalCleanup to shut down the executor
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);
/* Restore global vars and propagate error */
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
MemoryContextSwitchTo(oldContext);
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
{
bool result;
Portal saveActivePortal;
+ Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext saveQueryContext;
* 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;
/* Restore global vars and propagate error */
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
MemoryContextSwitchTo(oldContext);
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
nprocessed = RunFromStore(portal, direction, count, dest);
else
{
+ ActiveSnapshot = queryDesc->snapshot;
ExecutorRun(queryDesc, direction, count);
nprocessed = queryDesc->estate->es_processed;
}
nprocessed = RunFromStore(portal, direction, count, dest);
else
{
+ ActiveSnapshot = queryDesc->snapshot;
ExecutorRun(queryDesc, direction, count);
nprocessed = queryDesc->estate->es_processed;
}
* 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) ||
IsA(utilityStmt, NotifyStmt) ||
IsA(utilityStmt, UnlistenStmt) ||
IsA(utilityStmt, CheckPointStmt)))
- SetQuerySnapshot();
+ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
+ else
+ ActiveSnapshot = NULL;
if (query->canSetTag)
{
/* Some utility statements may change context on us */
MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+
+ if (ActiveSnapshot)
+ FreeSnapshot(ActiveSnapshot);
+ ActiveSnapshot = NULL;
}
/*
/*
* 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();
{
long result;
Portal saveActivePortal;
+ Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext saveQueryContext;
* 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;
/* Restore global vars and propagate error */
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
portal->status = PORTAL_READY;
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
}
+/*
+ * 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)
{
* 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.
*/
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;
+}
*
* 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 $
*
* ----------
*/
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)
Relation query_rel,
source_rel;
int key_idx;
- bool useCurrentSnapshot;
+ Snapshot test_snapshot;
+ Snapshot crosscheck_snapshot;
int limit;
int spi_result;
AclId save_uid;
}
/*
- * 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;
}
/*
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 : "",
* 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.
*
*/
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)
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)
* 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 $
*
*-------------------------------------------------------------------------
*/
#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;
/*
- * 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;
-
- 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();
+ Snapshot newsnap;
- /* 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);
}
/*
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 */
}
* 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 $
*
*-------------------------------------------------------------------------
*/
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 */
/* in pquery.c */
extern QueryDesc *CreateQueryDesc(Query *parsetree, Plan *plantree,
+ Snapshot snapshot,
+ Snapshot crosscheck_snapshot,
DestReceiver *dest,
ParamListInfo params,
bool doInstrument);
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* 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);
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
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);
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
* 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 $
*
*-------------------------------------------------------------------------
*/
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 */
* 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 $
*
*-------------------------------------------------------------------------
*/
#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;
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 */
* 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 $
*
**********************************************************************/
#include "executor/spi.h"
#include "fmgr.h"
#include "tcop/tcopprot.h"
+#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
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 */
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
**********************************************************************/
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);
/*
plperl_get_keys(HV *hv)
{
AV *ret;
- SV **svp;
int key_count;
SV *val;
char *key;
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++;
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;
}
SV *retval;
int i;
int count;
- char *ret_test;
ENTER;
SAVETMPS;
/* 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
************************************************************/
{
HV *row_hv;
SV **svp;
- char *row_key;
svp = av_fetch(ret_av, call_cntr, FALSE);
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcontext;
- int i;
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
Datum result;
AV *array;
SV **svp;
- int i;
array = (AV *) SvRV(perlret);
svp = av_fetch(array, funcctx->call_cntr, FALSE);
/* Find or compile the function */
prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true);
+ plperl_current_prodesc = prodesc;
+
/************************************************************
* Call the Perl function
************************************************************/
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
************************************************************/
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;
+}
-#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
{
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;
-}
int spi_ERROR(void);
+/* this is actually in plperl.c */
HV *plperl_spi_exec(char *, int);
* 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.
*
break;
}
+ /* Remember if function is STABLE/IMMUTABLE */
+ function->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
+
/*
* Create the magic FOUND variable.
*/
* 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.
*
* sub-transaction
*/
MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
volatile bool caught = false;
int xrc;
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;
/* Abort the inner transaction (and inner SPI connection) */
RollbackAndReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
SPI_pop();
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();
}
}
estate->retistuple = func->fn_retistuple;
estate->retisset = func->fn_retset;
+ estate->readonly_func = func->fn_readonly;
+
estate->rettupdesc = NULL;
estate->exitlabel = NULL;
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;
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));
/*
* 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:
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);
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:
* 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!
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);
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));
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));
* 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));
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));
*/
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));
/*
* 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),
* 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.
*
Oid fn_rettypioparam;
bool fn_retistuple;
bool fn_retset;
+ bool fn_readonly;
int fn_nargs;
int fn_argvarnos[FUNC_MAX_ARGS];
bool retistuple;
bool retisset;
+ bool readonly_func;
+
TupleDesc rettupdesc;
char *exitlabel;
* 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 $
*
*********************************************************************
*/
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];
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
plpython_call_handler(PG_FUNCTION_ARGS)
{
Datum retval;
+ PLyProcedure *save_curr_proc;
PLyProcedure *volatile proc = NULL;
PLy_init_all();
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))
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 */
}
PG_END_TRY();
+ PLy_curr_procedure = save_curr_proc;
+
Py_DECREF(proc->me);
return retval;
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
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]);
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();
}
}
- 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);
}
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();
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;
}
oldcontext = CurrentMemoryContext;
PG_TRY();
- rv = SPI_exec(query, limit);
+ {
+ rv = SPI_execute(query, PLy_curr_procedure->fn_readonly, limit);
+ }
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);
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();
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;
}
oldcontext = CurrentMemoryContext;
PG_TRY();
- elog(level, "%s", sv);
+ {
+ elog(level, "%s", sv);
+ }
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);
* 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 $
*
**********************************************************************/
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
**********************************************************************/
char *proname;
TransactionId fn_xmin;
CommandId fn_cmin;
+ bool fn_readonly;
bool lanpltrusted;
FmgrInfo result_in_func;
Oid result_typioparam;
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
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[]);
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);
}
/************************************************************
* 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");
************************************************************/
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");
{
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;
}
/* 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
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
RelationGetRelid(trigdata->tg_relation));
+ pltcl_current_prodesc = prodesc;
+
if (prodesc->lanpltrusted)
interp = pltcl_safe_interp;
else
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
************************************************************/
/**********************************************************************
* 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,
/**********************************************************************
- * 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;
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();
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;
/**********************************************************************
- * 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;
}
/************************************************************
- * 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;
************************************************************/
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);
PG_END_TRY();
/************************************************************
- * Check the return code from SPI_execp()
+ * Check the return code from SPI_execute_plan()
************************************************************/
switch (spi_rc)
{
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;
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;
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;