2 * reporting Python exceptions as PostgreSQL errors
4 * src/pl/plpython/plpy_elog.c
9 #include "lib/stringinfo.h"
10 #include "plpy_elog.h"
11 #include "plpy_main.h"
12 #include "plpy_procedure.h"
15 PyObject
*PLy_exc_error
= NULL
;
16 PyObject
*PLy_exc_fatal
= NULL
;
17 PyObject
*PLy_exc_spi_error
= NULL
;
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
);
31 static void get_string_attr(PyObject
*obj
, char *attrname
, char **str
);
32 static bool set_string_attr(PyObject
*obj
, char *attrname
, char *str
);
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
43 PLy_elog_impl(int elevel
, const char *fmt
,...)
45 int save_errno
= errno
;
53 const char *primary
= NULL
;
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
;
65 PyErr_Fetch(&exc
, &val
, &tb
);
69 PyErr_NormalizeException(&exc
, &val
, &tb
);
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
))
84 /* this releases our refcount on tb! */
85 PLy_traceback(exc
, val
, tb
,
86 &xmsg
, &tbmsg
, &tb_depth
);
90 initStringInfo(&emsg
);
98 needed
= appendStringInfoVA(&emsg
, dgettext(TEXTDOMAIN
, fmt
), ap
);
102 enlargeStringInfo(&emsg
, needed
);
106 /* If there's an exception message, it goes in the detail. */
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
,
128 (table_name
) ? err_generic_string(PG_DIAG_TABLE_NAME
,
130 (column_name
) ? err_generic_string(PG_DIAG_COLUMN_NAME
,
132 (datatype_name
) ? err_generic_string(PG_DIAG_DATATYPE_NAME
,
134 (constraint_name
) ? err_generic_string(PG_DIAG_CONSTRAINT_NAME
,
135 constraint_name
) : 0));
152 * Extract a Python traceback from the given exception data.
154 * The exception error message is returned in xmsg, the traceback in
155 * tbmsg (both as palloc'd strings) and the traceback depth in
158 * We release refcounts on all the Python objects in the traceback stack,
162 PLy_traceback(PyObject
*e
, PyObject
*v
, PyObject
*tb
,
163 char **xmsg
, char **tbmsg
, int *tb_depth
)
166 PyObject
*e_module_o
;
167 char *e_type_s
= NULL
;
168 char *e_module_s
= NULL
;
169 PyObject
*vob
= NULL
;
172 StringInfoData tbstr
;
175 * if no exception, return nulls
187 * Format the exception and its value and put it in xmsg.
190 e_type_o
= PyObject_GetAttrString(e
, "__name__");
191 e_module_o
= PyObject_GetAttrString(e
, "__module__");
193 e_type_s
= PLyUnicode_AsString(e_type_o
);
195 e_module_s
= PLyUnicode_AsString(e_module_o
);
197 if (v
&& ((vob
= PyObject_Str(v
)) != NULL
))
198 vstr
= PLyUnicode_AsString(vob
);
202 initStringInfo(&xstr
);
203 if (!e_type_s
|| !e_module_s
)
205 /* shouldn't happen */
206 appendStringInfoString(&xstr
, "unrecognized exception");
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
);
214 appendStringInfo(&xstr
, "%s.%s", e_module_s
, e_type_s
);
215 appendStringInfo(&xstr
, ": %s", vstr
);
220 * Now format the traceback and put it in tbmsg.
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
)
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
;
238 lineno
= PyObject_GetAttrString(tb
, "tb_lineno");
240 elog(ERROR
, "could not get line number from Python traceback");
242 frame
= PyObject_GetAttrString(tb
, "tb_frame");
244 elog(ERROR
, "could not get frame from Python traceback");
246 code
= PyObject_GetAttrString(frame
, "f_code");
248 elog(ERROR
, "could not get code object from Python frame");
250 name
= PyObject_GetAttrString(code
, "co_name");
252 elog(ERROR
, "could not get function name from Python code object");
254 filename
= PyObject_GetAttrString(code
, "co_filename");
255 if (filename
== NULL
)
256 elog(ERROR
, "could not get file name from Python code object");
264 Py_XDECREF(filename
);
269 /* The first frame always points at <module>, skip it. */
272 PLyExecutionContext
*exec_ctx
= PLy_current_execution_context();
276 char *plain_filename
;
280 * The second frame points at the internal function, but to mimic
281 * Python error reporting we want to say <module>.
286 fname
= PLyUnicode_AsString(name
);
288 proname
= PLy_procedure_name(exec_ctx
->curr_proc
);
289 plain_filename
= PLyUnicode_AsString(filename
);
290 plain_lineno
= PyLong_AsLong(lineno
);
293 appendStringInfo(&tbstr
, "\n PL/Python anonymous code block, line %ld, in %s",
294 plain_lineno
- 1, fname
);
296 appendStringInfo(&tbstr
, "\n PL/Python function \"%s\", line %ld, in %s",
297 proname
, plain_lineno
- 1, fname
);
300 * function code object was compiled with "<string>" as the
303 if (exec_ctx
->curr_proc
&& plain_filename
!= NULL
&&
304 strcmp(plain_filename
, "<string>") == 0)
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.
315 line
= get_source_line(exec_ctx
->curr_proc
->src
, plain_lineno
);
318 appendStringInfo(&tbstr
, "\n %s", line
);
330 /* Release the current frame and go to the next one. */
332 tb
= PyObject_GetAttrString(tb
, "tb_next");
333 Assert(tb_prev
!= Py_None
);
336 elog(ERROR
, "could not traverse Python traceback");
340 /* Return the traceback. */
343 Py_XDECREF(e_type_o
);
344 Py_XDECREF(e_module_o
);
349 * Extract error code from SPIError's sqlstate attribute.
352 PLy_get_sqlerrcode(PyObject
*exc
, int *sqlerrcode
)
357 sqlstate
= PyObject_GetAttrString(exc
, "sqlstate");
358 if (sqlstate
== NULL
)
361 buffer
= PLyUnicode_AsString(sqlstate
);
362 if (strlen(buffer
) == 5 &&
363 strspn(buffer
, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
365 *sqlerrcode
= MAKE_SQLSTATE(buffer
[0], buffer
[1], buffer
[2],
366 buffer
[3], buffer
[4]);
373 * Extract the error data from a SPIError
376 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
,
380 char **datatype_name
, char **constraint_name
)
384 spidata
= PyObject_GetAttrString(exc
, "spidata");
388 PyArg_ParseTuple(spidata
, "izzzizzzzz",
389 sqlerrcode
, detail
, hint
, query
, position
,
390 schema_name
, table_name
, column_name
,
391 datatype_name
, constraint_name
);
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.
399 PLy_get_sqlerrcode(exc
, sqlerrcode
);
406 * Extract the error data from an Error.
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.
412 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
)
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
);
427 * Get the given source line as a palloc'd string
430 get_source_line(const char *src
, int lineno
)
432 const char *s
= NULL
;
433 const char *next
= src
;
440 while (current
< lineno
)
443 next
= strchr(s
+ 1, '\n');
449 if (current
!= lineno
)
452 while (*s
&& isspace((unsigned char) *s
))
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
466 return pnstrdup(s
, next
- s
);
470 /* call PyErr_SetString with a vprint interface and translation support */
472 PLy_exception_set(PyObject
*exc
, const char *fmt
,...)
478 vsnprintf(buf
, sizeof(buf
), dgettext(TEXTDOMAIN
, fmt
), ap
);
481 PyErr_SetString(exc
, buf
);
484 /* same, with pluralized message */
486 PLy_exception_set_plural(PyObject
*exc
,
487 const char *fmt_singular
, const char *fmt_plural
,
494 vsnprintf(buf
, sizeof(buf
),
495 dngettext(TEXTDOMAIN
, fmt_singular
, fmt_plural
, n
),
499 PyErr_SetString(exc
, buf
);
502 /* set attributes of the given exception to details from ErrorData */
504 PLy_exception_set_with_details(PyObject
*excclass
, ErrorData
*edata
)
506 PyObject
*args
= NULL
;
507 PyObject
*error
= NULL
;
509 args
= Py_BuildValue("(s)", edata
->message
);
513 /* create a new exception with the error message as the parameter */
514 error
= PyObject_CallObject(excclass
, args
);
518 if (!set_string_attr(error
, "sqlstate",
519 unpack_sql_state(edata
->sqlerrcode
)))
522 if (!set_string_attr(error
, "detail", edata
->detail
))
525 if (!set_string_attr(error
, "hint", edata
->hint
))
528 if (!set_string_attr(error
, "query", edata
->internalquery
))
531 if (!set_string_attr(error
, "schema_name", edata
->schema_name
))
534 if (!set_string_attr(error
, "table_name", edata
->table_name
))
537 if (!set_string_attr(error
, "column_name", edata
->column_name
))
540 if (!set_string_attr(error
, "datatype_name", edata
->datatype_name
))
543 if (!set_string_attr(error
, "constraint_name", edata
->constraint_name
))
546 PyErr_SetObject(excclass
, error
);
557 elog(ERROR
, "could not convert error to Python exception");
560 /* get string value of an object attribute */
562 get_string_attr(PyObject
*obj
, char *attrname
, char **str
)
566 val
= PyObject_GetAttrString(obj
, attrname
);
567 if (val
!= NULL
&& val
!= Py_None
)
569 *str
= pstrdup(PLyUnicode_AsString(val
));
574 /* set an object attribute to a string value, returns true when the set was
578 set_string_attr(PyObject
*obj
, char *attrname
, char *str
)
585 val
= PLyUnicode_FromString(str
);
595 result
= PyObject_SetAttrString(obj
, attrname
, val
);