PostgreSQL Source Code git master
plpy_elog.c
Go to the documentation of this file.
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
15PyObject *PLy_exc_error = NULL;
16PyObject *PLy_exc_fatal = NULL;
17PyObject *PLy_exc_spi_error = NULL;
18
19
20static void PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
21 char **xmsg, char **tbmsg, int *tb_depth);
22static 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);
26static 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);
29static char *get_source_line(const char *src, int lineno);
30
31static void get_string_attr(PyObject *obj, char *attrname, char **str);
32static 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 */
42void
43PLy_elog_impl(int elevel, const char *fmt,...)
44{
45 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 const char *primary = NULL;
54 int sqlerrcode = 0;
55 char *detail = NULL;
56 char *hint = NULL;
57 char *query = NULL;
58 int position = 0;
59 char *schema_name = NULL;
60 char *table_name = NULL;
61 char *column_name = NULL;
62 char *datatype_name = NULL;
63 char *constraint_name = NULL;
64
65 PyErr_Fetch(&exc, &val, &tb);
66
67 if (exc != NULL)
68 {
69 PyErr_NormalizeException(&exc, &val, &tb);
70
71 if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
72 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 else if (PyErr_GivenExceptionMatches(val, PLy_exc_error))
77 PLy_get_error_data(val, &sqlerrcode, &detail, &hint,
78 &schema_name, &table_name, &column_name,
79 &datatype_name, &constraint_name);
80 else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
81 elevel = FATAL;
82 }
83
84 /* this releases our refcount on tb! */
85 PLy_traceback(exc, val, tb,
86 &xmsg, &tbmsg, &tb_depth);
87
88 if (fmt)
89 {
90 initStringInfo(&emsg);
91 for (;;)
92 {
93 va_list ap;
94 int needed;
95
96 errno = save_errno;
97 va_start(ap, fmt);
98 needed = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap);
99 va_end(ap);
100 if (needed == 0)
101 break;
102 enlargeStringInfo(&emsg, needed);
103 }
104 primary = emsg.data;
105
106 /* If there's an exception message, it goes in the detail. */
107 if (xmsg)
108 detail = xmsg;
109 }
110 else
111 {
112 if (xmsg)
113 primary = xmsg;
114 }
115
116 PG_TRY();
117 {
118 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,
127 schema_name) : 0,
129 table_name) : 0,
131 column_name) : 0,
133 datatype_name) : 0,
135 constraint_name) : 0));
136 }
137 PG_FINALLY();
138 {
139 if (fmt)
140 pfree(emsg.data);
141 if (xmsg)
142 pfree(xmsg);
143 if (tbmsg)
144 pfree(tbmsg);
145 Py_XDECREF(exc);
146 Py_XDECREF(val);
147 }
148 PG_END_TRY();
149}
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 */
161static void
162PLy_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 char *e_type_s = NULL;
168 char *e_module_s = NULL;
169 PyObject *vob = NULL;
170 char *vstr;
171 StringInfoData xstr;
172 StringInfoData tbstr;
173
174 /*
175 * if no exception, return nulls
176 */
177 if (e == NULL)
178 {
179 *xmsg = NULL;
180 *tbmsg = NULL;
181 *tb_depth = 0;
182
183 return;
184 }
185
186 /*
187 * Format the exception and its value and put it in xmsg.
188 */
189
190 e_type_o = PyObject_GetAttrString(e, "__name__");
191 e_module_o = PyObject_GetAttrString(e, "__module__");
192 if (e_type_o)
193 e_type_s = PLyUnicode_AsString(e_type_o);
194 if (e_type_s)
195 e_module_s = PLyUnicode_AsString(e_module_o);
196
197 if (v && ((vob = PyObject_Str(v)) != NULL))
198 vstr = PLyUnicode_AsString(vob);
199 else
200 vstr = "unknown";
201
202 initStringInfo(&xstr);
203 if (!e_type_s || !e_module_s)
204 {
205 /* shouldn't happen */
206 appendStringInfoString(&xstr, "unrecognized exception");
207 }
208 /* mimics behavior of traceback.format_exception_only */
209 else if (strcmp(e_module_s, "builtins") == 0
210 || strcmp(e_module_s, "__main__") == 0
211 || strcmp(e_module_s, "exceptions") == 0)
212 appendStringInfoString(&xstr, e_type_s);
213 else
214 appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s);
215 appendStringInfo(&xstr, ": %s", vstr);
216
217 *xmsg = xstr.data;
218
219 /*
220 * Now format the traceback and put it in tbmsg.
221 */
222
223 *tb_depth = 0;
224 initStringInfo(&tbstr);
225 /* Mimic Python traceback reporting as close as possible. */
226 appendStringInfoString(&tbstr, "Traceback (most recent call last):");
227 while (tb != NULL && tb != Py_None)
228 {
229 PyObject *volatile tb_prev = NULL;
230 PyObject *volatile frame = NULL;
231 PyObject *volatile code = NULL;
232 PyObject *volatile name = NULL;
233 PyObject *volatile lineno = NULL;
234 PyObject *volatile filename = NULL;
235
236 PG_TRY();
237 {
238 lineno = PyObject_GetAttrString(tb, "tb_lineno");
239 if (lineno == NULL)
240 elog(ERROR, "could not get line number from Python traceback");
241
242 frame = PyObject_GetAttrString(tb, "tb_frame");
243 if (frame == NULL)
244 elog(ERROR, "could not get frame from Python traceback");
245
246 code = PyObject_GetAttrString(frame, "f_code");
247 if (code == NULL)
248 elog(ERROR, "could not get code object from Python frame");
249
250 name = PyObject_GetAttrString(code, "co_name");
251 if (name == NULL)
252 elog(ERROR, "could not get function name from Python code object");
253
254 filename = PyObject_GetAttrString(code, "co_filename");
255 if (filename == NULL)
256 elog(ERROR, "could not get file name from Python code object");
257 }
258 PG_CATCH();
259 {
260 Py_XDECREF(frame);
261 Py_XDECREF(code);
262 Py_XDECREF(name);
263 Py_XDECREF(lineno);
264 Py_XDECREF(filename);
265 PG_RE_THROW();
266 }
267 PG_END_TRY();
268
269 /* The first frame always points at <module>, skip it. */
270 if (*tb_depth > 0)
271 {
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 if (*tb_depth == 1)
284 fname = "<module>";
285 else
286 fname = PLyUnicode_AsString(name);
287
289 plain_filename = PLyUnicode_AsString(filename);
290 plain_lineno = PyLong_AsLong(lineno);
291
292 if (proname == NULL)
293 appendStringInfo(&tbstr, "\n PL/Python anonymous code block, line %ld, in %s",
294 plain_lineno - 1, fname);
295 else
296 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 if (exec_ctx->curr_proc && plain_filename != NULL &&
304 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 line = get_source_line(exec_ctx->curr_proc->src, plain_lineno);
316 if (line)
317 {
318 appendStringInfo(&tbstr, "\n %s", line);
319 pfree(line);
320 }
321 }
322 }
323
324 Py_DECREF(frame);
325 Py_DECREF(code);
326 Py_DECREF(name);
327 Py_DECREF(lineno);
328 Py_DECREF(filename);
329
330 /* Release the current frame and go to the next one. */
331 tb_prev = tb;
332 tb = PyObject_GetAttrString(tb, "tb_next");
333 Assert(tb_prev != Py_None);
334 Py_DECREF(tb_prev);
335 if (tb == NULL)
336 elog(ERROR, "could not traverse Python traceback");
337 (*tb_depth)++;
338 }
339
340 /* Return the traceback. */
341 *tbmsg = tbstr.data;
342
343 Py_XDECREF(e_type_o);
344 Py_XDECREF(e_module_o);
345 Py_XDECREF(vob);
346}
347
348/*
349 * Extract error code from SPIError's sqlstate attribute.
350 */
351static void
352PLy_get_sqlerrcode(PyObject *exc, int *sqlerrcode)
353{
354 PyObject *sqlstate;
355 char *buffer;
356
357 sqlstate = PyObject_GetAttrString(exc, "sqlstate");
358 if (sqlstate == NULL)
359 return;
360
361 buffer = PLyUnicode_AsString(sqlstate);
362 if (strlen(buffer) == 5 &&
363 strspn(buffer, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
364 {
365 *sqlerrcode = MAKE_SQLSTATE(buffer[0], buffer[1], buffer[2],
366 buffer[3], buffer[4]);
367 }
368
369 Py_DECREF(sqlstate);
370}
371
372/*
373 * Extract the error data from a SPIError
374 */
375static void
376PLy_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 spidata = PyObject_GetAttrString(exc, "spidata");
385
386 if (spidata != NULL)
387 {
388 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 PLy_get_sqlerrcode(exc, sqlerrcode);
400 }
401
402 Py_XDECREF(spidata);
403}
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 */
411static void
412PLy_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 PLy_get_sqlerrcode(exc, sqlerrcode);
417 get_string_attr(exc, "detail", detail);
418 get_string_attr(exc, "hint", hint);
419 get_string_attr(exc, "schema_name", schema_name);
420 get_string_attr(exc, "table_name", table_name);
421 get_string_attr(exc, "column_name", column_name);
422 get_string_attr(exc, "datatype_name", datatype_name);
423 get_string_attr(exc, "constraint_name", constraint_name);
424}
425
426/*
427 * Get the given source line as a palloc'd string
428 */
429static char *
430get_source_line(const char *src, int lineno)
431{
432 const char *s = NULL;
433 const char *next = src;
434 int current = 0;
435
436 /* sanity check */
437 if (lineno <= 0)
438 return NULL;
439
440 while (current < lineno)
441 {
442 s = next;
443 next = strchr(s + 1, '\n');
444 current++;
445 if (next == NULL)
446 break;
447 }
448
449 if (current != lineno)
450 return NULL;
451
452 while (*s && isspace((unsigned char) *s))
453 s++;
454
455 if (next == NULL)
456 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 if (next < s)
464 return NULL;
465
466 return pnstrdup(s, next - s);
467}
468
469
470/* call PyErr_SetString with a vprint interface and translation support */
471void
472PLy_exception_set(PyObject *exc, const char *fmt,...)
473{
474 char buf[1024];
475 va_list ap;
476
477 va_start(ap, fmt);
478 vsnprintf(buf, sizeof(buf), dgettext(TEXTDOMAIN, fmt), ap);
479 va_end(ap);
480
481 PyErr_SetString(exc, buf);
482}
483
484/* same, with pluralized message */
485void
487 const char *fmt_singular, const char *fmt_plural,
488 unsigned long n,...)
489{
490 char buf[1024];
491 va_list ap;
492
493 va_start(ap, n);
494 vsnprintf(buf, sizeof(buf),
495 dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n),
496 ap);
497 va_end(ap);
498
499 PyErr_SetString(exc, buf);
500}
501
502/* set attributes of the given exception to details from ErrorData */
503void
504PLy_exception_set_with_details(PyObject *excclass, ErrorData *edata)
505{
506 PyObject *args = NULL;
507 PyObject *error = NULL;
508
509 args = Py_BuildValue("(s)", edata->message);
510 if (!args)
511 goto failure;
512
513 /* create a new exception with the error message as the parameter */
514 error = PyObject_CallObject(excclass, args);
515 if (!error)
516 goto failure;
517
518 if (!set_string_attr(error, "sqlstate",
520 goto failure;
521
522 if (!set_string_attr(error, "detail", edata->detail))
523 goto failure;
524
525 if (!set_string_attr(error, "hint", edata->hint))
526 goto failure;
527
528 if (!set_string_attr(error, "query", edata->internalquery))
529 goto failure;
530
531 if (!set_string_attr(error, "schema_name", edata->schema_name))
532 goto failure;
533
534 if (!set_string_attr(error, "table_name", edata->table_name))
535 goto failure;
536
537 if (!set_string_attr(error, "column_name", edata->column_name))
538 goto failure;
539
540 if (!set_string_attr(error, "datatype_name", edata->datatype_name))
541 goto failure;
542
543 if (!set_string_attr(error, "constraint_name", edata->constraint_name))
544 goto failure;
545
546 PyErr_SetObject(excclass, error);
547
548 Py_DECREF(args);
549 Py_DECREF(error);
550
551 return;
552
553failure:
554 Py_XDECREF(args);
555 Py_XDECREF(error);
556
557 elog(ERROR, "could not convert error to Python exception");
558}
559
560/* get string value of an object attribute */
561static void
562get_string_attr(PyObject *obj, char *attrname, char **str)
563{
564 PyObject *val;
565
566 val = PyObject_GetAttrString(obj, attrname);
567 if (val != NULL && val != Py_None)
568 {
570 }
571 Py_XDECREF(val);
572}
573
574/* set an object attribute to a string value, returns true when the set was
575 * successful
576 */
577static bool
578set_string_attr(PyObject *obj, char *attrname, char *str)
579{
580 int result;
581 PyObject *val;
582
583 if (str != NULL)
584 {
586 if (!val)
587 return false;
588 }
589 else
590 {
591 val = Py_None;
592 Py_INCREF(Py_None);
593 }
594
595 result = PyObject_SetAttrString(obj, attrname, val);
596 Py_DECREF(val);
597
598 return result != -1;
599}
static int32 next
Definition: blutils.c:221
#define Assert(condition)
Definition: c.h:815
#define dngettext(d, s, p, n)
Definition: c.h:1139
#define dgettext(d, x)
Definition: c.h:1137
int err_generic_string(int field, const char *str)
Definition: elog.c:1512
int internalerrquery(const char *query)
Definition: elog.c:1482
int internalerrposition(int cursorpos)
Definition: elog.c:1462
int errmsg_internal(const char *fmt,...)
Definition: elog.c:1157
int errdetail_internal(const char *fmt,...)
Definition: elog.c:1230
int errhint(const char *fmt,...)
Definition: elog.c:1317
int errcode(int sqlerrcode)
Definition: elog.c:853
char * unpack_sql_state(int sql_state)
Definition: elog.c:3169
#define PG_RE_THROW()
Definition: elog.h:412
#define errcontext
Definition: elog.h:196
#define FATAL
Definition: elog.h:41
#define PG_TRY(...)
Definition: elog.h:371
#define PG_END_TRY(...)
Definition: elog.h:396
#define ERROR
Definition: elog.h:39
#define PG_CATCH(...)
Definition: elog.h:381
#define MAKE_SQLSTATE(ch1, ch2, ch3, ch4, ch5)
Definition: elog.h:56
#define TEXTDOMAIN
Definition: elog.h:152
#define elog(elevel,...)
Definition: elog.h:225
#define PG_FINALLY(...)
Definition: elog.h:388
#define ereport(elevel,...)
Definition: elog.h:149
const char * str
long val
Definition: informix.c:689
static void const char * fmt
va_end(args)
va_start(args, fmt)
char * pstrdup(const char *in)
Definition: mcxt.c:1696
void pfree(void *pointer)
Definition: mcxt.c:1521
char * pnstrdup(const char *in, Size len)
Definition: mcxt.c:1707
static char * filename
Definition: pg_dumpall.c:119
NameData proname
Definition: pg_proc.h:35
static char * buf
Definition: pg_test_fsync.c:72
static void get_string_attr(PyObject *obj, char *attrname, char **str)
Definition: plpy_elog.c:562
static bool set_string_attr(PyObject *obj, char *attrname, char *str)
Definition: plpy_elog.c:578
PyObject * PLy_exc_error
Definition: plpy_elog.c:15
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)
Definition: plpy_elog.c:412
static void PLy_traceback(PyObject *e, PyObject *v, PyObject *tb, char **xmsg, char **tbmsg, int *tb_depth)
Definition: plpy_elog.c:162
void PLy_elog_impl(int elevel, const char *fmt,...)
Definition: plpy_elog.c:43
PyObject * PLy_exc_spi_error
Definition: plpy_elog.c:17
void PLy_exception_set_with_details(PyObject *excclass, ErrorData *edata)
Definition: plpy_elog.c:504
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)
Definition: plpy_elog.c:376
void PLy_exception_set(PyObject *exc, const char *fmt,...)
Definition: plpy_elog.c:472
static char * get_source_line(const char *src, int lineno)
Definition: plpy_elog.c:430
static void PLy_get_sqlerrcode(PyObject *exc, int *sqlerrcode)
Definition: plpy_elog.c:352
void PLy_exception_set_plural(PyObject *exc, const char *fmt_singular, const char *fmt_plural, unsigned long n,...)
Definition: plpy_elog.c:486
PyObject * PLy_exc_fatal
Definition: plpy_elog.c:16
PLyExecutionContext * PLy_current_execution_context(void)
Definition: plpy_main.c:365
char * PLy_procedure_name(PLyProcedure *proc)
char * PLyUnicode_AsString(PyObject *unicode)
Definition: plpy_util.c:82
PyObject * PLyUnicode_FromString(const char *s)
Definition: plpy_util.c:117
#define vsnprintf
Definition: port.h:238
#define PG_DIAG_SCHEMA_NAME
Definition: postgres_ext.h:65
#define PG_DIAG_CONSTRAINT_NAME
Definition: postgres_ext.h:69
#define PG_DIAG_DATATYPE_NAME
Definition: postgres_ext.h:68
#define PG_DIAG_TABLE_NAME
Definition: postgres_ext.h:66
#define PG_DIAG_COLUMN_NAME
Definition: postgres_ext.h:67
e
Definition: preproc-init.c:82
static void error(void)
Definition: sql-dyntest.c:147
int appendStringInfoVA(StringInfo str, const char *fmt, va_list args)
Definition: stringinfo.c:187
void appendStringInfo(StringInfo str, const char *fmt,...)
Definition: stringinfo.c:145
void enlargeStringInfo(StringInfo str, int needed)
Definition: stringinfo.c:337
void appendStringInfoString(StringInfo str, const char *s)
Definition: stringinfo.c:230
void initStringInfo(StringInfo str)
Definition: stringinfo.c:97
char * schema_name
Definition: elog.h:447
char * internalquery
Definition: elog.h:454
int sqlerrcode
Definition: elog.h:439
char * datatype_name
Definition: elog.h:450
char * detail
Definition: elog.h:441
char * table_name
Definition: elog.h:448
char * message
Definition: elog.h:440
char * hint
Definition: elog.h:443
char * constraint_name
Definition: elog.h:451
char * column_name
Definition: elog.h:449
PLyProcedure * curr_proc
Definition: plpy_main.h:20
const char * name