Age Owner Branch data TLA 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
5011 peter_e@gmx.net 69 :CBC 23 : PLy_cursor_init_type(void)
70 : : {
178 peter@eisentraut.org 71 : 23 : PLy_CursorType = (PyTypeObject *) PyType_FromSpec(&PLyCursor_spec);
72 [ - + ]: 23 : if (!PLy_CursorType)
5011 peter_e@gmx.net 73 [ # # ]:UBC 0 : elog(ERROR, "could not initialize PLy_CursorType");
5011 peter_e@gmx.net 74 :CBC 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 : :
5011 peter_e@gmx.net 91 :UBC 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 *
5011 peter_e@gmx.net 97 :CBC 14 : PLy_cursor_query(const char *query)
98 : : {
99 : : PLyCursorObject *cursor;
2851 tgl@sss.pgh.pa.us 100 : 14 : PLyExecutionContext *exec_ctx = PLy_current_execution_context();
101 : : volatile MemoryContext oldcontext;
102 : : volatile ResourceOwner oldowner;
103 : :
178 peter@eisentraut.org 104 [ - + ]: 14 : if ((cursor = PyObject_New(PLyCursorObject, PLy_CursorType)) == NULL)
5011 peter_e@gmx.net 105 :UBC 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
5011 peter_e@gmx.net 110 :CBC 14 : cursor->portalname = NULL;
111 : 14 : cursor->closed = false;
3593 tgl@sss.pgh.pa.us 112 : 14 : cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
113 : : "PL/Python cursor context",
114 : : ALLOCSET_DEFAULT_SIZES);
115 : :
116 : : /* Initialize for converting result tuples to Python */
2851 117 : 14 : PLy_input_setup_func(&cursor->result, cursor->mcxt,
118 : : RECORDOID, -1,
119 : 14 : exec_ctx->curr_proc);
120 : :
5011 peter_e@gmx.net 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)
5011 peter_e@gmx.net 135 [ # # ]:UBC 0 : elog(ERROR, "SPI_prepare failed: %s",
136 : : SPI_result_code_string(SPI_result));
137 : :
5011 peter_e@gmx.net 138 :CBC 28 : portal = SPI_cursor_open(NULL, plan, NULL, NULL,
4925 tgl@sss.pgh.pa.us 139 : 14 : exec_ctx->curr_proc->fn_readonly);
5011 peter_e@gmx.net 140 : 14 : SPI_freeplan(plan);
141 : :
142 [ - + ]: 14 : if (portal == NULL)
4988 peter_e@gmx.net 143 [ # # ]:UBC 0 : elog(ERROR, "SPI_cursor_open() failed: %s",
144 : : SPI_result_code_string(SPI_result));
145 : :
3593 tgl@sss.pgh.pa.us 146 :CBC 14 : cursor->portalname = MemoryContextStrdup(cursor->mcxt, portal->name);
147 : :
2825 peter_e@gmx.net 148 : 14 : PinPortal(portal);
149 : :
5011 150 : 14 : PLy_spi_subtransaction_commit(oldcontext, oldowner);
151 : : }
5011 peter_e@gmx.net 152 :UBC 0 : PG_CATCH();
153 : : {
154 : 0 : PLy_spi_subtransaction_abort(oldcontext, oldowner);
155 : 0 : return NULL;
156 : : }
5011 peter_e@gmx.net 157 [ - + ]:CBC 14 : PG_END_TRY();
158 : :
159 [ - + ]: 14 : 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;
2851 tgl@sss.pgh.pa.us 169 : 5 : PLyExecutionContext *exec_ctx = PLy_current_execution_context();
170 : : volatile MemoryContext oldcontext;
171 : : volatile ResourceOwner oldowner;
172 : :
5011 peter_e@gmx.net 173 [ + + ]: 5 : if (args)
174 : : {
1279 andres@anarazel.de 175 [ + - - + ]: 3 : if (!PySequence_Check(args) || PyUnicode_Check(args))
176 : : {
5011 peter_e@gmx.net 177 :UBC 0 : PLy_exception_set(PyExc_TypeError, "plpy.cursor takes a sequence as its second argument");
178 : 0 : return NULL;
179 : : }
5011 peter_e@gmx.net 180 :CBC 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)
5011 peter_e@gmx.net 193 :UBC 0 : PLy_elog(ERROR, "could not execute plan");
1279 andres@anarazel.de 194 :CBC 1 : sv = PLyUnicode_AsString(so);
5011 peter_e@gmx.net 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 : :
178 peter@eisentraut.org 205 [ - + ]: 4 : if ((cursor = PyObject_New(PLyCursorObject, PLy_CursorType)) == NULL)
5011 peter_e@gmx.net 206 :UBC 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
5011 peter_e@gmx.net 211 :CBC 4 : cursor->portalname = NULL;
212 : 4 : cursor->closed = false;
3593 tgl@sss.pgh.pa.us 213 : 4 : cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
214 : : "PL/Python cursor context",
215 : : ALLOCSET_DEFAULT_SIZES);
216 : :
217 : : /* Initialize for converting result tuples to Python */
2851 218 : 4 : PLy_input_setup_func(&cursor->result, cursor->mcxt,
219 : : RECORDOID, -1,
220 : 4 : exec_ctx->curr_proc);
221 : :
5011 peter_e@gmx.net 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 : : */
238 tgl@sss.pgh.pa.us 239 : 4 : tmpcontext = AllocSetContextCreate(CurTransactionContext,
240 : : "PL/Python temporary context",
241 : : ALLOCSET_SMALL_SIZES);
242 : 4 : MemoryContextSwitchTo(tmpcontext);
243 : :
5011 peter_e@gmx.net 244 [ + + ]: 4 : if (nargs > 0)
245 : : {
238 tgl@sss.pgh.pa.us 246 : 2 : values = (Datum *) palloc(nargs * sizeof(Datum));
247 : 2 : nulls = (char *) palloc(nargs * sizeof(char));
248 : : }
249 : : else
250 : : {
251 : 2 : values = NULL;
5011 peter_e@gmx.net 252 : 2 : nulls = NULL;
253 : : }
254 : :
255 [ + + ]: 6 : for (j = 0; j < nargs; j++)
256 : : {
2851 tgl@sss.pgh.pa.us 257 : 2 : PLyObToDatum *arg = &plan->args[j];
258 : : PyObject *elem;
259 : :
5011 peter_e@gmx.net 260 : 2 : elem = PySequence_GetItem(args, j);
1065 drowley@postgresql.o 261 [ + - ]: 2 : PG_TRY(2);
262 : : {
263 : : bool isnull;
264 : :
238 tgl@sss.pgh.pa.us 265 : 2 : values[j] = PLy_output_convert(arg, elem, &isnull);
2851 266 [ - + ]: 2 : nulls[j] = isnull ? 'n' : ' ';
267 : : }
1065 drowley@postgresql.o 268 : 2 : PG_FINALLY(2);
269 : : {
270 : : Py_DECREF(elem);
271 : : }
272 [ - + ]: 2 : PG_END_TRY(2);
273 : : }
274 : :
238 tgl@sss.pgh.pa.us 275 : 4 : MemoryContextSwitchTo(oldcontext);
276 : :
277 : 8 : portal = SPI_cursor_open(NULL, plan->plan, values, nulls,
4925 278 : 4 : exec_ctx->curr_proc->fn_readonly);
5011 peter_e@gmx.net 279 [ - + ]: 4 : if (portal == NULL)
4988 peter_e@gmx.net 280 [ # # ]:UBC 0 : elog(ERROR, "SPI_cursor_open() failed: %s",
281 : : SPI_result_code_string(SPI_result));
282 : :
3593 tgl@sss.pgh.pa.us 283 :CBC 4 : cursor->portalname = MemoryContextStrdup(cursor->mcxt, portal->name);
284 : :
2825 peter_e@gmx.net 285 : 4 : PinPortal(portal);
286 : :
238 tgl@sss.pgh.pa.us 287 : 4 : MemoryContextDelete(tmpcontext);
5011 peter_e@gmx.net 288 : 4 : PLy_spi_subtransaction_commit(oldcontext, oldowner);
289 : : }
5011 peter_e@gmx.net 290 :UBC 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 : : }
5011 peter_e@gmx.net 297 [ - + ]:CBC 4 : PG_END_TRY();
298 : :
299 [ - + ]: 4 : Assert(cursor->portalname != NULL);
300 : 4 : return (PyObject *) cursor;
301 : : }
302 : :
303 : : static void
178 peter@eisentraut.org 304 : 15 : PLy_cursor_dealloc(PLyCursorObject *self)
305 : : {
306 : : #if PY_VERSION_HEX >= 0x03080000
307 : 15 : PyTypeObject *tp = Py_TYPE(self);
308 : : #endif
309 : : Portal portal;
310 : :
311 [ + + ]: 15 : if (!self->closed)
312 : : {
313 : 12 : portal = GetPortalByName(self->portalname);
314 : :
5011 peter_e@gmx.net 315 [ + - ]: 12 : if (PortalIsValid(portal))
316 : : {
2825 317 : 12 : UnpinPortal(portal);
5011 318 : 12 : SPI_cursor_close(portal);
319 : : }
178 peter@eisentraut.org 320 : 12 : self->closed = true;
321 : : }
322 [ + - ]: 15 : if (self->mcxt)
323 : : {
324 : 15 : MemoryContextDelete(self->mcxt);
325 : 15 : self->mcxt = NULL;
326 : : }
327 : :
328 : 15 : 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
5011 peter_e@gmx.net 333 : 15 : }
334 : :
335 : : static PyObject *
336 : 36 : PLy_cursor_iternext(PyObject *self)
337 : : {
338 : : PLyCursorObject *cursor;
339 : : PyObject *ret;
2851 tgl@sss.pgh.pa.us 340 : 36 : PLyExecutionContext *exec_ctx = PLy_current_execution_context();
341 : : volatile MemoryContext oldcontext;
342 : : volatile ResourceOwner oldowner;
343 : : Portal portal;
344 : :
5011 peter_e@gmx.net 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 : : {
5011 peter_e@gmx.net 356 :UBC 0 : PLy_exception_set(PyExc_ValueError,
357 : : "iterating a cursor in an aborted subtransaction");
358 : 0 : return NULL;
359 : : }
360 : :
5011 peter_e@gmx.net 361 :CBC 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 : : {
2851 tgl@sss.pgh.pa.us 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],
2352 peter@eisentraut.org 380 : 26 : SPI_tuptable->tupdesc, true);
381 : : }
382 : :
5011 peter_e@gmx.net 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;
2851 tgl@sss.pgh.pa.us 403 : 13 : PLyExecutionContext *exec_ctx = PLy_current_execution_context();
404 : : volatile MemoryContext oldcontext;
405 : : volatile ResourceOwner oldowner;
406 : : Portal portal;
407 : :
3236 peter_e@gmx.net 408 [ - + ]: 13 : if (!PyArg_ParseTuple(args, "i:fetch", &count))
5011 peter_e@gmx.net 409 :UBC 0 : return NULL;
410 : :
5011 peter_e@gmx.net 411 :CBC 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)
5011 peter_e@gmx.net 429 :UBC 0 : return NULL;
430 : :
5011 peter_e@gmx.net 431 :CBC 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);
1279 andres@anarazel.de 441 : 10 : ret->status = PyLong_FromLong(SPI_OK_FETCH);
442 : :
5011 peter_e@gmx.net 443 : 10 : Py_DECREF(ret->nrows);
2786 444 : 10 : ret->nrows = PyLong_FromUnsignedLongLong(SPI_processed);
445 : :
5011 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 : : */
3465 tgl@sss.pgh.pa.us 455 [ - + ]: 7 : if (SPI_processed > (uint64) PY_SSIZE_T_MAX)
3465 tgl@sss.pgh.pa.us 456 [ # # ]:UBC 0 : ereport(ERROR,
457 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
458 : : errmsg("query result has too many rows to fit in a Python list")));
459 : :
5011 peter_e@gmx.net 460 :CBC 7 : Py_DECREF(ret->rows);
461 : 7 : ret->rows = PyList_New(SPI_processed);
2867 462 [ - + ]: 7 : if (!ret->rows)
463 : : {
464 : : Py_DECREF(ret);
2867 peter_e@gmx.net 465 :UBC 0 : ret = NULL;
466 : : }
467 : : else
468 : : {
2867 peter_e@gmx.net 469 :CBC 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],
2352 peter@eisentraut.org 476 : 37 : SPI_tuptable->tupdesc,
477 : : true);
478 : :
2867 peter_e@gmx.net 479 : 37 : PyList_SetItem(ret->rows, i, row);
480 : : }
481 : : }
482 : : }
483 : :
5011 484 : 10 : SPI_freetuptable(SPI_tuptable);
485 : :
486 : 10 : PLy_spi_subtransaction_commit(oldcontext, oldowner);
487 : : }
5011 peter_e@gmx.net 488 :UBC 0 : PG_CATCH();
489 : : {
490 : 0 : PLy_spi_subtransaction_abort(oldcontext, oldowner);
491 : 0 : return NULL;
492 : : }
5011 peter_e@gmx.net 493 [ - + ]:CBC 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 : : {
4836 bruce@momjian.us 505 : 4 : Portal portal = GetPortalByName(cursor->portalname);
506 : :
5011 peter_e@gmx.net 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 : :
2825 514 : 3 : UnpinPortal(portal);
5011 515 : 3 : SPI_cursor_close(portal);
516 : 3 : cursor->closed = true;
517 : : }
518 : :
2899 519 : 4 : Py_RETURN_NONE;
520 : : }
|