1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-28 23:42:10 +03:00

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:
Peter Eisentraut
2011-02-27 17:09:56 +02:00
parent 438cdf6e48
commit 22690719ea
8 changed files with 1404 additions and 8 deletions

View File

@ -81,6 +81,7 @@ REGRESS = \
plpython_unicode \
plpython_quote \
plpython_composite \
plpython_subtransaction \
plpython_drop
# where to find psql for running the tests
PSQLDIR = $(bindir)

View File

@ -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_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
Note: Building with Python 2.2 is supported, but there are no expected

View 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;

View 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;

View File

@ -43,9 +43,9 @@ contents.sort()
return ", ".join(contents)
$$ LANGUAGE plpythonu;
select module_contents();
module_contents
---------------------------------------------------------------------------------------------------------------------------------------
Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, quote_ident, quote_literal, quote_nullable, warning
module_contents
-------------------------------------------------------------------------------------------------------------------------------------------------------
Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, quote_ident, quote_literal, quote_nullable, subtransaction, warning
(1 row)
CREATE FUNCTION elog_test() RETURNS void

View File

@ -233,6 +233,13 @@ typedef struct PLyProcedureEntry
PLyProcedure *proc;
} PLyProcedureEntry;
/* explicit subtransaction data */
typedef struct PLySubtransactionData
{
MemoryContext oldcontext;
ResourceOwner oldowner;
} PLySubtransactionData;
/* Python objects */
typedef struct PLyPlanObject
@ -254,6 +261,13 @@ typedef struct PLyResultObject
PyObject *status; /* query status, SPI_OK_*, or SPI_ERR_* */
} PLyResultObject;
typedef struct PLySubtransactionObject
{
PyObject_HEAD
bool started;
bool exited;
} PLySubtransactionObject;
/* function declarations */
@ -382,6 +396,9 @@ static HeapTuple PLyGenericObject_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *);
*/
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_safe_globals = NULL;
static HTAB *PLy_procedure_cache = NULL;
@ -401,6 +418,10 @@ static char PLy_result_doc[] = {
"Results of a PostgreSQL query"
};
static char PLy_subtransaction_doc[] = {
"PostgreSQL subtransaction context manager"
};
/*
* the function definitions
@ -1226,20 +1247,71 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
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 *
PLy_procedure_call(PLyProcedure *proc, char *kargs, PyObject *vargs)
{
PyObject *rv;
int volatile save_subxact_level = list_length(explicit_subtransactions);
PyDict_SetItemString(proc->globals, kargs, vargs);
PG_TRY();
{
#if PY_VERSION_HEX >= 0x03020000
rv = PyEval_EvalCode(proc->code,
proc->globals, proc->globals);
rv = PyEval_EvalCode(proc->code,
proc->globals, proc->globals);
#else
rv = PyEval_EvalCode((PyCodeObject *) proc->code,
proc->globals, proc->globals);
rv = PyEval_EvalCode((PyCodeObject *) proc->code,
proc->globals, proc->globals);
#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 (rv == 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_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[] = {
{"status", PLy_plan_status, METH_VARARGS, NULL},
@ -2854,6 +2932,50 @@ static PyTypeObject PLy_ResultType = {
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[] = {
/*
* logging methods
@ -2883,6 +3005,11 @@ static PyMethodDef PLy_methods[] = {
{"quote_nullable", PLy_quote_nullable, 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}
};
@ -3553,6 +3680,150 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status)
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
@ -3653,6 +3924,8 @@ _PG_init(void)
PLy_trigger_cache = hash_create("PL/Python triggers", 32, &hash_ctl,
HASH_ELEM | HASH_FUNCTION);
explicit_subtransactions = NIL;
inited = true;
}
@ -3688,6 +3961,8 @@ PLy_init_plpy(void)
elog(ERROR, "could not initialize PLy_PlanType");
if (PyType_Ready(&PLy_ResultType) < 0)
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
plpy = PyModule_Create(&PLy_module);

View 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;