On 07/03/11 14:01, Jan Urbański wrote: > On 07/03/11 13:53, Peter Eisentraut wrote: >> On sön, 2011-03-06 at 13:14 +0100, Jan Urbański wrote: >>> But fixing "raise plpy.Fatal()" >>> to actually cause a FATAL is something that should be extracted from >>> this patch and committed, even if the full patch does not make it. >> >> Um, what? I didn't find any details about this in this thread, nor a >> test case.
> So this in fact are three separate things, tracebacks, fix for > plpy.Fatal and a one-line fix for reporting errors in Python iterators, > that as I noticed has a side effect of changing the SQLCODE being raised > :( I think I'll just respin the tracebacks patch as 3 separate ones, > coming right up. Respun as three separate patches. Sorry for the confusion. BTW: looks like plpy.Fatal behaviour has been broken for quite some time now. Jan
>From 06ac95d62de1aaf40dec020ac2892f20c3879db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Urba=C5=84ski?= <wulc...@wulczer.org> Date: Mon, 7 Mar 2011 14:16:25 +0100 Subject: [PATCH 3/3] Add Python tracebacks to error messages. For errors originating from Python exceptions add the traceback to the message context. While at it rework the Python to Postgres error passing mechanism a bit. A future optimisation might be not splitting the procedure source each time a traceback is generated, but for now it's probably not the most important scenario to optimise for. --- src/pl/plpython/expected/plpython_do.out | 5 +- src/pl/plpython/expected/plpython_error.out | 193 +++++++++++++- src/pl/plpython/expected/plpython_error_0.out | 193 +++++++++++++- .../plpython/expected/plpython_subtransaction.out | 55 +++- .../expected/plpython_subtransaction_0.out | 30 ++- .../expected/plpython_subtransaction_5.out | 30 ++- src/pl/plpython/expected/plpython_test.out | 5 +- src/pl/plpython/expected/plpython_types.out | 5 +- src/pl/plpython/expected/plpython_types_3.out | 5 +- src/pl/plpython/plpython.c | 287 ++++++++++++++++---- src/pl/plpython/sql/plpython_error.sql | 105 +++++++ 11 files changed, 821 insertions(+), 92 deletions(-) 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 @@ -3,4 +3,7 @@ NOTICE: This is plpythonu. 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 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 @@ -36,7 +36,10 @@ ERROR: spiexceptions.SyntaxError: syntax error at or near "syntax" 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 @@ -45,7 +48,10 @@ 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 @@ -59,7 +65,10 @@ LINE 1: SELECT test5('foo') ^ 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 @@ -75,7 +84,10 @@ return None 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 */ @@ -121,7 +133,10 @@ 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 @@ -140,6 +155,164 @@ SELECT valid_type('rick'); (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 ( @@ -187,7 +360,10 @@ plpy.execute("rollback to save") $$ 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 $$ @@ -199,4 +375,7 @@ plpy.execute(rollback) $$ 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" 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 @@ -36,7 +36,10 @@ ERROR: spiexceptions.SyntaxError: syntax error at or near "syntax" 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 @@ -45,7 +48,10 @@ 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 @@ -59,7 +65,10 @@ LINE 1: SELECT test5('foo') ^ 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 @@ -75,7 +84,10 @@ return None 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 */ @@ -121,7 +133,10 @@ 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 @@ -140,6 +155,164 @@ SELECT valid_type('rick'); (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 ( @@ -187,7 +360,10 @@ plpy.execute("rollback to save") $$ 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 $$ @@ -199,4 +375,7 @@ plpy.execute(rollback) $$ 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" 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 @@ -47,7 +47,10 @@ ERROR: spiexceptions.InvalidTextRepresentation: invalid input syntax for intege 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 --- @@ -56,7 +59,10 @@ SELECT * FROM subtransaction_tbl; 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 --- @@ -93,7 +99,10 @@ ERROR: spiexceptions.InvalidTextRepresentation: invalid input syntax for intege 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 --- @@ -102,7 +111,10 @@ SELECT * FROM subtransaction_tbl; 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 --- @@ -130,7 +142,10 @@ ERROR: spiexceptions.SyntaxError: syntax error at or near "error" 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 --- @@ -230,7 +245,10 @@ with plpy.subtransaction() as s: $$ 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" @@ -243,7 +261,10 @@ SELECT subtransaction_exit_twice(); WARNING: forcibly aborting a subtransaction that has not been exited CONTEXT: PL/Python function "subtransaction_exit_twice" ERROR: ValueError: this subtransaction has not been entered -CONTEXT: PL/Python function "subtransaction_exit_twice" +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" @@ -256,18 +277,30 @@ 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 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 @@ -47,7 +47,10 @@ ERROR: spiexceptions.InvalidTextRepresentation: invalid input syntax for intege 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 --- @@ -56,7 +59,10 @@ SELECT * FROM subtransaction_tbl; 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 --- @@ -223,7 +229,10 @@ ERROR: could not compile PL/Python function "subtransaction_exit_subtransaction 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" @@ -236,7 +245,10 @@ SELECT subtransaction_exit_twice(); WARNING: forcibly aborting a subtransaction that has not been exited CONTEXT: PL/Python function "subtransaction_exit_twice" ERROR: ValueError: this subtransaction has not been entered -CONTEXT: PL/Python function "subtransaction_exit_twice" +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" @@ -249,12 +261,18 @@ 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(); 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 @@ -47,7 +47,10 @@ ERROR: spiexceptions.InvalidTextRepresentation: invalid input syntax for intege 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 --- @@ -56,7 +59,10 @@ SELECT * FROM subtransaction_tbl; 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 --- @@ -223,7 +229,10 @@ ERROR: could not compile PL/Python function "subtransaction_exit_subtransaction 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" @@ -236,7 +245,10 @@ SELECT subtransaction_exit_twice(); WARNING: forcibly aborting a subtransaction that has not been exited CONTEXT: PL/Python function "subtransaction_exit_twice" ERROR: ValueError: this subtransaction has not been entered -CONTEXT: PL/Python function "subtransaction_exit_twice" +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" @@ -249,12 +261,18 @@ 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(); 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 @@ -74,4 +74,7 @@ CONTEXT: PL/Python function "elog_test" 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" 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 @@ -625,7 +625,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; 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 @@ -625,7 +625,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; diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index 1f8c27f..f643bc3 100644 --- a/src/pl/plpython/plpython.c +++ b/src/pl/plpython/plpython.c @@ -71,6 +71,7 @@ typedef int Py_ssize_t; */ #if PY_MAJOR_VERSION >= 3 #define PyInt_FromLong(x) PyLong_FromLong(x) +#define PyInt_AsLong(x) PyLong_AsLong(x) #endif /* @@ -217,6 +218,7 @@ typedef struct PLyProcedure * 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; @@ -342,7 +344,7 @@ static void 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); @@ -1603,6 +1605,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) proc->is_setof = procStruct->proretset; proc->setof = NULL; proc->argnames = NULL; + proc->src = NULL; PG_TRY(); { @@ -1788,6 +1791,8 @@ PLy_procedure_compile(PLyProcedure *proc, const char *src) * 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); @@ -1885,6 +1890,8 @@ PLy_procedure_delete(PLyProcedure *proc) 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); } @@ -3450,8 +3457,8 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit) 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", + "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); @@ -4366,19 +4373,21 @@ failure: * 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 put in the + * context. */ 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; + char *fmsg = NULL; + char *emsg = NULL; + char *xmsg = NULL; + int tb_depth = 0; PyErr_Fetch(&exc, &val, &tb); if (exc != NULL) @@ -4390,54 +4399,99 @@ PLy_elog(int elevel, const char *fmt,...) } PyErr_Restore(exc, val, tb); - xmsg = PLy_traceback(&xlevel); + /* this is a no-op if there is no current Python exception */ + PLy_traceback(&emsg, &xmsg, &tb_depth); if (fmt) { - initStringInfo(&emsg); + StringInfoData si; + initStringInfo(&si); for (;;) { va_list ap; bool success; va_start(ap, fmt); - success = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap); + success = appendStringInfoVA(&si, dgettext(TEXTDOMAIN, fmt), ap); va_end(ap); if (success) break; - enlargeStringInfo(&emsg, emsg.maxlen); + 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 + traceback 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") and put the + * traceback in the detail. + * + * The traceback is present if tb_depth > 0. + */ 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)); + { + if (tb_depth > 0) + { + ereport(elevel, + (errmsg("%s", fmsg), + (detail) ? errdetail("%s", detail) : 0, + (emsg) ? errdetail("%s", emsg) : 0, + (xmsg) ? errcontext("%s", xmsg) : 0, + (hint) ? errhint("%s", hint) : 0, + (query) ? internalerrquery(query) : 0, + (position) ? internalerrposition(position) : 0)); + } + else + { + ereport(elevel, + (errmsg("%s", fmsg), + (detail) ? errdetail("%s", detail) : 0, + (emsg) ? errdetail("%s", emsg) : 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)); + { + if (tb_depth > 0) + { + ereport(elevel, + (errmsg("%s", emsg ? emsg : "no exception data"), + (detail) ? errdetail("%s", detail) : 0, + (xmsg) ? errcontext("%s", xmsg) : 0, + (hint) ? errhint("%s", hint) : 0, + (query) ? internalerrquery(query) : 0, + (position) ? internalerrposition(position) : 0)); + } + else + { + ereport(elevel, + (errmsg("%s", emsg ? emsg : "no exception data"), + (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 (fmsg) + pfree(fmsg); + if (emsg) + pfree(emsg); if (xmsg) pfree(xmsg); PG_RE_THROW(); } PG_END_TRY(); - if (fmt) - pfree(emsg.data); + pfree(emsg); if (xmsg) pfree(xmsg); } @@ -4464,8 +4518,44 @@ cleanup: } +/* Get the given source line as a palloc'd string */ static char * -PLy_traceback(int *xlevel) +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, @@ -4476,6 +4566,7 @@ PLy_traceback(int *xlevel) char *e_module_s = NULL; PyObject *vob = NULL; char *vstr; + StringInfoData estr; StringInfoData xstr; /* @@ -4487,13 +4578,11 @@ PLy_traceback(int *xlevel) * oops, no exception, return */ if (e == NULL) - { - *xlevel = WARNING; - return NULL; - } + return; PyErr_NormalizeException(&e, &v, &tb); - Py_XDECREF(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__"); @@ -4507,42 +4596,138 @@ PLy_traceback(int *xlevel) else vstr = "unknown"; - initStringInfo(&xstr); + initStringInfo(&estr); if (!e_type_s || !e_module_s) { if (PyString_Check(e)) /* deprecated string exceptions */ - appendStringInfoString(&xstr, PyString_AsString(e)); + appendStringInfoString(&estr, PyString_AsString(e)); else /* shouldn't happen */ - appendStringInfoString(&xstr, "unrecognized exception"); + 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(&xstr, "%s", e_type_s); + appendStringInfo(&estr, "%s", e_type_s); else - appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s); - appendStringInfo(&xstr, ": %s", vstr); + 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); - - /* - * 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 */ 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 @@ -131,6 +131,111 @@ return None 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 ( -- 1.7.2.3
>From f7f6fde7a79fffa4c479dc5cfc1e529f9298e935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Urba=C5=84ski?= <wulc...@wulczer.org> Date: Mon, 7 Mar 2011 14:14:38 +0100 Subject: [PATCH 2/3] Fix behaviour when raising plpy.Fatal() It should cause a elog(FATAL) error, and it fact it was simply causing a elog(ERROR). --- src/pl/plpython/plpython.c | 9 +++++++-- 1 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index 43d332d..1f8c27f 100644 --- a/src/pl/plpython/plpython.c +++ b/src/pl/plpython/plpython.c @@ -4381,8 +4381,13 @@ PLy_elog(int elevel, const char *fmt,...) int position = 0; PyErr_Fetch(&exc, &val, &tb); - if (exc != NULL && PyErr_GivenExceptionMatches(val, PLy_exc_spi_error)) - PLy_get_spi_error_data(val, &detail, &hint, &query, &position); + if (exc != NULL) + { + if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error)) + PLy_get_spi_error_data(val, &detail, &hint, &query, &position); + else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal)) + elevel = FATAL; + } PyErr_Restore(exc, val, tb); xmsg = PLy_traceback(&xlevel); -- 1.7.2.3
>From 823b8c4267b48389a043fd30b26ddc3d9488d784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Urba=C5=84ski?= <wulc...@wulczer.org> Date: Mon, 7 Mar 2011 14:09:30 +0100 Subject: [PATCH 1/3] Report Python errors from iterators with PLy_elog. This improves reporting, as the error string now includes the actual Python exception. As a side effect, this no longer sets the errcode to ERRCODE_DATA_EXCEPTION, which might be considered a feature, as it's not documented and not clear why iterator errors should be treated differently. --- src/pl/plpython/plpython.c | 4 +--- 1 files changed, 1 insertions(+), 3 deletions(-) diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index 4a9e2a4..43d332d 100644 --- a/src/pl/plpython/plpython.c +++ b/src/pl/plpython/plpython.c @@ -1157,9 +1157,7 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *proc) PLy_function_delete_args(proc); if (has_error) - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("error fetching next item from iterator"))); + PLy_elog(ERROR, "error fetching next item from iterator"); /* Disconnect from the SPI manager before returning */ if (SPI_finish() != SPI_OK_FINISH) -- 1.7.2.3
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers