LCOV - differential code coverage report
Current view: top level - src/pl/plpython - plpy_elog.c (source / functions) Coverage Total Hit UBC CBC
Current: Differential Code Coverage HEAD vs 15 Lines: 82.9 % 258 214 44 214
Current Date: 2023-04-08 15:15:32 Functions: 100.0 % 11 11 11
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

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

Generated by: LCOV version v1.16-55-g56c0a2a