@@ -955,7 +955,7 @@ $$ LANGUAGE plpythonu;
955955
956956 </sect2>
957957
958- <sect2>
958+ <sect2 id="plpython-trapping" >
959959 <title>Trapping Errors</title>
960960
961961 <para>
@@ -981,6 +981,136 @@ $$ LANGUAGE plpythonu;
981981 </sect2>
982982 </sect1>
983983
984+ <sect1 id="plpython-subtransaction">
985+ <title>Explicit Subtransactions</title>
986+
987+ <para>
988+ Recovering from errors caused by database access as described in
989+ <xref linkend="plpython-trapping"> can lead to an undesirable
990+ situation where some operations succeed before one of them fails,
991+ and after recovering from that error the data is left in an
992+ inconsistent state. PL/Python offers a solution to this problem in
993+ the form of explicit subtransactions.
994+ </para>
995+
996+ <sect2>
997+ <title>Subtransaction Context Managers</title>
998+
999+ <para>
1000+ Consider a function that implements a transfer between two
1001+ accounts:
1002+ <programlisting>
1003+ CREATE FUNCTION transfer_funds() RETURNS void AS $$
1004+ try:
1005+ plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
1006+ plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
1007+ except plpy.SPIError, e:
1008+ result = "error transferring funds: %s" % e.args
1009+ else:
1010+ result = "funds transferred correctly"
1011+ plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
1012+ plpy.execute(plan, [result])
1013+ $$ LANGUAGE plpythonu;
1014+ </programlisting>
1015+ If the second <literal>UPDATE</literal> statement results in an
1016+ exception being raised, this function will report the error, but
1017+ the result of the first <literal>UPDATE</literal> will
1018+ nevertheless be committed. In other words, the funds will be
1019+ withdrawn from Joe's account, but will not be transferred to
1020+ Mary's account.
1021+ </para>
1022+
1023+ <para>
1024+ To avoid such issues, you can wrap your
1025+ <literal>plpy.execute</literal> calls in an explicit
1026+ subtransaction. The <literal>plpy</literal> module provides a
1027+ helper object to manage explicit subtransactions that gets created
1028+ with the <literal>plpy.subtransaction()</literal> function.
1029+ Objects created by this function implement the
1030+ <ulink url="http://docs.python.org/library/stdtypes.html#context-manager-types">
1031+ context manager interface</ulink>. Using explicit subtransactions
1032+ we can rewrite our function as:
1033+ <programlisting>
1034+ CREATE FUNCTION transfer_funds2() RETURNS void AS $$
1035+ try:
1036+ with plpy.subtransaction():
1037+ plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
1038+ plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
1039+ except plpy.SPIError, e:
1040+ result = "error transferring funds: %s" % e.args
1041+ else:
1042+ result = "funds transferred correctly"
1043+ plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
1044+ plpy.execute(plan, [result])
1045+ $$ LANGUAGE plpythonu;
1046+ </programlisting>
1047+ Note that the use of <literal>try/catch</literal> is still
1048+ required. Otherwise the exception would propagate to the top of
1049+ the Python stack and would cause the whole function to abort with
1050+ a <productname>PostgreSQL</productname> error, so that the
1051+ <literal>operations</literal> table would not have any row
1052+ inserted into it. The subtransaction context manager does not
1053+ trap errors, it only assures that all database operations executed
1054+ inside its scope will be atomically committed or rolled back. A
1055+ rollback of the subtransaction block occurrs on any kind of
1056+ exception exit, not only ones caused by errors originating from
1057+ database access. A regular Python exception raised inside an
1058+ explicit subtransaction block would also cause the subtransaction
1059+ to be rolled back.
1060+ </para>
1061+ </sect2>
1062+
1063+ <sect2>
1064+ <title>Older Python Versions</title>
1065+
1066+ <para>
1067+ Context managers syntax using the <literal>with</literal> keyword
1068+ is available by default in Python 2.6. If using PL/Python with an
1069+ older Python version, it is still possible to use explicit
1070+ subtransactions, although not as transparently. You can call the
1071+ subtransaction manager's <literal>__enter__</literal> and
1072+ <literal>__exit__</literal> functions using the
1073+ <literal>enter</literal> and <literal>exit</literal> convenience
1074+ aliases. The example function that transfers funds could be
1075+ written as:
1076+ <programlisting>
1077+ CREATE FUNCTION transfer_funds_old() RETURNS void AS $$
1078+ try:
1079+ subxact = plpy.subtransaction()
1080+ subxact.enter()
1081+ try:
1082+ plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
1083+ plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
1084+ except:
1085+ import sys
1086+ subxact.exit(*sys.exc_info())
1087+ raise
1088+ else:
1089+ subxact.exit(None, None, None)
1090+ except plpy.SPIError, e:
1091+ result = "error transferring funds: %s" % e.args
1092+ else:
1093+ result = "funds transferred correctly"
1094+
1095+ plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
1096+ plpy.execute(plan, [result])
1097+ $$ LANGUAGE plpythonu;
1098+ </programlisting>
1099+ </para>
1100+
1101+ <note>
1102+ <para>
1103+ Although context managers were implemented in Python 2.5, to use
1104+ the <literal>with</literal> syntax in that version you need to
1105+ use a <ulink
1106+ url="http://docs.python.org/release/2.5/ref/future.html">future
1107+ statement</ulink>. Because of implementation details, however,
1108+ you cannot use future statements in PL/Python functions.
1109+ </para>
1110+ </note>
1111+ </sect2>
1112+ </sect1>
1113+
9841114 <sect1 id="plpython-util">
9851115 <title>Utility Functions</title>
9861116 <para>
0 commit comments