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.

Files changed (148) hide show
  1. 51ff5a9eadfdefd49f98__mypyc.cpython-310-aarch64-linux-gnu.so +0 -0
  2. sqlspec/__init__.py +92 -0
  3. sqlspec/__main__.py +12 -0
  4. sqlspec/__metadata__.py +14 -0
  5. sqlspec/_serialization.py +77 -0
  6. sqlspec/_sql.py +1780 -0
  7. sqlspec/_typing.py +680 -0
  8. sqlspec/adapters/__init__.py +0 -0
  9. sqlspec/adapters/adbc/__init__.py +5 -0
  10. sqlspec/adapters/adbc/_types.py +12 -0
  11. sqlspec/adapters/adbc/config.py +361 -0
  12. sqlspec/adapters/adbc/driver.py +512 -0
  13. sqlspec/adapters/aiosqlite/__init__.py +19 -0
  14. sqlspec/adapters/aiosqlite/_types.py +13 -0
  15. sqlspec/adapters/aiosqlite/config.py +253 -0
  16. sqlspec/adapters/aiosqlite/driver.py +248 -0
  17. sqlspec/adapters/asyncmy/__init__.py +19 -0
  18. sqlspec/adapters/asyncmy/_types.py +12 -0
  19. sqlspec/adapters/asyncmy/config.py +180 -0
  20. sqlspec/adapters/asyncmy/driver.py +274 -0
  21. sqlspec/adapters/asyncpg/__init__.py +21 -0
  22. sqlspec/adapters/asyncpg/_types.py +17 -0
  23. sqlspec/adapters/asyncpg/config.py +229 -0
  24. sqlspec/adapters/asyncpg/driver.py +344 -0
  25. sqlspec/adapters/bigquery/__init__.py +18 -0
  26. sqlspec/adapters/bigquery/_types.py +12 -0
  27. sqlspec/adapters/bigquery/config.py +298 -0
  28. sqlspec/adapters/bigquery/driver.py +558 -0
  29. sqlspec/adapters/duckdb/__init__.py +22 -0
  30. sqlspec/adapters/duckdb/_types.py +12 -0
  31. sqlspec/adapters/duckdb/config.py +504 -0
  32. sqlspec/adapters/duckdb/driver.py +368 -0
  33. sqlspec/adapters/oracledb/__init__.py +32 -0
  34. sqlspec/adapters/oracledb/_types.py +14 -0
  35. sqlspec/adapters/oracledb/config.py +317 -0
  36. sqlspec/adapters/oracledb/driver.py +538 -0
  37. sqlspec/adapters/psqlpy/__init__.py +16 -0
  38. sqlspec/adapters/psqlpy/_types.py +11 -0
  39. sqlspec/adapters/psqlpy/config.py +214 -0
  40. sqlspec/adapters/psqlpy/driver.py +530 -0
  41. sqlspec/adapters/psycopg/__init__.py +32 -0
  42. sqlspec/adapters/psycopg/_types.py +17 -0
  43. sqlspec/adapters/psycopg/config.py +426 -0
  44. sqlspec/adapters/psycopg/driver.py +796 -0
  45. sqlspec/adapters/sqlite/__init__.py +15 -0
  46. sqlspec/adapters/sqlite/_types.py +11 -0
  47. sqlspec/adapters/sqlite/config.py +240 -0
  48. sqlspec/adapters/sqlite/driver.py +294 -0
  49. sqlspec/base.py +571 -0
  50. sqlspec/builder/__init__.py +62 -0
  51. sqlspec/builder/_base.py +473 -0
  52. sqlspec/builder/_column.py +320 -0
  53. sqlspec/builder/_ddl.py +1346 -0
  54. sqlspec/builder/_ddl_utils.py +103 -0
  55. sqlspec/builder/_delete.py +76 -0
  56. sqlspec/builder/_insert.py +256 -0
  57. sqlspec/builder/_merge.py +71 -0
  58. sqlspec/builder/_parsing_utils.py +140 -0
  59. sqlspec/builder/_select.py +170 -0
  60. sqlspec/builder/_update.py +188 -0
  61. sqlspec/builder/mixins/__init__.py +55 -0
  62. sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
  63. sqlspec/builder/mixins/_delete_operations.py +41 -0
  64. sqlspec/builder/mixins/_insert_operations.py +244 -0
  65. sqlspec/builder/mixins/_join_operations.py +122 -0
  66. sqlspec/builder/mixins/_merge_operations.py +476 -0
  67. sqlspec/builder/mixins/_order_limit_operations.py +135 -0
  68. sqlspec/builder/mixins/_pivot_operations.py +153 -0
  69. sqlspec/builder/mixins/_select_operations.py +603 -0
  70. sqlspec/builder/mixins/_update_operations.py +187 -0
  71. sqlspec/builder/mixins/_where_clause.py +621 -0
  72. sqlspec/cli.py +247 -0
  73. sqlspec/config.py +395 -0
  74. sqlspec/core/__init__.py +63 -0
  75. sqlspec/core/cache.cpython-310-aarch64-linux-gnu.so +0 -0
  76. sqlspec/core/cache.py +871 -0
  77. sqlspec/core/compiler.cpython-310-aarch64-linux-gnu.so +0 -0
  78. sqlspec/core/compiler.py +417 -0
  79. sqlspec/core/filters.cpython-310-aarch64-linux-gnu.so +0 -0
  80. sqlspec/core/filters.py +830 -0
  81. sqlspec/core/hashing.cpython-310-aarch64-linux-gnu.so +0 -0
  82. sqlspec/core/hashing.py +310 -0
  83. sqlspec/core/parameters.cpython-310-aarch64-linux-gnu.so +0 -0
  84. sqlspec/core/parameters.py +1237 -0
  85. sqlspec/core/result.cpython-310-aarch64-linux-gnu.so +0 -0
  86. sqlspec/core/result.py +677 -0
  87. sqlspec/core/splitter.cpython-310-aarch64-linux-gnu.so +0 -0
  88. sqlspec/core/splitter.py +819 -0
  89. sqlspec/core/statement.cpython-310-aarch64-linux-gnu.so +0 -0
  90. sqlspec/core/statement.py +676 -0
  91. sqlspec/driver/__init__.py +19 -0
  92. sqlspec/driver/_async.py +502 -0
  93. sqlspec/driver/_common.py +631 -0
  94. sqlspec/driver/_sync.py +503 -0
  95. sqlspec/driver/mixins/__init__.py +6 -0
  96. sqlspec/driver/mixins/_result_tools.py +193 -0
  97. sqlspec/driver/mixins/_sql_translator.py +86 -0
  98. sqlspec/exceptions.py +193 -0
  99. sqlspec/extensions/__init__.py +0 -0
  100. sqlspec/extensions/aiosql/__init__.py +10 -0
  101. sqlspec/extensions/aiosql/adapter.py +461 -0
  102. sqlspec/extensions/litestar/__init__.py +6 -0
  103. sqlspec/extensions/litestar/_utils.py +52 -0
  104. sqlspec/extensions/litestar/cli.py +48 -0
  105. sqlspec/extensions/litestar/config.py +92 -0
  106. sqlspec/extensions/litestar/handlers.py +260 -0
  107. sqlspec/extensions/litestar/plugin.py +145 -0
  108. sqlspec/extensions/litestar/providers.py +454 -0
  109. sqlspec/loader.cpython-310-aarch64-linux-gnu.so +0 -0
  110. sqlspec/loader.py +760 -0
  111. sqlspec/migrations/__init__.py +35 -0
  112. sqlspec/migrations/base.py +414 -0
  113. sqlspec/migrations/commands.py +443 -0
  114. sqlspec/migrations/loaders.py +402 -0
  115. sqlspec/migrations/runner.py +213 -0
  116. sqlspec/migrations/tracker.py +140 -0
  117. sqlspec/migrations/utils.py +129 -0
  118. sqlspec/protocols.py +407 -0
  119. sqlspec/py.typed +0 -0
  120. sqlspec/storage/__init__.py +23 -0
  121. sqlspec/storage/backends/__init__.py +0 -0
  122. sqlspec/storage/backends/base.py +163 -0
  123. sqlspec/storage/backends/fsspec.py +386 -0
  124. sqlspec/storage/backends/obstore.py +459 -0
  125. sqlspec/storage/capabilities.py +102 -0
  126. sqlspec/storage/registry.py +239 -0
  127. sqlspec/typing.py +299 -0
  128. sqlspec/utils/__init__.py +3 -0
  129. sqlspec/utils/correlation.py +150 -0
  130. sqlspec/utils/deprecation.py +106 -0
  131. sqlspec/utils/fixtures.cpython-310-aarch64-linux-gnu.so +0 -0
  132. sqlspec/utils/fixtures.py +58 -0
  133. sqlspec/utils/logging.py +127 -0
  134. sqlspec/utils/module_loader.py +89 -0
  135. sqlspec/utils/serializers.py +4 -0
  136. sqlspec/utils/singleton.py +32 -0
  137. sqlspec/utils/sync_tools.cpython-310-aarch64-linux-gnu.so +0 -0
  138. sqlspec/utils/sync_tools.py +237 -0
  139. sqlspec/utils/text.cpython-310-aarch64-linux-gnu.so +0 -0
  140. sqlspec/utils/text.py +96 -0
  141. sqlspec/utils/type_guards.cpython-310-aarch64-linux-gnu.so +0 -0
  142. sqlspec/utils/type_guards.py +1139 -0
  143. sqlspec-0.16.1.dist-info/METADATA +365 -0
  144. sqlspec-0.16.1.dist-info/RECORD +148 -0
  145. sqlspec-0.16.1.dist-info/WHEEL +7 -0
  146. sqlspec-0.16.1.dist-info/entry_points.txt +2 -0
  147. sqlspec-0.16.1.dist-info/licenses/LICENSE +21 -0
  148. sqlspec-0.16.1.dist-info/licenses/NOTICE +29 -0
@@ -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]