1212from sqlspec .core .parameters import ParameterStyle , ParameterStyleConfig
1313from sqlspec .core .statement import StatementConfig
1414from sqlspec .driver import AsyncDriverAdapterBase
15- from sqlspec .exceptions import SQLParsingError , SQLSpecError
15+ from sqlspec .exceptions import (
16+ CheckViolationError ,
17+ DatabaseConnectionError ,
18+ DataError ,
19+ ForeignKeyViolationError ,
20+ IntegrityError ,
21+ NotNullViolationError ,
22+ OperationalError ,
23+ SQLParsingError ,
24+ SQLSpecError ,
25+ UniqueViolationError ,
26+ )
1627from sqlspec .utils .serializers import to_json
1728
1829if TYPE_CHECKING :
2637
2738__all__ = ("AiosqliteCursor" , "AiosqliteDriver" , "AiosqliteExceptionHandler" , "aiosqlite_statement_config" )
2839
40+ SQLITE_CONSTRAINT_UNIQUE_CODE = 2067
41+ SQLITE_CONSTRAINT_FOREIGNKEY_CODE = 787
42+ SQLITE_CONSTRAINT_NOTNULL_CODE = 1811
43+ SQLITE_CONSTRAINT_CHECK_CODE = 531
44+ SQLITE_CONSTRAINT_CODE = 19
45+ SQLITE_CANTOPEN_CODE = 14
46+ SQLITE_IOERR_CODE = 10
47+ SQLITE_MISMATCH_CODE = 20
48+
2949
3050aiosqlite_statement_config = StatementConfig (
3151 dialect = "sqlite" ,
@@ -74,7 +94,11 @@ async def __aexit__(self, *_: Any) -> None:
7494
7595
7696class AiosqliteExceptionHandler :
77- """Async context manager for AIOSQLite database exceptions."""
97+ """Async context manager for handling aiosqlite database exceptions.
98+
99+ Maps SQLite extended result codes to specific SQLSpec exceptions
100+ for better error handling in application code.
101+ """
78102
79103 __slots__ = ()
80104
@@ -84,38 +108,103 @@ async def __aenter__(self) -> None:
84108 async def __aexit__ (self , exc_type : Any , exc_val : Any , exc_tb : Any ) -> None :
85109 if exc_type is None :
86110 return
87- if issubclass (exc_type , aiosqlite .IntegrityError ):
88- e = exc_val
89- msg = f"AIOSQLite integrity constraint violation: { e } "
90- raise SQLSpecError (msg ) from e
91- if issubclass (exc_type , aiosqlite .OperationalError ):
92- e = exc_val
93- error_msg = str (e ).lower ()
94- if "locked" in error_msg :
95- msg = f"AIOSQLite database locked: { e } . Consider enabling WAL mode or reducing concurrency."
96- raise SQLSpecError (msg ) from e
97- if "syntax" in error_msg or "malformed" in error_msg :
98- msg = f"AIOSQLite SQL syntax error: { e } "
99- raise SQLParsingError (msg ) from e
100- msg = f"AIOSQLite operational error: { e } "
101- raise SQLSpecError (msg ) from e
102- if issubclass (exc_type , aiosqlite .DatabaseError ):
103- e = exc_val
104- msg = f"AIOSQLite database error: { e } "
105- raise SQLSpecError (msg ) from e
106111 if issubclass (exc_type , aiosqlite .Error ):
107- e = exc_val
108- msg = f"AIOSQLite error: { e } "
109- raise SQLSpecError (msg ) from e
110- if issubclass (exc_type , Exception ):
111- e = exc_val
112- error_msg = str (e ).lower ()
113- if "parse" in error_msg or "syntax" in error_msg :
114- msg = f"SQL parsing failed: { e } "
115- raise SQLParsingError (msg ) from e
116- msg = f"Unexpected async database operation error: { e } "
112+ self ._map_sqlite_exception (exc_val )
113+
114+ def _map_sqlite_exception (self , e : Any ) -> None :
115+ """Map SQLite exception to SQLSpec exception.
116+
117+ Args:
118+ e: aiosqlite.Error instance
119+
120+ Raises:
121+ Specific SQLSpec exception based on error code
122+ """
123+ error_code = getattr (e , "sqlite_errorcode" , None )
124+ error_name = getattr (e , "sqlite_errorname" , None )
125+ error_msg = str (e ).lower ()
126+
127+ if "locked" in error_msg :
128+ msg = f"AIOSQLite database locked: { e } . Consider enabling WAL mode or reducing concurrency."
117129 raise SQLSpecError (msg ) from e
118130
131+ if not error_code :
132+ if "unique constraint" in error_msg :
133+ self ._raise_unique_violation (e , 0 )
134+ elif "foreign key constraint" in error_msg :
135+ self ._raise_foreign_key_violation (e , 0 )
136+ elif "not null constraint" in error_msg :
137+ self ._raise_not_null_violation (e , 0 )
138+ elif "check constraint" in error_msg :
139+ self ._raise_check_violation (e , 0 )
140+ elif "syntax" in error_msg :
141+ self ._raise_parsing_error (e , None )
142+ else :
143+ self ._raise_generic_error (e )
144+ return
145+
146+ if error_code == SQLITE_CONSTRAINT_UNIQUE_CODE or error_name == "SQLITE_CONSTRAINT_UNIQUE" :
147+ self ._raise_unique_violation (e , error_code )
148+ elif error_code == SQLITE_CONSTRAINT_FOREIGNKEY_CODE or error_name == "SQLITE_CONSTRAINT_FOREIGNKEY" :
149+ self ._raise_foreign_key_violation (e , error_code )
150+ elif error_code == SQLITE_CONSTRAINT_NOTNULL_CODE or error_name == "SQLITE_CONSTRAINT_NOTNULL" :
151+ self ._raise_not_null_violation (e , error_code )
152+ elif error_code == SQLITE_CONSTRAINT_CHECK_CODE or error_name == "SQLITE_CONSTRAINT_CHECK" :
153+ self ._raise_check_violation (e , error_code )
154+ elif error_code == SQLITE_CONSTRAINT_CODE or error_name == "SQLITE_CONSTRAINT" :
155+ self ._raise_integrity_error (e , error_code )
156+ elif error_code == SQLITE_CANTOPEN_CODE or error_name == "SQLITE_CANTOPEN" :
157+ self ._raise_connection_error (e , error_code )
158+ elif error_code == SQLITE_IOERR_CODE or error_name == "SQLITE_IOERR" :
159+ self ._raise_operational_error (e , error_code )
160+ elif error_code == SQLITE_MISMATCH_CODE or error_name == "SQLITE_MISMATCH" :
161+ self ._raise_data_error (e , error_code )
162+ elif error_code == 1 or "syntax" in error_msg :
163+ self ._raise_parsing_error (e , error_code )
164+ else :
165+ self ._raise_generic_error (e )
166+
167+ def _raise_unique_violation (self , e : Any , code : int ) -> None :
168+ msg = f"SQLite unique constraint violation [code { code } ]: { e } "
169+ raise UniqueViolationError (msg ) from e
170+
171+ def _raise_foreign_key_violation (self , e : Any , code : int ) -> None :
172+ msg = f"SQLite foreign key constraint violation [code { code } ]: { e } "
173+ raise ForeignKeyViolationError (msg ) from e
174+
175+ def _raise_not_null_violation (self , e : Any , code : int ) -> None :
176+ msg = f"SQLite not-null constraint violation [code { code } ]: { e } "
177+ raise NotNullViolationError (msg ) from e
178+
179+ def _raise_check_violation (self , e : Any , code : int ) -> None :
180+ msg = f"SQLite check constraint violation [code { code } ]: { e } "
181+ raise CheckViolationError (msg ) from e
182+
183+ def _raise_integrity_error (self , e : Any , code : int ) -> None :
184+ msg = f"SQLite integrity constraint violation [code { code } ]: { e } "
185+ raise IntegrityError (msg ) from e
186+
187+ def _raise_parsing_error (self , e : Any , code : "Optional[int]" ) -> None :
188+ code_str = f"[code { code } ]" if code else ""
189+ msg = f"SQLite SQL syntax error { code_str } : { e } "
190+ raise SQLParsingError (msg ) from e
191+
192+ def _raise_connection_error (self , e : Any , code : int ) -> None :
193+ msg = f"SQLite connection error [code { code } ]: { e } "
194+ raise DatabaseConnectionError (msg ) from e
195+
196+ def _raise_operational_error (self , e : Any , code : int ) -> None :
197+ msg = f"SQLite operational error [code { code } ]: { e } "
198+ raise OperationalError (msg ) from e
199+
200+ def _raise_data_error (self , e : Any , code : int ) -> None :
201+ msg = f"SQLite data error [code { code } ]: { e } "
202+ raise DataError (msg ) from e
203+
204+ def _raise_generic_error (self , e : Any ) -> None :
205+ msg = f"SQLite database error: { e } "
206+ raise SQLSpecError (msg ) from e
207+
119208
120209class AiosqliteDriver (AsyncDriverAdapterBase ):
121210 """AIOSQLite driver for async SQLite database operations."""
0 commit comments