]> granicus.if.org Git - postgresql/commitdiff
Add traceback information to PL/Python errors
authorPeter Eisentraut <peter_e@gmx.net>
Wed, 6 Apr 2011 19:36:06 +0000 (22:36 +0300)
committerPeter Eisentraut <peter_e@gmx.net>
Wed, 6 Apr 2011 19:36:06 +0000 (22:36 +0300)
This mimics the traceback information the Python interpreter prints
with exceptions.

Jan UrbaƄski

src/pl/plpython/expected/plpython_do.out
src/pl/plpython/expected/plpython_error.out
src/pl/plpython/expected/plpython_error_0.out
src/pl/plpython/expected/plpython_subtransaction.out
src/pl/plpython/expected/plpython_subtransaction_0.out
src/pl/plpython/expected/plpython_subtransaction_5.out
src/pl/plpython/expected/plpython_test.out
src/pl/plpython/expected/plpython_types.out
src/pl/plpython/expected/plpython_types_3.out
src/pl/plpython/plpython.c
src/pl/plpython/sql/plpython_error.sql

index a21b0887ad2c8728559b3a169cbc6dadd3bdae55..41b7a5113875d897e11f3eef74bcb0fd706c0833 100644 (file)
@@ -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
index e38ea60de1c8e6a1349efe719cdb784b058d323f..dbf19fda9b9eaffb068bf829b7de4de138224bc1 100644 (file)
@@ -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)
+
+/* 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 (
@@ -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"
index 1b65d35fc035ac40a895ca6ee30726eea9458950..b2194ffccfb67c372e57c0331bbb7d55d1aaa72f 100644 (file)
@@ -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)
+
+/* 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 (
@@ -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"
index 50d97faa78644e84941d60d0eb6950b296351097..515b0bb734443e21b507ab8c35ff61a12008cca6 100644 (file)
@@ -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 
index 164e9878addd6a9573c951444272a32a7991f909..4017c41edc92606831b1cc15843835b2ed11efe0 100644 (file)
@@ -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();
index 4e6c06729a8b499a2c9eea9dc4757a8e4a3538c7..9216151b94e5b56760d4ab814181cbad7e66422a 100644 (file)
@@ -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();
index c2358b452d9472e571a71d24681c921cbfeff375..f2dda66532e525f1ae79a9d92beb4bdde6cd51bd 100644 (file)
@@ -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"
index d5f2c703fad6c67a5d5386dff2c4109ff8201f80..888161323d1cf26932c5fed9f7dffbf5c89d8c9c 100644 (file)
@@ -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;
index ca81b08a052fd372a61444bbde22e59fe21a6f22..d1ae86377bd17306cd9e834d50ba6548f67c8173 100644 (file)
@@ -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;
index f3f58901fb10f73c08485d5a39fdf48fe090e57e..935258044db5ff0a7e1b2268d04afca095527b73 100644 (file)
@@ -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);
@@ -1602,6 +1604,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
        proc->globals = NULL;
        proc->is_setof = procStruct->proretset;
        proc->setof = NULL;
+       proc->src = NULL;
        proc->argnames = 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);
 }
@@ -4361,15 +4368,18 @@ 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 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;
@@ -4385,7 +4395,7 @@ PLy_elog(int elevel, const char *fmt,...)
        }
        PyErr_Restore(exc, val, tb);
 
-       xmsg = PLy_traceback(&xlevel);
+       PLy_traceback(&xmsg, &tbmsg, &tb_depth);
 
        if (fmt)
        {
@@ -4402,24 +4412,30 @@ PLy_elog(int elevel, const char *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();
        {
@@ -4427,6 +4443,8 @@ PLy_elog(int elevel, const char *fmt,...)
                        pfree(emsg.data);
                if (xmsg)
                        pfree(xmsg);
+               if (tbmsg)
+                       pfree(tbmsg);
                PG_RE_THROW();
        }
        PG_END_TRY();
@@ -4435,6 +4453,8 @@ PLy_elog(int elevel, const char *fmt,...)
                pfree(emsg.data);
        if (xmsg)
                pfree(xmsg);
+       if (tbmsg)
+               pfree(tbmsg);
 }
 
 /*
@@ -4458,9 +4478,47 @@ cleanup:
        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,
@@ -4472,6 +4530,7 @@ PLy_traceback(int *xlevel)
        PyObject   *vob = NULL;
        char       *vstr;
        StringInfoData xstr;
+       StringInfoData tbstr;
 
        /*
         * get the current exception
@@ -4483,12 +4542,18 @@ PLy_traceback(int *xlevel)
         */
        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__");
@@ -4521,23 +4586,124 @@ PLy_traceback(int *xlevel)
                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 */
index 0f456f4bc348dd6294c9501e90b779f1671cb395..4add6aaf05c90876d6b56fb855910112a3f6e343 100644 (file)
@@ -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();
+
+/* 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 (