sqlspec 0.17.1__py3-none-any.whl → 0.19.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.
- sqlspec/__init__.py +1 -1
- sqlspec/_sql.py +54 -159
- sqlspec/adapters/adbc/config.py +24 -30
- sqlspec/adapters/adbc/driver.py +42 -61
- sqlspec/adapters/aiosqlite/config.py +5 -10
- sqlspec/adapters/aiosqlite/driver.py +9 -25
- sqlspec/adapters/aiosqlite/pool.py +43 -35
- sqlspec/adapters/asyncmy/config.py +10 -7
- sqlspec/adapters/asyncmy/driver.py +18 -39
- sqlspec/adapters/asyncpg/config.py +4 -0
- sqlspec/adapters/asyncpg/driver.py +32 -79
- sqlspec/adapters/bigquery/config.py +12 -65
- sqlspec/adapters/bigquery/driver.py +39 -133
- sqlspec/adapters/duckdb/config.py +11 -15
- sqlspec/adapters/duckdb/driver.py +61 -85
- sqlspec/adapters/duckdb/pool.py +2 -5
- sqlspec/adapters/oracledb/_types.py +8 -1
- sqlspec/adapters/oracledb/config.py +55 -38
- sqlspec/adapters/oracledb/driver.py +35 -92
- sqlspec/adapters/oracledb/migrations.py +257 -0
- sqlspec/adapters/psqlpy/config.py +13 -9
- sqlspec/adapters/psqlpy/driver.py +28 -103
- sqlspec/adapters/psycopg/config.py +9 -5
- sqlspec/adapters/psycopg/driver.py +107 -175
- sqlspec/adapters/sqlite/config.py +7 -5
- sqlspec/adapters/sqlite/driver.py +37 -73
- sqlspec/adapters/sqlite/pool.py +3 -12
- sqlspec/base.py +19 -22
- sqlspec/builder/__init__.py +1 -1
- sqlspec/builder/_base.py +34 -20
- sqlspec/builder/_ddl.py +407 -183
- sqlspec/builder/_insert.py +1 -1
- sqlspec/builder/mixins/_insert_operations.py +26 -6
- sqlspec/builder/mixins/_merge_operations.py +1 -1
- sqlspec/builder/mixins/_select_operations.py +1 -5
- sqlspec/cli.py +281 -33
- sqlspec/config.py +183 -14
- sqlspec/core/__init__.py +89 -14
- sqlspec/core/cache.py +57 -104
- sqlspec/core/compiler.py +57 -112
- sqlspec/core/filters.py +1 -21
- sqlspec/core/hashing.py +13 -47
- sqlspec/core/parameters.py +272 -261
- sqlspec/core/result.py +12 -27
- sqlspec/core/splitter.py +17 -21
- sqlspec/core/statement.py +150 -159
- sqlspec/driver/_async.py +2 -15
- sqlspec/driver/_common.py +16 -95
- sqlspec/driver/_sync.py +2 -15
- sqlspec/driver/mixins/_result_tools.py +8 -29
- sqlspec/driver/mixins/_sql_translator.py +6 -8
- sqlspec/exceptions.py +1 -2
- sqlspec/extensions/litestar/plugin.py +15 -8
- sqlspec/loader.py +43 -115
- sqlspec/migrations/__init__.py +1 -1
- sqlspec/migrations/base.py +34 -45
- sqlspec/migrations/commands.py +34 -15
- sqlspec/migrations/loaders.py +1 -1
- sqlspec/migrations/runner.py +104 -19
- sqlspec/migrations/tracker.py +49 -2
- sqlspec/protocols.py +3 -6
- sqlspec/storage/__init__.py +4 -4
- sqlspec/storage/backends/fsspec.py +5 -6
- sqlspec/storage/backends/obstore.py +7 -8
- sqlspec/storage/registry.py +3 -3
- sqlspec/utils/__init__.py +2 -2
- sqlspec/utils/logging.py +6 -10
- sqlspec/utils/sync_tools.py +27 -4
- sqlspec/utils/text.py +6 -1
- {sqlspec-0.17.1.dist-info → sqlspec-0.19.0.dist-info}/METADATA +1 -1
- sqlspec-0.19.0.dist-info/RECORD +138 -0
- sqlspec/builder/_ddl_utils.py +0 -103
- sqlspec-0.17.1.dist-info/RECORD +0 -138
- {sqlspec-0.17.1.dist-info → sqlspec-0.19.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.17.1.dist-info → sqlspec-0.19.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.17.1.dist-info → sqlspec-0.19.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.17.1.dist-info → sqlspec-0.19.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/adapters/adbc/config.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""ADBC database configuration
|
|
1
|
+
"""ADBC database configuration."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from contextlib import contextmanager
|
|
@@ -60,16 +60,11 @@ __all__ = ("AdbcConfig", "AdbcConnectionParams")
|
|
|
60
60
|
class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
61
61
|
"""ADBC configuration for Arrow Database Connectivity.
|
|
62
62
|
|
|
63
|
-
ADBC
|
|
64
|
-
|
|
63
|
+
ADBC provides an interface for connecting to multiple database systems
|
|
64
|
+
with Arrow-native data transfer.
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
- Arrow data streaming
|
|
69
|
-
- Bulk ingestion operations
|
|
70
|
-
- Multiple database backends (PostgreSQL, SQLite, DuckDB, BigQuery, Snowflake, etc.)
|
|
71
|
-
- Driver path resolution
|
|
72
|
-
- Cloud database integrations
|
|
66
|
+
Supports multiple database backends including PostgreSQL, SQLite, DuckDB,
|
|
67
|
+
BigQuery, and Snowflake with automatic driver detection and loading.
|
|
73
68
|
"""
|
|
74
69
|
|
|
75
70
|
driver_type: ClassVar[type[AdbcDriver]] = AdbcDriver
|
|
@@ -79,15 +74,17 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
|
79
74
|
self,
|
|
80
75
|
*,
|
|
81
76
|
connection_config: Optional[Union[AdbcConnectionParams, dict[str, Any]]] = None,
|
|
82
|
-
statement_config: Optional[StatementConfig] = None,
|
|
83
77
|
migration_config: Optional[dict[str, Any]] = None,
|
|
78
|
+
statement_config: Optional[StatementConfig] = None,
|
|
79
|
+
driver_features: Optional[dict[str, Any]] = None,
|
|
84
80
|
) -> None:
|
|
85
|
-
"""Initialize
|
|
81
|
+
"""Initialize configuration.
|
|
86
82
|
|
|
87
83
|
Args:
|
|
88
84
|
connection_config: Connection configuration parameters
|
|
89
|
-
statement_config: Default SQL statement configuration
|
|
90
85
|
migration_config: Migration configuration
|
|
86
|
+
statement_config: Default SQL statement configuration
|
|
87
|
+
driver_features: Driver feature configuration
|
|
91
88
|
"""
|
|
92
89
|
if connection_config is None:
|
|
93
90
|
connection_config = {}
|
|
@@ -106,11 +103,11 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
|
106
103
|
connection_config=self.connection_config,
|
|
107
104
|
migration_config=migration_config,
|
|
108
105
|
statement_config=statement_config,
|
|
109
|
-
driver_features={},
|
|
106
|
+
driver_features=driver_features or {},
|
|
110
107
|
)
|
|
111
108
|
|
|
112
109
|
def _resolve_driver_name(self) -> str:
|
|
113
|
-
"""Resolve and normalize the
|
|
110
|
+
"""Resolve and normalize the driver name.
|
|
114
111
|
|
|
115
112
|
Returns:
|
|
116
113
|
The normalized driver connect function path.
|
|
@@ -164,7 +161,7 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
|
164
161
|
return "adbc_driver_sqlite.dbapi.connect"
|
|
165
162
|
|
|
166
163
|
def _get_connect_func(self) -> Callable[..., AdbcConnection]:
|
|
167
|
-
"""Get the
|
|
164
|
+
"""Get the driver connect function.
|
|
168
165
|
|
|
169
166
|
Returns:
|
|
170
167
|
The driver connect function.
|
|
@@ -182,7 +179,7 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
|
182
179
|
connect_func = import_string(driver_path_with_suffix)
|
|
183
180
|
except ImportError as e2:
|
|
184
181
|
msg = (
|
|
185
|
-
f"Failed to import
|
|
182
|
+
f"Failed to import connect function from '{driver_path}' or "
|
|
186
183
|
f"'{driver_path_with_suffix}'. Is the driver installed? "
|
|
187
184
|
f"Original errors: {e} / {e2}"
|
|
188
185
|
)
|
|
@@ -195,10 +192,10 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
|
195
192
|
return connect_func # type: ignore[no-any-return]
|
|
196
193
|
|
|
197
194
|
def _get_dialect(self) -> "DialectType":
|
|
198
|
-
"""Get the SQL dialect type based on the
|
|
195
|
+
"""Get the SQL dialect type based on the driver.
|
|
199
196
|
|
|
200
197
|
Returns:
|
|
201
|
-
The SQL dialect type for the
|
|
198
|
+
The SQL dialect type for the driver.
|
|
202
199
|
"""
|
|
203
200
|
try:
|
|
204
201
|
driver_path = self._resolve_driver_name()
|
|
@@ -239,14 +236,14 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
|
239
236
|
return (("qmark", "numeric"), "qmark")
|
|
240
237
|
|
|
241
238
|
except Exception:
|
|
242
|
-
logger.debug("Error resolving parameter styles
|
|
239
|
+
logger.debug("Error resolving parameter styles, using defaults")
|
|
243
240
|
return (("qmark",), "qmark")
|
|
244
241
|
|
|
245
242
|
def create_connection(self) -> AdbcConnection:
|
|
246
|
-
"""Create and return a new
|
|
243
|
+
"""Create and return a new connection using the specified driver.
|
|
247
244
|
|
|
248
245
|
Returns:
|
|
249
|
-
A new
|
|
246
|
+
A new connection instance.
|
|
250
247
|
|
|
251
248
|
Raises:
|
|
252
249
|
ImproperConfigurationError: If the connection could not be established.
|
|
@@ -258,20 +255,20 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
|
258
255
|
connection = connect_func(**connection_config_dict)
|
|
259
256
|
except Exception as e:
|
|
260
257
|
driver_name = self.connection_config.get("driver_name", "Unknown")
|
|
261
|
-
msg = f"Could not configure
|
|
258
|
+
msg = f"Could not configure connection using driver '{driver_name}'. Error: {e}"
|
|
262
259
|
raise ImproperConfigurationError(msg) from e
|
|
263
260
|
return connection
|
|
264
261
|
|
|
265
262
|
@contextmanager
|
|
266
263
|
def provide_connection(self, *args: Any, **kwargs: Any) -> "Generator[AdbcConnection, None, None]":
|
|
267
|
-
"""Provide
|
|
264
|
+
"""Provide a connection context manager.
|
|
268
265
|
|
|
269
266
|
Args:
|
|
270
267
|
*args: Additional arguments.
|
|
271
268
|
**kwargs: Additional keyword arguments.
|
|
272
269
|
|
|
273
270
|
Yields:
|
|
274
|
-
|
|
271
|
+
A connection instance.
|
|
275
272
|
"""
|
|
276
273
|
connection = self.create_connection()
|
|
277
274
|
try:
|
|
@@ -282,7 +279,7 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
|
282
279
|
def provide_session(
|
|
283
280
|
self, *args: Any, statement_config: "Optional[StatementConfig]" = None, **kwargs: Any
|
|
284
281
|
) -> "AbstractContextManager[AdbcDriver]":
|
|
285
|
-
"""Provide
|
|
282
|
+
"""Provide a driver session context manager.
|
|
286
283
|
|
|
287
284
|
Args:
|
|
288
285
|
*args: Additional arguments.
|
|
@@ -347,10 +344,7 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
|
347
344
|
return config
|
|
348
345
|
|
|
349
346
|
def get_signature_namespace(self) -> "dict[str, type[Any]]":
|
|
350
|
-
"""Get the signature namespace for
|
|
351
|
-
|
|
352
|
-
This provides all ADBC-specific types that Litestar needs to recognize
|
|
353
|
-
to avoid serialization attempts.
|
|
347
|
+
"""Get the signature namespace for types.
|
|
354
348
|
|
|
355
349
|
Returns:
|
|
356
350
|
Dictionary mapping type names to types.
|
sqlspec/adapters/adbc/driver.py
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
"""ADBC driver implementation for Arrow Database Connectivity.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
-
|
|
5
|
-
|
|
6
|
-
- Parameter style conversion for different database backends
|
|
7
|
-
- Transaction management with proper error handling
|
|
3
|
+
Provides ADBC driver integration with multi-dialect database connections,
|
|
4
|
+
Arrow-native data handling with type coercion, parameter style conversion
|
|
5
|
+
for different database backends, and transaction management.
|
|
8
6
|
"""
|
|
9
7
|
|
|
10
8
|
import contextlib
|
|
@@ -48,7 +46,7 @@ DIALECT_PARAMETER_STYLES = {
|
|
|
48
46
|
"postgres": (ParameterStyle.NUMERIC, [ParameterStyle.NUMERIC]),
|
|
49
47
|
"postgresql": (ParameterStyle.NUMERIC, [ParameterStyle.NUMERIC]),
|
|
50
48
|
"bigquery": (ParameterStyle.NAMED_AT, [ParameterStyle.NAMED_AT]),
|
|
51
|
-
"sqlite": (ParameterStyle.QMARK, [ParameterStyle.QMARK
|
|
49
|
+
"sqlite": (ParameterStyle.QMARK, [ParameterStyle.QMARK]),
|
|
52
50
|
"duckdb": (ParameterStyle.QMARK, [ParameterStyle.QMARK, ParameterStyle.NUMERIC, ParameterStyle.NAMED_DOLLAR]),
|
|
53
51
|
"mysql": (ParameterStyle.POSITIONAL_PYFORMAT, [ParameterStyle.POSITIONAL_PYFORMAT, ParameterStyle.NAMED_PYFORMAT]),
|
|
54
52
|
"snowflake": (ParameterStyle.QMARK, [ParameterStyle.QMARK, ParameterStyle.NUMERIC]),
|
|
@@ -56,17 +54,11 @@ DIALECT_PARAMETER_STYLES = {
|
|
|
56
54
|
|
|
57
55
|
|
|
58
56
|
def _adbc_ast_transformer(expression: Any, parameters: Any) -> tuple[Any, Any]:
|
|
59
|
-
"""
|
|
57
|
+
"""AST transformer for NULL parameter handling.
|
|
60
58
|
|
|
61
|
-
For PostgreSQL,
|
|
59
|
+
For PostgreSQL, replaces NULL parameter placeholders with NULL literals
|
|
62
60
|
in the AST to prevent Arrow from inferring 'na' types which cause binding errors.
|
|
63
61
|
|
|
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
62
|
Args:
|
|
71
63
|
expression: SQLGlot AST expression
|
|
72
64
|
parameters: Parameter values that may contain None
|
|
@@ -77,7 +69,6 @@ def _adbc_ast_transformer(expression: Any, parameters: Any) -> tuple[Any, Any]:
|
|
|
77
69
|
if not parameters:
|
|
78
70
|
return expression, parameters
|
|
79
71
|
|
|
80
|
-
# Detect NULL parameter positions
|
|
81
72
|
null_positions = set()
|
|
82
73
|
if isinstance(parameters, (list, tuple)):
|
|
83
74
|
for i, param in enumerate(parameters):
|
|
@@ -96,47 +87,42 @@ def _adbc_ast_transformer(expression: Any, parameters: Any) -> tuple[Any, Any]:
|
|
|
96
87
|
if not null_positions:
|
|
97
88
|
return expression, parameters
|
|
98
89
|
|
|
99
|
-
# Track position for QMARK-style placeholders
|
|
100
90
|
qmark_position = [0]
|
|
101
91
|
|
|
102
92
|
def transform_node(node: Any) -> Any:
|
|
103
|
-
"""Transform parameter nodes to NULL literals and renumber remaining ones."""
|
|
104
|
-
# Handle QMARK-style placeholders (?, ?, ?)
|
|
105
93
|
if isinstance(node, exp.Placeholder) and (not hasattr(node, "this") or node.this is None):
|
|
106
94
|
current_pos = qmark_position[0]
|
|
107
95
|
qmark_position[0] += 1
|
|
108
96
|
|
|
109
97
|
if current_pos in null_positions:
|
|
110
98
|
return exp.Null()
|
|
111
|
-
|
|
99
|
+
|
|
112
100
|
return node
|
|
113
101
|
|
|
114
|
-
# Handle PostgreSQL-style placeholders ($1, $2, etc.)
|
|
115
102
|
if isinstance(node, exp.Placeholder) and hasattr(node, "this") and node.this is not None:
|
|
116
103
|
try:
|
|
117
104
|
param_str = str(node.this).lstrip("$")
|
|
118
105
|
param_num = int(param_str)
|
|
119
|
-
param_index = param_num - 1
|
|
106
|
+
param_index = param_num - 1
|
|
120
107
|
|
|
121
108
|
if param_index in null_positions:
|
|
122
109
|
return exp.Null()
|
|
123
|
-
|
|
110
|
+
|
|
124
111
|
nulls_before = sum(1 for idx in null_positions if idx < param_index)
|
|
125
112
|
new_param_num = param_num - nulls_before
|
|
126
113
|
return exp.Placeholder(this=f"${new_param_num}")
|
|
127
114
|
except (ValueError, AttributeError):
|
|
128
115
|
pass
|
|
129
116
|
|
|
130
|
-
# Handle generic parameter nodes
|
|
131
117
|
if isinstance(node, exp.Parameter) and hasattr(node, "this"):
|
|
132
118
|
try:
|
|
133
119
|
param_str = str(node.this)
|
|
134
120
|
param_num = int(param_str)
|
|
135
|
-
param_index = param_num - 1
|
|
121
|
+
param_index = param_num - 1
|
|
136
122
|
|
|
137
123
|
if param_index in null_positions:
|
|
138
124
|
return exp.Null()
|
|
139
|
-
|
|
125
|
+
|
|
140
126
|
nulls_before = sum(1 for idx in null_positions if idx < param_index)
|
|
141
127
|
new_param_num = param_num - nulls_before
|
|
142
128
|
return exp.Parameter(this=str(new_param_num))
|
|
@@ -145,10 +131,8 @@ def _adbc_ast_transformer(expression: Any, parameters: Any) -> tuple[Any, Any]:
|
|
|
145
131
|
|
|
146
132
|
return node
|
|
147
133
|
|
|
148
|
-
# Transform the AST
|
|
149
134
|
modified_expression = expression.transform(transform_node)
|
|
150
135
|
|
|
151
|
-
# Remove NULL parameters from the parameter list
|
|
152
136
|
cleaned_params: Any
|
|
153
137
|
if isinstance(parameters, (list, tuple)):
|
|
154
138
|
cleaned_params = [p for i, p in enumerate(parameters) if i not in null_positions]
|
|
@@ -167,7 +151,7 @@ def _adbc_ast_transformer(expression: Any, parameters: Any) -> tuple[Any, Any]:
|
|
|
167
151
|
|
|
168
152
|
|
|
169
153
|
def get_adbc_statement_config(detected_dialect: str) -> StatementConfig:
|
|
170
|
-
"""Create
|
|
154
|
+
"""Create statement configuration for the specified dialect."""
|
|
171
155
|
default_style, supported_styles = DIALECT_PARAMETER_STYLES.get(
|
|
172
156
|
detected_dialect, (ParameterStyle.QMARK, [ParameterStyle.QMARK])
|
|
173
157
|
)
|
|
@@ -199,14 +183,14 @@ def get_adbc_statement_config(detected_dialect: str) -> StatementConfig:
|
|
|
199
183
|
|
|
200
184
|
|
|
201
185
|
def _convert_array_for_postgres_adbc(value: Any) -> Any:
|
|
202
|
-
"""Convert array values for PostgreSQL
|
|
186
|
+
"""Convert array values for PostgreSQL compatibility."""
|
|
203
187
|
if isinstance(value, tuple):
|
|
204
188
|
return list(value)
|
|
205
189
|
return value
|
|
206
190
|
|
|
207
191
|
|
|
208
192
|
def get_type_coercion_map(dialect: str) -> "dict[type, Any]":
|
|
209
|
-
"""Get type coercion map for Arrow
|
|
193
|
+
"""Get type coercion map for Arrow type handling."""
|
|
210
194
|
type_map = {
|
|
211
195
|
datetime.datetime: lambda x: x,
|
|
212
196
|
datetime.date: lambda x: x,
|
|
@@ -229,7 +213,7 @@ def get_type_coercion_map(dialect: str) -> "dict[type, Any]":
|
|
|
229
213
|
|
|
230
214
|
|
|
231
215
|
class AdbcCursor:
|
|
232
|
-
"""Context manager for
|
|
216
|
+
"""Context manager for cursor management."""
|
|
233
217
|
|
|
234
218
|
__slots__ = ("connection", "cursor")
|
|
235
219
|
|
|
@@ -249,7 +233,7 @@ class AdbcCursor:
|
|
|
249
233
|
|
|
250
234
|
|
|
251
235
|
class AdbcExceptionHandler:
|
|
252
|
-
"""
|
|
236
|
+
"""Context manager for handling database exceptions."""
|
|
253
237
|
|
|
254
238
|
__slots__ = ()
|
|
255
239
|
|
|
@@ -265,23 +249,23 @@ class AdbcExceptionHandler:
|
|
|
265
249
|
|
|
266
250
|
if issubclass(exc_type, IntegrityError):
|
|
267
251
|
e = exc_val
|
|
268
|
-
msg = f"
|
|
252
|
+
msg = f"Integrity constraint violation: {e}"
|
|
269
253
|
raise SQLSpecError(msg) from e
|
|
270
254
|
if issubclass(exc_type, ProgrammingError):
|
|
271
255
|
e = exc_val
|
|
272
256
|
error_msg = str(e).lower()
|
|
273
257
|
if "syntax" in error_msg or "parse" in error_msg:
|
|
274
|
-
msg = f"
|
|
258
|
+
msg = f"SQL syntax error: {e}"
|
|
275
259
|
raise SQLParsingError(msg) from e
|
|
276
|
-
msg = f"
|
|
260
|
+
msg = f"Programming error: {e}"
|
|
277
261
|
raise SQLSpecError(msg) from e
|
|
278
262
|
if issubclass(exc_type, OperationalError):
|
|
279
263
|
e = exc_val
|
|
280
|
-
msg = f"
|
|
264
|
+
msg = f"Operational error: {e}"
|
|
281
265
|
raise SQLSpecError(msg) from e
|
|
282
266
|
if issubclass(exc_type, DatabaseError):
|
|
283
267
|
e = exc_val
|
|
284
|
-
msg = f"
|
|
268
|
+
msg = f"Database error: {e}"
|
|
285
269
|
raise SQLSpecError(msg) from e
|
|
286
270
|
except ImportError:
|
|
287
271
|
pass
|
|
@@ -298,11 +282,9 @@ class AdbcExceptionHandler:
|
|
|
298
282
|
class AdbcDriver(SyncDriverAdapterBase):
|
|
299
283
|
"""ADBC driver for Arrow Database Connectivity.
|
|
300
284
|
|
|
301
|
-
Provides database connectivity through ADBC with
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
- Parameter style conversion for different backends
|
|
305
|
-
- Transaction management with proper error handling
|
|
285
|
+
Provides database connectivity through ADBC with multi-database dialect
|
|
286
|
+
support, Arrow-native data handling with type coercion, parameter style
|
|
287
|
+
conversion for different backends, and transaction management.
|
|
306
288
|
"""
|
|
307
289
|
|
|
308
290
|
__slots__ = ("_detected_dialect", "dialect")
|
|
@@ -318,17 +300,16 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
318
300
|
if statement_config is None:
|
|
319
301
|
cache_config = get_cache_config()
|
|
320
302
|
base_config = get_adbc_statement_config(self._detected_dialect)
|
|
321
|
-
|
|
303
|
+
statement_config = base_config.replace(
|
|
322
304
|
enable_caching=cache_config.compiled_cache_enabled, enable_parsing=True, enable_validation=True
|
|
323
305
|
)
|
|
324
|
-
statement_config = enhanced_config
|
|
325
306
|
|
|
326
307
|
super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
|
|
327
308
|
self.dialect = statement_config.dialect
|
|
328
309
|
|
|
329
310
|
@staticmethod
|
|
330
311
|
def _ensure_pyarrow_installed() -> None:
|
|
331
|
-
"""Ensure PyArrow is installed
|
|
312
|
+
"""Ensure PyArrow is installed."""
|
|
332
313
|
from sqlspec.typing import PYARROW_INSTALLED
|
|
333
314
|
|
|
334
315
|
if not PYARROW_INSTALLED:
|
|
@@ -336,7 +317,7 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
336
317
|
|
|
337
318
|
@staticmethod
|
|
338
319
|
def _get_dialect(connection: "AdbcConnection") -> str:
|
|
339
|
-
"""Detect database dialect from
|
|
320
|
+
"""Detect database dialect from connection information."""
|
|
340
321
|
try:
|
|
341
322
|
driver_info = connection.adbc_get_info()
|
|
342
323
|
vendor_name = driver_info.get("vendor_name", "").lower()
|
|
@@ -344,12 +325,12 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
344
325
|
|
|
345
326
|
for dialect, patterns in DIALECT_PATTERNS.items():
|
|
346
327
|
if any(pattern in vendor_name or pattern in driver_name for pattern in patterns):
|
|
347
|
-
logger.debug("
|
|
328
|
+
logger.debug("Dialect detected: %s (from %s/%s)", dialect, vendor_name, driver_name)
|
|
348
329
|
return dialect
|
|
349
330
|
except Exception as e:
|
|
350
|
-
logger.debug("
|
|
331
|
+
logger.debug("Dialect detection failed: %s", e)
|
|
351
332
|
|
|
352
|
-
logger.warning("Could not
|
|
333
|
+
logger.warning("Could not determine dialect from driver info. Defaulting to 'postgres'.")
|
|
353
334
|
return "postgres"
|
|
354
335
|
|
|
355
336
|
def _handle_postgres_rollback(self, cursor: "Cursor") -> None:
|
|
@@ -357,7 +338,7 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
357
338
|
if self.dialect == "postgres":
|
|
358
339
|
with contextlib.suppress(Exception):
|
|
359
340
|
cursor.execute("ROLLBACK")
|
|
360
|
-
logger.debug("PostgreSQL rollback executed after
|
|
341
|
+
logger.debug("PostgreSQL rollback executed after transaction failure")
|
|
361
342
|
|
|
362
343
|
def _handle_postgres_empty_parameters(self, parameters: Any) -> Any:
|
|
363
344
|
"""Process empty parameters for PostgreSQL compatibility."""
|
|
@@ -366,7 +347,7 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
366
347
|
return parameters
|
|
367
348
|
|
|
368
349
|
def with_cursor(self, connection: "AdbcConnection") -> "AdbcCursor":
|
|
369
|
-
"""Create context manager for
|
|
350
|
+
"""Create context manager for cursor."""
|
|
370
351
|
return AdbcCursor(connection)
|
|
371
352
|
|
|
372
353
|
def handle_database_exceptions(self) -> "AbstractContextManager[None]":
|
|
@@ -374,10 +355,10 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
374
355
|
return AdbcExceptionHandler()
|
|
375
356
|
|
|
376
357
|
def _try_special_handling(self, cursor: "Cursor", statement: SQL) -> "Optional[SQLResult]":
|
|
377
|
-
"""Handle
|
|
358
|
+
"""Handle special operations.
|
|
378
359
|
|
|
379
360
|
Args:
|
|
380
|
-
cursor:
|
|
361
|
+
cursor: Cursor object
|
|
381
362
|
statement: SQL statement to analyze
|
|
382
363
|
|
|
383
364
|
Returns:
|
|
@@ -387,7 +368,7 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
387
368
|
return None
|
|
388
369
|
|
|
389
370
|
def _execute_many(self, cursor: "Cursor", statement: SQL) -> "ExecutionResult":
|
|
390
|
-
"""Execute SQL with multiple parameter sets
|
|
371
|
+
"""Execute SQL with multiple parameter sets."""
|
|
391
372
|
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
392
373
|
|
|
393
374
|
try:
|
|
@@ -411,13 +392,13 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
411
392
|
|
|
412
393
|
except Exception:
|
|
413
394
|
self._handle_postgres_rollback(cursor)
|
|
414
|
-
logger.exception("
|
|
395
|
+
logger.exception("Executemany failed")
|
|
415
396
|
raise
|
|
416
397
|
|
|
417
398
|
return self.create_execution_result(cursor, rowcount_override=row_count, is_many_result=True)
|
|
418
399
|
|
|
419
400
|
def _execute_statement(self, cursor: "Cursor", statement: SQL) -> "ExecutionResult":
|
|
420
|
-
"""Execute single SQL statement
|
|
401
|
+
"""Execute single SQL statement."""
|
|
421
402
|
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
422
403
|
|
|
423
404
|
try:
|
|
@@ -449,7 +430,7 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
449
430
|
return self.create_execution_result(cursor, rowcount_override=row_count)
|
|
450
431
|
|
|
451
432
|
def _execute_script(self, cursor: "Cursor", statement: "SQL") -> "ExecutionResult":
|
|
452
|
-
"""Execute SQL script
|
|
433
|
+
"""Execute SQL script."""
|
|
453
434
|
if statement.is_script:
|
|
454
435
|
sql = statement._raw_sql
|
|
455
436
|
prepared_parameters: list[Any] = []
|
|
@@ -473,7 +454,7 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
473
454
|
last_rowcount = cursor.rowcount
|
|
474
455
|
except Exception:
|
|
475
456
|
self._handle_postgres_rollback(cursor)
|
|
476
|
-
logger.exception("
|
|
457
|
+
logger.exception("Script execution failed")
|
|
477
458
|
raise
|
|
478
459
|
|
|
479
460
|
return self.create_execution_result(
|
|
@@ -490,7 +471,7 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
490
471
|
with self.with_cursor(self.connection) as cursor:
|
|
491
472
|
cursor.execute("BEGIN")
|
|
492
473
|
except Exception as e:
|
|
493
|
-
msg = f"Failed to begin
|
|
474
|
+
msg = f"Failed to begin transaction: {e}"
|
|
494
475
|
raise SQLSpecError(msg) from e
|
|
495
476
|
|
|
496
477
|
def rollback(self) -> None:
|
|
@@ -499,7 +480,7 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
499
480
|
with self.with_cursor(self.connection) as cursor:
|
|
500
481
|
cursor.execute("ROLLBACK")
|
|
501
482
|
except Exception as e:
|
|
502
|
-
msg = f"Failed to rollback
|
|
483
|
+
msg = f"Failed to rollback transaction: {e}"
|
|
503
484
|
raise SQLSpecError(msg) from e
|
|
504
485
|
|
|
505
486
|
def commit(self) -> None:
|
|
@@ -508,5 +489,5 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
508
489
|
with self.with_cursor(self.connection) as cursor:
|
|
509
490
|
cursor.execute("COMMIT")
|
|
510
491
|
except Exception as e:
|
|
511
|
-
msg = f"Failed to commit
|
|
492
|
+
msg = f"Failed to commit transaction: {e}"
|
|
512
493
|
raise SQLSpecError(msg) from e
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Aiosqlite database configuration
|
|
1
|
+
"""Aiosqlite database configuration."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from contextlib import asynccontextmanager
|
|
@@ -61,7 +61,7 @@ class AiosqliteConfig(AsyncDatabaseConfig["AiosqliteConnection", AiosqliteConnec
|
|
|
61
61
|
pool_instance: "Optional[AiosqliteConnectionPool]" = None,
|
|
62
62
|
migration_config: "Optional[dict[str, Any]]" = None,
|
|
63
63
|
statement_config: "Optional[StatementConfig]" = None,
|
|
64
|
-
|
|
64
|
+
driver_features: "Optional[dict[str, Any]]" = None,
|
|
65
65
|
) -> None:
|
|
66
66
|
"""Initialize AioSQLite configuration.
|
|
67
67
|
|
|
@@ -70,12 +70,10 @@ class AiosqliteConfig(AsyncDatabaseConfig["AiosqliteConnection", AiosqliteConnec
|
|
|
70
70
|
pool_instance: Optional pre-configured connection pool instance.
|
|
71
71
|
migration_config: Optional migration configuration.
|
|
72
72
|
statement_config: Optional statement configuration.
|
|
73
|
-
|
|
73
|
+
driver_features: Optional driver feature configuration.
|
|
74
74
|
"""
|
|
75
75
|
config_dict = dict(pool_config) if pool_config else {}
|
|
76
|
-
config_dict.update(kwargs) # Allow kwargs to override pool_config values
|
|
77
76
|
|
|
78
|
-
# Handle memory database URI conversion - test expectation is different than sqlite pattern
|
|
79
77
|
if "database" not in config_dict or config_dict["database"] == ":memory:":
|
|
80
78
|
config_dict["database"] = "file::memory:?cache=shared"
|
|
81
79
|
config_dict["uri"] = True
|
|
@@ -85,7 +83,7 @@ class AiosqliteConfig(AsyncDatabaseConfig["AiosqliteConnection", AiosqliteConnec
|
|
|
85
83
|
pool_instance=pool_instance,
|
|
86
84
|
migration_config=migration_config,
|
|
87
85
|
statement_config=statement_config or aiosqlite_statement_config,
|
|
88
|
-
driver_features={},
|
|
86
|
+
driver_features=driver_features or {},
|
|
89
87
|
)
|
|
90
88
|
|
|
91
89
|
def _get_pool_config_dict(self) -> "dict[str, Any]":
|
|
@@ -105,7 +103,7 @@ class AiosqliteConfig(AsyncDatabaseConfig["AiosqliteConnection", AiosqliteConnec
|
|
|
105
103
|
Returns:
|
|
106
104
|
Dictionary with connection parameters for creating connections.
|
|
107
105
|
"""
|
|
108
|
-
|
|
106
|
+
|
|
109
107
|
excluded_keys = {
|
|
110
108
|
"pool_size",
|
|
111
109
|
"connect_timeout",
|
|
@@ -201,9 +199,6 @@ class AiosqliteConfig(AsyncDatabaseConfig["AiosqliteConnection", AiosqliteConnec
|
|
|
201
199
|
def get_signature_namespace(self) -> "dict[str, type[Any]]":
|
|
202
200
|
"""Get the signature namespace for aiosqlite types.
|
|
203
201
|
|
|
204
|
-
This provides all aiosqlite-specific types that Litestar needs to recognize
|
|
205
|
-
to avoid serialization attempts.
|
|
206
|
-
|
|
207
202
|
Returns:
|
|
208
203
|
Dictionary mapping type names to types.
|
|
209
204
|
"""
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
"""AIOSQLite driver implementation for async SQLite operations.
|
|
2
|
-
|
|
3
|
-
Provides async SQLite database connectivity with:
|
|
4
|
-
- Async parameter processing with type coercion
|
|
5
|
-
- Thread-safe caching system
|
|
6
|
-
- Context management for resource handling
|
|
7
|
-
- SQLite-specific optimizations
|
|
8
|
-
"""
|
|
1
|
+
"""AIOSQLite driver implementation for async SQLite operations."""
|
|
9
2
|
|
|
10
3
|
import asyncio
|
|
11
4
|
import contextlib
|
|
@@ -61,7 +54,7 @@ aiosqlite_statement_config = StatementConfig(
|
|
|
61
54
|
|
|
62
55
|
|
|
63
56
|
class AiosqliteCursor:
|
|
64
|
-
"""Async context manager for AIOSQLite
|
|
57
|
+
"""Async context manager for AIOSQLite cursors."""
|
|
65
58
|
|
|
66
59
|
__slots__ = ("connection", "cursor")
|
|
67
60
|
|
|
@@ -81,7 +74,7 @@ class AiosqliteCursor:
|
|
|
81
74
|
|
|
82
75
|
|
|
83
76
|
class AiosqliteExceptionHandler:
|
|
84
|
-
"""
|
|
77
|
+
"""Async context manager for AIOSQLite database exceptions."""
|
|
85
78
|
|
|
86
79
|
__slots__ = ()
|
|
87
80
|
|
|
@@ -125,13 +118,7 @@ class AiosqliteExceptionHandler:
|
|
|
125
118
|
|
|
126
119
|
|
|
127
120
|
class AiosqliteDriver(AsyncDriverAdapterBase):
|
|
128
|
-
"""AIOSQLite driver for async SQLite database operations.
|
|
129
|
-
|
|
130
|
-
Provides async SQLite connectivity with:
|
|
131
|
-
- Statement processing and parameter handling
|
|
132
|
-
- Cursor management and resource cleanup
|
|
133
|
-
- Exception handling for SQLite operations
|
|
134
|
-
"""
|
|
121
|
+
"""AIOSQLite driver for async SQLite database operations."""
|
|
135
122
|
|
|
136
123
|
__slots__ = ()
|
|
137
124
|
dialect = "sqlite"
|
|
@@ -170,7 +157,7 @@ class AiosqliteDriver(AsyncDriverAdapterBase):
|
|
|
170
157
|
return None
|
|
171
158
|
|
|
172
159
|
async def _execute_script(self, cursor: "aiosqlite.Cursor", statement: "SQL") -> "ExecutionResult":
|
|
173
|
-
"""Execute SQL script
|
|
160
|
+
"""Execute SQL script."""
|
|
174
161
|
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
175
162
|
statements = self.split_script_statements(sql, statement.statement_config, strip_trailing_semicolon=True)
|
|
176
163
|
|
|
@@ -186,7 +173,7 @@ class AiosqliteDriver(AsyncDriverAdapterBase):
|
|
|
186
173
|
)
|
|
187
174
|
|
|
188
175
|
async def _execute_many(self, cursor: "aiosqlite.Cursor", statement: "SQL") -> "ExecutionResult":
|
|
189
|
-
"""Execute SQL with multiple parameter sets
|
|
176
|
+
"""Execute SQL with multiple parameter sets."""
|
|
190
177
|
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
191
178
|
|
|
192
179
|
if not prepared_parameters:
|
|
@@ -200,7 +187,7 @@ class AiosqliteDriver(AsyncDriverAdapterBase):
|
|
|
200
187
|
return self.create_execution_result(cursor, rowcount_override=affected_rows, is_many_result=True)
|
|
201
188
|
|
|
202
189
|
async def _execute_statement(self, cursor: "aiosqlite.Cursor", statement: "SQL") -> "ExecutionResult":
|
|
203
|
-
"""Execute single SQL statement
|
|
190
|
+
"""Execute single SQL statement."""
|
|
204
191
|
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
205
192
|
await cursor.execute(sql, prepared_parameters or ())
|
|
206
193
|
|
|
@@ -218,14 +205,11 @@ class AiosqliteDriver(AsyncDriverAdapterBase):
|
|
|
218
205
|
return self.create_execution_result(cursor, rowcount_override=affected_rows)
|
|
219
206
|
|
|
220
207
|
async def begin(self) -> None:
|
|
221
|
-
"""Begin a database transaction
|
|
208
|
+
"""Begin a database transaction."""
|
|
222
209
|
try:
|
|
223
210
|
if not self.connection.in_transaction:
|
|
224
|
-
# For shared cache databases, use IMMEDIATE to reduce lock contention
|
|
225
|
-
# For other databases, BEGIN IMMEDIATE is also safer for concurrent access
|
|
226
211
|
await self.connection.execute("BEGIN IMMEDIATE")
|
|
227
212
|
except aiosqlite.Error as e:
|
|
228
|
-
# If IMMEDIATE fails due to lock, try with exponential backoff
|
|
229
213
|
import random
|
|
230
214
|
|
|
231
215
|
max_retries = 3
|
|
@@ -235,7 +219,7 @@ class AiosqliteDriver(AsyncDriverAdapterBase):
|
|
|
235
219
|
try:
|
|
236
220
|
await self.connection.execute("BEGIN IMMEDIATE")
|
|
237
221
|
except aiosqlite.Error:
|
|
238
|
-
if attempt == max_retries - 1:
|
|
222
|
+
if attempt == max_retries - 1:
|
|
239
223
|
break
|
|
240
224
|
else:
|
|
241
225
|
return
|