CONTEXT: PL/Python anonymous code block
DO $$ nonsense $$ LANGUAGE plpythonu;
ERROR: NameError: global name 'nonsense' is not defined
-CONTEXT: PL/Python anonymous code block
+CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 1, in <module>
+ nonsense
+PL/Python anonymous code block
LINE 1: syntax error
^
QUERY: syntax error
-CONTEXT: PL/Python function "sql_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
LANGUAGE plpythonu;
SELECT exception_index_invalid('test');
ERROR: IndexError: list index out of range
-CONTEXT: PL/Python function "exception_index_invalid"
+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
^
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"
+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
LANGUAGE plpythonu;
SELECT invalid_type_uncaught('rick');
ERROR: spiexceptions.UndefinedObject: type "test" does not exist
-CONTEXT: PL/Python function "invalid_type_uncaught"
+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
*/
LANGUAGE plpythonu;
SELECT invalid_type_reraised('rick');
ERROR: plpy.Error: type "test" does not exist
-CONTEXT: PL/Python function "invalid_type_reraised"
+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
(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)
+
+/* AttributeError 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 (
$$ LANGUAGE plpythonu;
SELECT manual_subxact();
ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
-CONTEXT: PL/Python function "manual_subxact"
+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 $$
$$ LANGUAGE plpythonu;
SELECT manual_subxact_prepared();
ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
-CONTEXT: PL/Python function "manual_subxact_prepared"
+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"
LINE 1: syntax error
^
QUERY: syntax error
-CONTEXT: PL/Python function "sql_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
LANGUAGE plpythonu;
SELECT exception_index_invalid('test');
ERROR: IndexError: list index out of range
-CONTEXT: PL/Python function "exception_index_invalid"
+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
^
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"
+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
LANGUAGE plpythonu;
SELECT invalid_type_uncaught('rick');
ERROR: spiexceptions.UndefinedObject: type "test" does not exist
-CONTEXT: PL/Python function "invalid_type_uncaught"
+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
*/
LANGUAGE plpythonu;
SELECT invalid_type_reraised('rick');
ERROR: plpy.Error: type "test" does not exist
-CONTEXT: PL/Python function "invalid_type_reraised"
+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
(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)
+
+/* AttributeError 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 (
$$ LANGUAGE plpythonu;
SELECT manual_subxact();
ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
-CONTEXT: PL/Python function "manual_subxact"
+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 $$
$$ LANGUAGE plpythonu;
SELECT manual_subxact_prepared();
ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
-CONTEXT: PL/Python function "manual_subxact_prepared"
+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"
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
-CONTEXT: PL/Python function "subtransaction_test"
+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
---
TRUNCATE subtransaction_tbl;
SELECT subtransaction_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
-CONTEXT: PL/Python function "subtransaction_test"
+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
---
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
-CONTEXT: PL/Python function "subtransaction_ctx_test"
+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
---
TRUNCATE subtransaction_tbl;
SELECT subtransaction_ctx_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
-CONTEXT: PL/Python function "subtransaction_ctx_test"
+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
---
LINE 1: error
^
QUERY: error
-CONTEXT: PL/Python function "subtransaction_nested_test"
+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
---
$$ LANGUAGE plpythonu;
SELECT subtransaction_exit_without_enter();
ERROR: ValueError: this subtransaction has not been entered
-CONTEXT: PL/Python function "subtransaction_exit_without_enter"
+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"
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"
+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"
SELECT subtransaction_exit_same_subtransaction_twice();
ERROR: ValueError: this subtransaction has already been exited
-CONTEXT: PL/Python function "subtransaction_exit_same_subtransaction_twice"
+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: PL/Python function "subtransaction_enter_same_subtransaction_twice"
+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: PL/Python function "subtransaction_enter_subtransaction_in_with"
+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: PL/Python function "subtransaction_exit_subtransaction_in_with"
+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
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
-CONTEXT: PL/Python function "subtransaction_test"
+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
---
TRUNCATE subtransaction_tbl;
SELECT subtransaction_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
-CONTEXT: PL/Python function "subtransaction_test"
+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
---
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"
+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"
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"
+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"
SELECT subtransaction_exit_same_subtransaction_twice();
ERROR: ValueError: this subtransaction has already been exited
-CONTEXT: PL/Python function "subtransaction_exit_same_subtransaction_twice"
+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: PL/Python function "subtransaction_enter_same_subtransaction_twice"
+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();
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
-CONTEXT: PL/Python function "subtransaction_test"
+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
---
TRUNCATE subtransaction_tbl;
SELECT subtransaction_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
-CONTEXT: PL/Python function "subtransaction_test"
+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
---
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"
+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"
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"
+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"
SELECT subtransaction_exit_same_subtransaction_twice();
ERROR: ValueError: this subtransaction has already been exited
-CONTEXT: PL/Python function "subtransaction_exit_same_subtransaction_twice"
+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: PL/Python function "subtransaction_enter_same_subtransaction_twice"
+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();
WARNING: warning
CONTEXT: PL/Python function "elog_test"
ERROR: plpy.Error: error
-CONTEXT: PL/Python function "elog_test"
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "elog_test", line 10, in <module>
+ plpy.error('error')
+PL/Python function "elog_test"
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"
+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;
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"
+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;
*/
#if PY_MAJOR_VERSION >= 3
#define PyInt_FromLong(x) PyLong_FromLong(x)
+#define PyInt_AsLong(x) PyLong_AsLong(x)
#endif
/*
* 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;
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_traceback(char **, char **, int *);
static void *PLy_malloc(size_t);
static void *PLy_malloc0(size_t);
proc->globals = NULL;
proc->is_setof = procStruct->proretset;
proc->setof = NULL;
+ proc->src = NULL;
proc->argnames = NULL;
PG_TRY();
* 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);
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);
}
* 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.
+ * it becomes the detail. If there is a Python traceback, it is put
+ * in the context.
*/
static void
PLy_elog(int elevel, const char *fmt,...)
{
char *xmsg;
- int xlevel;
+ char *tbmsg;
+ int tb_depth;
StringInfoData emsg;
PyObject *exc, *val, *tb;
+ const char *primary = NULL;
char *detail = NULL;
char *hint = NULL;
char *query = NULL;
}
PyErr_Restore(exc, val, tb);
- xmsg = PLy_traceback(&xlevel);
+ PLy_traceback(&xmsg, &tbmsg, &tb_depth);
if (fmt)
{
break;
enlargeStringInfo(&emsg, emsg.maxlen);
}
+ primary = emsg.data;
+
+ /* Since we have a format string, we cannot have a SPI detail. */
+ Assert(detail == NULL);
+
+ /* If there's an exception message, it goes in the detail. */
+ if (xmsg)
+ detail = xmsg;
+ }
+ else
+ {
+ if (xmsg)
+ primary = xmsg;
}
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));
+ ereport(elevel,
+ (errmsg("%s", primary ? primary : "no exception data"),
+ (detail) ? errdetail("%s", detail) : 0,
+ (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0,
+ (hint) ? errhint("%s", hint) : 0,
+ (query) ? internalerrquery(query) : 0,
+ (position) ? internalerrposition(position) : 0));
}
PG_CATCH();
{
pfree(emsg.data);
if (xmsg)
pfree(xmsg);
+ if (tbmsg)
+ pfree(tbmsg);
PG_RE_THROW();
}
PG_END_TRY();
pfree(emsg.data);
if (xmsg)
pfree(xmsg);
+ if (tbmsg)
+ pfree(tbmsg);
}
/*
Py_XDECREF(spidata);
}
-
+/*
+ * Get the given source line as a palloc'd string
+ */
static char *
-PLy_traceback(int *xlevel)
+get_source_line(const char *src, int lineno)
+{
+ const char *s;
+ const 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((unsigned char) *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 xmsg, the traceback in
+ * tbmsg (both as palloc'd strings) and the traceback depth in
+ * tb_depth.
+ */
+static void
+PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
{
PyObject *e,
*v,
PyObject *vob = NULL;
char *vstr;
StringInfoData xstr;
+ StringInfoData tbstr;
/*
* get the current exception
*/
if (e == NULL)
{
- *xlevel = WARNING;
- return NULL;
+ *xmsg = NULL;
+ *tbmsg = NULL;
+ *tb_depth = 0;
+
+ return;
}
PyErr_NormalizeException(&e, &v, &tb);
- Py_XDECREF(tb);
+
+ /*
+ * Format the exception and its value and put it in xmsg.
+ */
e_type_o = PyObject_GetAttrString(e, "__name__");
e_module_o = PyObject_GetAttrString(e, "__module__");
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);
+ *xmsg = xstr.data;
/*
- * intuit an appropriate error level based on the exception type
+ * Now format the traceback and put it in tbmsg.
*/
- 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;
+ *tb_depth = 0;
+ initStringInfo(&tbstr);
+ /* Mimick Python traceback reporting as close as possible. */
+ appendStringInfoString(&tbstr, "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(
+ &tbstr, "\n PL/Python anonymous code block, line %ld, in %s",
+ plain_lineno - 1, fname);
+ else
+ appendStringInfo(
+ &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
+ * line from the source, again mimicking Python's
+ * 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)
+ {
+ appendStringInfo(&tbstr, "\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. */
+ *tbmsg = tbstr.data;
+
+ Py_XDECREF(e_type_o);
+ Py_XDECREF(e_module_o);
+ Py_XDECREF(vob);
+ Py_XDECREF(v);
Py_DECREF(e);
- return xstr.data;
}
/* python module code */
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();
+
+/* AttributeError 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 (