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