LCOV - code coverage report
Current view: top level - src/pl/plpython - plpy_elog.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 86.1 % 251 216
Test Date: 2026-07-18 10:15:36 Functions: 100.0 % 11 11
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 65.2 % 184 120

             Branch data     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                 :          71 : PLy_elog_impl(int elevel, const char *fmt, ...)
      45                 :             : {
      46                 :          71 :     int         save_errno = errno;
      47                 :          71 :     char       *volatile xmsg = NULL;
      48                 :          71 :     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         [ +  + ]:          71 :     if (fmt)
      57                 :          14 :         initStringInfo(&emsg);
      58                 :             : 
      59                 :          71 :     PyErr_Fetch(&exc, &val, &tb);
      60                 :             : 
      61                 :             :     /* Use a PG_TRY block to ensure we release the PyObjects just acquired */
      62         [ +  + ]:          71 :     PG_TRY();
      63                 :             :     {
      64                 :          71 :         const char *primary = NULL;
      65                 :          71 :         int         sqlerrcode = 0;
      66                 :          71 :         char       *detail = NULL;
      67                 :          71 :         char       *hint = NULL;
      68                 :          71 :         char       *query = NULL;
      69                 :          71 :         int         position = 0;
      70                 :          71 :         char       *schema_name = NULL;
      71                 :          71 :         char       *table_name = NULL;
      72                 :          71 :         char       *column_name = NULL;
      73                 :          71 :         char       *datatype_name = NULL;
      74                 :          71 :         char       *constraint_name = NULL;
      75                 :             : 
      76         [ +  + ]:          71 :         if (exc != NULL)
      77                 :             :         {
      78                 :          70 :             PyErr_NormalizeException(&exc, &val, &tb);
      79                 :             : 
      80         [ +  + ]:          70 :             if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
      81                 :          26 :                 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         [ +  + ]:          44 :             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         [ -  + ]:          31 :             else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
      90                 :           0 :                 elevel = FATAL;
      91                 :             :         }
      92                 :             : 
      93                 :          71 :         PLy_traceback(exc, val, tb,
      94                 :             :                       &xmsg, &tbmsg, &tb_depth);
      95                 :             : 
      96         [ +  + ]:          71 :         if (fmt)
      97                 :             :         {
      98                 :             :             for (;;)
      99                 :           0 :             {
     100                 :             :                 va_list     ap;
     101                 :             :                 int         needed;
     102                 :             : 
     103                 :          14 :                 errno = save_errno;
     104                 :          14 :                 va_start(ap, fmt);
     105                 :          14 :                 needed = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap);
     106                 :          14 :                 va_end(ap);
     107         [ +  - ]:          14 :                 if (needed == 0)
     108                 :          14 :                     break;
     109                 :           0 :                 enlargeStringInfo(&emsg, needed);
     110                 :             :             }
     111                 :          14 :             primary = emsg.data;
     112                 :             : 
     113                 :             :             /* If there's an exception message, it goes in the detail. */
     114         [ +  + ]:          14 :             if (xmsg)
     115                 :          13 :                 detail = xmsg;
     116                 :             :         }
     117                 :             :         else
     118                 :             :         {
     119         [ +  - ]:          57 :             if (xmsg)
     120                 :          57 :                 primary = xmsg;
     121                 :             :         }
     122                 :             : 
     123   [ +  -  +  +  :          71 :         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                 :          71 :     PG_FINALLY();
     143                 :             :     {
     144                 :          71 :         Py_XDECREF(exc);
     145                 :          71 :         Py_XDECREF(val);
     146                 :          71 :         Py_XDECREF(tb);
     147                 :             :         /* For neatness' sake, also release our string buffers */
     148         [ +  + ]:          71 :         if (fmt)
     149                 :          14 :             pfree(emsg.data);
     150         [ +  + ]:          71 :         if (xmsg)
     151                 :          70 :             pfree(xmsg);
     152         [ +  + ]:          71 :         if (tbmsg)
     153                 :          70 :             pfree(tbmsg);
     154                 :             :     }
     155         [ +  - ]:          71 :     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                 :          71 : PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
     167                 :             :               char *volatile *xmsg, char *volatile *tbmsg, int *tb_depth)
     168                 :             : {
     169                 :          71 :     PyObject   *volatile e_type_o = NULL;
     170                 :          71 :     PyObject   *volatile e_module_o = NULL;
     171                 :          71 :     PyObject   *volatile vob = NULL;
     172                 :             :     StringInfoData tbstr;
     173                 :             : 
     174                 :             :     /*
     175                 :             :      * if no exception, return nulls
     176                 :             :      */
     177         [ +  + ]:          71 :     if (e == NULL)
     178                 :             :     {
     179                 :           1 :         *xmsg = NULL;
     180                 :           1 :         *tbmsg = NULL;
     181                 :           1 :         *tb_depth = 0;
     182                 :             : 
     183                 :           1 :         return;
     184                 :             :     }
     185                 :             : 
     186                 :             :     /*
     187                 :             :      * Format the exception and its value and put it in xmsg.
     188                 :             :      */
     189         [ +  - ]:          70 :     PG_TRY();
     190                 :             :     {
     191                 :          70 :         char       *e_type_s = NULL;
     192                 :          70 :         char       *e_module_s = NULL;
     193                 :             :         const char *vstr;
     194                 :             :         StringInfoData xstr;
     195                 :             : 
     196                 :          70 :         e_type_o = PyObject_GetAttrString(e, "__name__");
     197                 :          70 :         e_module_o = PyObject_GetAttrString(e, "__module__");
     198         [ +  - ]:          70 :         if (e_type_o)
     199                 :          70 :             e_type_s = PLyUnicode_AsString(e_type_o);
     200         [ +  - ]:          70 :         if (e_module_o)
     201                 :          70 :             e_module_s = PLyUnicode_AsString(e_module_o);
     202                 :             : 
     203   [ +  -  +  - ]:          70 :         if (v && ((vob = PyObject_Str(v)) != NULL))
     204                 :          70 :             vstr = PLyUnicode_AsString(vob);
     205                 :             :         else
     206                 :           0 :             vstr = "unknown";
     207                 :             : 
     208                 :          70 :         initStringInfo(&xstr);
     209   [ +  -  -  + ]:          70 :         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         [ +  + ]:          70 :         else if (strcmp(e_module_s, "builtins") == 0
     216         [ +  - ]:          39 :                  || strcmp(e_module_s, "__main__") == 0
     217         [ -  + ]:          39 :                  || strcmp(e_module_s, "exceptions") == 0)
     218                 :          31 :             appendStringInfoString(&xstr, e_type_s);
     219                 :             :         else
     220                 :          39 :             appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s);
     221                 :          70 :         appendStringInfo(&xstr, ": %s", vstr);
     222                 :             : 
     223                 :          70 :         *xmsg = xstr.data;
     224                 :             :     }
     225                 :           0 :     PG_FINALLY();
     226                 :             :     {
     227                 :          70 :         Py_XDECREF(e_type_o);
     228                 :          70 :         Py_XDECREF(e_module_o);
     229                 :          70 :         Py_XDECREF(vob);
     230                 :             :     }
     231         [ -  + ]:          70 :     PG_END_TRY();
     232                 :             : 
     233                 :             :     /*
     234                 :             :      * Now format the traceback and put it in tbmsg.
     235                 :             :      */
     236                 :          70 :     *tb_depth = 0;
     237                 :          70 :     initStringInfo(&tbstr);
     238                 :             :     /* Mimic Python traceback reporting as close as possible. */
     239                 :          70 :     appendStringInfoString(&tbstr, "Traceback (most recent call last):");
     240   [ +  +  +  + ]:         207 :     while (tb != NULL && tb != Py_None)
     241                 :             :     {
     242                 :         137 :         PyObject   *volatile frame = NULL;
     243                 :         137 :         PyObject   *volatile code = NULL;
     244                 :         137 :         PyObject   *volatile name = NULL;
     245                 :         137 :         PyObject   *volatile lineno = NULL;
     246                 :         137 :         PyObject   *volatile filename = NULL;
     247                 :             : 
     248         [ +  - ]:         137 :         PG_TRY();
     249                 :             :         {
     250                 :         137 :             lineno = PyObject_GetAttrString(tb, "tb_lineno");
     251         [ -  + ]:         137 :             if (lineno == NULL)
     252         [ #  # ]:           0 :                 elog(ERROR, "could not get line number from Python traceback");
     253                 :             : 
     254                 :         137 :             frame = PyObject_GetAttrString(tb, "tb_frame");
     255         [ -  + ]:         137 :             if (frame == NULL)
     256         [ #  # ]:           0 :                 elog(ERROR, "could not get frame from Python traceback");
     257                 :             : 
     258                 :         137 :             code = PyObject_GetAttrString(frame, "f_code");
     259         [ -  + ]:         137 :             if (code == NULL)
     260         [ #  # ]:           0 :                 elog(ERROR, "could not get code object from Python frame");
     261                 :             : 
     262                 :         137 :             name = PyObject_GetAttrString(code, "co_name");
     263         [ -  + ]:         137 :             if (name == NULL)
     264         [ #  # ]:           0 :                 elog(ERROR, "could not get function name from Python code object");
     265                 :             : 
     266                 :         137 :             filename = PyObject_GetAttrString(code, "co_filename");
     267         [ -  + ]:         137 :             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         [ +  + ]:         137 :             if (*tb_depth > 0)
     272                 :             :             {
     273                 :          71 :                 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         [ +  + ]:          71 :                 if (*tb_depth == 1)
     285                 :          57 :                     fname = "<module>";
     286                 :             :                 else
     287                 :          14 :                     fname = PLyUnicode_AsString(name);
     288                 :             : 
     289                 :          71 :                 proname = PLy_procedure_name(exec_ctx->curr_proc);
     290                 :          71 :                 plain_filename = PLyUnicode_AsString(filename);
     291                 :          71 :                 plain_lineno = PyLong_AsLong(lineno);
     292                 :             : 
     293         [ +  + ]:          71 :                 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                 :          58 :                     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   [ +  -  +  - ]:          71 :                 if (exec_ctx->curr_proc && plain_filename != NULL &&
     305         [ +  - ]:          71 :                     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                 :          71 :                     line = get_source_line(exec_ctx->curr_proc->src, plain_lineno);
     317         [ +  - ]:          71 :                     if (line)
     318                 :             :                     {
     319                 :          71 :                         appendStringInfo(&tbstr, "\n    %s", line);
     320                 :          71 :                         pfree(line);
     321                 :             :                     }
     322                 :             :                 }
     323                 :             :             }
     324                 :             :         }
     325                 :           0 :         PG_FINALLY();
     326                 :             :         {
     327                 :         137 :             Py_XDECREF(frame);
     328                 :         137 :             Py_XDECREF(code);
     329                 :         137 :             Py_XDECREF(name);
     330                 :         137 :             Py_XDECREF(lineno);
     331                 :         137 :             Py_XDECREF(filename);
     332                 :             :         }
     333         [ -  + ]:         137 :         PG_END_TRY();
     334                 :             : 
     335                 :             :         /* Advance to the next frame. */
     336                 :         137 :         tb = PyObject_GetAttrString(tb, "tb_next");
     337         [ -  + ]:         137 :         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                 :         137 :         (*tb_depth)++;
     351                 :             :     }
     352                 :             : 
     353                 :             :     /* Return the traceback. */
     354                 :          70 :     *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                 :          26 : 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                 :          26 :     spidata = PyObject_GetAttrString(exc, "spidata");
     398                 :             : 
     399         [ +  + ]:          26 :     if (spidata != NULL)
     400                 :             :     {
     401                 :          22 :         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                 :          26 :     Py_XDECREF(spidata);
     416                 :          26 : }
     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                 :          71 : get_source_line(const char *src, int lineno)
     449                 :             : {
     450                 :          71 :     const char *s = NULL;
     451                 :          71 :     const char *next = src;
     452                 :          71 :     int         current = 0;
     453                 :             : 
     454                 :             :     /* sanity check */
     455         [ -  + ]:          71 :     if (lineno <= 0)
     456                 :           0 :         return NULL;
     457                 :             : 
     458         [ +  + ]:         517 :     while (current < lineno)
     459                 :             :     {
     460                 :         446 :         s = next;
     461                 :         446 :         next = strchr(s + 1, '\n');
     462                 :         446 :         current++;
     463         [ -  + ]:         446 :         if (next == NULL)
     464                 :           0 :             break;
     465                 :             :     }
     466                 :             : 
     467         [ -  + ]:          71 :     if (current != lineno)
     468                 :           0 :         return NULL;
     469                 :             : 
     470   [ +  -  +  + ]:         327 :     while (*s && isspace((unsigned char) *s))
     471                 :         256 :         s++;
     472                 :             : 
     473         [ -  + ]:          71 :     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         [ -  + ]:          71 :     if (next < s)
     482                 :           0 :         return NULL;
     483                 :             : 
     484                 :          71 :     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                 :             : /*
     593                 :             :  * set an object attribute to a string value, returns true when the set was
     594                 :             :  * successful
     595                 :             :  */
     596                 :             : static bool
     597                 :          99 : set_string_attr(PyObject *obj, char *attrname, char *str)
     598                 :             : {
     599                 :             :     int         result;
     600                 :             :     PyObject   *val;
     601                 :             : 
     602         [ +  + ]:          99 :     if (str != NULL)
     603                 :             :     {
     604                 :          39 :         val = PLyUnicode_FromString(str);
     605         [ -  + ]:          39 :         if (!val)
     606                 :           0 :             return false;
     607                 :             :     }
     608                 :             :     else
     609                 :             :     {
     610         [ +  - ]:          60 :         val = Py_None;
     611                 :             :         Py_INCREF(Py_None);
     612                 :             :     }
     613                 :             : 
     614                 :          99 :     result = PyObject_SetAttrString(obj, attrname, val);
     615                 :             :     Py_DECREF(val);
     616                 :             : 
     617                 :          99 :     return result != -1;
     618                 :             : }
        

Generated by: LCOV version 2.0-1