From e934fa6a6eff4f427c9a5ab663d6e2e77486aaa0 Mon Sep 17 00:00:00 2001 From: Martin Gallwey Date: Mon, 29 Jun 2026 18:12:27 +0100 Subject: [PATCH 1/2] Optimize result fetching via block-draining and check inlining Accelerate data retrieval pathways in the Cursor class by optimizing row iteration and reducing interpreter overhead on high-frequency method loops. Detailed changes: - fetchone: Inline the `_check_closed()` state verification directly into the method body. This removes function-call stack overhead for every individual row retrieved. - fetchall: Refactor the loop to slice and drain the current in-memory result batch in bulk using `list.extend()` instead of sequentially invoking `fetchone()` row by row. - fetchall: Directly request the next network payload batch via `fetch_result_set_next` once the in-memory collection is exhausted, updating total `rownumber` tracking as a single block operation. - fetchall: Add a defensive guard clause against unallocated result sets to ensure strict DB-API compatibility. --- pynuodb/cursor.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/pynuodb/cursor.py b/pynuodb/cursor.py index 4e515f5..f944518 100644 --- a/pynuodb/cursor.py +++ b/pynuodb/cursor.py @@ -184,7 +184,11 @@ def executemany(self, operation, seq_of_parameters): def fetchone(self): # type: () -> Optional[result_set.Row] """Return the next row of results from the previous SQL operation.""" - self._check_closed() + # Inline _check_closed to avoid per-row function-call overhead. + if self.closed: + raise Error("cursor is closed") + if self.session.closed: + raise Error("connection is closed") if self._result_set is None: raise Error("Previous execute did not produce any results or no call was issued yet") self.rownumber += 1 @@ -218,14 +222,22 @@ def fetchall(self): # type: () -> List[result_set.Row] """Return all rows generated by the previous SQL operation.""" self._check_closed() + if self._result_set is None: + raise Error("Previous execute did not produce any results or no call was issued yet") - fetched_rows = [] + fetched_rows = [] # type: List[result_set.Row] while True: - row = self.fetchone() - if row is None: + # Drain the current in-memory batch in one shot instead of calling + # fetchone() per row. + idx = self._result_set.results_idx + batch = self._result_set.results + if idx < len(batch): + fetched_rows.extend(batch[idx:]) + self._result_set.results_idx = len(batch) + if self._result_set.complete: break - else: - fetched_rows.append(row) + self.session.fetch_result_set_next(self._result_set) + self.rownumber += len(fetched_rows) return fetched_rows def nextset(self): # pylint: disable=no-self-use From 8fe09f5484afa52223de919ec96eeb6dad8f963d Mon Sep 17 00:00:00 2001 From: Martin Gallwey Date: Wed, 1 Jul 2026 14:56:23 +0100 Subject: [PATCH 2/2] merge fetchmany and fetchall --- pynuodb/cursor.py | 61 +++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/pynuodb/cursor.py b/pynuodb/cursor.py index f944518..ed28d7c 100644 --- a/pynuodb/cursor.py +++ b/pynuodb/cursor.py @@ -196,50 +196,55 @@ def fetchone(self): self.session.fetch_result_set_next(self._result_set) return self._result_set.fetchone() - def fetchmany(self, size=None): + def _drain(self, limit): # type: (Optional[int]) -> List[result_set.Row] - """Return SIZE rows from the previous SQL operation. + """Drain rows from the current result set. - If size is None, uses the default size for this Cursor. + :param limit: maximum rows to return; None means "no cap". """ self._check_closed() - - if size is None: - size = self.arraysize - - fetched_rows = [] - num_fetched_rows = 0 - while num_fetched_rows < size: - row = self.fetchone() - if row is None: - break - else: - fetched_rows.append(row) - num_fetched_rows += 1 - return fetched_rows - - def fetchall(self): - # type: () -> List[result_set.Row] - """Return all rows generated by the previous SQL operation.""" - self._check_closed() if self._result_set is None: raise Error("Previous execute did not produce any results or no call was issued yet") fetched_rows = [] # type: List[result_set.Row] - while True: - # Drain the current in-memory batch in one shot instead of calling - # fetchone() per row. + remaining = limit # None == unlimited + while remaining is None or remaining > 0: + # Drain the current in-memory batch in one shot instead of + # calling fetchone() per row. idx = self._result_set.results_idx batch = self._result_set.results - if idx < len(batch): - fetched_rows.extend(batch[idx:]) - self._result_set.results_idx = len(batch) + avail = len(batch) - idx + if avail > 0: + if remaining is None: + take = avail + else: + take = avail if avail < remaining else remaining + remaining -= take + fetched_rows.extend(batch[idx:idx + take]) + self._result_set.results_idx = idx + take + if remaining == 0: + break if self._result_set.complete: break self.session.fetch_result_set_next(self._result_set) self.rownumber += len(fetched_rows) return fetched_rows + def fetchmany(self, size=None): + # type: (Optional[int]) -> List[result_set.Row] + """Return SIZE rows from the previous SQL operation. + + If size is None, uses the default size for this Cursor. + """ + if size is None: + size = self.arraysize + return self._drain(size) + + def fetchall(self): + # type: () -> List[result_set.Row] + """Return all rows generated by the previous SQL operation.""" + return self._drain(None) + def nextset(self): # pylint: disable=no-self-use # type: () -> None """Not supported."""