sqlspec 0.16.2__cp39-cp39-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-39-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 +1782 -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 +421 -0
  57. sqlspec/builder/_merge.py +71 -0
  58. sqlspec/builder/_parsing_utils.py +164 -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 +149 -0
  66. sqlspec/builder/mixins/_merge_operations.py +562 -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 +604 -0
  70. sqlspec/builder/mixins/_update_operations.py +202 -0
  71. sqlspec/builder/mixins/_where_clause.py +644 -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-aarch64-linux-gnu.so +0 -0
  76. sqlspec/core/cache.py +871 -0
  77. sqlspec/core/compiler.cpython-39-aarch64-linux-gnu.so +0 -0
  78. sqlspec/core/compiler.py +417 -0
  79. sqlspec/core/filters.cpython-39-aarch64-linux-gnu.so +0 -0
  80. sqlspec/core/filters.py +830 -0
  81. sqlspec/core/hashing.cpython-39-aarch64-linux-gnu.so +0 -0
  82. sqlspec/core/hashing.py +310 -0
  83. sqlspec/core/parameters.cpython-39-aarch64-linux-gnu.so +0 -0
  84. sqlspec/core/parameters.py +1237 -0
  85. sqlspec/core/result.cpython-39-aarch64-linux-gnu.so +0 -0
  86. sqlspec/core/result.py +677 -0
  87. sqlspec/core/splitter.cpython-39-aarch64-linux-gnu.so +0 -0
  88. sqlspec/core/splitter.py +819 -0
  89. sqlspec/core/statement.cpython-39-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-39-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-39-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-39-aarch64-linux-gnu.so +0 -0
  138. sqlspec/utils/sync_tools.py +237 -0
  139. sqlspec/utils/text.cpython-39-aarch64-linux-gnu.so +0 -0
  140. sqlspec/utils/text.py +96 -0
  141. sqlspec/utils/type_guards.cpython-39-aarch64-linux-gnu.so +0 -0
  142. sqlspec/utils/type_guards.py +1139 -0
  143. sqlspec-0.16.2.dist-info/METADATA +365 -0
  144. sqlspec-0.16.2.dist-info/RECORD +148 -0
  145. sqlspec-0.16.2.dist-info/WHEEL +7 -0
  146. sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
  147. sqlspec-0.16.2.dist-info/licenses/LICENSE +21 -0
  148. sqlspec-0.16.2.dist-info/licenses/NOTICE +29 -0
@@ -0,0 +1,631 @@
1
+ """Common driver attributes and utilities.
2
+
3
+ This module provides core driver infrastructure including execution result handling,
4
+ common driver attributes, parameter processing, and SQL compilation utilities.
5
+ """
6
+
7
+ from typing import TYPE_CHECKING, Any, Final, NamedTuple, Optional, Union, cast
8
+
9
+ from mypy_extensions import trait
10
+ from sqlglot import exp
11
+
12
+ from sqlspec.builder import QueryBuilder
13
+ from sqlspec.core import SQL, OperationType, ParameterStyle, SQLResult, Statement, StatementConfig, TypedParameter
14
+ from sqlspec.core.cache import get_cache_config, sql_cache
15
+ from sqlspec.core.splitter import split_sql_script
16
+ from sqlspec.exceptions import ImproperConfigurationError
17
+ from sqlspec.utils.logging import get_logger
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Sequence
21
+
22
+ from sqlspec.core.filters import FilterTypeT, StatementFilter
23
+ from sqlspec.typing import StatementParameters
24
+
25
+
26
+ __all__ = (
27
+ "DEFAULT_EXECUTION_RESULT",
28
+ "EXEC_CURSOR_RESULT",
29
+ "EXEC_ROWCOUNT_OVERRIDE",
30
+ "EXEC_SPECIAL_DATA",
31
+ "CommonDriverAttributesMixin",
32
+ "ExecutionResult",
33
+ "ScriptExecutionResult",
34
+ )
35
+
36
+
37
+ logger = get_logger("driver")
38
+
39
+
40
+ class ScriptExecutionResult(NamedTuple):
41
+ """Result from script execution with statement count information.
42
+
43
+ This named tuple eliminates the need for redundant script splitting
44
+ by providing statement count information during execution rather than
45
+ requiring re-parsing after execution.
46
+
47
+ Attributes:
48
+ cursor_result: The result returned by the database cursor/driver
49
+ rowcount_override: Optional override for the number of affected rows
50
+ special_data: Any special metadata or additional information
51
+ statement_count: Total number of statements in the script
52
+ successful_statements: Number of statements that executed successfully
53
+ """
54
+
55
+ cursor_result: Any
56
+ rowcount_override: Optional[int]
57
+ special_data: Any
58
+ statement_count: int
59
+ successful_statements: int
60
+
61
+
62
+ class ExecutionResult(NamedTuple):
63
+ """Comprehensive execution result containing all data needed for SQLResult building.
64
+
65
+ This named tuple consolidates all execution result data to eliminate the need
66
+ for additional data extraction calls and script re-parsing in build_statement_result.
67
+
68
+ Attributes:
69
+ cursor_result: The raw result returned by the database cursor/driver
70
+ rowcount_override: Optional override for the number of affected rows
71
+ special_data: Any special metadata or additional information from execution
72
+ selected_data: For SELECT operations, the extracted row data
73
+ column_names: For SELECT operations, the column names
74
+ data_row_count: For SELECT operations, the number of rows returned
75
+ statement_count: For script operations, total number of statements
76
+ successful_statements: For script operations, number of successful statements
77
+ is_script_result: Whether this result is from script execution
78
+ is_select_result: Whether this result is from a SELECT operation
79
+ is_many_result: Whether this result is from an execute_many operation
80
+ """
81
+
82
+ cursor_result: Any
83
+ rowcount_override: Optional[int]
84
+ special_data: Any
85
+ selected_data: Optional["list[dict[str, Any]]"]
86
+ column_names: Optional["list[str]"]
87
+ data_row_count: Optional[int]
88
+ statement_count: Optional[int]
89
+ successful_statements: Optional[int]
90
+ is_script_result: bool
91
+ is_select_result: bool
92
+ is_many_result: bool
93
+ last_inserted_id: Optional[Union[int, str]] = None
94
+
95
+
96
+ EXEC_CURSOR_RESULT = 0
97
+ EXEC_ROWCOUNT_OVERRIDE = 1
98
+ EXEC_SPECIAL_DATA = 2
99
+ DEFAULT_EXECUTION_RESULT: Final[tuple[Any, Optional[int], Any]] = (None, None, None)
100
+
101
+
102
+ @trait
103
+ class CommonDriverAttributesMixin:
104
+ """Common attributes and methods for driver adapters.
105
+
106
+ This mixin provides the foundation for all SQLSpec drivers, including
107
+ connection and configuration management, parameter processing, caching,
108
+ and SQL compilation.
109
+ """
110
+
111
+ __slots__ = ("connection", "driver_features", "statement_config")
112
+ connection: "Any"
113
+ statement_config: "StatementConfig"
114
+ driver_features: "dict[str, Any]"
115
+
116
+ def __init__(
117
+ self, connection: "Any", statement_config: "StatementConfig", driver_features: "Optional[dict[str, Any]]" = None
118
+ ) -> None:
119
+ """Initialize driver adapter with connection and configuration.
120
+
121
+ Args:
122
+ connection: Database connection instance
123
+ statement_config: Statement configuration for the driver
124
+ driver_features: Driver-specific features like extensions, secrets, and connection callbacks
125
+ """
126
+ self.connection = connection
127
+ self.statement_config = statement_config
128
+ self.driver_features = driver_features or {}
129
+
130
+ def create_execution_result(
131
+ self,
132
+ cursor_result: Any,
133
+ *,
134
+ rowcount_override: Optional[int] = None,
135
+ special_data: Any = None,
136
+ selected_data: Optional["list[dict[str, Any]]"] = None,
137
+ column_names: Optional["list[str]"] = None,
138
+ data_row_count: Optional[int] = None,
139
+ statement_count: Optional[int] = None,
140
+ successful_statements: Optional[int] = None,
141
+ is_script_result: bool = False,
142
+ is_select_result: bool = False,
143
+ is_many_result: bool = False,
144
+ last_inserted_id: Optional[Union[int, str]] = None,
145
+ ) -> ExecutionResult:
146
+ """Create ExecutionResult with all necessary data for any operation type.
147
+
148
+ Args:
149
+ cursor_result: The raw result returned by the database cursor/driver
150
+ rowcount_override: Optional override for the number of affected rows
151
+ special_data: Any special metadata or additional information
152
+ selected_data: For SELECT operations, the extracted row data
153
+ column_names: For SELECT operations, the column names
154
+ data_row_count: For SELECT operations, the number of rows returned
155
+ statement_count: For script operations, total number of statements
156
+ successful_statements: For script operations, number of successful statements
157
+ is_script_result: Whether this result is from script execution
158
+ is_select_result: Whether this result is from a SELECT operation
159
+ is_many_result: Whether this result is from an execute_many operation
160
+ last_inserted_id: The ID of the last inserted row (if applicable)
161
+
162
+ Returns:
163
+ ExecutionResult configured for the specified operation type
164
+ """
165
+ return ExecutionResult(
166
+ cursor_result=cursor_result,
167
+ rowcount_override=rowcount_override,
168
+ special_data=special_data,
169
+ selected_data=selected_data,
170
+ column_names=column_names,
171
+ data_row_count=data_row_count,
172
+ statement_count=statement_count,
173
+ successful_statements=successful_statements,
174
+ is_script_result=is_script_result,
175
+ is_select_result=is_select_result,
176
+ is_many_result=is_many_result,
177
+ last_inserted_id=last_inserted_id,
178
+ )
179
+
180
+ def build_statement_result(self, statement: "SQL", execution_result: ExecutionResult) -> "SQLResult":
181
+ """Build and return the SQLResult from ExecutionResult data.
182
+
183
+ Creates SQLResult objects from ExecutionResult data without requiring
184
+ additional data extraction calls or script re-parsing.
185
+
186
+ Args:
187
+ statement: SQL statement that was executed
188
+ execution_result: ExecutionResult containing all necessary data
189
+
190
+ Returns:
191
+ SQLResult with complete execution data
192
+ """
193
+ if execution_result.is_script_result:
194
+ return SQLResult(
195
+ statement=statement,
196
+ data=[],
197
+ rows_affected=execution_result.rowcount_override or 0,
198
+ operation_type="SCRIPT",
199
+ total_statements=execution_result.statement_count or 0,
200
+ successful_statements=execution_result.successful_statements or 0,
201
+ metadata=execution_result.special_data or {"status_message": "OK"},
202
+ )
203
+
204
+ if execution_result.is_select_result:
205
+ return SQLResult(
206
+ statement=statement,
207
+ data=execution_result.selected_data or [],
208
+ column_names=execution_result.column_names or [],
209
+ rows_affected=execution_result.data_row_count or 0,
210
+ operation_type="SELECT",
211
+ metadata=execution_result.special_data or {},
212
+ )
213
+
214
+ return SQLResult(
215
+ statement=statement,
216
+ data=[],
217
+ rows_affected=execution_result.rowcount_override or 0,
218
+ operation_type=self._determine_operation_type(statement),
219
+ last_inserted_id=execution_result.last_inserted_id,
220
+ metadata=execution_result.special_data or {"status_message": "OK"},
221
+ )
222
+
223
+ def _determine_operation_type(self, statement: "Any") -> OperationType:
224
+ """Determine operation type from SQL statement expression.
225
+
226
+ Examines the statement's expression type to determine if it's
227
+ INSERT, UPDATE, DELETE, SELECT, SCRIPT, or generic EXECUTE.
228
+
229
+ Args:
230
+ statement: SQL statement object with expression attribute
231
+
232
+ Returns:
233
+ OperationType literal value
234
+ """
235
+ if statement.is_script:
236
+ return "SCRIPT"
237
+
238
+ try:
239
+ expression = statement.expression
240
+ except AttributeError:
241
+ return "EXECUTE"
242
+
243
+ if not expression:
244
+ return "EXECUTE"
245
+
246
+ expr_type = type(expression).__name__.upper()
247
+
248
+ if "ANONYMOUS" in expr_type and statement.is_script:
249
+ return "SCRIPT"
250
+
251
+ if "INSERT" in expr_type:
252
+ return "INSERT"
253
+ if "UPDATE" in expr_type:
254
+ return "UPDATE"
255
+ if "DELETE" in expr_type:
256
+ return "DELETE"
257
+ if "SELECT" in expr_type:
258
+ return "SELECT"
259
+ if "COPY" in expr_type:
260
+ return "COPY"
261
+ return "EXECUTE"
262
+
263
+ def prepare_statement(
264
+ self,
265
+ statement: "Union[Statement, QueryBuilder]",
266
+ parameters: "tuple[Union[StatementParameters, StatementFilter], ...]" = (),
267
+ *,
268
+ statement_config: "StatementConfig",
269
+ kwargs: "Optional[dict[str, Any]]" = None,
270
+ ) -> "SQL":
271
+ """Build SQL statement from various input types.
272
+
273
+ Ensures dialect is set and preserves existing state when rebuilding SQL objects.
274
+ """
275
+ kwargs = kwargs or {}
276
+
277
+ if isinstance(statement, QueryBuilder):
278
+ return statement.to_statement(statement_config)
279
+ if isinstance(statement, SQL):
280
+ if parameters or kwargs:
281
+ merged_parameters = (
282
+ (*statement._positional_parameters, *parameters) if parameters else statement._positional_parameters
283
+ )
284
+ return SQL(statement.sql, *merged_parameters, statement_config=statement_config, **kwargs)
285
+ needs_rebuild = False
286
+
287
+ if statement_config.dialect and (
288
+ not statement.statement_config.dialect or statement.statement_config.dialect != statement_config.dialect
289
+ ):
290
+ needs_rebuild = True
291
+
292
+ if (
293
+ statement.statement_config.parameter_config.default_execution_parameter_style
294
+ != statement_config.parameter_config.default_execution_parameter_style
295
+ ):
296
+ needs_rebuild = True
297
+
298
+ if needs_rebuild:
299
+ sql_text = statement._raw_sql or statement.sql
300
+
301
+ if statement.is_many and statement.parameters:
302
+ new_sql = SQL(sql_text, statement.parameters, statement_config=statement_config, is_many=True)
303
+ elif statement._named_parameters:
304
+ new_sql = SQL(sql_text, statement_config=statement_config, **statement._named_parameters)
305
+ else:
306
+ new_sql = SQL(sql_text, *statement._positional_parameters, statement_config=statement_config)
307
+
308
+ return new_sql
309
+ return statement
310
+ return SQL(statement, *parameters, statement_config=statement_config, **kwargs)
311
+
312
+ def split_script_statements(
313
+ self, script: str, statement_config: "StatementConfig", strip_trailing_semicolon: bool = False
314
+ ) -> list[str]:
315
+ """Split a SQL script into individual statements.
316
+
317
+ Uses a lexer-driven state machine to handle multi-statement scripts,
318
+ including complex constructs like PL/SQL blocks, T-SQL batches, and nested blocks.
319
+
320
+ Args:
321
+ script: The SQL script to split
322
+ statement_config: Statement configuration containing dialect information
323
+ strip_trailing_semicolon: If True, remove trailing semicolons from statements
324
+
325
+ Returns:
326
+ A list of individual SQL statements
327
+ """
328
+ return [
329
+ sql_script.strip()
330
+ for sql_script in split_sql_script(
331
+ script, dialect=str(statement_config.dialect), strip_trailing_terminator=strip_trailing_semicolon
332
+ )
333
+ if sql_script.strip()
334
+ ]
335
+
336
+ def prepare_driver_parameters(
337
+ self, parameters: Any, statement_config: "StatementConfig", is_many: bool = False
338
+ ) -> Any:
339
+ """Prepare parameters for database driver consumption.
340
+
341
+ Normalizes parameter structure and unwraps TypedParameter objects
342
+ to their underlying values, which database drivers expect.
343
+
344
+ Args:
345
+ parameters: Parameters in any format (dict, list, tuple, scalar, TypedParameter)
346
+ statement_config: Statement configuration for parameter style detection
347
+ is_many: If True, handle as executemany parameter sequence
348
+
349
+ Returns:
350
+ Parameters with TypedParameter objects unwrapped to primitive values
351
+ """
352
+ if parameters is None and statement_config.parameter_config.needs_static_script_compilation:
353
+ return None
354
+
355
+ if not parameters:
356
+ return []
357
+
358
+ if is_many:
359
+ if isinstance(parameters, list):
360
+ return [self._format_parameter_set_for_many(param_set, statement_config) for param_set in parameters]
361
+ return [self._format_parameter_set_for_many(parameters, statement_config)]
362
+ return self._format_parameter_set(parameters, statement_config)
363
+
364
+ def _format_parameter_set_for_many(self, parameters: Any, statement_config: "StatementConfig") -> Any:
365
+ """Prepare a single parameter set for execute_many operations.
366
+
367
+ Unlike _format_parameter_set, this method handles parameter sets without
368
+ converting the structure itself to array format.
369
+
370
+ Args:
371
+ parameters: Single parameter set (tuple, list, or dict)
372
+ statement_config: Statement configuration for parameter style detection
373
+
374
+ Returns:
375
+ Processed parameter set with individual values coerced but structure preserved
376
+ """
377
+ if not parameters:
378
+ return []
379
+
380
+ def apply_type_coercion(value: Any) -> Any:
381
+ """Apply type coercion to a single value."""
382
+ unwrapped_value = value.value if isinstance(value, TypedParameter) else value
383
+
384
+ if statement_config.parameter_config.type_coercion_map:
385
+ for type_check, converter in statement_config.parameter_config.type_coercion_map.items():
386
+ if type_check in {list, tuple} and isinstance(unwrapped_value, (list, tuple)):
387
+ continue
388
+ if isinstance(unwrapped_value, type_check):
389
+ return converter(unwrapped_value)
390
+
391
+ return unwrapped_value
392
+
393
+ if isinstance(parameters, dict):
394
+ return {k: apply_type_coercion(v) for k, v in parameters.items()}
395
+
396
+ if isinstance(parameters, (list, tuple)):
397
+ coerced_params = [apply_type_coercion(p) for p in parameters]
398
+ return tuple(coerced_params) if isinstance(parameters, tuple) else coerced_params
399
+
400
+ return apply_type_coercion(parameters)
401
+
402
+ def _format_parameter_set(self, parameters: Any, statement_config: "StatementConfig") -> Any:
403
+ """Prepare a single parameter set for database driver consumption.
404
+
405
+ Args:
406
+ parameters: Single parameter set in any format
407
+ statement_config: Statement configuration for parameter style detection
408
+
409
+ Returns:
410
+ Processed parameter set with TypedParameter objects unwrapped and type coercion applied
411
+ """
412
+ if not parameters:
413
+ return []
414
+
415
+ def apply_type_coercion(value: Any) -> Any:
416
+ """Apply type coercion to a single value."""
417
+ unwrapped_value = value.value if isinstance(value, TypedParameter) else value
418
+
419
+ if statement_config.parameter_config.type_coercion_map:
420
+ for type_check, converter in statement_config.parameter_config.type_coercion_map.items():
421
+ if isinstance(unwrapped_value, type_check):
422
+ return converter(unwrapped_value)
423
+
424
+ return unwrapped_value
425
+
426
+ if isinstance(parameters, dict):
427
+ if not parameters:
428
+ return []
429
+ if statement_config.parameter_config.supported_execution_parameter_styles and (
430
+ ParameterStyle.NAMED_PYFORMAT in statement_config.parameter_config.supported_execution_parameter_styles
431
+ or ParameterStyle.NAMED_COLON in statement_config.parameter_config.supported_execution_parameter_styles
432
+ ):
433
+ return {k: apply_type_coercion(v) for k, v in parameters.items()}
434
+ if statement_config.parameter_config.default_parameter_style in {
435
+ ParameterStyle.NUMERIC,
436
+ ParameterStyle.QMARK,
437
+ ParameterStyle.POSITIONAL_PYFORMAT,
438
+ }:
439
+ ordered_parameters = []
440
+ sorted_items = sorted(
441
+ parameters.items(),
442
+ key=lambda item: int(item[0])
443
+ if item[0].isdigit()
444
+ else (int(item[0][6:]) if item[0].startswith("param_") and item[0][6:].isdigit() else float("inf")),
445
+ )
446
+ for _, value in sorted_items:
447
+ ordered_parameters.append(apply_type_coercion(value))
448
+ return ordered_parameters
449
+
450
+ return {k: apply_type_coercion(v) for k, v in parameters.items()}
451
+
452
+ if isinstance(parameters, (list, tuple)):
453
+ coerced_params = [apply_type_coercion(p) for p in parameters]
454
+ if statement_config.parameter_config.preserve_parameter_format and isinstance(parameters, tuple):
455
+ return tuple(coerced_params)
456
+ return coerced_params
457
+
458
+ return [apply_type_coercion(parameters)]
459
+
460
+ def _get_compiled_sql(
461
+ self, statement: "SQL", statement_config: "StatementConfig", flatten_single_parameters: bool = False
462
+ ) -> tuple[str, Any]:
463
+ """Get compiled SQL with parameter style conversion and caching.
464
+
465
+ Compiles the SQL statement and applies parameter style conversion.
466
+ Results are cached when caching is enabled.
467
+
468
+ Args:
469
+ statement: SQL statement to compile
470
+ statement_config: Complete statement configuration including parameter config, dialect, etc.
471
+ flatten_single_parameters: If True, flatten single-element lists for scalar parameters
472
+
473
+ Returns:
474
+ Tuple of (compiled_sql, parameters)
475
+ """
476
+ cache_config = get_cache_config()
477
+ cache_key = None
478
+ if cache_config.compiled_cache_enabled and statement_config.enable_caching:
479
+ cache_key = self._generate_compilation_cache_key(statement, statement_config, flatten_single_parameters)
480
+ cached_result = sql_cache.get(cache_key)
481
+ if cached_result is not None:
482
+ return cached_result
483
+
484
+ compiled_sql, execution_parameters = statement.compile()
485
+
486
+ prepared_parameters = self.prepare_driver_parameters(
487
+ execution_parameters, statement_config, is_many=statement.is_many
488
+ )
489
+
490
+ if statement_config.parameter_config.output_transformer:
491
+ compiled_sql, prepared_parameters = statement_config.parameter_config.output_transformer(
492
+ compiled_sql, prepared_parameters
493
+ )
494
+
495
+ if cache_key is not None:
496
+ sql_cache.set(cache_key, (compiled_sql, prepared_parameters))
497
+
498
+ return compiled_sql, prepared_parameters
499
+
500
+ def _generate_compilation_cache_key(
501
+ self, statement: "SQL", config: "StatementConfig", flatten_single_parameters: bool
502
+ ) -> str:
503
+ """Generate cache key that includes all compilation context.
504
+
505
+ Creates a deterministic cache key that includes all factors that affect SQL compilation,
506
+ preventing cache contamination between different compilation contexts.
507
+ """
508
+ context_hash = hash(
509
+ (
510
+ config.parameter_config.hash(),
511
+ config.dialect,
512
+ statement.is_script,
513
+ statement.is_many,
514
+ flatten_single_parameters,
515
+ bool(config.parameter_config.output_transformer),
516
+ bool(config.parameter_config.ast_transformer),
517
+ bool(config.parameter_config.needs_static_script_compilation),
518
+ )
519
+ )
520
+
521
+ params = statement.parameters
522
+ params_key: Any
523
+
524
+ def make_hashable(obj: Any) -> Any:
525
+ """Recursively convert unhashable types to hashable ones."""
526
+ if isinstance(obj, (list, tuple)):
527
+ return tuple(make_hashable(item) for item in obj)
528
+ if isinstance(obj, dict):
529
+ return tuple(sorted((k, make_hashable(v)) for k, v in obj.items()))
530
+ if isinstance(obj, set):
531
+ return frozenset(make_hashable(item) for item in obj)
532
+ return obj
533
+
534
+ try:
535
+ if isinstance(params, dict):
536
+ params_key = make_hashable(params)
537
+ elif isinstance(params, (list, tuple)) and params:
538
+ if isinstance(params[0], dict):
539
+ params_key = tuple(make_hashable(d) for d in params)
540
+ else:
541
+ params_key = make_hashable(params)
542
+ elif isinstance(params, (list, tuple)):
543
+ params_key = ()
544
+ else:
545
+ params_key = params
546
+ except (TypeError, AttributeError):
547
+ params_key = str(params)
548
+
549
+ base_hash = hash((statement.sql, params_key, statement.is_many, statement.is_script))
550
+ return f"compiled:{base_hash}:{context_hash}"
551
+
552
+ def _get_dominant_parameter_style(self, parameters: "list[Any]") -> "Optional[ParameterStyle]":
553
+ """Determine the dominant parameter style from parameter info list.
554
+
555
+ Args:
556
+ parameters: List of ParameterInfo objects from validator.extract_parameters()
557
+
558
+ Returns:
559
+ The dominant parameter style, or None if no parameters
560
+ """
561
+ if not parameters:
562
+ return None
563
+
564
+ style_counts: dict[ParameterStyle, int] = {}
565
+ for param in parameters:
566
+ style_counts[param.style] = style_counts.get(param.style, 0) + 1
567
+
568
+ precedence = {
569
+ ParameterStyle.QMARK: 1,
570
+ ParameterStyle.NUMERIC: 2,
571
+ ParameterStyle.POSITIONAL_COLON: 3,
572
+ ParameterStyle.POSITIONAL_PYFORMAT: 4,
573
+ ParameterStyle.NAMED_AT: 5,
574
+ ParameterStyle.NAMED_DOLLAR: 6,
575
+ ParameterStyle.NAMED_COLON: 7,
576
+ ParameterStyle.NAMED_PYFORMAT: 8,
577
+ }
578
+
579
+ return max(style_counts.keys(), key=lambda style: (style_counts[style], -precedence.get(style, 99)))
580
+
581
+ @staticmethod
582
+ def find_filter(
583
+ filter_type: "type[FilterTypeT]",
584
+ filters: "Sequence[StatementFilter | StatementParameters] | Sequence[StatementFilter]",
585
+ ) -> "FilterTypeT | None":
586
+ """Get the filter specified by filter type from the filters.
587
+
588
+ Args:
589
+ filter_type: The type of filter to find.
590
+ filters: filter types to apply to the query
591
+
592
+ Returns:
593
+ The match filter instance or None
594
+ """
595
+ return next(
596
+ (cast("FilterTypeT | None", filter_) for filter_ in filters if isinstance(filter_, filter_type)), None
597
+ )
598
+
599
+ def _create_count_query(self, original_sql: "SQL") -> "SQL":
600
+ """Create a COUNT query from the original SQL statement.
601
+
602
+ Transforms the original SELECT statement to count total rows while preserving
603
+ WHERE, HAVING, and GROUP BY clauses but removing ORDER BY, LIMIT, and OFFSET.
604
+ """
605
+ if not original_sql.expression:
606
+ msg = "Cannot create COUNT query from empty SQL expression"
607
+ raise ImproperConfigurationError(msg)
608
+ expr = original_sql.expression
609
+
610
+ if isinstance(expr, exp.Select):
611
+ if expr.args.get("group"):
612
+ subquery = expr.subquery(alias="grouped_data")
613
+ count_expr = exp.select(exp.Count(this=exp.Star())).from_(subquery)
614
+ else:
615
+ count_expr = exp.select(exp.Count(this=exp.Star())).from_(
616
+ cast("exp.Expression", expr.args.get("from")), copy=False
617
+ )
618
+ if expr.args.get("where"):
619
+ count_expr = count_expr.where(cast("exp.Expression", expr.args.get("where")), copy=False)
620
+ if expr.args.get("having"):
621
+ count_expr = count_expr.having(cast("exp.Expression", expr.args.get("having")), copy=False)
622
+
623
+ count_expr.set("order", None)
624
+ count_expr.set("limit", None)
625
+ count_expr.set("offset", None)
626
+
627
+ return SQL(count_expr, *original_sql._positional_parameters, statement_config=original_sql.statement_config)
628
+
629
+ subquery = cast("exp.Select", expr).subquery(alias="total_query")
630
+ count_expr = exp.select(exp.Count(this=exp.Star())).from_(subquery)
631
+ return SQL(count_expr, *original_sql._positional_parameters, statement_config=original_sql.statement_config)