sqlspec 0.26.0__py3-none-any.whl → 0.27.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sqlspec might be problematic. Click here for more details.

Files changed (197) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +55 -25
  3. sqlspec/_typing.py +62 -52
  4. sqlspec/adapters/adbc/_types.py +1 -1
  5. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  6. sqlspec/adapters/adbc/adk/store.py +870 -0
  7. sqlspec/adapters/adbc/config.py +62 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +52 -2
  9. sqlspec/adapters/adbc/driver.py +144 -45
  10. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  11. sqlspec/adapters/adbc/litestar/store.py +504 -0
  12. sqlspec/adapters/adbc/type_converter.py +44 -50
  13. sqlspec/adapters/aiosqlite/_types.py +1 -1
  14. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  15. sqlspec/adapters/aiosqlite/adk/store.py +527 -0
  16. sqlspec/adapters/aiosqlite/config.py +86 -16
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +34 -2
  18. sqlspec/adapters/aiosqlite/driver.py +127 -38
  19. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  20. sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
  21. sqlspec/adapters/aiosqlite/pool.py +7 -7
  22. sqlspec/adapters/asyncmy/__init__.py +7 -1
  23. sqlspec/adapters/asyncmy/_types.py +1 -1
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +493 -0
  26. sqlspec/adapters/asyncmy/config.py +59 -17
  27. sqlspec/adapters/asyncmy/data_dictionary.py +41 -2
  28. sqlspec/adapters/asyncmy/driver.py +293 -62
  29. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  30. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  31. sqlspec/adapters/asyncpg/__init__.py +2 -1
  32. sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
  33. sqlspec/adapters/asyncpg/_types.py +11 -7
  34. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  35. sqlspec/adapters/asyncpg/adk/store.py +450 -0
  36. sqlspec/adapters/asyncpg/config.py +57 -36
  37. sqlspec/adapters/asyncpg/data_dictionary.py +41 -2
  38. sqlspec/adapters/asyncpg/driver.py +153 -23
  39. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  40. sqlspec/adapters/asyncpg/litestar/store.py +253 -0
  41. sqlspec/adapters/bigquery/_types.py +1 -1
  42. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  43. sqlspec/adapters/bigquery/adk/store.py +576 -0
  44. sqlspec/adapters/bigquery/config.py +25 -11
  45. sqlspec/adapters/bigquery/data_dictionary.py +42 -2
  46. sqlspec/adapters/bigquery/driver.py +352 -144
  47. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  48. sqlspec/adapters/bigquery/litestar/store.py +327 -0
  49. sqlspec/adapters/bigquery/type_converter.py +55 -23
  50. sqlspec/adapters/duckdb/_types.py +2 -2
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +553 -0
  53. sqlspec/adapters/duckdb/config.py +79 -21
  54. sqlspec/adapters/duckdb/data_dictionary.py +41 -2
  55. sqlspec/adapters/duckdb/driver.py +138 -43
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +5 -5
  59. sqlspec/adapters/duckdb/type_converter.py +51 -21
  60. sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
  61. sqlspec/adapters/oracledb/_types.py +20 -2
  62. sqlspec/adapters/oracledb/adk/__init__.py +5 -0
  63. sqlspec/adapters/oracledb/adk/store.py +1745 -0
  64. sqlspec/adapters/oracledb/config.py +120 -36
  65. sqlspec/adapters/oracledb/data_dictionary.py +87 -20
  66. sqlspec/adapters/oracledb/driver.py +292 -84
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +767 -0
  69. sqlspec/adapters/oracledb/migrations.py +316 -25
  70. sqlspec/adapters/oracledb/type_converter.py +91 -16
  71. sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
  72. sqlspec/adapters/psqlpy/_types.py +2 -1
  73. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  74. sqlspec/adapters/psqlpy/adk/store.py +482 -0
  75. sqlspec/adapters/psqlpy/config.py +45 -19
  76. sqlspec/adapters/psqlpy/data_dictionary.py +41 -2
  77. sqlspec/adapters/psqlpy/driver.py +101 -31
  78. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  79. sqlspec/adapters/psqlpy/litestar/store.py +272 -0
  80. sqlspec/adapters/psqlpy/type_converter.py +40 -11
  81. sqlspec/adapters/psycopg/_type_handlers.py +80 -0
  82. sqlspec/adapters/psycopg/_types.py +2 -1
  83. sqlspec/adapters/psycopg/adk/__init__.py +5 -0
  84. sqlspec/adapters/psycopg/adk/store.py +944 -0
  85. sqlspec/adapters/psycopg/config.py +65 -37
  86. sqlspec/adapters/psycopg/data_dictionary.py +77 -3
  87. sqlspec/adapters/psycopg/driver.py +200 -78
  88. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  89. sqlspec/adapters/psycopg/litestar/store.py +554 -0
  90. sqlspec/adapters/sqlite/__init__.py +2 -1
  91. sqlspec/adapters/sqlite/_type_handlers.py +86 -0
  92. sqlspec/adapters/sqlite/_types.py +1 -1
  93. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  94. sqlspec/adapters/sqlite/adk/store.py +572 -0
  95. sqlspec/adapters/sqlite/config.py +85 -16
  96. sqlspec/adapters/sqlite/data_dictionary.py +34 -2
  97. sqlspec/adapters/sqlite/driver.py +120 -52
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +5 -5
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +91 -58
  104. sqlspec/builder/_column.py +5 -5
  105. sqlspec/builder/_ddl.py +98 -89
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +41 -44
  109. sqlspec/builder/_insert.py +5 -82
  110. sqlspec/builder/{mixins/_join_operations.py → _join.py} +145 -143
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +9 -11
  113. sqlspec/builder/_select.py +1313 -25
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +76 -69
  116. sqlspec/config.py +231 -60
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +18 -18
  119. sqlspec/core/compiler.py +6 -8
  120. sqlspec/core/filters.py +37 -37
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +76 -45
  123. sqlspec/core/result.py +102 -46
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +32 -31
  126. sqlspec/core/type_conversion.py +3 -2
  127. sqlspec/driver/__init__.py +1 -3
  128. sqlspec/driver/_async.py +95 -161
  129. sqlspec/driver/_common.py +133 -80
  130. sqlspec/driver/_sync.py +95 -162
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +70 -7
  134. sqlspec/extensions/adk/__init__.py +53 -0
  135. sqlspec/extensions/adk/_types.py +51 -0
  136. sqlspec/extensions/adk/converters.py +172 -0
  137. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
  138. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  139. sqlspec/extensions/adk/service.py +181 -0
  140. sqlspec/extensions/adk/store.py +536 -0
  141. sqlspec/extensions/aiosql/adapter.py +73 -53
  142. sqlspec/extensions/litestar/__init__.py +21 -4
  143. sqlspec/extensions/litestar/cli.py +54 -10
  144. sqlspec/extensions/litestar/config.py +59 -266
  145. sqlspec/extensions/litestar/handlers.py +46 -17
  146. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  147. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  148. sqlspec/extensions/litestar/plugin.py +324 -223
  149. sqlspec/extensions/litestar/providers.py +25 -25
  150. sqlspec/extensions/litestar/store.py +265 -0
  151. sqlspec/loader.py +30 -49
  152. sqlspec/migrations/base.py +200 -76
  153. sqlspec/migrations/commands.py +591 -62
  154. sqlspec/migrations/context.py +6 -9
  155. sqlspec/migrations/fix.py +199 -0
  156. sqlspec/migrations/loaders.py +47 -19
  157. sqlspec/migrations/runner.py +241 -75
  158. sqlspec/migrations/tracker.py +237 -21
  159. sqlspec/migrations/utils.py +51 -3
  160. sqlspec/migrations/validation.py +177 -0
  161. sqlspec/protocols.py +66 -36
  162. sqlspec/storage/_utils.py +98 -0
  163. sqlspec/storage/backends/fsspec.py +134 -106
  164. sqlspec/storage/backends/local.py +78 -51
  165. sqlspec/storage/backends/obstore.py +278 -162
  166. sqlspec/storage/registry.py +75 -39
  167. sqlspec/typing.py +14 -84
  168. sqlspec/utils/config_resolver.py +6 -6
  169. sqlspec/utils/correlation.py +4 -5
  170. sqlspec/utils/data_transformation.py +3 -2
  171. sqlspec/utils/deprecation.py +9 -8
  172. sqlspec/utils/fixtures.py +4 -4
  173. sqlspec/utils/logging.py +46 -6
  174. sqlspec/utils/module_loader.py +2 -2
  175. sqlspec/utils/schema.py +288 -0
  176. sqlspec/utils/serializers.py +3 -3
  177. sqlspec/utils/sync_tools.py +21 -17
  178. sqlspec/utils/text.py +1 -2
  179. sqlspec/utils/type_guards.py +111 -20
  180. sqlspec/utils/version.py +433 -0
  181. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
  182. sqlspec-0.27.0.dist-info/RECORD +207 -0
  183. sqlspec/builder/mixins/__init__.py +0 -55
  184. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -253
  185. sqlspec/builder/mixins/_delete_operations.py +0 -50
  186. sqlspec/builder/mixins/_insert_operations.py +0 -282
  187. sqlspec/builder/mixins/_merge_operations.py +0 -698
  188. sqlspec/builder/mixins/_order_limit_operations.py +0 -145
  189. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  190. sqlspec/builder/mixins/_select_operations.py +0 -930
  191. sqlspec/builder/mixins/_update_operations.py +0 -199
  192. sqlspec/builder/mixins/_where_clause.py +0 -1298
  193. sqlspec-0.26.0.dist-info/RECORD +0 -157
  194. sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
  195. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
  196. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
  197. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,9 @@
1
1
  """ADBC database configuration."""
2
2
 
3
3
  import logging
4
+ from collections.abc import Callable
4
5
  from contextlib import contextmanager
5
- from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional, TypedDict, Union
6
+ from typing import TYPE_CHECKING, Any, ClassVar, TypedDict
6
7
 
7
8
  from typing_extensions import NotRequired
8
9
 
@@ -22,7 +23,7 @@ if TYPE_CHECKING:
22
23
  logger = logging.getLogger("sqlspec.adapters.adbc")
23
24
 
24
25
 
25
- class AdbcConnectionParams(TypedDict, total=False):
26
+ class AdbcConnectionParams(TypedDict):
26
27
  """ADBC connection parameters."""
27
28
 
28
29
  uri: NotRequired[str]
@@ -54,7 +55,37 @@ class AdbcConnectionParams(TypedDict, total=False):
54
55
  extra: NotRequired[dict[str, Any]]
55
56
 
56
57
 
57
- __all__ = ("AdbcConfig", "AdbcConnectionParams")
58
+ class AdbcDriverFeatures(TypedDict):
59
+ """ADBC driver feature configuration.
60
+
61
+ Controls optional type handling and serialization behavior for the ADBC adapter.
62
+ These features configure how data is converted between Python and Arrow types.
63
+
64
+ Attributes:
65
+ json_serializer: JSON serialization function to use.
66
+ Callable that takes Any and returns str (JSON string).
67
+ Default: sqlspec.utils.serializers.to_json
68
+ enable_cast_detection: Enable cast-aware parameter processing.
69
+ When True, detects SQL casts (e.g., ::JSONB) and applies appropriate
70
+ serialization. Currently used for PostgreSQL JSONB handling.
71
+ Default: True
72
+ strict_type_coercion: Enforce strict type coercion rules.
73
+ When True, raises errors for unsupported type conversions.
74
+ When False, attempts best-effort conversion.
75
+ Default: False
76
+ arrow_extension_types: Enable PyArrow extension type support.
77
+ When True, preserves Arrow extension type metadata when reading data.
78
+ When False, falls back to storage types.
79
+ Default: True
80
+ """
81
+
82
+ json_serializer: "NotRequired[Callable[[Any], str]]"
83
+ enable_cast_detection: NotRequired[bool]
84
+ strict_type_coercion: NotRequired[bool]
85
+ arrow_extension_types: NotRequired[bool]
86
+
87
+
88
+ __all__ = ("AdbcConfig", "AdbcConnectionParams", "AdbcDriverFeatures")
58
89
 
59
90
 
60
91
  class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
@@ -69,15 +100,17 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
69
100
 
70
101
  driver_type: ClassVar[type[AdbcDriver]] = AdbcDriver
71
102
  connection_type: "ClassVar[type[AdbcConnection]]" = AdbcConnection
103
+ supports_transactional_ddl: ClassVar[bool] = False
72
104
 
73
105
  def __init__(
74
106
  self,
75
107
  *,
76
- connection_config: Optional[Union[AdbcConnectionParams, dict[str, Any]]] = None,
77
- migration_config: Optional[dict[str, Any]] = None,
78
- statement_config: Optional[StatementConfig] = None,
79
- driver_features: Optional[dict[str, Any]] = None,
80
- bind_key: Optional[str] = None,
108
+ connection_config: AdbcConnectionParams | dict[str, Any] | None = None,
109
+ migration_config: dict[str, Any] | None = None,
110
+ statement_config: StatementConfig | None = None,
111
+ driver_features: "AdbcDriverFeatures | dict[str, Any] | None" = None,
112
+ bind_key: str | None = None,
113
+ extension_config: "dict[str, dict[str, Any]] | None" = None,
81
114
  ) -> None:
82
115
  """Initialize configuration.
83
116
 
@@ -85,8 +118,9 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
85
118
  connection_config: Connection configuration parameters
86
119
  migration_config: Migration configuration
87
120
  statement_config: Default SQL statement configuration
88
- driver_features: Driver feature configuration
121
+ driver_features: Driver feature configuration (AdbcDriverFeatures)
89
122
  bind_key: Optional unique identifier for this configuration
123
+ extension_config: Extension-specific configuration (e.g., Litestar plugin settings)
90
124
  """
91
125
  if connection_config is None:
92
126
  connection_config = {}
@@ -101,12 +135,26 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
101
135
  detected_dialect = str(self._get_dialect() or "sqlite")
102
136
  statement_config = get_adbc_statement_config(detected_dialect)
103
137
 
138
+ from sqlspec.utils.serializers import to_json
139
+
140
+ if driver_features is None:
141
+ driver_features = {}
142
+ if "json_serializer" not in driver_features:
143
+ driver_features["json_serializer"] = to_json
144
+ if "enable_cast_detection" not in driver_features:
145
+ driver_features["enable_cast_detection"] = True
146
+ if "strict_type_coercion" not in driver_features:
147
+ driver_features["strict_type_coercion"] = False
148
+ if "arrow_extension_types" not in driver_features:
149
+ driver_features["arrow_extension_types"] = True
150
+
104
151
  super().__init__(
105
152
  connection_config=self.connection_config,
106
153
  migration_config=migration_config,
107
154
  statement_config=statement_config,
108
- driver_features=driver_features or {},
155
+ driver_features=dict(driver_features),
109
156
  bind_key=bind_key,
157
+ extension_config=extension_config,
110
158
  )
111
159
 
112
160
  def _resolve_driver_name(self) -> str:
@@ -284,7 +332,7 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
284
332
  connection.close()
285
333
 
286
334
  def provide_session(
287
- self, *args: Any, statement_config: "Optional[StatementConfig]" = None, **kwargs: Any
335
+ self, *args: Any, statement_config: "StatementConfig | None" = None, **kwargs: Any
288
336
  ) -> "AbstractContextManager[AdbcDriver]":
289
337
  """Provide a driver session context manager.
290
338
 
@@ -305,7 +353,9 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
305
353
  or self.statement_config
306
354
  or get_adbc_statement_config(str(self._get_dialect() or "sqlite"))
307
355
  )
308
- yield self.driver_type(connection=connection, statement_config=final_statement_config)
356
+ yield self.driver_type(
357
+ connection=connection, statement_config=final_statement_config, driver_features=self.driver_features
358
+ )
309
359
 
310
360
  return session_manager()
311
361
 
@@ -1,7 +1,7 @@
1
1
  """ADBC multi-dialect data dictionary for metadata queries."""
2
2
 
3
3
  import re
4
- from typing import TYPE_CHECKING, Optional, cast
4
+ from typing import TYPE_CHECKING, Any, cast
5
5
 
6
6
  from sqlspec.driver import SyncDataDictionaryBase, SyncDriverAdapterBase, VersionInfo
7
7
  from sqlspec.utils.logging import get_logger
@@ -38,7 +38,7 @@ class AdbcDataDictionary(SyncDataDictionaryBase):
38
38
  """
39
39
  return str(cast("AdbcDriver", driver).dialect)
40
40
 
41
- def get_version(self, driver: SyncDriverAdapterBase) -> "Optional[VersionInfo]":
41
+ def get_version(self, driver: SyncDriverAdapterBase) -> "VersionInfo | None":
42
42
  """Get database version information based on detected dialect.
43
43
 
44
44
  Args:
@@ -268,6 +268,56 @@ class AdbcDataDictionary(SyncDataDictionaryBase):
268
268
 
269
269
  return type_map.get(type_category, "TEXT")
270
270
 
271
+ def get_columns(
272
+ self, driver: SyncDriverAdapterBase, table: str, schema: "str | None" = None
273
+ ) -> "list[dict[str, Any]]":
274
+ """Get column information for a table based on detected dialect.
275
+
276
+ Args:
277
+ driver: ADBC driver instance
278
+ table: Table name to query columns for
279
+ schema: Schema name (None for default)
280
+
281
+ Returns:
282
+ List of column metadata dictionaries with keys:
283
+ - column_name: Name of the column
284
+ - data_type: Database data type
285
+ - is_nullable or nullable: Whether column allows NULL
286
+ - column_default or default_value: Default value if any
287
+ """
288
+ dialect = self._get_dialect(driver)
289
+ adbc_driver = cast("AdbcDriver", driver)
290
+
291
+ if dialect == "sqlite":
292
+ result = adbc_driver.execute(f"PRAGMA table_info({table})")
293
+ return [
294
+ {
295
+ "column_name": row["name"] if isinstance(row, dict) else row[1],
296
+ "data_type": row["type"] if isinstance(row, dict) else row[2],
297
+ "nullable": not (row["notnull"] if isinstance(row, dict) else row[3]),
298
+ "default_value": row["dflt_value"] if isinstance(row, dict) else row[4],
299
+ }
300
+ for row in result.data or []
301
+ ]
302
+
303
+ if schema:
304
+ sql = f"""
305
+ SELECT column_name, data_type, is_nullable, column_default
306
+ FROM information_schema.columns
307
+ WHERE table_name = '{table}' AND table_schema = '{schema}'
308
+ ORDER BY ordinal_position
309
+ """
310
+ else:
311
+ sql = f"""
312
+ SELECT column_name, data_type, is_nullable, column_default
313
+ FROM information_schema.columns
314
+ WHERE table_name = '{table}'
315
+ ORDER BY ordinal_position
316
+ """
317
+
318
+ result = adbc_driver.execute(sql)
319
+ return result.data or []
320
+
271
321
  def list_available_features(self) -> "list[str]":
272
322
  """List available feature flags across all supported dialects.
273
323
 
@@ -7,9 +7,8 @@ database dialects, parameter style conversion, and transaction management.
7
7
  import contextlib
8
8
  import datetime
9
9
  import decimal
10
- from typing import TYPE_CHECKING, Any, Optional, cast
10
+ from typing import TYPE_CHECKING, Any, cast
11
11
 
12
- from adbc_driver_manager.dbapi import DatabaseError, IntegrityError, OperationalError, ProgrammingError
13
12
  from sqlglot import exp
14
13
 
15
14
  from sqlspec.adapters.adbc.data_dictionary import AdbcDataDictionary
@@ -18,7 +17,19 @@ from sqlspec.core.cache import get_cache_config
18
17
  from sqlspec.core.parameters import ParameterStyle, ParameterStyleConfig
19
18
  from sqlspec.core.statement import SQL, StatementConfig
20
19
  from sqlspec.driver import SyncDriverAdapterBase
21
- from sqlspec.exceptions import MissingDependencyError, SQLParsingError, SQLSpecError
20
+ from sqlspec.exceptions import (
21
+ CheckViolationError,
22
+ DatabaseConnectionError,
23
+ DataError,
24
+ ForeignKeyViolationError,
25
+ IntegrityError,
26
+ MissingDependencyError,
27
+ NotNullViolationError,
28
+ SQLParsingError,
29
+ SQLSpecError,
30
+ TransactionError,
31
+ UniqueViolationError,
32
+ )
22
33
  from sqlspec.typing import Empty
23
34
  from sqlspec.utils.logging import get_logger
24
35
 
@@ -329,7 +340,7 @@ class AdbcCursor:
329
340
 
330
341
  def __init__(self, connection: "AdbcConnection") -> None:
331
342
  self.connection = connection
332
- self.cursor: Optional[Cursor] = None
343
+ self.cursor: Cursor | None = None
333
344
 
334
345
  def __enter__(self) -> "Cursor":
335
346
  self.cursor = self.connection.cursor()
@@ -342,7 +353,11 @@ class AdbcCursor:
342
353
 
343
354
 
344
355
  class AdbcExceptionHandler:
345
- """Context manager for handling database exceptions."""
356
+ """Context manager for handling ADBC database exceptions.
357
+
358
+ ADBC propagates underlying database errors. Exception mapping
359
+ depends on the specific ADBC driver being used.
360
+ """
346
361
 
347
362
  __slots__ = ()
348
363
 
@@ -350,40 +365,118 @@ class AdbcExceptionHandler:
350
365
  return None
351
366
 
352
367
  def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
368
+ _ = exc_tb
353
369
  if exc_type is None:
354
370
  return
371
+ self._map_adbc_exception(exc_val)
355
372
 
356
- try:
357
- if issubclass(exc_type, IntegrityError):
358
- e = exc_val
359
- msg = f"Integrity constraint violation: {e}"
360
- raise SQLSpecError(msg) from e
361
- if issubclass(exc_type, ProgrammingError):
362
- e = exc_val
363
- error_msg = str(e).lower()
364
- if "syntax" in error_msg or "parse" in error_msg:
365
- msg = f"SQL syntax error: {e}"
366
- raise SQLParsingError(msg) from e
367
- msg = f"Programming error: {e}"
368
- raise SQLSpecError(msg) from e
369
- if issubclass(exc_type, OperationalError):
370
- e = exc_val
371
- msg = f"Operational error: {e}"
372
- raise SQLSpecError(msg) from e
373
- if issubclass(exc_type, DatabaseError):
374
- e = exc_val
375
- msg = f"Database error: {e}"
376
- raise SQLSpecError(msg) from e
377
- except ImportError:
378
- pass
379
- if issubclass(exc_type, Exception):
380
- e = exc_val
381
- error_msg = str(e).lower()
382
- if "parse" in error_msg or "syntax" in error_msg:
383
- msg = f"SQL parsing failed: {e}"
384
- raise SQLParsingError(msg) from e
385
- msg = f"Unexpected database operation error: {e}"
386
- raise SQLSpecError(msg) from e
373
+ def _map_adbc_exception(self, e: Any) -> None:
374
+ """Map ADBC exception to SQLSpec exception.
375
+
376
+ ADBC drivers may expose SQLSTATE codes or driver-specific codes.
377
+
378
+ Args:
379
+ e: ADBC exception instance
380
+ """
381
+ sqlstate = getattr(e, "sqlstate", None)
382
+
383
+ if sqlstate:
384
+ self._map_sqlstate_exception(e, sqlstate)
385
+ else:
386
+ self._map_message_based_exception(e)
387
+
388
+ def _map_sqlstate_exception(self, e: Any, sqlstate: str) -> None:
389
+ """Map SQLSTATE code to exception.
390
+
391
+ Args:
392
+ e: Exception instance
393
+ sqlstate: SQLSTATE error code
394
+ """
395
+ if sqlstate == "23505":
396
+ self._raise_unique_violation(e)
397
+ elif sqlstate == "23503":
398
+ self._raise_foreign_key_violation(e)
399
+ elif sqlstate == "23502":
400
+ self._raise_not_null_violation(e)
401
+ elif sqlstate == "23514":
402
+ self._raise_check_violation(e)
403
+ elif sqlstate.startswith("23"):
404
+ self._raise_integrity_error(e)
405
+ elif sqlstate.startswith("42"):
406
+ self._raise_parsing_error(e)
407
+ elif sqlstate.startswith("08"):
408
+ self._raise_connection_error(e)
409
+ elif sqlstate.startswith("40"):
410
+ self._raise_transaction_error(e)
411
+ elif sqlstate.startswith("22"):
412
+ self._raise_data_error(e)
413
+ else:
414
+ self._raise_generic_error(e)
415
+
416
+ def _map_message_based_exception(self, e: Any) -> None:
417
+ """Map exception using message-based detection.
418
+
419
+ Args:
420
+ e: Exception instance
421
+ """
422
+ error_msg = str(e).lower()
423
+
424
+ if "unique" in error_msg or "duplicate" in error_msg:
425
+ self._raise_unique_violation(e)
426
+ elif "foreign key" in error_msg:
427
+ self._raise_foreign_key_violation(e)
428
+ elif "not null" in error_msg or "null value" in error_msg:
429
+ self._raise_not_null_violation(e)
430
+ elif "check constraint" in error_msg:
431
+ self._raise_check_violation(e)
432
+ elif "constraint" in error_msg:
433
+ self._raise_integrity_error(e)
434
+ elif "syntax" in error_msg:
435
+ self._raise_parsing_error(e)
436
+ elif "connection" in error_msg or "connect" in error_msg:
437
+ self._raise_connection_error(e)
438
+ else:
439
+ self._raise_generic_error(e)
440
+
441
+ def _raise_unique_violation(self, e: Any) -> None:
442
+ msg = f"ADBC unique constraint violation: {e}"
443
+ raise UniqueViolationError(msg) from e
444
+
445
+ def _raise_foreign_key_violation(self, e: Any) -> None:
446
+ msg = f"ADBC foreign key constraint violation: {e}"
447
+ raise ForeignKeyViolationError(msg) from e
448
+
449
+ def _raise_not_null_violation(self, e: Any) -> None:
450
+ msg = f"ADBC not-null constraint violation: {e}"
451
+ raise NotNullViolationError(msg) from e
452
+
453
+ def _raise_check_violation(self, e: Any) -> None:
454
+ msg = f"ADBC check constraint violation: {e}"
455
+ raise CheckViolationError(msg) from e
456
+
457
+ def _raise_integrity_error(self, e: Any) -> None:
458
+ msg = f"ADBC integrity constraint violation: {e}"
459
+ raise IntegrityError(msg) from e
460
+
461
+ def _raise_parsing_error(self, e: Any) -> None:
462
+ msg = f"ADBC SQL parsing error: {e}"
463
+ raise SQLParsingError(msg) from e
464
+
465
+ def _raise_connection_error(self, e: Any) -> None:
466
+ msg = f"ADBC connection error: {e}"
467
+ raise DatabaseConnectionError(msg) from e
468
+
469
+ def _raise_transaction_error(self, e: Any) -> None:
470
+ msg = f"ADBC transaction error: {e}"
471
+ raise TransactionError(msg) from e
472
+
473
+ def _raise_data_error(self, e: Any) -> None:
474
+ msg = f"ADBC data error: {e}"
475
+ raise DataError(msg) from e
476
+
477
+ def _raise_generic_error(self, e: Any) -> None:
478
+ msg = f"ADBC database error: {e}"
479
+ raise SQLSpecError(msg) from e
387
480
 
388
481
 
389
482
  class AdbcDriver(SyncDriverAdapterBase):
@@ -398,8 +491,8 @@ class AdbcDriver(SyncDriverAdapterBase):
398
491
  def __init__(
399
492
  self,
400
493
  connection: "AdbcConnection",
401
- statement_config: "Optional[StatementConfig]" = None,
402
- driver_features: "Optional[dict[str, Any]]" = None,
494
+ statement_config: "StatementConfig | None" = None,
495
+ driver_features: "dict[str, Any] | None" = None,
403
496
  ) -> None:
404
497
  self._detected_dialect = self._get_dialect(connection)
405
498
 
@@ -412,7 +505,7 @@ class AdbcDriver(SyncDriverAdapterBase):
412
505
 
413
506
  super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
414
507
  self.dialect = statement_config.dialect
415
- self._data_dictionary: Optional[SyncDataDictionaryBase] = None
508
+ self._data_dictionary: SyncDataDictionaryBase | None = None
416
509
 
417
510
  @staticmethod
418
511
  def _ensure_pyarrow_installed() -> None:
@@ -480,12 +573,13 @@ class AdbcDriver(SyncDriverAdapterBase):
480
573
  parameters: Any,
481
574
  statement_config: "StatementConfig",
482
575
  is_many: bool = False,
483
- prepared_statement: Optional[Any] = None,
576
+ prepared_statement: Any | None = None,
484
577
  ) -> Any:
485
578
  """Prepare parameters with cast-aware type coercion for ADBC.
486
579
 
487
580
  For PostgreSQL, applies cast-aware parameter processing using metadata from the compiled statement.
488
581
  This allows proper handling of JSONB casts and other type conversions.
582
+ Respects driver_features['enable_cast_detection'] configuration.
489
583
 
490
584
  Args:
491
585
  parameters: Parameters in any format
@@ -496,7 +590,9 @@ class AdbcDriver(SyncDriverAdapterBase):
496
590
  Returns:
497
591
  Parameters with cast-aware type coercion applied
498
592
  """
499
- if prepared_statement and self.dialect in {"postgres", "postgresql"} and not is_many:
593
+ enable_cast_detection = self.driver_features.get("enable_cast_detection", True)
594
+
595
+ if enable_cast_detection and prepared_statement and self.dialect in {"postgres", "postgresql"} and not is_many:
500
596
  parameter_casts = self._get_parameter_casts(prepared_statement)
501
597
  postgres_compatible = self._handle_postgres_empty_parameters(parameters)
502
598
  return self._prepare_parameters_with_casts(postgres_compatible, parameter_casts, statement_config)
@@ -524,6 +620,7 @@ class AdbcDriver(SyncDriverAdapterBase):
524
620
  """Prepare parameters with cast-aware type coercion.
525
621
 
526
622
  Uses type coercion map for non-dict types and dialect-aware dict handling.
623
+ Respects driver_features configuration for JSON serialization backend.
527
624
 
528
625
  Args:
529
626
  parameters: Parameter values (list, tuple, or scalar)
@@ -533,7 +630,9 @@ class AdbcDriver(SyncDriverAdapterBase):
533
630
  Returns:
534
631
  Parameters with cast-aware type coercion applied
535
632
  """
536
- from sqlspec._serialization import encode_json
633
+ from sqlspec.utils.serializers import to_json
634
+
635
+ json_encoder = self.driver_features.get("json_serializer", to_json)
537
636
 
538
637
  if isinstance(parameters, (list, tuple)):
539
638
  result: list[Any] = []
@@ -541,7 +640,7 @@ class AdbcDriver(SyncDriverAdapterBase):
541
640
  cast_type = parameter_casts.get(idx, "").upper()
542
641
  if cast_type in {"JSON", "JSONB", "TYPE.JSON", "TYPE.JSONB"}:
543
642
  if isinstance(param, dict):
544
- result.append(encode_json(param))
643
+ result.append(json_encoder(param))
545
644
  else:
546
645
  result.append(param)
547
646
  elif isinstance(param, dict):
@@ -575,7 +674,7 @@ class AdbcDriver(SyncDriverAdapterBase):
575
674
  """
576
675
  return AdbcExceptionHandler()
577
676
 
578
- def _try_special_handling(self, cursor: "Cursor", statement: SQL) -> "Optional[SQLResult]":
677
+ def _try_special_handling(self, cursor: "Cursor", statement: SQL) -> "SQLResult | None":
579
678
  """Handle special operations.
580
679
 
581
680
  Args:
@@ -668,7 +767,7 @@ class AdbcDriver(SyncDriverAdapterBase):
668
767
  column_names = [col[0] for col in cursor.description or []]
669
768
 
670
769
  if fetched_data and isinstance(fetched_data[0], tuple):
671
- dict_data: list[dict[Any, Any]] = [dict(zip(column_names, row)) for row in fetched_data]
770
+ dict_data: list[dict[Any, Any]] = [dict(zip(column_names, row, strict=False)) for row in fetched_data]
672
771
  else:
673
772
  dict_data = fetched_data # type: ignore[assignment]
674
773
 
@@ -0,0 +1,5 @@
1
+ """Litestar integration for ADBC adapter."""
2
+
3
+ from sqlspec.adapters.adbc.litestar.store import ADBCStore
4
+
5
+ __all__ = ("ADBCStore",)