Skip to content

Commit 319b148

Browse files
Reject negative size in AsyncAdaptedCursor.fetchmany
Both sibling cursors in dqlitedbapi (Cursor, AsyncCursor) raise ProgrammingError on a negative size per ISSUE-82 and ISSUE-121. The SQLAlchemy adapter was overlooked and silently returned [] under min(size, len(rows)) for size<0. Surface the same ProgrammingError so a caller bug (e.g. off-by-one on size) fails loudly and the stack's cursors behave uniformly.
1 parent 8f70c48 commit 319b148

2 files changed

Lines changed: 43 additions & 1 deletion

File tree

src/sqlalchemydqlite/aio.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@
1313
from sqlalchemy.util import await_only
1414

1515
from dqliteclient.exceptions import DqliteConnectionError
16-
from dqlitedbapi.exceptions import InterfaceError, NotSupportedError, OperationalError
16+
from dqlitedbapi.exceptions import (
17+
InterfaceError,
18+
NotSupportedError,
19+
OperationalError,
20+
ProgrammingError,
21+
)
1722
from sqlalchemydqlite.base import DqliteDialect
1823

1924
logger = logging.getLogger(__name__)
@@ -136,6 +141,8 @@ def fetchone(self) -> Any:
136141
def fetchmany(self, size: int | None = None) -> Sequence[Any]:
137142
if size is None:
138143
size = self.arraysize
144+
if size < 0:
145+
raise ProgrammingError(f"fetchmany size must be non-negative, got {size}")
139146
return [self._rows.popleft() for _ in range(min(size, len(self._rows)))]
140147

141148
def fetchall(self) -> Sequence[Any]:
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""AsyncAdaptedCursor.fetchmany must reject negative size, matching
2+
the PEP-249 contract enforced by the two sibling dqlitedbapi cursors
3+
(ISSUE-82 and ISSUE-121).
4+
"""
5+
6+
from __future__ import annotations
7+
8+
from collections import deque
9+
from unittest.mock import MagicMock
10+
11+
import pytest
12+
13+
from dqlitedbapi.exceptions import ProgrammingError
14+
from sqlalchemydqlite.aio import AsyncAdaptedCursor
15+
16+
17+
def test_fetchmany_negative_size_raises_programming_error() -> None:
18+
conn = MagicMock()
19+
cur = AsyncAdaptedCursor(conn)
20+
cur._rows = deque([("a",), ("b",)])
21+
22+
with pytest.raises(ProgrammingError, match="must be non-negative"):
23+
cur.fetchmany(-1)
24+
25+
# Rows must not have been consumed on error.
26+
assert len(cur._rows) == 2
27+
28+
29+
def test_fetchmany_zero_size_returns_empty() -> None:
30+
"""Existing behaviour: size=0 returns []."""
31+
conn = MagicMock()
32+
cur = AsyncAdaptedCursor(conn)
33+
cur._rows = deque([("a",)])
34+
assert list(cur.fetchmany(0)) == []
35+
assert len(cur._rows) == 1

0 commit comments

Comments
 (0)