pl/python tracebacks
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.
Git branch for this patch:
https://github.com/wulczer/postgres/tree/tracebacks.
It's a variant of
http://archives.postgresql.org/pgsql-patches/2006-02/msg00288.php with a
few more twists.
For errors originating from Python exceptions add the traceback as the
message detail. The patch tries to mimick Python's traceback.py module
behaviour as close as possible, icluding interleaving stack frames with
source code lines in the detail message. Any Python developer should
instantly recognize these kind of error reporting, it looks almost the
same as an error in the interactive Python shell.
A future optimisation might be not splitting the procedure source each
time a traceback is generated, but for now it's probably not the most
important scenario to optimise for.
Cheers,
Jan
Attachments:
plpython-tracebacks.difftext/x-patch; name=plpython-tracebacks.diffDownload
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 70890a8..fe8a91f 100644
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** SELECT sql_syntax_error();
*** 11,16 ****
--- 11,19 ----
WARNING: plpy.SPIError: unrecognized error in PLy_spi_execute_query
CONTEXT: PL/Python function "sql_syntax_error"
ERROR: plpy.SPIError: syntax error at or near "syntax"
+ DETAIL: Traceback (most recent call last):
+ PL/Python function "sql_syntax_error", line 1, in <module>
+ plpy.execute("syntax error")
CONTEXT: PL/Python function "sql_syntax_error"
/* check the handling of uncaught python exceptions
*/
*************** CREATE FUNCTION exception_index_invalid(
*** 20,25 ****
--- 23,31 ----
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();
*** 32,37 ****
--- 38,46 ----
WARNING: plpy.SPIError: unrecognized error in PLy_spi_execute_query
CONTEXT: PL/Python function "exception_index_invalid_nested"
ERROR: plpy.SPIError: function test5(unknown) does not exist
+ DETAIL: Traceback (most recent call last):
+ PL/Python function "exception_index_invalid_nested", line 1, in <module>
+ rv = plpy.execute("SELECT test5('foo')")
CONTEXT: PL/Python function "exception_index_invalid_nested"
/* a typo
*/
*************** SELECT invalid_type_uncaught('rick');
*** 50,55 ****
--- 59,67 ----
WARNING: plpy.SPIError: unrecognized error in PLy_spi_prepare
CONTEXT: PL/Python function "invalid_type_uncaught"
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
*************** SELECT invalid_type_reraised('rick');
*** 100,105 ****
--- 112,120 ----
WARNING: plpy.SPIError: unrecognized error in PLy_spi_prepare
CONTEXT: PL/Python function "invalid_type_reraised"
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');
*** 119,121 ****
--- 134,231 ----
(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"
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 67eb0f3..8e549bd 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
*** 208,213 ****
--- 209,215 ----
* 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 char *PLy_procedure_name(PLyProce
*** 288,294 ****
static void
PLy_elog(int, const char *,...)
__attribute__((format(printf, 2, 3)));
! static char *PLy_traceback(int *);
static void *PLy_malloc(size_t);
static void *PLy_malloc0(size_t);
--- 290,296 ----
static void
PLy_elog(int, const char *,...)
__attribute__((format(printf, 2, 3)));
! static void PLy_traceback(char **, char **, int*, int *);
static void *PLy_malloc(size_t);
static void *PLy_malloc0(size_t);
*************** PLy_function_handler(FunctionCallInfo fc
*** 1056,1064 ****
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)
--- 1058,1064 ----
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,
*** 1523,1528 ****
--- 1523,1529 ----
proc->is_setof = procStruct->proretset;
proc->setof = NULL;
proc->argnames = NULL;
+ proc->src = NULL;
PG_TRY();
{
*************** PLy_procedure_compile(PLyProcedure *proc
*** 1590,1595 ****
--- 1591,1598 ----
* 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)
*** 1687,1692 ****
--- 1690,1697 ----
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_exception_set_plural(PyObject *exc,
*** 3564,3625 ****
* 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;
! 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));
else
ereport(elevel,
! (errmsg("%s", xmsg)));
}
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);
}
static char *
! PLy_traceback(int *xlevel)
{
PyObject *e,
*v,
--- 3569,3698 ----
* 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,...)
{
! char *fmsg = NULL;
! char *emsg = NULL;
! char *xmsg = NULL;
! int tb_depth = 0;
! int errorposition = 0;
! /* this is a no-op if there is no current Python exception */
! PLy_traceback(&emsg, &xmsg, &tb_depth, &errorposition);
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));
! }
! else
! {
! ereport(elevel,
! (errmsg("%s", fmsg),
! emsg ? errdetail("%s", emsg) : 0));
! }
! }
else
+ {
ereport(elevel,
! (errmsg("%s", emsg ? emsg : "no exception data"),
! (tb_depth > 0) ? errdetail("%s", xmsg) : 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);
}
+ /* 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. errorposition is
! * the position of the error pointer, currently always set to 0, but we could
! * try to extract it for syntax errors.
! */
! static void
! PLy_traceback(char **emsg, char **xmsg, int *tb_depth, int *errorposition)
{
PyObject *e,
*v,
*************** PLy_traceback(int *xlevel)
*** 3630,3635 ****
--- 3703,3709 ----
char *e_module_s = NULL;
PyObject *vob = NULL;
char *vstr;
+ StringInfoData estr;
StringInfoData xstr;
/*
*************** PLy_traceback(int *xlevel)
*** 3641,3653 ****
* 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__");
--- 3715,3725 ----
* 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)
*** 3661,3702 ****
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 */
--- 3733,3870 ----
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 5ca6849..e9b819c 100644
*** a/src/pl/plpython/sql/plpython_error.sql
--- b/src/pl/plpython/sql/plpython_error.sql
*************** return None
*** 107,109 ****
--- 107,178 ----
LANGUAGE plpythonu;
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();
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.
Attachments:
plpython-tracebacks.patchtext/x-patch; name=plpython-tracebacks.patchDownload
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 ea4a54c..1e6295e 100644
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** CONTEXT: PL/Python function "sql_syntax
*** 13,18 ****
--- 13,21 ----
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(
*** 23,28 ****
--- 26,34 ----
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
*/
*************** CONTEXT: PL/Python function "exception_
*** 37,42 ****
--- 43,51 ----
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"
*************** SELECT invalid_type_uncaught('rick');
*** 57,62 ****
--- 66,74 ----
WARNING: plpy.SPIError: unrecognized error in PLy_spi_prepare
CONTEXT: PL/Python function "invalid_type_uncaught"
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
*************** SELECT invalid_type_reraised('rick');
*** 107,112 ****
--- 119,127 ----
WARNING: plpy.SPIError: unrecognized error in PLy_spi_prepare
CONTEXT: PL/Python function "invalid_type_reraised"
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');
*** 126,128 ****
--- 141,238 ----
(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"
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 aafe556..f2d8bdc 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
*** 209,214 ****
--- 210,216 ----
* 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
*** 295,301 ****
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);
--- 297,303 ----
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
*** 1064,1072 ****
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)
--- 1066,1072 ----
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,
*** 1400,1405 ****
--- 1400,1406 ----
proc->is_setof = procStruct->proretset;
proc->setof = NULL;
proc->argnames = NULL;
+ proc->src = NULL;
PG_TRY();
{
*************** PLy_procedure_compile(PLyProcedure *proc
*** 1580,1585 ****
--- 1581,1588 ----
* 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)
*** 1677,1682 ****
--- 1680,1687 ----
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
*** 2999,3006 ****
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);
--- 3004,3011 ----
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:
*** 3573,3645 ****
* 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);
}
--- 3578,3693 ----
* 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:
*** 3666,3673 ****
}
static char *
! PLy_traceback(int *xlevel)
{
PyObject *e,
*v,
--- 3714,3757 ----
}
+ /* 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)
*** 3678,3683 ****
--- 3762,3768 ----
char *e_module_s = NULL;
PyObject *vob = NULL;
char *vstr;
+ StringInfoData estr;
StringInfoData xstr;
/*
*************** PLy_traceback(int *xlevel)
*** 3689,3701 ****
* 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__");
--- 3774,3784 ----
* 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)
*** 3709,3750 ****
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 */
--- 3792,3929 ----
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 5ca6849..e9b819c 100644
*** a/src/pl/plpython/sql/plpython_error.sql
--- b/src/pl/plpython/sql/plpython_error.sql
*************** return None
*** 107,109 ****
--- 107,178 ----
LANGUAGE plpythonu;
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();
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.
Attachments:
plpython-tracebacks.patchtext/x-patch; name=plpython-tracebacks.patchDownload
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 $$
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.
Attachments:
plpython-tracebacks.patchtext/x-patch; name=plpython-tracebacks.patchDownload
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 $$
On Wed, Feb 9, 2011 at 4:10 AM, Jan Urbański <wulczer@wulczer.org> wrote:
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.
Alex Hunsaker is listed as the reviewer for this patch, but I don't
see a review posted. If this feature is still wanted for 9.1, can
someone jump in here?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Fri, Feb 11, 2011 at 08:45, Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Feb 9, 2011 at 4:10 AM, Jan Urbański <wulczer@wulczer.org> wrote:
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.
Alex Hunsaker is listed as the reviewer for this patch, but I don't
see a review posted. If this feature is still wanted for 9.1, can
someone jump in here?
Goodness... I picked up this patch the day before yesterday because
no-one was listed. That being said, if anyone else wants to beat me to
the punch of reviewing this, have at it! The more eyes the merrier!
I wish I could squeeze
the lime of my time to find
a few more hours
On Fri, Feb 11, 2011 at 11:22 AM, Alex Hunsaker <badalex@gmail.com> wrote:
On Fri, Feb 11, 2011 at 08:45, Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Feb 9, 2011 at 4:10 AM, Jan Urbański <wulczer@wulczer.org> wrote:
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.
Alex Hunsaker is listed as the reviewer for this patch, but I don't
see a review posted. If this feature is still wanted for 9.1, can
someone jump in here?Goodness... I picked up this patch the day before yesterday because
no-one was listed. That being said, if anyone else wants to beat me to
the punch of reviewing this, have at it! The more eyes the merrier!
Sorry, I didn't see when you'd picked it up. I was just keeping an
eye on my wall calendar.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Robert Haas <robertmhaas@gmail.com> wrote:
Goodness... I picked up this patch the day before yesterday
because no-one was listed. That being said, if anyone else wants
to beat me to the punch of reviewing this, have at it! The more
eyes the merrier!Sorry, I didn't see when you'd picked it up. I was just keeping
an eye on my wall calendar.
[OT]
FWIW, this is the sort of situation which caused me to suggest that
the web app somehow show the date of the last reviewer change when
it is past the "Last Activity" date. I don't really care whether it
would be in the Reviewers column or as a second line, in
parentheses, in the Last Activity column. I would find it useful
when managing a CF, anyway....
-Kevin
On Wed, Feb 9, 2011 at 02:10, Jan Urbański <wulczer@wulczer.org> wrote:
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.
In PLy_traceback fname and prname look like they will leak (well as
much as a palloc() in an error path can leak I suppose). Other than
that everything looks good. I tested plpython2 and plpython3 and
skimmed the docs to see if there was anything obvious that needed
updating. I also obviously looked at the added regression tests and
made sure they worked.
Marking as Ready.
On 12/02/11 04:12, Alex Hunsaker wrote:
On Wed, Feb 9, 2011 at 02:10, Jan Urbański <wulczer@wulczer.org> wrote:
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.
In PLy_traceback fname and prname look like they will leak (well as
much as a palloc() in an error path can leak I suppose).
But they're no palloc'd, no? fname is either a static "<module"> string,
or PyString_AsString, which also doesn't allocate memory, AFAIK. proname
is also a static string. They're transferred to heap-allocated memory in
appendStringInfo, which gets pfreed after emitting the error message.
Marking as Ready.
Thanks!
Jan
On Sat, Feb 12, 2011 at 01:50, Jan Urbański <wulczer@wulczer.org> wrote:
On 12/02/11 04:12, Alex Hunsaker wrote:
In PLy_traceback fname and prname look like they will leak (well as
much as a palloc() in an error path can leak I suppose).But they're no palloc'd, no? fname is either a static "<module"> string,
or PyString_AsString, which also doesn't allocate memory, AFAIK.
Yeah, I was flat out wrong about proname :-(.
As for fname, I must be missing some magic. We have:
#if PY_MAJOR_VERSION > 3
...
#define PyString_AsString(x) PLyUnicode_AsString(x)
....
PLyUnicode_AsString(PyObject *unicode)
{
PyObject *o = PLyUnicode_Bytes(unicode);
char *rv = pstrdup(PyBytes_AsString(o));
Py_XDECREF(o);
return rv;
}
PyString_AsString is used all over the place without any pfrees. But I
have no Idea how that pstrdup() is getting freed if at all.
Care to enlighten me ?
On 12/02/11 10:00, Alex Hunsaker wrote:
On Sat, Feb 12, 2011 at 01:50, Jan Urbański <wulczer@wulczer.org> wrote:
On 12/02/11 04:12, Alex Hunsaker wrote:
In PLy_traceback fname and prname look like they will leak (well as
much as a palloc() in an error path can leak I suppose).But they're no palloc'd, no? fname is either a static "<module"> string,
or PyString_AsString, which also doesn't allocate memory, AFAIK.Yeah, I was flat out wrong about proname :-(.
As for fname, I must be missing some magic. We have:
#if PY_MAJOR_VERSION > 3
...
#define PyString_AsString(x) PLyUnicode_AsString(x)
....
PLyUnicode_AsString(PyObject *unicode)
{
PyObject *o = PLyUnicode_Bytes(unicode);
char *rv = pstrdup(PyBytes_AsString(o));Py_XDECREF(o);
return rv;
}PyString_AsString is used all over the place without any pfrees. But I
have no Idea how that pstrdup() is getting freed if at all.Care to enlighten me ?
Ooops, seems that this hack that's meant to improve compatibility with
Python3 makes it leak. I wonder is the pstrdup is necessary here, but
OTOH the leak should not be overly significant, given that no-one
complained about it before... and PyString_AsString is being used in
several other places.
Jan
On lör, 2011-02-12 at 02:00 -0700, Alex Hunsaker wrote:
PyString_AsString is used all over the place without any pfrees. But I
have no Idea how that pstrdup() is getting freed if at all.
pstrdup() like palloc() allocates memory from the current memory
context, which is freed automatically at some useful time, often at the
end of the query. It is very common throughout the PostgreSQL code that
memory is not explicitly freed. See src/backend/utils/mmgr/README for
more information.
On lör, 2011-02-12 at 10:07 +0100, Jan Urbański wrote:
PLyUnicode_AsString(PyObject *unicode)
{
PyObject *o = PLyUnicode_Bytes(unicode);
char *rv = pstrdup(PyBytes_AsString(o));Py_XDECREF(o);
return rv;
}PyString_AsString is used all over the place without any pfrees. But
I
have no Idea how that pstrdup() is getting freed if at all.
Care to enlighten me ?
Ooops, seems that this hack that's meant to improve compatibility with
Python3 makes it leak. I wonder is the pstrdup is necessary here,
The result of PyBytes_AsString(o) points into the internal storage of o,
which is released (effectively freed) by the decref on the next line.
So you'd better make a copy if you want to keep using it.
On tor, 2010-12-23 at 14:56 +0100, Jan Urbański wrote:
For errors originating from Python exceptions add the traceback as the
message detail. The patch tries to mimick Python's traceback.py module
behaviour as close as possible, icluding interleaving stack frames
with source code lines in the detail message. Any Python developer
should instantly recognize these kind of error reporting, it looks
almost the same as an error in the interactive Python shell.
I think the traceback should go into the CONTEXT part of the error. The
context message that's already there is now redundant with the
traceback.
You could even call errcontext() multiple times to build up the
traceback, but maybe that's not necessary.
On 24/02/11 14:10, Peter Eisentraut wrote:
On tor, 2010-12-23 at 14:56 +0100, Jan Urbański wrote:
For errors originating from Python exceptions add the traceback as the
message detail. The patch tries to mimick Python's traceback.py module
behaviour as close as possible, icluding interleaving stack frames
with source code lines in the detail message. Any Python developer
should instantly recognize these kind of error reporting, it looks
almost the same as an error in the interactive Python shell.I think the traceback should go into the CONTEXT part of the error. The
context message that's already there is now redundant with the
traceback.You could even call errcontext() multiple times to build up the
traceback, but maybe that's not necessary.
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?
Jan
On Thu, Feb 24, 2011 at 9:03 AM, Jan Urbański <wulczer@wulczer.org> wrote:
On 24/02/11 14:10, Peter Eisentraut wrote:
On tor, 2010-12-23 at 14:56 +0100, Jan Urbański wrote:
For errors originating from Python exceptions add the traceback as the
message detail. The patch tries to mimick Python's traceback.py module
behaviour as close as possible, icluding interleaving stack frames
with source code lines in the detail message. Any Python developer
should instantly recognize these kind of error reporting, it looks
almost the same as an error in the interactive Python shell.I think the traceback should go into the CONTEXT part of the error. The
context message that's already there is now redundant with the
traceback.You could even call errcontext() multiple times to build up the
traceback, but maybe that's not necessary.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.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
----- Original message -----
On Thu, Feb 24, 2011 at 9:03 AM, Jan Urbański <wulczer@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.
Jan
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 <wulczer@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.
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 <wulczer@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
Attachments:
plpython-tracebacks.difftext/x-patch; name=plpython-tracebacks.diffDownload
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 (
=?UTF-8?B?SmFuIFVyYmHFhHNraQ==?= <wulczer@wulczer.org> writes:
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.
To me, none of those arguments seem good. Traceback is the sort of
thing that belongs in errcontext, and arbitarily deciding that plpython
isn't going to play by the rules doesn't sit well here. I agree that
what you are showing is redundant with the current errcontext printout,
but the solution for that is to change the errcontext printout, not to
add redundant and inappropriate errdetail.
An example of the reasoning for this is the situation where a plpython
function calls back into SQL, and something there throws an ereport
(which might include an errdetail). It would be useful to include the
Python traceback in the errcontext stack, since there might be multiple
levels of Python function call within what PG sees as just a "plpython
function". But you can't get there with this approach.
regards, tom lane
On 01/03/11 20:15, Tom Lane wrote:
=?UTF-8?B?SmFuIFVyYmHFhHNraQ==?= <wulczer@wulczer.org> writes:
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.To me, none of those arguments seem good. Traceback is the sort of
thing that belongs in errcontext, and arbitarily deciding that plpython
isn't going to play by the rules doesn't sit well here. I agree that
what you are showing is redundant with the current errcontext printout,
but the solution for that is to change the errcontext printout, not to
add redundant and inappropriate errdetail.An example of the reasoning for this is the situation where a plpython
function calls back into SQL, and something there throws an ereport
(which might include an errdetail). It would be useful to include the
Python traceback in the errcontext stack, since there might be multiple
levels of Python function call within what PG sees as just a "plpython
function". But you can't get there with this approach.
Currently the traceback is added to the detail and the original
errdetail is preserved. So you'd get the detail line and the traceback
below it.
But OK, since there are more voices in favour of putting tracebacks in
the context field, I'll keep on looking for a solution.
Jan
=?UTF-8?B?SmFuIFVyYmHFhHNraQ==?= <wulczer@wulczer.org> writes:
Currently the traceback is added to the detail and the original
errdetail is preserved. So you'd get the detail line and the traceback
below it.
Hm? I'm talking about plpython_error_callback() and friends, which
AFAICS you haven't changed the behavior of at all. And it would
certainly be completely inappropriate to do what's said above for
an errdetail with a non-plpython origin.
regards, tom lane
On 01/03/11 20:35, Tom Lane wrote:
=?UTF-8?B?SmFuIFVyYmHFhHNraQ==?= <wulczer@wulczer.org> writes:
Currently the traceback is added to the detail and the original
errdetail is preserved. So you'd get the detail line and the traceback
below it.Hm? I'm talking about plpython_error_callback() and friends, which
AFAICS you haven't changed the behavior of at all. And it would
certainly be completely inappropriate to do what's said above for
an errdetail with a non-plpython origin.
Not sure if I understand the problem. PL/Python sets
plpython_error_callback right after entering the function call handler,
so any elog thrown while the function is executing has a "PL/Python
function %s" context message. If plpython calls into SQL with SPI and
something there throws an elog(ERROR) with an errdetail, that detail is
saved inside the exception and a Python error is then thrown. If this
Python error reaches the top of the Python stack, the error reporting
code kicks in, extracts the saved errdetail value from the Python
exception, and then extract the stack trace and also adds it to the
errdetail.
So you end up with a context message saying "PL/Python function %s" and
a detail message with the saved detail (if it's present) *and* the
traceback. The problem is that the name of the function is already in
the traceback, so there's no need for the context *if* there's a
traceback present.
The problem I'm having is technical: since the callback is already set
when the code reaches the traceback-printing stage, you can't really
unset it. AFAICS the elog code calls *all* callbacks from
error_context_stack. So I can't prevent the context message from
appearing. If I make the traceback part of the context as well, it's
just going to appear together with the message from the callback.
Jan
On Mar 1, 2011, at 12:10 PM, Jan Urbański wrote:
So you end up with a context message saying "PL/Python function %s" and
a detail message with the saved detail (if it's present) *and* the
traceback. The problem is that the name of the function is already in
the traceback, so there's no need for the context *if* there's a
traceback present.The problem I'm having is technical: since the callback is already set
when the code reaches the traceback-printing stage, you can't really
unset it. AFAICS the elog code calls *all* callbacks from
error_context_stack. So I can't prevent the context message from
appearing. If I make the traceback part of the context as well, it's
just going to appear together with the message from the callback.
I remember going through a lot of pain getting this done "right"
in pg-python[pl/py].
SELECT it_blows_up();
ERROR: function's "main" raised a Python exception
CONTEXT: [exception from Python]
Traceback (most recent call last):
File "public.it_blows_up()", line 13, in main
three()
File "public.it_blows_up()", line 10, in three
return two()
File "public.it_blows_up()", line 7, in two
return one()
File "public.it_blows_up()", line 4, in one
raise OverflowError("there's water everywhere")
OverflowError: there's water everywhere
[public.it_blows_up()]
IIRC, I unconditionally print the "[public.it_blows_up()]" part iff it's
not an ERROR. If it is an ERROR, I let the traceback rendering part of the code
handle it on the PL's entry point exit. It was really tricky to do this because I
was rendering the traceback *after* the error_context_stack had been called.
On tis, 2011-03-01 at 21:10 +0100, Jan Urbański wrote:
So you end up with a context message saying "PL/Python function %s"
and a detail message with the saved detail (if it's present) *and* the
traceback. The problem is that the name of the function is already in
the traceback, so there's no need for the context *if* there's a
traceback present.
I wouldn't actually worry about that bit of redundancy so much. Getting
proper context for nested calls is much more important.
On 01/03/11 22:12, Peter Eisentraut wrote:
On tis, 2011-03-01 at 21:10 +0100, Jan Urbański wrote:
So you end up with a context message saying "PL/Python function %s"
and a detail message with the saved detail (if it's present) *and* the
traceback. The problem is that the name of the function is already in
the traceback, so there's no need for the context *if* there's a
traceback present.I wouldn't actually worry about that bit of redundancy so much. Getting
proper context for nested calls is much more important.
Here's another version that puts tracebacks in the context field.
I did some tests with the attached test script, calling various of the
functions defined there and the error messages more or less made sense
(or at least were not worse than before).
Cheers,
Jan
Attachments:
plpython-tracebacks.difftext/x-patch; name=plpython-tracebacks.diffDownload
diff --git a/src/pl/plpython/expected/plpython_do.out b/src/pl/plpython/expected/plpython_do.out
index a21b088..41b7a51 100644
*** a/src/pl/plpython/expected/plpython_do.out
--- b/src/pl/plpython/expected/plpython_do.out
*************** NOTICE: This is plpythonu.
*** 3,6 ****
CONTEXT: PL/Python anonymous code block
DO $$ nonsense $$ LANGUAGE plpythonu;
ERROR: NameError: global name 'nonsense' is not defined
! CONTEXT: PL/Python anonymous code block
--- 3,9 ----
CONTEXT: PL/Python anonymous code block
DO $$ nonsense $$ LANGUAGE plpythonu;
ERROR: NameError: global name 'nonsense' is not defined
! CONTEXT: Traceback (most recent call last):
! PL/Python anonymous code block, line 1, in <module>
! nonsense
! PL/Python anonymous code block
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
index e38ea60..5fdf65f 100644
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** ERROR: spiexceptions.SyntaxError: synta
*** 36,42 ****
LINE 1: syntax error
^
QUERY: syntax error
! CONTEXT: PL/Python function "sql_syntax_error"
/* check the handling of uncaught python exceptions
*/
CREATE FUNCTION exception_index_invalid(text) RETURNS text
--- 36,45 ----
LINE 1: syntax error
^
QUERY: 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
*************** CREATE FUNCTION exception_index_invalid(
*** 45,51 ****
LANGUAGE plpythonu;
SELECT exception_index_invalid('test');
ERROR: IndexError: list index out of range
! CONTEXT: PL/Python function "exception_index_invalid"
/* check handling of nested exceptions
*/
CREATE FUNCTION exception_index_invalid_nested() RETURNS text
--- 48,57 ----
LANGUAGE plpythonu;
SELECT exception_index_invalid('test');
ERROR: IndexError: list index out of range
! 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
*************** LINE 1: SELECT test5('foo')
*** 59,65 ****
^
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"
/* a typo
*/
CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text
--- 65,74 ----
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
QUERY: SELECT test5('foo')
! 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
*************** return None
*** 75,81 ****
LANGUAGE plpythonu;
SELECT invalid_type_uncaught('rick');
ERROR: spiexceptions.UndefinedObject: type "test" does not exist
! CONTEXT: PL/Python function "invalid_type_uncaught"
/* for what it's worth catch the exception generated by
* the typo, and return None
*/
--- 84,93 ----
LANGUAGE plpythonu;
SELECT invalid_type_uncaught('rick');
ERROR: spiexceptions.UndefinedObject: type "test" does not exist
! 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
*/
*************** return None
*** 121,127 ****
LANGUAGE plpythonu;
SELECT invalid_type_reraised('rick');
ERROR: plpy.Error: type "test" does not exist
! CONTEXT: PL/Python function "invalid_type_reraised"
/* no typo no messing about
*/
CREATE FUNCTION valid_type(a text) RETURNS text
--- 133,142 ----
LANGUAGE plpythonu;
SELECT invalid_type_reraised('rick');
ERROR: plpy.Error: type "test" does not exist
! 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
*************** 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
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "nested_error", line 10, in <module>
+ fun3()
+ PL/Python function "nested_error", line 8, in fun3
+ fun2()
+ PL/Python function "nested_error", line 5, in fun2
+ fun1()
+ PL/Python function "nested_error", line 2, in fun1
+ plpy.error("boom")
+ PL/Python function "nested_error"
+ /* raising plpy.Error is just like calling plpy.error
+ */
+ CREATE FUNCTION nested_error_raise() RETURNS text
+ AS
+ 'def fun1():
+ raise plpy.Error("boom")
+
+ def fun2():
+ fun1()
+
+ def fun3():
+ fun2()
+
+ fun3()
+ return "not reached"
+ '
+ LANGUAGE plpythonu;
+ SELECT nested_error_raise();
+ ERROR: plpy.Error: boom
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "nested_error_raise", line 10, in <module>
+ fun3()
+ PL/Python function "nested_error_raise", line 8, in fun3
+ fun2()
+ PL/Python function "nested_error_raise", line 5, in fun2
+ fun1()
+ PL/Python function "nested_error_raise", line 2, in fun1
+ raise plpy.Error("boom")
+ PL/Python function "nested_error_raise"
+ /* using plpy.warning should not produce a traceback
+ */
+ CREATE FUNCTION nested_warning() RETURNS text
+ AS
+ 'def fun1():
+ plpy.warning("boom")
+
+ def fun2():
+ fun1()
+
+ def fun3():
+ fun2()
+
+ fun3()
+ return "you''ve been warned"
+ '
+ LANGUAGE plpythonu;
+ SELECT nested_warning();
+ WARNING: boom
+ CONTEXT: PL/Python function "nested_warning"
+ nested_warning
+ --------------------
+ you've been warned
+ (1 row)
+
+ /* AttirbuteError at toplevel used to give segfaults with the traceback
+ */
+ CREATE FUNCTION toplevel_attribute_error() RETURNS void AS
+ $$
+ plpy.nonexistent
+ $$ LANGUAGE plpythonu;
+ SELECT toplevel_attribute_error();
+ ERROR: AttributeError: 'module' object has no attribute 'nonexistent'
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "toplevel_attribute_error", line 2, in <module>
+ plpy.nonexistent
+ PL/Python function "toplevel_attribute_error"
/* check catching specific types of exceptions
*/
CREATE TABLE specific (
*************** plpy.execute("rollback to save")
*** 187,193 ****
$$ LANGUAGE plpythonu;
SELECT manual_subxact();
ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
! CONTEXT: PL/Python function "manual_subxact"
/* same for prepared plans
*/
CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$
--- 297,306 ----
$$ LANGUAGE plpythonu;
SELECT manual_subxact();
ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
! 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 $$
*************** plpy.execute(rollback)
*** 199,202 ****
$$ LANGUAGE plpythonu;
SELECT manual_subxact_prepared();
ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
! CONTEXT: PL/Python function "manual_subxact_prepared"
--- 312,318 ----
$$ LANGUAGE plpythonu;
SELECT manual_subxact_prepared();
ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
! CONTEXT: Traceback (most recent call last):
! PL/Python function "manual_subxact_prepared", line 4, in <module>
! plpy.execute(save)
! PL/Python function "manual_subxact_prepared"
diff --git a/src/pl/plpython/expected/plpython_error_0.out b/src/pl/plpython/expected/plpython_error_0.out
index 1b65d35..ea5287c 100644
*** a/src/pl/plpython/expected/plpython_error_0.out
--- b/src/pl/plpython/expected/plpython_error_0.out
*************** ERROR: spiexceptions.SyntaxError: synta
*** 36,42 ****
LINE 1: syntax error
^
QUERY: syntax error
! CONTEXT: PL/Python function "sql_syntax_error"
/* check the handling of uncaught python exceptions
*/
CREATE FUNCTION exception_index_invalid(text) RETURNS text
--- 36,45 ----
LINE 1: syntax error
^
QUERY: 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
*************** CREATE FUNCTION exception_index_invalid(
*** 45,51 ****
LANGUAGE plpythonu;
SELECT exception_index_invalid('test');
ERROR: IndexError: list index out of range
! CONTEXT: PL/Python function "exception_index_invalid"
/* check handling of nested exceptions
*/
CREATE FUNCTION exception_index_invalid_nested() RETURNS text
--- 48,57 ----
LANGUAGE plpythonu;
SELECT exception_index_invalid('test');
ERROR: IndexError: list index out of range
! 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
*************** LINE 1: SELECT test5('foo')
*** 59,65 ****
^
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"
/* a typo
*/
CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text
--- 65,74 ----
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
QUERY: SELECT test5('foo')
! 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
*************** return None
*** 75,81 ****
LANGUAGE plpythonu;
SELECT invalid_type_uncaught('rick');
ERROR: spiexceptions.UndefinedObject: type "test" does not exist
! CONTEXT: PL/Python function "invalid_type_uncaught"
/* for what it's worth catch the exception generated by
* the typo, and return None
*/
--- 84,93 ----
LANGUAGE plpythonu;
SELECT invalid_type_uncaught('rick');
ERROR: spiexceptions.UndefinedObject: type "test" does not exist
! 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
*/
*************** return None
*** 121,127 ****
LANGUAGE plpythonu;
SELECT invalid_type_reraised('rick');
ERROR: plpy.Error: type "test" does not exist
! CONTEXT: PL/Python function "invalid_type_reraised"
/* no typo no messing about
*/
CREATE FUNCTION valid_type(a text) RETURNS text
--- 133,142 ----
LANGUAGE plpythonu;
SELECT invalid_type_reraised('rick');
ERROR: plpy.Error: type "test" does not exist
! 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
*************** 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
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "nested_error", line 10, in <module>
+ fun3()
+ PL/Python function "nested_error", line 8, in fun3
+ fun2()
+ PL/Python function "nested_error", line 5, in fun2
+ fun1()
+ PL/Python function "nested_error", line 2, in fun1
+ plpy.error("boom")
+ PL/Python function "nested_error"
+ /* raising plpy.Error is just like calling plpy.error
+ */
+ CREATE FUNCTION nested_error_raise() RETURNS text
+ AS
+ 'def fun1():
+ raise plpy.Error("boom")
+
+ def fun2():
+ fun1()
+
+ def fun3():
+ fun2()
+
+ fun3()
+ return "not reached"
+ '
+ LANGUAGE plpythonu;
+ SELECT nested_error_raise();
+ ERROR: plpy.Error: boom
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "nested_error_raise", line 10, in <module>
+ fun3()
+ PL/Python function "nested_error_raise", line 8, in fun3
+ fun2()
+ PL/Python function "nested_error_raise", line 5, in fun2
+ fun1()
+ PL/Python function "nested_error_raise", line 2, in fun1
+ raise plpy.Error("boom")
+ PL/Python function "nested_error_raise"
+ /* using plpy.warning should not produce a traceback
+ */
+ CREATE FUNCTION nested_warning() RETURNS text
+ AS
+ 'def fun1():
+ plpy.warning("boom")
+
+ def fun2():
+ fun1()
+
+ def fun3():
+ fun2()
+
+ fun3()
+ return "you''ve been warned"
+ '
+ LANGUAGE plpythonu;
+ SELECT nested_warning();
+ WARNING: boom
+ CONTEXT: PL/Python function "nested_warning"
+ nested_warning
+ --------------------
+ you've been warned
+ (1 row)
+
+ /* AttirbuteError at toplevel used to give segfaults with the traceback
+ */
+ CREATE FUNCTION toplevel_attribute_error() RETURNS void AS
+ $$
+ plpy.nonexistent
+ $$ LANGUAGE plpythonu;
+ SELECT toplevel_attribute_error();
+ ERROR: AttributeError: 'module' object has no attribute 'nonexistent'
+ CONTEXT: Traceback (most recent call last):
+ PL/Python function "toplevel_attribute_error", line 2, in <module>
+ plpy.nonexistent
+ PL/Python function "toplevel_attribute_error"
/* check catching specific types of exceptions
*/
CREATE TABLE specific (
*************** plpy.execute("rollback to save")
*** 187,193 ****
$$ LANGUAGE plpythonu;
SELECT manual_subxact();
ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
! CONTEXT: PL/Python function "manual_subxact"
/* same for prepared plans
*/
CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$
--- 297,306 ----
$$ LANGUAGE plpythonu;
SELECT manual_subxact();
ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
! 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 $$
*************** plpy.execute(rollback)
*** 199,202 ****
$$ LANGUAGE plpythonu;
SELECT manual_subxact_prepared();
ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
! CONTEXT: PL/Python function "manual_subxact_prepared"
--- 312,318 ----
$$ LANGUAGE plpythonu;
SELECT manual_subxact_prepared();
ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
! CONTEXT: Traceback (most recent call last):
! PL/Python function "manual_subxact_prepared", line 4, in <module>
! plpy.execute(save)
! PL/Python function "manual_subxact_prepared"
diff --git a/src/pl/plpython/expected/plpython_subtransaction.out b/src/pl/plpython/expected/plpython_subtransaction.out
index 50d97fa..515b0bb 100644
*** a/src/pl/plpython/expected/plpython_subtransaction.out
--- b/src/pl/plpython/expected/plpython_subtransaction.out
*************** ERROR: spiexceptions.InvalidTextReprese
*** 47,53 ****
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
! CONTEXT: PL/Python function "subtransaction_test"
SELECT * FROM subtransaction_tbl;
i
---
--- 47,56 ----
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
! 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
---
*************** SELECT * FROM subtransaction_tbl;
*** 56,62 ****
TRUNCATE subtransaction_tbl;
SELECT subtransaction_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
! CONTEXT: PL/Python function "subtransaction_test"
SELECT * FROM subtransaction_tbl;
i
---
--- 59,68 ----
TRUNCATE subtransaction_tbl;
SELECT subtransaction_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
! 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
---
*************** ERROR: spiexceptions.InvalidTextReprese
*** 93,99 ****
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
! CONTEXT: PL/Python function "subtransaction_ctx_test"
SELECT * FROM subtransaction_tbl;
i
---
--- 99,108 ----
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
! 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
---
*************** SELECT * FROM subtransaction_tbl;
*** 102,108 ****
TRUNCATE subtransaction_tbl;
SELECT subtransaction_ctx_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
! CONTEXT: PL/Python function "subtransaction_ctx_test"
SELECT * FROM subtransaction_tbl;
i
---
--- 111,120 ----
TRUNCATE subtransaction_tbl;
SELECT subtransaction_ctx_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
! 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
---
*************** ERROR: spiexceptions.SyntaxError: synta
*** 130,136 ****
LINE 1: error
^
QUERY: error
! CONTEXT: PL/Python function "subtransaction_nested_test"
SELECT * FROM subtransaction_tbl;
i
---
--- 142,151 ----
LINE 1: error
^
QUERY: error
! 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
---
*************** with plpy.subtransaction() as s:
*** 230,236 ****
$$ LANGUAGE plpythonu;
SELECT subtransaction_exit_without_enter();
ERROR: ValueError: this subtransaction has not been entered
! CONTEXT: 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"
--- 245,254 ----
$$ LANGUAGE plpythonu;
SELECT subtransaction_exit_without_enter();
ERROR: ValueError: this subtransaction has not been entered
! 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"
*************** SELECT subtransaction_exit_twice();
*** 243,249 ****
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"
SELECT subtransaction_enter_twice();
WARNING: forcibly aborting a subtransaction that has not been exited
CONTEXT: PL/Python function "subtransaction_enter_twice"
--- 261,270 ----
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: 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"
*************** CONTEXT: PL/Python function "subtransac
*** 256,273 ****
SELECT subtransaction_exit_same_subtransaction_twice();
ERROR: ValueError: this subtransaction has already been exited
! 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
! CONTEXT: 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"
SELECT subtransaction_exit_subtransaction_in_with();
ERROR: ValueError: this subtransaction has already been exited
! 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;
test
--- 277,306 ----
SELECT subtransaction_exit_same_subtransaction_twice();
ERROR: ValueError: this subtransaction has already been exited
! 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: 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: 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: Traceback (most recent call last):
! PL/Python function "subtransaction_exit_subtransaction_in_with", line 3, in <module>
! s.__exit__(None, None, None)
! PL/Python function "subtransaction_exit_subtransaction_in_with"
-- Make sure we don't get a "current transaction is aborted" error
SELECT 1 as test;
test
diff --git a/src/pl/plpython/expected/plpython_subtransaction_0.out b/src/pl/plpython/expected/plpython_subtransaction_0.out
index 164e987..4017c41 100644
*** a/src/pl/plpython/expected/plpython_subtransaction_0.out
--- b/src/pl/plpython/expected/plpython_subtransaction_0.out
*************** ERROR: spiexceptions.InvalidTextReprese
*** 47,53 ****
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
! CONTEXT: PL/Python function "subtransaction_test"
SELECT * FROM subtransaction_tbl;
i
---
--- 47,56 ----
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
! 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
---
*************** SELECT * FROM subtransaction_tbl;
*** 56,62 ****
TRUNCATE subtransaction_tbl;
SELECT subtransaction_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
! CONTEXT: PL/Python function "subtransaction_test"
SELECT * FROM subtransaction_tbl;
i
---
--- 59,68 ----
TRUNCATE subtransaction_tbl;
SELECT subtransaction_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
! 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
---
*************** ERROR: could not compile PL/Python func
*** 223,229 ****
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"
SELECT subtransaction_enter_without_exit();
WARNING: forcibly aborting a subtransaction that has not been exited
CONTEXT: PL/Python function "subtransaction_enter_without_exit"
--- 229,238 ----
DETAIL: SyntaxError: invalid syntax (line 3)
SELECT subtransaction_exit_without_enter();
ERROR: ValueError: this subtransaction has not been entered
! 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"
*************** SELECT subtransaction_exit_twice();
*** 236,242 ****
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"
SELECT subtransaction_enter_twice();
WARNING: forcibly aborting a subtransaction that has not been exited
CONTEXT: PL/Python function "subtransaction_enter_twice"
--- 245,254 ----
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: 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"
*************** CONTEXT: PL/Python function "subtransac
*** 249,260 ****
SELECT subtransaction_exit_same_subtransaction_twice();
ERROR: ValueError: this subtransaction has already been exited
! 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
! 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
LINE 1: SELECT subtransaction_enter_subtransaction_in_with();
--- 261,278 ----
SELECT subtransaction_exit_same_subtransaction_twice();
ERROR: ValueError: this subtransaction has already been exited
! 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: Traceback (most recent call last):
! PL/Python function "subtransaction_enter_same_subtransaction_twice", line 4, in <module>
! s.__enter__()
! PL/Python function "subtransaction_enter_same_subtransaction_twice"
SELECT subtransaction_enter_subtransaction_in_with();
ERROR: function subtransaction_enter_subtransaction_in_with() does not exist
LINE 1: SELECT subtransaction_enter_subtransaction_in_with();
diff --git a/src/pl/plpython/expected/plpython_subtransaction_5.out b/src/pl/plpython/expected/plpython_subtransaction_5.out
index 4e6c067..9216151 100644
*** a/src/pl/plpython/expected/plpython_subtransaction_5.out
--- b/src/pl/plpython/expected/plpython_subtransaction_5.out
*************** ERROR: spiexceptions.InvalidTextReprese
*** 47,53 ****
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
! CONTEXT: PL/Python function "subtransaction_test"
SELECT * FROM subtransaction_tbl;
i
---
--- 47,56 ----
LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
^
QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
! 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
---
*************** SELECT * FROM subtransaction_tbl;
*** 56,62 ****
TRUNCATE subtransaction_tbl;
SELECT subtransaction_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
! CONTEXT: PL/Python function "subtransaction_test"
SELECT * FROM subtransaction_tbl;
i
---
--- 59,68 ----
TRUNCATE subtransaction_tbl;
SELECT subtransaction_test('Python');
ERROR: AttributeError: 'module' object has no attribute 'attribute_error'
! 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
---
*************** ERROR: could not compile PL/Python func
*** 223,229 ****
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"
SELECT subtransaction_enter_without_exit();
WARNING: forcibly aborting a subtransaction that has not been exited
CONTEXT: PL/Python function "subtransaction_enter_without_exit"
--- 229,238 ----
DETAIL: SyntaxError: invalid syntax (<string>, line 3)
SELECT subtransaction_exit_without_enter();
ERROR: ValueError: this subtransaction has not been entered
! 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"
*************** SELECT subtransaction_exit_twice();
*** 236,242 ****
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"
SELECT subtransaction_enter_twice();
WARNING: forcibly aborting a subtransaction that has not been exited
CONTEXT: PL/Python function "subtransaction_enter_twice"
--- 245,254 ----
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: 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"
*************** CONTEXT: PL/Python function "subtransac
*** 249,260 ****
SELECT subtransaction_exit_same_subtransaction_twice();
ERROR: ValueError: this subtransaction has already been exited
! 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
! 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
LINE 1: SELECT subtransaction_enter_subtransaction_in_with();
--- 261,278 ----
SELECT subtransaction_exit_same_subtransaction_twice();
ERROR: ValueError: this subtransaction has already been exited
! 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: Traceback (most recent call last):
! PL/Python function "subtransaction_enter_same_subtransaction_twice", line 4, in <module>
! s.__enter__()
! PL/Python function "subtransaction_enter_same_subtransaction_twice"
SELECT subtransaction_enter_subtransaction_in_with();
ERROR: function subtransaction_enter_subtransaction_in_with() does not exist
LINE 1: SELECT subtransaction_enter_subtransaction_in_with();
diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out
index 7b2e170..4afa9ae 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 ****
WARNING: warning
CONTEXT: PL/Python function "elog_test"
ERROR: plpy.Error: error
! CONTEXT: PL/Python function "elog_test"
--- 74,80 ----
WARNING: warning
CONTEXT: PL/Python function "elog_test"
ERROR: plpy.Error: error
! CONTEXT: Traceback (most recent call last):
! PL/Python function "elog_test", line 10, in <module>
! plpy.error('error')
! PL/Python function "elog_test"
diff --git a/src/pl/plpython/expected/plpython_types.out b/src/pl/plpython/expected/plpython_types.out
index d5f2c70..8881613 100644
*** a/src/pl/plpython/expected/plpython_types.out
--- b/src/pl/plpython/expected/plpython_types.out
*************** SELECT name, test_composite_table_input(
*** 625,631 ****
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"
ALTER TABLE employee ADD bonus integer;
UPDATE employee SET bonus = 10;
SELECT name, test_composite_table_input(employee.*) FROM employee;
--- 625,634 ----
ALTER TABLE employee DROP bonus;
SELECT name, test_composite_table_input(employee.*) FROM employee;
ERROR: KeyError: 'bonus'
! CONTEXT: Traceback (most recent call last):
! PL/Python function "test_composite_table_input", line 2, in <module>
! return e['basesalary'] + e['bonus']
! PL/Python function "test_composite_table_input"
ALTER TABLE employee ADD bonus integer;
UPDATE employee SET bonus = 10;
SELECT name, test_composite_table_input(employee.*) FROM employee;
diff --git a/src/pl/plpython/expected/plpython_types_3.out b/src/pl/plpython/expected/plpython_types_3.out
index ca81b08..d1ae863 100644
*** a/src/pl/plpython/expected/plpython_types_3.out
--- b/src/pl/plpython/expected/plpython_types_3.out
*************** SELECT name, test_composite_table_input(
*** 625,631 ****
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"
ALTER TABLE employee ADD bonus integer;
UPDATE employee SET bonus = 10;
SELECT name, test_composite_table_input(employee.*) FROM employee;
--- 625,634 ----
ALTER TABLE employee DROP bonus;
SELECT name, test_composite_table_input(employee.*) FROM employee;
ERROR: KeyError: 'bonus'
! CONTEXT: Traceback (most recent call last):
! PL/Python function "test_composite_table_input", line 2, in <module>
! return e['basesalary'] + e['bonus']
! PL/Python function "test_composite_table_input"
ALTER TABLE employee ADD bonus integer;
UPDATE employee SET bonus = 10;
SELECT name, test_composite_table_input(employee.*) FROM employee;
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index a2ebd22..46d2f3f 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:
*** 4358,4430 ****
* 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);
}
--- 4363,4487 ----
* 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) ? errcontext("%s", xmsg) : 0,
! (hint) ? errhint("%s", hint) : 0,
! (query) ? internalerrquery(query) : 0,
! (position) ? internalerrposition(position) : 0));
! }
! else
! {
! ereport(elevel,
! (errmsg("%s", fmsg),
! (detail) ? errdetail("%s", detail) : 0,
! (emsg) ? errdetail("%s", emsg) : 0,
! (hint) ? errhint("%s", hint) : 0,
! (query) ? internalerrquery(query) : 0,
! (position) ? internalerrposition(position) : 0));
! }
! }
else
! {
! if (tb_depth > 0)
! {
! ereport(elevel,
! (errmsg("%s", emsg ? emsg : "no exception data"),
! (detail) ? errdetail("%s", detail) : 0,
! (xmsg) ? errcontext("%s", xmsg) : 0,
! (hint) ? errhint("%s", hint) : 0,
! (query) ? internalerrquery(query) : 0,
! (position) ? internalerrposition(position) : 0));
! }
! else
! {
! ereport(elevel,
! (errmsg("%s", emsg ? emsg : "no exception data"),
! (detail) ? errdetail("%s", detail) : 0,
! (hint) ? errhint("%s", hint) : 0,
! (query) ? internalerrquery(query) : 0,
! (position) ? internalerrposition(position) : 0));
! }
! }
}
PG_CATCH();
{
! if (fmsg)
! pfree(fmsg);
! if (emsg)
! pfree(emsg);
if (xmsg)
pfree(xmsg);
PG_RE_THROW();
}
PG_END_TRY();
! pfree(emsg);
if (xmsg)
pfree(xmsg);
}
*************** cleanup:
*** 4451,4458 ****
}
static char *
! PLy_traceback(int *xlevel)
{
PyObject *e,
*v,
--- 4508,4551 ----
}
+ /* 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)
*** 4463,4468 ****
--- 4556,4562 ----
char *e_module_s = NULL;
PyObject *vob = NULL;
char *vstr;
+ StringInfoData estr;
StringInfoData xstr;
/*
*************** PLy_traceback(int *xlevel)
*** 4474,4486 ****
* 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__");
--- 4568,4578 ----
* 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)
*** 4494,4535 ****
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 */
--- 4586,4723 ----
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 (
On 02/03/11 22:28, Jan Urbański wrote:
On 01/03/11 22:12, Peter Eisentraut wrote:
On tis, 2011-03-01 at 21:10 +0100, Jan Urbański wrote:
So you end up with a context message saying "PL/Python function %s"
and a detail message with the saved detail (if it's present) *and* the
traceback. The problem is that the name of the function is already in
the traceback, so there's no need for the context *if* there's a
traceback present.I wouldn't actually worry about that bit of redundancy so much. Getting
proper context for nested calls is much more important.Here's another version that puts tracebacks in the context field.
I did some tests with the attached test script, calling various of the
functions defined there and the error messages more or less made sense
(or at least were not worse than before).
I realized I did not update the patch state in the CF app when I added
this version, so I flipped it back to Ready for Committer now.
Tracebacks are a nice-to-have, so if we decide to drop this one due to
time constraints, I'd understand that. But fixing "raise plpy.Fatal()"
to actually cause a FATAL is something that should be extracted from
this patch and committed, even if the full patch does not make it.
Cheers,
Jan
On ons, 2011-03-02 at 22:28 +0100, Jan Urbański wrote:
I did some tests with the attached test script, calling various of the
functions defined there and the error messages more or less made sense
(or at least were not worse than before).
Is that script part of the regression tests you added?
On sön, 2011-03-06 at 13:14 +0100, Jan Urbański wrote:
But fixing "raise plpy.Fatal()"
to actually cause a FATAL is something that should be extracted from
this patch and committed, even if the full patch does not make it.
Um, what? I didn't find any details about this in this thread, nor a
test case.
On 07/03/11 13:53, Peter Eisentraut wrote:
On ons, 2011-03-02 at 22:28 +0100, Jan Urbański wrote:
I did some tests with the attached test script, calling various of the
functions defined there and the error messages more or less made sense
(or at least were not worse than before).Is that script part of the regression tests you added?
No, the regression tests are a bit different. Maybe this script should
be part of them as well?
On 07/03/11 13:53, Peter Eisentraut wrote:
On sön, 2011-03-06 at 13:14 +0100, Jan Urbański wrote:
But fixing "raise plpy.Fatal()"
to actually cause a FATAL is something that should be extracted from
this patch and committed, even if the full patch does not make it.Um, what? I didn't find any details about this in this thread, nor a
test case.
Yes, my fault for sneaking it here without more introduction than this
comment several messages upthread:
"""
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.
"""
So this in fact are three separate things, tracebacks, fix for
plpy.Fatal and a one-line fix for reporting errors in Python iterators,
that as I noticed has a side effect of changing the SQLCODE being raised
:( I think I'll just respin the tracebacks patch as 3 separate ones,
coming right up.
BTW, it's hard to test if raising plpy.Fatal actually causes a FATAL
elog, because that would terminate the backend running the tests, and I
though pg_regress treats this as an unconditional error (or am I mistaken?).
Jan
On 07/03/11 14:01, Jan Urbański wrote:
On 07/03/11 13:53, Peter Eisentraut wrote:
On sön, 2011-03-06 at 13:14 +0100, Jan Urbański wrote:
But fixing "raise plpy.Fatal()"
to actually cause a FATAL is something that should be extracted from
this patch and committed, even if the full patch does not make it.Um, what? I didn't find any details about this in this thread, nor a
test case.
So this in fact are three separate things, tracebacks, fix for
plpy.Fatal and a one-line fix for reporting errors in Python iterators,
that as I noticed has a side effect of changing the SQLCODE being raised
:( I think I'll just respin the tracebacks patch as 3 separate ones,
coming right up.
Respun as three separate patches. Sorry for the confusion. BTW: looks
like plpy.Fatal behaviour has been broken for quite some time now.
Jan
Attachments:
0003-Add-Python-tracebacks-to-error-messages.patchtext/x-patch; name=0003-Add-Python-tracebacks-to-error-messages.patchDownload
On mån, 2011-03-07 at 14:19 +0100, Jan Urbański wrote:
On 07/03/11 14:01, Jan Urbański wrote:
On 07/03/11 13:53, Peter Eisentraut wrote:
On sön, 2011-03-06 at 13:14 +0100, Jan Urbański wrote:
But fixing "raise plpy.Fatal()"
to actually cause a FATAL is something that should be extracted from
this patch and committed, even if the full patch does not make it.Um, what? I didn't find any details about this in this thread, nor a
test case.So this in fact are three separate things, tracebacks, fix for
plpy.Fatal and a one-line fix for reporting errors in Python iterators,
that as I noticed has a side effect of changing the SQLCODE being raised
:( I think I'll just respin the tracebacks patch as 3 separate ones,
coming right up.Respun as three separate patches. Sorry for the confusion. BTW: looks
like plpy.Fatal behaviour has been broken for quite some time now.
Committed 1 and 2.
Your traceback implementation in PLy_elog is now using two errdetail
calls in one ereport call, which doesn't work (first one wins). Please
reconsider that. Also, the comment still talks about the traceback
going into detail.
On 07/03/11 22:55, Peter Eisentraut wrote:
On mån, 2011-03-07 at 14:19 +0100, Jan Urbański wrote:
On 07/03/11 14:01, Jan Urbański wrote:
On 07/03/11 13:53, Peter Eisentraut wrote:
On sön, 2011-03-06 at 13:14 +0100, Jan Urbański wrote:
But fixing "raise plpy.Fatal()"
to actually cause a FATAL is something that should be extracted from
this patch and committed, even if the full patch does not make it.Um, what? I didn't find any details about this in this thread, nor a
test case.So this in fact are three separate things, tracebacks, fix for
plpy.Fatal and a one-line fix for reporting errors in Python iterators,
that as I noticed has a side effect of changing the SQLCODE being raised
:( I think I'll just respin the tracebacks patch as 3 separate ones,
coming right up.Respun as three separate patches. Sorry for the confusion. BTW: looks
like plpy.Fatal behaviour has been broken for quite some time now.Committed 1 and 2.
Your traceback implementation in PLy_elog is now using two errdetail
calls in one ereport call, which doesn't work (first one wins). Please
reconsider that. Also, the comment still talks about the traceback
going into detail.
Gah, will look at this and fix.
Jan