From 7d62391b15845f51c137fa493da43c2c19468c95 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sat, 13 Sep 2025 23:18:13 +0200 Subject: [PATCH] test: add a couple of test implementation for load_rows - `_load_rows_by_row` is a basic row-col loop, kicking in when `_load_rows_page_size` == 1 - `_load_rows_by_row_loop` is a test implementation which is basically a loop over `load_row()`. It kicks in when `_load_rows_page_size` == 0 `_load_rows_by_row` seems adequately fast and suggests that pagination is not really needed. the `_loop` version is about 10% slower. --- psycopg_c/psycopg_c/_psycopg/transform.pyx | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/psycopg_c/psycopg_c/_psycopg/transform.pyx b/psycopg_c/psycopg_c/_psycopg/transform.pyx index 08de3dcdd..05e86cf28 100644 --- a/psycopg_c/psycopg_c/_psycopg/transform.pyx +++ b/psycopg_c/psycopg_c/_psycopg/transform.pyx @@ -426,6 +426,11 @@ cdef class Transformer: return out def load_rows(self, int row0, int row1, object make_row) -> list[Row]: + if self._load_rows_page_size == 1: + return self._load_rows_by_row(row0, row1, make_row) + if self._load_rows_page_size == 0: + return self._load_rows_by_row_loop(row0, row1, make_row) + if self._pgresult is None: raise e.InterfaceError("result not set") @@ -504,6 +509,78 @@ cdef class Transformer: Py_DECREF(brecord) return records + def _load_rows_by_row_loop(self, int row0, int row1, object make_row) -> list[Row]: + + cdef int row + cdef object record + cdef object records = PyList_New(row1 - row0) + + for row in range(row0, row1): + record = self.load_row(row, make_row) + Py_INCREF(record) + PyList_SET_ITEM(records, row - row0, record) + + return records + + def _load_rows_by_row(self, int row0, int row1, object make_row) -> list[Row]: + if self._pgresult is None: + raise e.InterfaceError("result not set") + + if not (0 <= row0 <= self._ntuples and 0 <= row1 <= self._ntuples): + raise e.InterfaceError( + f"rows must be included between 0 and {self._ntuples}" + ) + + if self._load_rows_page_size <= 0: + raise ValueError(f"_load_rows_page_size must be positive") + + cdef libpq.PGresult *res = self._pgresult._pgresult_ptr + # cheeky access to the internal PGresult structure + cdef pg_result_int *ires = res + + cdef int row, col + cdef PGresAttValue *attval + cdef object record + + cdef object records = PyList_New(row1 - row0) + + cdef PyObject *loader # borrowed RowLoader + + row_loaders = self._row_loaders # avoid an incref/decref per item + + for row in range(row0, row1): + # Ownership of 'brecord' transferred to 'records' + # so brecord is borrowed. + record = PyTuple_New(self._nfields) + Py_INCREF(record) + PyList_SET_ITEM(records, row - row0, record) + + for col in range(self._nfields): + loader = PyList_GET_ITEM(row_loaders, col) + attval = &(ires.tuples[row][col]) + if attval.len == -1: # NULL_LEN + pyval = None + continue + if (loader).cloader is not None: + pyval = (loader).cloader.cload( + attval.value, attval.len) + else: + b = PyMemoryView_FromObject( + ViewBuffer._from_buffer( + self._pgresult, + attval.value, attval.len)) + pyval = PyObject_CallFunctionObjArgs( + (loader).loadfunc, b, NULL) + + Py_INCREF(pyval) + PyTuple_SET_ITEM(record, col, pyval) + + if make_row is not tuple: + record = PyObject_CallFunctionObjArgs(make_row, record, NULL) + PyList_SET_ITEM(records, row - row0, record) + + return records + def load_row(self, int row, object make_row) -> Row: if self._pgresult is None: raise e.InterfaceError("result not set") -- 2.47.3