Home Explore Blog CI



postgresql

13th chunk of `doc/src/sgml/pltcl.sgml`
c53dda58e2b865f33de7c8fd4b654576fa11e4feb08d783b0000000100000fbc
 look like
<programlisting>
if {[catch { spi_exec $sql_command }]} {
    if {[lindex $::errorCode 0] == "POSTGRES"} {
        array set errorArray $::errorCode
        if {$errorArray(condition) == "undefined_table"} {
            # deal with missing table
        } else {
            # deal with some other type of SQL error
        }
    }
}
</programlisting>
     (The double colons explicitly specify that <varname>errorCode</varname>
     is a global variable.)
    </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>)

Title: Explicit Subtransactions in PL/Tcl for Data Consistency
Summary
This section discusses the use of explicit subtransactions in PL/Tcl to maintain data consistency when performing multiple database operations. It highlights a scenario where individual 'spi_exec' commands, if failing mid-process, can leave the database in an inconsistent state. The 'subtransaction' command is introduced as a means to group database operations into a single atomic unit, ensuring that either all operations succeed or all are rolled back in case of an error. The example provided showcases how to use 'subtransaction' with 'catch' to handle exceptions and guarantee data integrity during fund transfers between accounts.