@@ -476,6 +476,20 @@ $$ LANGUAGE pltcl;
476476 </listitem>
477477 </varlistentry>
478478
479+ <varlistentry>
480+ <term><function>subtransaction</function> <replaceable>command</replaceable></term>
481+ <listitem>
482+ <para>
483+ The Tcl script contained in <replaceable>command</replaceable> is
484+ executed within a SQL subtransaction. If the script returns an
485+ error, that entire subtransaction is rolled back before returning the
486+ error out to the surrounding Tcl code.
487+ See <xref linkend="pltcl-subtransactions"> for more details and an
488+ example.
489+ </para>
490+ </listitem>
491+ </varlistentry>
492+
479493 <varlistentry>
480494 <term><function>quote</> <replaceable>string</replaceable></term>
481495 <listitem>
@@ -844,18 +858,22 @@ CREATE EVENT TRIGGER tcl_a_snitch ON ddl_command_start EXECUTE PROCEDURE tclsnit
844858 either by executing some invalid operation or by generating an error
845859 using the Tcl <function>error</function> command or
846860 PL/Tcl's <function>elog</function> command. Such errors can be caught
847- within Tcl using the Tcl <function>catch</function> command. If they
848- are not caught but are allowed to propagate out to the top level of
849- execution of the PL/Tcl function, they turn into database errors.
861+ within Tcl using the Tcl <function>catch</function> command. If an
862+ error is not caught but is allowed to propagate out to the top level of
863+ execution of the PL/Tcl function, it is reported as a SQL error in the
864+ function's calling query.
850865 </para>
851866
852867 <para>
853- Conversely, database errors that occur within PL/Tcl's
868+ Conversely, SQL errors that occur within PL/Tcl's
854869 <function>spi_exec</function>, <function>spi_prepare</function>,
855870 and <function>spi_execp</function> commands are reported as Tcl errors,
856871 so they are catchable by Tcl's <function>catch</function> command.
857- Again, if they propagate out to the top level without being caught,
858- they turn back into database errors.
872+ (Each of these PL/Tcl commands runs its SQL operation in a
873+ subtransaction, which is rolled back on error, so that any
874+ partially-completed operation is automatically cleaned up.)
875+ Again, if an error propagates out to the top level without being caught,
876+ it turns back into a SQL error.
859877 </para>
860878
861879 <para>
@@ -902,6 +920,88 @@ if {[catch { spi_exec $sql_command }]} {
902920 </para>
903921 </sect1>
904922
923+ <sect1 id="pltcl-subtransactions">
924+ <title>Explicit Subtransactions in PL/Tcl</title>
925+
926+ <indexterm>
927+ <primary>subtransactions</primary>
928+ <secondary>in PL/Tcl</secondary>
929+ </indexterm>
930+
931+ <para>
932+ Recovering from errors caused by database access as described in
933+ <xref linkend="pltcl-error-handling"> can lead to an undesirable
934+ situation where some operations succeed before one of them fails,
935+ and after recovering from that error the data is left in an
936+ inconsistent state. PL/Tcl offers a solution to this problem in
937+ the form of explicit subtransactions.
938+ </para>
939+
940+ <para>
941+ Consider a function that implements a transfer between two accounts:
942+ <programlisting>
943+ CREATE FUNCTION transfer_funds() RETURNS void AS $$
944+ if [catch {
945+ spi_exec "UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'"
946+ spi_exec "UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'"
947+ } errormsg] {
948+ set result [format "error transferring funds: %s" $errormsg]
949+ } else {
950+ set result "funds transferred successfully"
951+ }
952+ spi_exec "INSERT INTO operations (result) VALUES ('[quote $result]')"
953+ $$ LANGUAGE pltcl;
954+ </programlisting>
955+ If the second <command>UPDATE</command> statement results in an
956+ exception being raised, this function will log the failure, but
957+ the result of the first <command>UPDATE</command> will
958+ nevertheless be committed. In other words, the funds will be
959+ withdrawn from Joe's account, but will not be transferred to
960+ Mary's account. This happens because each <function>spi_exec</function>
961+ is a separate subtransaction, and only one of those subtransactions
962+ got rolled back.
963+ </para>
964+
965+ <para>
966+ To handle such cases, you can wrap multiple database operations in an
967+ explicit subtransaction, which will succeed or roll back as a whole.
968+ PL/Tcl provides a <function>subtransaction</function> command to manage
969+ this. We can rewrite our function as:
970+ <programlisting>
971+ CREATE FUNCTION transfer_funds2() RETURNS void AS $$
972+ if [catch {
973+ subtransaction {
974+ spi_exec "UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'"
975+ spi_exec "UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'"
976+ }
977+ } errormsg] {
978+ set result [format "error transferring funds: %s" $errormsg]
979+ } else {
980+ set result "funds transferred successfully"
981+ }
982+ spi_exec "INSERT INTO operations (result) VALUES ('[quote $result]')"
983+ $$ LANGUAGE pltcl;
984+ </programlisting>
985+ Note that use of <function>catch</function> is still required for this
986+ purpose. Otherwise the error would propagate to the top level of the
987+ function, preventing the desired insertion into
988+ the <structname>operations</structname> table.
989+ The <function>subtransaction</function> command does not trap errors, it
990+ only assures that all database operations executed inside its scope will
991+ be rolled back together when an error is reported.
992+ </para>
993+
994+ <para>
995+ A rollback of an explicit subtransaction occurs on any error reported
996+ by the contained Tcl code, not only errors originating from database
997+ access. Thus a regular Tcl exception raised inside
998+ a <function>subtransaction</function> command will also cause the
999+ subtransaction to be rolled back. However, non-error exits out of the
1000+ contained Tcl code (for instance, due to <function>return</function>) do
1001+ not cause a rollback.
1002+ </para>
1003+ </sect1>
1004+
9051005 <sect1 id="pltcl-config">
9061006 <title>PL/Tcl Configuration</title>
9071007
0 commit comments