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