sqlspec 0.14.1__py3-none-any.whl → 0.15.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (158) hide show
  1. sqlspec/__init__.py +50 -25
  2. sqlspec/__main__.py +1 -1
  3. sqlspec/__metadata__.py +1 -3
  4. sqlspec/_serialization.py +1 -2
  5. sqlspec/_sql.py +256 -120
  6. sqlspec/_typing.py +278 -142
  7. sqlspec/adapters/adbc/__init__.py +4 -3
  8. sqlspec/adapters/adbc/_types.py +12 -0
  9. sqlspec/adapters/adbc/config.py +115 -260
  10. sqlspec/adapters/adbc/driver.py +462 -367
  11. sqlspec/adapters/aiosqlite/__init__.py +18 -3
  12. sqlspec/adapters/aiosqlite/_types.py +13 -0
  13. sqlspec/adapters/aiosqlite/config.py +199 -129
  14. sqlspec/adapters/aiosqlite/driver.py +230 -269
  15. sqlspec/adapters/asyncmy/__init__.py +18 -3
  16. sqlspec/adapters/asyncmy/_types.py +12 -0
  17. sqlspec/adapters/asyncmy/config.py +80 -168
  18. sqlspec/adapters/asyncmy/driver.py +260 -225
  19. sqlspec/adapters/asyncpg/__init__.py +19 -4
  20. sqlspec/adapters/asyncpg/_types.py +17 -0
  21. sqlspec/adapters/asyncpg/config.py +82 -181
  22. sqlspec/adapters/asyncpg/driver.py +285 -383
  23. sqlspec/adapters/bigquery/__init__.py +17 -3
  24. sqlspec/adapters/bigquery/_types.py +12 -0
  25. sqlspec/adapters/bigquery/config.py +191 -258
  26. sqlspec/adapters/bigquery/driver.py +474 -646
  27. sqlspec/adapters/duckdb/__init__.py +14 -3
  28. sqlspec/adapters/duckdb/_types.py +12 -0
  29. sqlspec/adapters/duckdb/config.py +415 -351
  30. sqlspec/adapters/duckdb/driver.py +343 -413
  31. sqlspec/adapters/oracledb/__init__.py +19 -5
  32. sqlspec/adapters/oracledb/_types.py +14 -0
  33. sqlspec/adapters/oracledb/config.py +123 -379
  34. sqlspec/adapters/oracledb/driver.py +507 -560
  35. sqlspec/adapters/psqlpy/__init__.py +13 -3
  36. sqlspec/adapters/psqlpy/_types.py +11 -0
  37. sqlspec/adapters/psqlpy/config.py +93 -254
  38. sqlspec/adapters/psqlpy/driver.py +505 -234
  39. sqlspec/adapters/psycopg/__init__.py +19 -5
  40. sqlspec/adapters/psycopg/_types.py +17 -0
  41. sqlspec/adapters/psycopg/config.py +143 -403
  42. sqlspec/adapters/psycopg/driver.py +706 -872
  43. sqlspec/adapters/sqlite/__init__.py +14 -3
  44. sqlspec/adapters/sqlite/_types.py +11 -0
  45. sqlspec/adapters/sqlite/config.py +202 -118
  46. sqlspec/adapters/sqlite/driver.py +264 -303
  47. sqlspec/base.py +105 -9
  48. sqlspec/{statement/builder → builder}/__init__.py +12 -14
  49. sqlspec/{statement/builder → builder}/_base.py +120 -55
  50. sqlspec/{statement/builder → builder}/_column.py +17 -6
  51. sqlspec/{statement/builder → builder}/_ddl.py +46 -79
  52. sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
  53. sqlspec/{statement/builder → builder}/_delete.py +6 -25
  54. sqlspec/{statement/builder → builder}/_insert.py +6 -64
  55. sqlspec/builder/_merge.py +56 -0
  56. sqlspec/{statement/builder → builder}/_parsing_utils.py +3 -10
  57. sqlspec/{statement/builder → builder}/_select.py +11 -56
  58. sqlspec/{statement/builder → builder}/_update.py +12 -18
  59. sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
  60. sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
  61. sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +22 -16
  62. sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
  63. sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +3 -5
  64. sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
  65. sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
  66. sqlspec/{statement/builder → builder}/mixins/_select_operations.py +21 -36
  67. sqlspec/{statement/builder → builder}/mixins/_update_operations.py +3 -14
  68. sqlspec/{statement/builder → builder}/mixins/_where_clause.py +52 -79
  69. sqlspec/cli.py +4 -5
  70. sqlspec/config.py +180 -133
  71. sqlspec/core/__init__.py +63 -0
  72. sqlspec/core/cache.py +873 -0
  73. sqlspec/core/compiler.py +396 -0
  74. sqlspec/core/filters.py +828 -0
  75. sqlspec/core/hashing.py +310 -0
  76. sqlspec/core/parameters.py +1209 -0
  77. sqlspec/core/result.py +664 -0
  78. sqlspec/{statement → core}/splitter.py +321 -191
  79. sqlspec/core/statement.py +651 -0
  80. sqlspec/driver/__init__.py +7 -10
  81. sqlspec/driver/_async.py +387 -176
  82. sqlspec/driver/_common.py +527 -289
  83. sqlspec/driver/_sync.py +390 -172
  84. sqlspec/driver/mixins/__init__.py +2 -19
  85. sqlspec/driver/mixins/_result_tools.py +168 -0
  86. sqlspec/driver/mixins/_sql_translator.py +6 -3
  87. sqlspec/exceptions.py +5 -252
  88. sqlspec/extensions/aiosql/adapter.py +93 -96
  89. sqlspec/extensions/litestar/config.py +0 -1
  90. sqlspec/extensions/litestar/handlers.py +15 -26
  91. sqlspec/extensions/litestar/plugin.py +16 -14
  92. sqlspec/extensions/litestar/providers.py +17 -52
  93. sqlspec/loader.py +424 -105
  94. sqlspec/migrations/__init__.py +12 -0
  95. sqlspec/migrations/base.py +92 -68
  96. sqlspec/migrations/commands.py +24 -106
  97. sqlspec/migrations/loaders.py +402 -0
  98. sqlspec/migrations/runner.py +49 -51
  99. sqlspec/migrations/tracker.py +31 -44
  100. sqlspec/migrations/utils.py +64 -24
  101. sqlspec/protocols.py +7 -183
  102. sqlspec/storage/__init__.py +1 -1
  103. sqlspec/storage/backends/base.py +37 -40
  104. sqlspec/storage/backends/fsspec.py +136 -112
  105. sqlspec/storage/backends/obstore.py +138 -160
  106. sqlspec/storage/capabilities.py +5 -4
  107. sqlspec/storage/registry.py +57 -106
  108. sqlspec/typing.py +136 -115
  109. sqlspec/utils/__init__.py +2 -3
  110. sqlspec/utils/correlation.py +0 -3
  111. sqlspec/utils/deprecation.py +6 -6
  112. sqlspec/utils/fixtures.py +6 -6
  113. sqlspec/utils/logging.py +0 -2
  114. sqlspec/utils/module_loader.py +7 -12
  115. sqlspec/utils/singleton.py +0 -1
  116. sqlspec/utils/sync_tools.py +16 -37
  117. sqlspec/utils/text.py +12 -51
  118. sqlspec/utils/type_guards.py +443 -232
  119. {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/METADATA +7 -2
  120. sqlspec-0.15.0.dist-info/RECORD +134 -0
  121. sqlspec/adapters/adbc/transformers.py +0 -108
  122. sqlspec/driver/connection.py +0 -207
  123. sqlspec/driver/mixins/_cache.py +0 -114
  124. sqlspec/driver/mixins/_csv_writer.py +0 -91
  125. sqlspec/driver/mixins/_pipeline.py +0 -508
  126. sqlspec/driver/mixins/_query_tools.py +0 -796
  127. sqlspec/driver/mixins/_result_utils.py +0 -138
  128. sqlspec/driver/mixins/_storage.py +0 -912
  129. sqlspec/driver/mixins/_type_coercion.py +0 -128
  130. sqlspec/driver/parameters.py +0 -138
  131. sqlspec/statement/__init__.py +0 -21
  132. sqlspec/statement/builder/_merge.py +0 -95
  133. sqlspec/statement/cache.py +0 -50
  134. sqlspec/statement/filters.py +0 -625
  135. sqlspec/statement/parameters.py +0 -956
  136. sqlspec/statement/pipelines/__init__.py +0 -210
  137. sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
  138. sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
  139. sqlspec/statement/pipelines/context.py +0 -109
  140. sqlspec/statement/pipelines/transformers/__init__.py +0 -7
  141. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
  142. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
  143. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
  144. sqlspec/statement/pipelines/validators/__init__.py +0 -23
  145. sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
  146. sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
  147. sqlspec/statement/pipelines/validators/_performance.py +0 -714
  148. sqlspec/statement/pipelines/validators/_security.py +0 -967
  149. sqlspec/statement/result.py +0 -435
  150. sqlspec/statement/sql.py +0 -1774
  151. sqlspec/utils/cached_property.py +0 -25
  152. sqlspec/utils/statement_hashing.py +0 -203
  153. sqlspec-0.14.1.dist-info/RECORD +0 -145
  154. /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
  155. {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/WHEEL +0 -0
  156. {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/entry_points.txt +0 -0
  157. {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/licenses/LICENSE +0 -0
  158. {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,651 @@
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
+ filters = [p for p in parameters if is_statement_filter(p)]
224
+ actual_params = [p for p in parameters if not is_statement_filter(p)]
225
+
226
+ self._filters.extend(filters)
227
+
228
+ if actual_params:
229
+ if len(actual_params) == 1:
230
+ param = actual_params[0]
231
+ if isinstance(param, dict):
232
+ self._named_parameters.update(param)
233
+ elif isinstance(param, (list, tuple)):
234
+ if self._is_many:
235
+ self._positional_parameters = list(param)
236
+ else:
237
+ self._positional_parameters.extend(param)
238
+ else:
239
+ self._positional_parameters.append(param)
240
+ else:
241
+ self._positional_parameters.extend(actual_params)
242
+
243
+ self._named_parameters.update(kwargs)
244
+
245
+ # PRESERVED PROPERTIES - Exact same interface as existing SQL class
246
+ @property
247
+ def sql(self) -> str:
248
+ """Get the raw SQL string - no compilation triggered."""
249
+ return self._raw_sql
250
+
251
+ @property
252
+ def parameters(self) -> Any:
253
+ """Get the original parameters without triggering compilation."""
254
+ if self._named_parameters:
255
+ return self._named_parameters
256
+ return self._positional_parameters or []
257
+
258
+ @property
259
+ def operation_type(self) -> str:
260
+ """SQL operation type - requires explicit compilation."""
261
+ if self._processed_state is Empty:
262
+ return "UNKNOWN"
263
+ return self._processed_state.operation_type
264
+
265
+ @property
266
+ def statement_config(self) -> "StatementConfig":
267
+ """Statement configuration - preserved interface."""
268
+ return self._statement_config
269
+
270
+ @property
271
+ def expression(self) -> "Optional[exp.Expression]":
272
+ """SQLGlot expression - only available after explicit compilation."""
273
+ # This property should only be accessed after compilation
274
+ # If not compiled yet, return None
275
+ if self._processed_state is not Empty:
276
+ return self._processed_state.parsed_expression
277
+ return None
278
+
279
+ @property
280
+ def filters(self) -> "list[StatementFilter]":
281
+ """Applied filters."""
282
+ return self._filters.copy()
283
+
284
+ @property
285
+ def dialect(self) -> "Optional[str]":
286
+ """SQL dialect."""
287
+ return self._dialect
288
+
289
+ @property
290
+ def _statement(self) -> "Optional[exp.Expression]":
291
+ """Internal SQLGlot expression."""
292
+ return self.expression
293
+
294
+ @property
295
+ def is_many(self) -> bool:
296
+ """Check if this is execute_many."""
297
+ return self._is_many
298
+
299
+ @property
300
+ def is_script(self) -> bool:
301
+ """Check if this is script execution."""
302
+ return self._is_script
303
+
304
+ @property
305
+ def validation_errors(self) -> "list[str]":
306
+ """Validation errors - requires explicit compilation."""
307
+ if self._processed_state is Empty:
308
+ return []
309
+ return self._processed_state.validation_errors.copy()
310
+
311
+ @property
312
+ def has_errors(self) -> bool:
313
+ """Check if there are validation errors."""
314
+ return len(self.validation_errors) > 0
315
+
316
+ def returns_rows(self) -> bool:
317
+ """Check if statement returns rows."""
318
+ sql_upper = self._raw_sql.strip().upper()
319
+ if any(sql_upper.startswith(op) for op in ("SELECT", "WITH", "VALUES", "TABLE", "SHOW", "DESCRIBE", "PRAGMA")):
320
+ return True
321
+
322
+ return "RETURNING" in sql_upper
323
+
324
+ def is_modifying_operation(self) -> bool:
325
+ """Check if the SQL statement is a modifying operation.
326
+
327
+ Returns:
328
+ True if the operation modifies data (INSERT/UPDATE/DELETE)
329
+ """
330
+ expression = self.expression
331
+ if expression and isinstance(expression, (exp.Insert, exp.Update, exp.Delete)):
332
+ return True
333
+
334
+ sql_upper = self.sql.strip().upper()
335
+ modifying_operations = ("INSERT", "UPDATE", "DELETE")
336
+ return any(sql_upper.startswith(op) for op in modifying_operations)
337
+
338
+ def compile(self) -> tuple[str, Any]:
339
+ """Explicitly compile the SQL statement."""
340
+ if self._processed_state is Empty:
341
+ try:
342
+ current_parameters = self._named_parameters or self._positional_parameters
343
+ processor = SQLProcessor(self._statement_config)
344
+
345
+ compiled_result = processor.compile(self._raw_sql, current_parameters, is_many=self._is_many)
346
+
347
+ self._processed_state = ProcessedState(
348
+ compiled_sql=compiled_result.compiled_sql,
349
+ execution_parameters=compiled_result.execution_parameters,
350
+ parsed_expression=compiled_result.expression,
351
+ operation_type=compiled_result.operation_type,
352
+ validation_errors=[],
353
+ is_many=self._is_many,
354
+ )
355
+ except Exception as e:
356
+ logger.warning("Processing failed, using fallback: %s", e)
357
+ self._processed_state = ProcessedState(
358
+ compiled_sql=self._raw_sql,
359
+ execution_parameters=self._named_parameters or self._positional_parameters,
360
+ operation_type="UNKNOWN",
361
+ is_many=self._is_many,
362
+ )
363
+
364
+ return self._processed_state.compiled_sql, self._processed_state.execution_parameters
365
+
366
+ def as_script(self) -> "SQL":
367
+ """Mark as script execution."""
368
+ new_sql = SQL(
369
+ self._raw_sql, *self._original_parameters, statement_config=self._statement_config, is_many=self._is_many
370
+ )
371
+ new_sql._is_script = True
372
+ return new_sql
373
+
374
+ def copy(
375
+ self, statement: "Optional[Union[str, exp.Expression]]" = None, parameters: Optional[Any] = None, **kwargs: Any
376
+ ) -> "SQL":
377
+ """Create copy with modifications."""
378
+ return SQL(
379
+ statement or self._raw_sql,
380
+ *(parameters if parameters is not None else self._original_parameters),
381
+ statement_config=self._statement_config,
382
+ is_many=self._is_many,
383
+ **kwargs,
384
+ )
385
+
386
+ def add_named_parameter(self, name: str, value: Any) -> "SQL":
387
+ """Add a named parameter and return a new SQL instance.
388
+
389
+ Args:
390
+ name: Parameter name
391
+ value: Parameter value
392
+
393
+ Returns:
394
+ New SQL instance with the added parameter
395
+ """
396
+ new_sql = SQL(
397
+ self._raw_sql, *self._original_parameters, statement_config=self._statement_config, is_many=self._is_many
398
+ )
399
+ new_sql._named_parameters.update(self._named_parameters)
400
+ new_sql._named_parameters[name] = value
401
+ new_sql._positional_parameters = self._positional_parameters.copy()
402
+ new_sql._filters = self._filters.copy()
403
+ return new_sql
404
+
405
+ def where(self, condition: "Union[str, exp.Expression]") -> "SQL":
406
+ """Add WHERE condition to the SQL statement.
407
+
408
+ Args:
409
+ condition: WHERE condition as string or SQLGlot expression
410
+
411
+ Returns:
412
+ New SQL instance with the WHERE condition applied
413
+ """
414
+ current_expr = None
415
+ with contextlib.suppress(ParseError):
416
+ current_expr = sqlglot.parse_one(self._raw_sql, dialect=self._dialect)
417
+
418
+ if current_expr is None:
419
+ try:
420
+ current_expr = sqlglot.parse_one(self._raw_sql, dialect=self._dialect)
421
+ except ParseError:
422
+ current_expr = sqlglot.parse_one(f"SELECT * FROM ({self._raw_sql}) AS subquery", dialect=self._dialect)
423
+
424
+ condition_expr: exp.Expression
425
+ if isinstance(condition, str):
426
+ try:
427
+ condition_expr = sqlglot.parse_one(condition, dialect=self._dialect, into=exp.Condition)
428
+ except ParseError:
429
+ condition_expr = exp.Condition(this=condition)
430
+ else:
431
+ condition_expr = condition
432
+
433
+ if isinstance(current_expr, exp.Select) or supports_where(current_expr):
434
+ new_expr = current_expr.where(condition_expr)
435
+ else:
436
+ new_expr = exp.Select().from_(current_expr).where(condition_expr)
437
+
438
+ new_sql_text = new_expr.sql(dialect=self._dialect)
439
+
440
+ return SQL(
441
+ new_sql_text, *self._original_parameters, statement_config=self._statement_config, is_many=self._is_many
442
+ )
443
+
444
+ def __hash__(self) -> int:
445
+ """Hash value."""
446
+ if self._hash is None:
447
+ self._hash = hash(
448
+ (
449
+ self._raw_sql,
450
+ tuple(self._positional_parameters),
451
+ tuple(sorted(self._named_parameters.items())),
452
+ self._is_many,
453
+ self._is_script,
454
+ )
455
+ )
456
+ return self._hash
457
+
458
+ def __eq__(self, other: object) -> bool:
459
+ """Equality comparison."""
460
+ if not isinstance(other, SQL):
461
+ return False
462
+ return (
463
+ self._raw_sql == other._raw_sql
464
+ and self._positional_parameters == other._positional_parameters
465
+ and self._named_parameters == other._named_parameters
466
+ and self._is_many == other._is_many
467
+ and self._is_script == other._is_script
468
+ )
469
+
470
+ def __repr__(self) -> str:
471
+ """String representation."""
472
+ params_str = ""
473
+ if self._named_parameters:
474
+ params_str = f", named_params={self._named_parameters}"
475
+ elif self._positional_parameters:
476
+ params_str = f", params={self._positional_parameters}"
477
+
478
+ flags = []
479
+ if self._is_many:
480
+ flags.append("is_many")
481
+ if self._is_script:
482
+ flags.append("is_script")
483
+ flags_str = f", {', '.join(flags)}" if flags else ""
484
+
485
+ return f"SQL({self._raw_sql!r}{params_str}{flags_str})"
486
+
487
+
488
+ @mypyc_attr(allow_interpreted_subclasses=True)
489
+ class StatementConfig:
490
+ """Configuration for SQL statement processing.
491
+
492
+ Provides all attributes that drivers expect for SQL processing.
493
+
494
+ Features:
495
+ - Complete parameter processing configuration
496
+ - Caching and execution mode interfaces
497
+ - Support for various database-specific operations
498
+ - Immutable updates via replace() method
499
+ """
500
+
501
+ __slots__ = SQL_CONFIG_SLOTS
502
+
503
+ def __init__(
504
+ self,
505
+ parameter_config: "Optional[ParameterStyleConfig]" = None,
506
+ enable_parsing: bool = True,
507
+ enable_validation: bool = True,
508
+ enable_transformations: bool = True,
509
+ enable_analysis: bool = False,
510
+ enable_expression_simplification: bool = False,
511
+ enable_parameter_type_wrapping: bool = True,
512
+ enable_caching: bool = True,
513
+ parameter_converter: "Optional[ParameterConverter]" = None,
514
+ parameter_validator: "Optional[ParameterValidator]" = None,
515
+ dialect: "Optional[DialectType]" = None,
516
+ pre_process_steps: "Optional[list[Any]]" = None,
517
+ post_process_steps: "Optional[list[Any]]" = None,
518
+ execution_mode: "Optional[str]" = None,
519
+ execution_args: "Optional[dict[str, Any]]" = None,
520
+ output_transformer: "Optional[Callable[[str, Any], tuple[str, Any]]]" = None,
521
+ ) -> None:
522
+ """Initialize StatementConfig.
523
+
524
+ Args:
525
+ parameter_config: Parameter style configuration
526
+ enable_parsing: Enable SQL parsing using sqlglot
527
+ enable_validation: Run SQL validators to check for safety issues
528
+ enable_transformations: Apply SQL transformers
529
+ enable_analysis: Run SQL analyzers for metadata extraction
530
+ enable_expression_simplification: Apply expression simplification
531
+ enable_parameter_type_wrapping: Wrap parameters with type information
532
+ enable_caching: Cache processed SQL statements
533
+ parameter_converter: Handles parameter style conversions
534
+ parameter_validator: Validates parameter usage and styles
535
+ dialect: SQL dialect for parsing and generation
536
+ pre_process_steps: Optional list of preprocessing steps
537
+ post_process_steps: Optional list of postprocessing steps
538
+ execution_mode: Special execution mode
539
+ execution_args: Arguments for special execution modes
540
+ output_transformer: Optional output transformation function
541
+ """
542
+ self.enable_parsing = enable_parsing
543
+ self.enable_validation = enable_validation
544
+ self.enable_transformations = enable_transformations
545
+ self.enable_analysis = enable_analysis
546
+ self.enable_expression_simplification = enable_expression_simplification
547
+ self.enable_parameter_type_wrapping = enable_parameter_type_wrapping
548
+ self.enable_caching = enable_caching
549
+ self.parameter_converter = parameter_converter or ParameterConverter()
550
+ self.parameter_validator = parameter_validator or ParameterValidator()
551
+ self.parameter_config = parameter_config or ParameterStyleConfig(
552
+ default_parameter_style=ParameterStyle.QMARK, supported_parameter_styles={ParameterStyle.QMARK}
553
+ )
554
+
555
+ self.dialect = dialect
556
+ self.pre_process_steps = pre_process_steps
557
+ self.post_process_steps = post_process_steps
558
+ self.execution_mode = execution_mode
559
+ self.execution_args = execution_args
560
+ self.output_transformer = output_transformer
561
+
562
+ def replace(self, **kwargs: Any) -> "StatementConfig":
563
+ """Immutable update pattern.
564
+
565
+ Args:
566
+ **kwargs: Attributes to update
567
+
568
+ Returns:
569
+ New StatementConfig instance with updated attributes
570
+ """
571
+ for key in kwargs:
572
+ if key not in SQL_CONFIG_SLOTS:
573
+ msg = f"{key!r} is not a field in {type(self).__name__}"
574
+ raise TypeError(msg)
575
+
576
+ current_kwargs = {slot: getattr(self, slot) for slot in SQL_CONFIG_SLOTS}
577
+ current_kwargs.update(kwargs)
578
+ return type(self)(**current_kwargs)
579
+
580
+ def __hash__(self) -> int:
581
+ """Hash based on key configuration settings."""
582
+ return hash(
583
+ (
584
+ self.enable_parsing,
585
+ self.enable_validation,
586
+ self.enable_transformations,
587
+ self.enable_analysis,
588
+ self.enable_expression_simplification,
589
+ self.enable_parameter_type_wrapping,
590
+ self.enable_caching,
591
+ str(self.dialect),
592
+ )
593
+ )
594
+
595
+ def __repr__(self) -> str:
596
+ """String representation of the StatementConfig instance."""
597
+ field_strs = []
598
+ for slot in SQL_CONFIG_SLOTS:
599
+ value = getattr(self, slot)
600
+ field_strs.append(f"{slot}={value!r}")
601
+ return f"{self.__class__.__name__}({', '.join(field_strs)})"
602
+
603
+ def __eq__(self, other: object) -> bool:
604
+ """Equality comparison."""
605
+ if not isinstance(other, type(self)):
606
+ return False
607
+
608
+ for slot in SQL_CONFIG_SLOTS:
609
+ self_val = getattr(self, slot)
610
+ other_val = getattr(other, slot)
611
+
612
+ if hasattr(self_val, "__class__") and hasattr(other_val, "__class__"):
613
+ if self_val.__class__ != other_val.__class__:
614
+ return False
615
+ if slot == "parameter_config":
616
+ if not self._compare_parameter_configs(self_val, other_val):
617
+ return False
618
+ elif slot in {"parameter_converter", "parameter_validator"}:
619
+ continue
620
+ elif self_val != other_val:
621
+ return False
622
+ elif self_val != other_val:
623
+ return False
624
+ return True
625
+
626
+ def _compare_parameter_configs(self, config1: Any, config2: Any) -> bool:
627
+ """Compare parameter configs by key attributes."""
628
+ try:
629
+ return (
630
+ config1.default_parameter_style == config2.default_parameter_style
631
+ and config1.supported_parameter_styles == config2.supported_parameter_styles
632
+ and getattr(config1, "supported_execution_parameter_styles", None)
633
+ == getattr(config2, "supported_execution_parameter_styles", None)
634
+ )
635
+ except AttributeError:
636
+ return False
637
+
638
+
639
+ def get_default_config() -> StatementConfig:
640
+ """Get default statement configuration."""
641
+ return StatementConfig()
642
+
643
+
644
+ def get_default_parameter_config() -> ParameterStyleConfig:
645
+ """Get default parameter configuration."""
646
+ return ParameterStyleConfig(
647
+ default_parameter_style=ParameterStyle.QMARK, supported_parameter_styles={ParameterStyle.QMARK}
648
+ )
649
+
650
+
651
+ Statement: TypeAlias = Union[str, exp.Expression, SQL]
@@ -3,20 +3,17 @@
3
3
  from typing import Union
4
4
 
5
5
  from sqlspec.driver import mixins
6
- from sqlspec.driver._async import AsyncDriverAdapterProtocol
7
- from sqlspec.driver._common import CommonDriverAttributesMixin
8
- from sqlspec.driver._sync import SyncDriverAdapterProtocol
9
- from sqlspec.typing import ConnectionT, RowT
6
+ from sqlspec.driver._async import AsyncDriverAdapterBase
7
+ from sqlspec.driver._common import CommonDriverAttributesMixin, ExecutionResult
8
+ from sqlspec.driver._sync import SyncDriverAdapterBase
10
9
 
11
10
  __all__ = (
12
- "AsyncDriverAdapterProtocol",
11
+ "AsyncDriverAdapterBase",
13
12
  "CommonDriverAttributesMixin",
14
13
  "DriverAdapterProtocol",
15
- "SyncDriverAdapterProtocol",
14
+ "ExecutionResult",
15
+ "SyncDriverAdapterBase",
16
16
  "mixins",
17
17
  )
18
18
 
19
- # Type alias for convenience
20
- DriverAdapterProtocol = Union[
21
- SyncDriverAdapterProtocol[ConnectionT, RowT], AsyncDriverAdapterProtocol[ConnectionT, RowT]
22
- ]
19
+ DriverAdapterProtocol = Union[SyncDriverAdapterBase, AsyncDriverAdapterBase]