pl/python tracebacks v2
I finally got around to updating the PL/Python tracebacks patch. The
other day I was writing some very simple PL/Python code and the lack of
tracebacks is extremely annoying.
Getting a TypeError without any line information in a function with 30
lines because there's a %d instead of a %s somewhere in a logging call
can make your debugging experience very lousy.
Attached is a patch against master, with simplified error forming logic
and the traceback in the context field. If you look at the expected
regression test output, you will see that the only thing that changes is
the presense of tracebacks in context messages.
I'll update the commitfest app for the 2011-Next commitfest, but if
someone would like to pick this up and include it in the 9.1 PL/Python
revamp pack, I'd be thrilled.
Cheers,
Jan
Attachments:
plpython-tracebacks-v2.difftext/x-patch; name=plpython-tracebacks-v2.diffDownload
diff --git a/src/pl/plpython/expected/plpython_do.out b/src/pl/plpython/expected/plpython_do.out
index a21b088..41b7a51 100644
*** a/src/pl/plpython/expected/plpython_do.out
--- b/src/pl/plpython/expected/plpython_do.out
*************** NOTICE: This is plpythonu.
*** 3,6 ****
CONTEXT: PL/Python anonymous code block
DO $$ nonsense $$ LANGUAGE plpythonu;
ERROR: NameError: global name 'nonsense' is not defined
! CONTEXT: PL/Python anonymous code block
--- 3,9 ----
CONTEXT: PL/Python anonymous code block
DO $$ nonsense $$ LANGUAGE plpythonu;
ERROR: NameError: global name 'nonsense' is not defined
! CONTEXT: Traceback (most recent call last):
! PL/Python anonymous code block, line 1, in <module>
! nonsense
! PL/Python anonymous code block
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
index e38ea60..0b7d87f 100644
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** ERROR: spiexceptions.SyntaxError: synta
*** 36,42 ****
LINE 1: syntax error
^
QUERY: syntax error
! CONTEXT: PL/Python function "sql_syntax_error"
/* check the handling of uncaught python exceptions
*/
CREATE FUNCTION exception_index_invalid(text) RETURNS text
--- 36,45 ----
LINE 1: syntax error
^
QUERY: syntax error
! CONTEXT: Traceback (most recent call last):
! PL/Python function "sql_syntax_error", line 1, in <module>
! plpy.execute("syntax error")
! PL/Python function "sql_syntax_error"
/* check the handling of uncaught python exceptions
*/
CREATE FUNCTION exception_index_invalid(text) RETURNS text
*************** CREATE FUNCTION exception_index_invalid(
*** 45,51 ****
LANGUAGE plpythonu;
SELECT exception_index_invalid('test');
ERROR: IndexError: list index out of range
! CONTEXT: PL/Python function "exception_index_invalid"
/* check handling of nested exceptions
*/
CREATE FUNCTION exception_index_invalid_nested() RETURNS text
--- 48,57 ----
LANGUAGE plpythonu;
SELECT exception_index_invalid('test');
ERROR: IndexError: list index out of range
! CONTEXT: Traceback (most recent call last):
! PL/Python function "exception_index_invalid", line 1, in <module>
! return args[1]
! PL/Python function "exception_index_invalid"
/* check handling of nested exceptions
*/
CREATE FUNCTION exception_index_invalid_nested() RETURNS text
*************** LINE 1: SELECT test5('foo')
*** 59,65 ****
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
QUERY: SELECT test5('foo')
! CONTEXT: PL/Python function "exception_index_invalid_nested"
/* a typo
*/
CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text
--- 65,74 ----
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
QUERY: SELECT test5('foo')
! CONTEXT: Traceback (most recent call last):
! PL/Python function "exception_index_invalid_nested", line 1, in <module>
! rv = plpy.execute("SELECT test5('foo')")
! PL/Python function "exception_index_invalid_nested"
/* a typo
*/
CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text
*************** return None
*** 75,81 ****
LANGUAGE plpythonu;
SELECT invalid_type_uncaught('rick');
ERROR: spiexceptions.UndefinedObject: type "test" does not exist
! CONTEXT: PL/Python function "invalid_type_uncaught"
/* for what it's worth catch the exception generated by
* the typo, and return None
*/
--- 84,93 ----
LANGUAGE plpythonu;
SELECT invalid_type_uncaught('rick');
ERROR: spiexceptions.UndefinedObject: type "test" does not exist
! CONTEXT: Traceback (most recent call last):
! PL/Python function "invalid_type_uncaught", line 3, in <module>
! SD["plan"] = plpy.prepare(q, [ "test" ])
! PL/Python function "invalid_type_uncaught"
/* for what it's worth catch the exception generated by
* the typo, and return None
*/
*************** return None
*** 121,127 ****
LANGUAGE plpythonu;
SELECT invalid_type_reraised('rick');
ERROR: plpy.Error: type "test" does not exist
! CONTEXT: PL/Python function "invalid_type_reraised"
/* no typo no messing about
*/
CREATE FUNCTION valid_type(a text) RETURNS text
--- 133,142 ----
LANGUAGE plpythonu;
SELECT invalid_type_reraised('rick');
ERROR: plpy.Error: type "test" does not exist
! CONTEXT: Traceback (most recent call last):
! PL/Python function "invalid_type_reraised", line 6, in <module>
! plpy.error(str(ex))
! PL/Python function "invalid_type_reraised"
/* no typo no messing about
*/
CREATE FUNCTION valid_type(a text) RETURNS text
*************** SELECT valid_type('rick');
*** 140,145 ****
--- 155,318 ----
(1 row)
+ /* error in nested functions to get a traceback
+ */
+ CREATE FUNCTION nested_error() RETURNS text
+ AS
+ 'def fun1():
+ plpy.error("boom")
+
+ def fun2():
+ fun1()
+
+ def fun3():
+ fun2()
+
+ fun3()
+ return "not reached"
+ '
+ LANGUAGE plpythonu;
+ SELECT nested_error();
+ ERROR: plpy.Error: boom
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "nested_error", line 10, in <module>
+ fun3()
+ PL/Python function "nested_error", line 8, in fun3
+ fun2()
+ PL/Python function "nested_error", line 5, in fun2
+ fun1()
+ PL/Python function "nested_error", line 2, in fun1
+ plpy.error("boom")
+ PL/Python function "nested_error"
+ /* raising plpy.Error is just like calling plpy.error
+ */
+ CREATE FUNCTION nested_error_raise() RETURNS text
+ AS
+ 'def fun1():
+ raise plpy.Error("boom")
+
+ def fun2():
+ fun1()
+
+ def fun3():
+ fun2()
+
+ fun3()
+ return "not reached"
+ '
+ LANGUAGE plpythonu;
+ SELECT nested_error_raise();
+ ERROR: plpy.Error: boom
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "nested_error_raise", line 10, in <module>
+ fun3()
+ PL/Python function "nested_error_raise", line 8, in fun3
+ fun2()
+ PL/Python function "nested_error_raise", line 5, in fun2
+ fun1()
+ PL/Python function "nested_error_raise", line 2, in fun1
+ raise plpy.Error("boom")
+ PL/Python function "nested_error_raise"
+ /* using plpy.warning should not produce a traceback
+ */
+ CREATE FUNCTION nested_warning() RETURNS text
+ AS
+ 'def fun1():
+ plpy.warning("boom")
+
+ def fun2():
+ fun1()
+
+ def fun3():
+ fun2()
+
+ fun3()
+ return "you''ve been warned"
+ '
+ LANGUAGE plpythonu;
+ SELECT nested_warning();
+ WARNING: boom
+ CONTEXT: PL/Python function "nested_warning"
+ nested_warning
+ --------------------
+ you've been warned
+ (1 row)
+
+ /* AttirbuteError at toplevel used to give segfaults with the traceback
+ */
+ CREATE FUNCTION toplevel_attribute_error() RETURNS void AS
+ $$
+ plpy.nonexistent
+ $$ LANGUAGE plpythonu;
+ SELECT toplevel_attribute_error();
+ ERROR: AttributeError: 'module' object has no attribute 'nonexistent'
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "toplevel_attribute_error", line 2, in <module>
+ plpy.nonexistent
+ PL/Python function "toplevel_attribute_error"
+ /* Calling PL/Python functions from SQL and vice versa should not lose context.
+ */
+ CREATE OR REPLACE FUNCTION python_traceback() RETURNS void AS $$
+ def first():
+ second()
+
+ def second():
+ third()
+
+ def third():
+ plpy.execute("select sql_error()")
+
+ first()
+ $$ LANGUAGE plpythonu;
+ CREATE OR REPLACE FUNCTION sql_error() RETURNS void AS $$
+ begin
+ select 1/0;
+ end
+ $$ LANGUAGE plpgsql;
+ CREATE OR REPLACE FUNCTION python_from_sql_error() RETURNS void AS $$
+ begin
+ select python_traceback();
+ end
+ $$ LANGUAGE plpgsql;
+ CREATE OR REPLACE FUNCTION sql_from_python_error() RETURNS void AS $$
+ plpy.execute("select sql_error()")
+ $$ LANGUAGE plpythonu;
+ SELECT python_traceback();
+ ERROR: spiexceptions.DivisionByZero: division by zero
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "python_traceback", line 11, in <module>
+ first()
+ PL/Python function "python_traceback", line 3, in first
+ second()
+ PL/Python function "python_traceback", line 6, in second
+ third()
+ PL/Python function "python_traceback", line 9, in third
+ plpy.execute("select sql_error()")
+ PL/Python function "python_traceback"
+ SELECT sql_error();
+ ERROR: division by zero
+ CONTEXT: SQL statement "select 1/0"
+ PL/pgSQL function "sql_error" line 3 at SQL statement
+ SELECT python_from_sql_error();
+ ERROR: spiexceptions.DivisionByZero: division by zero
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "python_traceback", line 11, in <module>
+ first()
+ PL/Python function "python_traceback", line 3, in first
+ second()
+ PL/Python function "python_traceback", line 6, in second
+ third()
+ PL/Python function "python_traceback", line 9, in third
+ plpy.execute("select sql_error()")
+ PL/Python function "python_traceback"
+ SQL statement "select python_traceback()"
+ PL/pgSQL function "python_from_sql_error" line 3 at SQL statement
+ SELECT sql_from_python_error();
+ ERROR: spiexceptions.DivisionByZero: division by zero
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "sql_from_python_error", line 2, in <module>
+ plpy.execute("select sql_error()")
+ PL/Python function "sql_from_python_error"
/* check catching specific types of exceptions
*/
CREATE TABLE specific (
*************** plpy.execute("rollback to save")
*** 187,193 ****
$$ LANGUAGE plpythonu;
SELECT manual_subxact();
ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
! CONTEXT: PL/Python function "manual_subxact"
/* same for prepared plans
*/
CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$
--- 360,369 ----
$$ LANGUAGE plpythonu;
SELECT manual_subxact();
ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
! CONTEXT: Traceback (most recent call last):
! PL/Python function "manual_subxact", line 2, in <module>
! plpy.execute("savepoint save")
! PL/Python function "manual_subxact"
/* same for prepared plans
*/
CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$
*************** plpy.execute(rollback)
*** 199,202 ****
$$ LANGUAGE plpythonu;
SELECT manual_subxact_prepared();
ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
! CONTEXT: PL/Python function "manual_subxact_prepared"
--- 375,381 ----
$$ LANGUAGE plpythonu;
SELECT manual_subxact_prepared();
ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
! CONTEXT: Traceback (most recent call last):
! PL/Python function "manual_subxact_prepared", line 4, in <module>
! plpy.execute(save)
! PL/Python function "manual_subxact_prepared"
diff --git a/src/pl/plpython/expected/plpython_error_0.out b/src/pl/plpython/expected/plpython_error_0.out
index 1b65d35..ab1566f 100644
*** a/src/pl/plpython/expected/plpython_error_0.out
--- b/src/pl/plpython/expected/plpython_error_0.out
*************** ERROR: spiexceptions.SyntaxError: synta
*** 36,42 ****
LINE 1: syntax error
^
QUERY: syntax error
! CONTEXT: PL/Python function "sql_syntax_error"
/* check the handling of uncaught python exceptions
*/
CREATE FUNCTION exception_index_invalid(text) RETURNS text
--- 36,45 ----
LINE 1: syntax error
^
QUERY: syntax error
! CONTEXT: Traceback (most recent call last):
! PL/Python function "sql_syntax_error", line 1, in <module>
! plpy.execute("syntax error")
! PL/Python function "sql_syntax_error"
/* check the handling of uncaught python exceptions
*/
CREATE FUNCTION exception_index_invalid(text) RETURNS text
*************** CREATE FUNCTION exception_index_invalid(
*** 45,51 ****
LANGUAGE plpythonu;
SELECT exception_index_invalid('test');
ERROR: IndexError: list index out of range
! CONTEXT: PL/Python function "exception_index_invalid"
/* check handling of nested exceptions
*/
CREATE FUNCTION exception_index_invalid_nested() RETURNS text
--- 48,57 ----
LANGUAGE plpythonu;
SELECT exception_index_invalid('test');
ERROR: IndexError: list index out of range
! CONTEXT: Traceback (most recent call last):
! PL/Python function "exception_index_invalid", line 1, in <module>
! return args[1]
! PL/Python function "exception_index_invalid"
/* check handling of nested exceptions
*/
CREATE FUNCTION exception_index_invalid_nested() RETURNS text
*************** LINE 1: SELECT test5('foo')
*** 59,65 ****
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
QUERY: SELECT test5('foo')
! CONTEXT: PL/Python function "exception_index_invalid_nested"
/* a typo
*/
CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text
--- 65,74 ----
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
QUERY: SELECT test5('foo')
! CONTEXT: Traceback (most recent call last):
! PL/Python function "exception_index_invalid_nested", line 1, in <module>
! rv = plpy.execute("SELECT test5('foo')")
! PL/Python function "exception_index_invalid_nested"
/* a typo
*/
CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text
*************** return None
*** 75,81 ****
LANGUAGE plpythonu;
SELECT invalid_type_uncaught('rick');
ERROR: spiexceptions.UndefinedObject: type "test" does not exist
! CONTEXT: PL/Python function "invalid_type_uncaught"
/* for what it's worth catch the exception generated by
* the typo, and return None
*/
--- 84,93 ----
LANGUAGE plpythonu;
SELECT invalid_type_uncaught('rick');
ERROR: spiexceptions.UndefinedObject: type "test" does not exist
! CONTEXT: Traceback (most recent call last):
! PL/Python function "invalid_type_uncaught", line 3, in <module>
! SD["plan"] = plpy.prepare(q, [ "test" ])
! PL/Python function "invalid_type_uncaught"
/* for what it's worth catch the exception generated by
* the typo, and return None
*/
*************** return None
*** 121,127 ****
LANGUAGE plpythonu;
SELECT invalid_type_reraised('rick');
ERROR: plpy.Error: type "test" does not exist
! CONTEXT: PL/Python function "invalid_type_reraised"
/* no typo no messing about
*/
CREATE FUNCTION valid_type(a text) RETURNS text
--- 133,142 ----
LANGUAGE plpythonu;
SELECT invalid_type_reraised('rick');
ERROR: plpy.Error: type "test" does not exist
! CONTEXT: Traceback (most recent call last):
! PL/Python function "invalid_type_reraised", line 6, in <module>
! plpy.error(str(ex))
! PL/Python function "invalid_type_reraised"
/* no typo no messing about
*/
CREATE FUNCTION valid_type(a text) RETURNS text
*************** SELECT valid_type('rick');
*** 140,145 ****
--- 155,318 ----
(1 row)
+ /* error in nested functions to get a traceback
+ */
+ CREATE FUNCTION nested_error() RETURNS text
+ AS
+ 'def fun1():
+ plpy.error("boom")
+
+ def fun2():
+ fun1()
+
+ def fun3():
+ fun2()
+
+ fun3()
+ return "not reached"
+ '
+ LANGUAGE plpythonu;
+ SELECT nested_error();
+ ERROR: plpy.Error: boom
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "nested_error", line 10, in <module>
+ fun3()
+ PL/Python function "nested_error", line 8, in fun3
+ fun2()
+ PL/Python function "nested_error", line 5, in fun2
+ fun1()
+ PL/Python function "nested_error", line 2, in fun1
+ plpy.error("boom")
+ PL/Python function "nested_error"
+ /* raising plpy.Error is just like calling plpy.error
+ */
+ CREATE FUNCTION nested_error_raise() RETURNS text
+ AS
+ 'def fun1():
+ raise plpy.Error("boom")
+
+ def fun2():
+ fun1()
+
+ def fun3():
+ fun2()
+
+ fun3()
+ return "not reached"
+ '
+ LANGUAGE plpythonu;
+ SELECT nested_error_raise();
+ ERROR: plpy.Error: boom
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "nested_error_raise", line 10, in <module>
+ fun3()
+ PL/Python function "nested_error_raise", line 8, in fun3
+ fun2()
+ PL/Python function "nested_error_raise", line 5, in fun2
+ fun1()
+ PL/Python function "nested_error_raise", line 2, in fun1
+ raise plpy.Error("boom")
+ PL/Python function "nested_error_raise"
+ /* using plpy.warning should not produce a traceback
+ */
+ CREATE FUNCTION nested_warning() RETURNS text
+ AS
+ 'def fun1():
+ plpy.warning("boom")
+
+ def fun2():
+ fun1()
+
+ def fun3():
+ fun2()
+
+ fun3()
+ return "you''ve been warned"
+ '
+ LANGUAGE plpythonu;
+ SELECT nested_warning();
+ WARNING: boom
+ CONTEXT: PL/Python function "nested_warning"
+ nested_warning
+ --------------------
+ you've been warned
+ (1 row)
+
+ /* AttirbuteError at toplevel used to give segfaults with the traceback
+ */
+ CREATE FUNCTION toplevel_attribute_error() RETURNS void AS
+ $$
+ plpy.nonexistent
+ $$ LANGUAGE plpythonu;
+ SELECT toplevel_attribute_error();
+ ERROR: AttributeError: 'module' object has no attribute 'nonexistent'
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "toplevel_attribute_error", line 2, in <module>
+ plpy.nonexistent
+ PL/Python function "toplevel_attribute_error"
+ /* Calling PL/Python functions from SQL and vice versa should not lose context.
+ */
+ CREATE OR REPLACE FUNCTION python_traceback() RETURNS void AS $$
+ def first():
+ second()
+
+ def second():
+ third()
+
+ def third():
+ plpy.execute("select sql_error()")
+
+ first()
+ $$ LANGUAGE plpythonu;
+ CREATE OR REPLACE FUNCTION sql_error() RETURNS void AS $$
+ begin
+ select 1/0;
+ end
+ $$ LANGUAGE plpgsql;
+ CREATE OR REPLACE FUNCTION python_from_sql_error() RETURNS void AS $$
+ begin
+ select python_traceback();
+ end
+ $$ LANGUAGE plpgsql;
+ CREATE OR REPLACE FUNCTION sql_from_python_error() RETURNS void AS $$
+ plpy.execute("select sql_error()")
+ $$ LANGUAGE plpythonu;
+ SELECT python_traceback();
+ ERROR: spiexceptions.DivisionByZero: division by zero
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "python_traceback", line 11, in <module>
+ first()
+ PL/Python function "python_traceback", line 3, in first
+ second()
+ PL/Python function "python_traceback", line 6, in second
+ third()
+ PL/Python function "python_traceback", line 9, in third
+ plpy.execute("select sql_error()")
+ PL/Python function "python_traceback"
+ SELECT sql_error();
+ ERROR: division by zero
+ CONTEXT: SQL statement "select 1/0"
+ PL/pgSQL function "sql_error" line 3 at SQL statement
+ SELECT python_from_sql_error();
+ ERROR: spiexceptions.DivisionByZero: division by zero
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "python_traceback", line 11, in <module>
+ first()
+ PL/Python function "python_traceback", line 3, in first
+ second()
+ PL/Python function "python_traceback", line 6, in second
+ third()
+ PL/Python function "python_traceback", line 9, in third
+ plpy.execute("select sql_error()")
+ PL/Python function "python_traceback"
+ SQL statement "select python_traceback()"
+ PL/pgSQL function "python_from_sql_error" line 3 at SQL statement
+ SELECT sql_from_python_error();
+ ERROR: spiexceptions.DivisionByZero: division by zero
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "sql_from_python_error", line 2, in <module>
+ plpy.execute("select sql_error()")
+ PL/Python function "sql_from_python_error"
/* check catching specific types of exceptions
*/
CREATE TABLE specific (
*************** plpy.execute("rollback to save")
*** 187,193 ****
$$ LANGUAGE plpythonu;
SELECT manual_subxact();
ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
! CONTEXT: PL/Python function "manual_subxact"
/* same for prepared plans
*/
CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$
--- 360,369 ----
$$ LANGUAGE plpythonu;
SELECT manual_subxact();
ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
! CONTEXT: Traceback (most recent call last):
! PL/Python function "manual_subxact", line 2, in <module>
! plpy.execute("savepoint save")
! PL/Python function "manual_subxact"
/* same for prepared plans
*/
CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$
*************** plpy.execute(rollback)
*** 199,202 ****
$$ LANGUAGE plpythonu;
SELECT manual_subxact_prepared();
ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
! CONTEXT: PL/Python function "manual_subxact_prepared"
--- 375,381 ----
$$ LANGUAGE plpythonu;
SELECT manual_subxact_prepared();
ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
! CONTEXT: Traceback (most recent call last):
! PL/Python function "manual_subxact_prepared", line 4, in <module>
! plpy.execute(save)
! PL/Python function "manual_subxact_prepared"
diff --git a/src/pl/plpython/expected/plpython_subtransaction.out b/src/pl/plpython/expected/plpython_subtransaction.out
index 50d97fa..515b0bb 100644
*** a/src/pl/plpython/expected/plpython_subtransaction.out
--- b/src/pl/plpython/expected/plpython_subtransaction.out
*************** ERROR: spiexceptions.InvalidTextReprese
*** 47,53 ****
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
---
--- 47,56 ----
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_test", line 11, in <module>
! plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
! PL/Python function "subtransaction_test"
SELECT * FROM subtransaction_tbl;
i
---
*************** SELECT * FROM subtransaction_tbl;
*** 56,62 ****
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
---
--- 59,68 ----
TRUNCATE subtransaction_tbl;
SELECT subtransaction_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_test", line 13, in <module>
! plpy.attribute_error
! PL/Python function "subtransaction_test"
SELECT * FROM subtransaction_tbl;
i
---
*************** ERROR: spiexceptions.InvalidTextReprese
*** 93,99 ****
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
---
--- 99,108 ----
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_ctx_test", line 6, in <module>
! plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
! PL/Python function "subtransaction_ctx_test"
SELECT * FROM subtransaction_tbl;
i
---
*************** SELECT * FROM subtransaction_tbl;
*** 102,108 ****
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
---
--- 111,120 ----
TRUNCATE subtransaction_tbl;
SELECT subtransaction_ctx_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_ctx_test", line 8, in <module>
! plpy.attribute_error
! PL/Python function "subtransaction_ctx_test"
SELECT * FROM subtransaction_tbl;
i
---
*************** ERROR: spiexceptions.SyntaxError: synta
*** 130,136 ****
LINE 1: error
^
QUERY: error
! CONTEXT: PL/Python function "subtransaction_nested_test"
SELECT * FROM subtransaction_tbl;
i
---
--- 142,151 ----
LINE 1: error
^
QUERY: error
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_nested_test", line 8, in <module>
! plpy.execute("error")
! PL/Python function "subtransaction_nested_test"
SELECT * FROM subtransaction_tbl;
i
---
*************** with plpy.subtransaction() as s:
*** 230,236 ****
$$ 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"
--- 245,254 ----
$$ LANGUAGE plpythonu;
SELECT subtransaction_exit_without_enter();
ERROR: ValueError: this subtransaction has not been entered
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_exit_without_enter", line 2, in <module>
! plpy.subtransaction().__exit__(None, None, None)
! 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"
*************** SELECT subtransaction_exit_twice();
*** 243,249 ****
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"
--- 261,270 ----
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: Traceback (most recent call last):
! PL/Python function "subtransaction_exit_twice", line 3, in <module>
! plpy.subtransaction().__exit__(None, None, None)
! 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"
*************** CONTEXT: PL/Python function "subtransac
*** 256,273 ****
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
--- 277,306 ----
SELECT subtransaction_exit_same_subtransaction_twice();
ERROR: ValueError: this subtransaction has already been exited
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_exit_same_subtransaction_twice", line 5, in <module>
! s.__exit__(None, None, None)
! 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: Traceback (most recent call last):
! PL/Python function "subtransaction_enter_same_subtransaction_twice", line 4, in <module>
! s.__enter__()
! PL/Python function "subtransaction_enter_same_subtransaction_twice"
SELECT subtransaction_enter_subtransaction_in_with();
ERROR: ValueError: this subtransaction has already been entered
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_enter_subtransaction_in_with", line 3, in <module>
! s.__enter__()
! PL/Python function "subtransaction_enter_subtransaction_in_with"
SELECT subtransaction_exit_subtransaction_in_with();
ERROR: ValueError: this subtransaction has already been exited
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_exit_subtransaction_in_with", line 3, in <module>
! s.__exit__(None, None, None)
! 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
diff --git a/src/pl/plpython/expected/plpython_subtransaction_0.out b/src/pl/plpython/expected/plpython_subtransaction_0.out
index 164e987..4017c41 100644
*** a/src/pl/plpython/expected/plpython_subtransaction_0.out
--- b/src/pl/plpython/expected/plpython_subtransaction_0.out
*************** ERROR: spiexceptions.InvalidTextReprese
*** 47,53 ****
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
---
--- 47,56 ----
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_test", line 11, in <module>
! plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
! PL/Python function "subtransaction_test"
SELECT * FROM subtransaction_tbl;
i
---
*************** SELECT * FROM subtransaction_tbl;
*** 56,62 ****
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
---
--- 59,68 ----
TRUNCATE subtransaction_tbl;
SELECT subtransaction_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_test", line 13, in <module>
! plpy.attribute_error
! PL/Python function "subtransaction_test"
SELECT * FROM subtransaction_tbl;
i
---
*************** ERROR: could not compile PL/Python func
*** 223,229 ****
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"
--- 229,238 ----
DETAIL: SyntaxError: invalid syntax (line 3)
SELECT subtransaction_exit_without_enter();
ERROR: ValueError: this subtransaction has not been entered
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_exit_without_enter", line 2, in <module>
! plpy.subtransaction().__exit__(None, None, None)
! 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"
*************** SELECT subtransaction_exit_twice();
*** 236,242 ****
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"
--- 245,254 ----
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: Traceback (most recent call last):
! PL/Python function "subtransaction_exit_twice", line 3, in <module>
! plpy.subtransaction().__exit__(None, None, None)
! 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"
*************** CONTEXT: PL/Python function "subtransac
*** 249,260 ****
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();
--- 261,278 ----
SELECT subtransaction_exit_same_subtransaction_twice();
ERROR: ValueError: this subtransaction has already been exited
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_exit_same_subtransaction_twice", line 5, in <module>
! s.__exit__(None, None, None)
! 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: Traceback (most recent call last):
! PL/Python function "subtransaction_enter_same_subtransaction_twice", line 4, in <module>
! s.__enter__()
! 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();
diff --git a/src/pl/plpython/expected/plpython_subtransaction_5.out b/src/pl/plpython/expected/plpython_subtransaction_5.out
index 4e6c067..9216151 100644
*** a/src/pl/plpython/expected/plpython_subtransaction_5.out
--- b/src/pl/plpython/expected/plpython_subtransaction_5.out
*************** ERROR: spiexceptions.InvalidTextReprese
*** 47,53 ****
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
---
--- 47,56 ----
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_test", line 11, in <module>
! plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
! PL/Python function "subtransaction_test"
SELECT * FROM subtransaction_tbl;
i
---
*************** SELECT * FROM subtransaction_tbl;
*** 56,62 ****
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
---
--- 59,68 ----
TRUNCATE subtransaction_tbl;
SELECT subtransaction_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_test", line 13, in <module>
! plpy.attribute_error
! PL/Python function "subtransaction_test"
SELECT * FROM subtransaction_tbl;
i
---
*************** ERROR: could not compile PL/Python func
*** 223,229 ****
DETAIL: SyntaxError: invalid syntax (<string>, 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"
--- 229,238 ----
DETAIL: SyntaxError: invalid syntax (<string>, line 3)
SELECT subtransaction_exit_without_enter();
ERROR: ValueError: this subtransaction has not been entered
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_exit_without_enter", line 2, in <module>
! plpy.subtransaction().__exit__(None, None, None)
! 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"
*************** SELECT subtransaction_exit_twice();
*** 236,242 ****
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"
--- 245,254 ----
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: Traceback (most recent call last):
! PL/Python function "subtransaction_exit_twice", line 3, in <module>
! plpy.subtransaction().__exit__(None, None, None)
! 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"
*************** CONTEXT: PL/Python function "subtransac
*** 249,260 ****
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();
--- 261,278 ----
SELECT subtransaction_exit_same_subtransaction_twice();
ERROR: ValueError: this subtransaction has already been exited
! CONTEXT: Traceback (most recent call last):
! PL/Python function "subtransaction_exit_same_subtransaction_twice", line 5, in <module>
! s.__exit__(None, None, None)
! 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: Traceback (most recent call last):
! PL/Python function "subtransaction_enter_same_subtransaction_twice", line 4, in <module>
! s.__enter__()
! 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();
diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out
index c2358b4..f2dda66 100644
*** a/src/pl/plpython/expected/plpython_test.out
--- b/src/pl/plpython/expected/plpython_test.out
*************** CONTEXT: PL/Python function "elog_test"
*** 74,77 ****
WARNING: warning
CONTEXT: PL/Python function "elog_test"
ERROR: plpy.Error: error
! CONTEXT: PL/Python function "elog_test"
--- 74,80 ----
WARNING: warning
CONTEXT: PL/Python function "elog_test"
ERROR: plpy.Error: error
! CONTEXT: Traceback (most recent call last):
! PL/Python function "elog_test", line 10, in <module>
! plpy.error('error')
! PL/Python function "elog_test"
diff --git a/src/pl/plpython/expected/plpython_types.out b/src/pl/plpython/expected/plpython_types.out
index d5f2c70..8881613 100644
*** a/src/pl/plpython/expected/plpython_types.out
--- b/src/pl/plpython/expected/plpython_types.out
*************** SELECT name, test_composite_table_input(
*** 625,631 ****
ALTER TABLE employee DROP bonus;
SELECT name, test_composite_table_input(employee.*) FROM employee;
ERROR: KeyError: 'bonus'
! CONTEXT: PL/Python function "test_composite_table_input"
ALTER TABLE employee ADD bonus integer;
UPDATE employee SET bonus = 10;
SELECT name, test_composite_table_input(employee.*) FROM employee;
--- 625,634 ----
ALTER TABLE employee DROP bonus;
SELECT name, test_composite_table_input(employee.*) FROM employee;
ERROR: KeyError: 'bonus'
! CONTEXT: Traceback (most recent call last):
! PL/Python function "test_composite_table_input", line 2, in <module>
! return e['basesalary'] + e['bonus']
! PL/Python function "test_composite_table_input"
ALTER TABLE employee ADD bonus integer;
UPDATE employee SET bonus = 10;
SELECT name, test_composite_table_input(employee.*) FROM employee;
diff --git a/src/pl/plpython/expected/plpython_types_3.out b/src/pl/plpython/expected/plpython_types_3.out
index ca81b08..d1ae863 100644
*** a/src/pl/plpython/expected/plpython_types_3.out
--- b/src/pl/plpython/expected/plpython_types_3.out
*************** SELECT name, test_composite_table_input(
*** 625,631 ****
ALTER TABLE employee DROP bonus;
SELECT name, test_composite_table_input(employee.*) FROM employee;
ERROR: KeyError: 'bonus'
! CONTEXT: PL/Python function "test_composite_table_input"
ALTER TABLE employee ADD bonus integer;
UPDATE employee SET bonus = 10;
SELECT name, test_composite_table_input(employee.*) FROM employee;
--- 625,634 ----
ALTER TABLE employee DROP bonus;
SELECT name, test_composite_table_input(employee.*) FROM employee;
ERROR: KeyError: 'bonus'
! CONTEXT: Traceback (most recent call last):
! PL/Python function "test_composite_table_input", line 2, in <module>
! return e['basesalary'] + e['bonus']
! PL/Python function "test_composite_table_input"
ALTER TABLE employee ADD bonus integer;
UPDATE employee SET bonus = 10;
SELECT name, test_composite_table_input(employee.*) FROM employee;
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index dd2b919..54956f5 100644
*** a/src/pl/plpython/plpython.c
--- b/src/pl/plpython/plpython.c
*************** typedef int Py_ssize_t;
*** 71,76 ****
--- 71,77 ----
*/
#if PY_MAJOR_VERSION >= 3
#define PyInt_FromLong(x) PyLong_FromLong(x)
+ #define PyInt_AsLong(x) PyLong_AsLong(x)
#endif
/*
*************** typedef struct PLyProcedure
*** 217,222 ****
--- 218,224 ----
* type */
bool is_setof; /* true, if procedure returns result set */
PyObject *setof; /* contents of result set. */
+ char *src; /* textual procedure code, after mangling */
char **argnames; /* Argument names */
PLyTypeInfo args[FUNC_MAX_ARGS];
int nargs;
*************** static void
*** 342,348 ****
PLy_elog(int, const char *,...)
__attribute__((format(printf, 2, 3)));
static void PLy_get_spi_error_data(PyObject *exc, char **detail, char **hint, char **query, int *position);
! static char *PLy_traceback(int *);
static void *PLy_malloc(size_t);
static void *PLy_malloc0(size_t);
--- 344,350 ----
PLy_elog(int, const char *,...)
__attribute__((format(printf, 2, 3)));
static void PLy_get_spi_error_data(PyObject *exc, char **detail, char **hint, char **query, int *position);
! static void PLy_traceback(char **, char **, int *);
static void *PLy_malloc(size_t);
static void *PLy_malloc0(size_t);
*************** PLy_procedure_create(HeapTuple procTup,
*** 1603,1608 ****
--- 1605,1611 ----
proc->is_setof = procStruct->proretset;
proc->setof = NULL;
proc->argnames = NULL;
+ proc->src = NULL;
PG_TRY();
{
*************** PLy_procedure_compile(PLyProcedure *proc
*** 1788,1793 ****
--- 1791,1798 ----
* insert the function code into the interpreter
*/
msrc = PLy_procedure_munge_source(proc->pyname, src);
+ /* Save the mangled source for later inclusion in tracebacks */
+ proc->src = PLy_strdup(msrc);
crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL);
pfree(msrc);
*************** PLy_procedure_delete(PLyProcedure *proc)
*** 1885,1890 ****
--- 1890,1897 ----
if (proc->argnames && proc->argnames[i])
PLy_free(proc->argnames[i]);
}
+ if (proc->src)
+ PLy_free(proc->src);
if (proc->argnames)
PLy_free(proc->argnames);
}
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3445,3452 ****
PLy_elog(ERROR, "could not execute plan");
sv = PyString_AsString(so);
PLy_exception_set_plural(PyExc_TypeError,
! "Expected sequence of %d argument, got %d: %s",
! "Expected sequence of %d arguments, got %d: %s",
plan->nargs,
plan->nargs, nargs, sv);
Py_DECREF(so);
--- 3452,3459 ----
PLy_elog(ERROR, "could not execute plan");
sv = PyString_AsString(so);
PLy_exception_set_plural(PyExc_TypeError,
! "Expected sequence of %d argument, got %d: %s",
! "Expected sequence of %d arguments, got %d: %s",
plan->nargs,
plan->nargs, nargs, sv);
Py_DECREF(so);
*************** failure:
*** 4361,4379 ****
* the current Python error, previously set by PLy_exception_set().
* This should be used to propagate Python errors into PG. If fmt is
* NULL, the Python error becomes the primary error message, otherwise
! * it becomes the detail.
*/
static void
PLy_elog(int elevel, const char *fmt,...)
{
- char *xmsg;
- int xlevel;
- StringInfoData emsg;
PyObject *exc, *val, *tb;
char *detail = NULL;
char *hint = NULL;
char *query = NULL;
int position = 0;
PyErr_Fetch(&exc, &val, &tb);
if (exc != NULL)
--- 4368,4388 ----
* the current Python error, previously set by PLy_exception_set().
* This should be used to propagate Python errors into PG. If fmt is
* NULL, the Python error becomes the primary error message, otherwise
! * it becomes the detail. If there is a Python traceback, it put in the
! * context.
*/
static void
PLy_elog(int elevel, const char *fmt,...)
{
PyObject *exc, *val, *tb;
char *detail = NULL;
char *hint = NULL;
char *query = NULL;
int position = 0;
+ char *fmsg = NULL;
+ char *emsg = NULL;
+ char *xmsg = NULL;
+ int tb_depth = 0;
PyErr_Fetch(&exc, &val, &tb);
if (exc != NULL)
*************** PLy_elog(int elevel, const char *fmt,...
*** 4385,4438 ****
}
PyErr_Restore(exc, val, tb);
! xmsg = PLy_traceback(&xlevel);
if (fmt)
{
! initStringInfo(&emsg);
for (;;)
{
va_list ap;
bool success;
va_start(ap, fmt);
! success = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap);
va_end(ap);
if (success)
break;
! enlargeStringInfo(&emsg, emsg.maxlen);
}
}
PG_TRY();
{
if (fmt)
! ereport(elevel,
! (errmsg("%s", emsg.data),
! (xmsg) ? errdetail("%s", xmsg) : 0,
! (hint) ? errhint("%s", hint) : 0,
! (query) ? internalerrquery(query) : 0,
! (position) ? internalerrposition(position) : 0));
else
! ereport(elevel,
! (errmsg("%s", xmsg),
! (detail) ? errdetail("%s", detail) : 0,
! (hint) ? errhint("%s", hint) : 0,
! (query) ? internalerrquery(query) : 0,
! (position) ? internalerrposition(position) : 0));
}
PG_CATCH();
{
! if (fmt)
! pfree(emsg.data);
if (xmsg)
pfree(xmsg);
PG_RE_THROW();
}
PG_END_TRY();
! if (fmt)
! pfree(emsg.data);
if (xmsg)
pfree(xmsg);
}
--- 4394,4472 ----
}
PyErr_Restore(exc, val, tb);
! /* this is a no-op if there is no current Python exception */
! PLy_traceback(&emsg, &xmsg, &tb_depth);
if (fmt)
{
! StringInfoData si;
! initStringInfo(&si);
for (;;)
{
va_list ap;
bool success;
va_start(ap, fmt);
! success = appendStringInfoVA(&si, dgettext(TEXTDOMAIN, fmt), ap);
va_end(ap);
if (success)
break;
! enlargeStringInfo(&si, si.maxlen);
}
+ fmsg = si.data;
}
PG_TRY();
{
+ /* If we have a format string, it should be the main error message and
+ * the emsg the detailed message.
+ *
+ * If we don't have fmsg, we should use emsg as the main error message
+ * (and failing that just say "no exception data").
+ *
+ * The traceback is present if tb_depth > 0 and should be put in the
+ * context field.
+ *
+ * Saved SPI error detail, hint and query position go in the
+ * corresponding fields.
+ */
if (fmt)
! {
! /* since we have a format string, we cannot have a SPI detail */
! Assert(detail == NULL);
! /* if there's an error message, it goes in the detail */
! if (emsg)
! detail = emsg;
! }
else
! {
! Assert(fmsg == NULL);
! /* no format message, try to use emsg */
! if (emsg)
! fmsg = pstrdup(emsg);
! }
!
! ereport(elevel,
! (errmsg("%s", fmsg ? fmsg : "no exception data"),
! (detail) ? errdetail("%s", detail) : 0,
! (tb_depth > 0 && xmsg) ? errcontext("%s", xmsg) : 0,
! (hint) ? errhint("%s", hint) : 0,
! (query) ? internalerrquery(query) : 0,
! (position) ? internalerrposition(position) : 0));
}
PG_CATCH();
{
! if (fmsg)
! pfree(fmsg);
! if (emsg)
! pfree(emsg);
if (xmsg)
pfree(xmsg);
PG_RE_THROW();
}
PG_END_TRY();
! pfree(emsg);
if (xmsg)
pfree(xmsg);
}
*************** cleanup:
*** 4459,4466 ****
}
static char *
! PLy_traceback(int *xlevel)
{
PyObject *e,
*v,
--- 4493,4536 ----
}
+ /* Get the given source line as a palloc'd string */
static char *
! get_source_line(char *src, int lineno)
! {
! char *s;
! char *next;
! int current = 0;
!
! next = src;
! while (current != lineno)
! {
! s = next;
! next = strchr(s + 1, '\n');
! current++;
! if (next == NULL)
! break;
! }
!
! if (current != lineno)
! return NULL;
!
! while (s && isspace(*s))
! s++;
!
! if (next == NULL)
! return pstrdup(s);
!
! return pnstrdup(s, next - s);
! }
!
! /*
! * Extract a Python traceback from the current exception.
! *
! * The exception error message is returned in emsg, the traceback in xmsg (both
! * as palloc's strings) and the traceback depth in tb_depth.
! */
! static void
! PLy_traceback(char **emsg, char **xmsg, int *tb_depth)
{
PyObject *e,
*v,
*************** PLy_traceback(int *xlevel)
*** 4471,4476 ****
--- 4541,4547 ----
char *e_module_s = NULL;
PyObject *vob = NULL;
char *vstr;
+ StringInfoData estr;
StringInfoData xstr;
/*
*************** PLy_traceback(int *xlevel)
*** 4482,4494 ****
* oops, no exception, return
*/
if (e == NULL)
! {
! *xlevel = WARNING;
! return NULL;
! }
PyErr_NormalizeException(&e, &v, &tb);
! Py_XDECREF(tb);
e_type_o = PyObject_GetAttrString(e, "__name__");
e_module_o = PyObject_GetAttrString(e, "__module__");
--- 4553,4563 ----
* oops, no exception, return
*/
if (e == NULL)
! return;
PyErr_NormalizeException(&e, &v, &tb);
!
! /* format the exception and its value and put it in emsg */
e_type_o = PyObject_GetAttrString(e, "__name__");
e_module_o = PyObject_GetAttrString(e, "__module__");
*************** PLy_traceback(int *xlevel)
*** 4502,4543 ****
else
vstr = "unknown";
! initStringInfo(&xstr);
if (!e_type_s || !e_module_s)
{
if (PyString_Check(e))
/* deprecated string exceptions */
! appendStringInfoString(&xstr, PyString_AsString(e));
else
/* shouldn't happen */
! appendStringInfoString(&xstr, "unrecognized exception");
}
/* mimics behavior of traceback.format_exception_only */
else if (strcmp(e_module_s, "builtins") == 0
|| strcmp(e_module_s, "__main__") == 0
|| strcmp(e_module_s, "exceptions") == 0)
! appendStringInfo(&xstr, "%s", e_type_s);
else
! appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s);
! appendStringInfo(&xstr, ": %s", vstr);
Py_XDECREF(e_type_o);
Py_XDECREF(e_module_o);
Py_XDECREF(vob);
Py_XDECREF(v);
-
- /*
- * intuit an appropriate error level based on the exception type
- */
- if (PLy_exc_error && PyErr_GivenExceptionMatches(e, PLy_exc_error))
- *xlevel = ERROR;
- else if (PLy_exc_fatal && PyErr_GivenExceptionMatches(e, PLy_exc_fatal))
- *xlevel = FATAL;
- else
- *xlevel = ERROR;
-
Py_DECREF(e);
- return xstr.data;
}
/* python module code */
--- 4571,4708 ----
else
vstr = "unknown";
! initStringInfo(&estr);
if (!e_type_s || !e_module_s)
{
if (PyString_Check(e))
/* deprecated string exceptions */
! appendStringInfoString(&estr, PyString_AsString(e));
else
/* shouldn't happen */
! appendStringInfoString(&estr, "unrecognized exception");
}
/* mimics behavior of traceback.format_exception_only */
else if (strcmp(e_module_s, "builtins") == 0
|| strcmp(e_module_s, "__main__") == 0
|| strcmp(e_module_s, "exceptions") == 0)
! appendStringInfo(&estr, "%s", e_type_s);
else
! appendStringInfo(&estr, "%s.%s", e_module_s, e_type_s);
! appendStringInfo(&estr, ": %s", vstr);
!
! *emsg = estr.data;
!
! /* now format the traceback and put it in xmsg */
! *tb_depth = 0;
! initStringInfo(&xstr);
! /* Mimick Python traceback reporting as close as possible */
! appendStringInfoString(&xstr, "Traceback (most recent call last):");
! while (tb != NULL && tb != Py_None)
! {
! PyObject *volatile tb_prev = NULL;
! PyObject *volatile frame = NULL;
! PyObject *volatile code = NULL;
! PyObject *volatile name = NULL;
! PyObject *volatile lineno = NULL;
!
! PG_TRY();
! {
! lineno = PyObject_GetAttrString(tb, "tb_lineno");
! if (lineno == NULL)
! elog(ERROR, "could not get line number from Python traceback");
!
! frame = PyObject_GetAttrString(tb, "tb_frame");
! if (frame == NULL)
! elog(ERROR, "could not get frame from Python traceback");
!
! code = PyObject_GetAttrString(frame, "f_code");
! if (code == NULL)
! elog(ERROR, "could not get code object from Python frame");
!
! name = PyObject_GetAttrString(code, "co_name");
! if (name == NULL)
! elog(ERROR, "could not get function name from Python code object");
! }
! PG_CATCH();
! {
! Py_XDECREF(frame);
! Py_XDECREF(code);
! Py_XDECREF(name);
! Py_XDECREF(lineno);
! PG_RE_THROW();
! }
! PG_END_TRY();
!
! /* The first frame always points at <module>, skip it */
! if (*tb_depth > 0)
! {
! char *proname;
! char *fname;
! char *line;
! long plain_lineno;
!
! /*
! * The second frame points at the internal function, but to mimick
! * Python error reporting we want to say <module>
! */
! if (*tb_depth == 1)
! fname = "<module>";
! else
! fname = PyString_AsString(name);
!
! proname = PLy_procedure_name(PLy_curr_procedure);
! plain_lineno = PyInt_AsLong(lineno);
!
! if (proname == NULL)
! appendStringInfo(
! &xstr, "\n PL/Python anonymous code block, line %ld, in %s",
! plain_lineno - 1, fname);
! else
! appendStringInfo(
! &xstr, "\n PL/Python function \"%s\", line %ld, in %s",
! proname, plain_lineno - 1, fname);
!
! if (PLy_curr_procedure)
! {
! /*
! * If we know the current procedure, append the exact line from
! * the source, again mimicking Python's traceback.py module
! * behaviour. We could store the already line-splitted source
! * to avoid splitting it every time, but producing a traceback
! * is not the most important scenario to optimise for.
! */
! line = get_source_line(PLy_curr_procedure->src, plain_lineno);
! if (line != NULL)
! {
! appendStringInfo(&xstr, "\n %s", line);
! pfree(line);
! }
! }
! }
!
! Py_DECREF(frame);
! Py_DECREF(code);
! Py_DECREF(name);
! Py_DECREF(lineno);
!
! /* Release the current frame and go to the next one */
! tb_prev = tb;
! tb = PyObject_GetAttrString(tb, "tb_next");
! Assert(tb_prev != Py_None);
! Py_DECREF(tb_prev);
! if (tb == NULL)
! elog(ERROR, "could not traverse Python traceback");
! (*tb_depth)++;
! }
!
! /* Return the traceback */
! *xmsg = xstr.data;
Py_XDECREF(e_type_o);
Py_XDECREF(e_module_o);
Py_XDECREF(vob);
Py_XDECREF(v);
Py_DECREF(e);
}
/* python module code */
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
index 0f456f4..98d950b 100644
*** a/src/pl/plpython/sql/plpython_error.sql
--- b/src/pl/plpython/sql/plpython_error.sql
*************** return None
*** 131,136 ****
--- 131,241 ----
SELECT valid_type('rick');
+ /* error in nested functions to get a traceback
+ */
+ CREATE FUNCTION nested_error() RETURNS text
+ AS
+ 'def fun1():
+ plpy.error("boom")
+
+ def fun2():
+ fun1()
+
+ def fun3():
+ fun2()
+
+ fun3()
+ return "not reached"
+ '
+ LANGUAGE plpythonu;
+
+ SELECT nested_error();
+
+ /* raising plpy.Error is just like calling plpy.error
+ */
+ CREATE FUNCTION nested_error_raise() RETURNS text
+ AS
+ 'def fun1():
+ raise plpy.Error("boom")
+
+ def fun2():
+ fun1()
+
+ def fun3():
+ fun2()
+
+ fun3()
+ return "not reached"
+ '
+ LANGUAGE plpythonu;
+
+ SELECT nested_error_raise();
+
+ /* using plpy.warning should not produce a traceback
+ */
+ CREATE FUNCTION nested_warning() RETURNS text
+ AS
+ 'def fun1():
+ plpy.warning("boom")
+
+ def fun2():
+ fun1()
+
+ def fun3():
+ fun2()
+
+ fun3()
+ return "you''ve been warned"
+ '
+ LANGUAGE plpythonu;
+
+ SELECT nested_warning();
+
+ /* AttirbuteError at toplevel used to give segfaults with the traceback
+ */
+ CREATE FUNCTION toplevel_attribute_error() RETURNS void AS
+ $$
+ plpy.nonexistent
+ $$ LANGUAGE plpythonu;
+
+ SELECT toplevel_attribute_error();
+
+ /* Calling PL/Python functions from SQL and vice versa should not lose context.
+ */
+ CREATE OR REPLACE FUNCTION python_traceback() RETURNS void AS $$
+ def first():
+ second()
+
+ def second():
+ third()
+
+ def third():
+ plpy.execute("select sql_error()")
+
+ first()
+ $$ LANGUAGE plpythonu;
+
+ CREATE OR REPLACE FUNCTION sql_error() RETURNS void AS $$
+ begin
+ select 1/0;
+ end
+ $$ LANGUAGE plpgsql;
+
+ CREATE OR REPLACE FUNCTION python_from_sql_error() RETURNS void AS $$
+ begin
+ select python_traceback();
+ end
+ $$ LANGUAGE plpgsql;
+
+ CREATE OR REPLACE FUNCTION sql_from_python_error() RETURNS void AS $$
+ plpy.execute("select sql_error()")
+ $$ LANGUAGE plpythonu;
+
+ SELECT python_traceback();
+ SELECT sql_error();
+ SELECT python_from_sql_error();
+ SELECT sql_from_python_error();
+
/* check catching specific types of exceptions
*/
CREATE TABLE specific (
On 20 March 2011 23:40, Jan Urbański <wulczer@wulczer.org> wrote:
I'll update the commitfest app for the 2011-Next commitfest, but if
someone would like to pick this up and include it in the 9.1 PL/Python
revamp pack, I'd be thrilled.
I would also be thrilled. I definitely share your sense of frustration
about the lack of tracebacks available when writing pl/python.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
On mån, 2011-03-21 at 00:40 +0100, Jan Urbański wrote:
I finally got around to updating the PL/Python tracebacks patch. The
other day I was writing some very simple PL/Python code and the lack of
tracebacks is extremely annoying.
I tweaked this a bit to make the patch less invasive, and then committed
it. :)
On 06/04/11 21:38, Peter Eisentraut wrote:
On mån, 2011-03-21 at 00:40 +0100, Jan Urbański wrote:
I finally got around to updating the PL/Python tracebacks patch. The
other day I was writing some very simple PL/Python code and the lack of
tracebacks is extremely annoying.I tweaked this a bit to make the patch less invasive, and then committed
it. :)
Ouch, just today I found a flaw in this, namely that it assumes the
lineno from the traceback always refers to the PL/Python function. If
you create a PL/Python function that imports some code, runs it, and
that code raises an exception, PLy_traceback will get utterly confused.
Working on a fix...
Jan
PS: obviously it'd be great to have PL/Python traceback support in 9.1,
but I sure hope we'll get some testing in beta for issues like this...
J
On 06/04/11 22:16, Jan Urbański wrote:
On 06/04/11 21:38, Peter Eisentraut wrote:
On mån, 2011-03-21 at 00:40 +0100, Jan Urbański wrote:
I finally got around to updating the PL/Python tracebacks patch. The
other day I was writing some very simple PL/Python code and the lack of
tracebacks is extremely annoying.I tweaked this a bit to make the patch less invasive, and then committed
it. :)Ouch, just today I found a flaw in this, namely that it assumes the
lineno from the traceback always refers to the PL/Python function. If
you create a PL/Python function that imports some code, runs it, and
that code raises an exception, PLy_traceback will get utterly confused.Working on a fix...
Here's the fix.
The actual bug was funny. The traceback code was fetching the file line
from the traceback and trying to get that line from the original source
to print it. But sometimes that line was refering to a different source
file, like when the exception originated from an imported module.
In my testing I accidentally had the error (in a separate module) on
line 2, so the traceback code tried to fetch line 2 of the function,
which was completely whitespace. This can never happen in theory,
because you can't have a frame starting at an all-whitespace line. The
code to get that line was misbehaving and trying to do a malloc(-2),
which in turn was causing an "ERROR invalid memory allocation".
All that is fixed with the attached patch.
Cheers,
Jan
PS: and thanks for committing that in the first place! :)
J
Attachments:
plpython-traceback-bug.patchtext/x-patch; name=plpython-traceback-bug.patchDownload
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index 9352580..b2333b8 100644
*** a/src/pl/plpython/plpython.c
--- b/src/pl/plpython/plpython.c
*************** get_source_line(const char *src, int lin
*** 4507,4512 ****
--- 4507,4520 ----
if (next == NULL)
return pstrdup(s);
+ /*
+ * Sanity check, next < s if the line was all-whitespace, which should
+ * never happen if Python reported an frame created on that line, but
+ * check anyway.
+ */
+ if (next < s)
+ return NULL;
+
return pnstrdup(s, next - s);
}
*************** PLy_traceback(char **xmsg, char **tbmsg,
*** 4603,4608 ****
--- 4611,4617 ----
PyObject *volatile code = NULL;
PyObject *volatile name = NULL;
PyObject *volatile lineno = NULL;
+ PyObject *volatile filename = NULL;
PG_TRY();
{
*************** PLy_traceback(char **xmsg, char **tbmsg,
*** 4621,4626 ****
--- 4630,4639 ----
name = PyObject_GetAttrString(code, "co_name");
if (name == NULL)
elog(ERROR, "could not get function name from Python code object");
+
+ filename = PyObject_GetAttrString(code, "co_filename");
+ if (filename == NULL)
+ elog(ERROR, "could not get file name from Python code object");
}
PG_CATCH();
{
*************** PLy_traceback(char **xmsg, char **tbmsg,
*** 4628,4633 ****
--- 4641,4647 ----
Py_XDECREF(code);
Py_XDECREF(name);
Py_XDECREF(lineno);
+ Py_XDECREF(filename);
PG_RE_THROW();
}
PG_END_TRY();
*************** PLy_traceback(char **xmsg, char **tbmsg,
*** 4638,4643 ****
--- 4652,4658 ----
char *proname;
char *fname;
char *line;
+ char *plain_filename;
long plain_lineno;
/*
*************** PLy_traceback(char **xmsg, char **tbmsg,
*** 4651,4656 ****
--- 4666,4672 ----
fname = PyString_AsString(name);
proname = PLy_procedure_name(PLy_curr_procedure);
+ plain_filename = PyString_AsString(filename);
plain_lineno = PyInt_AsLong(lineno);
if (proname == NULL)
*************** PLy_traceback(char **xmsg, char **tbmsg,
*** 4662,4668 ****
&tbstr, "\n PL/Python function \"%s\", line %ld, in %s",
proname, plain_lineno - 1, fname);
! if (PLy_curr_procedure)
{
/*
* If we know the current procedure, append the exact
--- 4678,4686 ----
&tbstr, "\n PL/Python function \"%s\", line %ld, in %s",
proname, plain_lineno - 1, fname);
! /* the code object was compiled with "<string>" as the filename */
! if (PLy_curr_procedure && plain_filename != NULL &&
! strcmp(plain_filename, "<string>") == 0)
{
/*
* If we know the current procedure, append the exact
*************** PLy_traceback(char **xmsg, char **tbmsg,
*** 4670,4676 ****
* traceback.py module behavior. We could store the
* already line-split source to avoid splitting it
* every time, but producing a traceback is not the
! * most important scenario to optimize for.
*/
line = get_source_line(PLy_curr_procedure->src, plain_lineno);
if (line)
--- 4688,4696 ----
* traceback.py module behavior. We could store the
* already line-split source to avoid splitting it
* every time, but producing a traceback is not the
! * most important scenario to optimize for. However,
! * do not go as far as traceback.py in reading the source
! * of imported modules.
*/
line = get_source_line(PLy_curr_procedure->src, plain_lineno);
if (line)
*************** PLy_traceback(char **xmsg, char **tbmsg,
*** 4685,4690 ****
--- 4705,4711 ----
Py_DECREF(code);
Py_DECREF(name);
Py_DECREF(lineno);
+ Py_DECREF(filename);
/* Release the current frame and go to the next one. */
tb_prev = tb;
On Wed, 2011-04-06 at 23:54 +0200, Jan Urbański wrote:
Ouch, just today I found a flaw in this, namely that it assumes the
lineno from the traceback always refers to the PL/Python function. If
you create a PL/Python function that imports some code, runs it, and
that code raises an exception, PLy_traceback will get utterly confused.Working on a fix...
Here's the fix.
Committed.