sqlspec 0.16.2__cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.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 (148) hide show
  1. 51ff5a9eadfdefd49f98__mypyc.cpython-39-aarch64-linux-gnu.so +0 -0
  2. sqlspec/__init__.py +92 -0
  3. sqlspec/__main__.py +12 -0
  4. sqlspec/__metadata__.py +14 -0
  5. sqlspec/_serialization.py +77 -0
  6. sqlspec/_sql.py +1782 -0
  7. sqlspec/_typing.py +680 -0
  8. sqlspec/adapters/__init__.py +0 -0
  9. sqlspec/adapters/adbc/__init__.py +5 -0
  10. sqlspec/adapters/adbc/_types.py +12 -0
  11. sqlspec/adapters/adbc/config.py +361 -0
  12. sqlspec/adapters/adbc/driver.py +512 -0
  13. sqlspec/adapters/aiosqlite/__init__.py +19 -0
  14. sqlspec/adapters/aiosqlite/_types.py +13 -0
  15. sqlspec/adapters/aiosqlite/config.py +253 -0
  16. sqlspec/adapters/aiosqlite/driver.py +248 -0
  17. sqlspec/adapters/asyncmy/__init__.py +19 -0
  18. sqlspec/adapters/asyncmy/_types.py +12 -0
  19. sqlspec/adapters/asyncmy/config.py +180 -0
  20. sqlspec/adapters/asyncmy/driver.py +274 -0
  21. sqlspec/adapters/asyncpg/__init__.py +21 -0
  22. sqlspec/adapters/asyncpg/_types.py +17 -0
  23. sqlspec/adapters/asyncpg/config.py +229 -0
  24. sqlspec/adapters/asyncpg/driver.py +344 -0
  25. sqlspec/adapters/bigquery/__init__.py +18 -0
  26. sqlspec/adapters/bigquery/_types.py +12 -0
  27. sqlspec/adapters/bigquery/config.py +298 -0
  28. sqlspec/adapters/bigquery/driver.py +558 -0
  29. sqlspec/adapters/duckdb/__init__.py +22 -0
  30. sqlspec/adapters/duckdb/_types.py +12 -0
  31. sqlspec/adapters/duckdb/config.py +504 -0
  32. sqlspec/adapters/duckdb/driver.py +368 -0
  33. sqlspec/adapters/oracledb/__init__.py +32 -0
  34. sqlspec/adapters/oracledb/_types.py +14 -0
  35. sqlspec/adapters/oracledb/config.py +317 -0
  36. sqlspec/adapters/oracledb/driver.py +538 -0
  37. sqlspec/adapters/psqlpy/__init__.py +16 -0
  38. sqlspec/adapters/psqlpy/_types.py +11 -0
  39. sqlspec/adapters/psqlpy/config.py +214 -0
  40. sqlspec/adapters/psqlpy/driver.py +530 -0
  41. sqlspec/adapters/psycopg/__init__.py +32 -0
  42. sqlspec/adapters/psycopg/_types.py +17 -0
  43. sqlspec/adapters/psycopg/config.py +426 -0
  44. sqlspec/adapters/psycopg/driver.py +796 -0
  45. sqlspec/adapters/sqlite/__init__.py +15 -0
  46. sqlspec/adapters/sqlite/_types.py +11 -0
  47. sqlspec/adapters/sqlite/config.py +240 -0
  48. sqlspec/adapters/sqlite/driver.py +294 -0
  49. sqlspec/base.py +571 -0
  50. sqlspec/builder/__init__.py +62 -0
  51. sqlspec/builder/_base.py +473 -0
  52. sqlspec/builder/_column.py +320 -0
  53. sqlspec/builder/_ddl.py +1346 -0
  54. sqlspec/builder/_ddl_utils.py +103 -0
  55. sqlspec/builder/_delete.py +76 -0
  56. sqlspec/builder/_insert.py +421 -0
  57. sqlspec/builder/_merge.py +71 -0
  58. sqlspec/builder/_parsing_utils.py +164 -0
  59. sqlspec/builder/_select.py +170 -0
  60. sqlspec/builder/_update.py +188 -0
  61. sqlspec/builder/mixins/__init__.py +55 -0
  62. sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
  63. sqlspec/builder/mixins/_delete_operations.py +41 -0
  64. sqlspec/builder/mixins/_insert_operations.py +244 -0
  65. sqlspec/builder/mixins/_join_operations.py +149 -0
  66. sqlspec/builder/mixins/_merge_operations.py +562 -0
  67. sqlspec/builder/mixins/_order_limit_operations.py +135 -0
  68. sqlspec/builder/mixins/_pivot_operations.py +153 -0
  69. sqlspec/builder/mixins/_select_operations.py +604 -0
  70. sqlspec/builder/mixins/_update_operations.py +202 -0
  71. sqlspec/builder/mixins/_where_clause.py +644 -0
  72. sqlspec/cli.py +247 -0
  73. sqlspec/config.py +395 -0
  74. sqlspec/core/__init__.py +63 -0
  75. sqlspec/core/cache.cpython-39-aarch64-linux-gnu.so +0 -0
  76. sqlspec/core/cache.py +871 -0
  77. sqlspec/core/compiler.cpython-39-aarch64-linux-gnu.so +0 -0
  78. sqlspec/core/compiler.py +417 -0
  79. sqlspec/core/filters.cpython-39-aarch64-linux-gnu.so +0 -0
  80. sqlspec/core/filters.py +830 -0
  81. sqlspec/core/hashing.cpython-39-aarch64-linux-gnu.so +0 -0
  82. sqlspec/core/hashing.py +310 -0
  83. sqlspec/core/parameters.cpython-39-aarch64-linux-gnu.so +0 -0
  84. sqlspec/core/parameters.py +1237 -0
  85. sqlspec/core/result.cpython-39-aarch64-linux-gnu.so +0 -0
  86. sqlspec/core/result.py +677 -0
  87. sqlspec/core/splitter.cpython-39-aarch64-linux-gnu.so +0 -0
  88. sqlspec/core/splitter.py +819 -0
  89. sqlspec/core/statement.cpython-39-aarch64-linux-gnu.so +0 -0
  90. sqlspec/core/statement.py +676 -0
  91. sqlspec/driver/__init__.py +19 -0
  92. sqlspec/driver/_async.py +502 -0
  93. sqlspec/driver/_common.py +631 -0
  94. sqlspec/driver/_sync.py +503 -0
  95. sqlspec/driver/mixins/__init__.py +6 -0
  96. sqlspec/driver/mixins/_result_tools.py +193 -0
  97. sqlspec/driver/mixins/_sql_translator.py +86 -0
  98. sqlspec/exceptions.py +193 -0
  99. sqlspec/extensions/__init__.py +0 -0
  100. sqlspec/extensions/aiosql/__init__.py +10 -0
  101. sqlspec/extensions/aiosql/adapter.py +461 -0
  102. sqlspec/extensions/litestar/__init__.py +6 -0
  103. sqlspec/extensions/litestar/_utils.py +52 -0
  104. sqlspec/extensions/litestar/cli.py +48 -0
  105. sqlspec/extensions/litestar/config.py +92 -0
  106. sqlspec/extensions/litestar/handlers.py +260 -0
  107. sqlspec/extensions/litestar/plugin.py +145 -0
  108. sqlspec/extensions/litestar/providers.py +454 -0
  109. sqlspec/loader.cpython-39-aarch64-linux-gnu.so +0 -0
  110. sqlspec/loader.py +760 -0
  111. sqlspec/migrations/__init__.py +35 -0
  112. sqlspec/migrations/base.py +414 -0
  113. sqlspec/migrations/commands.py +443 -0
  114. sqlspec/migrations/loaders.py +402 -0
  115. sqlspec/migrations/runner.py +213 -0
  116. sqlspec/migrations/tracker.py +140 -0
  117. sqlspec/migrations/utils.py +129 -0
  118. sqlspec/protocols.py +407 -0
  119. sqlspec/py.typed +0 -0
  120. sqlspec/storage/__init__.py +23 -0
  121. sqlspec/storage/backends/__init__.py +0 -0
  122. sqlspec/storage/backends/base.py +163 -0
  123. sqlspec/storage/backends/fsspec.py +386 -0
  124. sqlspec/storage/backends/obstore.py +459 -0
  125. sqlspec/storage/capabilities.py +102 -0
  126. sqlspec/storage/registry.py +239 -0
  127. sqlspec/typing.py +299 -0
  128. sqlspec/utils/__init__.py +3 -0
  129. sqlspec/utils/correlation.py +150 -0
  130. sqlspec/utils/deprecation.py +106 -0
  131. sqlspec/utils/fixtures.cpython-39-aarch64-linux-gnu.so +0 -0
  132. sqlspec/utils/fixtures.py +58 -0
  133. sqlspec/utils/logging.py +127 -0
  134. sqlspec/utils/module_loader.py +89 -0
  135. sqlspec/utils/serializers.py +4 -0
  136. sqlspec/utils/singleton.py +32 -0
  137. sqlspec/utils/sync_tools.cpython-39-aarch64-linux-gnu.so +0 -0
  138. sqlspec/utils/sync_tools.py +237 -0
  139. sqlspec/utils/text.cpython-39-aarch64-linux-gnu.so +0 -0
  140. sqlspec/utils/text.py +96 -0
  141. sqlspec/utils/type_guards.cpython-39-aarch64-linux-gnu.so +0 -0
  142. sqlspec/utils/type_guards.py +1139 -0
  143. sqlspec-0.16.2.dist-info/METADATA +365 -0
  144. sqlspec-0.16.2.dist-info/RECORD +148 -0
  145. sqlspec-0.16.2.dist-info/WHEEL +7 -0
  146. sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
  147. sqlspec-0.16.2.dist-info/licenses/LICENSE +21 -0
  148. sqlspec-0.16.2.dist-info/licenses/NOTICE +29 -0
@@ -0,0 +1,512 @@
1
+ """ADBC driver implementation for Arrow Database Connectivity.
2
+
3
+ This module provides ADBC driver integration with support for:
4
+ - Multi-dialect database connections through ADBC
5
+ - Arrow-native data handling with type coercion
6
+ - Parameter style conversion for different database backends
7
+ - Transaction management with proper error handling
8
+ """
9
+
10
+ import contextlib
11
+ import datetime
12
+ import decimal
13
+ from typing import TYPE_CHECKING, Any, Optional, cast
14
+
15
+ from sqlglot import exp
16
+
17
+ from sqlspec.core.cache import get_cache_config
18
+ from sqlspec.core.parameters import ParameterStyle, ParameterStyleConfig
19
+ from sqlspec.core.statement import SQL, StatementConfig
20
+ from sqlspec.driver import SyncDriverAdapterBase
21
+ from sqlspec.exceptions import MissingDependencyError, SQLParsingError, SQLSpecError
22
+ from sqlspec.utils.logging import get_logger
23
+ from sqlspec.utils.serializers import to_json
24
+
25
+ if TYPE_CHECKING:
26
+ from contextlib import AbstractContextManager
27
+
28
+ from adbc_driver_manager.dbapi import Cursor
29
+
30
+ from sqlspec.adapters.adbc._types import AdbcConnection
31
+ from sqlspec.core.result import SQLResult
32
+ from sqlspec.driver import ExecutionResult
33
+
34
+ __all__ = ("AdbcCursor", "AdbcDriver", "AdbcExceptionHandler", "get_adbc_statement_config")
35
+
36
+ logger = get_logger("adapters.adbc")
37
+
38
+ DIALECT_PATTERNS = {
39
+ "postgres": ["postgres", "postgresql"],
40
+ "bigquery": ["bigquery"],
41
+ "sqlite": ["sqlite", "flight", "flightsql"],
42
+ "duckdb": ["duckdb"],
43
+ "mysql": ["mysql"],
44
+ "snowflake": ["snowflake"],
45
+ }
46
+
47
+ DIALECT_PARAMETER_STYLES = {
48
+ "postgres": (ParameterStyle.NUMERIC, [ParameterStyle.NUMERIC]),
49
+ "postgresql": (ParameterStyle.NUMERIC, [ParameterStyle.NUMERIC]),
50
+ "bigquery": (ParameterStyle.NAMED_AT, [ParameterStyle.NAMED_AT]),
51
+ "sqlite": (ParameterStyle.QMARK, [ParameterStyle.QMARK, ParameterStyle.NAMED_COLON]),
52
+ "duckdb": (ParameterStyle.QMARK, [ParameterStyle.QMARK, ParameterStyle.NUMERIC, ParameterStyle.NAMED_DOLLAR]),
53
+ "mysql": (ParameterStyle.POSITIONAL_PYFORMAT, [ParameterStyle.POSITIONAL_PYFORMAT, ParameterStyle.NAMED_PYFORMAT]),
54
+ "snowflake": (ParameterStyle.QMARK, [ParameterStyle.QMARK, ParameterStyle.NUMERIC]),
55
+ }
56
+
57
+
58
+ def _adbc_ast_transformer(expression: Any, parameters: Any) -> tuple[Any, Any]:
59
+ """ADBC-specific AST transformer for NULL parameter handling.
60
+
61
+ For PostgreSQL, this transformer replaces NULL parameter placeholders with NULL literals
62
+ in the AST to prevent Arrow from inferring 'na' types which cause binding errors.
63
+
64
+ The transformer:
65
+ 1. Detects None parameters in the parameter list
66
+ 2. Replaces corresponding placeholders in the AST with NULL literals
67
+ 3. Removes the None parameters from the list
68
+ 4. Renumbers remaining placeholders to maintain correct mapping
69
+
70
+ Args:
71
+ expression: SQLGlot AST expression
72
+ parameters: Parameter values that may contain None
73
+
74
+ Returns:
75
+ Tuple of (modified_expression, cleaned_parameters)
76
+ """
77
+ if not parameters:
78
+ return expression, parameters
79
+
80
+ # Detect NULL parameter positions
81
+ null_positions = set()
82
+ if isinstance(parameters, (list, tuple)):
83
+ for i, param in enumerate(parameters):
84
+ if param is None:
85
+ null_positions.add(i)
86
+ elif isinstance(parameters, dict):
87
+ for key, param in parameters.items():
88
+ if param is None:
89
+ try:
90
+ if isinstance(key, str) and key.lstrip("$").isdigit():
91
+ param_num = int(key.lstrip("$"))
92
+ null_positions.add(param_num - 1)
93
+ except ValueError:
94
+ pass
95
+
96
+ if not null_positions:
97
+ return expression, parameters
98
+
99
+ # Track position for QMARK-style placeholders
100
+ qmark_position = [0]
101
+
102
+ def transform_node(node: Any) -> Any:
103
+ """Transform parameter nodes to NULL literals and renumber remaining ones."""
104
+ # Handle QMARK-style placeholders (?, ?, ?)
105
+ if isinstance(node, exp.Placeholder) and (not hasattr(node, "this") or node.this is None):
106
+ current_pos = qmark_position[0]
107
+ qmark_position[0] += 1
108
+
109
+ if current_pos in null_positions:
110
+ return exp.Null()
111
+ # Don't renumber QMARK placeholders - they stay as ?
112
+ return node
113
+
114
+ # Handle PostgreSQL-style placeholders ($1, $2, etc.)
115
+ if isinstance(node, exp.Placeholder) and hasattr(node, "this") and node.this is not None:
116
+ try:
117
+ param_str = str(node.this).lstrip("$")
118
+ param_num = int(param_str)
119
+ param_index = param_num - 1 # Convert to 0-based
120
+
121
+ if param_index in null_positions:
122
+ return exp.Null()
123
+ # Renumber placeholder to account for removed NULLs
124
+ nulls_before = sum(1 for idx in null_positions if idx < param_index)
125
+ new_param_num = param_num - nulls_before
126
+ return exp.Placeholder(this=f"${new_param_num}")
127
+ except (ValueError, AttributeError):
128
+ pass
129
+
130
+ # Handle generic parameter nodes
131
+ if isinstance(node, exp.Parameter) and hasattr(node, "this"):
132
+ try:
133
+ param_str = str(node.this)
134
+ param_num = int(param_str)
135
+ param_index = param_num - 1 # Convert to 0-based
136
+
137
+ if param_index in null_positions:
138
+ return exp.Null()
139
+ # Renumber parameter to account for removed NULLs
140
+ nulls_before = sum(1 for idx in null_positions if idx < param_index)
141
+ new_param_num = param_num - nulls_before
142
+ return exp.Parameter(this=str(new_param_num))
143
+ except (ValueError, AttributeError):
144
+ pass
145
+
146
+ return node
147
+
148
+ # Transform the AST
149
+ modified_expression = expression.transform(transform_node)
150
+
151
+ # Remove NULL parameters from the parameter list
152
+ cleaned_params: Any
153
+ if isinstance(parameters, (list, tuple)):
154
+ cleaned_params = [p for i, p in enumerate(parameters) if i not in null_positions]
155
+ elif isinstance(parameters, dict):
156
+ cleaned_params_dict = {}
157
+ new_num = 1
158
+ for val in parameters.values():
159
+ if val is not None:
160
+ cleaned_params_dict[str(new_num)] = val
161
+ new_num += 1
162
+ cleaned_params = cleaned_params_dict
163
+ else:
164
+ cleaned_params = parameters
165
+
166
+ return modified_expression, cleaned_params
167
+
168
+
169
+ def get_adbc_statement_config(detected_dialect: str) -> StatementConfig:
170
+ """Create ADBC statement configuration for the specified dialect."""
171
+ default_style, supported_styles = DIALECT_PARAMETER_STYLES.get(
172
+ detected_dialect, (ParameterStyle.QMARK, [ParameterStyle.QMARK])
173
+ )
174
+
175
+ type_map = get_type_coercion_map(detected_dialect)
176
+
177
+ sqlglot_dialect = "postgres" if detected_dialect == "postgresql" else detected_dialect
178
+
179
+ parameter_config = ParameterStyleConfig(
180
+ default_parameter_style=default_style,
181
+ supported_parameter_styles=set(supported_styles),
182
+ default_execution_parameter_style=default_style,
183
+ supported_execution_parameter_styles=set(supported_styles),
184
+ type_coercion_map=type_map,
185
+ has_native_list_expansion=True,
186
+ needs_static_script_compilation=False,
187
+ preserve_parameter_format=True,
188
+ ast_transformer=_adbc_ast_transformer if detected_dialect in {"postgres", "postgresql"} else None,
189
+ )
190
+
191
+ return StatementConfig(
192
+ dialect=sqlglot_dialect,
193
+ parameter_config=parameter_config,
194
+ enable_parsing=True,
195
+ enable_validation=True,
196
+ enable_caching=True,
197
+ enable_parameter_type_wrapping=True,
198
+ )
199
+
200
+
201
+ def _convert_array_for_postgres_adbc(value: Any) -> Any:
202
+ """Convert array values for PostgreSQL ADBC compatibility."""
203
+ if isinstance(value, tuple):
204
+ return list(value)
205
+ return value
206
+
207
+
208
+ def get_type_coercion_map(dialect: str) -> "dict[type, Any]":
209
+ """Get type coercion map for Arrow/ADBC type handling."""
210
+ type_map = {
211
+ datetime.datetime: lambda x: x,
212
+ datetime.date: lambda x: x,
213
+ datetime.time: lambda x: x,
214
+ decimal.Decimal: float,
215
+ bool: lambda x: x,
216
+ int: lambda x: x,
217
+ float: lambda x: x,
218
+ str: lambda x: x,
219
+ bytes: lambda x: x,
220
+ tuple: _convert_array_for_postgres_adbc,
221
+ list: _convert_array_for_postgres_adbc,
222
+ dict: lambda x: x,
223
+ }
224
+
225
+ if dialect in {"postgres", "postgresql"}:
226
+ type_map[dict] = lambda x: to_json(x) if x is not None else None
227
+
228
+ return type_map
229
+
230
+
231
+ class AdbcCursor:
232
+ """Context manager for ADBC cursor management."""
233
+
234
+ __slots__ = ("connection", "cursor")
235
+
236
+ def __init__(self, connection: "AdbcConnection") -> None:
237
+ self.connection = connection
238
+ self.cursor: Optional[Cursor] = None
239
+
240
+ def __enter__(self) -> "Cursor":
241
+ self.cursor = self.connection.cursor()
242
+ return self.cursor
243
+
244
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
245
+ _ = (exc_type, exc_val, exc_tb)
246
+ if self.cursor is not None:
247
+ with contextlib.suppress(Exception):
248
+ self.cursor.close() # type: ignore[no-untyped-call]
249
+
250
+
251
+ class AdbcExceptionHandler:
252
+ """Custom sync context manager for handling ADBC database exceptions."""
253
+
254
+ __slots__ = ()
255
+
256
+ def __enter__(self) -> None:
257
+ return None
258
+
259
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
260
+ if exc_type is None:
261
+ return
262
+
263
+ try:
264
+ from adbc_driver_manager.dbapi import DatabaseError, IntegrityError, OperationalError, ProgrammingError
265
+
266
+ if issubclass(exc_type, IntegrityError):
267
+ e = exc_val
268
+ msg = f"ADBC integrity constraint violation: {e}"
269
+ raise SQLSpecError(msg) from e
270
+ if issubclass(exc_type, ProgrammingError):
271
+ e = exc_val
272
+ error_msg = str(e).lower()
273
+ if "syntax" in error_msg or "parse" in error_msg:
274
+ msg = f"ADBC SQL syntax error: {e}"
275
+ raise SQLParsingError(msg) from e
276
+ msg = f"ADBC programming error: {e}"
277
+ raise SQLSpecError(msg) from e
278
+ if issubclass(exc_type, OperationalError):
279
+ e = exc_val
280
+ msg = f"ADBC operational error: {e}"
281
+ raise SQLSpecError(msg) from e
282
+ if issubclass(exc_type, DatabaseError):
283
+ e = exc_val
284
+ msg = f"ADBC database error: {e}"
285
+ raise SQLSpecError(msg) from e
286
+ except ImportError:
287
+ pass
288
+ if issubclass(exc_type, Exception):
289
+ e = exc_val
290
+ error_msg = str(e).lower()
291
+ if "parse" in error_msg or "syntax" in error_msg:
292
+ msg = f"SQL parsing failed: {e}"
293
+ raise SQLParsingError(msg) from e
294
+ msg = f"Unexpected database operation error: {e}"
295
+ raise SQLSpecError(msg) from e
296
+
297
+
298
+ class AdbcDriver(SyncDriverAdapterBase):
299
+ """ADBC driver for Arrow Database Connectivity.
300
+
301
+ Provides database connectivity through ADBC with support for:
302
+ - Multi-database dialect support with automatic detection
303
+ - Arrow-native data handling with type coercion
304
+ - Parameter style conversion for different backends
305
+ - Transaction management with proper error handling
306
+ """
307
+
308
+ __slots__ = ("_detected_dialect", "dialect")
309
+
310
+ def __init__(
311
+ self,
312
+ connection: "AdbcConnection",
313
+ statement_config: "Optional[StatementConfig]" = None,
314
+ driver_features: "Optional[dict[str, Any]]" = None,
315
+ ) -> None:
316
+ self._detected_dialect = self._get_dialect(connection)
317
+
318
+ if statement_config is None:
319
+ cache_config = get_cache_config()
320
+ base_config = get_adbc_statement_config(self._detected_dialect)
321
+ enhanced_config = base_config.replace(
322
+ enable_caching=cache_config.compiled_cache_enabled, enable_parsing=True, enable_validation=True
323
+ )
324
+ statement_config = enhanced_config
325
+
326
+ super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
327
+ self.dialect = statement_config.dialect
328
+
329
+ @staticmethod
330
+ def _ensure_pyarrow_installed() -> None:
331
+ """Ensure PyArrow is installed for Arrow operations."""
332
+ from sqlspec.typing import PYARROW_INSTALLED
333
+
334
+ if not PYARROW_INSTALLED:
335
+ raise MissingDependencyError(package="pyarrow", install_package="arrow")
336
+
337
+ @staticmethod
338
+ def _get_dialect(connection: "AdbcConnection") -> str:
339
+ """Detect database dialect from ADBC connection information."""
340
+ try:
341
+ driver_info = connection.adbc_get_info()
342
+ vendor_name = driver_info.get("vendor_name", "").lower()
343
+ driver_name = driver_info.get("driver_name", "").lower()
344
+
345
+ for dialect, patterns in DIALECT_PATTERNS.items():
346
+ if any(pattern in vendor_name or pattern in driver_name for pattern in patterns):
347
+ logger.debug("ADBC dialect detected: %s (from %s/%s)", dialect, vendor_name, driver_name)
348
+ return dialect
349
+ except Exception as e:
350
+ logger.debug("ADBC dialect detection failed: %s", e)
351
+
352
+ logger.warning("Could not reliably determine ADBC dialect from driver info. Defaulting to 'postgres'.")
353
+ return "postgres"
354
+
355
+ def _handle_postgres_rollback(self, cursor: "Cursor") -> None:
356
+ """Execute rollback for PostgreSQL after transaction failure."""
357
+ if self.dialect == "postgres":
358
+ with contextlib.suppress(Exception):
359
+ cursor.execute("ROLLBACK")
360
+ logger.debug("PostgreSQL rollback executed after ADBC transaction failure")
361
+
362
+ def _handle_postgres_empty_parameters(self, parameters: Any) -> Any:
363
+ """Process empty parameters for PostgreSQL compatibility."""
364
+ if self.dialect == "postgres" and isinstance(parameters, dict) and not parameters:
365
+ return None
366
+ return parameters
367
+
368
+ def with_cursor(self, connection: "AdbcConnection") -> "AdbcCursor":
369
+ """Create context manager for ADBC cursor."""
370
+ return AdbcCursor(connection)
371
+
372
+ def handle_database_exceptions(self) -> "AbstractContextManager[None]":
373
+ """Handle database-specific exceptions and wrap them appropriately."""
374
+ return AdbcExceptionHandler()
375
+
376
+ def _try_special_handling(self, cursor: "Cursor", statement: SQL) -> "Optional[SQLResult]":
377
+ """Handle ADBC-specific operations.
378
+
379
+ Args:
380
+ cursor: ADBC cursor object
381
+ statement: SQL statement to analyze
382
+
383
+ Returns:
384
+ SQLResult if special operation was handled, None for standard execution
385
+ """
386
+ _ = (cursor, statement)
387
+ return None
388
+
389
+ def _execute_many(self, cursor: "Cursor", statement: SQL) -> "ExecutionResult":
390
+ """Execute SQL with multiple parameter sets using batch processing."""
391
+ sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
392
+
393
+ try:
394
+ if not prepared_parameters:
395
+ cursor._rowcount = 0
396
+ row_count = 0
397
+ elif isinstance(prepared_parameters, list) and prepared_parameters:
398
+ processed_params = []
399
+ for param_set in prepared_parameters:
400
+ postgres_compatible = self._handle_postgres_empty_parameters(param_set)
401
+ formatted_params = self.prepare_driver_parameters(
402
+ postgres_compatible, self.statement_config, is_many=False
403
+ )
404
+ processed_params.append(formatted_params)
405
+
406
+ cursor.executemany(sql, processed_params)
407
+ row_count = cursor.rowcount if cursor.rowcount is not None else -1
408
+ else:
409
+ cursor.executemany(sql, prepared_parameters)
410
+ row_count = cursor.rowcount if cursor.rowcount is not None else -1
411
+
412
+ except Exception:
413
+ self._handle_postgres_rollback(cursor)
414
+ logger.exception("ADBC executemany failed")
415
+ raise
416
+
417
+ return self.create_execution_result(cursor, rowcount_override=row_count, is_many_result=True)
418
+
419
+ def _execute_statement(self, cursor: "Cursor", statement: SQL) -> "ExecutionResult":
420
+ """Execute single SQL statement with ADBC-specific data handling."""
421
+ sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
422
+
423
+ try:
424
+ postgres_compatible_params = self._handle_postgres_empty_parameters(prepared_parameters)
425
+ cursor.execute(sql, parameters=postgres_compatible_params)
426
+
427
+ except Exception:
428
+ self._handle_postgres_rollback(cursor)
429
+ raise
430
+
431
+ if statement.returns_rows():
432
+ fetched_data = cursor.fetchall()
433
+ column_names = [col[0] for col in cursor.description or []]
434
+
435
+ if fetched_data and isinstance(fetched_data[0], tuple):
436
+ dict_data: list[dict[Any, Any]] = [dict(zip(column_names, row)) for row in fetched_data]
437
+ else:
438
+ dict_data = fetched_data # type: ignore[assignment]
439
+
440
+ return self.create_execution_result(
441
+ cursor,
442
+ selected_data=cast("list[dict[str, Any]]", dict_data),
443
+ column_names=column_names,
444
+ data_row_count=len(dict_data),
445
+ is_select_result=True,
446
+ )
447
+
448
+ row_count = cursor.rowcount if cursor.rowcount is not None else -1
449
+ return self.create_execution_result(cursor, rowcount_override=row_count)
450
+
451
+ def _execute_script(self, cursor: "Cursor", statement: "SQL") -> "ExecutionResult":
452
+ """Execute SQL script with ADBC-specific transaction handling."""
453
+ if statement.is_script:
454
+ sql = statement._raw_sql
455
+ prepared_parameters: list[Any] = []
456
+ else:
457
+ sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
458
+
459
+ statements = self.split_script_statements(sql, self.statement_config, strip_trailing_semicolon=True)
460
+
461
+ successful_count = 0
462
+ last_rowcount = 0
463
+
464
+ try:
465
+ for stmt in statements:
466
+ if prepared_parameters:
467
+ postgres_compatible_params = self._handle_postgres_empty_parameters(prepared_parameters)
468
+ cursor.execute(stmt, parameters=postgres_compatible_params)
469
+ else:
470
+ cursor.execute(stmt)
471
+ successful_count += 1
472
+ if cursor.rowcount is not None:
473
+ last_rowcount = cursor.rowcount
474
+ except Exception:
475
+ self._handle_postgres_rollback(cursor)
476
+ logger.exception("ADBC script execution failed")
477
+ raise
478
+
479
+ return self.create_execution_result(
480
+ cursor,
481
+ statement_count=len(statements),
482
+ successful_statements=successful_count,
483
+ rowcount_override=last_rowcount,
484
+ is_script_result=True,
485
+ )
486
+
487
+ def begin(self) -> None:
488
+ """Begin database transaction."""
489
+ try:
490
+ with self.with_cursor(self.connection) as cursor:
491
+ cursor.execute("BEGIN")
492
+ except Exception as e:
493
+ msg = f"Failed to begin ADBC transaction: {e}"
494
+ raise SQLSpecError(msg) from e
495
+
496
+ def rollback(self) -> None:
497
+ """Rollback database transaction."""
498
+ try:
499
+ with self.with_cursor(self.connection) as cursor:
500
+ cursor.execute("ROLLBACK")
501
+ except Exception as e:
502
+ msg = f"Failed to rollback ADBC transaction: {e}"
503
+ raise SQLSpecError(msg) from e
504
+
505
+ def commit(self) -> None:
506
+ """Commit database transaction."""
507
+ try:
508
+ with self.with_cursor(self.connection) as cursor:
509
+ cursor.execute("COMMIT")
510
+ except Exception as e:
511
+ msg = f"Failed to commit ADBC transaction: {e}"
512
+ raise SQLSpecError(msg) from e
@@ -0,0 +1,19 @@
1
+ from sqlspec.adapters.aiosqlite._types import AiosqliteConnection
2
+ from sqlspec.adapters.aiosqlite.config import AiosqliteConfig, AiosqliteConnectionParams, AiosqliteConnectionPool
3
+ from sqlspec.adapters.aiosqlite.driver import (
4
+ AiosqliteCursor,
5
+ AiosqliteDriver,
6
+ AiosqliteExceptionHandler,
7
+ aiosqlite_statement_config,
8
+ )
9
+
10
+ __all__ = (
11
+ "AiosqliteConfig",
12
+ "AiosqliteConnection",
13
+ "AiosqliteConnectionParams",
14
+ "AiosqliteConnectionPool",
15
+ "AiosqliteCursor",
16
+ "AiosqliteDriver",
17
+ "AiosqliteExceptionHandler",
18
+ "aiosqlite_statement_config",
19
+ )
@@ -0,0 +1,13 @@
1
+ # pyright: reportCallIssue=false, reportAttributeAccessIssue=false, reportArgumentType=false
2
+ from typing import TYPE_CHECKING
3
+
4
+ import aiosqlite
5
+
6
+ if TYPE_CHECKING:
7
+ from typing_extensions import TypeAlias
8
+
9
+ AiosqliteConnection: TypeAlias = aiosqlite.Connection
10
+ else:
11
+ AiosqliteConnection = aiosqlite.Connection
12
+
13
+ __all__ = ("AiosqliteConnection",)