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
|
Binary file
|
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
"""SQL statement with complete backward compatibility.
|
|
2
|
+
|
|
3
|
+
This module implements the core SQL class and StatementConfig with complete
|
|
4
|
+
backward compatibility while using an optimized processing pipeline.
|
|
5
|
+
|
|
6
|
+
Components:
|
|
7
|
+
- SQL class: SQL statement with identical external interface
|
|
8
|
+
- StatementConfig: Complete backward compatibility for all driver requirements
|
|
9
|
+
- ProcessedState: Cached processing results
|
|
10
|
+
|
|
11
|
+
Features:
|
|
12
|
+
- Lazy compilation: Only compile when needed
|
|
13
|
+
- Cached properties: Avoid redundant computation
|
|
14
|
+
- Complete StatementConfig compatibility
|
|
15
|
+
- Integrated parameter processing and compilation caching
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import contextlib
|
|
19
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
|
|
20
|
+
|
|
21
|
+
import sqlglot
|
|
22
|
+
from mypy_extensions import mypyc_attr
|
|
23
|
+
from sqlglot import exp
|
|
24
|
+
from sqlglot.errors import ParseError
|
|
25
|
+
from typing_extensions import TypeAlias
|
|
26
|
+
|
|
27
|
+
from sqlspec.core.compiler import SQLProcessor
|
|
28
|
+
from sqlspec.core.parameters import ParameterConverter, ParameterStyle, ParameterStyleConfig, ParameterValidator
|
|
29
|
+
from sqlspec.typing import Empty, EmptyEnum
|
|
30
|
+
from sqlspec.utils.logging import get_logger
|
|
31
|
+
from sqlspec.utils.type_guards import is_statement_filter, supports_where
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from sqlglot.dialects.dialect import DialectType
|
|
35
|
+
|
|
36
|
+
from sqlspec.core.filters import StatementFilter
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = (
|
|
40
|
+
"SQL",
|
|
41
|
+
"ProcessedState",
|
|
42
|
+
"Statement",
|
|
43
|
+
"StatementConfig",
|
|
44
|
+
"get_default_config",
|
|
45
|
+
"get_default_parameter_config",
|
|
46
|
+
)
|
|
47
|
+
logger = get_logger("sqlspec.core.statement")
|
|
48
|
+
|
|
49
|
+
SQL_CONFIG_SLOTS = (
|
|
50
|
+
"pre_process_steps",
|
|
51
|
+
"post_process_steps",
|
|
52
|
+
"dialect",
|
|
53
|
+
"enable_analysis",
|
|
54
|
+
"enable_caching",
|
|
55
|
+
"enable_expression_simplification",
|
|
56
|
+
"enable_parameter_type_wrapping",
|
|
57
|
+
"enable_parsing",
|
|
58
|
+
"enable_transformations",
|
|
59
|
+
"enable_validation",
|
|
60
|
+
"execution_mode",
|
|
61
|
+
"execution_args",
|
|
62
|
+
"output_transformer",
|
|
63
|
+
"parameter_config",
|
|
64
|
+
"parameter_converter",
|
|
65
|
+
"parameter_validator",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
PROCESSED_STATE_SLOTS = (
|
|
69
|
+
"compiled_sql",
|
|
70
|
+
"execution_parameters",
|
|
71
|
+
"parsed_expression",
|
|
72
|
+
"operation_type",
|
|
73
|
+
"validation_errors",
|
|
74
|
+
"is_many",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@mypyc_attr(allow_interpreted_subclasses=False)
|
|
79
|
+
class ProcessedState:
|
|
80
|
+
"""Cached processing results for SQL statements.
|
|
81
|
+
|
|
82
|
+
Stores the results of processing to avoid redundant compilation,
|
|
83
|
+
parsing, and parameter processing.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
__slots__ = PROCESSED_STATE_SLOTS
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
compiled_sql: str,
|
|
91
|
+
execution_parameters: Any,
|
|
92
|
+
parsed_expression: "Optional[exp.Expression]" = None,
|
|
93
|
+
operation_type: str = "UNKNOWN",
|
|
94
|
+
validation_errors: "Optional[list[str]]" = None,
|
|
95
|
+
is_many: bool = False,
|
|
96
|
+
) -> None:
|
|
97
|
+
self.compiled_sql = compiled_sql
|
|
98
|
+
self.execution_parameters = execution_parameters
|
|
99
|
+
self.parsed_expression = parsed_expression
|
|
100
|
+
self.operation_type = operation_type
|
|
101
|
+
self.validation_errors = validation_errors or []
|
|
102
|
+
self.is_many = is_many
|
|
103
|
+
|
|
104
|
+
def __hash__(self) -> int:
|
|
105
|
+
return hash((self.compiled_sql, str(self.execution_parameters), self.operation_type))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@mypyc_attr(allow_interpreted_subclasses=True) # Enable when MyPyC ready
|
|
109
|
+
class SQL:
|
|
110
|
+
"""SQL statement with complete backward compatibility.
|
|
111
|
+
|
|
112
|
+
Provides 100% backward compatibility while using an optimized
|
|
113
|
+
core processing pipeline.
|
|
114
|
+
|
|
115
|
+
Features:
|
|
116
|
+
- Lazy evaluation with cached properties
|
|
117
|
+
- Integrated parameter processing pipeline
|
|
118
|
+
- Complete StatementFilter and execution mode support
|
|
119
|
+
- Same parameter processing behavior
|
|
120
|
+
- Same result types and interfaces
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
__slots__ = (
|
|
124
|
+
"_dialect",
|
|
125
|
+
"_filters",
|
|
126
|
+
"_hash",
|
|
127
|
+
"_is_many",
|
|
128
|
+
"_is_script",
|
|
129
|
+
"_named_parameters",
|
|
130
|
+
"_original_parameters",
|
|
131
|
+
"_positional_parameters",
|
|
132
|
+
"_processed_state",
|
|
133
|
+
"_raw_sql",
|
|
134
|
+
"_statement_config",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def __init__(
|
|
138
|
+
self,
|
|
139
|
+
statement: "Union[str, exp.Expression, 'SQL']",
|
|
140
|
+
*parameters: "Union[Any, StatementFilter, list[Union[Any, StatementFilter]]]",
|
|
141
|
+
statement_config: Optional["StatementConfig"] = None,
|
|
142
|
+
is_many: Optional[bool] = None,
|
|
143
|
+
**kwargs: Any,
|
|
144
|
+
) -> None:
|
|
145
|
+
"""Initialize SQL statement.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
statement: SQL string, expression, or existing SQL object
|
|
149
|
+
*parameters: Parameters and filters
|
|
150
|
+
statement_config: Configuration
|
|
151
|
+
is_many: Mark as execute_many operation
|
|
152
|
+
**kwargs: Additional parameters
|
|
153
|
+
"""
|
|
154
|
+
self._statement_config = statement_config or self._create_auto_config(statement, parameters, kwargs)
|
|
155
|
+
|
|
156
|
+
self._dialect = self._normalize_dialect(self._statement_config.dialect)
|
|
157
|
+
self._processed_state: Union[EmptyEnum, ProcessedState] = Empty
|
|
158
|
+
self._hash: Optional[int] = None
|
|
159
|
+
self._filters: list[StatementFilter] = []
|
|
160
|
+
self._named_parameters: dict[str, Any] = {}
|
|
161
|
+
self._positional_parameters: list[Any] = []
|
|
162
|
+
self._is_script = False
|
|
163
|
+
|
|
164
|
+
if isinstance(statement, SQL):
|
|
165
|
+
self._init_from_sql_object(statement)
|
|
166
|
+
if is_many is not None:
|
|
167
|
+
self._is_many = is_many
|
|
168
|
+
else:
|
|
169
|
+
if isinstance(statement, str):
|
|
170
|
+
self._raw_sql = statement
|
|
171
|
+
else:
|
|
172
|
+
self._raw_sql = statement.sql(dialect=str(self._dialect) if self._dialect else None)
|
|
173
|
+
|
|
174
|
+
self._is_many = is_many if is_many is not None else self._should_auto_detect_many(parameters)
|
|
175
|
+
|
|
176
|
+
self._original_parameters = parameters
|
|
177
|
+
self._process_parameters(*parameters, **kwargs)
|
|
178
|
+
|
|
179
|
+
def _create_auto_config(
|
|
180
|
+
self, statement: "Union[str, exp.Expression, 'SQL']", parameters: tuple, kwargs: dict[str, Any]
|
|
181
|
+
) -> "StatementConfig":
|
|
182
|
+
"""Create auto-detected StatementConfig when none provided."""
|
|
183
|
+
return get_default_config()
|
|
184
|
+
|
|
185
|
+
def _normalize_dialect(self, dialect: "Optional[DialectType]") -> "Optional[str]":
|
|
186
|
+
"""Normalize dialect to string representation."""
|
|
187
|
+
if dialect is None:
|
|
188
|
+
return None
|
|
189
|
+
if isinstance(dialect, str):
|
|
190
|
+
return dialect
|
|
191
|
+
try:
|
|
192
|
+
return dialect.__class__.__name__.lower()
|
|
193
|
+
except AttributeError:
|
|
194
|
+
return str(dialect)
|
|
195
|
+
|
|
196
|
+
def _init_from_sql_object(self, sql_obj: "SQL") -> None:
|
|
197
|
+
"""Initialize from existing SQL object."""
|
|
198
|
+
self._raw_sql = sql_obj._raw_sql
|
|
199
|
+
self._filters = sql_obj._filters.copy()
|
|
200
|
+
self._named_parameters = sql_obj._named_parameters.copy()
|
|
201
|
+
self._positional_parameters = sql_obj._positional_parameters.copy()
|
|
202
|
+
self._is_many = sql_obj._is_many
|
|
203
|
+
self._is_script = sql_obj._is_script
|
|
204
|
+
if sql_obj._processed_state is not Empty:
|
|
205
|
+
self._processed_state = sql_obj._processed_state
|
|
206
|
+
|
|
207
|
+
def _should_auto_detect_many(self, parameters: tuple) -> bool:
|
|
208
|
+
"""Auto-detect execute_many from parameter structure."""
|
|
209
|
+
if len(parameters) == 1 and isinstance(parameters[0], list):
|
|
210
|
+
param_list = parameters[0]
|
|
211
|
+
if len(param_list) > 1 and all(isinstance(item, (tuple, list)) for item in param_list):
|
|
212
|
+
return True
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
def _process_parameters(self, *parameters: Any, dialect: Optional[str] = None, **kwargs: Any) -> None:
|
|
216
|
+
"""Process parameters using parameter system."""
|
|
217
|
+
if dialect is not None:
|
|
218
|
+
self._dialect = self._normalize_dialect(dialect)
|
|
219
|
+
|
|
220
|
+
if "is_script" in kwargs:
|
|
221
|
+
self._is_script = bool(kwargs.pop("is_script"))
|
|
222
|
+
|
|
223
|
+
# Optimize parameter filtering with direct iteration
|
|
224
|
+
filters: list[StatementFilter] = []
|
|
225
|
+
actual_params: list[Any] = []
|
|
226
|
+
for p in parameters:
|
|
227
|
+
if is_statement_filter(p):
|
|
228
|
+
filters.append(p)
|
|
229
|
+
else:
|
|
230
|
+
actual_params.append(p)
|
|
231
|
+
|
|
232
|
+
self._filters.extend(filters)
|
|
233
|
+
|
|
234
|
+
if actual_params:
|
|
235
|
+
param_count = len(actual_params)
|
|
236
|
+
if param_count == 1:
|
|
237
|
+
param = actual_params[0]
|
|
238
|
+
if isinstance(param, dict):
|
|
239
|
+
self._named_parameters.update(param)
|
|
240
|
+
elif isinstance(param, (list, tuple)):
|
|
241
|
+
if self._is_many:
|
|
242
|
+
self._positional_parameters = list(param)
|
|
243
|
+
else:
|
|
244
|
+
self._positional_parameters.extend(param)
|
|
245
|
+
else:
|
|
246
|
+
self._positional_parameters.append(param)
|
|
247
|
+
else:
|
|
248
|
+
self._positional_parameters.extend(actual_params)
|
|
249
|
+
|
|
250
|
+
self._named_parameters.update(kwargs)
|
|
251
|
+
|
|
252
|
+
# PRESERVED PROPERTIES - Exact same interface as existing SQL class
|
|
253
|
+
@property
|
|
254
|
+
def sql(self) -> str:
|
|
255
|
+
"""Get the raw SQL string - no compilation triggered."""
|
|
256
|
+
return self._raw_sql
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def parameters(self) -> Any:
|
|
260
|
+
"""Get the original parameters without triggering compilation."""
|
|
261
|
+
if self._named_parameters:
|
|
262
|
+
return self._named_parameters
|
|
263
|
+
return self._positional_parameters or []
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def operation_type(self) -> str:
|
|
267
|
+
"""SQL operation type - requires explicit compilation."""
|
|
268
|
+
if self._processed_state is Empty:
|
|
269
|
+
return "UNKNOWN"
|
|
270
|
+
return self._processed_state.operation_type
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def statement_config(self) -> "StatementConfig":
|
|
274
|
+
"""Statement configuration - preserved interface."""
|
|
275
|
+
return self._statement_config
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def expression(self) -> "Optional[exp.Expression]":
|
|
279
|
+
"""SQLGlot expression - only available after explicit compilation."""
|
|
280
|
+
# This property should only be accessed after compilation
|
|
281
|
+
# If not compiled yet, return None
|
|
282
|
+
if self._processed_state is not Empty:
|
|
283
|
+
return self._processed_state.parsed_expression
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def filters(self) -> "list[StatementFilter]":
|
|
288
|
+
"""Applied filters."""
|
|
289
|
+
return self._filters.copy()
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def dialect(self) -> "Optional[str]":
|
|
293
|
+
"""SQL dialect."""
|
|
294
|
+
return self._dialect
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def _statement(self) -> "Optional[exp.Expression]":
|
|
298
|
+
"""Internal SQLGlot expression."""
|
|
299
|
+
return self.expression
|
|
300
|
+
|
|
301
|
+
@property
|
|
302
|
+
def is_many(self) -> bool:
|
|
303
|
+
"""Check if this is execute_many."""
|
|
304
|
+
return self._is_many
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def is_script(self) -> bool:
|
|
308
|
+
"""Check if this is script execution."""
|
|
309
|
+
return self._is_script
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def validation_errors(self) -> "list[str]":
|
|
313
|
+
"""Validation errors - requires explicit compilation."""
|
|
314
|
+
if self._processed_state is Empty:
|
|
315
|
+
return []
|
|
316
|
+
return self._processed_state.validation_errors.copy()
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def has_errors(self) -> bool:
|
|
320
|
+
"""Check if there are validation errors."""
|
|
321
|
+
return len(self.validation_errors) > 0
|
|
322
|
+
|
|
323
|
+
def returns_rows(self) -> bool:
|
|
324
|
+
"""Check if statement returns rows."""
|
|
325
|
+
sql_upper = self._raw_sql.strip().upper()
|
|
326
|
+
if any(sql_upper.startswith(op) for op in ("SELECT", "WITH", "VALUES", "TABLE", "SHOW", "DESCRIBE", "PRAGMA")):
|
|
327
|
+
return True
|
|
328
|
+
|
|
329
|
+
return "RETURNING" in sql_upper
|
|
330
|
+
|
|
331
|
+
def is_modifying_operation(self) -> bool:
|
|
332
|
+
"""Check if the SQL statement is a modifying operation.
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
True if the operation modifies data (INSERT/UPDATE/DELETE)
|
|
336
|
+
"""
|
|
337
|
+
expression = self.expression
|
|
338
|
+
if expression and isinstance(expression, (exp.Insert, exp.Update, exp.Delete)):
|
|
339
|
+
return True
|
|
340
|
+
|
|
341
|
+
sql_upper = self.sql.strip().upper()
|
|
342
|
+
modifying_operations = ("INSERT", "UPDATE", "DELETE")
|
|
343
|
+
return any(sql_upper.startswith(op) for op in modifying_operations)
|
|
344
|
+
|
|
345
|
+
def compile(self) -> tuple[str, Any]:
|
|
346
|
+
"""Explicitly compile the SQL statement."""
|
|
347
|
+
if self._processed_state is Empty:
|
|
348
|
+
try:
|
|
349
|
+
# Avoid unnecessary variable assignment
|
|
350
|
+
processor = SQLProcessor(self._statement_config)
|
|
351
|
+
compiled_result = processor.compile(
|
|
352
|
+
self._raw_sql, self._named_parameters or self._positional_parameters, is_many=self._is_many
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
self._processed_state = ProcessedState(
|
|
356
|
+
compiled_sql=compiled_result.compiled_sql,
|
|
357
|
+
execution_parameters=compiled_result.execution_parameters,
|
|
358
|
+
parsed_expression=compiled_result.expression,
|
|
359
|
+
operation_type=compiled_result.operation_type,
|
|
360
|
+
validation_errors=[],
|
|
361
|
+
is_many=self._is_many,
|
|
362
|
+
)
|
|
363
|
+
except Exception as e:
|
|
364
|
+
logger.warning("Processing failed, using fallback: %s", e)
|
|
365
|
+
self._processed_state = ProcessedState(
|
|
366
|
+
compiled_sql=self._raw_sql,
|
|
367
|
+
execution_parameters=self._named_parameters or self._positional_parameters,
|
|
368
|
+
operation_type="UNKNOWN",
|
|
369
|
+
is_many=self._is_many,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
return self._processed_state.compiled_sql, self._processed_state.execution_parameters
|
|
373
|
+
|
|
374
|
+
def as_script(self) -> "SQL":
|
|
375
|
+
"""Mark as script execution."""
|
|
376
|
+
new_sql = SQL(
|
|
377
|
+
self._raw_sql, *self._original_parameters, statement_config=self._statement_config, is_many=self._is_many
|
|
378
|
+
)
|
|
379
|
+
# Preserve accumulated parameters when marking as script
|
|
380
|
+
new_sql._named_parameters.update(self._named_parameters)
|
|
381
|
+
new_sql._positional_parameters = self._positional_parameters.copy()
|
|
382
|
+
new_sql._filters = self._filters.copy()
|
|
383
|
+
new_sql._is_script = True
|
|
384
|
+
return new_sql
|
|
385
|
+
|
|
386
|
+
def copy(
|
|
387
|
+
self, statement: "Optional[Union[str, exp.Expression]]" = None, parameters: Optional[Any] = None, **kwargs: Any
|
|
388
|
+
) -> "SQL":
|
|
389
|
+
"""Create copy with modifications."""
|
|
390
|
+
new_sql = SQL(
|
|
391
|
+
statement or self._raw_sql,
|
|
392
|
+
*(parameters if parameters is not None else self._original_parameters),
|
|
393
|
+
statement_config=self._statement_config,
|
|
394
|
+
is_many=self._is_many,
|
|
395
|
+
**kwargs,
|
|
396
|
+
)
|
|
397
|
+
# Only preserve accumulated parameters when no explicit parameters are provided
|
|
398
|
+
if parameters is None:
|
|
399
|
+
new_sql._named_parameters.update(self._named_parameters)
|
|
400
|
+
new_sql._positional_parameters = self._positional_parameters.copy()
|
|
401
|
+
new_sql._filters = self._filters.copy()
|
|
402
|
+
return new_sql
|
|
403
|
+
|
|
404
|
+
def add_named_parameter(self, name: str, value: Any) -> "SQL":
|
|
405
|
+
"""Add a named parameter and return a new SQL instance.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
name: Parameter name
|
|
409
|
+
value: Parameter value
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
New SQL instance with the added parameter
|
|
413
|
+
"""
|
|
414
|
+
new_sql = SQL(
|
|
415
|
+
self._raw_sql, *self._original_parameters, statement_config=self._statement_config, is_many=self._is_many
|
|
416
|
+
)
|
|
417
|
+
new_sql._named_parameters.update(self._named_parameters)
|
|
418
|
+
new_sql._named_parameters[name] = value
|
|
419
|
+
new_sql._positional_parameters = self._positional_parameters.copy()
|
|
420
|
+
new_sql._filters = self._filters.copy()
|
|
421
|
+
return new_sql
|
|
422
|
+
|
|
423
|
+
def where(self, condition: "Union[str, exp.Expression]") -> "SQL":
|
|
424
|
+
"""Add WHERE condition to the SQL statement.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
condition: WHERE condition as string or SQLGlot expression
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
New SQL instance with the WHERE condition applied
|
|
431
|
+
"""
|
|
432
|
+
# Parse current SQL with copy=False optimization
|
|
433
|
+
current_expr = None
|
|
434
|
+
with contextlib.suppress(ParseError):
|
|
435
|
+
current_expr = sqlglot.parse_one(self._raw_sql, dialect=self._dialect)
|
|
436
|
+
|
|
437
|
+
if current_expr is None:
|
|
438
|
+
try:
|
|
439
|
+
current_expr = sqlglot.parse_one(self._raw_sql, dialect=self._dialect)
|
|
440
|
+
except ParseError:
|
|
441
|
+
# Use f-string optimization and copy=False
|
|
442
|
+
subquery_sql = f"SELECT * FROM ({self._raw_sql}) AS subquery"
|
|
443
|
+
current_expr = sqlglot.parse_one(subquery_sql, dialect=self._dialect)
|
|
444
|
+
|
|
445
|
+
# Parse condition with copy=False optimization
|
|
446
|
+
condition_expr: exp.Expression
|
|
447
|
+
if isinstance(condition, str):
|
|
448
|
+
try:
|
|
449
|
+
condition_expr = sqlglot.parse_one(condition, dialect=self._dialect, into=exp.Condition)
|
|
450
|
+
except ParseError:
|
|
451
|
+
condition_expr = exp.Condition(this=condition)
|
|
452
|
+
else:
|
|
453
|
+
condition_expr = condition
|
|
454
|
+
|
|
455
|
+
# Apply WHERE clause
|
|
456
|
+
if isinstance(current_expr, exp.Select) or supports_where(current_expr):
|
|
457
|
+
new_expr = current_expr.where(condition_expr, copy=False)
|
|
458
|
+
else:
|
|
459
|
+
new_expr = exp.Select().from_(current_expr).where(condition_expr, copy=False)
|
|
460
|
+
|
|
461
|
+
# Generate SQL and create new instance
|
|
462
|
+
new_sql_text = new_expr.sql(dialect=self._dialect)
|
|
463
|
+
new_sql = SQL(
|
|
464
|
+
new_sql_text, *self._original_parameters, statement_config=self._statement_config, is_many=self._is_many
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# Preserve state efficiently
|
|
468
|
+
new_sql._named_parameters.update(self._named_parameters)
|
|
469
|
+
new_sql._positional_parameters = self._positional_parameters.copy()
|
|
470
|
+
new_sql._filters = self._filters.copy()
|
|
471
|
+
return new_sql
|
|
472
|
+
|
|
473
|
+
def __hash__(self) -> int:
|
|
474
|
+
"""Hash value with optimized computation."""
|
|
475
|
+
if self._hash is None:
|
|
476
|
+
# Pre-compute tuple components to avoid multiple tuple() calls
|
|
477
|
+
positional_tuple = tuple(self._positional_parameters)
|
|
478
|
+
named_tuple = tuple(sorted(self._named_parameters.items())) if self._named_parameters else ()
|
|
479
|
+
|
|
480
|
+
self._hash = hash((self._raw_sql, positional_tuple, named_tuple, self._is_many, self._is_script))
|
|
481
|
+
return self._hash
|
|
482
|
+
|
|
483
|
+
def __eq__(self, other: object) -> bool:
|
|
484
|
+
"""Equality comparison."""
|
|
485
|
+
if not isinstance(other, SQL):
|
|
486
|
+
return False
|
|
487
|
+
return (
|
|
488
|
+
self._raw_sql == other._raw_sql
|
|
489
|
+
and self._positional_parameters == other._positional_parameters
|
|
490
|
+
and self._named_parameters == other._named_parameters
|
|
491
|
+
and self._is_many == other._is_many
|
|
492
|
+
and self._is_script == other._is_script
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
def __repr__(self) -> str:
|
|
496
|
+
"""String representation."""
|
|
497
|
+
params_str = ""
|
|
498
|
+
if self._named_parameters:
|
|
499
|
+
params_str = f", named_params={self._named_parameters}"
|
|
500
|
+
elif self._positional_parameters:
|
|
501
|
+
params_str = f", params={self._positional_parameters}"
|
|
502
|
+
|
|
503
|
+
flags = []
|
|
504
|
+
if self._is_many:
|
|
505
|
+
flags.append("is_many")
|
|
506
|
+
if self._is_script:
|
|
507
|
+
flags.append("is_script")
|
|
508
|
+
flags_str = f", {', '.join(flags)}" if flags else ""
|
|
509
|
+
|
|
510
|
+
return f"SQL({self._raw_sql!r}{params_str}{flags_str})"
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
@mypyc_attr(allow_interpreted_subclasses=True)
|
|
514
|
+
class StatementConfig:
|
|
515
|
+
"""Configuration for SQL statement processing.
|
|
516
|
+
|
|
517
|
+
Provides all attributes that drivers expect for SQL processing.
|
|
518
|
+
|
|
519
|
+
Features:
|
|
520
|
+
- Complete parameter processing configuration
|
|
521
|
+
- Caching and execution mode interfaces
|
|
522
|
+
- Support for various database-specific operations
|
|
523
|
+
- Immutable updates via replace() method
|
|
524
|
+
"""
|
|
525
|
+
|
|
526
|
+
__slots__ = SQL_CONFIG_SLOTS
|
|
527
|
+
|
|
528
|
+
def __init__(
|
|
529
|
+
self,
|
|
530
|
+
parameter_config: "Optional[ParameterStyleConfig]" = None,
|
|
531
|
+
enable_parsing: bool = True,
|
|
532
|
+
enable_validation: bool = True,
|
|
533
|
+
enable_transformations: bool = True,
|
|
534
|
+
enable_analysis: bool = False,
|
|
535
|
+
enable_expression_simplification: bool = False,
|
|
536
|
+
enable_parameter_type_wrapping: bool = True,
|
|
537
|
+
enable_caching: bool = True,
|
|
538
|
+
parameter_converter: "Optional[ParameterConverter]" = None,
|
|
539
|
+
parameter_validator: "Optional[ParameterValidator]" = None,
|
|
540
|
+
dialect: "Optional[DialectType]" = None,
|
|
541
|
+
pre_process_steps: "Optional[list[Any]]" = None,
|
|
542
|
+
post_process_steps: "Optional[list[Any]]" = None,
|
|
543
|
+
execution_mode: "Optional[str]" = None,
|
|
544
|
+
execution_args: "Optional[dict[str, Any]]" = None,
|
|
545
|
+
output_transformer: "Optional[Callable[[str, Any], tuple[str, Any]]]" = None,
|
|
546
|
+
) -> None:
|
|
547
|
+
"""Initialize StatementConfig.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
parameter_config: Parameter style configuration
|
|
551
|
+
enable_parsing: Enable SQL parsing using sqlglot
|
|
552
|
+
enable_validation: Run SQL validators to check for safety issues
|
|
553
|
+
enable_transformations: Apply SQL transformers
|
|
554
|
+
enable_analysis: Run SQL analyzers for metadata extraction
|
|
555
|
+
enable_expression_simplification: Apply expression simplification
|
|
556
|
+
enable_parameter_type_wrapping: Wrap parameters with type information
|
|
557
|
+
enable_caching: Cache processed SQL statements
|
|
558
|
+
parameter_converter: Handles parameter style conversions
|
|
559
|
+
parameter_validator: Validates parameter usage and styles
|
|
560
|
+
dialect: SQL dialect for parsing and generation
|
|
561
|
+
pre_process_steps: Optional list of preprocessing steps
|
|
562
|
+
post_process_steps: Optional list of postprocessing steps
|
|
563
|
+
execution_mode: Special execution mode
|
|
564
|
+
execution_args: Arguments for special execution modes
|
|
565
|
+
output_transformer: Optional output transformation function
|
|
566
|
+
"""
|
|
567
|
+
self.enable_parsing = enable_parsing
|
|
568
|
+
self.enable_validation = enable_validation
|
|
569
|
+
self.enable_transformations = enable_transformations
|
|
570
|
+
self.enable_analysis = enable_analysis
|
|
571
|
+
self.enable_expression_simplification = enable_expression_simplification
|
|
572
|
+
self.enable_parameter_type_wrapping = enable_parameter_type_wrapping
|
|
573
|
+
self.enable_caching = enable_caching
|
|
574
|
+
self.parameter_converter = parameter_converter or ParameterConverter()
|
|
575
|
+
self.parameter_validator = parameter_validator or ParameterValidator()
|
|
576
|
+
self.parameter_config = parameter_config or ParameterStyleConfig(
|
|
577
|
+
default_parameter_style=ParameterStyle.QMARK, supported_parameter_styles={ParameterStyle.QMARK}
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
self.dialect = dialect
|
|
581
|
+
self.pre_process_steps = pre_process_steps
|
|
582
|
+
self.post_process_steps = post_process_steps
|
|
583
|
+
self.execution_mode = execution_mode
|
|
584
|
+
self.execution_args = execution_args
|
|
585
|
+
self.output_transformer = output_transformer
|
|
586
|
+
|
|
587
|
+
def replace(self, **kwargs: Any) -> "StatementConfig":
|
|
588
|
+
"""Immutable update pattern.
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
**kwargs: Attributes to update
|
|
592
|
+
|
|
593
|
+
Returns:
|
|
594
|
+
New StatementConfig instance with updated attributes
|
|
595
|
+
"""
|
|
596
|
+
for key in kwargs:
|
|
597
|
+
if key not in SQL_CONFIG_SLOTS:
|
|
598
|
+
msg = f"{key!r} is not a field in {type(self).__name__}"
|
|
599
|
+
raise TypeError(msg)
|
|
600
|
+
|
|
601
|
+
current_kwargs = {slot: getattr(self, slot) for slot in SQL_CONFIG_SLOTS}
|
|
602
|
+
current_kwargs.update(kwargs)
|
|
603
|
+
return type(self)(**current_kwargs)
|
|
604
|
+
|
|
605
|
+
def __hash__(self) -> int:
|
|
606
|
+
"""Hash based on key configuration settings."""
|
|
607
|
+
return hash(
|
|
608
|
+
(
|
|
609
|
+
self.enable_parsing,
|
|
610
|
+
self.enable_validation,
|
|
611
|
+
self.enable_transformations,
|
|
612
|
+
self.enable_analysis,
|
|
613
|
+
self.enable_expression_simplification,
|
|
614
|
+
self.enable_parameter_type_wrapping,
|
|
615
|
+
self.enable_caching,
|
|
616
|
+
str(self.dialect),
|
|
617
|
+
)
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
def __repr__(self) -> str:
|
|
621
|
+
"""String representation of the StatementConfig instance."""
|
|
622
|
+
field_strs = []
|
|
623
|
+
for slot in SQL_CONFIG_SLOTS:
|
|
624
|
+
value = getattr(self, slot)
|
|
625
|
+
field_strs.append(f"{slot}={value!r}")
|
|
626
|
+
return f"{self.__class__.__name__}({', '.join(field_strs)})"
|
|
627
|
+
|
|
628
|
+
def __eq__(self, other: object) -> bool:
|
|
629
|
+
"""Equality comparison."""
|
|
630
|
+
if not isinstance(other, type(self)):
|
|
631
|
+
return False
|
|
632
|
+
|
|
633
|
+
for slot in SQL_CONFIG_SLOTS:
|
|
634
|
+
self_val = getattr(self, slot)
|
|
635
|
+
other_val = getattr(other, slot)
|
|
636
|
+
|
|
637
|
+
if hasattr(self_val, "__class__") and hasattr(other_val, "__class__"):
|
|
638
|
+
if self_val.__class__ != other_val.__class__:
|
|
639
|
+
return False
|
|
640
|
+
if slot == "parameter_config":
|
|
641
|
+
if not self._compare_parameter_configs(self_val, other_val):
|
|
642
|
+
return False
|
|
643
|
+
elif slot in {"parameter_converter", "parameter_validator"}:
|
|
644
|
+
continue
|
|
645
|
+
elif self_val != other_val:
|
|
646
|
+
return False
|
|
647
|
+
elif self_val != other_val:
|
|
648
|
+
return False
|
|
649
|
+
return True
|
|
650
|
+
|
|
651
|
+
def _compare_parameter_configs(self, config1: Any, config2: Any) -> bool:
|
|
652
|
+
"""Compare parameter configs by key attributes."""
|
|
653
|
+
try:
|
|
654
|
+
return (
|
|
655
|
+
config1.default_parameter_style == config2.default_parameter_style
|
|
656
|
+
and config1.supported_parameter_styles == config2.supported_parameter_styles
|
|
657
|
+
and getattr(config1, "supported_execution_parameter_styles", None)
|
|
658
|
+
== getattr(config2, "supported_execution_parameter_styles", None)
|
|
659
|
+
)
|
|
660
|
+
except AttributeError:
|
|
661
|
+
return False
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def get_default_config() -> StatementConfig:
|
|
665
|
+
"""Get default statement configuration."""
|
|
666
|
+
return StatementConfig()
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
def get_default_parameter_config() -> ParameterStyleConfig:
|
|
670
|
+
"""Get default parameter configuration."""
|
|
671
|
+
return ParameterStyleConfig(
|
|
672
|
+
default_parameter_style=ParameterStyle.QMARK, supported_parameter_styles={ParameterStyle.QMARK}
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
Statement: TypeAlias = Union[str, exp.Expression, SQL]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Driver protocols and base classes for database adapters."""
|
|
2
|
+
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
from sqlspec.driver import mixins
|
|
6
|
+
from sqlspec.driver._async import AsyncDriverAdapterBase
|
|
7
|
+
from sqlspec.driver._common import CommonDriverAttributesMixin, ExecutionResult
|
|
8
|
+
from sqlspec.driver._sync import SyncDriverAdapterBase
|
|
9
|
+
|
|
10
|
+
__all__ = (
|
|
11
|
+
"AsyncDriverAdapterBase",
|
|
12
|
+
"CommonDriverAttributesMixin",
|
|
13
|
+
"DriverAdapterProtocol",
|
|
14
|
+
"ExecutionResult",
|
|
15
|
+
"SyncDriverAdapterBase",
|
|
16
|
+
"mixins",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
DriverAdapterProtocol = Union[SyncDriverAdapterBase, AsyncDriverAdapterBase]
|