Skip to content

Commit dc45f95

Browse files
Add executescript and interrupt NotSupportedError stubs across sync + async surfaces
stdlib ``sqlite3.Connection`` and ``sqlite3.Cursor`` both expose ``executescript``; ``sqlite3.Connection`` also exposes ``interrupt``. aiosqlite mirrors all three on the async surface. Cross-driver code that ports from stdlib / aiosqlite hit bare ``AttributeError`` — escapes ``except dbapi.Error:`` and the SA dialect cannot translate. Add ``NotSupportedError`` stubs (NOT real implementations) on all four surfaces (sync Connection / sync Cursor / async Connection / async Cursor) so the failure stays in the dbapi error hierarchy. Same shape as the existing TPC / load_extension / backup / iterdump / create_* stubs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6b9bb56 commit dc45f95

4 files changed

Lines changed: 71 additions & 0 deletions

File tree

src/dqlitedbapi/aio/connection.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,23 @@ async def tpc_recover(self) -> NoReturn:
952952
def xid(self, format_id: int, global_transaction_id: str, branch_qualifier: str) -> NoReturn:
953953
raise NotSupportedError("dqlite does not support two-phase commit")
954954

955+
async def executescript(self, sql_script: str) -> NoReturn:
956+
"""stdlib ``sqlite3``-parity stub. dqlite has no
957+
multi-statement-script primitive on the wire; raises
958+
``NotSupportedError`` rather than escaping ``dbapi.Error``
959+
as ``AttributeError``. Mirrors the sync sibling."""
960+
raise NotSupportedError(
961+
"dqlite does not support stdlib sqlite3 executescript; "
962+
"split the script and execute each statement individually"
963+
)
964+
965+
def interrupt(self) -> NoReturn:
966+
"""stdlib ``sqlite3``-parity stub. See sync sibling."""
967+
raise NotSupportedError(
968+
"dqlite does not surface interrupt() at the dbapi layer; "
969+
"use asyncio.timeout(...) or rely on the per-RPC timeout"
970+
)
971+
955972
def enable_load_extension(self, enabled: bool) -> NoReturn:
956973
raise NotSupportedError("dqlite-server does not support runtime extension loading")
957974

src/dqlitedbapi/aio/cursor.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,19 @@ def scroll(self, value: int, mode: str = "relative") -> None:
662662
self._connection._check_loop_binding()
663663
raise NotSupportedError("dqlite cursors are not scrollable")
664664

665+
async def executescript(self, sql_script: str) -> NoReturn:
666+
"""stdlib ``sqlite3.Cursor``-parity stub. See sync sibling."""
667+
del self.messages[:]
668+
conn_messages = getattr(self._connection, "messages", None)
669+
if conn_messages is not None:
670+
del conn_messages[:]
671+
self._check_closed()
672+
self._connection._check_loop_binding()
673+
raise NotSupportedError(
674+
"dqlite does not support stdlib sqlite3 executescript; "
675+
"split the script and execute each statement individually"
676+
)
677+
665678
def __repr__(self) -> str:
666679
state = "closed" if self._closed else "open"
667680
# Include the parent connection's address and ``id(self)`` so

src/dqlitedbapi/connection.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,30 @@ def tpc_recover(self) -> NoReturn:
14071407
def xid(self, format_id: int, global_transaction_id: str, branch_qualifier: str) -> NoReturn:
14081408
raise NotSupportedError("dqlite does not support two-phase commit")
14091409

1410+
def executescript(self, sql_script: str) -> NoReturn:
1411+
"""stdlib ``sqlite3``-parity stub. dqlite has no
1412+
multi-statement-script primitive on the wire (each statement
1413+
requires a separate Prepare → Exec / Query round-trip), so
1414+
this raises ``NotSupportedError`` rather than escaping
1415+
``dbapi.Error`` as ``AttributeError``. Callers should split
1416+
the script and ``execute`` each statement individually."""
1417+
raise NotSupportedError(
1418+
"dqlite does not support stdlib sqlite3 executescript; "
1419+
"split the script and execute each statement individually"
1420+
)
1421+
1422+
def interrupt(self) -> NoReturn:
1423+
"""stdlib ``sqlite3``-parity stub. dqlite's wire-level
1424+
interrupt primitive is not surfaced at the dbapi layer in
1425+
this driver. Callers needing cross-thread cancellation
1426+
should wrap calls in ``asyncio.timeout`` (async surface)
1427+
or rely on the configured per-RPC timeout."""
1428+
raise NotSupportedError(
1429+
"dqlite does not surface interrupt() at the dbapi layer; "
1430+
"use asyncio.timeout(...) on the async surface or rely "
1431+
"on the per-RPC timeout"
1432+
)
1433+
14101434
def enable_load_extension(self, enabled: bool) -> NoReturn:
14111435
raise NotSupportedError("dqlite-server does not support runtime extension loading")
14121436

src/dqlitedbapi/cursor.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,6 +1395,23 @@ def scroll(self, value: int, mode: str = "relative") -> None:
13951395
self._check_closed()
13961396
raise NotSupportedError("dqlite cursors are not scrollable")
13971397

1398+
def executescript(self, sql_script: str) -> NoReturn:
1399+
"""stdlib ``sqlite3.Cursor``-parity stub. dqlite has no
1400+
multi-statement-script primitive on the wire; raises
1401+
``NotSupportedError`` rather than escaping ``dbapi.Error``
1402+
as ``AttributeError``. Same shape as the
1403+
``Connection.executescript`` stub."""
1404+
del self.messages[:]
1405+
conn_messages = getattr(self._connection, "messages", None)
1406+
if conn_messages is not None:
1407+
del conn_messages[:]
1408+
self._connection._check_thread()
1409+
self._check_closed()
1410+
raise NotSupportedError(
1411+
"dqlite does not support stdlib sqlite3 executescript; "
1412+
"split the script and execute each statement individually"
1413+
)
1414+
13981415
def __repr__(self) -> str:
13991416
state = "closed" if self._closed else "open"
14001417
# Include the parent connection's address and ``id(self)`` so

0 commit comments

Comments
 (0)