LCOV - code coverage report
Current view: top level - src/pl/plpython - plpy_elog.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 84.5 % 251 212
Test Date: 2026-03-04 15:14:37 Functions: 100.0 % 11 11
Legend: Lines:     hit not hit

            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           58 : PLy_elog_impl(int elevel, const char *fmt,...)
      45              : {
      46           58 :     int         save_errno = errno;
      47           58 :     char       *volatile xmsg = NULL;
      48           58 :     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           58 :     if (fmt)
      57            4 :         initStringInfo(&emsg);
      58              : 
      59           58 :     PyErr_Fetch(&exc, &val, &tb);
      60              : 
      61              :     /* Use a PG_TRY block to ensure we release the PyObjects just acquired */
      62           58 :     PG_TRY();
      63              :     {
      64           58 :         const char *primary = NULL;
      65           58 :         int         sqlerrcode = 0;
      66           58 :         char       *detail = NULL;
      67           58 :         char       *hint = NULL;
      68           58 :         char       *query = NULL;
      69           58 :         int         position = 0;
      70           58 :         char       *schema_name = NULL;
      71           58 :         char       *table_name = NULL;
      72           58 :         char       *column_name = NULL;
      73           58 :         char       *datatype_name = NULL;
      74           58 :         char       *constraint_name = NULL;
      75              : 
      76           58 :         if (exc != NULL)
      77              :         {
      78           58 :             PyErr_NormalizeException(&exc, &val, &tb);
      79              : 
      80           58 :             if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
      81           23 :                 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           35 :             else if (PyErr_GivenExceptionMatches(val, PLy_exc_error))
      86           13 :                 PLy_get_error_data(val, &sqlerrcode, &detail, &hint,
      87              :                                    &schema_name, &table_name, &column_name,
      88              :                                    &datatype_name, &constraint_name);
      89           22 :             else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
      90            0 :                 elevel = FATAL;
      91              :         }
      92              : 
      93           58 :         PLy_traceback(exc, val, tb,
      94              :                       &xmsg, &tbmsg, &tb_depth);
      95              : 
      96           58 :         if (fmt)
      97              :         {
      98              :             for (;;)
      99            0 :             {
     100              :                 va_list     ap;
     101              :                 int         needed;
     102              : 
     103            4 :                 errno = save_errno;
     104            4 :                 va_start(ap, fmt);
     105            4 :                 needed = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap);
     106            4 :                 va_end(ap);
     107            4 :                 if (needed == 0)
     108            4 :                     break;
     109            0 :                 enlargeStringInfo(&emsg, needed);
     110              :             }
     111            4 :             primary = emsg.data;
     112              : 
     113              :             /* If there's an exception message, it goes in the detail. */
     114            4 :             if (xmsg)
     115            4 :                 detail = xmsg;
     116              :         }
     117              :         else
     118              :         {
     119           54 :             if (xmsg)
     120           54 :                 primary = xmsg;
     121              :         }
     122              : 
     123           58 :         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           58 :     PG_FINALLY();
     143              :     {
     144           58 :         Py_XDECREF(exc);
     145           58 :         Py_XDECREF(val);
     146           58 :         Py_XDECREF(tb);
     147              :         /* For neatness' sake, also release our string buffers */
     148           58 :         if (fmt)
     149            4 :             pfree(emsg.data);
     150           58 :         if (xmsg)
     151           58 :             pfree(xmsg);
     152           58 :         if (tbmsg)
     153           58 :             pfree(tbmsg);
     154              :     }
     155           58 :     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           58 : PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
     167              :               char *volatile *xmsg, char *volatile *tbmsg, int *tb_depth)
     168              : {
     169           58 :     PyObject   *volatile e_type_o = NULL;
     170           58 :     PyObject   *volatile e_module_o = NULL;
     171           58 :     PyObject   *volatile vob = NULL;
     172              :     StringInfoData tbstr;
     173              : 
     174              :     /*
     175              :      * if no exception, return nulls
     176              :      */
     177           58 :     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           58 :     PG_TRY();
     190              :     {
     191           58 :         char       *e_type_s = NULL;
     192           58 :         char       *e_module_s = NULL;
     193              :         const char *vstr;
     194              :         StringInfoData xstr;
     195              : 
     196           58 :         e_type_o = PyObject_GetAttrString(e, "__name__");
     197           58 :         e_module_o = PyObject_GetAttrString(e, "__module__");
     198           58 :         if (e_type_o)
     199           58 :             e_type_s = PLyUnicode_AsString(e_type_o);
     200           58 :         if (e_module_o)
     201           58 :             e_module_s = PLyUnicode_AsString(e_module_o);
     202              : 
     203           58 :         if (v && ((vob = PyObject_Str(v)) != NULL))
     204           58 :             vstr = PLyUnicode_AsString(vob);
     205              :         else
     206            0 :             vstr = "unknown";
     207              : 
     208           58 :         initStringInfo(&xstr);
     209           58 :         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           58 :         else if (strcmp(e_module_s, "builtins") == 0
     216           36 :                  || strcmp(e_module_s, "__main__") == 0
     217           36 :                  || strcmp(e_module_s, "exceptions") == 0)
     218           22 :             appendStringInfoString(&xstr, e_type_s);
     219              :         else
     220           36 :             appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s);
     221           58 :         appendStringInfo(&xstr, ": %s", vstr);
     222              : 
     223           58 :         *xmsg = xstr.data;
     224              :     }
     225            0 :     PG_FINALLY();
     226              :     {
     227           58 :         Py_XDECREF(e_type_o);
     228           58 :         Py_XDECREF(e_module_o);
     229           58 :         Py_XDECREF(vob);
     230              :     }
     231           58 :     PG_END_TRY();
     232              : 
     233              :     /*
     234              :      * Now format the traceback and put it in tbmsg.
     235              :      */
     236           58 :     *tb_depth = 0;
     237           58 :     initStringInfo(&tbstr);
     238              :     /* Mimic Python traceback reporting as close as possible. */
     239           58 :     appendStringInfoString(&tbstr, "Traceback (most recent call last):");
     240          181 :     while (tb != NULL && tb != Py_None)
     241              :     {
     242          123 :         PyObject   *volatile frame = NULL;
     243          123 :         PyObject   *volatile code = NULL;
     244          123 :         PyObject   *volatile name = NULL;
     245          123 :         PyObject   *volatile lineno = NULL;
     246          123 :         PyObject   *volatile filename = NULL;
     247              : 
     248          123 :         PG_TRY();
     249              :         {
     250          123 :             lineno = PyObject_GetAttrString(tb, "tb_lineno");
     251          123 :             if (lineno == NULL)
     252            0 :                 elog(ERROR, "could not get line number from Python traceback");
     253              : 
     254          123 :             frame = PyObject_GetAttrString(tb, "tb_frame");
     255          123 :             if (frame == NULL)
     256            0 :                 elog(ERROR, "could not get frame from Python traceback");
     257              : 
     258          123 :             code = PyObject_GetAttrString(frame, "f_code");
     259          123 :             if (code == NULL)
     260            0 :                 elog(ERROR, "could not get code object from Python frame");
     261              : 
     262          123 :             name = PyObject_GetAttrString(code, "co_name");
     263          123 :             if (name == NULL)
     264            0 :                 elog(ERROR, "could not get function name from Python code object");
     265              : 
     266          123 :             filename = PyObject_GetAttrString(code, "co_filename");
     267          123 :             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          123 :             if (*tb_depth > 0)
     272              :             {
     273           68 :                 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           68 :                 if (*tb_depth == 1)
     285           54 :                     fname = "<module>";
     286              :                 else
     287           14 :                     fname = PLyUnicode_AsString(name);
     288              : 
     289           68 :                 proname = PLy_procedure_name(exec_ctx->curr_proc);
     290           68 :                 plain_filename = PLyUnicode_AsString(filename);
     291           68 :                 plain_lineno = PyLong_AsLong(lineno);
     292              : 
     293           68 :                 if (proname == NULL)
     294           13 :                     appendStringInfo(&tbstr, "\n  PL/Python anonymous code block, line %ld, in %s",
     295              :                                      plain_lineno - 1, fname);
     296              :                 else
     297           55 :                     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           68 :                 if (exec_ctx->curr_proc && plain_filename != NULL &&
     305           68 :                     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           68 :                     line = get_source_line(exec_ctx->curr_proc->src, plain_lineno);
     317           68 :                     if (line)
     318              :                     {
     319           68 :                         appendStringInfo(&tbstr, "\n    %s", line);
     320           68 :                         pfree(line);
     321              :                     }
     322              :                 }
     323              :             }
     324              :         }
     325            0 :         PG_FINALLY();
     326              :         {
     327          123 :             Py_XDECREF(frame);
     328          123 :             Py_XDECREF(code);
     329          123 :             Py_XDECREF(name);
     330          123 :             Py_XDECREF(lineno);
     331          123 :             Py_XDECREF(filename);
     332              :         }
     333          123 :         PG_END_TRY();
     334              : 
     335              :         /* Advance to the next frame. */
     336          123 :         tb = PyObject_GetAttrString(tb, "tb_next");
     337          123 :         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              :         Py_DECREF(tb);
     349              : 
     350          123 :         (*tb_depth)++;
     351              :     }
     352              : 
     353              :     /* Return the traceback. */
     354           58 :     *tbmsg = tbstr.data;
     355              : }
     356              : 
     357              : /*
     358              :  * Extract error code from SPIError's sqlstate attribute.
     359              :  */
     360              : static void
     361           17 : PLy_get_sqlerrcode(PyObject *exc, int *sqlerrcode)
     362              : {
     363              :     PyObject   *sqlstate;
     364              :     char       *buffer;
     365              : 
     366           17 :     sqlstate = PyObject_GetAttrString(exc, "sqlstate");
     367           17 :     if (sqlstate == NULL)
     368            4 :         return;
     369              : 
     370           13 :     buffer = PLyUnicode_AsString(sqlstate);
     371           13 :     if (strlen(buffer) == 5 &&
     372           13 :         strspn(buffer, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
     373              :     {
     374           13 :         *sqlerrcode = MAKE_SQLSTATE(buffer[0], buffer[1], buffer[2],
     375              :                                     buffer[3], buffer[4]);
     376              :     }
     377              : 
     378              :     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           23 : 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           23 :     spidata = PyObject_GetAttrString(exc, "spidata");
     398              : 
     399           23 :     if (spidata != NULL)
     400              :     {
     401           19 :         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            4 :         PLy_get_sqlerrcode(exc, sqlerrcode);
     413              :     }
     414              : 
     415           23 :     Py_XDECREF(spidata);
     416           23 : }
     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           13 : 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           13 :     PLy_get_sqlerrcode(exc, sqlerrcode);
     435           13 :     get_string_attr(exc, "detail", detail);
     436           13 :     get_string_attr(exc, "hint", hint);
     437           13 :     get_string_attr(exc, "schema_name", schema_name);
     438           13 :     get_string_attr(exc, "table_name", table_name);
     439           13 :     get_string_attr(exc, "column_name", column_name);
     440           13 :     get_string_attr(exc, "datatype_name", datatype_name);
     441           13 :     get_string_attr(exc, "constraint_name", constraint_name);
     442           13 : }
     443              : 
     444              : /*
     445              :  * Get the given source line as a palloc'd string
     446              :  */
     447              : static char *
     448           68 : get_source_line(const char *src, int lineno)
     449              : {
     450           68 :     const char *s = NULL;
     451           68 :     const char *next = src;
     452           68 :     int         current = 0;
     453              : 
     454              :     /* sanity check */
     455           68 :     if (lineno <= 0)
     456            0 :         return NULL;
     457              : 
     458          488 :     while (current < lineno)
     459              :     {
     460          420 :         s = next;
     461          420 :         next = strchr(s + 1, '\n');
     462          420 :         current++;
     463          420 :         if (next == NULL)
     464            0 :             break;
     465              :     }
     466              : 
     467           68 :     if (current != lineno)
     468            0 :         return NULL;
     469              : 
     470          318 :     while (*s && isspace((unsigned char) *s))
     471          250 :         s++;
     472              : 
     473           68 :     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           68 :     if (next < s)
     482            0 :         return NULL;
     483              : 
     484           68 :     return pnstrdup(s, next - s);
     485              : }
     486              : 
     487              : 
     488              : /* call PyErr_SetString with a vprint interface and translation support */
     489              : void
     490           18 : PLy_exception_set(PyObject *exc, const char *fmt,...)
     491              : {
     492              :     char        buf[1024];
     493              :     va_list     ap;
     494              : 
     495           18 :     va_start(ap, fmt);
     496           18 :     vsnprintf(buf, sizeof(buf), dgettext(TEXTDOMAIN, fmt), ap);
     497           18 :     va_end(ap);
     498              : 
     499           18 :     PyErr_SetString(exc, buf);
     500           18 : }
     501              : 
     502              : /* same, with pluralized message */
     503              : void
     504            1 : 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            1 :     va_start(ap, n);
     512            1 :     vsnprintf(buf, sizeof(buf),
     513            1 :               dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n),
     514              :               ap);
     515            1 :     va_end(ap);
     516              : 
     517            1 :     PyErr_SetString(exc, buf);
     518            1 : }
     519              : 
     520              : /* set attributes of the given exception to details from ErrorData */
     521              : void
     522           11 : PLy_exception_set_with_details(PyObject *excclass, ErrorData *edata)
     523              : {
     524           11 :     PyObject   *args = NULL;
     525           11 :     PyObject   *error = NULL;
     526              : 
     527           11 :     args = Py_BuildValue("(s)", edata->message);
     528           11 :     if (!args)
     529            0 :         goto failure;
     530              : 
     531              :     /* create a new exception with the error message as the parameter */
     532           11 :     error = PyObject_CallObject(excclass, args);
     533           11 :     if (!error)
     534            0 :         goto failure;
     535              : 
     536           11 :     if (!set_string_attr(error, "sqlstate",
     537              :                          unpack_sql_state(edata->sqlerrcode)))
     538            0 :         goto failure;
     539              : 
     540           11 :     if (!set_string_attr(error, "detail", edata->detail))
     541            0 :         goto failure;
     542              : 
     543           11 :     if (!set_string_attr(error, "hint", edata->hint))
     544            0 :         goto failure;
     545              : 
     546           11 :     if (!set_string_attr(error, "query", edata->internalquery))
     547            0 :         goto failure;
     548              : 
     549           11 :     if (!set_string_attr(error, "schema_name", edata->schema_name))
     550            0 :         goto failure;
     551              : 
     552           11 :     if (!set_string_attr(error, "table_name", edata->table_name))
     553            0 :         goto failure;
     554              : 
     555           11 :     if (!set_string_attr(error, "column_name", edata->column_name))
     556            0 :         goto failure;
     557              : 
     558           11 :     if (!set_string_attr(error, "datatype_name", edata->datatype_name))
     559            0 :         goto failure;
     560              : 
     561           11 :     if (!set_string_attr(error, "constraint_name", edata->constraint_name))
     562            0 :         goto failure;
     563              : 
     564           11 :     PyErr_SetObject(excclass, error);
     565              : 
     566              :     Py_DECREF(args);
     567              :     Py_DECREF(error);
     568              : 
     569           11 :     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           91 : get_string_attr(PyObject *obj, char *attrname, char **str)
     581              : {
     582              :     PyObject   *val;
     583              : 
     584           91 :     val = PyObject_GetAttrString(obj, attrname);
     585           91 :     if (val != NULL && val != Py_None)
     586              :     {
     587           28 :         *str = PLyUnicode_AsString(val);
     588              :     }
     589           91 :     Py_XDECREF(val);
     590           91 : }
     591              : 
     592              : /* set an object attribute to a string value, returns true when the set was
     593              :  * successful
     594              :  */
     595              : static bool
     596           99 : set_string_attr(PyObject *obj, char *attrname, char *str)
     597              : {
     598              :     int         result;
     599              :     PyObject   *val;
     600              : 
     601           99 :     if (str != NULL)
     602              :     {
     603           39 :         val = PLyUnicode_FromString(str);
     604           39 :         if (!val)
     605            0 :             return false;
     606              :     }
     607              :     else
     608              :     {
     609           60 :         val = Py_None;
     610              :         Py_INCREF(Py_None);
     611              :     }
     612              : 
     613           99 :     result = PyObject_SetAttrString(obj, attrname, val);
     614              :     Py_DECREF(val);
     615              : 
     616           99 :     return result != -1;
     617              : }
        

Generated by: LCOV version 2.0-1