On 06/02/11 20:12, Jan Urbański wrote:
> On 27/01/11 22:58, Jan Urbański wrote:
>> On 23/12/10 14:56, Jan Urbański wrote:
>>> Here's a patch implementing traceback support for PL/Python mentioned in
>>> http://archives.postgresql.org/pgsql-hackers/2010-12/msg01991.php. It's
>>> an incremental patch on top of the plpython-refactor patch sent eariler.
>>
>> Updated to master.
> 
> Updated to master again.

Once more.
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 7597ca7..08b6ba4 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:  plpy.SPIError: 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:  plpy.SPIError: 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:  plpy.SPIError: 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"
  /* manually starting subtransactions - a bad idea
   */
  CREATE FUNCTION manual_subxact() RETURNS void AS $$
*************** plpy.execute("rollback to save")
*** 149,154 ****
--- 259,267 ----
  $$ 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)
*** 161,164 ****
--- 274,280 ----
  $$ 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 42e4119..318f5e2 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:  plpy.SPIError: 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:  plpy.SPIError: 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:  plpy.SPIError: 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"
  /* manually starting subtransactions - a bad idea
   */
  CREATE FUNCTION manual_subxact() RETURNS void AS $$
*************** plpy.execute("rollback to save")
*** 149,154 ****
--- 259,267 ----
  $$ 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)
*** 161,164 ****
--- 274,280 ----
  $$ 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_test.out b/src/pl/plpython/expected/plpython_test.out
index d92c987..f3bdb3b 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/plpython.c b/src/pl/plpython/plpython.c
index fff7de7..436f546 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
*** 210,215 ****
--- 211,217 ----
  								 * 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
*** 299,305 ****
  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);
--- 301,307 ----
  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
*** 1104,1112 ****
  				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)
--- 1106,1112 ----
  				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,
*** 1440,1445 ****
--- 1440,1446 ----
  	proc->is_setof = procStruct->proretset;
  	proc->setof = NULL;
  	proc->argnames = NULL;
+ 	proc->src = NULL;
  
  	PG_TRY();
  	{
*************** PLy_procedure_compile(PLyProcedure *proc
*** 1620,1625 ****
--- 1621,1628 ----
  	 * 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)
*** 1717,1722 ****
--- 1720,1727 ----
  		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
*** 3068,3075 ****
  			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);
--- 3073,3080 ----
  			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:
*** 3706,3778 ****
   * 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);
  }
--- 3711,3826 ----
   * 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 && PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
  		PLy_get_spi_error_data(val, &detail, &hint, &query, &position);
  	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),
! 						 (emsg) ? errdetail("%s\n%s", emsg, xmsg) : 0,
! 						 (hint) ? errhint("%s", hint) : 0,
! 						 (query) ? internalerrquery(query) : 0,
! 						 (position) ? internalerrposition(position) : 0));
! 			}
! 			else
! 			{
! 				ereport(elevel,
! 						(errmsg("%s", fmsg),
! 						 (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\n%s", detail, xmsg) : errdetail("%s", xmsg),
! 						 (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:
*** 3799,3806 ****
  }
  
  
  static char *
! PLy_traceback(int *xlevel)
  {
  	PyObject   *e,
  			   *v,
--- 3847,3890 ----
  }
  
  
+ /* 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)
*** 3811,3816 ****
--- 3895,3901 ----
  	char	   *e_module_s = NULL;
  	PyObject   *vob = NULL;
  	char	   *vstr;
+ 	StringInfoData estr;
  	StringInfoData xstr;
  
  	/*
*************** PLy_traceback(int *xlevel)
*** 3822,3834 ****
  	 * 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__");
--- 3907,3917 ----
  	 * 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)
*** 3842,3883 ****
  	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 */
--- 3925,4062 ----
  	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 7861cd6..5d5477e 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();
+ 
  /* manually starting subtransactions - a bad idea
   */
  CREATE FUNCTION manual_subxact() RETURNS void AS $$
-- 
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