sqlspec 0.16.0__cp39-cp39-macosx_14_0_arm64.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-39-darwin.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 +1347 -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 +440 -0
  52. sqlspec/builder/_column.py +324 -0
  53. sqlspec/builder/_ddl.py +1383 -0
  54. sqlspec/builder/_ddl_utils.py +104 -0
  55. sqlspec/builder/_delete.py +77 -0
  56. sqlspec/builder/_insert.py +241 -0
  57. sqlspec/builder/_merge.py +56 -0
  58. sqlspec/builder/_parsing_utils.py +140 -0
  59. sqlspec/builder/_select.py +174 -0
  60. sqlspec/builder/_update.py +186 -0
  61. sqlspec/builder/mixins/__init__.py +55 -0
  62. sqlspec/builder/mixins/_cte_and_set_ops.py +195 -0
  63. sqlspec/builder/mixins/_delete_operations.py +36 -0
  64. sqlspec/builder/mixins/_insert_operations.py +152 -0
  65. sqlspec/builder/mixins/_join_operations.py +115 -0
  66. sqlspec/builder/mixins/_merge_operations.py +416 -0
  67. sqlspec/builder/mixins/_order_limit_operations.py +123 -0
  68. sqlspec/builder/mixins/_pivot_operations.py +144 -0
  69. sqlspec/builder/mixins/_select_operations.py +599 -0
  70. sqlspec/builder/mixins/_update_operations.py +164 -0
  71. sqlspec/builder/mixins/_where_clause.py +609 -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-39-darwin.so +0 -0
  76. sqlspec/core/cache.py +873 -0
  77. sqlspec/core/compiler.cpython-39-darwin.so +0 -0
  78. sqlspec/core/compiler.py +396 -0
  79. sqlspec/core/filters.cpython-39-darwin.so +0 -0
  80. sqlspec/core/filters.py +830 -0
  81. sqlspec/core/hashing.cpython-39-darwin.so +0 -0
  82. sqlspec/core/hashing.py +310 -0
  83. sqlspec/core/parameters.cpython-39-darwin.so +0 -0
  84. sqlspec/core/parameters.py +1209 -0
  85. sqlspec/core/result.cpython-39-darwin.so +0 -0
  86. sqlspec/core/result.py +664 -0
  87. sqlspec/core/splitter.cpython-39-darwin.so +0 -0
  88. sqlspec/core/splitter.py +819 -0
  89. sqlspec/core/statement.cpython-39-darwin.so +0 -0
  90. sqlspec/core/statement.py +666 -0
  91. sqlspec/driver/__init__.py +19 -0
  92. sqlspec/driver/_async.py +472 -0
  93. sqlspec/driver/_common.py +612 -0
  94. sqlspec/driver/_sync.py +473 -0
  95. sqlspec/driver/mixins/__init__.py +6 -0
  96. sqlspec/driver/mixins/_result_tools.py +164 -0
  97. sqlspec/driver/mixins/_sql_translator.py +36 -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-39-darwin.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 +400 -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-39-darwin.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-39-darwin.so +0 -0
  138. sqlspec/utils/sync_tools.py +237 -0
  139. sqlspec/utils/text.cpython-39-darwin.so +0 -0
  140. sqlspec/utils/text.py +96 -0
  141. sqlspec/utils/type_guards.cpython-39-darwin.so +0 -0
  142. sqlspec/utils/type_guards.py +1135 -0
  143. sqlspec-0.16.0.dist-info/METADATA +365 -0
  144. sqlspec-0.16.0.dist-info/RECORD +148 -0
  145. sqlspec-0.16.0.dist-info/WHEEL +4 -0
  146. sqlspec-0.16.0.dist-info/entry_points.txt +2 -0
  147. sqlspec-0.16.0.dist-info/licenses/LICENSE +21 -0
  148. sqlspec-0.16.0.dist-info/licenses/NOTICE +29 -0
@@ -0,0 +1,666 @@
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
+ # Preserve accumulated parameters when marking as script
372
+ new_sql._named_parameters.update(self._named_parameters)
373
+ new_sql._positional_parameters = self._positional_parameters.copy()
374
+ new_sql._filters = self._filters.copy()
375
+ new_sql._is_script = True
376
+ return new_sql
377
+
378
+ def copy(
379
+ self, statement: "Optional[Union[str, exp.Expression]]" = None, parameters: Optional[Any] = None, **kwargs: Any
380
+ ) -> "SQL":
381
+ """Create copy with modifications."""
382
+ new_sql = SQL(
383
+ statement or self._raw_sql,
384
+ *(parameters if parameters is not None else self._original_parameters),
385
+ statement_config=self._statement_config,
386
+ is_many=self._is_many,
387
+ **kwargs,
388
+ )
389
+ # Only preserve accumulated parameters when no explicit parameters are provided
390
+ if parameters is None:
391
+ new_sql._named_parameters.update(self._named_parameters)
392
+ new_sql._positional_parameters = self._positional_parameters.copy()
393
+ new_sql._filters = self._filters.copy()
394
+ return new_sql
395
+
396
+ def add_named_parameter(self, name: str, value: Any) -> "SQL":
397
+ """Add a named parameter and return a new SQL instance.
398
+
399
+ Args:
400
+ name: Parameter name
401
+ value: Parameter value
402
+
403
+ Returns:
404
+ New SQL instance with the added parameter
405
+ """
406
+ new_sql = SQL(
407
+ self._raw_sql, *self._original_parameters, statement_config=self._statement_config, is_many=self._is_many
408
+ )
409
+ new_sql._named_parameters.update(self._named_parameters)
410
+ new_sql._named_parameters[name] = value
411
+ new_sql._positional_parameters = self._positional_parameters.copy()
412
+ new_sql._filters = self._filters.copy()
413
+ return new_sql
414
+
415
+ def where(self, condition: "Union[str, exp.Expression]") -> "SQL":
416
+ """Add WHERE condition to the SQL statement.
417
+
418
+ Args:
419
+ condition: WHERE condition as string or SQLGlot expression
420
+
421
+ Returns:
422
+ New SQL instance with the WHERE condition applied
423
+ """
424
+ current_expr = None
425
+ with contextlib.suppress(ParseError):
426
+ current_expr = sqlglot.parse_one(self._raw_sql, dialect=self._dialect)
427
+
428
+ if current_expr is None:
429
+ try:
430
+ current_expr = sqlglot.parse_one(self._raw_sql, dialect=self._dialect)
431
+ except ParseError:
432
+ current_expr = sqlglot.parse_one(f"SELECT * FROM ({self._raw_sql}) AS subquery", dialect=self._dialect)
433
+
434
+ condition_expr: exp.Expression
435
+ if isinstance(condition, str):
436
+ try:
437
+ condition_expr = sqlglot.parse_one(condition, dialect=self._dialect, into=exp.Condition)
438
+ except ParseError:
439
+ condition_expr = exp.Condition(this=condition)
440
+ else:
441
+ condition_expr = condition
442
+
443
+ if isinstance(current_expr, exp.Select) or supports_where(current_expr):
444
+ new_expr = current_expr.where(condition_expr)
445
+ else:
446
+ new_expr = exp.Select().from_(current_expr).where(condition_expr)
447
+
448
+ new_sql_text = new_expr.sql(dialect=self._dialect)
449
+
450
+ new_sql = SQL(
451
+ new_sql_text, *self._original_parameters, statement_config=self._statement_config, is_many=self._is_many
452
+ )
453
+ # Preserve accumulated named parameters when creating WHERE clause
454
+ new_sql._named_parameters.update(self._named_parameters)
455
+ new_sql._positional_parameters = self._positional_parameters.copy()
456
+ new_sql._filters = self._filters.copy()
457
+ return new_sql
458
+
459
+ def __hash__(self) -> int:
460
+ """Hash value."""
461
+ if self._hash is None:
462
+ self._hash = hash(
463
+ (
464
+ self._raw_sql,
465
+ tuple(self._positional_parameters),
466
+ tuple(sorted(self._named_parameters.items())),
467
+ self._is_many,
468
+ self._is_script,
469
+ )
470
+ )
471
+ return self._hash
472
+
473
+ def __eq__(self, other: object) -> bool:
474
+ """Equality comparison."""
475
+ if not isinstance(other, SQL):
476
+ return False
477
+ return (
478
+ self._raw_sql == other._raw_sql
479
+ and self._positional_parameters == other._positional_parameters
480
+ and self._named_parameters == other._named_parameters
481
+ and self._is_many == other._is_many
482
+ and self._is_script == other._is_script
483
+ )
484
+
485
+ def __repr__(self) -> str:
486
+ """String representation."""
487
+ params_str = ""
488
+ if self._named_parameters:
489
+ params_str = f", named_params={self._named_parameters}"
490
+ elif self._positional_parameters:
491
+ params_str = f", params={self._positional_parameters}"
492
+
493
+ flags = []
494
+ if self._is_many:
495
+ flags.append("is_many")
496
+ if self._is_script:
497
+ flags.append("is_script")
498
+ flags_str = f", {', '.join(flags)}" if flags else ""
499
+
500
+ return f"SQL({self._raw_sql!r}{params_str}{flags_str})"
501
+
502
+
503
+ @mypyc_attr(allow_interpreted_subclasses=True)
504
+ class StatementConfig:
505
+ """Configuration for SQL statement processing.
506
+
507
+ Provides all attributes that drivers expect for SQL processing.
508
+
509
+ Features:
510
+ - Complete parameter processing configuration
511
+ - Caching and execution mode interfaces
512
+ - Support for various database-specific operations
513
+ - Immutable updates via replace() method
514
+ """
515
+
516
+ __slots__ = SQL_CONFIG_SLOTS
517
+
518
+ def __init__(
519
+ self,
520
+ parameter_config: "Optional[ParameterStyleConfig]" = None,
521
+ enable_parsing: bool = True,
522
+ enable_validation: bool = True,
523
+ enable_transformations: bool = True,
524
+ enable_analysis: bool = False,
525
+ enable_expression_simplification: bool = False,
526
+ enable_parameter_type_wrapping: bool = True,
527
+ enable_caching: bool = True,
528
+ parameter_converter: "Optional[ParameterConverter]" = None,
529
+ parameter_validator: "Optional[ParameterValidator]" = None,
530
+ dialect: "Optional[DialectType]" = None,
531
+ pre_process_steps: "Optional[list[Any]]" = None,
532
+ post_process_steps: "Optional[list[Any]]" = None,
533
+ execution_mode: "Optional[str]" = None,
534
+ execution_args: "Optional[dict[str, Any]]" = None,
535
+ output_transformer: "Optional[Callable[[str, Any], tuple[str, Any]]]" = None,
536
+ ) -> None:
537
+ """Initialize StatementConfig.
538
+
539
+ Args:
540
+ parameter_config: Parameter style configuration
541
+ enable_parsing: Enable SQL parsing using sqlglot
542
+ enable_validation: Run SQL validators to check for safety issues
543
+ enable_transformations: Apply SQL transformers
544
+ enable_analysis: Run SQL analyzers for metadata extraction
545
+ enable_expression_simplification: Apply expression simplification
546
+ enable_parameter_type_wrapping: Wrap parameters with type information
547
+ enable_caching: Cache processed SQL statements
548
+ parameter_converter: Handles parameter style conversions
549
+ parameter_validator: Validates parameter usage and styles
550
+ dialect: SQL dialect for parsing and generation
551
+ pre_process_steps: Optional list of preprocessing steps
552
+ post_process_steps: Optional list of postprocessing steps
553
+ execution_mode: Special execution mode
554
+ execution_args: Arguments for special execution modes
555
+ output_transformer: Optional output transformation function
556
+ """
557
+ self.enable_parsing = enable_parsing
558
+ self.enable_validation = enable_validation
559
+ self.enable_transformations = enable_transformations
560
+ self.enable_analysis = enable_analysis
561
+ self.enable_expression_simplification = enable_expression_simplification
562
+ self.enable_parameter_type_wrapping = enable_parameter_type_wrapping
563
+ self.enable_caching = enable_caching
564
+ self.parameter_converter = parameter_converter or ParameterConverter()
565
+ self.parameter_validator = parameter_validator or ParameterValidator()
566
+ self.parameter_config = parameter_config or ParameterStyleConfig(
567
+ default_parameter_style=ParameterStyle.QMARK, supported_parameter_styles={ParameterStyle.QMARK}
568
+ )
569
+
570
+ self.dialect = dialect
571
+ self.pre_process_steps = pre_process_steps
572
+ self.post_process_steps = post_process_steps
573
+ self.execution_mode = execution_mode
574
+ self.execution_args = execution_args
575
+ self.output_transformer = output_transformer
576
+
577
+ def replace(self, **kwargs: Any) -> "StatementConfig":
578
+ """Immutable update pattern.
579
+
580
+ Args:
581
+ **kwargs: Attributes to update
582
+
583
+ Returns:
584
+ New StatementConfig instance with updated attributes
585
+ """
586
+ for key in kwargs:
587
+ if key not in SQL_CONFIG_SLOTS:
588
+ msg = f"{key!r} is not a field in {type(self).__name__}"
589
+ raise TypeError(msg)
590
+
591
+ current_kwargs = {slot: getattr(self, slot) for slot in SQL_CONFIG_SLOTS}
592
+ current_kwargs.update(kwargs)
593
+ return type(self)(**current_kwargs)
594
+
595
+ def __hash__(self) -> int:
596
+ """Hash based on key configuration settings."""
597
+ return hash(
598
+ (
599
+ self.enable_parsing,
600
+ self.enable_validation,
601
+ self.enable_transformations,
602
+ self.enable_analysis,
603
+ self.enable_expression_simplification,
604
+ self.enable_parameter_type_wrapping,
605
+ self.enable_caching,
606
+ str(self.dialect),
607
+ )
608
+ )
609
+
610
+ def __repr__(self) -> str:
611
+ """String representation of the StatementConfig instance."""
612
+ field_strs = []
613
+ for slot in SQL_CONFIG_SLOTS:
614
+ value = getattr(self, slot)
615
+ field_strs.append(f"{slot}={value!r}")
616
+ return f"{self.__class__.__name__}({', '.join(field_strs)})"
617
+
618
+ def __eq__(self, other: object) -> bool:
619
+ """Equality comparison."""
620
+ if not isinstance(other, type(self)):
621
+ return False
622
+
623
+ for slot in SQL_CONFIG_SLOTS:
624
+ self_val = getattr(self, slot)
625
+ other_val = getattr(other, slot)
626
+
627
+ if hasattr(self_val, "__class__") and hasattr(other_val, "__class__"):
628
+ if self_val.__class__ != other_val.__class__:
629
+ return False
630
+ if slot == "parameter_config":
631
+ if not self._compare_parameter_configs(self_val, other_val):
632
+ return False
633
+ elif slot in {"parameter_converter", "parameter_validator"}:
634
+ continue
635
+ elif self_val != other_val:
636
+ return False
637
+ elif self_val != other_val:
638
+ return False
639
+ return True
640
+
641
+ def _compare_parameter_configs(self, config1: Any, config2: Any) -> bool:
642
+ """Compare parameter configs by key attributes."""
643
+ try:
644
+ return (
645
+ config1.default_parameter_style == config2.default_parameter_style
646
+ and config1.supported_parameter_styles == config2.supported_parameter_styles
647
+ and getattr(config1, "supported_execution_parameter_styles", None)
648
+ == getattr(config2, "supported_execution_parameter_styles", None)
649
+ )
650
+ except AttributeError:
651
+ return False
652
+
653
+
654
+ def get_default_config() -> StatementConfig:
655
+ """Get default statement configuration."""
656
+ return StatementConfig()
657
+
658
+
659
+ def get_default_parameter_config() -> ParameterStyleConfig:
660
+ """Get default parameter configuration."""
661
+ return ParameterStyleConfig(
662
+ default_parameter_style=ParameterStyle.QMARK, supported_parameter_styles={ParameterStyle.QMARK}
663
+ )
664
+
665
+
666
+ 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]