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
This commit is contained in:
parent
438cdf6e48
commit
22690719ea
doc/src/sgml
src/pl/plpython
@ -955,7 +955,7 @@ $$ LANGUAGE plpythonu;
|
|||||||
|
|
||||||
</sect2>
|
</sect2>
|
||||||
|
|
||||||
<sect2>
|
<sect2 id="plpython-trapping">
|
||||||
<title>Trapping Errors</title>
|
<title>Trapping Errors</title>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -981,6 +981,136 @@ $$ LANGUAGE plpythonu;
|
|||||||
</sect2>
|
</sect2>
|
||||||
</sect1>
|
</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">
|
<sect1 id="plpython-util">
|
||||||
<title>Utility Functions</title>
|
<title>Utility Functions</title>
|
||||||
<para>
|
<para>
|
||||||
|
@ -81,6 +81,7 @@ REGRESS = \
|
|||||||
plpython_unicode \
|
plpython_unicode \
|
||||||
plpython_quote \
|
plpython_quote \
|
||||||
plpython_composite \
|
plpython_composite \
|
||||||
|
plpython_subtransaction \
|
||||||
plpython_drop
|
plpython_drop
|
||||||
# where to find psql for running the tests
|
# where to find psql for running the tests
|
||||||
PSQLDIR = $(bindir)
|
PSQLDIR = $(bindir)
|
||||||
|
@ -6,6 +6,8 @@ plpython_unicode.out server encoding != SQL_ASCII and client encoding == UTF8;
|
|||||||
plpython_unicode_0.out server encoding != SQL_ASCII and client encoding != UTF8; else ...
|
plpython_unicode_0.out server encoding != SQL_ASCII and client encoding != UTF8; else ...
|
||||||
plpython_unicode_3.out server encoding == SQL_ASCII
|
plpython_unicode_3.out server encoding == SQL_ASCII
|
||||||
|
|
||||||
|
plpython_subtransaction_0.out Python 2.5 and older (without with statement)
|
||||||
|
|
||||||
plpython_types_3.out Python 3.x
|
plpython_types_3.out Python 3.x
|
||||||
|
|
||||||
Note: Building with Python 2.2 is supported, but there are no expected
|
Note: Building with Python 2.2 is supported, but there are no expected
|
||||||
|
378
src/pl/plpython/expected/plpython_subtransaction.out
Normal file
378
src/pl/plpython/expected/plpython_subtransaction.out
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
--
|
||||||
|
-- Test explicit subtransactions
|
||||||
|
--
|
||||||
|
-- Test table to see if transactions get properly rolled back
|
||||||
|
CREATE TABLE subtransaction_tbl (
|
||||||
|
i integer
|
||||||
|
);
|
||||||
|
-- Explicit case for Python <2.6
|
||||||
|
CREATE FUNCTION subtransaction_test(what_error text = NULL) RETURNS text
|
||||||
|
AS $$
|
||||||
|
import sys
|
||||||
|
subxact = plpy.subtransaction()
|
||||||
|
subxact.__enter__()
|
||||||
|
exc = True
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
|
||||||
|
if what_error == "SPI":
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
|
||||||
|
elif what_error == "Python":
|
||||||
|
plpy.attribute_error
|
||||||
|
except:
|
||||||
|
exc = False
|
||||||
|
subxact.__exit__(*sys.exc_info())
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if exc:
|
||||||
|
subxact.__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
SELECT subtransaction_test();
|
||||||
|
subtransaction_test
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
1
|
||||||
|
2
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
SELECT subtransaction_test('SPI');
|
||||||
|
ERROR: plpy.SPIError: invalid input syntax for integer: "oops"
|
||||||
|
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
|
||||||
|
^
|
||||||
|
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
|
||||||
|
CONTEXT: PL/Python function "subtransaction_test"
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
SELECT subtransaction_test('Python');
|
||||||
|
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
|
||||||
|
CONTEXT: PL/Python function "subtransaction_test"
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
-- Context manager case for Python >=2.6
|
||||||
|
CREATE FUNCTION subtransaction_ctx_test(what_error text = NULL) RETURNS text
|
||||||
|
AS $$
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
|
||||||
|
if what_error == "SPI":
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
|
||||||
|
elif what_error == "Python":
|
||||||
|
plpy.attribute_error
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
SELECT subtransaction_ctx_test();
|
||||||
|
subtransaction_ctx_test
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
1
|
||||||
|
2
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
SELECT subtransaction_ctx_test('SPI');
|
||||||
|
ERROR: plpy.SPIError: invalid input syntax for integer: "oops"
|
||||||
|
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
|
||||||
|
^
|
||||||
|
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
|
||||||
|
CONTEXT: PL/Python function "subtransaction_ctx_test"
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
SELECT subtransaction_ctx_test('Python');
|
||||||
|
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
|
||||||
|
CONTEXT: PL/Python function "subtransaction_ctx_test"
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
-- Nested subtransactions
|
||||||
|
CREATE FUNCTION subtransaction_nested_test(swallow boolean = 'f') RETURNS text
|
||||||
|
AS $$
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
|
||||||
|
try:
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (3)")
|
||||||
|
plpy.execute("error")
|
||||||
|
except plpy.SPIError, e:
|
||||||
|
if not swallow:
|
||||||
|
raise
|
||||||
|
plpy.notice("Swallowed %r" % e)
|
||||||
|
return "ok"
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
SELECT subtransaction_nested_test();
|
||||||
|
ERROR: plpy.SPIError: syntax error at or near "error"
|
||||||
|
LINE 1: error
|
||||||
|
^
|
||||||
|
QUERY: error
|
||||||
|
CONTEXT: PL/Python function "subtransaction_nested_test"
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
SELECT subtransaction_nested_test('t');
|
||||||
|
NOTICE: Swallowed SPIError('syntax error at or near "error"',)
|
||||||
|
CONTEXT: PL/Python function "subtransaction_nested_test"
|
||||||
|
subtransaction_nested_test
|
||||||
|
----------------------------
|
||||||
|
ok
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
1
|
||||||
|
2
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
-- Nested subtransactions that recursively call code dealing with
|
||||||
|
-- subtransactions
|
||||||
|
CREATE FUNCTION subtransaction_deeply_nested_test() RETURNS text
|
||||||
|
AS $$
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
|
||||||
|
plpy.execute("SELECT subtransaction_nested_test('t')")
|
||||||
|
return "ok"
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
SELECT subtransaction_deeply_nested_test();
|
||||||
|
NOTICE: Swallowed SPIError('syntax error at or near "error"',)
|
||||||
|
CONTEXT: PL/Python function "subtransaction_nested_test"
|
||||||
|
SQL statement "SELECT subtransaction_nested_test('t')"
|
||||||
|
PL/Python function "subtransaction_nested_test"
|
||||||
|
subtransaction_deeply_nested_test
|
||||||
|
-----------------------------------
|
||||||
|
ok
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
1
|
||||||
|
2
|
||||||
|
1
|
||||||
|
2
|
||||||
|
(4 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
-- Error conditions from not opening/closing subtransactions
|
||||||
|
CREATE FUNCTION subtransaction_exit_without_enter() RETURNS void
|
||||||
|
AS $$
|
||||||
|
plpy.subtransaction().__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
CREATE FUNCTION subtransaction_enter_without_exit() RETURNS void
|
||||||
|
AS $$
|
||||||
|
plpy.subtransaction().__enter__()
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
CREATE FUNCTION subtransaction_exit_twice() RETURNS void
|
||||||
|
AS $$
|
||||||
|
plpy.subtransaction().__enter__()
|
||||||
|
plpy.subtransaction().__exit__(None, None, None)
|
||||||
|
plpy.subtransaction().__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
CREATE FUNCTION subtransaction_enter_twice() RETURNS void
|
||||||
|
AS $$
|
||||||
|
plpy.subtransaction().__enter__()
|
||||||
|
plpy.subtransaction().__enter__()
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
CREATE FUNCTION subtransaction_exit_same_subtransaction_twice() RETURNS void
|
||||||
|
AS $$
|
||||||
|
s = plpy.subtransaction()
|
||||||
|
s.__enter__()
|
||||||
|
s.__exit__(None, None, None)
|
||||||
|
s.__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
CREATE FUNCTION subtransaction_enter_same_subtransaction_twice() RETURNS void
|
||||||
|
AS $$
|
||||||
|
s = plpy.subtransaction()
|
||||||
|
s.__enter__()
|
||||||
|
s.__enter__()
|
||||||
|
s.__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
-- No warnings here, as the subtransaction gets indeed closed
|
||||||
|
CREATE FUNCTION subtransaction_enter_subtransaction_in_with() RETURNS void
|
||||||
|
AS $$
|
||||||
|
with plpy.subtransaction() as s:
|
||||||
|
s.__enter__()
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
CREATE FUNCTION subtransaction_exit_subtransaction_in_with() RETURNS void
|
||||||
|
AS $$
|
||||||
|
with plpy.subtransaction() as s:
|
||||||
|
s.__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
SELECT subtransaction_exit_without_enter();
|
||||||
|
ERROR: ValueError: this subtransaction has not been entered
|
||||||
|
CONTEXT: PL/Python function "subtransaction_exit_without_enter"
|
||||||
|
SELECT subtransaction_enter_without_exit();
|
||||||
|
WARNING: forcibly aborting a subtransaction that has not been exited
|
||||||
|
CONTEXT: PL/Python function "subtransaction_enter_without_exit"
|
||||||
|
subtransaction_enter_without_exit
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT subtransaction_exit_twice();
|
||||||
|
WARNING: forcibly aborting a subtransaction that has not been exited
|
||||||
|
CONTEXT: PL/Python function "subtransaction_exit_twice"
|
||||||
|
ERROR: ValueError: this subtransaction has not been entered
|
||||||
|
CONTEXT: PL/Python function "subtransaction_exit_twice"
|
||||||
|
SELECT subtransaction_enter_twice();
|
||||||
|
WARNING: forcibly aborting a subtransaction that has not been exited
|
||||||
|
CONTEXT: PL/Python function "subtransaction_enter_twice"
|
||||||
|
WARNING: forcibly aborting a subtransaction that has not been exited
|
||||||
|
CONTEXT: PL/Python function "subtransaction_enter_twice"
|
||||||
|
subtransaction_enter_twice
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT subtransaction_exit_same_subtransaction_twice();
|
||||||
|
ERROR: ValueError: this subtransaction has already been exited
|
||||||
|
CONTEXT: PL/Python function "subtransaction_exit_same_subtransaction_twice"
|
||||||
|
SELECT subtransaction_enter_same_subtransaction_twice();
|
||||||
|
WARNING: forcibly aborting a subtransaction that has not been exited
|
||||||
|
CONTEXT: PL/Python function "subtransaction_enter_same_subtransaction_twice"
|
||||||
|
ERROR: ValueError: this subtransaction has already been entered
|
||||||
|
CONTEXT: PL/Python function "subtransaction_enter_same_subtransaction_twice"
|
||||||
|
SELECT subtransaction_enter_subtransaction_in_with();
|
||||||
|
ERROR: ValueError: this subtransaction has already been entered
|
||||||
|
CONTEXT: PL/Python function "subtransaction_enter_subtransaction_in_with"
|
||||||
|
SELECT subtransaction_exit_subtransaction_in_with();
|
||||||
|
ERROR: ValueError: this subtransaction has already been exited
|
||||||
|
CONTEXT: PL/Python function "subtransaction_exit_subtransaction_in_with"
|
||||||
|
-- Make sure we don't get a "current transaction is aborted" error
|
||||||
|
SELECT 1 as test;
|
||||||
|
test
|
||||||
|
------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- Mix explicit subtransactions and normal SPI calls
|
||||||
|
CREATE FUNCTION subtransaction_mix_explicit_and_implicit() RETURNS void
|
||||||
|
AS $$
|
||||||
|
p = plpy.prepare("INSERT INTO subtransaction_tbl VALUES ($1)", ["integer"])
|
||||||
|
try:
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
plpy.execute(p, [2])
|
||||||
|
plpy.execute(p, ["wrong"])
|
||||||
|
except plpy.SPIError:
|
||||||
|
plpy.warning("Caught a SPI error from an explicit subtransaction")
|
||||||
|
|
||||||
|
try:
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
plpy.execute(p, [2])
|
||||||
|
plpy.execute(p, ["wrong"])
|
||||||
|
except plpy.SPIError:
|
||||||
|
plpy.warning("Caught a SPI error")
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
SELECT subtransaction_mix_explicit_and_implicit();
|
||||||
|
WARNING: Caught a SPI error from an explicit subtransaction
|
||||||
|
CONTEXT: PL/Python function "subtransaction_mix_explicit_and_implicit"
|
||||||
|
WARNING: Caught a SPI error
|
||||||
|
CONTEXT: PL/Python function "subtransaction_mix_explicit_and_implicit"
|
||||||
|
subtransaction_mix_explicit_and_implicit
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
1
|
||||||
|
2
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
-- Alternative method names for Python <2.6
|
||||||
|
CREATE FUNCTION subtransaction_alternative_names() RETURNS void
|
||||||
|
AS $$
|
||||||
|
s = plpy.subtransaction()
|
||||||
|
s.enter()
|
||||||
|
s.exit(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
SELECT subtransaction_alternative_names();
|
||||||
|
subtransaction_alternative_names
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- try/catch inside a subtransaction block
|
||||||
|
CREATE FUNCTION try_catch_inside_subtransaction() RETURNS void
|
||||||
|
AS $$
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
try:
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES ('a')")
|
||||||
|
except plpy.SPIError:
|
||||||
|
plpy.notice("caught")
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
SELECT try_catch_inside_subtransaction();
|
||||||
|
NOTICE: caught
|
||||||
|
CONTEXT: PL/Python function "try_catch_inside_subtransaction"
|
||||||
|
try_catch_inside_subtransaction
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
ALTER TABLE subtransaction_tbl ADD PRIMARY KEY (i);
|
||||||
|
NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "subtransaction_tbl_pkey" for table "subtransaction_tbl"
|
||||||
|
CREATE FUNCTION pk_violation_inside_subtransaction() RETURNS void
|
||||||
|
AS $$
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
try:
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
except plpy.SPIError:
|
||||||
|
plpy.notice("caught")
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
SELECT pk_violation_inside_subtransaction();
|
||||||
|
NOTICE: caught
|
||||||
|
CONTEXT: PL/Python function "pk_violation_inside_subtransaction"
|
||||||
|
pk_violation_inside_subtransaction
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DROP TABLE subtransaction_tbl;
|
366
src/pl/plpython/expected/plpython_subtransaction_0.out
Normal file
366
src/pl/plpython/expected/plpython_subtransaction_0.out
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
--
|
||||||
|
-- Test explicit subtransactions
|
||||||
|
--
|
||||||
|
-- Test table to see if transactions get properly rolled back
|
||||||
|
CREATE TABLE subtransaction_tbl (
|
||||||
|
i integer
|
||||||
|
);
|
||||||
|
-- Explicit case for Python <2.6
|
||||||
|
CREATE FUNCTION subtransaction_test(what_error text = NULL) RETURNS text
|
||||||
|
AS $$
|
||||||
|
import sys
|
||||||
|
subxact = plpy.subtransaction()
|
||||||
|
subxact.__enter__()
|
||||||
|
exc = True
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
|
||||||
|
if what_error == "SPI":
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
|
||||||
|
elif what_error == "Python":
|
||||||
|
plpy.attribute_error
|
||||||
|
except:
|
||||||
|
exc = False
|
||||||
|
subxact.__exit__(*sys.exc_info())
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if exc:
|
||||||
|
subxact.__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
SELECT subtransaction_test();
|
||||||
|
subtransaction_test
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
1
|
||||||
|
2
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
SELECT subtransaction_test('SPI');
|
||||||
|
ERROR: plpy.SPIError: invalid input syntax for integer: "oops"
|
||||||
|
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
|
||||||
|
^
|
||||||
|
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
|
||||||
|
CONTEXT: PL/Python function "subtransaction_test"
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
SELECT subtransaction_test('Python');
|
||||||
|
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
|
||||||
|
CONTEXT: PL/Python function "subtransaction_test"
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
-- Context manager case for Python >=2.6
|
||||||
|
CREATE FUNCTION subtransaction_ctx_test(what_error text = NULL) RETURNS text
|
||||||
|
AS $$
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
|
||||||
|
if what_error == "SPI":
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
|
||||||
|
elif what_error == "Python":
|
||||||
|
plpy.attribute_error
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
ERROR: could not compile PL/Python function "subtransaction_ctx_test"
|
||||||
|
DETAIL: SyntaxError: invalid syntax (line 3)
|
||||||
|
SELECT subtransaction_ctx_test();
|
||||||
|
ERROR: function subtransaction_ctx_test() does not exist
|
||||||
|
LINE 1: SELECT subtransaction_ctx_test();
|
||||||
|
^
|
||||||
|
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
SELECT subtransaction_ctx_test('SPI');
|
||||||
|
ERROR: function subtransaction_ctx_test(unknown) does not exist
|
||||||
|
LINE 1: SELECT subtransaction_ctx_test('SPI');
|
||||||
|
^
|
||||||
|
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
SELECT subtransaction_ctx_test('Python');
|
||||||
|
ERROR: function subtransaction_ctx_test(unknown) does not exist
|
||||||
|
LINE 1: SELECT subtransaction_ctx_test('Python');
|
||||||
|
^
|
||||||
|
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
-- Nested subtransactions
|
||||||
|
CREATE FUNCTION subtransaction_nested_test(swallow boolean = 'f') RETURNS text
|
||||||
|
AS $$
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
|
||||||
|
try:
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (3)")
|
||||||
|
plpy.execute("error")
|
||||||
|
except plpy.SPIError, e:
|
||||||
|
if not swallow:
|
||||||
|
raise
|
||||||
|
plpy.notice("Swallowed %r" % e)
|
||||||
|
return "ok"
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
ERROR: could not compile PL/Python function "subtransaction_nested_test"
|
||||||
|
DETAIL: SyntaxError: invalid syntax (line 4)
|
||||||
|
SELECT subtransaction_nested_test();
|
||||||
|
ERROR: function subtransaction_nested_test() does not exist
|
||||||
|
LINE 1: SELECT subtransaction_nested_test();
|
||||||
|
^
|
||||||
|
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
SELECT subtransaction_nested_test('t');
|
||||||
|
ERROR: function subtransaction_nested_test(unknown) does not exist
|
||||||
|
LINE 1: SELECT subtransaction_nested_test('t');
|
||||||
|
^
|
||||||
|
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
-- Nested subtransactions that recursively call code dealing with
|
||||||
|
-- subtransactions
|
||||||
|
CREATE FUNCTION subtransaction_deeply_nested_test() RETURNS text
|
||||||
|
AS $$
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
|
||||||
|
plpy.execute("SELECT subtransaction_nested_test('t')")
|
||||||
|
return "ok"
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
ERROR: could not compile PL/Python function "subtransaction_deeply_nested_test"
|
||||||
|
DETAIL: SyntaxError: invalid syntax (line 4)
|
||||||
|
SELECT subtransaction_deeply_nested_test();
|
||||||
|
ERROR: function subtransaction_deeply_nested_test() does not exist
|
||||||
|
LINE 1: SELECT subtransaction_deeply_nested_test();
|
||||||
|
^
|
||||||
|
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
-- Error conditions from not opening/closing subtransactions
|
||||||
|
CREATE FUNCTION subtransaction_exit_without_enter() RETURNS void
|
||||||
|
AS $$
|
||||||
|
plpy.subtransaction().__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
CREATE FUNCTION subtransaction_enter_without_exit() RETURNS void
|
||||||
|
AS $$
|
||||||
|
plpy.subtransaction().__enter__()
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
CREATE FUNCTION subtransaction_exit_twice() RETURNS void
|
||||||
|
AS $$
|
||||||
|
plpy.subtransaction().__enter__()
|
||||||
|
plpy.subtransaction().__exit__(None, None, None)
|
||||||
|
plpy.subtransaction().__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
CREATE FUNCTION subtransaction_enter_twice() RETURNS void
|
||||||
|
AS $$
|
||||||
|
plpy.subtransaction().__enter__()
|
||||||
|
plpy.subtransaction().__enter__()
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
CREATE FUNCTION subtransaction_exit_same_subtransaction_twice() RETURNS void
|
||||||
|
AS $$
|
||||||
|
s = plpy.subtransaction()
|
||||||
|
s.__enter__()
|
||||||
|
s.__exit__(None, None, None)
|
||||||
|
s.__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
CREATE FUNCTION subtransaction_enter_same_subtransaction_twice() RETURNS void
|
||||||
|
AS $$
|
||||||
|
s = plpy.subtransaction()
|
||||||
|
s.__enter__()
|
||||||
|
s.__enter__()
|
||||||
|
s.__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
-- No warnings here, as the subtransaction gets indeed closed
|
||||||
|
CREATE FUNCTION subtransaction_enter_subtransaction_in_with() RETURNS void
|
||||||
|
AS $$
|
||||||
|
with plpy.subtransaction() as s:
|
||||||
|
s.__enter__()
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
ERROR: could not compile PL/Python function "subtransaction_enter_subtransaction_in_with"
|
||||||
|
DETAIL: SyntaxError: invalid syntax (line 3)
|
||||||
|
CREATE FUNCTION subtransaction_exit_subtransaction_in_with() RETURNS void
|
||||||
|
AS $$
|
||||||
|
with plpy.subtransaction() as s:
|
||||||
|
s.__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
ERROR: could not compile PL/Python function "subtransaction_exit_subtransaction_in_with"
|
||||||
|
DETAIL: SyntaxError: invalid syntax (line 3)
|
||||||
|
SELECT subtransaction_exit_without_enter();
|
||||||
|
ERROR: ValueError: this subtransaction has not been entered
|
||||||
|
CONTEXT: PL/Python function "subtransaction_exit_without_enter"
|
||||||
|
SELECT subtransaction_enter_without_exit();
|
||||||
|
WARNING: forcibly aborting a subtransaction that has not been exited
|
||||||
|
CONTEXT: PL/Python function "subtransaction_enter_without_exit"
|
||||||
|
subtransaction_enter_without_exit
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT subtransaction_exit_twice();
|
||||||
|
WARNING: forcibly aborting a subtransaction that has not been exited
|
||||||
|
CONTEXT: PL/Python function "subtransaction_exit_twice"
|
||||||
|
ERROR: ValueError: this subtransaction has not been entered
|
||||||
|
CONTEXT: PL/Python function "subtransaction_exit_twice"
|
||||||
|
SELECT subtransaction_enter_twice();
|
||||||
|
WARNING: forcibly aborting a subtransaction that has not been exited
|
||||||
|
CONTEXT: PL/Python function "subtransaction_enter_twice"
|
||||||
|
WARNING: forcibly aborting a subtransaction that has not been exited
|
||||||
|
CONTEXT: PL/Python function "subtransaction_enter_twice"
|
||||||
|
subtransaction_enter_twice
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT subtransaction_exit_same_subtransaction_twice();
|
||||||
|
ERROR: ValueError: this subtransaction has already been exited
|
||||||
|
CONTEXT: PL/Python function "subtransaction_exit_same_subtransaction_twice"
|
||||||
|
SELECT subtransaction_enter_same_subtransaction_twice();
|
||||||
|
WARNING: forcibly aborting a subtransaction that has not been exited
|
||||||
|
CONTEXT: PL/Python function "subtransaction_enter_same_subtransaction_twice"
|
||||||
|
ERROR: ValueError: this subtransaction has already been entered
|
||||||
|
CONTEXT: PL/Python function "subtransaction_enter_same_subtransaction_twice"
|
||||||
|
SELECT subtransaction_enter_subtransaction_in_with();
|
||||||
|
ERROR: function subtransaction_enter_subtransaction_in_with() does not exist
|
||||||
|
LINE 1: SELECT subtransaction_enter_subtransaction_in_with();
|
||||||
|
^
|
||||||
|
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
|
||||||
|
SELECT subtransaction_exit_subtransaction_in_with();
|
||||||
|
ERROR: function subtransaction_exit_subtransaction_in_with() does not exist
|
||||||
|
LINE 1: SELECT subtransaction_exit_subtransaction_in_with();
|
||||||
|
^
|
||||||
|
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
|
||||||
|
-- Make sure we don't get a "current transaction is aborted" error
|
||||||
|
SELECT 1 as test;
|
||||||
|
test
|
||||||
|
------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- Mix explicit subtransactions and normal SPI calls
|
||||||
|
CREATE FUNCTION subtransaction_mix_explicit_and_implicit() RETURNS void
|
||||||
|
AS $$
|
||||||
|
p = plpy.prepare("INSERT INTO subtransaction_tbl VALUES ($1)", ["integer"])
|
||||||
|
try:
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
plpy.execute(p, [2])
|
||||||
|
plpy.execute(p, ["wrong"])
|
||||||
|
except plpy.SPIError:
|
||||||
|
plpy.warning("Caught a SPI error from an explicit subtransaction")
|
||||||
|
|
||||||
|
try:
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
plpy.execute(p, [2])
|
||||||
|
plpy.execute(p, ["wrong"])
|
||||||
|
except plpy.SPIError:
|
||||||
|
plpy.warning("Caught a SPI error")
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
ERROR: could not compile PL/Python function "subtransaction_mix_explicit_and_implicit"
|
||||||
|
DETAIL: SyntaxError: invalid syntax (line 5)
|
||||||
|
SELECT subtransaction_mix_explicit_and_implicit();
|
||||||
|
ERROR: function subtransaction_mix_explicit_and_implicit() does not exist
|
||||||
|
LINE 1: SELECT subtransaction_mix_explicit_and_implicit();
|
||||||
|
^
|
||||||
|
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
-- Alternative method names for Python <2.6
|
||||||
|
CREATE FUNCTION subtransaction_alternative_names() RETURNS void
|
||||||
|
AS $$
|
||||||
|
s = plpy.subtransaction()
|
||||||
|
s.enter()
|
||||||
|
s.exit(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
SELECT subtransaction_alternative_names();
|
||||||
|
subtransaction_alternative_names
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- try/catch inside a subtransaction block
|
||||||
|
CREATE FUNCTION try_catch_inside_subtransaction() RETURNS void
|
||||||
|
AS $$
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
try:
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES ('a')")
|
||||||
|
except plpy.SPIError:
|
||||||
|
plpy.notice("caught")
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
ERROR: could not compile PL/Python function "try_catch_inside_subtransaction"
|
||||||
|
DETAIL: SyntaxError: invalid syntax (line 3)
|
||||||
|
SELECT try_catch_inside_subtransaction();
|
||||||
|
ERROR: function try_catch_inside_subtransaction() does not exist
|
||||||
|
LINE 1: SELECT try_catch_inside_subtransaction();
|
||||||
|
^
|
||||||
|
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
ALTER TABLE subtransaction_tbl ADD PRIMARY KEY (i);
|
||||||
|
NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "subtransaction_tbl_pkey" for table "subtransaction_tbl"
|
||||||
|
CREATE FUNCTION pk_violation_inside_subtransaction() RETURNS void
|
||||||
|
AS $$
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
try:
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
except plpy.SPIError:
|
||||||
|
plpy.notice("caught")
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
ERROR: could not compile PL/Python function "pk_violation_inside_subtransaction"
|
||||||
|
DETAIL: SyntaxError: invalid syntax (line 3)
|
||||||
|
SELECT pk_violation_inside_subtransaction();
|
||||||
|
ERROR: function pk_violation_inside_subtransaction() does not exist
|
||||||
|
LINE 1: SELECT pk_violation_inside_subtransaction();
|
||||||
|
^
|
||||||
|
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
i
|
||||||
|
---
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
DROP TABLE subtransaction_tbl;
|
@ -44,8 +44,8 @@ return ", ".join(contents)
|
|||||||
$$ LANGUAGE plpythonu;
|
$$ LANGUAGE plpythonu;
|
||||||
select module_contents();
|
select module_contents();
|
||||||
module_contents
|
module_contents
|
||||||
---------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, quote_ident, quote_literal, quote_nullable, warning
|
Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, quote_ident, quote_literal, quote_nullable, subtransaction, warning
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
CREATE FUNCTION elog_test() RETURNS void
|
CREATE FUNCTION elog_test() RETURNS void
|
||||||
|
@ -233,6 +233,13 @@ typedef struct PLyProcedureEntry
|
|||||||
PLyProcedure *proc;
|
PLyProcedure *proc;
|
||||||
} PLyProcedureEntry;
|
} PLyProcedureEntry;
|
||||||
|
|
||||||
|
/* explicit subtransaction data */
|
||||||
|
typedef struct PLySubtransactionData
|
||||||
|
{
|
||||||
|
MemoryContext oldcontext;
|
||||||
|
ResourceOwner oldowner;
|
||||||
|
} PLySubtransactionData;
|
||||||
|
|
||||||
|
|
||||||
/* Python objects */
|
/* Python objects */
|
||||||
typedef struct PLyPlanObject
|
typedef struct PLyPlanObject
|
||||||
@ -254,6 +261,13 @@ typedef struct PLyResultObject
|
|||||||
PyObject *status; /* query status, SPI_OK_*, or SPI_ERR_* */
|
PyObject *status; /* query status, SPI_OK_*, or SPI_ERR_* */
|
||||||
} PLyResultObject;
|
} PLyResultObject;
|
||||||
|
|
||||||
|
typedef struct PLySubtransactionObject
|
||||||
|
{
|
||||||
|
PyObject_HEAD
|
||||||
|
bool started;
|
||||||
|
bool exited;
|
||||||
|
} PLySubtransactionObject;
|
||||||
|
|
||||||
|
|
||||||
/* function declarations */
|
/* function declarations */
|
||||||
|
|
||||||
@ -382,6 +396,9 @@ static HeapTuple PLyGenericObject_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *);
|
|||||||
*/
|
*/
|
||||||
static PLyProcedure *PLy_curr_procedure = NULL;
|
static PLyProcedure *PLy_curr_procedure = NULL;
|
||||||
|
|
||||||
|
/* list of explicit subtransaction data */
|
||||||
|
static List *explicit_subtransactions = NIL;
|
||||||
|
|
||||||
static PyObject *PLy_interp_globals = NULL;
|
static PyObject *PLy_interp_globals = NULL;
|
||||||
static PyObject *PLy_interp_safe_globals = NULL;
|
static PyObject *PLy_interp_safe_globals = NULL;
|
||||||
static HTAB *PLy_procedure_cache = NULL;
|
static HTAB *PLy_procedure_cache = NULL;
|
||||||
@ -401,6 +418,10 @@ static char PLy_result_doc[] = {
|
|||||||
"Results of a PostgreSQL query"
|
"Results of a PostgreSQL query"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static char PLy_subtransaction_doc[] = {
|
||||||
|
"PostgreSQL subtransaction context manager"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* the function definitions
|
* the function definitions
|
||||||
@ -1226,12 +1247,47 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
|
|||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Abort lingering subtransactions that have been explicitly started
|
||||||
|
* by plpy.subtransaction().start() and not properly closed.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
PLy_abort_open_subtransactions(int save_subxact_level)
|
||||||
|
{
|
||||||
|
Assert(save_subxact_level >= 0);
|
||||||
|
|
||||||
|
while (list_length(explicit_subtransactions) > save_subxact_level)
|
||||||
|
{
|
||||||
|
PLySubtransactionData *subtransactiondata;
|
||||||
|
|
||||||
|
Assert(explicit_subtransactions != NIL);
|
||||||
|
|
||||||
|
ereport(WARNING,
|
||||||
|
(errmsg("forcibly aborting a subtransaction that has not been exited")));
|
||||||
|
|
||||||
|
RollbackAndReleaseCurrentSubTransaction();
|
||||||
|
|
||||||
|
SPI_restore_connection();
|
||||||
|
|
||||||
|
subtransactiondata = (PLySubtransactionData *) linitial(explicit_subtransactions);
|
||||||
|
explicit_subtransactions = list_delete_first(explicit_subtransactions);
|
||||||
|
|
||||||
|
MemoryContextSwitchTo(subtransactiondata->oldcontext);
|
||||||
|
CurrentResourceOwner = subtransactiondata->oldowner;
|
||||||
|
PLy_free(subtransactiondata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
PLy_procedure_call(PLyProcedure *proc, char *kargs, PyObject *vargs)
|
PLy_procedure_call(PLyProcedure *proc, char *kargs, PyObject *vargs)
|
||||||
{
|
{
|
||||||
PyObject *rv;
|
PyObject *rv;
|
||||||
|
int volatile save_subxact_level = list_length(explicit_subtransactions);
|
||||||
|
|
||||||
PyDict_SetItemString(proc->globals, kargs, vargs);
|
PyDict_SetItemString(proc->globals, kargs, vargs);
|
||||||
|
|
||||||
|
PG_TRY();
|
||||||
|
{
|
||||||
#if PY_VERSION_HEX >= 0x03020000
|
#if PY_VERSION_HEX >= 0x03020000
|
||||||
rv = PyEval_EvalCode(proc->code,
|
rv = PyEval_EvalCode(proc->code,
|
||||||
proc->globals, proc->globals);
|
proc->globals, proc->globals);
|
||||||
@ -1240,6 +1296,22 @@ PLy_procedure_call(PLyProcedure *proc, char *kargs, PyObject *vargs)
|
|||||||
proc->globals, proc->globals);
|
proc->globals, proc->globals);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since plpy will only let you close subtransactions that
|
||||||
|
* you started, you cannot *unnest* subtransactions, only
|
||||||
|
* *nest* them without closing.
|
||||||
|
*/
|
||||||
|
Assert(list_length(explicit_subtransactions) >= save_subxact_level);
|
||||||
|
}
|
||||||
|
PG_CATCH();
|
||||||
|
{
|
||||||
|
PLy_abort_open_subtransactions(save_subxact_level);
|
||||||
|
PG_RE_THROW();
|
||||||
|
}
|
||||||
|
PG_END_TRY();
|
||||||
|
|
||||||
|
PLy_abort_open_subtransactions(save_subxact_level);
|
||||||
|
|
||||||
/* If the Python code returned an error, propagate it */
|
/* If the Python code returned an error, propagate it */
|
||||||
if (rv == NULL)
|
if (rv == NULL)
|
||||||
PLy_elog(ERROR, NULL);
|
PLy_elog(ERROR, NULL);
|
||||||
@ -2762,6 +2834,12 @@ static PyObject *PLy_quote_literal(PyObject *self, PyObject *args);
|
|||||||
static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args);
|
static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args);
|
||||||
static PyObject *PLy_quote_ident(PyObject *self, PyObject *args);
|
static PyObject *PLy_quote_ident(PyObject *self, PyObject *args);
|
||||||
|
|
||||||
|
static PyObject *PLy_subtransaction(PyObject *, PyObject *);
|
||||||
|
static PyObject *PLy_subtransaction_new(void);
|
||||||
|
static void PLy_subtransaction_dealloc(PyObject *);
|
||||||
|
static PyObject *PLy_subtransaction_enter(PyObject *, PyObject *);
|
||||||
|
static PyObject *PLy_subtransaction_exit(PyObject *, PyObject *);
|
||||||
|
|
||||||
|
|
||||||
static PyMethodDef PLy_plan_methods[] = {
|
static PyMethodDef PLy_plan_methods[] = {
|
||||||
{"status", PLy_plan_status, METH_VARARGS, NULL},
|
{"status", PLy_plan_status, METH_VARARGS, NULL},
|
||||||
@ -2854,6 +2932,50 @@ static PyTypeObject PLy_ResultType = {
|
|||||||
PLy_result_methods, /* tp_tpmethods */
|
PLy_result_methods, /* tp_tpmethods */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static PyMethodDef PLy_subtransaction_methods[] = {
|
||||||
|
{"__enter__", PLy_subtransaction_enter, METH_VARARGS, NULL},
|
||||||
|
{"__exit__", PLy_subtransaction_exit, METH_VARARGS, NULL},
|
||||||
|
/* user-friendly names for Python <2.6 */
|
||||||
|
{"enter", PLy_subtransaction_enter, METH_VARARGS, NULL},
|
||||||
|
{"exit", PLy_subtransaction_exit, METH_VARARGS, NULL},
|
||||||
|
{NULL, NULL, 0, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyTypeObject PLy_SubtransactionType = {
|
||||||
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
|
"PLySubtransaction", /* tp_name */
|
||||||
|
sizeof(PLySubtransactionObject), /* tp_size */
|
||||||
|
0, /* tp_itemsize */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* methods
|
||||||
|
*/
|
||||||
|
PLy_subtransaction_dealloc, /* tp_dealloc */
|
||||||
|
0, /* tp_print */
|
||||||
|
0, /* tp_getattr */
|
||||||
|
0, /* tp_setattr */
|
||||||
|
0, /* tp_compare */
|
||||||
|
0, /* tp_repr */
|
||||||
|
0, /* tp_as_number */
|
||||||
|
0, /* tp_as_sequence */
|
||||||
|
0, /* tp_as_mapping */
|
||||||
|
0, /* tp_hash */
|
||||||
|
0, /* tp_call */
|
||||||
|
0, /* tp_str */
|
||||||
|
0, /* tp_getattro */
|
||||||
|
0, /* tp_setattro */
|
||||||
|
0, /* tp_as_buffer */
|
||||||
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
|
||||||
|
PLy_subtransaction_doc, /* tp_doc */
|
||||||
|
0, /* tp_traverse */
|
||||||
|
0, /* tp_clear */
|
||||||
|
0, /* tp_richcompare */
|
||||||
|
0, /* tp_weaklistoffset */
|
||||||
|
0, /* tp_iter */
|
||||||
|
0, /* tp_iternext */
|
||||||
|
PLy_subtransaction_methods, /* tp_tpmethods */
|
||||||
|
};
|
||||||
|
|
||||||
static PyMethodDef PLy_methods[] = {
|
static PyMethodDef PLy_methods[] = {
|
||||||
/*
|
/*
|
||||||
* logging methods
|
* logging methods
|
||||||
@ -2883,6 +3005,11 @@ static PyMethodDef PLy_methods[] = {
|
|||||||
{"quote_nullable", PLy_quote_nullable, METH_VARARGS, NULL},
|
{"quote_nullable", PLy_quote_nullable, METH_VARARGS, NULL},
|
||||||
{"quote_ident", PLy_quote_ident, METH_VARARGS, NULL},
|
{"quote_ident", PLy_quote_ident, METH_VARARGS, NULL},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* create the subtransaction context manager
|
||||||
|
*/
|
||||||
|
{"subtransaction", PLy_subtransaction, METH_NOARGS, NULL},
|
||||||
|
|
||||||
{NULL, NULL, 0, NULL}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -3553,6 +3680,150 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status)
|
|||||||
return (PyObject *) result;
|
return (PyObject *) result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* s = plpy.subtransaction() */
|
||||||
|
static PyObject *
|
||||||
|
PLy_subtransaction(PyObject *self, PyObject *unused)
|
||||||
|
{
|
||||||
|
return PLy_subtransaction_new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate and initialize a PLySubtransactionObject */
|
||||||
|
static PyObject *
|
||||||
|
PLy_subtransaction_new(void)
|
||||||
|
{
|
||||||
|
PLySubtransactionObject *ob;
|
||||||
|
|
||||||
|
ob = PyObject_New(PLySubtransactionObject, &PLy_SubtransactionType);
|
||||||
|
|
||||||
|
if (ob == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
ob->started = false;
|
||||||
|
ob->exited = false;
|
||||||
|
|
||||||
|
return (PyObject *) ob;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Python requires a dealloc function to be defined */
|
||||||
|
static void
|
||||||
|
PLy_subtransaction_dealloc(PyObject *subxact)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* subxact.__enter__() or subxact.enter()
|
||||||
|
*
|
||||||
|
* Start an explicit subtransaction. SPI calls within an explicit
|
||||||
|
* subtransaction will not start another one, so you can atomically
|
||||||
|
* execute many SPI calls and still get a controllable exception if
|
||||||
|
* one of them fails.
|
||||||
|
*/
|
||||||
|
static PyObject *
|
||||||
|
PLy_subtransaction_enter(PyObject *self, PyObject *unused)
|
||||||
|
{
|
||||||
|
PLySubtransactionData *subxactdata;
|
||||||
|
MemoryContext oldcontext;
|
||||||
|
PLySubtransactionObject *subxact = (PLySubtransactionObject *) self;
|
||||||
|
|
||||||
|
if (subxact->started)
|
||||||
|
{
|
||||||
|
PLy_exception_set(PyExc_ValueError, "this subtransaction has already been entered");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subxact->exited)
|
||||||
|
{
|
||||||
|
PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
subxact->started = true;
|
||||||
|
oldcontext = CurrentMemoryContext;
|
||||||
|
|
||||||
|
subxactdata = PLy_malloc(sizeof(*subxactdata));
|
||||||
|
subxactdata->oldcontext = oldcontext;
|
||||||
|
subxactdata->oldowner = CurrentResourceOwner;
|
||||||
|
|
||||||
|
BeginInternalSubTransaction(NULL);
|
||||||
|
/* Do not want to leave the previous memory context */
|
||||||
|
MemoryContextSwitchTo(oldcontext);
|
||||||
|
|
||||||
|
explicit_subtransactions = lcons(subxactdata, explicit_subtransactions);
|
||||||
|
|
||||||
|
Py_INCREF(self);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* subxact.__exit__(exc_type, exc, tb) or subxact.exit(exc_type, exc, tb)
|
||||||
|
*
|
||||||
|
* Exit an explicit subtransaction. exc_type is an exception type, exc
|
||||||
|
* is the exception object, tb is the traceback. If exc_type is None,
|
||||||
|
* commit the subtransactiony, if not abort it.
|
||||||
|
*
|
||||||
|
* The method signature is chosen to allow subtransaction objects to
|
||||||
|
* be used as context managers as described in
|
||||||
|
* <http://www.python.org/dev/peps/pep-0343/>.
|
||||||
|
*/
|
||||||
|
static PyObject *
|
||||||
|
PLy_subtransaction_exit(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
PyObject *type;
|
||||||
|
PyObject *value;
|
||||||
|
PyObject *traceback;
|
||||||
|
PLySubtransactionData *subxactdata;
|
||||||
|
PLySubtransactionObject *subxact = (PLySubtransactionObject *) self;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "OOO", &type, &value, &traceback))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!subxact->started)
|
||||||
|
{
|
||||||
|
PLy_exception_set(PyExc_ValueError, "this subtransaction has not been entered");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subxact->exited)
|
||||||
|
{
|
||||||
|
PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (explicit_subtransactions == NIL)
|
||||||
|
{
|
||||||
|
PLy_exception_set(PyExc_ValueError, "there is no subtransaction to exit from");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
subxact->exited = true;
|
||||||
|
|
||||||
|
if (type != Py_None)
|
||||||
|
{
|
||||||
|
/* Abort the inner transaction */
|
||||||
|
RollbackAndReleaseCurrentSubTransaction();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ReleaseCurrentSubTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
subxactdata = (PLySubtransactionData *) linitial(explicit_subtransactions);
|
||||||
|
explicit_subtransactions = list_delete_first(explicit_subtransactions);
|
||||||
|
|
||||||
|
MemoryContextSwitchTo(subxactdata->oldcontext);
|
||||||
|
CurrentResourceOwner = subxactdata->oldowner;
|
||||||
|
PLy_free(subxactdata);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AtEOSubXact_SPI() should not have popped any SPI context, but
|
||||||
|
* just in case it did, make sure we remain connected.
|
||||||
|
*/
|
||||||
|
SPI_restore_connection();
|
||||||
|
|
||||||
|
Py_INCREF(Py_None);
|
||||||
|
return Py_None;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* language handler and interpreter initialization
|
* language handler and interpreter initialization
|
||||||
@ -3653,6 +3924,8 @@ _PG_init(void)
|
|||||||
PLy_trigger_cache = hash_create("PL/Python triggers", 32, &hash_ctl,
|
PLy_trigger_cache = hash_create("PL/Python triggers", 32, &hash_ctl,
|
||||||
HASH_ELEM | HASH_FUNCTION);
|
HASH_ELEM | HASH_FUNCTION);
|
||||||
|
|
||||||
|
explicit_subtransactions = NIL;
|
||||||
|
|
||||||
inited = true;
|
inited = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3688,6 +3961,8 @@ PLy_init_plpy(void)
|
|||||||
elog(ERROR, "could not initialize PLy_PlanType");
|
elog(ERROR, "could not initialize PLy_PlanType");
|
||||||
if (PyType_Ready(&PLy_ResultType) < 0)
|
if (PyType_Ready(&PLy_ResultType) < 0)
|
||||||
elog(ERROR, "could not initialize PLy_ResultType");
|
elog(ERROR, "could not initialize PLy_ResultType");
|
||||||
|
if (PyType_Ready(&PLy_SubtransactionType) < 0)
|
||||||
|
elog(ERROR, "could not initialize PLy_SubtransactionType");
|
||||||
|
|
||||||
#if PY_MAJOR_VERSION >= 3
|
#if PY_MAJOR_VERSION >= 3
|
||||||
plpy = PyModule_Create(&PLy_module);
|
plpy = PyModule_Create(&PLy_module);
|
||||||
|
244
src/pl/plpython/sql/plpython_subtransaction.sql
Normal file
244
src/pl/plpython/sql/plpython_subtransaction.sql
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
--
|
||||||
|
-- Test explicit subtransactions
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Test table to see if transactions get properly rolled back
|
||||||
|
|
||||||
|
CREATE TABLE subtransaction_tbl (
|
||||||
|
i integer
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Explicit case for Python <2.6
|
||||||
|
|
||||||
|
CREATE FUNCTION subtransaction_test(what_error text = NULL) RETURNS text
|
||||||
|
AS $$
|
||||||
|
import sys
|
||||||
|
subxact = plpy.subtransaction()
|
||||||
|
subxact.__enter__()
|
||||||
|
exc = True
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
|
||||||
|
if what_error == "SPI":
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
|
||||||
|
elif what_error == "Python":
|
||||||
|
plpy.attribute_error
|
||||||
|
except:
|
||||||
|
exc = False
|
||||||
|
subxact.__exit__(*sys.exc_info())
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if exc:
|
||||||
|
subxact.__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
SELECT subtransaction_test();
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
SELECT subtransaction_test('SPI');
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
SELECT subtransaction_test('Python');
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
|
||||||
|
-- Context manager case for Python >=2.6
|
||||||
|
|
||||||
|
CREATE FUNCTION subtransaction_ctx_test(what_error text = NULL) RETURNS text
|
||||||
|
AS $$
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
|
||||||
|
if what_error == "SPI":
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
|
||||||
|
elif what_error == "Python":
|
||||||
|
plpy.attribute_error
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
SELECT subtransaction_ctx_test();
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
SELECT subtransaction_ctx_test('SPI');
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
SELECT subtransaction_ctx_test('Python');
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
|
||||||
|
-- Nested subtransactions
|
||||||
|
|
||||||
|
CREATE FUNCTION subtransaction_nested_test(swallow boolean = 'f') RETURNS text
|
||||||
|
AS $$
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
|
||||||
|
try:
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (3)")
|
||||||
|
plpy.execute("error")
|
||||||
|
except plpy.SPIError, e:
|
||||||
|
if not swallow:
|
||||||
|
raise
|
||||||
|
plpy.notice("Swallowed %r" % e)
|
||||||
|
return "ok"
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
SELECT subtransaction_nested_test();
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
|
||||||
|
SELECT subtransaction_nested_test('t');
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
|
||||||
|
-- Nested subtransactions that recursively call code dealing with
|
||||||
|
-- subtransactions
|
||||||
|
|
||||||
|
CREATE FUNCTION subtransaction_deeply_nested_test() RETURNS text
|
||||||
|
AS $$
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
|
||||||
|
plpy.execute("SELECT subtransaction_nested_test('t')")
|
||||||
|
return "ok"
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
SELECT subtransaction_deeply_nested_test();
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
|
||||||
|
-- Error conditions from not opening/closing subtransactions
|
||||||
|
|
||||||
|
CREATE FUNCTION subtransaction_exit_without_enter() RETURNS void
|
||||||
|
AS $$
|
||||||
|
plpy.subtransaction().__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
CREATE FUNCTION subtransaction_enter_without_exit() RETURNS void
|
||||||
|
AS $$
|
||||||
|
plpy.subtransaction().__enter__()
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
CREATE FUNCTION subtransaction_exit_twice() RETURNS void
|
||||||
|
AS $$
|
||||||
|
plpy.subtransaction().__enter__()
|
||||||
|
plpy.subtransaction().__exit__(None, None, None)
|
||||||
|
plpy.subtransaction().__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
CREATE FUNCTION subtransaction_enter_twice() RETURNS void
|
||||||
|
AS $$
|
||||||
|
plpy.subtransaction().__enter__()
|
||||||
|
plpy.subtransaction().__enter__()
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
CREATE FUNCTION subtransaction_exit_same_subtransaction_twice() RETURNS void
|
||||||
|
AS $$
|
||||||
|
s = plpy.subtransaction()
|
||||||
|
s.__enter__()
|
||||||
|
s.__exit__(None, None, None)
|
||||||
|
s.__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
CREATE FUNCTION subtransaction_enter_same_subtransaction_twice() RETURNS void
|
||||||
|
AS $$
|
||||||
|
s = plpy.subtransaction()
|
||||||
|
s.__enter__()
|
||||||
|
s.__enter__()
|
||||||
|
s.__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
-- No warnings here, as the subtransaction gets indeed closed
|
||||||
|
CREATE FUNCTION subtransaction_enter_subtransaction_in_with() RETURNS void
|
||||||
|
AS $$
|
||||||
|
with plpy.subtransaction() as s:
|
||||||
|
s.__enter__()
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
CREATE FUNCTION subtransaction_exit_subtransaction_in_with() RETURNS void
|
||||||
|
AS $$
|
||||||
|
with plpy.subtransaction() as s:
|
||||||
|
s.__exit__(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
SELECT subtransaction_exit_without_enter();
|
||||||
|
SELECT subtransaction_enter_without_exit();
|
||||||
|
SELECT subtransaction_exit_twice();
|
||||||
|
SELECT subtransaction_enter_twice();
|
||||||
|
SELECT subtransaction_exit_same_subtransaction_twice();
|
||||||
|
SELECT subtransaction_enter_same_subtransaction_twice();
|
||||||
|
SELECT subtransaction_enter_subtransaction_in_with();
|
||||||
|
SELECT subtransaction_exit_subtransaction_in_with();
|
||||||
|
|
||||||
|
-- Make sure we don't get a "current transaction is aborted" error
|
||||||
|
SELECT 1 as test;
|
||||||
|
|
||||||
|
-- Mix explicit subtransactions and normal SPI calls
|
||||||
|
|
||||||
|
CREATE FUNCTION subtransaction_mix_explicit_and_implicit() RETURNS void
|
||||||
|
AS $$
|
||||||
|
p = plpy.prepare("INSERT INTO subtransaction_tbl VALUES ($1)", ["integer"])
|
||||||
|
try:
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
plpy.execute(p, [2])
|
||||||
|
plpy.execute(p, ["wrong"])
|
||||||
|
except plpy.SPIError:
|
||||||
|
plpy.warning("Caught a SPI error from an explicit subtransaction")
|
||||||
|
|
||||||
|
try:
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
plpy.execute(p, [2])
|
||||||
|
plpy.execute(p, ["wrong"])
|
||||||
|
except plpy.SPIError:
|
||||||
|
plpy.warning("Caught a SPI error")
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
SELECT subtransaction_mix_explicit_and_implicit();
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
|
||||||
|
-- Alternative method names for Python <2.6
|
||||||
|
|
||||||
|
CREATE FUNCTION subtransaction_alternative_names() RETURNS void
|
||||||
|
AS $$
|
||||||
|
s = plpy.subtransaction()
|
||||||
|
s.enter()
|
||||||
|
s.exit(None, None, None)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
SELECT subtransaction_alternative_names();
|
||||||
|
|
||||||
|
-- try/catch inside a subtransaction block
|
||||||
|
|
||||||
|
CREATE FUNCTION try_catch_inside_subtransaction() RETURNS void
|
||||||
|
AS $$
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
try:
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES ('a')")
|
||||||
|
except plpy.SPIError:
|
||||||
|
plpy.notice("caught")
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
SELECT try_catch_inside_subtransaction();
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
TRUNCATE subtransaction_tbl;
|
||||||
|
|
||||||
|
ALTER TABLE subtransaction_tbl ADD PRIMARY KEY (i);
|
||||||
|
|
||||||
|
CREATE FUNCTION pk_violation_inside_subtransaction() RETURNS void
|
||||||
|
AS $$
|
||||||
|
with plpy.subtransaction():
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
try:
|
||||||
|
plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
|
||||||
|
except plpy.SPIError:
|
||||||
|
plpy.notice("caught")
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
SELECT pk_violation_inside_subtransaction();
|
||||||
|
SELECT * FROM subtransaction_tbl;
|
||||||
|
|
||||||
|
DROP TABLE subtransaction_tbl;
|
Loading…
x
Reference in New Issue
Block a user