handling it will catch any database access error.
</para>
<para>
As an alternative way of handling different error conditions, you
can catch the <literal>SPIError</literal> exception and determine
the specific error condition inside the <literal>except</literal>
block by looking at the <literal>sqlstate</literal> attribute of
the exception object. This attribute is a string value containing
the <quote>SQLSTATE</quote> error code. This approach provides
approximately the same functionality
</para>
</sect2>
</sect1>
<sect1 id="plpython-subtransaction">
<title>Explicit Subtransactions</title>
<para>
Recovering from errors caused by database access as described in
<xref linkend="plpython-trapping"/> 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/Python offers a solution to this problem in
the form of explicit subtransactions.
</para>
<sect2 id="plpython-subtransaction-context-managers">
<title>Subtransaction Context Managers</title>
<para>
Consider a function that implements a transfer between two
accounts:
<programlisting>
CREATE FUNCTION transfer_funds() RETURNS void AS $$
try:
plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except plpy.SPIError as e:
result = "error transferring funds: %s" % e.args
else:
result = "funds transferred correctly"
plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpython3u;
</programlisting>
If the second <literal>UPDATE</literal> statement results in an
exception being raised, this function will report the error, but
the result of the first <literal>UPDATE</literal> 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.
</para>
<para>
To avoid such issues, you can wrap your
<literal>plpy.execute</literal> calls in an explicit
subtransaction. The <literal>plpy</literal> module provides a
helper object to manage explicit subtransactions that gets created
with the <literal>plpy.subtransaction()</literal> function.
Objects created by this function implement the
<ulink url="https://docs.python.org/library/stdtypes.html#context-manager-types">
context manager interface</ulink>. Using explicit subtransactions
we can rewrite our function as:
<programlisting>
CREATE FUNCTION transfer_funds2() RETURNS void AS $$
try:
with plpy.subtransaction():
plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except plpy.SPIError as e:
result = "error transferring funds: %s" % e.args
else:
result = "funds transferred correctly"
plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpython3u;
</programlisting>
Note that the use of <literal>try</literal>/<literal>except</literal> is still
required. Otherwise the exception would propagate to the top of
the Python stack and would cause the whole function to abort with
a <productname>PostgreSQL</productname> error, so that the
<literal>operations</literal> table would not have any row
inserted into it. The subtransaction context manager does not
trap errors, it only assures that all database operations executed
inside its scope will be atomically committed or rolled back. A
rollback of the subtransaction block occurs on any kind of
exception exit, not only ones caused by errors originating from
database access. A regular Python exception raised