Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 41 additions & 24 deletions pynuodb/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,49 +184,66 @@ 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")
Comment on lines -187 to +191

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this buying us enough to justify duplicating the code? I know method calls in python are slower but IMO we need to weigh the overhead of supporting multiple copies of the same code and the possibilities for behavior divergence.

My opinion is that no one who chooses to use the python driver is going to be fussed about an extra 50 nanoseconds of speed or whatever, even in a per-row basis. And if they do care about it then we should be working on using a compiled solution rather than a native python solution.

The change below to fetchall() is great. But to me this one feels like chasing cycles at the expense of code maintainability and IMO that's not the way we should be leaning, for a Python implementation.

if self._result_set is None:
raise Error("Previous execute did not produce any results or no call was issued yet")
self.rownumber += 1
if not self._result_set.is_complete():
self.session.fetch_result_set_next(self._result_set)
return self._result_set.fetchone()

def _drain(self, limit):
# type: (Optional[int]) -> List[result_set.Row]
"""Drain rows from the current result set.

:param limit: maximum rows to return; None means "no cap".
"""
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]
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
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.
"""
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
return self._drain(size)

def fetchall(self):
# type: () -> List[result_set.Row]
"""Return all rows generated by the previous SQL operation."""
self._check_closed()

fetched_rows = []
while True:
row = self.fetchone()
if row is None:
break
else:
fetched_rows.append(row)
return fetched_rows
return self._drain(None)

def nextset(self): # pylint: disable=no-self-use
# type: () -> None
Expand Down