Header And Logo

PostgreSQL
| The world's most advanced open source database.

plpy_elog.c

Go to the documentation of this file.
00001 /*
00002  * reporting Python exceptions as PostgreSQL errors
00003  *
00004  * src/pl/plpython/plpy_elog.c
00005  */
00006 
00007 #include "postgres.h"
00008 
00009 #include "lib/stringinfo.h"
00010 
00011 #include "plpython.h"
00012 
00013 #include "plpy_elog.h"
00014 
00015 #include "plpy_main.h"
00016 #include "plpy_procedure.h"
00017 
00018 
00019 PyObject   *PLy_exc_error = NULL;
00020 PyObject   *PLy_exc_fatal = NULL;
00021 PyObject   *PLy_exc_spi_error = NULL;
00022 
00023 
00024 static void PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth);
00025 static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
00026                        char **hint, char **query, int *position);
00027 static char *get_source_line(const char *src, int lineno);
00028 
00029 
00030 /*
00031  * Emit a PG error or notice, together with any available info about
00032  * the current Python error, previously set by PLy_exception_set().
00033  * This should be used to propagate Python errors into PG.  If fmt is
00034  * NULL, the Python error becomes the primary error message, otherwise
00035  * it becomes the detail.  If there is a Python traceback, it is put
00036  * in the context.
00037  */
00038 void
00039 PLy_elog(int elevel, const char *fmt,...)
00040 {
00041     char       *xmsg;
00042     char       *tbmsg;
00043     int         tb_depth;
00044     StringInfoData emsg;
00045     PyObject   *exc,
00046                *val,
00047                *tb;
00048     const char *primary = NULL;
00049     int         sqlerrcode = 0;
00050     char       *detail = NULL;
00051     char       *hint = NULL;
00052     char       *query = NULL;
00053     int         position = 0;
00054 
00055     PyErr_Fetch(&exc, &val, &tb);
00056     if (exc != NULL)
00057     {
00058         if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
00059             PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position);
00060         else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
00061             elevel = FATAL;
00062     }
00063     PyErr_Restore(exc, val, tb);
00064 
00065     PLy_traceback(&xmsg, &tbmsg, &tb_depth);
00066 
00067     if (fmt)
00068     {
00069         initStringInfo(&emsg);
00070         for (;;)
00071         {
00072             va_list     ap;
00073             bool        success;
00074 
00075             va_start(ap, fmt);
00076             success = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap);
00077             va_end(ap);
00078             if (success)
00079                 break;
00080             enlargeStringInfo(&emsg, emsg.maxlen);
00081         }
00082         primary = emsg.data;
00083 
00084         /* Since we have a format string, we cannot have a SPI detail. */
00085         Assert(detail == NULL);
00086 
00087         /* If there's an exception message, it goes in the detail. */
00088         if (xmsg)
00089             detail = xmsg;
00090     }
00091     else
00092     {
00093         if (xmsg)
00094             primary = xmsg;
00095     }
00096 
00097     PG_TRY();
00098     {
00099         ereport(elevel,
00100                 (errcode(sqlerrcode ? sqlerrcode : ERRCODE_INTERNAL_ERROR),
00101               errmsg_internal("%s", primary ? primary : "no exception data"),
00102                  (detail) ? errdetail_internal("%s", detail) : 0,
00103                  (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0,
00104                  (hint) ? errhint("%s", hint) : 0,
00105                  (query) ? internalerrquery(query) : 0,
00106                  (position) ? internalerrposition(position) : 0));
00107     }
00108     PG_CATCH();
00109     {
00110         if (fmt)
00111             pfree(emsg.data);
00112         if (xmsg)
00113             pfree(xmsg);
00114         if (tbmsg)
00115             pfree(tbmsg);
00116         PG_RE_THROW();
00117     }
00118     PG_END_TRY();
00119 
00120     if (fmt)
00121         pfree(emsg.data);
00122     if (xmsg)
00123         pfree(xmsg);
00124     if (tbmsg)
00125         pfree(tbmsg);
00126 }
00127 
00128 /*
00129  * Extract a Python traceback from the current exception.
00130  *
00131  * The exception error message is returned in xmsg, the traceback in
00132  * tbmsg (both as palloc'd strings) and the traceback depth in
00133  * tb_depth.
00134  */
00135 static void
00136 PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
00137 {
00138     PyObject   *e,
00139                *v,
00140                *tb;
00141     PyObject   *e_type_o;
00142     PyObject   *e_module_o;
00143     char       *e_type_s = NULL;
00144     char       *e_module_s = NULL;
00145     PyObject   *vob = NULL;
00146     char       *vstr;
00147     StringInfoData xstr;
00148     StringInfoData tbstr;
00149 
00150     /*
00151      * get the current exception
00152      */
00153     PyErr_Fetch(&e, &v, &tb);
00154 
00155     /*
00156      * oops, no exception, return
00157      */
00158     if (e == NULL)
00159     {
00160         *xmsg = NULL;
00161         *tbmsg = NULL;
00162         *tb_depth = 0;
00163 
00164         return;
00165     }
00166 
00167     PyErr_NormalizeException(&e, &v, &tb);
00168 
00169     /*
00170      * Format the exception and its value and put it in xmsg.
00171      */
00172 
00173     e_type_o = PyObject_GetAttrString(e, "__name__");
00174     e_module_o = PyObject_GetAttrString(e, "__module__");
00175     if (e_type_o)
00176         e_type_s = PyString_AsString(e_type_o);
00177     if (e_type_s)
00178         e_module_s = PyString_AsString(e_module_o);
00179 
00180     if (v && ((vob = PyObject_Str(v)) != NULL))
00181         vstr = PyString_AsString(vob);
00182     else
00183         vstr = "unknown";
00184 
00185     initStringInfo(&xstr);
00186     if (!e_type_s || !e_module_s)
00187     {
00188         if (PyString_Check(e))
00189             /* deprecated string exceptions */
00190             appendStringInfoString(&xstr, PyString_AsString(e));
00191         else
00192             /* shouldn't happen */
00193             appendStringInfoString(&xstr, "unrecognized exception");
00194     }
00195     /* mimics behavior of traceback.format_exception_only */
00196     else if (strcmp(e_module_s, "builtins") == 0
00197              || strcmp(e_module_s, "__main__") == 0
00198              || strcmp(e_module_s, "exceptions") == 0)
00199         appendStringInfo(&xstr, "%s", e_type_s);
00200     else
00201         appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s);
00202     appendStringInfo(&xstr, ": %s", vstr);
00203 
00204     *xmsg = xstr.data;
00205 
00206     /*
00207      * Now format the traceback and put it in tbmsg.
00208      */
00209 
00210     *tb_depth = 0;
00211     initStringInfo(&tbstr);
00212     /* Mimick Python traceback reporting as close as possible. */
00213     appendStringInfoString(&tbstr, "Traceback (most recent call last):");
00214     while (tb != NULL && tb != Py_None)
00215     {
00216         PyObject   *volatile tb_prev = NULL;
00217         PyObject   *volatile frame = NULL;
00218         PyObject   *volatile code = NULL;
00219         PyObject   *volatile name = NULL;
00220         PyObject   *volatile lineno = NULL;
00221         PyObject   *volatile filename = NULL;
00222 
00223         PG_TRY();
00224         {
00225             lineno = PyObject_GetAttrString(tb, "tb_lineno");
00226             if (lineno == NULL)
00227                 elog(ERROR, "could not get line number from Python traceback");
00228 
00229             frame = PyObject_GetAttrString(tb, "tb_frame");
00230             if (frame == NULL)
00231                 elog(ERROR, "could not get frame from Python traceback");
00232 
00233             code = PyObject_GetAttrString(frame, "f_code");
00234             if (code == NULL)
00235                 elog(ERROR, "could not get code object from Python frame");
00236 
00237             name = PyObject_GetAttrString(code, "co_name");
00238             if (name == NULL)
00239                 elog(ERROR, "could not get function name from Python code object");
00240 
00241             filename = PyObject_GetAttrString(code, "co_filename");
00242             if (filename == NULL)
00243                 elog(ERROR, "could not get file name from Python code object");
00244         }
00245         PG_CATCH();
00246         {
00247             Py_XDECREF(frame);
00248             Py_XDECREF(code);
00249             Py_XDECREF(name);
00250             Py_XDECREF(lineno);
00251             Py_XDECREF(filename);
00252             PG_RE_THROW();
00253         }
00254         PG_END_TRY();
00255 
00256         /* The first frame always points at <module>, skip it. */
00257         if (*tb_depth > 0)
00258         {
00259             PLyExecutionContext *exec_ctx = PLy_current_execution_context();
00260             char       *proname;
00261             char       *fname;
00262             char       *line;
00263             char       *plain_filename;
00264             long        plain_lineno;
00265 
00266             /*
00267              * The second frame points at the internal function, but to mimick
00268              * Python error reporting we want to say <module>.
00269              */
00270             if (*tb_depth == 1)
00271                 fname = "<module>";
00272             else
00273                 fname = PyString_AsString(name);
00274 
00275             proname = PLy_procedure_name(exec_ctx->curr_proc);
00276             plain_filename = PyString_AsString(filename);
00277             plain_lineno = PyInt_AsLong(lineno);
00278 
00279             if (proname == NULL)
00280                 appendStringInfo(
00281                 &tbstr, "\n  PL/Python anonymous code block, line %ld, in %s",
00282                                  plain_lineno - 1, fname);
00283             else
00284                 appendStringInfo(
00285                     &tbstr, "\n  PL/Python function \"%s\", line %ld, in %s",
00286                                  proname, plain_lineno - 1, fname);
00287 
00288             /*
00289              * function code object was compiled with "<string>" as the
00290              * filename
00291              */
00292             if (exec_ctx->curr_proc && plain_filename != NULL &&
00293                 strcmp(plain_filename, "<string>") == 0)
00294             {
00295                 /*
00296                  * If we know the current procedure, append the exact line
00297                  * from the source, again mimicking Python's traceback.py
00298                  * module behavior.  We could store the already line-split
00299                  * source to avoid splitting it every time, but producing a
00300                  * traceback is not the most important scenario to optimize
00301                  * for.  But we do not go as far as traceback.py in reading
00302                  * the source of imported modules.
00303                  */
00304                 line = get_source_line(exec_ctx->curr_proc->src, plain_lineno);
00305                 if (line)
00306                 {
00307                     appendStringInfo(&tbstr, "\n    %s", line);
00308                     pfree(line);
00309                 }
00310             }
00311         }
00312 
00313         Py_DECREF(frame);
00314         Py_DECREF(code);
00315         Py_DECREF(name);
00316         Py_DECREF(lineno);
00317         Py_DECREF(filename);
00318 
00319         /* Release the current frame and go to the next one. */
00320         tb_prev = tb;
00321         tb = PyObject_GetAttrString(tb, "tb_next");
00322         Assert(tb_prev != Py_None);
00323         Py_DECREF(tb_prev);
00324         if (tb == NULL)
00325             elog(ERROR, "could not traverse Python traceback");
00326         (*tb_depth)++;
00327     }
00328 
00329     /* Return the traceback. */
00330     *tbmsg = tbstr.data;
00331 
00332     Py_XDECREF(e_type_o);
00333     Py_XDECREF(e_module_o);
00334     Py_XDECREF(vob);
00335     Py_XDECREF(v);
00336     Py_DECREF(e);
00337 }
00338 
00339 /*
00340  * Extract error code from SPIError's sqlstate attribute.
00341  */
00342 static void
00343 PLy_get_spi_sqlerrcode(PyObject *exc, int *sqlerrcode)
00344 {
00345     PyObject    *sqlstate;
00346     char        *buffer;
00347 
00348     sqlstate = PyObject_GetAttrString(exc, "sqlstate");
00349     if (sqlstate == NULL)
00350         return;
00351 
00352     buffer = PyString_AsString(sqlstate);
00353     if (strlen(buffer) == 5 &&
00354         strspn(buffer, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
00355     {
00356         *sqlerrcode = MAKE_SQLSTATE(buffer[0], buffer[1], buffer[2],
00357                                     buffer[3], buffer[4]);
00358     }
00359 
00360     Py_DECREF(sqlstate);
00361 }
00362 
00363 
00364 /*
00365  * Extract the error data from a SPIError
00366  */
00367 static void
00368 PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, char **query, int *position)
00369 {
00370     PyObject   *spidata = NULL;
00371 
00372     spidata = PyObject_GetAttrString(exc, "spidata");
00373 
00374     if (spidata != NULL)
00375     {
00376         PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position);
00377     }
00378     else
00379     {
00380         /*
00381          * If there's no spidata, at least set the sqlerrcode. This can happen
00382          * if someone explicitly raises a SPI exception from Python code.
00383          */
00384         PLy_get_spi_sqlerrcode(exc, sqlerrcode);
00385     }
00386 
00387     PyErr_Clear();
00388     /* no elog here, we simply won't report the errhint, errposition etc */
00389     Py_XDECREF(spidata);
00390 }
00391 
00392 /*
00393  * Get the given source line as a palloc'd string
00394  */
00395 static char *
00396 get_source_line(const char *src, int lineno)
00397 {
00398     const char *s = NULL;
00399     const char *next = src;
00400     int         current = 0;
00401 
00402     /* sanity check */
00403     if (lineno <= 0)
00404         return NULL;
00405 
00406     while (current < lineno)
00407     {
00408         s = next;
00409         next = strchr(s + 1, '\n');
00410         current++;
00411         if (next == NULL)
00412             break;
00413     }
00414 
00415     if (current != lineno)
00416         return NULL;
00417 
00418     while (*s && isspace((unsigned char) *s))
00419         s++;
00420 
00421     if (next == NULL)
00422         return pstrdup(s);
00423 
00424     /*
00425      * Sanity check, next < s if the line was all-whitespace, which should
00426      * never happen if Python reported a frame created on that line, but check
00427      * anyway.
00428      */
00429     if (next < s)
00430         return NULL;
00431 
00432     return pnstrdup(s, next - s);
00433 }
00434 
00435 
00436 /* call PyErr_SetString with a vprint interface and translation support */
00437 void
00438 PLy_exception_set(PyObject *exc, const char *fmt,...)
00439 {
00440     char        buf[1024];
00441     va_list     ap;
00442 
00443     va_start(ap, fmt);
00444     vsnprintf(buf, sizeof(buf), dgettext(TEXTDOMAIN, fmt), ap);
00445     va_end(ap);
00446 
00447     PyErr_SetString(exc, buf);
00448 }
00449 
00450 /* same, with pluralized message */
00451 void
00452 PLy_exception_set_plural(PyObject *exc,
00453                          const char *fmt_singular, const char *fmt_plural,
00454                          unsigned long n,...)
00455 {
00456     char        buf[1024];
00457     va_list     ap;
00458 
00459     va_start(ap, n);
00460     vsnprintf(buf, sizeof(buf),
00461               dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n),
00462               ap);
00463     va_end(ap);
00464 
00465     PyErr_SetString(exc, buf);
00466 }