Home Explore Blog CI



postgresql

13th chunk of `doc/src/sgml/plpython.sgml`
1936b1168d3ccabac2b70ae037e47aa16e9d61aa3be2933d0000000100000fa4
 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

Title: Explicit Subtransactions in PL/Python
Summary
PL/Python provides explicit subtransactions to handle database access errors and maintain data consistency, using the plpy.subtransaction() function to create a context manager that ensures atomic commit or rollback of database operations, while still requiring try/except blocks to catch and handle exceptions.