On 06/02/11 20:12, Jan Urbański wrote: > On 27/01/11 22:58, Jan Urbański wrote: >> On 23/12/10 14:56, Jan Urbański wrote: >>> Here's a patch implementing traceback support for PL/Python mentioned in >>> http://archives.postgresql.org/pgsql-hackers/2010-12/msg01991.php. It's >>> an incremental patch on top of the plpython-refactor patch sent eariler. >> >> Updated to master. > > Updated to master again.
Once more.
diff --git a/src/pl/plpython/expected/plpython_do.out b/src/pl/plpython/expected/plpython_do.out index a21b088..fb0f0e5 100644 *** a/src/pl/plpython/expected/plpython_do.out --- b/src/pl/plpython/expected/plpython_do.out *************** NOTICE: This is plpythonu. *** 3,6 **** --- 3,9 ---- CONTEXT: PL/Python anonymous code block DO $$ nonsense $$ LANGUAGE plpythonu; ERROR: NameError: global name 'nonsense' is not defined + DETAIL: Traceback (most recent call last): + PL/Python anonymous code block, line 1, in <module> + nonsense CONTEXT: PL/Python anonymous code block diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out index 7597ca7..08b6ba4 100644 *** a/src/pl/plpython/expected/plpython_error.out --- b/src/pl/plpython/expected/plpython_error.out *************** SELECT sql_syntax_error(); *** 35,40 **** --- 35,43 ---- ERROR: plpy.SPIError: syntax error at or near "syntax" LINE 1: syntax error ^ + DETAIL: Traceback (most recent call last): + PL/Python function "sql_syntax_error", line 1, in <module> + plpy.execute("syntax error") QUERY: syntax error CONTEXT: PL/Python function "sql_syntax_error" /* check the handling of uncaught python exceptions *************** CREATE FUNCTION exception_index_invalid( *** 45,50 **** --- 48,56 ---- LANGUAGE plpythonu; SELECT exception_index_invalid('test'); ERROR: IndexError: list index out of range + DETAIL: Traceback (most recent call last): + PL/Python function "exception_index_invalid", line 1, in <module> + return args[1] CONTEXT: PL/Python function "exception_index_invalid" /* check handling of nested exceptions */ *************** SELECT exception_index_invalid_nested(); *** 57,62 **** --- 63,71 ---- ERROR: plpy.SPIError: function test5(unknown) does not exist LINE 1: SELECT test5('foo') ^ + DETAIL: Traceback (most recent call last): + PL/Python function "exception_index_invalid_nested", line 1, in <module> + rv = plpy.execute("SELECT test5('foo')") HINT: No function matches the given name and argument types. You might need to add explicit type casts. QUERY: SELECT test5('foo') CONTEXT: PL/Python function "exception_index_invalid_nested" *************** return None *** 75,80 **** --- 84,92 ---- LANGUAGE plpythonu; SELECT invalid_type_uncaught('rick'); ERROR: plpy.SPIError: type "test" does not exist + DETAIL: Traceback (most recent call last): + PL/Python function "invalid_type_uncaught", line 3, in <module> + SD["plan"] = plpy.prepare(q, [ "test" ]) CONTEXT: PL/Python function "invalid_type_uncaught" /* for what it's worth catch the exception generated by * the typo, and return None *************** return None *** 121,126 **** --- 133,141 ---- LANGUAGE plpythonu; SELECT invalid_type_reraised('rick'); ERROR: plpy.Error: type "test" does not exist + DETAIL: Traceback (most recent call last): + PL/Python function "invalid_type_reraised", line 6, in <module> + plpy.error(str(ex)) CONTEXT: PL/Python function "invalid_type_reraised" /* no typo no messing about */ *************** SELECT valid_type('rick'); *** 140,145 **** --- 155,255 ---- (1 row) + /* error in nested functions to get a traceback + */ + CREATE FUNCTION nested_error() RETURNS text + AS + 'def fun1(): + plpy.error("boom") + + def fun2(): + fun1() + + def fun3(): + fun2() + + fun3() + return "not reached" + ' + LANGUAGE plpythonu; + SELECT nested_error(); + ERROR: plpy.Error: boom + DETAIL: Traceback (most recent call last): + PL/Python function "nested_error", line 10, in <module> + fun3() + PL/Python function "nested_error", line 8, in fun3 + fun2() + PL/Python function "nested_error", line 5, in fun2 + fun1() + PL/Python function "nested_error", line 2, in fun1 + plpy.error("boom") + CONTEXT: PL/Python function "nested_error" + /* raising plpy.Error is just like calling plpy.error + */ + CREATE FUNCTION nested_error_raise() RETURNS text + AS + 'def fun1(): + raise plpy.Error("boom") + + def fun2(): + fun1() + + def fun3(): + fun2() + + fun3() + return "not reached" + ' + LANGUAGE plpythonu; + SELECT nested_error_raise(); + ERROR: plpy.Error: boom + DETAIL: Traceback (most recent call last): + PL/Python function "nested_error_raise", line 10, in <module> + fun3() + PL/Python function "nested_error_raise", line 8, in fun3 + fun2() + PL/Python function "nested_error_raise", line 5, in fun2 + fun1() + PL/Python function "nested_error_raise", line 2, in fun1 + raise plpy.Error("boom") + CONTEXT: PL/Python function "nested_error_raise" + /* using plpy.warning should not produce a traceback + */ + CREATE FUNCTION nested_warning() RETURNS text + AS + 'def fun1(): + plpy.warning("boom") + + def fun2(): + fun1() + + def fun3(): + fun2() + + fun3() + return "you''ve been warned" + ' + LANGUAGE plpythonu; + SELECT nested_warning(); + WARNING: boom + CONTEXT: PL/Python function "nested_warning" + nested_warning + -------------------- + you've been warned + (1 row) + + /* AttirbuteError at toplevel used to give segfaults with the traceback + */ + CREATE FUNCTION toplevel_attribute_error() RETURNS void AS + $$ + plpy.nonexistent + $$ LANGUAGE plpythonu; + SELECT toplevel_attribute_error(); + ERROR: AttributeError: 'module' object has no attribute 'nonexistent' + DETAIL: Traceback (most recent call last): + PL/Python function "toplevel_attribute_error", line 2, in <module> + plpy.nonexistent + CONTEXT: PL/Python function "toplevel_attribute_error" /* manually starting subtransactions - a bad idea */ CREATE FUNCTION manual_subxact() RETURNS void AS $$ *************** plpy.execute("rollback to save") *** 149,154 **** --- 259,267 ---- $$ LANGUAGE plpythonu; SELECT manual_subxact(); ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION + DETAIL: Traceback (most recent call last): + PL/Python function "manual_subxact", line 2, in <module> + plpy.execute("savepoint save") CONTEXT: PL/Python function "manual_subxact" /* same for prepared plans */ *************** plpy.execute(rollback) *** 161,164 **** --- 274,280 ---- $$ LANGUAGE plpythonu; SELECT manual_subxact_prepared(); ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION + DETAIL: Traceback (most recent call last): + PL/Python function "manual_subxact_prepared", line 4, in <module> + plpy.execute(save) CONTEXT: PL/Python function "manual_subxact_prepared" diff --git a/src/pl/plpython/expected/plpython_error_0.out b/src/pl/plpython/expected/plpython_error_0.out index 42e4119..318f5e2 100644 *** a/src/pl/plpython/expected/plpython_error_0.out --- b/src/pl/plpython/expected/plpython_error_0.out *************** SELECT sql_syntax_error(); *** 35,40 **** --- 35,43 ---- ERROR: plpy.SPIError: syntax error at or near "syntax" LINE 1: syntax error ^ + DETAIL: Traceback (most recent call last): + PL/Python function "sql_syntax_error", line 1, in <module> + plpy.execute("syntax error") QUERY: syntax error CONTEXT: PL/Python function "sql_syntax_error" /* check the handling of uncaught python exceptions *************** CREATE FUNCTION exception_index_invalid( *** 45,50 **** --- 48,56 ---- LANGUAGE plpythonu; SELECT exception_index_invalid('test'); ERROR: IndexError: list index out of range + DETAIL: Traceback (most recent call last): + PL/Python function "exception_index_invalid", line 1, in <module> + return args[1] CONTEXT: PL/Python function "exception_index_invalid" /* check handling of nested exceptions */ *************** SELECT exception_index_invalid_nested(); *** 57,62 **** --- 63,71 ---- ERROR: plpy.SPIError: function test5(unknown) does not exist LINE 1: SELECT test5('foo') ^ + DETAIL: Traceback (most recent call last): + PL/Python function "exception_index_invalid_nested", line 1, in <module> + rv = plpy.execute("SELECT test5('foo')") HINT: No function matches the given name and argument types. You might need to add explicit type casts. QUERY: SELECT test5('foo') CONTEXT: PL/Python function "exception_index_invalid_nested" *************** return None *** 75,80 **** --- 84,92 ---- LANGUAGE plpythonu; SELECT invalid_type_uncaught('rick'); ERROR: plpy.SPIError: type "test" does not exist + DETAIL: Traceback (most recent call last): + PL/Python function "invalid_type_uncaught", line 3, in <module> + SD["plan"] = plpy.prepare(q, [ "test" ]) CONTEXT: PL/Python function "invalid_type_uncaught" /* for what it's worth catch the exception generated by * the typo, and return None *************** return None *** 121,126 **** --- 133,141 ---- LANGUAGE plpythonu; SELECT invalid_type_reraised('rick'); ERROR: plpy.Error: type "test" does not exist + DETAIL: Traceback (most recent call last): + PL/Python function "invalid_type_reraised", line 6, in <module> + plpy.error(str(ex)) CONTEXT: PL/Python function "invalid_type_reraised" /* no typo no messing about */ *************** SELECT valid_type('rick'); *** 140,145 **** --- 155,255 ---- (1 row) + /* error in nested functions to get a traceback + */ + CREATE FUNCTION nested_error() RETURNS text + AS + 'def fun1(): + plpy.error("boom") + + def fun2(): + fun1() + + def fun3(): + fun2() + + fun3() + return "not reached" + ' + LANGUAGE plpythonu; + SELECT nested_error(); + ERROR: plpy.Error: boom + DETAIL: Traceback (most recent call last): + PL/Python function "nested_error", line 10, in <module> + fun3() + PL/Python function "nested_error", line 8, in fun3 + fun2() + PL/Python function "nested_error", line 5, in fun2 + fun1() + PL/Python function "nested_error", line 2, in fun1 + plpy.error("boom") + CONTEXT: PL/Python function "nested_error" + /* raising plpy.Error is just like calling plpy.error + */ + CREATE FUNCTION nested_error_raise() RETURNS text + AS + 'def fun1(): + raise plpy.Error("boom") + + def fun2(): + fun1() + + def fun3(): + fun2() + + fun3() + return "not reached" + ' + LANGUAGE plpythonu; + SELECT nested_error_raise(); + ERROR: plpy.Error: boom + DETAIL: Traceback (most recent call last): + PL/Python function "nested_error_raise", line 10, in <module> + fun3() + PL/Python function "nested_error_raise", line 8, in fun3 + fun2() + PL/Python function "nested_error_raise", line 5, in fun2 + fun1() + PL/Python function "nested_error_raise", line 2, in fun1 + raise plpy.Error("boom") + CONTEXT: PL/Python function "nested_error_raise" + /* using plpy.warning should not produce a traceback + */ + CREATE FUNCTION nested_warning() RETURNS text + AS + 'def fun1(): + plpy.warning("boom") + + def fun2(): + fun1() + + def fun3(): + fun2() + + fun3() + return "you''ve been warned" + ' + LANGUAGE plpythonu; + SELECT nested_warning(); + WARNING: boom + CONTEXT: PL/Python function "nested_warning" + nested_warning + -------------------- + you've been warned + (1 row) + + /* AttirbuteError at toplevel used to give segfaults with the traceback + */ + CREATE FUNCTION toplevel_attribute_error() RETURNS void AS + $$ + plpy.nonexistent + $$ LANGUAGE plpythonu; + SELECT toplevel_attribute_error(); + ERROR: AttributeError: 'module' object has no attribute 'nonexistent' + DETAIL: Traceback (most recent call last): + PL/Python function "toplevel_attribute_error", line 2, in <module> + plpy.nonexistent + CONTEXT: PL/Python function "toplevel_attribute_error" /* manually starting subtransactions - a bad idea */ CREATE FUNCTION manual_subxact() RETURNS void AS $$ *************** plpy.execute("rollback to save") *** 149,154 **** --- 259,267 ---- $$ LANGUAGE plpythonu; SELECT manual_subxact(); ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION + DETAIL: Traceback (most recent call last): + PL/Python function "manual_subxact", line 2, in <module> + plpy.execute("savepoint save") CONTEXT: PL/Python function "manual_subxact" /* same for prepared plans */ *************** plpy.execute(rollback) *** 161,164 **** --- 274,280 ---- $$ LANGUAGE plpythonu; SELECT manual_subxact_prepared(); ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION + DETAIL: Traceback (most recent call last): + PL/Python function "manual_subxact_prepared", line 4, in <module> + plpy.execute(save) CONTEXT: PL/Python function "manual_subxact_prepared" diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out index d92c987..f3bdb3b 100644 *** a/src/pl/plpython/expected/plpython_test.out --- b/src/pl/plpython/expected/plpython_test.out *************** CONTEXT: PL/Python function "elog_test" *** 74,77 **** --- 74,80 ---- WARNING: warning CONTEXT: PL/Python function "elog_test" ERROR: plpy.Error: error + DETAIL: Traceback (most recent call last): + PL/Python function "elog_test", line 10, in <module> + plpy.error('error') CONTEXT: PL/Python function "elog_test" diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index fff7de7..436f546 100644 *** a/src/pl/plpython/plpython.c --- b/src/pl/plpython/plpython.c *************** typedef int Py_ssize_t; *** 71,76 **** --- 71,77 ---- */ #if PY_MAJOR_VERSION >= 3 #define PyInt_FromLong(x) PyLong_FromLong(x) + #define PyInt_AsLong(x) PyLong_AsLong(x) #endif /* *************** typedef struct PLyProcedure *** 210,215 **** --- 211,217 ---- * type */ bool is_setof; /* true, if procedure returns result set */ PyObject *setof; /* contents of result set. */ + char *src; /* textual procedure code, after mangling */ char **argnames; /* Argument names */ PLyTypeInfo args[FUNC_MAX_ARGS]; int nargs; *************** static void *** 299,305 **** PLy_elog(int, const char *,...) __attribute__((format(printf, 2, 3))); static void PLy_get_spi_error_data(PyObject *exc, char **detail, char **hint, char **query, int *position); ! static char *PLy_traceback(int *); static void *PLy_malloc(size_t); static void *PLy_malloc0(size_t); --- 301,307 ---- PLy_elog(int, const char *,...) __attribute__((format(printf, 2, 3))); static void PLy_get_spi_error_data(PyObject *exc, char **detail, char **hint, char **query, int *position); ! static void PLy_traceback(char **, char **, int *); static void *PLy_malloc(size_t); static void *PLy_malloc0(size_t); *************** PLy_function_handler(FunctionCallInfo fc *** 1104,1112 **** PLy_function_delete_args(proc); if (has_error) ! ereport(ERROR, ! (errcode(ERRCODE_DATA_EXCEPTION), ! errmsg("error fetching next item from iterator"))); /* Disconnect from the SPI manager before returning */ if (SPI_finish() != SPI_OK_FINISH) --- 1106,1112 ---- PLy_function_delete_args(proc); if (has_error) ! PLy_elog(ERROR, "error fetching next item from iterator"); /* Disconnect from the SPI manager before returning */ if (SPI_finish() != SPI_OK_FINISH) *************** PLy_procedure_create(HeapTuple procTup, *** 1440,1445 **** --- 1440,1446 ---- proc->is_setof = procStruct->proretset; proc->setof = NULL; proc->argnames = NULL; + proc->src = NULL; PG_TRY(); { *************** PLy_procedure_compile(PLyProcedure *proc *** 1620,1625 **** --- 1621,1628 ---- * insert the function code into the interpreter */ msrc = PLy_procedure_munge_source(proc->pyname, src); + /* Save the mangled source for later inclusion in tracebacks */ + proc->src = PLy_strdup(msrc); crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL); pfree(msrc); *************** PLy_procedure_delete(PLyProcedure *proc) *** 1717,1722 **** --- 1720,1727 ---- if (proc->argnames && proc->argnames[i]) PLy_free(proc->argnames[i]); } + if (proc->src) + PLy_free(proc->src); if (proc->argnames) PLy_free(proc->argnames); } *************** PLy_spi_execute_plan(PyObject *ob, PyObj *** 3068,3075 **** PLy_elog(ERROR, "could not execute plan"); sv = PyString_AsString(so); PLy_exception_set_plural(PyExc_TypeError, ! "Expected sequence of %d argument, got %d: %s", ! "Expected sequence of %d arguments, got %d: %s", plan->nargs, plan->nargs, nargs, sv); Py_DECREF(so); --- 3073,3080 ---- PLy_elog(ERROR, "could not execute plan"); sv = PyString_AsString(so); PLy_exception_set_plural(PyExc_TypeError, ! "Expected sequence of %d argument, got %d: %s", ! "Expected sequence of %d arguments, got %d: %s", plan->nargs, plan->nargs, nargs, sv); Py_DECREF(so); *************** failure: *** 3706,3778 **** * the current Python error, previously set by PLy_exception_set(). * This should be used to propagate Python errors into PG. If fmt is * NULL, the Python error becomes the primary error message, otherwise ! * it becomes the detail. */ static void PLy_elog(int elevel, const char *fmt,...) { - char *xmsg; - int xlevel; - StringInfoData emsg; PyObject *exc, *val, *tb; char *detail = NULL; char *hint = NULL; char *query = NULL; int position = 0; PyErr_Fetch(&exc, &val, &tb); if (exc != NULL && PyErr_GivenExceptionMatches(val, PLy_exc_spi_error)) PLy_get_spi_error_data(val, &detail, &hint, &query, &position); PyErr_Restore(exc, val, tb); ! xmsg = PLy_traceback(&xlevel); if (fmt) { ! initStringInfo(&emsg); for (;;) { va_list ap; bool success; va_start(ap, fmt); ! success = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap); va_end(ap); if (success) break; ! enlargeStringInfo(&emsg, emsg.maxlen); } } PG_TRY(); { if (fmt) ! ereport(elevel, ! (errmsg("%s", emsg.data), ! (xmsg) ? errdetail("%s", xmsg) : 0, ! (hint) ? errhint("%s", hint) : 0, ! (query) ? internalerrquery(query) : 0, ! (position) ? internalerrposition(position) : 0)); else ! ereport(elevel, ! (errmsg("%s", xmsg), ! (detail) ? errdetail("%s", detail) : 0, ! (hint) ? errhint("%s", hint) : 0, ! (query) ? internalerrquery(query) : 0, ! (position) ? internalerrposition(position) : 0)); } PG_CATCH(); { ! if (fmt) ! pfree(emsg.data); if (xmsg) pfree(xmsg); PG_RE_THROW(); } PG_END_TRY(); ! if (fmt) ! pfree(emsg.data); if (xmsg) pfree(xmsg); } --- 3711,3826 ---- * the current Python error, previously set by PLy_exception_set(). * This should be used to propagate Python errors into PG. If fmt is * NULL, the Python error becomes the primary error message, otherwise ! * it becomes the detail. If there is a Python traceback, it is also put ! * in the detail. */ static void PLy_elog(int elevel, const char *fmt,...) { PyObject *exc, *val, *tb; char *detail = NULL; char *hint = NULL; char *query = NULL; int position = 0; + char *fmsg = NULL; + char *emsg = NULL; + char *xmsg = NULL; + int tb_depth = 0; PyErr_Fetch(&exc, &val, &tb); if (exc != NULL && PyErr_GivenExceptionMatches(val, PLy_exc_spi_error)) PLy_get_spi_error_data(val, &detail, &hint, &query, &position); PyErr_Restore(exc, val, tb); ! /* this is a no-op if there is no current Python exception */ ! PLy_traceback(&emsg, &xmsg, &tb_depth); if (fmt) { ! StringInfoData si; ! initStringInfo(&si); for (;;) { va_list ap; bool success; va_start(ap, fmt); ! success = appendStringInfoVA(&si, dgettext(TEXTDOMAIN, fmt), ap); va_end(ap); if (success) break; ! enlargeStringInfo(&si, si.maxlen); } + fmsg = si.data; } PG_TRY(); { + /* If we have a format string, it should be the main error message and + * the emsg + traceback the detailed message. + * + * If we don't have fmsg, we should use emsg as the main error message + * (and failing that just say "no exception data") and put the + * traceback in the detail. + * + * The traceback is present if tb_depth > 0. + */ if (fmt) ! { ! if (tb_depth > 0) ! { ! ereport(elevel, ! (errmsg("%s", fmsg), ! (emsg) ? errdetail("%s\n%s", emsg, xmsg) : 0, ! (hint) ? errhint("%s", hint) : 0, ! (query) ? internalerrquery(query) : 0, ! (position) ? internalerrposition(position) : 0)); ! } ! else ! { ! ereport(elevel, ! (errmsg("%s", fmsg), ! (emsg) ? errdetail("%s", emsg) : 0, ! (hint) ? errhint("%s", hint) : 0, ! (query) ? internalerrquery(query) : 0, ! (position) ? internalerrposition(position) : 0)); ! } ! } else ! { ! if (tb_depth > 0) ! { ! ereport(elevel, ! (errmsg("%s", emsg ? emsg : "no exception data"), ! (detail) ? errdetail("%s\n%s", detail, xmsg) : errdetail("%s", xmsg), ! (hint) ? errhint("%s", hint) : 0, ! (query) ? internalerrquery(query) : 0, ! (position) ? internalerrposition(position) : 0)); ! } ! else ! { ! ereport(elevel, ! (errmsg("%s", emsg ? emsg : "no exception data"), ! (detail) ? errdetail("%s", detail) : 0, ! (hint) ? errhint("%s", hint) : 0, ! (query) ? internalerrquery(query) : 0, ! (position) ? internalerrposition(position) : 0)); ! } ! } } PG_CATCH(); { ! if (fmsg) ! pfree(fmsg); ! if (emsg) ! pfree(emsg); if (xmsg) pfree(xmsg); PG_RE_THROW(); } PG_END_TRY(); ! pfree(emsg); if (xmsg) pfree(xmsg); } *************** cleanup: *** 3799,3806 **** } static char * ! PLy_traceback(int *xlevel) { PyObject *e, *v, --- 3847,3890 ---- } + /* Get the given source line as a palloc'd string */ static char * ! get_source_line(char *src, int lineno) ! { ! char *s; ! char *next; ! int current = 0; ! ! next = src; ! while (current != lineno) ! { ! s = next; ! next = strchr(s + 1, '\n'); ! current++; ! if (next == NULL) ! break; ! } ! ! if (current != lineno) ! return NULL; ! ! while (s && isspace(*s)) ! s++; ! ! if (next == NULL) ! return pstrdup(s); ! ! return pnstrdup(s, next - s); ! } ! ! /* ! * Extract a Python traceback from the current exception. ! * ! * The exception error message is returned in emsg, the traceback in xmsg (both ! * as palloc's strings) and the traceback depth in tb_depth. ! */ ! static void ! PLy_traceback(char **emsg, char **xmsg, int *tb_depth) { PyObject *e, *v, *************** PLy_traceback(int *xlevel) *** 3811,3816 **** --- 3895,3901 ---- char *e_module_s = NULL; PyObject *vob = NULL; char *vstr; + StringInfoData estr; StringInfoData xstr; /* *************** PLy_traceback(int *xlevel) *** 3822,3834 **** * oops, no exception, return */ if (e == NULL) ! { ! *xlevel = WARNING; ! return NULL; ! } PyErr_NormalizeException(&e, &v, &tb); ! Py_XDECREF(tb); e_type_o = PyObject_GetAttrString(e, "__name__"); e_module_o = PyObject_GetAttrString(e, "__module__"); --- 3907,3917 ---- * oops, no exception, return */ if (e == NULL) ! return; PyErr_NormalizeException(&e, &v, &tb); ! ! /* format the exception and its value and put it in emsg */ e_type_o = PyObject_GetAttrString(e, "__name__"); e_module_o = PyObject_GetAttrString(e, "__module__"); *************** PLy_traceback(int *xlevel) *** 3842,3883 **** else vstr = "unknown"; ! initStringInfo(&xstr); if (!e_type_s || !e_module_s) { if (PyString_Check(e)) /* deprecated string exceptions */ ! appendStringInfoString(&xstr, PyString_AsString(e)); else /* shouldn't happen */ ! appendStringInfoString(&xstr, "unrecognized exception"); } /* mimics behavior of traceback.format_exception_only */ else if (strcmp(e_module_s, "builtins") == 0 || strcmp(e_module_s, "__main__") == 0 || strcmp(e_module_s, "exceptions") == 0) ! appendStringInfo(&xstr, "%s", e_type_s); else ! appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s); ! appendStringInfo(&xstr, ": %s", vstr); Py_XDECREF(e_type_o); Py_XDECREF(e_module_o); Py_XDECREF(vob); Py_XDECREF(v); - - /* - * intuit an appropriate error level based on the exception type - */ - if (PLy_exc_error && PyErr_GivenExceptionMatches(e, PLy_exc_error)) - *xlevel = ERROR; - else if (PLy_exc_fatal && PyErr_GivenExceptionMatches(e, PLy_exc_fatal)) - *xlevel = FATAL; - else - *xlevel = ERROR; - Py_DECREF(e); - return xstr.data; } /* python module code */ --- 3925,4062 ---- else vstr = "unknown"; ! initStringInfo(&estr); if (!e_type_s || !e_module_s) { if (PyString_Check(e)) /* deprecated string exceptions */ ! appendStringInfoString(&estr, PyString_AsString(e)); else /* shouldn't happen */ ! appendStringInfoString(&estr, "unrecognized exception"); } /* mimics behavior of traceback.format_exception_only */ else if (strcmp(e_module_s, "builtins") == 0 || strcmp(e_module_s, "__main__") == 0 || strcmp(e_module_s, "exceptions") == 0) ! appendStringInfo(&estr, "%s", e_type_s); else ! appendStringInfo(&estr, "%s.%s", e_module_s, e_type_s); ! appendStringInfo(&estr, ": %s", vstr); ! ! *emsg = estr.data; ! ! /* now format the traceback and put it in xmsg */ ! *tb_depth = 0; ! initStringInfo(&xstr); ! /* Mimick Python traceback reporting as close as possible */ ! appendStringInfoString(&xstr, "Traceback (most recent call last):"); ! while (tb != NULL && tb != Py_None) ! { ! PyObject *volatile tb_prev = NULL; ! PyObject *volatile frame = NULL; ! PyObject *volatile code = NULL; ! PyObject *volatile name = NULL; ! PyObject *volatile lineno = NULL; ! ! PG_TRY(); ! { ! lineno = PyObject_GetAttrString(tb, "tb_lineno"); ! if (lineno == NULL) ! elog(ERROR, "could not get line number from Python traceback"); ! ! frame = PyObject_GetAttrString(tb, "tb_frame"); ! if (frame == NULL) ! elog(ERROR, "could not get frame from Python traceback"); ! ! code = PyObject_GetAttrString(frame, "f_code"); ! if (code == NULL) ! elog(ERROR, "could not get code object from Python frame"); ! ! name = PyObject_GetAttrString(code, "co_name"); ! if (name == NULL) ! elog(ERROR, "could not get function name from Python code object"); ! } ! PG_CATCH(); ! { ! Py_XDECREF(frame); ! Py_XDECREF(code); ! Py_XDECREF(name); ! Py_XDECREF(lineno); ! PG_RE_THROW(); ! } ! PG_END_TRY(); ! ! /* The first frame always points at <module>, skip it */ ! if (*tb_depth > 0) ! { ! char *proname; ! char *fname; ! char *line; ! long plain_lineno; ! ! /* ! * The second frame points at the internal function, but to mimick ! * Python error reporting we want to say <module> ! */ ! if (*tb_depth == 1) ! fname = "<module>"; ! else ! fname = PyString_AsString(name); ! ! proname = PLy_procedure_name(PLy_curr_procedure); ! plain_lineno = PyInt_AsLong(lineno); ! ! if (proname == NULL) ! appendStringInfo( ! &xstr, "\n PL/Python anonymous code block, line %ld, in %s", ! plain_lineno - 1, fname); ! else ! appendStringInfo( ! &xstr, "\n PL/Python function \"%s\", line %ld, in %s", ! proname, plain_lineno - 1, fname); ! ! if (PLy_curr_procedure) ! { ! /* ! * If we know the current procedure, append the exact line from ! * the source, again mimicking Python's traceback.py module ! * behaviour. We could store the already line-splitted source ! * to avoid splitting it every time, but producing a traceback ! * is not the most important scenario to optimise for. ! */ ! line = get_source_line(PLy_curr_procedure->src, plain_lineno); ! if (line != NULL) ! { ! appendStringInfo(&xstr, "\n %s", line); ! pfree(line); ! } ! } ! } ! ! Py_DECREF(frame); ! Py_DECREF(code); ! Py_DECREF(name); ! Py_DECREF(lineno); ! ! /* Release the current frame and go to the next one */ ! tb_prev = tb; ! tb = PyObject_GetAttrString(tb, "tb_next"); ! Assert(tb_prev != Py_None); ! Py_DECREF(tb_prev); ! if (tb == NULL) ! elog(ERROR, "could not traverse Python traceback"); ! (*tb_depth)++; ! } ! ! /* Return the traceback */ ! *xmsg = xstr.data; Py_XDECREF(e_type_o); Py_XDECREF(e_module_o); Py_XDECREF(vob); Py_XDECREF(v); Py_DECREF(e); } /* python module code */ diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql index 7861cd6..5d5477e 100644 *** a/src/pl/plpython/sql/plpython_error.sql --- b/src/pl/plpython/sql/plpython_error.sql *************** return None *** 131,136 **** --- 131,205 ---- SELECT valid_type('rick'); + /* error in nested functions to get a traceback + */ + CREATE FUNCTION nested_error() RETURNS text + AS + 'def fun1(): + plpy.error("boom") + + def fun2(): + fun1() + + def fun3(): + fun2() + + fun3() + return "not reached" + ' + LANGUAGE plpythonu; + + SELECT nested_error(); + + /* raising plpy.Error is just like calling plpy.error + */ + CREATE FUNCTION nested_error_raise() RETURNS text + AS + 'def fun1(): + raise plpy.Error("boom") + + def fun2(): + fun1() + + def fun3(): + fun2() + + fun3() + return "not reached" + ' + LANGUAGE plpythonu; + + SELECT nested_error_raise(); + + /* using plpy.warning should not produce a traceback + */ + CREATE FUNCTION nested_warning() RETURNS text + AS + 'def fun1(): + plpy.warning("boom") + + def fun2(): + fun1() + + def fun3(): + fun2() + + fun3() + return "you''ve been warned" + ' + LANGUAGE plpythonu; + + SELECT nested_warning(); + + /* AttirbuteError at toplevel used to give segfaults with the traceback + */ + CREATE FUNCTION toplevel_attribute_error() RETURNS void AS + $$ + plpy.nonexistent + $$ LANGUAGE plpythonu; + + SELECT toplevel_attribute_error(); + /* manually starting subtransactions - a bad idea */ CREATE FUNCTION manual_subxact() RETURNS void AS $$
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers