Line data Source code
1 : /*
2 : * reporting Python exceptions as PostgreSQL errors
3 : *
4 : * src/pl/plpython/plpy_elog.c
5 : */
6 :
7 : #include "postgres.h"
8 :
9 : #include "lib/stringinfo.h"
10 : #include "plpy_elog.h"
11 : #include "plpy_main.h"
12 : #include "plpy_procedure.h"
13 : #include "plpython.h"
14 :
15 : PyObject *PLy_exc_error = NULL;
16 : PyObject *PLy_exc_fatal = NULL;
17 : PyObject *PLy_exc_spi_error = NULL;
18 :
19 :
20 : static void PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
21 : char **xmsg, char **tbmsg, int *tb_depth);
22 : static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
23 : char **hint, char **query, int *position,
24 : char **schema_name, char **table_name, char **column_name,
25 : char **datatype_name, char **constraint_name);
26 : static void PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail,
27 : char **hint, char **schema_name, char **table_name, char **column_name,
28 : char **datatype_name, char **constraint_name);
29 : static char *get_source_line(const char *src, int lineno);
30 :
31 : static void get_string_attr(PyObject *obj, char *attrname, char **str);
32 : static bool set_string_attr(PyObject *obj, char *attrname, char *str);
33 :
34 : /*
35 : * Emit a PG error or notice, together with any available info about
36 : * the current Python error, previously set by PLy_exception_set().
37 : * This should be used to propagate Python errors into PG. If fmt is
38 : * NULL, the Python error becomes the primary error message, otherwise
39 : * it becomes the detail. If there is a Python traceback, it is put
40 : * in the context.
41 : */
42 : void
43 116 : PLy_elog_impl(int elevel, const char *fmt,...)
44 : {
45 116 : int save_errno = errno;
46 : char *xmsg;
47 : char *tbmsg;
48 : int tb_depth;
49 : StringInfoData emsg;
50 : PyObject *exc,
51 : *val,
52 : *tb;
53 116 : const char *primary = NULL;
54 116 : int sqlerrcode = 0;
55 116 : char *detail = NULL;
56 116 : char *hint = NULL;
57 116 : char *query = NULL;
58 116 : int position = 0;
59 116 : char *schema_name = NULL;
60 116 : char *table_name = NULL;
61 116 : char *column_name = NULL;
62 116 : char *datatype_name = NULL;
63 116 : char *constraint_name = NULL;
64 :
65 116 : PyErr_Fetch(&exc, &val, &tb);
66 :
67 116 : if (exc != NULL)
68 : {
69 116 : PyErr_NormalizeException(&exc, &val, &tb);
70 :
71 116 : if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
72 46 : PLy_get_spi_error_data(val, &sqlerrcode,
73 : &detail, &hint, &query, &position,
74 : &schema_name, &table_name, &column_name,
75 : &datatype_name, &constraint_name);
76 70 : else if (PyErr_GivenExceptionMatches(val, PLy_exc_error))
77 26 : PLy_get_error_data(val, &sqlerrcode, &detail, &hint,
78 : &schema_name, &table_name, &column_name,
79 : &datatype_name, &constraint_name);
80 44 : else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
81 0 : elevel = FATAL;
82 : }
83 :
84 : /* this releases our refcount on tb! */
85 116 : PLy_traceback(exc, val, tb,
86 : &xmsg, &tbmsg, &tb_depth);
87 :
88 116 : if (fmt)
89 : {
90 8 : initStringInfo(&emsg);
91 : for (;;)
92 0 : {
93 : va_list ap;
94 : int needed;
95 :
96 8 : errno = save_errno;
97 8 : va_start(ap, fmt);
98 8 : needed = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap);
99 8 : va_end(ap);
100 8 : if (needed == 0)
101 8 : break;
102 0 : enlargeStringInfo(&emsg, needed);
103 : }
104 8 : primary = emsg.data;
105 :
106 : /* If there's an exception message, it goes in the detail. */
107 8 : if (xmsg)
108 8 : detail = xmsg;
109 : }
110 : else
111 : {
112 108 : if (xmsg)
113 108 : primary = xmsg;
114 : }
115 :
116 116 : PG_TRY();
117 : {
118 116 : ereport(elevel,
119 : (errcode(sqlerrcode ? sqlerrcode : ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
120 : errmsg_internal("%s", primary ? primary : "no exception data"),
121 : (detail) ? errdetail_internal("%s", detail) : 0,
122 : (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0,
123 : (hint) ? errhint("%s", hint) : 0,
124 : (query) ? internalerrquery(query) : 0,
125 : (position) ? internalerrposition(position) : 0,
126 : (schema_name) ? err_generic_string(PG_DIAG_SCHEMA_NAME,
127 : schema_name) : 0,
128 : (table_name) ? err_generic_string(PG_DIAG_TABLE_NAME,
129 : table_name) : 0,
130 : (column_name) ? err_generic_string(PG_DIAG_COLUMN_NAME,
131 : column_name) : 0,
132 : (datatype_name) ? err_generic_string(PG_DIAG_DATATYPE_NAME,
133 : datatype_name) : 0,
134 : (constraint_name) ? err_generic_string(PG_DIAG_CONSTRAINT_NAME,
135 : constraint_name) : 0));
136 : }
137 116 : PG_FINALLY();
138 : {
139 116 : if (fmt)
140 8 : pfree(emsg.data);
141 116 : if (xmsg)
142 116 : pfree(xmsg);
143 116 : if (tbmsg)
144 116 : pfree(tbmsg);
145 116 : Py_XDECREF(exc);
146 116 : Py_XDECREF(val);
147 : }
148 116 : PG_END_TRY();
149 0 : }
150 :
151 : /*
152 : * Extract a Python traceback from the given exception data.
153 : *
154 : * The exception error message is returned in xmsg, the traceback in
155 : * tbmsg (both as palloc'd strings) and the traceback depth in
156 : * tb_depth.
157 : *
158 : * We release refcounts on all the Python objects in the traceback stack,
159 : * but not on e or v.
160 : */
161 : static void
162 116 : PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
163 : char **xmsg, char **tbmsg, int *tb_depth)
164 : {
165 : PyObject *e_type_o;
166 : PyObject *e_module_o;
167 116 : char *e_type_s = NULL;
168 116 : char *e_module_s = NULL;
169 116 : PyObject *vob = NULL;
170 : char *vstr;
171 : StringInfoData xstr;
172 : StringInfoData tbstr;
173 :
174 : /*
175 : * if no exception, return nulls
176 : */
177 116 : if (e == NULL)
178 : {
179 0 : *xmsg = NULL;
180 0 : *tbmsg = NULL;
181 0 : *tb_depth = 0;
182 :
183 0 : return;
184 : }
185 :
186 : /*
187 : * Format the exception and its value and put it in xmsg.
188 : */
189 :
190 116 : e_type_o = PyObject_GetAttrString(e, "__name__");
191 116 : e_module_o = PyObject_GetAttrString(e, "__module__");
192 116 : if (e_type_o)
193 116 : e_type_s = PLyUnicode_AsString(e_type_o);
194 116 : if (e_type_s)
195 116 : e_module_s = PLyUnicode_AsString(e_module_o);
196 :
197 116 : if (v && ((vob = PyObject_Str(v)) != NULL))
198 116 : vstr = PLyUnicode_AsString(vob);
199 : else
200 0 : vstr = "unknown";
201 :
202 116 : initStringInfo(&xstr);
203 116 : if (!e_type_s || !e_module_s)
204 : {
205 : /* shouldn't happen */
206 0 : appendStringInfoString(&xstr, "unrecognized exception");
207 : }
208 : /* mimics behavior of traceback.format_exception_only */
209 116 : else if (strcmp(e_module_s, "builtins") == 0
210 72 : || strcmp(e_module_s, "__main__") == 0
211 72 : || strcmp(e_module_s, "exceptions") == 0)
212 44 : appendStringInfoString(&xstr, e_type_s);
213 : else
214 72 : appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s);
215 116 : appendStringInfo(&xstr, ": %s", vstr);
216 :
217 116 : *xmsg = xstr.data;
218 :
219 : /*
220 : * Now format the traceback and put it in tbmsg.
221 : */
222 :
223 116 : *tb_depth = 0;
224 116 : initStringInfo(&tbstr);
225 : /* Mimic Python traceback reporting as close as possible. */
226 116 : appendStringInfoString(&tbstr, "Traceback (most recent call last):");
227 362 : while (tb != NULL && tb != Py_None)
228 : {
229 246 : PyObject *volatile tb_prev = NULL;
230 246 : PyObject *volatile frame = NULL;
231 246 : PyObject *volatile code = NULL;
232 246 : PyObject *volatile name = NULL;
233 246 : PyObject *volatile lineno = NULL;
234 246 : PyObject *volatile filename = NULL;
235 :
236 246 : PG_TRY();
237 : {
238 246 : lineno = PyObject_GetAttrString(tb, "tb_lineno");
239 246 : if (lineno == NULL)
240 0 : elog(ERROR, "could not get line number from Python traceback");
241 :
242 246 : frame = PyObject_GetAttrString(tb, "tb_frame");
243 246 : if (frame == NULL)
244 0 : elog(ERROR, "could not get frame from Python traceback");
245 :
246 246 : code = PyObject_GetAttrString(frame, "f_code");
247 246 : if (code == NULL)
248 0 : elog(ERROR, "could not get code object from Python frame");
249 :
250 246 : name = PyObject_GetAttrString(code, "co_name");
251 246 : if (name == NULL)
252 0 : elog(ERROR, "could not get function name from Python code object");
253 :
254 246 : filename = PyObject_GetAttrString(code, "co_filename");
255 246 : if (filename == NULL)
256 0 : elog(ERROR, "could not get file name from Python code object");
257 : }
258 0 : PG_CATCH();
259 : {
260 0 : Py_XDECREF(frame);
261 0 : Py_XDECREF(code);
262 0 : Py_XDECREF(name);
263 0 : Py_XDECREF(lineno);
264 0 : Py_XDECREF(filename);
265 0 : PG_RE_THROW();
266 : }
267 246 : PG_END_TRY();
268 :
269 : /* The first frame always points at <module>, skip it. */
270 246 : if (*tb_depth > 0)
271 : {
272 136 : PLyExecutionContext *exec_ctx = PLy_current_execution_context();
273 : char *proname;
274 : char *fname;
275 : char *line;
276 : char *plain_filename;
277 : long plain_lineno;
278 :
279 : /*
280 : * The second frame points at the internal function, but to mimic
281 : * Python error reporting we want to say <module>.
282 : */
283 136 : if (*tb_depth == 1)
284 108 : fname = "<module>";
285 : else
286 28 : fname = PLyUnicode_AsString(name);
287 :
288 136 : proname = PLy_procedure_name(exec_ctx->curr_proc);
289 136 : plain_filename = PLyUnicode_AsString(filename);
290 136 : plain_lineno = PyLong_AsLong(lineno);
291 :
292 136 : if (proname == NULL)
293 26 : appendStringInfo(&tbstr, "\n PL/Python anonymous code block, line %ld, in %s",
294 : plain_lineno - 1, fname);
295 : else
296 110 : appendStringInfo(&tbstr, "\n PL/Python function \"%s\", line %ld, in %s",
297 : proname, plain_lineno - 1, fname);
298 :
299 : /*
300 : * function code object was compiled with "<string>" as the
301 : * filename
302 : */
303 136 : if (exec_ctx->curr_proc && plain_filename != NULL &&
304 136 : strcmp(plain_filename, "<string>") == 0)
305 : {
306 : /*
307 : * If we know the current procedure, append the exact line
308 : * from the source, again mimicking Python's traceback.py
309 : * module behavior. We could store the already line-split
310 : * source to avoid splitting it every time, but producing a
311 : * traceback is not the most important scenario to optimize
312 : * for. But we do not go as far as traceback.py in reading
313 : * the source of imported modules.
314 : */
315 136 : line = get_source_line(exec_ctx->curr_proc->src, plain_lineno);
316 136 : if (line)
317 : {
318 136 : appendStringInfo(&tbstr, "\n %s", line);
319 136 : pfree(line);
320 : }
321 : }
322 : }
323 :
324 246 : Py_DECREF(frame);
325 246 : Py_DECREF(code);
326 246 : Py_DECREF(name);
327 246 : Py_DECREF(lineno);
328 246 : Py_DECREF(filename);
329 :
330 : /* Release the current frame and go to the next one. */
331 246 : tb_prev = tb;
332 246 : tb = PyObject_GetAttrString(tb, "tb_next");
333 : Assert(tb_prev != Py_None);
334 246 : Py_DECREF(tb_prev);
335 246 : if (tb == NULL)
336 0 : elog(ERROR, "could not traverse Python traceback");
337 246 : (*tb_depth)++;
338 : }
339 :
340 : /* Return the traceback. */
341 116 : *tbmsg = tbstr.data;
342 :
343 116 : Py_XDECREF(e_type_o);
344 116 : Py_XDECREF(e_module_o);
345 116 : Py_XDECREF(vob);
346 : }
347 :
348 : /*
349 : * Extract error code from SPIError's sqlstate attribute.
350 : */
351 : static void
352 34 : PLy_get_sqlerrcode(PyObject *exc, int *sqlerrcode)
353 : {
354 : PyObject *sqlstate;
355 : char *buffer;
356 :
357 34 : sqlstate = PyObject_GetAttrString(exc, "sqlstate");
358 34 : if (sqlstate == NULL)
359 8 : return;
360 :
361 26 : buffer = PLyUnicode_AsString(sqlstate);
362 26 : if (strlen(buffer) == 5 &&
363 26 : strspn(buffer, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
364 : {
365 26 : *sqlerrcode = MAKE_SQLSTATE(buffer[0], buffer[1], buffer[2],
366 : buffer[3], buffer[4]);
367 : }
368 :
369 26 : Py_DECREF(sqlstate);
370 : }
371 :
372 : /*
373 : * Extract the error data from a SPIError
374 : */
375 : static void
376 46 : PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
377 : char **hint, char **query, int *position,
378 : char **schema_name, char **table_name,
379 : char **column_name,
380 : char **datatype_name, char **constraint_name)
381 : {
382 : PyObject *spidata;
383 :
384 46 : spidata = PyObject_GetAttrString(exc, "spidata");
385 :
386 46 : if (spidata != NULL)
387 : {
388 38 : PyArg_ParseTuple(spidata, "izzzizzzzz",
389 : sqlerrcode, detail, hint, query, position,
390 : schema_name, table_name, column_name,
391 : datatype_name, constraint_name);
392 : }
393 : else
394 : {
395 : /*
396 : * If there's no spidata, at least set the sqlerrcode. This can happen
397 : * if someone explicitly raises a SPI exception from Python code.
398 : */
399 8 : PLy_get_sqlerrcode(exc, sqlerrcode);
400 : }
401 :
402 46 : Py_XDECREF(spidata);
403 46 : }
404 :
405 : /*
406 : * Extract the error data from an Error.
407 : *
408 : * Note: position and query attributes are never set for Error so, unlike
409 : * PLy_get_spi_error_data, this function doesn't return them.
410 : */
411 : static void
412 26 : PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint,
413 : char **schema_name, char **table_name, char **column_name,
414 : char **datatype_name, char **constraint_name)
415 : {
416 26 : PLy_get_sqlerrcode(exc, sqlerrcode);
417 26 : get_string_attr(exc, "detail", detail);
418 26 : get_string_attr(exc, "hint", hint);
419 26 : get_string_attr(exc, "schema_name", schema_name);
420 26 : get_string_attr(exc, "table_name", table_name);
421 26 : get_string_attr(exc, "column_name", column_name);
422 26 : get_string_attr(exc, "datatype_name", datatype_name);
423 26 : get_string_attr(exc, "constraint_name", constraint_name);
424 26 : }
425 :
426 : /*
427 : * Get the given source line as a palloc'd string
428 : */
429 : static char *
430 136 : get_source_line(const char *src, int lineno)
431 : {
432 136 : const char *s = NULL;
433 136 : const char *next = src;
434 136 : int current = 0;
435 :
436 : /* sanity check */
437 136 : if (lineno <= 0)
438 0 : return NULL;
439 :
440 976 : while (current < lineno)
441 : {
442 840 : s = next;
443 840 : next = strchr(s + 1, '\n');
444 840 : current++;
445 840 : if (next == NULL)
446 0 : break;
447 : }
448 :
449 136 : if (current != lineno)
450 0 : return NULL;
451 :
452 636 : while (*s && isspace((unsigned char) *s))
453 500 : s++;
454 :
455 136 : if (next == NULL)
456 0 : return pstrdup(s);
457 :
458 : /*
459 : * Sanity check, next < s if the line was all-whitespace, which should
460 : * never happen if Python reported a frame created on that line, but check
461 : * anyway.
462 : */
463 136 : if (next < s)
464 0 : return NULL;
465 :
466 136 : return pnstrdup(s, next - s);
467 : }
468 :
469 :
470 : /* call PyErr_SetString with a vprint interface and translation support */
471 : void
472 36 : PLy_exception_set(PyObject *exc, const char *fmt,...)
473 : {
474 : char buf[1024];
475 : va_list ap;
476 :
477 36 : va_start(ap, fmt);
478 36 : vsnprintf(buf, sizeof(buf), dgettext(TEXTDOMAIN, fmt), ap);
479 36 : va_end(ap);
480 :
481 36 : PyErr_SetString(exc, buf);
482 36 : }
483 :
484 : /* same, with pluralized message */
485 : void
486 2 : PLy_exception_set_plural(PyObject *exc,
487 : const char *fmt_singular, const char *fmt_plural,
488 : unsigned long n,...)
489 : {
490 : char buf[1024];
491 : va_list ap;
492 :
493 2 : va_start(ap, n);
494 2 : vsnprintf(buf, sizeof(buf),
495 2 : dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n),
496 : ap);
497 2 : va_end(ap);
498 :
499 2 : PyErr_SetString(exc, buf);
500 2 : }
501 :
502 : /* set attributes of the given exception to details from ErrorData */
503 : void
504 22 : PLy_exception_set_with_details(PyObject *excclass, ErrorData *edata)
505 : {
506 22 : PyObject *args = NULL;
507 22 : PyObject *error = NULL;
508 :
509 22 : args = Py_BuildValue("(s)", edata->message);
510 22 : if (!args)
511 0 : goto failure;
512 :
513 : /* create a new exception with the error message as the parameter */
514 22 : error = PyObject_CallObject(excclass, args);
515 22 : if (!error)
516 0 : goto failure;
517 :
518 22 : if (!set_string_attr(error, "sqlstate",
519 : unpack_sql_state(edata->sqlerrcode)))
520 0 : goto failure;
521 :
522 22 : if (!set_string_attr(error, "detail", edata->detail))
523 0 : goto failure;
524 :
525 22 : if (!set_string_attr(error, "hint", edata->hint))
526 0 : goto failure;
527 :
528 22 : if (!set_string_attr(error, "query", edata->internalquery))
529 0 : goto failure;
530 :
531 22 : if (!set_string_attr(error, "schema_name", edata->schema_name))
532 0 : goto failure;
533 :
534 22 : if (!set_string_attr(error, "table_name", edata->table_name))
535 0 : goto failure;
536 :
537 22 : if (!set_string_attr(error, "column_name", edata->column_name))
538 0 : goto failure;
539 :
540 22 : if (!set_string_attr(error, "datatype_name", edata->datatype_name))
541 0 : goto failure;
542 :
543 22 : if (!set_string_attr(error, "constraint_name", edata->constraint_name))
544 0 : goto failure;
545 :
546 22 : PyErr_SetObject(excclass, error);
547 :
548 22 : Py_DECREF(args);
549 22 : Py_DECREF(error);
550 :
551 22 : return;
552 :
553 0 : failure:
554 0 : Py_XDECREF(args);
555 0 : Py_XDECREF(error);
556 :
557 0 : elog(ERROR, "could not convert error to Python exception");
558 : }
559 :
560 : /* get string value of an object attribute */
561 : static void
562 182 : get_string_attr(PyObject *obj, char *attrname, char **str)
563 : {
564 : PyObject *val;
565 :
566 182 : val = PyObject_GetAttrString(obj, attrname);
567 182 : if (val != NULL && val != Py_None)
568 : {
569 56 : *str = pstrdup(PLyUnicode_AsString(val));
570 : }
571 182 : Py_XDECREF(val);
572 182 : }
573 :
574 : /* set an object attribute to a string value, returns true when the set was
575 : * successful
576 : */
577 : static bool
578 198 : set_string_attr(PyObject *obj, char *attrname, char *str)
579 : {
580 : int result;
581 : PyObject *val;
582 :
583 198 : if (str != NULL)
584 : {
585 78 : val = PLyUnicode_FromString(str);
586 78 : if (!val)
587 0 : return false;
588 : }
589 : else
590 : {
591 120 : val = Py_None;
592 120 : Py_INCREF(Py_None);
593 : }
594 :
595 198 : result = PyObject_SetAttrString(obj, attrname, val);
596 198 : Py_DECREF(val);
597 :
598 198 : return result != -1;
599 : }
|