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