LCOV - code coverage report
Current view: top level - src/pl/plpython - plpy_plpymodule.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 159 179 88.8 %
Date: 2026-02-02 14:17:46 Functions: 15 16 93.8 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * the plpy module
       3             :  *
       4             :  * src/pl/plpython/plpy_plpymodule.c
       5             :  */
       6             : 
       7             : #include "postgres.h"
       8             : 
       9             : #include "mb/pg_wchar.h"
      10             : #include "plpy_cursorobject.h"
      11             : #include "plpy_elog.h"
      12             : #include "plpy_planobject.h"
      13             : #include "plpy_plpymodule.h"
      14             : #include "plpy_resultobject.h"
      15             : #include "plpy_spi.h"
      16             : #include "plpy_subxactobject.h"
      17             : #include "plpy_util.h"
      18             : #include "utils/builtins.h"
      19             : 
      20             : HTAB       *PLy_spi_exceptions = NULL;
      21             : 
      22             : 
      23             : static void PLy_add_exceptions(PyObject *plpy);
      24             : static PyObject *PLy_create_exception(char *name,
      25             :                                       PyObject *base, PyObject *dict,
      26             :                                       const char *modname, PyObject *mod);
      27             : static void PLy_generate_spi_exceptions(PyObject *mod, PyObject *base);
      28             : 
      29             : /* module functions */
      30             : static PyObject *PLy_debug(PyObject *self, PyObject *args, PyObject *kw);
      31             : static PyObject *PLy_log(PyObject *self, PyObject *args, PyObject *kw);
      32             : static PyObject *PLy_info(PyObject *self, PyObject *args, PyObject *kw);
      33             : static PyObject *PLy_notice(PyObject *self, PyObject *args, PyObject *kw);
      34             : static PyObject *PLy_warning(PyObject *self, PyObject *args, PyObject *kw);
      35             : static PyObject *PLy_error(PyObject *self, PyObject *args, PyObject *kw);
      36             : static PyObject *PLy_fatal(PyObject *self, PyObject *args, PyObject *kw);
      37             : static PyObject *PLy_quote_literal(PyObject *self, PyObject *args);
      38             : static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args);
      39             : static PyObject *PLy_quote_ident(PyObject *self, PyObject *args);
      40             : 
      41             : 
      42             : /* A list of all known exceptions, generated from backend/utils/errcodes.txt */
      43             : typedef struct ExceptionMap
      44             : {
      45             :     char       *name;
      46             :     char       *classname;
      47             :     int         sqlstate;
      48             : } ExceptionMap;
      49             : 
      50             : static const ExceptionMap exception_map[] = {
      51             : #include "spiexceptions.h"
      52             :     {NULL, NULL, 0}
      53             : };
      54             : 
      55             : static PyMethodDef PLy_methods[] = {
      56             :     /*
      57             :      * logging methods
      58             :      */
      59             :     {"debug", (PyCFunction) (pg_funcptr_t) PLy_debug, METH_VARARGS | METH_KEYWORDS, NULL},
      60             :     {"log", (PyCFunction) (pg_funcptr_t) PLy_log, METH_VARARGS | METH_KEYWORDS, NULL},
      61             :     {"info", (PyCFunction) (pg_funcptr_t) PLy_info, METH_VARARGS | METH_KEYWORDS, NULL},
      62             :     {"notice", (PyCFunction) (pg_funcptr_t) PLy_notice, METH_VARARGS | METH_KEYWORDS, NULL},
      63             :     {"warning", (PyCFunction) (pg_funcptr_t) PLy_warning, METH_VARARGS | METH_KEYWORDS, NULL},
      64             :     {"error", (PyCFunction) (pg_funcptr_t) PLy_error, METH_VARARGS | METH_KEYWORDS, NULL},
      65             :     {"fatal", (PyCFunction) (pg_funcptr_t) PLy_fatal, METH_VARARGS | METH_KEYWORDS, NULL},
      66             : 
      67             :     /*
      68             :      * create a stored plan
      69             :      */
      70             :     {"prepare", PLy_spi_prepare, METH_VARARGS, NULL},
      71             : 
      72             :     /*
      73             :      * execute a plan or query
      74             :      */
      75             :     {"execute", PLy_spi_execute, METH_VARARGS, NULL},
      76             : 
      77             :     /*
      78             :      * escaping strings
      79             :      */
      80             :     {"quote_literal", PLy_quote_literal, METH_VARARGS, NULL},
      81             :     {"quote_nullable", PLy_quote_nullable, METH_VARARGS, NULL},
      82             :     {"quote_ident", PLy_quote_ident, METH_VARARGS, NULL},
      83             : 
      84             :     /*
      85             :      * create the subtransaction context manager
      86             :      */
      87             :     {"subtransaction", PLy_subtransaction_new, METH_NOARGS, NULL},
      88             : 
      89             :     /*
      90             :      * create a cursor
      91             :      */
      92             :     {"cursor", PLy_cursor, METH_VARARGS, NULL},
      93             : 
      94             :     /*
      95             :      * transaction control
      96             :      */
      97             :     {"commit", PLy_commit, METH_NOARGS, NULL},
      98             :     {"rollback", PLy_rollback, METH_NOARGS, NULL},
      99             : 
     100             :     {NULL, NULL, 0, NULL}
     101             : };
     102             : 
     103             : static PyMethodDef PLy_exc_methods[] = {
     104             :     {NULL, NULL, 0, NULL}
     105             : };
     106             : 
     107             : static PyModuleDef PLy_module = {
     108             :     PyModuleDef_HEAD_INIT,
     109             :     .m_name = "plpy",
     110             :     .m_size = -1,
     111             :     .m_methods = PLy_methods,
     112             : };
     113             : 
     114             : static PyModuleDef PLy_exc_module = {
     115             :     PyModuleDef_HEAD_INIT,
     116             :     .m_name = "spiexceptions",
     117             :     .m_size = -1,
     118             :     .m_methods = PLy_exc_methods,
     119             : };
     120             : 
     121             : /*
     122             :  * Must have external linkage, because PyMODINIT_FUNC does dllexport on
     123             :  * Windows-like platforms.
     124             :  */
     125             : PyMODINIT_FUNC
     126          46 : PyInit_plpy(void)
     127             : {
     128             :     PyObject   *m;
     129             : 
     130          46 :     m = PyModule_Create(&PLy_module);
     131          46 :     if (m == NULL)
     132           0 :         return NULL;
     133             : 
     134          46 :     PLy_add_exceptions(m);
     135             : 
     136          46 :     PLy_plan_init_type();
     137          46 :     PLy_result_init_type();
     138          46 :     PLy_subtransaction_init_type();
     139          46 :     PLy_cursor_init_type();
     140             : 
     141          46 :     return m;
     142             : }
     143             : 
     144             : static void
     145          46 : PLy_add_exceptions(PyObject *plpy)
     146             : {
     147             :     PyObject   *excmod;
     148             :     HASHCTL     hash_ctl;
     149             : 
     150          46 :     PLy_exc_error = PLy_create_exception("plpy.Error", NULL, NULL,
     151             :                                          "Error", plpy);
     152          46 :     PLy_exc_fatal = PLy_create_exception("plpy.Fatal", NULL, NULL,
     153             :                                          "Fatal", plpy);
     154          46 :     PLy_exc_spi_error = PLy_create_exception("plpy.SPIError", NULL, NULL,
     155             :                                              "SPIError", plpy);
     156             : 
     157          46 :     excmod = PyModule_Create(&PLy_exc_module);
     158          46 :     if (excmod == NULL)
     159           0 :         PLy_elog(ERROR, "could not create the spiexceptions module");
     160             : 
     161          46 :     hash_ctl.keysize = sizeof(int);
     162          46 :     hash_ctl.entrysize = sizeof(PLyExceptionEntry);
     163          46 :     PLy_spi_exceptions = hash_create("PL/Python SPI exceptions", 256,
     164             :                                      &hash_ctl, HASH_ELEM | HASH_BLOBS);
     165             : 
     166          46 :     PLy_generate_spi_exceptions(excmod, PLy_exc_spi_error);
     167             : 
     168          46 :     if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0)
     169             :     {
     170           0 :         Py_XDECREF(excmod);
     171           0 :         PLy_elog(ERROR, "could not add the spiexceptions module");
     172             :     }
     173          46 : }
     174             : 
     175             : /*
     176             :  * Create an exception object and add it to the module
     177             :  *
     178             :  * The created exception object is also returned.
     179             :  */
     180             : static PyObject *
     181       11684 : PLy_create_exception(char *name, PyObject *base, PyObject *dict,
     182             :                      const char *modname, PyObject *mod)
     183             : {
     184             :     PyObject   *exc;
     185             : 
     186       11684 :     exc = PyErr_NewException(name, base, dict);
     187       11684 :     if (exc == NULL)
     188           0 :         PLy_elog(ERROR, NULL);
     189             : 
     190             :     /*
     191             :      * PyModule_AddObject() (below) steals the reference to exc, but we also
     192             :      * want to return the value from this function, so add another ref to
     193             :      * account for that.  (The caller will store a pointer to the exception
     194             :      * object in some permanent variable.)
     195             :      */
     196       11684 :     Py_INCREF(exc);
     197             : 
     198       11684 :     if (PyModule_AddObject(mod, modname, exc) < 0)
     199             :     {
     200           0 :         Py_XDECREF(exc);
     201           0 :         PLy_elog(ERROR, "could not add exception %s", name);
     202             :     }
     203             : 
     204       11684 :     return exc;
     205             : }
     206             : 
     207             : /*
     208             :  * Add all the autogenerated exceptions as subclasses of SPIError
     209             :  */
     210             : static void
     211          46 : PLy_generate_spi_exceptions(PyObject *mod, PyObject *base)
     212             : {
     213             :     int         i;
     214             : 
     215       11592 :     for (i = 0; exception_map[i].name != NULL; i++)
     216             :     {
     217             :         bool        found;
     218             :         PyObject   *exc;
     219             :         PLyExceptionEntry *entry;
     220             :         PyObject   *sqlstate;
     221       11546 :         PyObject   *dict = PyDict_New();
     222             : 
     223       11546 :         if (dict == NULL)
     224           0 :             PLy_elog(ERROR, NULL);
     225             : 
     226       11546 :         sqlstate = PLyUnicode_FromString(unpack_sql_state(exception_map[i].sqlstate));
     227       11546 :         if (sqlstate == NULL)
     228           0 :             PLy_elog(ERROR, "could not generate SPI exceptions");
     229             : 
     230       11546 :         PyDict_SetItemString(dict, "sqlstate", sqlstate);
     231       11546 :         Py_DECREF(sqlstate);
     232             : 
     233       11546 :         exc = PLy_create_exception(exception_map[i].name, base, dict,
     234       11546 :                                    exception_map[i].classname, mod);
     235             : 
     236       11546 :         entry = hash_search(PLy_spi_exceptions, &exception_map[i].sqlstate,
     237             :                             HASH_ENTER, &found);
     238             :         Assert(!found);
     239       11546 :         entry->exc = exc;
     240             :     }
     241          46 : }
     242             : 
     243             : 
     244             : /*
     245             :  * the python interface to the elog function
     246             :  * don't confuse these with PLy_elog
     247             :  */
     248             : static PyObject *PLy_output(volatile int level, PyObject *self,
     249             :                             PyObject *args, PyObject *kw);
     250             : 
     251             : static PyObject *
     252           4 : PLy_debug(PyObject *self, PyObject *args, PyObject *kw)
     253             : {
     254           4 :     return PLy_output(DEBUG2, self, args, kw);
     255             : }
     256             : 
     257             : static PyObject *
     258           4 : PLy_log(PyObject *self, PyObject *args, PyObject *kw)
     259             : {
     260           4 :     return PLy_output(LOG, self, args, kw);
     261             : }
     262             : 
     263             : static PyObject *
     264         224 : PLy_info(PyObject *self, PyObject *args, PyObject *kw)
     265             : {
     266         224 :     return PLy_output(INFO, self, args, kw);
     267             : }
     268             : 
     269             : static PyObject *
     270         430 : PLy_notice(PyObject *self, PyObject *args, PyObject *kw)
     271             : {
     272         430 :     return PLy_output(NOTICE, self, args, kw);
     273             : }
     274             : 
     275             : static PyObject *
     276          10 : PLy_warning(PyObject *self, PyObject *args, PyObject *kw)
     277             : {
     278          10 :     return PLy_output(WARNING, self, args, kw);
     279             : }
     280             : 
     281             : static PyObject *
     282          22 : PLy_error(PyObject *self, PyObject *args, PyObject *kw)
     283             : {
     284          22 :     return PLy_output(ERROR, self, args, kw);
     285             : }
     286             : 
     287             : static PyObject *
     288           0 : PLy_fatal(PyObject *self, PyObject *args, PyObject *kw)
     289             : {
     290           0 :     return PLy_output(FATAL, self, args, kw);
     291             : }
     292             : 
     293             : static PyObject *
     294          12 : PLy_quote_literal(PyObject *self, PyObject *args)
     295             : {
     296             :     const char *str;
     297             :     char       *quoted;
     298             :     PyObject   *ret;
     299             : 
     300          12 :     if (!PyArg_ParseTuple(args, "s:quote_literal", &str))
     301           0 :         return NULL;
     302             : 
     303          12 :     quoted = quote_literal_cstr(str);
     304          12 :     ret = PLyUnicode_FromString(quoted);
     305          12 :     pfree(quoted);
     306             : 
     307          12 :     return ret;
     308             : }
     309             : 
     310             : static PyObject *
     311          12 : PLy_quote_nullable(PyObject *self, PyObject *args)
     312             : {
     313             :     const char *str;
     314             :     char       *quoted;
     315             :     PyObject   *ret;
     316             : 
     317          12 :     if (!PyArg_ParseTuple(args, "z:quote_nullable", &str))
     318           0 :         return NULL;
     319             : 
     320          12 :     if (str == NULL)
     321           2 :         return PLyUnicode_FromString("NULL");
     322             : 
     323          10 :     quoted = quote_literal_cstr(str);
     324          10 :     ret = PLyUnicode_FromString(quoted);
     325          10 :     pfree(quoted);
     326             : 
     327          10 :     return ret;
     328             : }
     329             : 
     330             : static PyObject *
     331           6 : PLy_quote_ident(PyObject *self, PyObject *args)
     332             : {
     333             :     const char *str;
     334             :     const char *quoted;
     335             :     PyObject   *ret;
     336             : 
     337           6 :     if (!PyArg_ParseTuple(args, "s:quote_ident", &str))
     338           0 :         return NULL;
     339             : 
     340           6 :     quoted = quote_identifier(str);
     341           6 :     ret = PLyUnicode_FromString(quoted);
     342             : 
     343           6 :     return ret;
     344             : }
     345             : 
     346             : /* enforce cast of object to string (returns a palloc'd string or NULL) */
     347             : static char *
     348         116 : object_to_string(PyObject *obj)
     349             : {
     350         116 :     if (obj)
     351             :     {
     352         116 :         PyObject   *so = PyObject_Str(obj);
     353             : 
     354         116 :         if (so != NULL)
     355             :         {
     356             :             char       *str;
     357             : 
     358         116 :             str = PLyUnicode_AsString(so);
     359         116 :             Py_DECREF(so);
     360             : 
     361         116 :             return str;
     362             :         }
     363             :     }
     364             : 
     365           0 :     return NULL;
     366             : }
     367             : 
     368             : static PyObject *
     369         694 : PLy_output(volatile int level, PyObject *self, PyObject *args, PyObject *kw)
     370             : {
     371         694 :     int         sqlstate = 0;
     372         694 :     char       *volatile sqlstatestr = NULL;
     373         694 :     char       *volatile message = NULL;
     374         694 :     char       *volatile detail = NULL;
     375         694 :     char       *volatile hint = NULL;
     376         694 :     char       *volatile column_name = NULL;
     377         694 :     char       *volatile constraint_name = NULL;
     378         694 :     char       *volatile datatype_name = NULL;
     379         694 :     char       *volatile table_name = NULL;
     380         694 :     char       *volatile schema_name = NULL;
     381             :     volatile MemoryContext oldcontext;
     382             :     PyObject   *key,
     383             :                *value;
     384             :     PyObject   *volatile so;
     385         694 :     Py_ssize_t  pos = 0;
     386             : 
     387         694 :     if (PyTuple_Size(args) == 1)
     388             :     {
     389             :         /*
     390             :          * Treat single argument specially to avoid undesirable ('tuple',)
     391             :          * decoration.
     392             :          */
     393             :         PyObject   *o;
     394             : 
     395         522 :         if (!PyArg_UnpackTuple(args, "plpy.elog", 1, 1, &o))
     396           0 :             PLy_elog(ERROR, "could not unpack arguments in plpy.elog");
     397         522 :         so = PyObject_Str(o);
     398             :     }
     399             :     else
     400         172 :         so = PyObject_Str(args);
     401             : 
     402         694 :     if (so == NULL || ((message = PLyUnicode_AsString(so)) == NULL))
     403             :     {
     404           0 :         level = ERROR;
     405           0 :         message = dgettext(TEXTDOMAIN, "could not parse error message in plpy.elog");
     406             :     }
     407         694 :     message = pstrdup(message);
     408             : 
     409         694 :     Py_XDECREF(so);
     410             : 
     411         694 :     if (kw != NULL)
     412             :     {
     413         160 :         while (PyDict_Next(kw, &pos, &key, &value))
     414             :         {
     415         122 :             char       *keyword = PLyUnicode_AsString(key);
     416             : 
     417         122 :             if (strcmp(keyword, "message") == 0)
     418             :             {
     419             :                 /* the message should not be overwritten */
     420          18 :                 if (PyTuple_Size(args) != 0)
     421             :                 {
     422           4 :                     PLy_exception_set(PyExc_TypeError, "argument 'message' given by name and position");
     423           4 :                     return NULL;
     424             :                 }
     425             : 
     426          14 :                 if (message)
     427          14 :                     pfree(message);
     428          14 :                 message = object_to_string(value);
     429             :             }
     430         104 :             else if (strcmp(keyword, "detail") == 0)
     431          30 :                 detail = object_to_string(value);
     432          74 :             else if (strcmp(keyword, "hint") == 0)
     433          14 :                 hint = object_to_string(value);
     434          60 :             else if (strcmp(keyword, "sqlstate") == 0)
     435          14 :                 sqlstatestr = object_to_string(value);
     436          46 :             else if (strcmp(keyword, "schema_name") == 0)
     437           8 :                 schema_name = object_to_string(value);
     438          38 :             else if (strcmp(keyword, "table_name") == 0)
     439          10 :                 table_name = object_to_string(value);
     440          28 :             else if (strcmp(keyword, "column_name") == 0)
     441           8 :                 column_name = object_to_string(value);
     442          20 :             else if (strcmp(keyword, "datatype_name") == 0)
     443          10 :                 datatype_name = object_to_string(value);
     444          10 :             else if (strcmp(keyword, "constraint_name") == 0)
     445           8 :                 constraint_name = object_to_string(value);
     446             :             else
     447             :             {
     448           2 :                 PLy_exception_set(PyExc_TypeError,
     449             :                                   "'%s' is an invalid keyword argument for this function",
     450             :                                   keyword);
     451           2 :                 return NULL;
     452             :             }
     453             :         }
     454             :     }
     455             : 
     456         688 :     if (sqlstatestr != NULL)
     457             :     {
     458          14 :         if (strlen(sqlstatestr) != 5)
     459             :         {
     460           2 :             PLy_exception_set(PyExc_ValueError, "invalid SQLSTATE code");
     461           2 :             return NULL;
     462             :         }
     463             : 
     464          12 :         if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
     465             :         {
     466           0 :             PLy_exception_set(PyExc_ValueError, "invalid SQLSTATE code");
     467           0 :             return NULL;
     468             :         }
     469             : 
     470          12 :         sqlstate = MAKE_SQLSTATE(sqlstatestr[0],
     471             :                                  sqlstatestr[1],
     472             :                                  sqlstatestr[2],
     473             :                                  sqlstatestr[3],
     474             :                                  sqlstatestr[4]);
     475             :     }
     476             : 
     477         686 :     oldcontext = CurrentMemoryContext;
     478         686 :     PG_TRY();
     479             :     {
     480         686 :         if (message != NULL)
     481         686 :             pg_verifymbstr(message, strlen(message), false);
     482         686 :         if (detail != NULL)
     483          30 :             pg_verifymbstr(detail, strlen(detail), false);
     484         686 :         if (hint != NULL)
     485          14 :             pg_verifymbstr(hint, strlen(hint), false);
     486         686 :         if (schema_name != NULL)
     487           8 :             pg_verifymbstr(schema_name, strlen(schema_name), false);
     488         686 :         if (table_name != NULL)
     489          10 :             pg_verifymbstr(table_name, strlen(table_name), false);
     490         686 :         if (column_name != NULL)
     491           8 :             pg_verifymbstr(column_name, strlen(column_name), false);
     492         686 :         if (datatype_name != NULL)
     493          10 :             pg_verifymbstr(datatype_name, strlen(datatype_name), false);
     494         686 :         if (constraint_name != NULL)
     495           8 :             pg_verifymbstr(constraint_name, strlen(constraint_name), false);
     496             : 
     497         686 :         ereport(level,
     498             :                 ((sqlstate != 0) ? errcode(sqlstate) : 0,
     499             :                  (message != NULL) ? errmsg_internal("%s", message) : 0,
     500             :                  (detail != NULL) ? errdetail_internal("%s", detail) : 0,
     501             :                  (hint != NULL) ? errhint("%s", hint) : 0,
     502             :                  (column_name != NULL) ?
     503             :                  err_generic_string(PG_DIAG_COLUMN_NAME, column_name) : 0,
     504             :                  (constraint_name != NULL) ?
     505             :                  err_generic_string(PG_DIAG_CONSTRAINT_NAME, constraint_name) : 0,
     506             :                  (datatype_name != NULL) ?
     507             :                  err_generic_string(PG_DIAG_DATATYPE_NAME, datatype_name) : 0,
     508             :                  (table_name != NULL) ?
     509             :                  err_generic_string(PG_DIAG_TABLE_NAME, table_name) : 0,
     510             :                  (schema_name != NULL) ?
     511             :                  err_generic_string(PG_DIAG_SCHEMA_NAME, schema_name) : 0));
     512             :     }
     513          22 :     PG_CATCH();
     514             :     {
     515             :         ErrorData  *edata;
     516             : 
     517          22 :         MemoryContextSwitchTo(oldcontext);
     518          22 :         edata = CopyErrorData();
     519          22 :         FlushErrorState();
     520             : 
     521          22 :         PLy_exception_set_with_details(PLy_exc_error, edata);
     522          22 :         FreeErrorData(edata);
     523             : 
     524          22 :         return NULL;
     525             :     }
     526         664 :     PG_END_TRY();
     527             : 
     528             :     /*
     529             :      * return a legal object so the interpreter will continue on its merry way
     530             :      */
     531         664 :     Py_RETURN_NONE;
     532             : }

Generated by: LCOV version 1.16