]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
test: add a couple of test implementation for load_rows fix/rows-load-order-3.2
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 13 Sep 2025 21:18:13 +0000 (23:18 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 13 Sep 2025 21:18:13 +0000 (23:18 +0200)
- `_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

index 08de3dcdd4861510bc04bf418d5f7293eab5c46d..05e86cf28b4452525ed9353773b7e0d186c4ce2f 100644 (file)
@@ -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(<object>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 = <pg_result_int*>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 (<RowLoader>loader).cloader is not None:
+                    pyval = (<RowLoader>loader).cloader.cload(
+                        attval.value, attval.len)
+                else:
+                    b = PyMemoryView_FromObject(
+                        ViewBuffer._from_buffer(
+                            self._pgresult,
+                            <unsigned char *>attval.value, attval.len))
+                    pyval = PyObject_CallFunctionObjArgs(
+                        (<RowLoader>loader).loadfunc, <PyObject *>b, NULL)
+
+                Py_INCREF(pyval)
+                PyTuple_SET_ITEM(record, col, pyval)
+
+            if make_row is not tuple:
+                record = PyObject_CallFunctionObjArgs(make_row, <PyObject *>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")