aboutsummaryrefslogtreecommitdiff
path: root/doc/src
diff options
context:
space:
mode:
authorPeter Eisentraut <peter_e@gmx.net>2011-02-27 17:09:56 +0200
committerPeter Eisentraut <peter_e@gmx.net>2011-02-27 21:15:35 +0200
commit22690719ea5a89ccbcd04deb58c83d8d5f138df8 (patch)
treeda9e49d59849631b4af9536cb414e31a825cb571 /doc/src
parent438cdf6e4893311df4e94dc55b961a691908a530 (diff)
downloadpostgresql-22690719ea5a89ccbcd04deb58c83d8d5f138df8.tar.gz
postgresql-22690719ea5a89ccbcd04deb58c83d8d5f138df8.zip
PL/Python explicit subtransactions
Adds a context manager, obtainable by plpy.subtransaction(), to run a group of statements in a subtransaction. Jan UrbaƄski, reviewed by Steve Singer, additional scribbling by me
Diffstat (limited to 'doc/src')
-rw-r--r--doc/src/sgml/plpython.sgml132
1 files changed, 131 insertions, 1 deletions
diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index 94d42bfaaa1..73203e62512 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -955,7 +955,7 @@ $$ LANGUAGE plpythonu;
</sect2>
- <sect2>
+ <sect2 id="plpython-trapping">
<title>Trapping Errors</title>
<para>
@@ -981,6 +981,136 @@ $$ LANGUAGE plpythonu;
</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>
+ <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, 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 plpythonu;
+</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="http://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, 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 plpythonu;
+</programlisting>
+ Note that the use of <literal>try/catch</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 occurrs on any kind of
+ exception exit, not only ones caused by errors originating from
+ database access. A regular Python exception raised inside an
+ explicit subtransaction block would also cause the subtransaction
+ to be rolled back.
+ </para>
+ </sect2>
+
+ <sect2>
+ <title>Older Python Versions</title>
+
+ <para>
+ Context managers syntax using the <literal>with</literal> keyword
+ is available by default in Python 2.6. If using PL/Python with an
+ older Python version, it is still possible to use explicit
+ subtransactions, although not as transparently. You can call the
+ subtransaction manager's <literal>__enter__</literal> and
+ <literal>__exit__</literal> functions using the
+ <literal>enter</literal> and <literal>exit</literal> convenience
+ aliases. The example function that transfers funds could be
+ written as:
+<programlisting>
+CREATE FUNCTION transfer_funds_old() RETURNS void AS $$
+try:
+ subxact = plpy.subtransaction()
+ subxact.enter()
+ 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:
+ import sys
+ subxact.exit(*sys.exc_info())
+ raise
+ else:
+ subxact.exit(None, None, None)
+except plpy.SPIError, 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 plpythonu;
+</programlisting>
+ </para>
+
+ <note>
+ <para>
+ Although context managers were implemented in Python 2.5, to use
+ the <literal>with</literal> syntax in that version you need to
+ use a <ulink
+ url="http://docs.python.org/release/2.5/ref/future.html">future
+ statement</ulink>. Because of implementation details, however,
+ you cannot use future statements in PL/Python functions.
+ </para>
+ </note>
+ </sect2>
+ </sect1>
+
<sect1 id="plpython-util">
<title>Utility Functions</title>
<para>