sqlspec 0.16.1__cp310-cp310-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.
- 51ff5a9eadfdefd49f98__mypyc.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/__init__.py +92 -0
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +14 -0
- sqlspec/_serialization.py +77 -0
- sqlspec/_sql.py +1780 -0
- sqlspec/_typing.py +680 -0
- sqlspec/adapters/__init__.py +0 -0
- sqlspec/adapters/adbc/__init__.py +5 -0
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +361 -0
- sqlspec/adapters/adbc/driver.py +512 -0
- sqlspec/adapters/aiosqlite/__init__.py +19 -0
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +253 -0
- sqlspec/adapters/aiosqlite/driver.py +248 -0
- sqlspec/adapters/asyncmy/__init__.py +19 -0
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +180 -0
- sqlspec/adapters/asyncmy/driver.py +274 -0
- sqlspec/adapters/asyncpg/__init__.py +21 -0
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +229 -0
- sqlspec/adapters/asyncpg/driver.py +344 -0
- sqlspec/adapters/bigquery/__init__.py +18 -0
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +298 -0
- sqlspec/adapters/bigquery/driver.py +558 -0
- sqlspec/adapters/duckdb/__init__.py +22 -0
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +504 -0
- sqlspec/adapters/duckdb/driver.py +368 -0
- sqlspec/adapters/oracledb/__init__.py +32 -0
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +317 -0
- sqlspec/adapters/oracledb/driver.py +538 -0
- sqlspec/adapters/psqlpy/__init__.py +16 -0
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +214 -0
- sqlspec/adapters/psqlpy/driver.py +530 -0
- sqlspec/adapters/psycopg/__init__.py +32 -0
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +426 -0
- sqlspec/adapters/psycopg/driver.py +796 -0
- sqlspec/adapters/sqlite/__init__.py +15 -0
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +240 -0
- sqlspec/adapters/sqlite/driver.py +294 -0
- sqlspec/base.py +571 -0
- sqlspec/builder/__init__.py +62 -0
- sqlspec/builder/_base.py +473 -0
- sqlspec/builder/_column.py +320 -0
- sqlspec/builder/_ddl.py +1346 -0
- sqlspec/builder/_ddl_utils.py +103 -0
- sqlspec/builder/_delete.py +76 -0
- sqlspec/builder/_insert.py +256 -0
- sqlspec/builder/_merge.py +71 -0
- sqlspec/builder/_parsing_utils.py +140 -0
- sqlspec/builder/_select.py +170 -0
- sqlspec/builder/_update.py +188 -0
- sqlspec/builder/mixins/__init__.py +55 -0
- sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
- sqlspec/builder/mixins/_delete_operations.py +41 -0
- sqlspec/builder/mixins/_insert_operations.py +244 -0
- sqlspec/builder/mixins/_join_operations.py +122 -0
- sqlspec/builder/mixins/_merge_operations.py +476 -0
- sqlspec/builder/mixins/_order_limit_operations.py +135 -0
- sqlspec/builder/mixins/_pivot_operations.py +153 -0
- sqlspec/builder/mixins/_select_operations.py +603 -0
- sqlspec/builder/mixins/_update_operations.py +187 -0
- sqlspec/builder/mixins/_where_clause.py +621 -0
- sqlspec/cli.py +247 -0
- sqlspec/config.py +395 -0
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/cache.py +871 -0
- sqlspec/core/compiler.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/compiler.py +417 -0
- sqlspec/core/filters.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/filters.py +830 -0
- sqlspec/core/hashing.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters.py +1237 -0
- sqlspec/core/result.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/result.py +677 -0
- sqlspec/core/splitter.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/splitter.py +819 -0
- sqlspec/core/statement.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/core/statement.py +676 -0
- sqlspec/driver/__init__.py +19 -0
- sqlspec/driver/_async.py +502 -0
- sqlspec/driver/_common.py +631 -0
- sqlspec/driver/_sync.py +503 -0
- sqlspec/driver/mixins/__init__.py +6 -0
- sqlspec/driver/mixins/_result_tools.py +193 -0
- sqlspec/driver/mixins/_sql_translator.py +86 -0
- sqlspec/exceptions.py +193 -0
- sqlspec/extensions/__init__.py +0 -0
- sqlspec/extensions/aiosql/__init__.py +10 -0
- sqlspec/extensions/aiosql/adapter.py +461 -0
- sqlspec/extensions/litestar/__init__.py +6 -0
- sqlspec/extensions/litestar/_utils.py +52 -0
- sqlspec/extensions/litestar/cli.py +48 -0
- sqlspec/extensions/litestar/config.py +92 -0
- sqlspec/extensions/litestar/handlers.py +260 -0
- sqlspec/extensions/litestar/plugin.py +145 -0
- sqlspec/extensions/litestar/providers.py +454 -0
- sqlspec/loader.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/loader.py +760 -0
- sqlspec/migrations/__init__.py +35 -0
- sqlspec/migrations/base.py +414 -0
- sqlspec/migrations/commands.py +443 -0
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +213 -0
- sqlspec/migrations/tracker.py +140 -0
- sqlspec/migrations/utils.py +129 -0
- sqlspec/protocols.py +407 -0
- sqlspec/py.typed +0 -0
- sqlspec/storage/__init__.py +23 -0
- sqlspec/storage/backends/__init__.py +0 -0
- sqlspec/storage/backends/base.py +163 -0
- sqlspec/storage/backends/fsspec.py +386 -0
- sqlspec/storage/backends/obstore.py +459 -0
- sqlspec/storage/capabilities.py +102 -0
- sqlspec/storage/registry.py +239 -0
- sqlspec/typing.py +299 -0
- sqlspec/utils/__init__.py +3 -0
- sqlspec/utils/correlation.py +150 -0
- sqlspec/utils/deprecation.py +106 -0
- sqlspec/utils/fixtures.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/fixtures.py +58 -0
- sqlspec/utils/logging.py +127 -0
- sqlspec/utils/module_loader.py +89 -0
- sqlspec/utils/serializers.py +4 -0
- sqlspec/utils/singleton.py +32 -0
- sqlspec/utils/sync_tools.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/sync_tools.py +237 -0
- sqlspec/utils/text.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/text.py +96 -0
- sqlspec/utils/type_guards.cpython-310-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/type_guards.py +1139 -0
- sqlspec-0.16.1.dist-info/METADATA +365 -0
- sqlspec-0.16.1.dist-info/RECORD +148 -0
- sqlspec-0.16.1.dist-info/WHEEL +7 -0
- sqlspec-0.16.1.dist-info/entry_points.txt +2 -0
- sqlspec-0.16.1.dist-info/licenses/LICENSE +21 -0
- sqlspec-0.16.1.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",)
|