Repository navigation
Share the buffer refill between fetchone() and the bulk fetches of the result sets #1195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -459,14 +459,18 @@ def _get_query_results(self, max_results: int, next_token: str | None = None) -> | |
| return cast(dict[str, Any], response) | ||
|
|
||
| def _fetch(self) -> None: | ||
| """Fetch the next page of rows into the result set. | ||
| """Fetch the next rows, if any, into the buffer. | ||
|
|
||
| The default result set fetches the next page; it fetches nothing when no | ||
| page remains, as after ``close()``. | ||
|
|
||
| Raises: | ||
| ProgrammingError: If there is no next page. | ||
| ProgrammingError: If the next page is requested and the query ID is | ||
| missing, the query has not succeeded, or the result set is closed. | ||
| OperationalError: If the request fails. | ||
| """ | ||
| if not self._next_token: | ||
| raise ProgrammingError("NextToken is none or empty.") | ||
| return | ||
| response = self._get_query_results(self._arraysize, self._next_token) | ||
| rows, self._next_token = self._parse_result_rows(response) | ||
| self._process_rows(rows) | ||
|
|
@@ -490,10 +494,10 @@ def _pre_fetch(self) -> None: | |
| def fetchone( | ||
| self, | ||
| ) -> tuple[Any | None, ...] | dict[Any, Any | None] | None: | ||
| if not self._rows and self._next_token: | ||
| self._fetch() | ||
| if not self._rows: | ||
| return None | ||
| self._fetch() | ||
| if not self._rows: | ||
| return None | ||
| row = self._rows.popleft() | ||
| # As _count_rows() does, inline on the per-row path. | ||
| rownumber = self._rownumber | ||
|
|
@@ -536,9 +540,9 @@ def fetchall( | |
| def _take_rows(self, size: int | None) -> list[tuple[Any | None, ...] | dict[Any, Any | None]]: | ||
| """Take the next rows and count them in ``rownumber``. | ||
|
|
||
| The buffered rows are taken in bulk. When the buffer is empty, ``fetchone()`` | ||
| takes the next row, so that it fetches the next rows into the buffer as the | ||
| result set does. | ||
| The buffered rows are taken in bulk, and ``_fetch()`` refills the buffer when | ||
| it is empty. A subclass whose ``fetchone()`` does not use the buffer overrides | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Self-review round two (claims, compatibility, operations): FINDINGS, repaired in the PR description Scope: Claims checked:
Existing callers: dbt-athena ( AWS operator: no change to retries or request counts. An exhausted or closed result set now calls Corrections to the PR description:
Limits: the full |
||
| this method too. | ||
|
|
||
| Args: | ||
| size: The maximum number of rows, or None for all remaining rows. | ||
|
|
@@ -548,13 +552,11 @@ def _take_rows(self, size: int | None) -> list[tuple[Any | None, ...] | dict[Any | |
| """ | ||
| rows: list[tuple[Any | None, ...] | dict[Any, Any | None]] = [] | ||
| while size is None or len(rows) < size: | ||
| if self._rows: | ||
| self._take_buffered_rows(rows, size) | ||
| else: | ||
| row = self.fetchone() | ||
| if row is None: | ||
| if not self._rows: | ||
| self._fetch() | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Self-review round one (implementation behavior): FINDINGS, repaired Scope: Verified unchanged: the rows, Repairs in 0100221:
Deferred with reasons:
|
||
| if not self._rows: | ||
| break | ||
| rows.append(row) | ||
| self._take_buffered_rows(rows, size) | ||
| return rows | ||
|
|
||
| def _take_buffered_rows( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Independent review (relayed), static only: FINDINGS
Reviewer: Codex CLI 0.162.1 (
codex exec -s read-only), model gpt-6-astra, session01a14031-fbf3-7883-941a-4fbeed65dd05. Scope:git diff 06224494788043a4887afcb112a96c94bbcb02f1..0100221287e8329c273967a3b1923c2ce9dce287on a detached snapshot at 0100221. The prompt left out the PR description and the prior findings, and allowed no edits, builds, tests, GitHub, or network. The snapshot was unchanged afterwards.Coverage: base, dict, and asyncio result sets (including the
AthenaAioDictResultSetMRO) and all three fetch methods; the Arrow/S3FS refill; the pandas/Polars bulk overrides; the sync, futures-based, and aio cursor callers; request counts, failure accounting,rownumber, close and concurrent close, docstrings, and tests.Findings:
pyathena/aio/result_set.py:158andpyathena/result_set.py:475. Ifclose()completes during a page request and the response carries anotherNextToken, the response restores that token. The cleared metadata keeps rows out of the buffer, but the next fetch requests again and raisesProgrammingErrorfor the missing query ID. The reviewer confirmed that the base commit has the same defect.→ Deferred as pre-existing and outside Share the buffer refill between fetchone() and the bulk fetches of the result sets #1193. The old
fetchone()hit the same request through its_next_tokencheck. A follow-up issue is proposed to the maintainer.Raises: ProgrammingErrortext on_fetch()/_async_fetch()said a closed result set raises, butclose(); _fetch()returns without error because the token is cleared.→ Verified and repaired in 6cd9b59 (reply below).
The reviewer found no other functional regression and judged that the tests exercise page/batch refills, exhaustion, request counts, and partial-consumption accounting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Repair of finding 2: 6cd9b59 (docstrings only, base and aio).
Raises: ProgrammingErrornow reads "If the next page is requested and the query ID is missing, the query has not succeeded, or the result set is closed."just lintpassed, andtests/pyathena/test_result_set.pyplustests/pyathena/aio/test_result_set.pypassed (27)._build_get_query_results_request()or the closed check in_get_query_results()/_async_get_query_results(), and both run only when a_next_tokenremains.codex exec -s read-only, session01a14038-971d-7491-896b-0f610f70ec86) reviewedgit diff 0100221287e8329c273967a3b1923c2ce9dce287..6cd9b5962dcc93ac668eff417e137641aef5e0c4on a detached snapshot and returned CLEAN: the repair resolves the finding, describes both methods accurately, and adds nothing new. This was a static review.