Skip to content

Commit e531964

Browse files
feat(cursor): add optional callproc/nextset/scroll methods
PEP 249 specifies these three optional cursor methods. When unsupported, drivers must raise NotSupportedError (not AttributeError). Add stubs on both sync and async cursors that raise with a clear message. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a5e7ee7 commit e531964

File tree

4 files changed

+93
-1
lines changed

4 files changed

+93
-1
lines changed

src/dqlitedbapi/aio/cursor.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
_convert_row,
1010
_strip_leading_comments,
1111
)
12-
from dqlitedbapi.exceptions import InterfaceError, ProgrammingError
12+
from dqlitedbapi.exceptions import InterfaceError, NotSupportedError, ProgrammingError
1313

1414
if TYPE_CHECKING:
1515
from dqlitedbapi.aio.connection import AsyncConnection
@@ -213,6 +213,20 @@ def setoutputsize(self, size: int, column: int | None = None) -> None:
213213
"""Set output size (no-op for dqlite)."""
214214
pass
215215

216+
async def callproc(
217+
self, procname: str, parameters: Sequence[Any] | None = None
218+
) -> Sequence[Any] | None:
219+
"""PEP 249 optional extension — not supported."""
220+
raise NotSupportedError("dqlite does not support stored procedures")
221+
222+
def nextset(self) -> bool | None:
223+
"""PEP 249 optional extension — not supported."""
224+
raise NotSupportedError("dqlite does not support multiple result sets")
225+
226+
def scroll(self, value: int, mode: str = "relative") -> None:
227+
"""PEP 249 optional extension — not supported."""
228+
raise NotSupportedError("dqlite cursors are not scrollable")
229+
216230
def __repr__(self) -> str:
217231
state = "closed" if self._closed else "open"
218232
return f"<AsyncCursor rowcount={self._rowcount} {state}>"

src/dqlitedbapi/cursor.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from dqlitedbapi.exceptions import (
88
DataError,
99
InterfaceError,
10+
NotSupportedError,
1011
OperationalError,
1112
ProgrammingError,
1213
)
@@ -338,6 +339,30 @@ def setoutputsize(self, size: int, column: int | None = None) -> None:
338339
"""Set output size (no-op for dqlite)."""
339340
pass
340341

342+
def callproc(
343+
self, procname: str, parameters: Sequence[Any] | None = None
344+
) -> Sequence[Any] | None:
345+
"""PEP 249 optional extension — not supported.
346+
347+
dqlite (and SQLite) have no stored-procedure concept.
348+
"""
349+
raise NotSupportedError("dqlite does not support stored procedures")
350+
351+
def nextset(self) -> bool | None:
352+
"""PEP 249 optional extension — not supported.
353+
354+
dqlite's wire protocol does not return multiple result sets.
355+
"""
356+
raise NotSupportedError("dqlite does not support multiple result sets")
357+
358+
def scroll(self, value: int, mode: str = "relative") -> None:
359+
"""PEP 249 optional extension — not supported.
360+
361+
The dqlite cursor is forward-only; rows are buffered from a
362+
streamed wire response.
363+
"""
364+
raise NotSupportedError("dqlite cursors are not scrollable")
365+
341366
def __repr__(self) -> str:
342367
state = "closed" if self._closed else "open"
343368
return f"<Cursor rowcount={self._rowcount} {state}>"

tests/test_async_cursor.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,30 @@ def test_setoutputsize_noop(self) -> None:
108108
conn = AsyncConnection("localhost:9001")
109109
cursor = AsyncCursor(conn)
110110
cursor.setoutputsize(100, 0)
111+
112+
113+
class TestOptionalAsyncCursorMethodsRaise:
114+
@pytest.mark.asyncio
115+
async def test_callproc_raises_not_supported(self) -> None:
116+
from dqlitedbapi.exceptions import NotSupportedError
117+
118+
conn = AsyncConnection("localhost:9001")
119+
cursor = AsyncCursor(conn)
120+
with pytest.raises(NotSupportedError):
121+
await cursor.callproc("some_proc")
122+
123+
def test_nextset_raises_not_supported(self) -> None:
124+
from dqlitedbapi.exceptions import NotSupportedError
125+
126+
conn = AsyncConnection("localhost:9001")
127+
cursor = AsyncCursor(conn)
128+
with pytest.raises(NotSupportedError):
129+
cursor.nextset()
130+
131+
def test_scroll_raises_not_supported(self) -> None:
132+
from dqlitedbapi.exceptions import NotSupportedError
133+
134+
conn = AsyncConnection("localhost:9001")
135+
cursor = AsyncCursor(conn)
136+
with pytest.raises(NotSupportedError):
137+
cursor.scroll(0)

tests/test_cursor.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,29 @@ def test_setoutputsize_noop(self) -> None:
9999
cursor = Cursor(conn)
100100
# Should not raise
101101
cursor.setoutputsize(100, 0)
102+
103+
104+
class TestOptionalCursorMethodsRaise:
105+
def test_callproc_raises_not_supported(self) -> None:
106+
from dqlitedbapi.exceptions import NotSupportedError
107+
108+
conn = Connection("localhost:9001")
109+
cursor = Cursor(conn)
110+
with pytest.raises(NotSupportedError):
111+
cursor.callproc("some_proc")
112+
113+
def test_nextset_raises_not_supported(self) -> None:
114+
from dqlitedbapi.exceptions import NotSupportedError
115+
116+
conn = Connection("localhost:9001")
117+
cursor = Cursor(conn)
118+
with pytest.raises(NotSupportedError):
119+
cursor.nextset()
120+
121+
def test_scroll_raises_not_supported(self) -> None:
122+
from dqlitedbapi.exceptions import NotSupportedError
123+
124+
conn = Connection("localhost:9001")
125+
cursor = Cursor(conn)
126+
with pytest.raises(NotSupportedError):
127+
cursor.scroll(0)

0 commit comments

Comments
 (0)