summaryrefslogtreecommitdiff
path: root/doc/src
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2017-03-11 14:37:05 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2017-03-11 14:37:05 -0500
commitb58fd4a9cab21e9d937a4e369bab31b3a5d24710 (patch)
tree0824f3a7b17dd7d950e1267e8d7b63c0e4efc4a6 /doc/src
parentf9dfa5c9776649f769d537dd0923003b35f128de (diff)
Add a "subtransaction" command to PL/Tcl.
This allows rolling back the effects of some SPI commands without having to fail the entire PL/Tcl function. Victor Wagner, reviewed by Pavel Stehule Discussion: https://postgr.es/m/20170108205750.2dab04a1@wagner.wagner.home
Diffstat (limited to 'doc/src')
-rw-r--r--doc/src/sgml/pltcl.sgml112
1 files changed, 106 insertions, 6 deletions
diff --git a/doc/src/sgml/pltcl.sgml b/doc/src/sgml/pltcl.sgml
index ad216dd5b75..ed745a74810 100644
--- a/doc/src/sgml/pltcl.sgml
+++ b/doc/src/sgml/pltcl.sgml
@@ -477,6 +477,20 @@ $$ LANGUAGE pltcl;
</varlistentry>
<varlistentry>
+ <term><function>subtransaction</function> <replaceable>command</replaceable></term>
+ <listitem>
+ <para>
+ The Tcl script contained in <replaceable>command</replaceable> is
+ executed within a SQL subtransaction. If the script returns an
+ error, that entire subtransaction is rolled back before returning the
+ error out to the surrounding Tcl code.
+ See <xref linkend="pltcl-subtransactions"> for more details and an
+ example.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><function>quote</> <replaceable>string</replaceable></term>
<listitem>
<para>
@@ -844,18 +858,22 @@ CREATE EVENT TRIGGER tcl_a_snitch ON ddl_command_start EXECUTE PROCEDURE tclsnit
either by executing some invalid operation or by generating an error
using the Tcl <function>error</function> command or
PL/Tcl's <function>elog</function> command. Such errors can be caught
- within Tcl using the Tcl <function>catch</function> command. If they
- are not caught but are allowed to propagate out to the top level of
- execution of the PL/Tcl function, they turn into database errors.
+ within Tcl using the Tcl <function>catch</function> command. If an
+ error is not caught but is allowed to propagate out to the top level of
+ execution of the PL/Tcl function, it is reported as a SQL error in the
+ function's calling query.
</para>
<para>
- Conversely, database errors that occur within PL/Tcl's
+ Conversely, SQL errors that occur within PL/Tcl's
<function>spi_exec</function>, <function>spi_prepare</function>,
and <function>spi_execp</function> commands are reported as Tcl errors,
so they are catchable by Tcl's <function>catch</function> command.
- Again, if they propagate out to the top level without being caught,
- they turn back into database errors.
+ (Each of these PL/Tcl commands runs its SQL operation in a
+ subtransaction, which is rolled back on error, so that any
+ partially-completed operation is automatically cleaned up.)
+ Again, if an error propagates out to the top level without being caught,
+ it turns back into a SQL error.
</para>
<para>
@@ -902,6 +920,88 @@ if {[catch { spi_exec $sql_command }]} {
</para>
</sect1>
+ <sect1 id="pltcl-subtransactions">
+ <title>Explicit Subtransactions in PL/Tcl</title>
+
+ <indexterm>
+ <primary>subtransactions</primary>
+ <secondary>in PL/Tcl</secondary>
+ </indexterm>
+
+ <para>
+ Recovering from errors caused by database access as described in
+ <xref linkend="pltcl-error-handling"> can lead to an undesirable
+ situation where some operations succeed before one of them fails,
+ and after recovering from that error the data is left in an
+ inconsistent state. PL/Tcl offers a solution to this problem in
+ the form of explicit subtransactions.
+ </para>
+
+ <para>
+ Consider a function that implements a transfer between two accounts:
+<programlisting>
+CREATE FUNCTION transfer_funds() RETURNS void AS $$
+ if [catch {
+ spi_exec "UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'"
+ spi_exec "UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'"
+ } errormsg] {
+ set result [format "error transferring funds: %s" $errormsg]
+ } else {
+ set result "funds transferred successfully"
+ }
+ spi_exec "INSERT INTO operations (result) VALUES ('[quote $result]')"
+$$ LANGUAGE pltcl;
+</programlisting>
+ If the second <command>UPDATE</command> statement results in an
+ exception being raised, this function will log the failure, but
+ the result of the first <command>UPDATE</command> will
+ nevertheless be committed. In other words, the funds will be
+ withdrawn from Joe's account, but will not be transferred to
+ Mary's account. This happens because each <function>spi_exec</function>
+ is a separate subtransaction, and only one of those subtransactions
+ got rolled back.
+ </para>
+
+ <para>
+ To handle such cases, you can wrap multiple database operations in an
+ explicit subtransaction, which will succeed or roll back as a whole.
+ PL/Tcl provides a <function>subtransaction</function> command to manage
+ this. We can rewrite our function as:
+<programlisting>
+CREATE FUNCTION transfer_funds2() RETURNS void AS $$
+ if [catch {
+ subtransaction {
+ spi_exec "UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'"
+ spi_exec "UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'"
+ }
+ } errormsg] {
+ set result [format "error transferring funds: %s" $errormsg]
+ } else {
+ set result "funds transferred successfully"
+ }
+ spi_exec "INSERT INTO operations (result) VALUES ('[quote $result]')"
+$$ LANGUAGE pltcl;
+</programlisting>
+ Note that use of <function>catch</function> is still required for this
+ purpose. Otherwise the error would propagate to the top level of the
+ function, preventing the desired insertion into
+ the <structname>operations</structname> table.
+ The <function>subtransaction</function> command does not trap errors, it
+ only assures that all database operations executed inside its scope will
+ be rolled back together when an error is reported.
+ </para>
+
+ <para>
+ A rollback of an explicit subtransaction occurs on any error reported
+ by the contained Tcl code, not only errors originating from database
+ access. Thus a regular Tcl exception raised inside
+ a <function>subtransaction</function> command will also cause the
+ subtransaction to be rolled back. However, non-error exits out of the
+ contained Tcl code (for instance, due to <function>return</function>) do
+ not cause a rollback.
+ </para>
+ </sect1>
+
<sect1 id="pltcl-config">
<title>PL/Tcl Configuration</title>