@@ -476,6 +476,20 @@ $$ LANGUAGE pltcl;
476
476
</listitem>
477
477
</varlistentry>
478
478
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
+
479
493
<varlistentry>
480
494
<term><function>quote</> <replaceable>string</replaceable></term>
481
495
<listitem>
@@ -844,18 +858,22 @@ CREATE EVENT TRIGGER tcl_a_snitch ON ddl_command_start EXECUTE PROCEDURE tclsnit
844
858
either by executing some invalid operation or by generating an error
845
859
using the Tcl <function>error</function> command or
846
860
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.
850
865
</para>
851
866
852
867
<para>
853
- Conversely, database errors that occur within PL/Tcl's
868
+ Conversely, SQL errors that occur within PL/Tcl's
854
869
<function>spi_exec</function>, <function>spi_prepare</function>,
855
870
and <function>spi_execp</function> commands are reported as Tcl errors,
856
871
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.
859
877
</para>
860
878
861
879
<para>
@@ -902,6 +920,88 @@ if {[catch { spi_exec $sql_command }]} {
902
920
</para>
903
921
</sect1>
904
922
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
+
905
1005
<sect1 id="pltcl-config">
906
1006
<title>PL/Tcl Configuration</title>
907
1007
0 commit comments