diff options
Diffstat (limited to 'src/pl/plpython/plpython.c')
-rw-r--r-- | src/pl/plpython/plpython.c | 236 |
1 files changed, 201 insertions, 35 deletions
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index f3f58901fb1..935258044db 100644 --- a/src/pl/plpython/plpython.c +++ b/src/pl/plpython/plpython.c @@ -71,6 +71,7 @@ typedef int Py_ssize_t; */ #if PY_MAJOR_VERSION >= 3 #define PyInt_FromLong(x) PyLong_FromLong(x) +#define PyInt_AsLong(x) PyLong_AsLong(x) #endif /* @@ -217,6 +218,7 @@ typedef struct PLyProcedure * type */ bool is_setof; /* true, if procedure returns result set */ PyObject *setof; /* contents of result set. */ + char *src; /* textual procedure code, after mangling */ char **argnames; /* Argument names */ PLyTypeInfo args[FUNC_MAX_ARGS]; int nargs; @@ -342,7 +344,7 @@ static void PLy_elog(int, const char *,...) __attribute__((format(printf, 2, 3))); static void PLy_get_spi_error_data(PyObject *exc, char **detail, char **hint, char **query, int *position); -static char *PLy_traceback(int *); +static void PLy_traceback(char **, char **, int *); static void *PLy_malloc(size_t); static void *PLy_malloc0(size_t); @@ -1602,6 +1604,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) proc->globals = NULL; proc->is_setof = procStruct->proretset; proc->setof = NULL; + proc->src = NULL; proc->argnames = NULL; PG_TRY(); @@ -1788,6 +1791,8 @@ PLy_procedure_compile(PLyProcedure *proc, const char *src) * insert the function code into the interpreter */ msrc = PLy_procedure_munge_source(proc->pyname, src); + /* Save the mangled source for later inclusion in tracebacks */ + proc->src = PLy_strdup(msrc); crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL); pfree(msrc); @@ -1885,6 +1890,8 @@ PLy_procedure_delete(PLyProcedure *proc) if (proc->argnames && proc->argnames[i]) PLy_free(proc->argnames[i]); } + if (proc->src) + PLy_free(proc->src); if (proc->argnames) PLy_free(proc->argnames); } @@ -4361,15 +4368,18 @@ failure: * the current Python error, previously set by PLy_exception_set(). * This should be used to propagate Python errors into PG. If fmt is * NULL, the Python error becomes the primary error message, otherwise - * it becomes the detail. + * it becomes the detail. If there is a Python traceback, it is put + * in the context. */ static void PLy_elog(int elevel, const char *fmt,...) { char *xmsg; - int xlevel; + char *tbmsg; + int tb_depth; StringInfoData emsg; PyObject *exc, *val, *tb; + const char *primary = NULL; char *detail = NULL; char *hint = NULL; char *query = NULL; @@ -4385,7 +4395,7 @@ PLy_elog(int elevel, const char *fmt,...) } PyErr_Restore(exc, val, tb); - xmsg = PLy_traceback(&xlevel); + PLy_traceback(&xmsg, &tbmsg, &tb_depth); if (fmt) { @@ -4402,24 +4412,30 @@ PLy_elog(int elevel, const char *fmt,...) break; enlargeStringInfo(&emsg, emsg.maxlen); } + primary = emsg.data; + + /* Since we have a format string, we cannot have a SPI detail. */ + Assert(detail == NULL); + + /* If there's an exception message, it goes in the detail. */ + if (xmsg) + detail = xmsg; + } + else + { + if (xmsg) + primary = xmsg; } PG_TRY(); { - if (fmt) - ereport(elevel, - (errmsg("%s", emsg.data), - (xmsg) ? errdetail("%s", xmsg) : 0, - (hint) ? errhint("%s", hint) : 0, - (query) ? internalerrquery(query) : 0, - (position) ? internalerrposition(position) : 0)); - else - ereport(elevel, - (errmsg("%s", xmsg), - (detail) ? errdetail("%s", detail) : 0, - (hint) ? errhint("%s", hint) : 0, - (query) ? internalerrquery(query) : 0, - (position) ? internalerrposition(position) : 0)); + ereport(elevel, + (errmsg("%s", primary ? primary : "no exception data"), + (detail) ? errdetail("%s", detail) : 0, + (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0, + (hint) ? errhint("%s", hint) : 0, + (query) ? internalerrquery(query) : 0, + (position) ? internalerrposition(position) : 0)); } PG_CATCH(); { @@ -4427,6 +4443,8 @@ PLy_elog(int elevel, const char *fmt,...) pfree(emsg.data); if (xmsg) pfree(xmsg); + if (tbmsg) + pfree(tbmsg); PG_RE_THROW(); } PG_END_TRY(); @@ -4435,6 +4453,8 @@ PLy_elog(int elevel, const char *fmt,...) pfree(emsg.data); if (xmsg) pfree(xmsg); + if (tbmsg) + pfree(tbmsg); } /* @@ -4458,9 +4478,47 @@ cleanup: Py_XDECREF(spidata); } - +/* + * Get the given source line as a palloc'd string + */ static char * -PLy_traceback(int *xlevel) +get_source_line(const char *src, int lineno) +{ + const char *s; + const char *next; + int current = 0; + + next = src; + while (current != lineno) + { + s = next; + next = strchr(s + 1, '\n'); + current++; + if (next == NULL) + break; + } + + if (current != lineno) + return NULL; + + while (s && isspace((unsigned char) *s)) + s++; + + if (next == NULL) + return pstrdup(s); + + return pnstrdup(s, next - s); +} + +/* + * Extract a Python traceback from the current exception. + * + * The exception error message is returned in xmsg, the traceback in + * tbmsg (both as palloc'd strings) and the traceback depth in + * tb_depth. + */ +static void +PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth) { PyObject *e, *v, @@ -4472,6 +4530,7 @@ PLy_traceback(int *xlevel) PyObject *vob = NULL; char *vstr; StringInfoData xstr; + StringInfoData tbstr; /* * get the current exception @@ -4483,12 +4542,18 @@ PLy_traceback(int *xlevel) */ if (e == NULL) { - *xlevel = WARNING; - return NULL; + *xmsg = NULL; + *tbmsg = NULL; + *tb_depth = 0; + + return; } PyErr_NormalizeException(&e, &v, &tb); - Py_XDECREF(tb); + + /* + * Format the exception and its value and put it in xmsg. + */ e_type_o = PyObject_GetAttrString(e, "__name__"); e_module_o = PyObject_GetAttrString(e, "__module__"); @@ -4521,23 +4586,124 @@ PLy_traceback(int *xlevel) appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s); appendStringInfo(&xstr, ": %s", vstr); - Py_XDECREF(e_type_o); - Py_XDECREF(e_module_o); - Py_XDECREF(vob); - Py_XDECREF(v); + *xmsg = xstr.data; /* - * intuit an appropriate error level based on the exception type + * Now format the traceback and put it in tbmsg. */ - if (PLy_exc_error && PyErr_GivenExceptionMatches(e, PLy_exc_error)) - *xlevel = ERROR; - else if (PLy_exc_fatal && PyErr_GivenExceptionMatches(e, PLy_exc_fatal)) - *xlevel = FATAL; - else - *xlevel = ERROR; + *tb_depth = 0; + initStringInfo(&tbstr); + /* Mimick Python traceback reporting as close as possible. */ + appendStringInfoString(&tbstr, "Traceback (most recent call last):"); + while (tb != NULL && tb != Py_None) + { + PyObject *volatile tb_prev = NULL; + PyObject *volatile frame = NULL; + PyObject *volatile code = NULL; + PyObject *volatile name = NULL; + PyObject *volatile lineno = NULL; + + PG_TRY(); + { + lineno = PyObject_GetAttrString(tb, "tb_lineno"); + if (lineno == NULL) + elog(ERROR, "could not get line number from Python traceback"); + + frame = PyObject_GetAttrString(tb, "tb_frame"); + if (frame == NULL) + elog(ERROR, "could not get frame from Python traceback"); + + code = PyObject_GetAttrString(frame, "f_code"); + if (code == NULL) + elog(ERROR, "could not get code object from Python frame"); + + name = PyObject_GetAttrString(code, "co_name"); + if (name == NULL) + elog(ERROR, "could not get function name from Python code object"); + } + PG_CATCH(); + { + Py_XDECREF(frame); + Py_XDECREF(code); + Py_XDECREF(name); + Py_XDECREF(lineno); + PG_RE_THROW(); + } + PG_END_TRY(); + + /* The first frame always points at <module>, skip it. */ + if (*tb_depth > 0) + { + char *proname; + char *fname; + char *line; + long plain_lineno; + + /* + * The second frame points at the internal function, but + * to mimick Python error reporting we want to say + * <module>. + */ + if (*tb_depth == 1) + fname = "<module>"; + else + fname = PyString_AsString(name); + + proname = PLy_procedure_name(PLy_curr_procedure); + plain_lineno = PyInt_AsLong(lineno); + + if (proname == NULL) + appendStringInfo( + &tbstr, "\n PL/Python anonymous code block, line %ld, in %s", + plain_lineno - 1, fname); + else + appendStringInfo( + &tbstr, "\n PL/Python function \"%s\", line %ld, in %s", + proname, plain_lineno - 1, fname); + + if (PLy_curr_procedure) + { + /* + * If we know the current procedure, append the exact + * line from the source, again mimicking Python's + * traceback.py module behavior. We could store the + * already line-split source to avoid splitting it + * every time, but producing a traceback is not the + * most important scenario to optimize for. + */ + line = get_source_line(PLy_curr_procedure->src, plain_lineno); + if (line) + { + appendStringInfo(&tbstr, "\n %s", line); + pfree(line); + } + } + } + + Py_DECREF(frame); + Py_DECREF(code); + Py_DECREF(name); + Py_DECREF(lineno); + + /* Release the current frame and go to the next one. */ + tb_prev = tb; + tb = PyObject_GetAttrString(tb, "tb_next"); + Assert(tb_prev != Py_None); + Py_DECREF(tb_prev); + if (tb == NULL) + elog(ERROR, "could not traverse Python traceback"); + (*tb_depth)++; + } + + /* Return the traceback. */ + *tbmsg = tbstr.data; + + Py_XDECREF(e_type_o); + Py_XDECREF(e_module_o); + Py_XDECREF(vob); + Py_XDECREF(v); Py_DECREF(e); - return xstr.data; } /* python module code */ |