On 26/02/11 16:10, Peter Eisentraut wrote:
> On lör, 2011-02-26 at 09:34 +0100, Jan Urbański wrote:
>> ----- Original message -----
>>> On Thu, Feb 24, 2011 at 9:03 AM, Jan Urbański <wulc...@wulczer.org>
>>> wrote:
>>>> On 24/02/11 14:10, Peter Eisentraut wrote:
>>>> Hm, perhaps, I put it in the details, because it sounded like the place
>>>> to put information that is not that important, but still helpful. It's
>>>> kind of natural to think of the traceback as the detail of the error
>>>> message. But if you prefer context, I'm fine with that. You want me to
>>>> update the patch to put the traceback in the context?
>>>
>>> I don't see a response to this question from Peter, but I read his
>>> email to indicate that he was hoping you'd rework along these lines.
>>
>> I can do that, but not until Monday evening.
> 
> Well, I was hoping for some other opinion, but I guess my request
> stands.

I looked into putting the tracebacks in the context field, but IMHO it
doesn't really play out nice. PL/Python uses a errcontext callback to
populate the context field, so the reduntant information (the name of
the function) is always there. If that callback would be removed, the
context information will not appear in plpy.warning output, which I
think would be bad.

So: the context is currently put unconditionally into every elog
message, which I think is good. In case of errors, the traceback already
includes the procedure name (because of how Python tracebacks are
typically formatted), which makes the traceback contain redundant
information to the context field. Replacing the context field with the
traceback is difficult, because it is populated by the error context
callback.

After thinking about it more I believe that the context field should
keep on being a one line indication of which function the message comes
from (and that's how it's done in PL/pgSQL for instance), and the detail
field should be used for the details of the message, like the traceback
that comes with it, and that's what the attached patch does.

While testing I noticed that this broke "raise plpy.Fatal()" behaviour -
it was no longer terminating the backend but just raising an error.
That's fixed in this version. This patch also fixes a place where
ereport is being used to report Python errors, which leads to losing the
original error. Incidentally, this is exactly the issue that made
diagnosing this bug:

http://postgresql.1045698.n5.nabble.com/Bug-in-plpython-s-Python-Generators-td3230402.html

so difficult.

Cheers,
Jan
diff --git a/src/pl/plpython/expected/plpython_do.out b/src/pl/plpython/expected/plpython_do.out
index a21b088..fb0f0e5 100644
*** a/src/pl/plpython/expected/plpython_do.out
--- b/src/pl/plpython/expected/plpython_do.out
*************** NOTICE:  This is plpythonu.
*** 3,6 ****
--- 3,9 ----
  CONTEXT:  PL/Python anonymous code block
  DO $$ nonsense $$ LANGUAGE plpythonu;
  ERROR:  NameError: global name 'nonsense' is not defined
+ DETAIL:  Traceback (most recent call last):
+   PL/Python anonymous code block, line 1, in <module>
+     nonsense 
  CONTEXT:  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..949c705 100644
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** SELECT sql_syntax_error();
*** 35,40 ****
--- 35,43 ----
  ERROR:  spiexceptions.SyntaxError: syntax error at or near "syntax"
  LINE 1: syntax error
          ^
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "sql_syntax_error", line 1, in <module>
+     plpy.execute("syntax error")
  QUERY:  syntax error
  CONTEXT:  PL/Python function "sql_syntax_error"
  /* check the handling of uncaught python exceptions
*************** CREATE FUNCTION exception_index_invalid(
*** 45,50 ****
--- 48,56 ----
  	LANGUAGE plpythonu;
  SELECT exception_index_invalid('test');
  ERROR:  IndexError: list index out of range
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "exception_index_invalid", line 1, in <module>
+     return args[1]
  CONTEXT:  PL/Python function "exception_index_invalid"
  /* check handling of nested exceptions
   */
*************** SELECT exception_index_invalid_nested();
*** 57,62 ****
--- 63,71 ----
  ERROR:  spiexceptions.UndefinedFunction: function test5(unknown) does not exist
  LINE 1: SELECT test5('foo')
                 ^
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "exception_index_invalid_nested", line 1, in <module>
+     rv = plpy.execute("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"
*************** return None
*** 75,80 ****
--- 84,92 ----
  	LANGUAGE plpythonu;
  SELECT invalid_type_uncaught('rick');
  ERROR:  spiexceptions.UndefinedObject: type "test" does not exist
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "invalid_type_uncaught", line 3, in <module>
+     SD["plan"] = plpy.prepare(q, [ "test" ])
  CONTEXT:  PL/Python function "invalid_type_uncaught"
  /* for what it's worth catch the exception generated by
   * the typo, and return None
*************** return None
*** 121,126 ****
--- 133,141 ----
  	LANGUAGE plpythonu;
  SELECT invalid_type_reraised('rick');
  ERROR:  plpy.Error: type "test" does not exist
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "invalid_type_reraised", line 6, in <module>
+     plpy.error(str(ex))
  CONTEXT:  PL/Python function "invalid_type_reraised"
  /* no typo no messing about
   */
*************** SELECT valid_type('rick');
*** 140,145 ****
--- 155,255 ----
   
  (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
+ DETAIL:  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")
+ CONTEXT:  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
+ DETAIL:  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")
+ CONTEXT:  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'
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "toplevel_attribute_error", line 2, in <module>
+     plpy.nonexistent
+ CONTEXT:  PL/Python function "toplevel_attribute_error"
  /* check catching specific types of exceptions
   */
  CREATE TABLE specific (
*************** plpy.execute("rollback to save")
*** 187,192 ****
--- 297,305 ----
  $$ LANGUAGE plpythonu;
  SELECT manual_subxact();
  ERROR:  plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "manual_subxact", line 2, in <module>
+     plpy.execute("savepoint save")
  CONTEXT:  PL/Python function "manual_subxact"
  /* same for prepared plans
   */
*************** plpy.execute(rollback)
*** 199,202 ****
--- 312,318 ----
  $$ LANGUAGE plpythonu;
  SELECT manual_subxact_prepared();
  ERROR:  plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "manual_subxact_prepared", line 4, in <module>
+     plpy.execute(save)
  CONTEXT:  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..8b4dcbb 100644
*** a/src/pl/plpython/expected/plpython_error_0.out
--- b/src/pl/plpython/expected/plpython_error_0.out
*************** SELECT sql_syntax_error();
*** 35,40 ****
--- 35,43 ----
  ERROR:  spiexceptions.SyntaxError: syntax error at or near "syntax"
  LINE 1: syntax error
          ^
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "sql_syntax_error", line 1, in <module>
+     plpy.execute("syntax error")
  QUERY:  syntax error
  CONTEXT:  PL/Python function "sql_syntax_error"
  /* check the handling of uncaught python exceptions
*************** CREATE FUNCTION exception_index_invalid(
*** 45,50 ****
--- 48,56 ----
  	LANGUAGE plpythonu;
  SELECT exception_index_invalid('test');
  ERROR:  IndexError: list index out of range
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "exception_index_invalid", line 1, in <module>
+     return args[1]
  CONTEXT:  PL/Python function "exception_index_invalid"
  /* check handling of nested exceptions
   */
*************** SELECT exception_index_invalid_nested();
*** 57,62 ****
--- 63,71 ----
  ERROR:  spiexceptions.UndefinedFunction: function test5(unknown) does not exist
  LINE 1: SELECT test5('foo')
                 ^
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "exception_index_invalid_nested", line 1, in <module>
+     rv = plpy.execute("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"
*************** return None
*** 75,80 ****
--- 84,92 ----
  	LANGUAGE plpythonu;
  SELECT invalid_type_uncaught('rick');
  ERROR:  spiexceptions.UndefinedObject: type "test" does not exist
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "invalid_type_uncaught", line 3, in <module>
+     SD["plan"] = plpy.prepare(q, [ "test" ])
  CONTEXT:  PL/Python function "invalid_type_uncaught"
  /* for what it's worth catch the exception generated by
   * the typo, and return None
*************** return None
*** 121,126 ****
--- 133,141 ----
  	LANGUAGE plpythonu;
  SELECT invalid_type_reraised('rick');
  ERROR:  plpy.Error: type "test" does not exist
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "invalid_type_reraised", line 6, in <module>
+     plpy.error(str(ex))
  CONTEXT:  PL/Python function "invalid_type_reraised"
  /* no typo no messing about
   */
*************** SELECT valid_type('rick');
*** 140,145 ****
--- 155,255 ----
   
  (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
+ DETAIL:  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")
+ CONTEXT:  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
+ DETAIL:  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")
+ CONTEXT:  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'
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "toplevel_attribute_error", line 2, in <module>
+     plpy.nonexistent
+ CONTEXT:  PL/Python function "toplevel_attribute_error"
  /* check catching specific types of exceptions
   */
  CREATE TABLE specific (
*************** plpy.execute("rollback to save")
*** 187,192 ****
--- 297,305 ----
  $$ LANGUAGE plpythonu;
  SELECT manual_subxact();
  ERROR:  plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "manual_subxact", line 2, in <module>
+     plpy.execute("savepoint save")
  CONTEXT:  PL/Python function "manual_subxact"
  /* same for prepared plans
   */
*************** plpy.execute(rollback)
*** 199,202 ****
--- 312,318 ----
  $$ LANGUAGE plpythonu;
  SELECT manual_subxact_prepared();
  ERROR:  plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "manual_subxact_prepared", line 4, in <module>
+     plpy.execute(save)
  CONTEXT:  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..3cccfd0 100644
*** a/src/pl/plpython/expected/plpython_subtransaction.out
--- b/src/pl/plpython/expected/plpython_subtransaction.out
*************** SELECT subtransaction_test('SPI');
*** 46,51 ****
--- 46,54 ----
  ERROR:  spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
  LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
                                                 ^
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_test", line 11, in <module>
+     plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
  QUERY:  INSERT INTO subtransaction_tbl VALUES ('oops')
  CONTEXT:  PL/Python function "subtransaction_test"
  SELECT * FROM subtransaction_tbl;
*************** SELECT * FROM subtransaction_tbl;
*** 56,61 ****
--- 59,67 ----
  TRUNCATE subtransaction_tbl;
  SELECT subtransaction_test('Python');
  ERROR:  AttributeError: 'module' object has no attribute 'attribute_error'
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_test", line 13, in <module>
+     plpy.attribute_error
  CONTEXT:  PL/Python function "subtransaction_test"
  SELECT * FROM subtransaction_tbl;
   i 
*************** SELECT subtransaction_ctx_test('SPI');
*** 92,97 ****
--- 98,106 ----
  ERROR:  spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
  LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
                                                 ^
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_ctx_test", line 6, in <module>
+     plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
  QUERY:  INSERT INTO subtransaction_tbl VALUES ('oops')
  CONTEXT:  PL/Python function "subtransaction_ctx_test"
  SELECT * FROM subtransaction_tbl;
*************** SELECT * FROM subtransaction_tbl;
*** 102,107 ****
--- 111,119 ----
  TRUNCATE subtransaction_tbl;
  SELECT subtransaction_ctx_test('Python');
  ERROR:  AttributeError: 'module' object has no attribute 'attribute_error'
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_ctx_test", line 8, in <module>
+     plpy.attribute_error
  CONTEXT:  PL/Python function "subtransaction_ctx_test"
  SELECT * FROM subtransaction_tbl;
   i 
*************** SELECT subtransaction_nested_test();
*** 129,134 ****
--- 141,149 ----
  ERROR:  spiexceptions.SyntaxError: syntax error at or near "error"
  LINE 1: error
          ^
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_nested_test", line 8, in <module>
+     plpy.execute("error")
  QUERY:  error
  CONTEXT:  PL/Python function "subtransaction_nested_test"
  SELECT * FROM subtransaction_tbl;
*************** with plpy.subtransaction() as s:
*** 230,235 ****
--- 245,253 ----
  $$ LANGUAGE plpythonu;
  SELECT subtransaction_exit_without_enter();
  ERROR:  ValueError: this subtransaction has not been entered
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_exit_without_enter", line 2, in <module>
+     plpy.subtransaction().__exit__(None, None, None)
  CONTEXT:  PL/Python function "subtransaction_exit_without_enter"
  SELECT subtransaction_enter_without_exit();
  WARNING:  forcibly aborting a subtransaction that has not been exited
*************** SELECT subtransaction_exit_twice();
*** 243,248 ****
--- 261,269 ----
  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
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_exit_twice", line 3, in <module>
+     plpy.subtransaction().__exit__(None, None, None)
  CONTEXT:  PL/Python function "subtransaction_exit_twice"
  SELECT subtransaction_enter_twice();
  WARNING:  forcibly aborting a subtransaction that has not been exited
*************** CONTEXT:  PL/Python function "subtransac
*** 256,272 ****
--- 277,305 ----
  
  SELECT subtransaction_exit_same_subtransaction_twice();
  ERROR:  ValueError: this subtransaction has already been exited
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_exit_same_subtransaction_twice", line 5, in <module>
+     s.__exit__(None, None, None)
  CONTEXT:  PL/Python function "subtransaction_exit_same_subtransaction_twice"
  SELECT subtransaction_enter_same_subtransaction_twice();
  WARNING:  forcibly aborting a subtransaction that has not been exited
  CONTEXT:  PL/Python function "subtransaction_enter_same_subtransaction_twice"
  ERROR:  ValueError: this subtransaction has already been entered
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_enter_same_subtransaction_twice", line 4, in <module>
+     s.__enter__()
  CONTEXT:  PL/Python function "subtransaction_enter_same_subtransaction_twice"
  SELECT subtransaction_enter_subtransaction_in_with();
  ERROR:  ValueError: this subtransaction has already been entered
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_enter_subtransaction_in_with", line 3, in <module>
+     s.__enter__()
  CONTEXT:  PL/Python function "subtransaction_enter_subtransaction_in_with"
  SELECT subtransaction_exit_subtransaction_in_with();
  ERROR:  ValueError: this subtransaction has already been exited
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_exit_subtransaction_in_with", line 3, in <module>
+     s.__exit__(None, None, None)
  CONTEXT:  PL/Python function "subtransaction_exit_subtransaction_in_with"
  -- Make sure we don't get a "current transaction is aborted" error
  SELECT 1 as test;
diff --git a/src/pl/plpython/expected/plpython_subtransaction_0.out b/src/pl/plpython/expected/plpython_subtransaction_0.out
index 164e987..e760aac 100644
*** a/src/pl/plpython/expected/plpython_subtransaction_0.out
--- b/src/pl/plpython/expected/plpython_subtransaction_0.out
*************** SELECT subtransaction_test('SPI');
*** 46,51 ****
--- 46,54 ----
  ERROR:  spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops"
  LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
                                                 ^
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_test", line 11, in <module>
+     plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
  QUERY:  INSERT INTO subtransaction_tbl VALUES ('oops')
  CONTEXT:  PL/Python function "subtransaction_test"
  SELECT * FROM subtransaction_tbl;
*************** SELECT * FROM subtransaction_tbl;
*** 56,61 ****
--- 59,67 ----
  TRUNCATE subtransaction_tbl;
  SELECT subtransaction_test('Python');
  ERROR:  AttributeError: 'module' object has no attribute 'attribute_error'
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_test", line 13, in <module>
+     plpy.attribute_error
  CONTEXT:  PL/Python function "subtransaction_test"
  SELECT * FROM subtransaction_tbl;
   i 
*************** ERROR:  could not compile PL/Python func
*** 223,228 ****
--- 229,237 ----
  DETAIL:  SyntaxError: invalid syntax (line 3)
  SELECT subtransaction_exit_without_enter();
  ERROR:  ValueError: this subtransaction has not been entered
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_exit_without_enter", line 2, in <module>
+     plpy.subtransaction().__exit__(None, None, None)
  CONTEXT:  PL/Python function "subtransaction_exit_without_enter"
  SELECT subtransaction_enter_without_exit();
  WARNING:  forcibly aborting a subtransaction that has not been exited
*************** SELECT subtransaction_exit_twice();
*** 236,241 ****
--- 245,253 ----
  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
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_exit_twice", line 3, in <module>
+     plpy.subtransaction().__exit__(None, None, None)
  CONTEXT:  PL/Python function "subtransaction_exit_twice"
  SELECT subtransaction_enter_twice();
  WARNING:  forcibly aborting a subtransaction that has not been exited
*************** CONTEXT:  PL/Python function "subtransac
*** 249,259 ****
--- 261,277 ----
  
  SELECT subtransaction_exit_same_subtransaction_twice();
  ERROR:  ValueError: this subtransaction has already been exited
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_exit_same_subtransaction_twice", line 5, in <module>
+     s.__exit__(None, None, None)
  CONTEXT:  PL/Python function "subtransaction_exit_same_subtransaction_twice"
  SELECT subtransaction_enter_same_subtransaction_twice();
  WARNING:  forcibly aborting a subtransaction that has not been exited
  CONTEXT:  PL/Python function "subtransaction_enter_same_subtransaction_twice"
  ERROR:  ValueError: this subtransaction has already been entered
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "subtransaction_enter_same_subtransaction_twice", line 4, in <module>
+     s.__enter__()
  CONTEXT:  PL/Python function "subtransaction_enter_same_subtransaction_twice"
  SELECT subtransaction_enter_subtransaction_in_with();
  ERROR:  function subtransaction_enter_subtransaction_in_with() does not exist
diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out
index 7b2e170..5620031 100644
*** a/src/pl/plpython/expected/plpython_test.out
--- b/src/pl/plpython/expected/plpython_test.out
*************** CONTEXT:  PL/Python function "elog_test"
*** 74,77 ****
--- 74,80 ----
  WARNING:  warning
  CONTEXT:  PL/Python function "elog_test"
  ERROR:  plpy.Error: error
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "elog_test", line 10, in <module>
+     plpy.error('error')
  CONTEXT:  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..9fc92dc 100644
*** a/src/pl/plpython/expected/plpython_types.out
--- b/src/pl/plpython/expected/plpython_types.out
*************** SELECT name, test_composite_table_input(
*** 625,630 ****
--- 625,633 ----
  ALTER TABLE employee DROP bonus;
  SELECT name, test_composite_table_input(employee.*) FROM employee;
  ERROR:  KeyError: 'bonus'
+ DETAIL:  Traceback (most recent call last):
+   PL/Python function "test_composite_table_input", line 2, in <module>
+     return e['basesalary'] + e['bonus']
  CONTEXT:  PL/Python function "test_composite_table_input"
  ALTER TABLE employee ADD bonus integer;
  UPDATE employee SET bonus = 10;
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index 4cc0708..5f5c025 100644
*** a/src/pl/plpython/plpython.c
--- b/src/pl/plpython/plpython.c
*************** typedef int Py_ssize_t;
*** 71,76 ****
--- 71,77 ----
   */
  #if PY_MAJOR_VERSION >= 3
  #define PyInt_FromLong(x) PyLong_FromLong(x)
+ #define PyInt_AsLong(x) PyLong_AsLong(x)
  #endif
  
  /*
*************** typedef struct PLyProcedure
*** 217,222 ****
--- 218,224 ----
  								 * type */
  	bool		is_setof;		/* true, if procedure returns result set */
  	PyObject   *setof;			/* contents of result set. */
+ 	char		*src;			/* textual procedure code, after mangling */
  	char	  **argnames;		/* Argument names */
  	PLyTypeInfo args[FUNC_MAX_ARGS];
  	int			nargs;
*************** static void
*** 342,348 ****
  PLy_elog(int, const char *,...)
  __attribute__((format(printf, 2, 3)));
  static void PLy_get_spi_error_data(PyObject *exc, char **detail, char **hint, char **query, int *position);
! static char *PLy_traceback(int *);
  
  static void *PLy_malloc(size_t);
  static void *PLy_malloc0(size_t);
--- 344,350 ----
  PLy_elog(int, const char *,...)
  __attribute__((format(printf, 2, 3)));
  static void PLy_get_spi_error_data(PyObject *exc, char **detail, char **hint, char **query, int *position);
! static void PLy_traceback(char **, char **, int *);
  
  static void *PLy_malloc(size_t);
  static void *PLy_malloc0(size_t);
*************** PLy_function_handler(FunctionCallInfo fc
*** 1157,1165 ****
  				PLy_function_delete_args(proc);
  
  				if (has_error)
! 					ereport(ERROR,
! 							(errcode(ERRCODE_DATA_EXCEPTION),
! 						  errmsg("error fetching next item from iterator")));
  
  				/* Disconnect from the SPI manager before returning */
  				if (SPI_finish() != SPI_OK_FINISH)
--- 1159,1165 ----
  				PLy_function_delete_args(proc);
  
  				if (has_error)
! 					PLy_elog(ERROR, "error fetching next item from iterator");
  
  				/* Disconnect from the SPI manager before returning */
  				if (SPI_finish() != SPI_OK_FINISH)
*************** PLy_procedure_create(HeapTuple procTup,
*** 1605,1610 ****
--- 1605,1611 ----
  	proc->is_setof = procStruct->proretset;
  	proc->setof = NULL;
  	proc->argnames = NULL;
+ 	proc->src = NULL;
  
  	PG_TRY();
  	{
*************** PLy_procedure_compile(PLyProcedure *proc
*** 1790,1795 ****
--- 1791,1798 ----
  	 * insert the function code into the interpreter
  	 */
  	msrc = PLy_procedure_munge_source(proc->pyname, src);
+ 	/* Save the mangled source for later inclusion in tracebacks */
+ 	proc->src = PLy_strdup(msrc);
  	crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL);
  	pfree(msrc);
  
*************** PLy_procedure_delete(PLyProcedure *proc)
*** 1887,1892 ****
--- 1890,1897 ----
  		if (proc->argnames && proc->argnames[i])
  			PLy_free(proc->argnames[i]);
  	}
+ 	if (proc->src)
+ 		PLy_free(proc->src);
  	if (proc->argnames)
  		PLy_free(proc->argnames);
  }
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3452,3459 ****
  			PLy_elog(ERROR, "could not execute plan");
  		sv = PyString_AsString(so);
  		PLy_exception_set_plural(PyExc_TypeError,
! 							  "Expected sequence of %d argument, got %d: %s",
! 							 "Expected sequence of %d arguments, got %d: %s",
  								 plan->nargs,
  								 plan->nargs, nargs, sv);
  		Py_DECREF(so);
--- 3457,3464 ----
  			PLy_elog(ERROR, "could not execute plan");
  		sv = PyString_AsString(so);
  		PLy_exception_set_plural(PyExc_TypeError,
! 								 "Expected sequence of %d argument, got %d: %s",
! 								 "Expected sequence of %d arguments, got %d: %s",
  								 plan->nargs,
  								 plan->nargs, nargs, sv);
  		Py_DECREF(so);
*************** failure:
*** 4357,4429 ****
   * the current Python error, previously set by PLy_exception_set().
   * This should be used to propagate Python errors into PG.	If fmt is
   * NULL, the Python error becomes the primary error message, otherwise
!  * it becomes the detail.
   */
  static void
  PLy_elog(int elevel, const char *fmt,...)
  {
- 	char	   *xmsg;
- 	int			xlevel;
- 	StringInfoData emsg;
  	PyObject	*exc, *val, *tb;
  	char		*detail = NULL;
  	char		*hint = NULL;
  	char		*query = NULL;
  	int			position = 0;
  
  	PyErr_Fetch(&exc, &val, &tb);
! 	if (exc != NULL && PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
! 		PLy_get_spi_error_data(val, &detail, &hint, &query, &position);
  	PyErr_Restore(exc, val, tb);
  
! 	xmsg = PLy_traceback(&xlevel);
  
  	if (fmt)
  	{
! 		initStringInfo(&emsg);
  		for (;;)
  		{
  			va_list		ap;
  			bool		success;
  
  			va_start(ap, fmt);
! 			success = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap);
  			va_end(ap);
  			if (success)
  				break;
! 			enlargeStringInfo(&emsg, emsg.maxlen);
  		}
  	}
  
  	PG_TRY();
  	{
  		if (fmt)
! 			ereport(elevel,
! 					(errmsg("%s", emsg.data),
! 					 (xmsg) ? errdetail("%s", xmsg) : 0,
! 					 (hint) ? errhint("%s", hint) : 0,
! 					 (query) ? internalerrquery(query) : 0,
! 					 (position) ? internalerrposition(position) : 0));
  		else
! 			ereport(elevel,
! 					(errmsg("%s", xmsg),
! 					 (detail) ? errdetail("%s", detail) : 0,
! 					 (hint) ? errhint("%s", hint) : 0,
! 					 (query) ? internalerrquery(query) : 0,
! 					 (position) ? internalerrposition(position) : 0));
  	}
  	PG_CATCH();
  	{
! 		if (fmt)
! 			pfree(emsg.data);
  		if (xmsg)
  			pfree(xmsg);
  		PG_RE_THROW();
  	}
  	PG_END_TRY();
  
! 	if (fmt)
! 		pfree(emsg.data);
  	if (xmsg)
  		pfree(xmsg);
  }
--- 4362,4486 ----
   * the current Python error, previously set by PLy_exception_set().
   * This should be used to propagate Python errors into PG.	If fmt is
   * NULL, the Python error becomes the primary error message, otherwise
!  * it becomes the detail. If there is a Python traceback, it is also put
!  * in the detail.
   */
  static void
  PLy_elog(int elevel, const char *fmt,...)
  {
  	PyObject	*exc, *val, *tb;
  	char		*detail = NULL;
  	char		*hint = NULL;
  	char		*query = NULL;
  	int			position = 0;
+ 	char		*fmsg = NULL;
+ 	char		*emsg = NULL;
+ 	char		*xmsg = NULL;
+ 	int			tb_depth = 0;
  
  	PyErr_Fetch(&exc, &val, &tb);
! 	if (exc != NULL)
! 	{
! 		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);
  
! 	/* this is a no-op if there is no current Python exception */
! 	PLy_traceback(&emsg, &xmsg, &tb_depth);
  
  	if (fmt)
  	{
! 		StringInfoData	si;
! 		initStringInfo(&si);
  		for (;;)
  		{
  			va_list		ap;
  			bool		success;
  
  			va_start(ap, fmt);
! 			success = appendStringInfoVA(&si, dgettext(TEXTDOMAIN, fmt), ap);
  			va_end(ap);
  			if (success)
  				break;
! 			enlargeStringInfo(&si, si.maxlen);
  		}
+ 		fmsg = si.data;
  	}
  
  	PG_TRY();
  	{
+ 		/* If we have a format string, it should be the main error message and
+ 		 * the emsg + 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)
! 		{
! 			if (tb_depth > 0)
! 			{
! 				ereport(elevel,
! 						(errmsg("%s", fmsg),
! 						 (detail) ? errdetail("%s", detail) : 0,
! 						 (emsg) ? errdetail("%s", emsg) : 0,
! 						 (xmsg) ? errdetail("%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
! 		{
! 			if (tb_depth > 0)
! 			{
! 				ereport(elevel,
! 						(errmsg("%s", emsg ? emsg : "no exception data"),
! 						 (detail) ? errdetail("%s", detail) : 0,
! 						 (xmsg) ? errdetail("%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 (fmsg)
! 			pfree(fmsg);
! 		if (emsg)
! 			pfree(emsg);
  		if (xmsg)
  			pfree(xmsg);
  		PG_RE_THROW();
  	}
  	PG_END_TRY();
  
! 	pfree(emsg);
  	if (xmsg)
  		pfree(xmsg);
  }
*************** cleanup:
*** 4450,4457 ****
  }
  
  
  static char *
! PLy_traceback(int *xlevel)
  {
  	PyObject   *e,
  			   *v,
--- 4507,4550 ----
  }
  
  
+ /* Get the given source line as a palloc'd string */
  static char *
! get_source_line(char *src, int lineno)
! {
! 	char	*s;
! 	char	*next;
! 	int		current = 0;
! 
! 	next = src;
! 	while (current != lineno)
! 	{
! 		s = next;
! 		next = strchr(s + 1, '\n');
! 		current++;
! 		if (next == NULL)
! 			break;
! 	}
! 
! 	if (current != lineno)
! 		return NULL;
! 
! 	while (s && isspace(*s))
! 		s++;
! 
! 	if (next == NULL)
! 		return pstrdup(s);
! 
! 	return pnstrdup(s, next - s);
! }
! 
! /*
!  * Extract a Python traceback from the current exception.
!  *
!  * The exception error message is returned in emsg, the traceback in xmsg (both
!  * as palloc's strings) and the traceback depth in tb_depth.
!  */
! static void
! PLy_traceback(char **emsg, char **xmsg, int *tb_depth)
  {
  	PyObject   *e,
  			   *v,
*************** PLy_traceback(int *xlevel)
*** 4462,4467 ****
--- 4555,4561 ----
  	char	   *e_module_s = NULL;
  	PyObject   *vob = NULL;
  	char	   *vstr;
+ 	StringInfoData estr;
  	StringInfoData xstr;
  
  	/*
*************** PLy_traceback(int *xlevel)
*** 4473,4485 ****
  	 * oops, no exception, return
  	 */
  	if (e == NULL)
! 	{
! 		*xlevel = WARNING;
! 		return NULL;
! 	}
  
  	PyErr_NormalizeException(&e, &v, &tb);
! 	Py_XDECREF(tb);
  
  	e_type_o = PyObject_GetAttrString(e, "__name__");
  	e_module_o = PyObject_GetAttrString(e, "__module__");
--- 4567,4577 ----
  	 * oops, no exception, return
  	 */
  	if (e == NULL)
! 		return;
  
  	PyErr_NormalizeException(&e, &v, &tb);
! 
! 	/* format the exception and its value and put it in emsg */
  
  	e_type_o = PyObject_GetAttrString(e, "__name__");
  	e_module_o = PyObject_GetAttrString(e, "__module__");
*************** PLy_traceback(int *xlevel)
*** 4493,4534 ****
  	else
  		vstr = "unknown";
  
! 	initStringInfo(&xstr);
  	if (!e_type_s || !e_module_s)
  	{
  		if (PyString_Check(e))
  			/* deprecated string exceptions */
! 			appendStringInfoString(&xstr, PyString_AsString(e));
  		else
  			/* shouldn't happen */
! 			appendStringInfoString(&xstr, "unrecognized exception");
  	}
  	/* mimics behavior of traceback.format_exception_only */
  	else if (strcmp(e_module_s, "builtins") == 0
  			 || strcmp(e_module_s, "__main__") == 0
  			 || strcmp(e_module_s, "exceptions") == 0)
! 		appendStringInfo(&xstr, "%s", e_type_s);
  	else
! 		appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s);
! 	appendStringInfo(&xstr, ": %s", vstr);
  
  	Py_XDECREF(e_type_o);
  	Py_XDECREF(e_module_o);
  	Py_XDECREF(vob);
  	Py_XDECREF(v);
- 
- 	/*
- 	 * intuit an appropriate error level based on the exception type
- 	 */
- 	if (PLy_exc_error && PyErr_GivenExceptionMatches(e, PLy_exc_error))
- 		*xlevel = ERROR;
- 	else if (PLy_exc_fatal && PyErr_GivenExceptionMatches(e, PLy_exc_fatal))
- 		*xlevel = FATAL;
- 	else
- 		*xlevel = ERROR;
- 
  	Py_DECREF(e);
- 	return xstr.data;
  }
  
  /* python module code */
--- 4585,4722 ----
  	else
  		vstr = "unknown";
  
! 	initStringInfo(&estr);
  	if (!e_type_s || !e_module_s)
  	{
  		if (PyString_Check(e))
  			/* deprecated string exceptions */
! 			appendStringInfoString(&estr, PyString_AsString(e));
  		else
  			/* shouldn't happen */
! 			appendStringInfoString(&estr, "unrecognized exception");
  	}
  	/* mimics behavior of traceback.format_exception_only */
  	else if (strcmp(e_module_s, "builtins") == 0
  			 || strcmp(e_module_s, "__main__") == 0
  			 || strcmp(e_module_s, "exceptions") == 0)
! 		appendStringInfo(&estr, "%s", e_type_s);
  	else
! 		appendStringInfo(&estr, "%s.%s", e_module_s, e_type_s);
! 	appendStringInfo(&estr, ": %s", vstr);
! 
! 	*emsg = estr.data;
! 
! 	/* now format the traceback and put it in xmsg */
! 	*tb_depth = 0;
! 	initStringInfo(&xstr);
! 	/* Mimick Python traceback reporting as close as possible */
! 	appendStringInfoString(&xstr, "Traceback (most recent call last):");
! 	while (tb != NULL && tb != Py_None)
! 	{
! 		PyObject	*volatile tb_prev = NULL;
! 		PyObject	*volatile frame = NULL;
! 		PyObject	*volatile code = NULL;
! 		PyObject	*volatile name = NULL;
! 		PyObject	*volatile lineno = NULL;
! 
! 		PG_TRY();
! 		{
! 			lineno = PyObject_GetAttrString(tb, "tb_lineno");
! 			if (lineno == NULL)
! 				elog(ERROR, "could not get line number from Python traceback");
! 
! 			frame = PyObject_GetAttrString(tb, "tb_frame");
! 			if (frame == NULL)
! 				elog(ERROR, "could not get frame from Python traceback");
! 
! 			code = PyObject_GetAttrString(frame, "f_code");
! 			if (code == NULL)
! 				elog(ERROR, "could not get code object from Python frame");
! 
! 			name = PyObject_GetAttrString(code, "co_name");
! 			if (name == NULL)
! 				elog(ERROR, "could not get function name from Python code object");
! 		}
! 		PG_CATCH();
! 		{
! 			Py_XDECREF(frame);
! 			Py_XDECREF(code);
! 			Py_XDECREF(name);
! 			Py_XDECREF(lineno);
! 			PG_RE_THROW();
! 		}
! 		PG_END_TRY();
! 
! 		/* The first frame always points at <module>, skip it */
! 		if (*tb_depth > 0)
! 		{
! 			char	*proname;
! 			char	*fname;
! 			char	*line;
! 			long	plain_lineno;
! 
! 			/*
! 			 * The second frame points at the internal function, but to mimick
! 			 * Python error reporting we want to say <module>
! 			 */
! 			if (*tb_depth == 1)
! 				fname = "<module>";
! 			else
! 				fname = PyString_AsString(name);
! 
! 			proname = PLy_procedure_name(PLy_curr_procedure);
! 			plain_lineno = PyInt_AsLong(lineno);
! 
! 			if (proname == NULL)
! 				appendStringInfo(
! 					&xstr, "\n  PL/Python anonymous code block, line %ld, in %s",
! 					plain_lineno - 1, fname);
! 			else
! 				appendStringInfo(
! 					&xstr, "\n  PL/Python function \"%s\", line %ld, in %s",
! 					proname, plain_lineno - 1, fname);
! 
! 			if (PLy_curr_procedure)
! 			{
! 				/*
! 				 * If we know the current procedure, append the exact line from
! 				 * the source, again mimicking Python's traceback.py module
! 				 * behaviour. We could store the already line-splitted source
! 				 * to avoid splitting it every time, but producing a traceback
! 				 * is not the most important scenario to optimise for.
! 				 */
! 				line = get_source_line(PLy_curr_procedure->src, plain_lineno);
! 				if (line != NULL)
! 				{
! 					appendStringInfo(&xstr, "\n    %s", line);
! 					pfree(line);
! 				}
! 			}
! 		}
! 
! 		Py_DECREF(frame);
! 		Py_DECREF(code);
! 		Py_DECREF(name);
! 		Py_DECREF(lineno);
! 
! 		/* Release the current frame and go to the next one */
! 		tb_prev = tb;
! 		tb = PyObject_GetAttrString(tb, "tb_next");
! 		Assert(tb_prev != Py_None);
! 		Py_DECREF(tb_prev);
! 		if (tb == NULL)
! 			elog(ERROR, "could not traverse Python traceback");
! 		(*tb_depth)++;
! 	}
! 
! 	/* Return the traceback */
! 	*xmsg = xstr.data;
  
  	Py_XDECREF(e_type_o);
  	Py_XDECREF(e_module_o);
  	Py_XDECREF(vob);
  	Py_XDECREF(v);
  	Py_DECREF(e);
  }
  
  /* python module code */
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
index 0f456f4..fe11042 100644
*** a/src/pl/plpython/sql/plpython_error.sql
--- b/src/pl/plpython/sql/plpython_error.sql
*************** return None
*** 131,136 ****
--- 131,205 ----
  
  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();
+ 
  /* check catching specific types of exceptions
   */
  CREATE TABLE specific (
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to