Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit2269071

Browse files
committed
PL/Python explicit subtransactions
Adds a context manager, obtainable by plpy.subtransaction(), to run agroup of statements in a subtransaction.Jan Urbański, reviewed by Steve Singer, additional scribbling by me
1 parent438cdf6 commit2269071

File tree

8 files changed

+1404
-8
lines changed

8 files changed

+1404
-8
lines changed

‎doc/src/sgml/plpython.sgml

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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>

‎src/pl/plpython/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ REGRESS = \
8181
plpython_unicode\
8282
plpython_quote\
8383
plpython_composite\
84+
plpython_subtransaction\
8485
plpython_drop
8586
# where to find psql for running the tests
8687
PSQLDIR =$(bindir)

‎src/pl/plpython/expected/README

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ plpython_unicode.outserver encoding != SQL_ASCII and client encoding == UTF8;
66
plpython_unicode_0.outserver encoding != SQL_ASCII and client encoding != UTF8; else ...
77
plpython_unicode_3.outserver encoding == SQL_ASCII
88

9+
plpython_subtransaction_0.outPython 2.5 and older (without with statement)
10+
911
plpython_types_3.outPython 3.x
1012

1113
Note: Building with Python 2.2 is supported, but there are no expected

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp