LCOV - code coverage report
Current view: top level - src/pl/plpython - plpy_cursorobject.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 86.9 % 199 173
Test Date: 2026-02-28 14:14:49 Functions: 100.0 % 8 8
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*
       2              :  * the PLyCursor class
       3              :  *
       4              :  * src/pl/plpython/plpy_cursorobject.c
       5              :  */
       6              : 
       7              : #include "postgres.h"
       8              : 
       9              : #include <limits.h>
      10              : 
      11              : #include "catalog/pg_type.h"
      12              : #include "mb/pg_wchar.h"
      13              : #include "plpy_cursorobject.h"
      14              : #include "plpy_elog.h"
      15              : #include "plpy_main.h"
      16              : #include "plpy_planobject.h"
      17              : #include "plpy_resultobject.h"
      18              : #include "plpy_spi.h"
      19              : #include "plpy_util.h"
      20              : #include "utils/memutils.h"
      21              : 
      22              : static PyObject *PLy_cursor_query(const char *query);
      23              : static void PLy_cursor_dealloc(PLyCursorObject *self);
      24              : static PyObject *PLy_cursor_iternext(PyObject *self);
      25              : static PyObject *PLy_cursor_fetch(PyObject *self, PyObject *args);
      26              : static PyObject *PLy_cursor_close(PyObject *self, PyObject *unused);
      27              : 
      28              : static const char PLy_cursor_doc[] = "Wrapper around a PostgreSQL cursor";
      29              : 
      30              : static PyMethodDef PLy_cursor_methods[] = {
      31              :     {"fetch", PLy_cursor_fetch, METH_VARARGS, NULL},
      32              :     {"close", PLy_cursor_close, METH_NOARGS, NULL},
      33              :     {NULL, NULL, 0, NULL}
      34              : };
      35              : 
      36              : static PyType_Slot PLyCursor_slots[] =
      37              : {
      38              :     {
      39              :         Py_tp_dealloc, PLy_cursor_dealloc
      40              :     },
      41              :     {
      42              :         Py_tp_doc, (char *) PLy_cursor_doc
      43              :     },
      44              :     {
      45              :         Py_tp_iter, PyObject_SelfIter
      46              :     },
      47              :     {
      48              :         Py_tp_iternext, PLy_cursor_iternext
      49              :     },
      50              :     {
      51              :         Py_tp_methods, PLy_cursor_methods
      52              :     },
      53              :     {
      54              :         0, NULL
      55              :     }
      56              : };
      57              : 
      58              : static PyType_Spec PLyCursor_spec =
      59              : {
      60              :     .name = "PLyCursor",
      61              :     .basicsize = sizeof(PLyCursorObject),
      62              :     .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
      63              :     .slots = PLyCursor_slots,
      64              : };
      65              : 
      66              : static PyTypeObject *PLy_CursorType;
      67              : 
      68              : void
      69           23 : PLy_cursor_init_type(void)
      70              : {
      71           23 :     PLy_CursorType = (PyTypeObject *) PyType_FromSpec(&PLyCursor_spec);
      72           23 :     if (!PLy_CursorType)
      73            0 :         elog(ERROR, "could not initialize PLy_CursorType");
      74           23 : }
      75              : 
      76              : PyObject *
      77           18 : PLy_cursor(PyObject *self, PyObject *args)
      78              : {
      79              :     char       *query;
      80              :     PyObject   *plan;
      81           18 :     PyObject   *planargs = NULL;
      82              : 
      83           18 :     if (PyArg_ParseTuple(args, "s", &query))
      84           14 :         return PLy_cursor_query(query);
      85              : 
      86            4 :     PyErr_Clear();
      87              : 
      88            4 :     if (PyArg_ParseTuple(args, "O|O", &plan, &planargs))
      89            4 :         return PLy_cursor_plan(plan, planargs);
      90              : 
      91            0 :     PLy_exception_set(PLy_exc_error, "plpy.cursor expected a query or a plan");
      92            0 :     return NULL;
      93              : }
      94              : 
      95              : 
      96              : static PyObject *
      97           14 : PLy_cursor_query(const char *query)
      98              : {
      99              :     PLyCursorObject *cursor;
     100           14 :     PLyExecutionContext *exec_ctx = PLy_current_execution_context();
     101              :     volatile MemoryContext oldcontext;
     102              :     volatile ResourceOwner oldowner;
     103              : 
     104           14 :     if ((cursor = PyObject_New(PLyCursorObject, PLy_CursorType)) == NULL)
     105            0 :         return NULL;
     106              : #if PY_VERSION_HEX < 0x03080000
     107              :     /* Workaround for Python issue 35810; no longer necessary in Python 3.8 */
     108              :     Py_INCREF(PLy_CursorType);
     109              : #endif
     110           14 :     cursor->portalname = NULL;
     111           14 :     cursor->closed = false;
     112           14 :     cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
     113              :                                          "PL/Python cursor context",
     114              :                                          ALLOCSET_DEFAULT_SIZES);
     115              : 
     116              :     /* Initialize for converting result tuples to Python */
     117           14 :     PLy_input_setup_func(&cursor->result, cursor->mcxt,
     118              :                          RECORDOID, -1,
     119           14 :                          exec_ctx->curr_proc);
     120              : 
     121           14 :     oldcontext = CurrentMemoryContext;
     122           14 :     oldowner = CurrentResourceOwner;
     123              : 
     124           14 :     PLy_spi_subtransaction_begin(oldcontext, oldowner);
     125              : 
     126           14 :     PG_TRY();
     127              :     {
     128              :         SPIPlanPtr  plan;
     129              :         Portal      portal;
     130              : 
     131           14 :         pg_verifymbstr(query, strlen(query), false);
     132              : 
     133           14 :         plan = SPI_prepare(query, 0, NULL);
     134           14 :         if (plan == NULL)
     135            0 :             elog(ERROR, "SPI_prepare failed: %s",
     136              :                  SPI_result_code_string(SPI_result));
     137              : 
     138           28 :         portal = SPI_cursor_open(NULL, plan, NULL, NULL,
     139           14 :                                  exec_ctx->curr_proc->fn_readonly);
     140           14 :         SPI_freeplan(plan);
     141              : 
     142           14 :         if (portal == NULL)
     143            0 :             elog(ERROR, "SPI_cursor_open() failed: %s",
     144              :                  SPI_result_code_string(SPI_result));
     145              : 
     146           14 :         cursor->portalname = MemoryContextStrdup(cursor->mcxt, portal->name);
     147              : 
     148           14 :         PinPortal(portal);
     149              : 
     150           14 :         PLy_spi_subtransaction_commit(oldcontext, oldowner);
     151              :     }
     152            0 :     PG_CATCH();
     153              :     {
     154            0 :         PLy_spi_subtransaction_abort(oldcontext, oldowner);
     155            0 :         return NULL;
     156              :     }
     157           14 :     PG_END_TRY();
     158              : 
     159              :     Assert(cursor->portalname != NULL);
     160           14 :     return (PyObject *) cursor;
     161              : }
     162              : 
     163              : PyObject *
     164            5 : PLy_cursor_plan(PyObject *ob, PyObject *args)
     165              : {
     166              :     PLyCursorObject *cursor;
     167              :     volatile int nargs;
     168              :     PLyPlanObject *plan;
     169            5 :     PLyExecutionContext *exec_ctx = PLy_current_execution_context();
     170              :     volatile MemoryContext oldcontext;
     171              :     volatile ResourceOwner oldowner;
     172              : 
     173            5 :     if (args)
     174              :     {
     175            3 :         if (!PySequence_Check(args) || PyUnicode_Check(args))
     176              :         {
     177            0 :             PLy_exception_set(PyExc_TypeError, "plpy.cursor takes a sequence as its second argument");
     178            0 :             return NULL;
     179              :         }
     180            3 :         nargs = PySequence_Length(args);
     181              :     }
     182              :     else
     183            2 :         nargs = 0;
     184              : 
     185            5 :     plan = (PLyPlanObject *) ob;
     186              : 
     187            5 :     if (nargs != plan->nargs)
     188              :     {
     189              :         char       *sv;
     190            1 :         PyObject   *so = PyObject_Str(args);
     191              : 
     192            1 :         if (!so)
     193            0 :             PLy_elog(ERROR, "could not execute plan");
     194            1 :         sv = PLyUnicode_AsString(so);
     195            1 :         PLy_exception_set_plural(PyExc_TypeError,
     196              :                                  "Expected sequence of %d argument, got %d: %s",
     197              :                                  "Expected sequence of %d arguments, got %d: %s",
     198            1 :                                  plan->nargs,
     199              :                                  plan->nargs, nargs, sv);
     200              :         Py_DECREF(so);
     201              : 
     202            1 :         return NULL;
     203              :     }
     204              : 
     205            4 :     if ((cursor = PyObject_New(PLyCursorObject, PLy_CursorType)) == NULL)
     206            0 :         return NULL;
     207              : #if PY_VERSION_HEX < 0x03080000
     208              :     /* Workaround for Python issue 35810; no longer necessary in Python 3.8 */
     209              :     Py_INCREF(PLy_CursorType);
     210              : #endif
     211            4 :     cursor->portalname = NULL;
     212            4 :     cursor->closed = false;
     213            4 :     cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
     214              :                                          "PL/Python cursor context",
     215              :                                          ALLOCSET_DEFAULT_SIZES);
     216              : 
     217              :     /* Initialize for converting result tuples to Python */
     218            4 :     PLy_input_setup_func(&cursor->result, cursor->mcxt,
     219              :                          RECORDOID, -1,
     220            4 :                          exec_ctx->curr_proc);
     221              : 
     222            4 :     oldcontext = CurrentMemoryContext;
     223            4 :     oldowner = CurrentResourceOwner;
     224              : 
     225            4 :     PLy_spi_subtransaction_begin(oldcontext, oldowner);
     226              : 
     227            4 :     PG_TRY();
     228              :     {
     229              :         Portal      portal;
     230              :         MemoryContext tmpcontext;
     231              :         Datum      *volatile values;
     232              :         char       *volatile nulls;
     233              :         volatile int j;
     234              : 
     235              :         /*
     236              :          * Converted arguments and associated cruft will be in this context,
     237              :          * which is local to our subtransaction.
     238              :          */
     239            4 :         tmpcontext = AllocSetContextCreate(CurTransactionContext,
     240              :                                            "PL/Python temporary context",
     241              :                                            ALLOCSET_SMALL_SIZES);
     242            4 :         MemoryContextSwitchTo(tmpcontext);
     243              : 
     244            4 :         if (nargs > 0)
     245              :         {
     246            2 :             values = (Datum *) palloc(nargs * sizeof(Datum));
     247            2 :             nulls = (char *) palloc(nargs * sizeof(char));
     248              :         }
     249              :         else
     250              :         {
     251            2 :             values = NULL;
     252            2 :             nulls = NULL;
     253              :         }
     254              : 
     255            6 :         for (j = 0; j < nargs; j++)
     256              :         {
     257            2 :             PLyObToDatum *arg = &plan->args[j];
     258              :             PyObject   *elem;
     259              : 
     260            2 :             elem = PySequence_GetItem(args, j);
     261            2 :             PG_TRY(2);
     262              :             {
     263              :                 bool        isnull;
     264              : 
     265            2 :                 values[j] = PLy_output_convert(arg, elem, &isnull);
     266            2 :                 nulls[j] = isnull ? 'n' : ' ';
     267              :             }
     268            2 :             PG_FINALLY(2);
     269              :             {
     270              :                 Py_DECREF(elem);
     271              :             }
     272            2 :             PG_END_TRY(2);
     273              :         }
     274              : 
     275            4 :         MemoryContextSwitchTo(oldcontext);
     276              : 
     277            8 :         portal = SPI_cursor_open(NULL, plan->plan, values, nulls,
     278            4 :                                  exec_ctx->curr_proc->fn_readonly);
     279            4 :         if (portal == NULL)
     280            0 :             elog(ERROR, "SPI_cursor_open() failed: %s",
     281              :                  SPI_result_code_string(SPI_result));
     282              : 
     283            4 :         cursor->portalname = MemoryContextStrdup(cursor->mcxt, portal->name);
     284              : 
     285            4 :         PinPortal(portal);
     286              : 
     287            4 :         MemoryContextDelete(tmpcontext);
     288            4 :         PLy_spi_subtransaction_commit(oldcontext, oldowner);
     289              :     }
     290            0 :     PG_CATCH();
     291              :     {
     292              :         Py_DECREF(cursor);
     293              :         /* Subtransaction abort will remove the tmpcontext */
     294            0 :         PLy_spi_subtransaction_abort(oldcontext, oldowner);
     295            0 :         return NULL;
     296              :     }
     297            4 :     PG_END_TRY();
     298              : 
     299              :     Assert(cursor->portalname != NULL);
     300            4 :     return (PyObject *) cursor;
     301              : }
     302              : 
     303              : static void
     304           18 : PLy_cursor_dealloc(PLyCursorObject *self)
     305              : {
     306              : #if PY_VERSION_HEX >= 0x03080000
     307           18 :     PyTypeObject *tp = Py_TYPE(self);
     308              : #endif
     309              :     Portal      portal;
     310              : 
     311           18 :     if (!self->closed)
     312              :     {
     313           15 :         portal = GetPortalByName(self->portalname);
     314              : 
     315           15 :         if (PortalIsValid(portal))
     316              :         {
     317           12 :             UnpinPortal(portal);
     318           12 :             SPI_cursor_close(portal);
     319              :         }
     320           15 :         self->closed = true;
     321              :     }
     322           18 :     if (self->mcxt)
     323              :     {
     324           18 :         MemoryContextDelete(self->mcxt);
     325           18 :         self->mcxt = NULL;
     326              :     }
     327              : 
     328           18 :     PyObject_Free(self);
     329              : #if PY_VERSION_HEX >= 0x03080000
     330              :     /* This was not needed before Python 3.8 (Python issue 35810) */
     331              :     Py_DECREF(tp);
     332              : #endif
     333           18 : }
     334              : 
     335              : static PyObject *
     336           36 : PLy_cursor_iternext(PyObject *self)
     337              : {
     338              :     PLyCursorObject *cursor;
     339              :     PyObject   *ret;
     340           36 :     PLyExecutionContext *exec_ctx = PLy_current_execution_context();
     341              :     volatile MemoryContext oldcontext;
     342              :     volatile ResourceOwner oldowner;
     343              :     Portal      portal;
     344              : 
     345           36 :     cursor = (PLyCursorObject *) self;
     346              : 
     347           36 :     if (cursor->closed)
     348              :     {
     349            1 :         PLy_exception_set(PyExc_ValueError, "iterating a closed cursor");
     350            1 :         return NULL;
     351              :     }
     352              : 
     353           35 :     portal = GetPortalByName(cursor->portalname);
     354           35 :     if (!PortalIsValid(portal))
     355              :     {
     356            0 :         PLy_exception_set(PyExc_ValueError,
     357              :                           "iterating a cursor in an aborted subtransaction");
     358            0 :         return NULL;
     359              :     }
     360              : 
     361           35 :     oldcontext = CurrentMemoryContext;
     362           35 :     oldowner = CurrentResourceOwner;
     363              : 
     364           35 :     PLy_spi_subtransaction_begin(oldcontext, oldowner);
     365              : 
     366           35 :     PG_TRY();
     367              :     {
     368           35 :         SPI_cursor_fetch(portal, true, 1);
     369           34 :         if (SPI_processed == 0)
     370              :         {
     371            8 :             PyErr_SetNone(PyExc_StopIteration);
     372            8 :             ret = NULL;
     373              :         }
     374              :         else
     375              :         {
     376           26 :             PLy_input_setup_tuple(&cursor->result, SPI_tuptable->tupdesc,
     377           26 :                                   exec_ctx->curr_proc);
     378              : 
     379           26 :             ret = PLy_input_from_tuple(&cursor->result, SPI_tuptable->vals[0],
     380           26 :                                        SPI_tuptable->tupdesc, true);
     381              :         }
     382              : 
     383           34 :         SPI_freetuptable(SPI_tuptable);
     384              : 
     385           34 :         PLy_spi_subtransaction_commit(oldcontext, oldowner);
     386              :     }
     387            1 :     PG_CATCH();
     388              :     {
     389            1 :         PLy_spi_subtransaction_abort(oldcontext, oldowner);
     390            1 :         return NULL;
     391              :     }
     392           34 :     PG_END_TRY();
     393              : 
     394           34 :     return ret;
     395              : }
     396              : 
     397              : static PyObject *
     398           13 : PLy_cursor_fetch(PyObject *self, PyObject *args)
     399              : {
     400              :     PLyCursorObject *cursor;
     401              :     int         count;
     402              :     PLyResultObject *ret;
     403           13 :     PLyExecutionContext *exec_ctx = PLy_current_execution_context();
     404              :     volatile MemoryContext oldcontext;
     405              :     volatile ResourceOwner oldowner;
     406              :     Portal      portal;
     407              : 
     408           13 :     if (!PyArg_ParseTuple(args, "i:fetch", &count))
     409            0 :         return NULL;
     410              : 
     411           13 :     cursor = (PLyCursorObject *) self;
     412              : 
     413           13 :     if (cursor->closed)
     414              :     {
     415            1 :         PLy_exception_set(PyExc_ValueError, "fetch from a closed cursor");
     416            1 :         return NULL;
     417              :     }
     418              : 
     419           12 :     portal = GetPortalByName(cursor->portalname);
     420           12 :     if (!PortalIsValid(portal))
     421              :     {
     422            2 :         PLy_exception_set(PyExc_ValueError,
     423              :                           "iterating a cursor in an aborted subtransaction");
     424            2 :         return NULL;
     425              :     }
     426              : 
     427           10 :     ret = (PLyResultObject *) PLy_result_new();
     428           10 :     if (ret == NULL)
     429            0 :         return NULL;
     430              : 
     431           10 :     oldcontext = CurrentMemoryContext;
     432           10 :     oldowner = CurrentResourceOwner;
     433              : 
     434           10 :     PLy_spi_subtransaction_begin(oldcontext, oldowner);
     435              : 
     436           10 :     PG_TRY();
     437              :     {
     438           10 :         SPI_cursor_fetch(portal, true, count);
     439              : 
     440           10 :         Py_DECREF(ret->status);
     441           10 :         ret->status = PyLong_FromLong(SPI_OK_FETCH);
     442              : 
     443           10 :         Py_DECREF(ret->nrows);
     444           10 :         ret->nrows = PyLong_FromUnsignedLongLong(SPI_processed);
     445              : 
     446           10 :         if (SPI_processed != 0)
     447              :         {
     448              :             uint64      i;
     449              : 
     450              :             /*
     451              :              * PyList_New() and PyList_SetItem() use Py_ssize_t for list size
     452              :              * and list indices; so we cannot support a result larger than
     453              :              * PY_SSIZE_T_MAX.
     454              :              */
     455            7 :             if (SPI_processed > (uint64) PY_SSIZE_T_MAX)
     456            0 :                 ereport(ERROR,
     457              :                         (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
     458              :                          errmsg("query result has too many rows to fit in a Python list")));
     459              : 
     460            7 :             Py_DECREF(ret->rows);
     461            7 :             ret->rows = PyList_New(SPI_processed);
     462            7 :             if (!ret->rows)
     463              :             {
     464              :                 Py_DECREF(ret);
     465            0 :                 ret = NULL;
     466              :             }
     467              :             else
     468              :             {
     469            7 :                 PLy_input_setup_tuple(&cursor->result, SPI_tuptable->tupdesc,
     470            7 :                                       exec_ctx->curr_proc);
     471              : 
     472           44 :                 for (i = 0; i < SPI_processed; i++)
     473              :                 {
     474           74 :                     PyObject   *row = PLy_input_from_tuple(&cursor->result,
     475           37 :                                                            SPI_tuptable->vals[i],
     476           37 :                                                            SPI_tuptable->tupdesc,
     477              :                                                            true);
     478              : 
     479           37 :                     PyList_SetItem(ret->rows, i, row);
     480              :                 }
     481              :             }
     482              :         }
     483              : 
     484           10 :         SPI_freetuptable(SPI_tuptable);
     485              : 
     486           10 :         PLy_spi_subtransaction_commit(oldcontext, oldowner);
     487              :     }
     488            0 :     PG_CATCH();
     489              :     {
     490            0 :         PLy_spi_subtransaction_abort(oldcontext, oldowner);
     491            0 :         return NULL;
     492              :     }
     493           10 :     PG_END_TRY();
     494              : 
     495           10 :     return (PyObject *) ret;
     496              : }
     497              : 
     498              : static PyObject *
     499            5 : PLy_cursor_close(PyObject *self, PyObject *unused)
     500              : {
     501            5 :     PLyCursorObject *cursor = (PLyCursorObject *) self;
     502              : 
     503            5 :     if (!cursor->closed)
     504              :     {
     505            4 :         Portal      portal = GetPortalByName(cursor->portalname);
     506              : 
     507            4 :         if (!PortalIsValid(portal))
     508              :         {
     509            1 :             PLy_exception_set(PyExc_ValueError,
     510              :                               "closing a cursor in an aborted subtransaction");
     511            1 :             return NULL;
     512              :         }
     513              : 
     514            3 :         UnpinPortal(portal);
     515            3 :         SPI_cursor_close(portal);
     516            3 :         cursor->closed = true;
     517              :     }
     518              : 
     519            4 :     Py_RETURN_NONE;
     520              : }
        

Generated by: LCOV version 2.0-1