Description
A mypyc-compiled SQLSpec installation segfaults when the async aiosqlite driver encounters an ordinary SQLite statement error. The equivalent interpreted SQLSpec installation correctly raises sqlspec.exceptions.SQLSpecError.
This is not limited to result conversion: both driver.select() and driver.execute() reproduce the crash.
Environment
- SQLSpec: 0.58.1 with the
mypyc extra
- Python: 3.10.18
- aiosqlite: 0.22.1
- SQLite: 3.50.4
- Platform: Linux x86_64, kernel 7.0.0-28-generic
Compiled modules involved:
sqlspec/driver/_async.cpython-310-x86_64-linux-gnu.so
sqlspec/driver/_exception_handler.cpython-310-x86_64-linux-gnu.so
The aiosqlite adapter driver itself is interpreted Python.
Minimal reproducer
import asyncio
import tempfile
from sqlspec import SQLSpec
from sqlspec.adapters.aiosqlite import AiosqliteConfig
async def main() -> None:
with tempfile.NamedTemporaryFile(suffix=".db") as database:
config = AiosqliteConfig(
connection_config={"database": database.name},
)
registry = SQLSpec()
registry.add_config(config)
async with registry.provide_session(config) as driver:
await driver.execute(
"CREATE TABLE example (id INTEGER PRIMARY KEY)"
)
await driver.select(
"SELECT missing_column FROM example"
)
asyncio.run(main())
Actual behavior
With a mypyc-compiled SQLSpec installation:
Segmentation fault
exit code 139
The final Python frame is AsyncDriverAdapterBase._check_pending_exception() in sqlspec/driver/_async.py.
The call path is:
AsyncDriverAdapterBase.select()
-> execute()
-> dispatch_statement_execution()
-> _check_pending_exception()
Expected behavior
The interpreted installation raises:
sqlspec.exceptions.SQLSpecError:
AIOSQLite database error: no such column: missing_column
Comparison and suspected boundary
Running the reproducer against the interpreted SQLSpec 0.58.1 source tree produces the expected SQLSpecError and exits normally with status 1. The compiled synchronous SQLite adapter also raises SQLSpecError correctly, so the defect appears specific to compiled asynchronous dispatch.
dispatch_statement_execution() uses a deferred exception handler:
exc_handler = self.handle_database_exceptions()
async with exc_handler, self.with_cursor(connection) as cursor:
...
self._check_pending_exception(exc_handler)
The handler stores the mapped exception in pending_exception, which is raised outside __aexit__. A manually constructed handler and exception do not segfault; the crash requires the full compiled async driver path with the original exception coming from an awaited aiosqlite operation. This suggests a mypyc lifetime or exception-state problem around the combined async context-manager dispatch.
Additional observations
- Replacing
select() with execute() still exits with status 139.
- The equivalent synchronous SQLite operation does not crash.
- PyArrow is not required or loaded by the minimal reproducer.
- The relevant exception-dispatch code remains structurally unchanged on current
origin/main at version 0.58.3, although this reproduction used the installed 0.58.1 compiled package.
- A regression test should run invalid SQL in a subprocess against a compiled installation and assert normal exception termination rather than a signal, so a regression cannot kill the main pytest process.
Description
A mypyc-compiled SQLSpec installation segfaults when the async
aiosqlitedriver encounters an ordinary SQLite statement error. The equivalent interpreted SQLSpec installation correctly raisessqlspec.exceptions.SQLSpecError.This is not limited to result conversion: both
driver.select()anddriver.execute()reproduce the crash.Environment
mypycextraCompiled modules involved:
sqlspec/driver/_async.cpython-310-x86_64-linux-gnu.sosqlspec/driver/_exception_handler.cpython-310-x86_64-linux-gnu.soThe
aiosqliteadapter driver itself is interpreted Python.Minimal reproducer
Actual behavior
With a mypyc-compiled SQLSpec installation:
The final Python frame is
AsyncDriverAdapterBase._check_pending_exception()insqlspec/driver/_async.py.The call path is:
Expected behavior
The interpreted installation raises:
Comparison and suspected boundary
Running the reproducer against the interpreted SQLSpec 0.58.1 source tree produces the expected
SQLSpecErrorand exits normally with status 1. The compiled synchronous SQLite adapter also raisesSQLSpecErrorcorrectly, so the defect appears specific to compiled asynchronous dispatch.dispatch_statement_execution()uses a deferred exception handler:The handler stores the mapped exception in
pending_exception, which is raised outside__aexit__. A manually constructed handler and exception do not segfault; the crash requires the full compiled async driver path with the original exception coming from an awaitedaiosqliteoperation. This suggests a mypyc lifetime or exception-state problem around the combined async context-manager dispatch.Additional observations
select()withexecute()still exits with status 139.origin/mainat version 0.58.3, although this reproduction used the installed 0.58.1 compiled package.