sqlspec 0.26.0__py3-none-any.whl → 0.27.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (197) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +55 -25
  3. sqlspec/_typing.py +62 -52
  4. sqlspec/adapters/adbc/_types.py +1 -1
  5. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  6. sqlspec/adapters/adbc/adk/store.py +870 -0
  7. sqlspec/adapters/adbc/config.py +62 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +52 -2
  9. sqlspec/adapters/adbc/driver.py +144 -45
  10. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  11. sqlspec/adapters/adbc/litestar/store.py +504 -0
  12. sqlspec/adapters/adbc/type_converter.py +44 -50
  13. sqlspec/adapters/aiosqlite/_types.py +1 -1
  14. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  15. sqlspec/adapters/aiosqlite/adk/store.py +527 -0
  16. sqlspec/adapters/aiosqlite/config.py +86 -16
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +34 -2
  18. sqlspec/adapters/aiosqlite/driver.py +127 -38
  19. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  20. sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
  21. sqlspec/adapters/aiosqlite/pool.py +7 -7
  22. sqlspec/adapters/asyncmy/__init__.py +7 -1
  23. sqlspec/adapters/asyncmy/_types.py +1 -1
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +493 -0
  26. sqlspec/adapters/asyncmy/config.py +59 -17
  27. sqlspec/adapters/asyncmy/data_dictionary.py +41 -2
  28. sqlspec/adapters/asyncmy/driver.py +293 -62
  29. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  30. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  31. sqlspec/adapters/asyncpg/__init__.py +2 -1
  32. sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
  33. sqlspec/adapters/asyncpg/_types.py +11 -7
  34. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  35. sqlspec/adapters/asyncpg/adk/store.py +450 -0
  36. sqlspec/adapters/asyncpg/config.py +57 -36
  37. sqlspec/adapters/asyncpg/data_dictionary.py +41 -2
  38. sqlspec/adapters/asyncpg/driver.py +153 -23
  39. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  40. sqlspec/adapters/asyncpg/litestar/store.py +253 -0
  41. sqlspec/adapters/bigquery/_types.py +1 -1
  42. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  43. sqlspec/adapters/bigquery/adk/store.py +576 -0
  44. sqlspec/adapters/bigquery/config.py +25 -11
  45. sqlspec/adapters/bigquery/data_dictionary.py +42 -2
  46. sqlspec/adapters/bigquery/driver.py +352 -144
  47. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  48. sqlspec/adapters/bigquery/litestar/store.py +327 -0
  49. sqlspec/adapters/bigquery/type_converter.py +55 -23
  50. sqlspec/adapters/duckdb/_types.py +2 -2
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +553 -0
  53. sqlspec/adapters/duckdb/config.py +79 -21
  54. sqlspec/adapters/duckdb/data_dictionary.py +41 -2
  55. sqlspec/adapters/duckdb/driver.py +138 -43
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +5 -5
  59. sqlspec/adapters/duckdb/type_converter.py +51 -21
  60. sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
  61. sqlspec/adapters/oracledb/_types.py +20 -2
  62. sqlspec/adapters/oracledb/adk/__init__.py +5 -0
  63. sqlspec/adapters/oracledb/adk/store.py +1745 -0
  64. sqlspec/adapters/oracledb/config.py +120 -36
  65. sqlspec/adapters/oracledb/data_dictionary.py +87 -20
  66. sqlspec/adapters/oracledb/driver.py +292 -84
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +767 -0
  69. sqlspec/adapters/oracledb/migrations.py +316 -25
  70. sqlspec/adapters/oracledb/type_converter.py +91 -16
  71. sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
  72. sqlspec/adapters/psqlpy/_types.py +2 -1
  73. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  74. sqlspec/adapters/psqlpy/adk/store.py +482 -0
  75. sqlspec/adapters/psqlpy/config.py +45 -19
  76. sqlspec/adapters/psqlpy/data_dictionary.py +41 -2
  77. sqlspec/adapters/psqlpy/driver.py +101 -31
  78. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  79. sqlspec/adapters/psqlpy/litestar/store.py +272 -0
  80. sqlspec/adapters/psqlpy/type_converter.py +40 -11
  81. sqlspec/adapters/psycopg/_type_handlers.py +80 -0
  82. sqlspec/adapters/psycopg/_types.py +2 -1
  83. sqlspec/adapters/psycopg/adk/__init__.py +5 -0
  84. sqlspec/adapters/psycopg/adk/store.py +944 -0
  85. sqlspec/adapters/psycopg/config.py +65 -37
  86. sqlspec/adapters/psycopg/data_dictionary.py +77 -3
  87. sqlspec/adapters/psycopg/driver.py +200 -78
  88. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  89. sqlspec/adapters/psycopg/litestar/store.py +554 -0
  90. sqlspec/adapters/sqlite/__init__.py +2 -1
  91. sqlspec/adapters/sqlite/_type_handlers.py +86 -0
  92. sqlspec/adapters/sqlite/_types.py +1 -1
  93. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  94. sqlspec/adapters/sqlite/adk/store.py +572 -0
  95. sqlspec/adapters/sqlite/config.py +85 -16
  96. sqlspec/adapters/sqlite/data_dictionary.py +34 -2
  97. sqlspec/adapters/sqlite/driver.py +120 -52
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +5 -5
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +91 -58
  104. sqlspec/builder/_column.py +5 -5
  105. sqlspec/builder/_ddl.py +98 -89
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +41 -44
  109. sqlspec/builder/_insert.py +5 -82
  110. sqlspec/builder/{mixins/_join_operations.py → _join.py} +145 -143
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +9 -11
  113. sqlspec/builder/_select.py +1313 -25
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +76 -69
  116. sqlspec/config.py +231 -60
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +18 -18
  119. sqlspec/core/compiler.py +6 -8
  120. sqlspec/core/filters.py +37 -37
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +76 -45
  123. sqlspec/core/result.py +102 -46
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +32 -31
  126. sqlspec/core/type_conversion.py +3 -2
  127. sqlspec/driver/__init__.py +1 -3
  128. sqlspec/driver/_async.py +95 -161
  129. sqlspec/driver/_common.py +133 -80
  130. sqlspec/driver/_sync.py +95 -162
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +70 -7
  134. sqlspec/extensions/adk/__init__.py +53 -0
  135. sqlspec/extensions/adk/_types.py +51 -0
  136. sqlspec/extensions/adk/converters.py +172 -0
  137. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
  138. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  139. sqlspec/extensions/adk/service.py +181 -0
  140. sqlspec/extensions/adk/store.py +536 -0
  141. sqlspec/extensions/aiosql/adapter.py +73 -53
  142. sqlspec/extensions/litestar/__init__.py +21 -4
  143. sqlspec/extensions/litestar/cli.py +54 -10
  144. sqlspec/extensions/litestar/config.py +59 -266
  145. sqlspec/extensions/litestar/handlers.py +46 -17
  146. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  147. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  148. sqlspec/extensions/litestar/plugin.py +324 -223
  149. sqlspec/extensions/litestar/providers.py +25 -25
  150. sqlspec/extensions/litestar/store.py +265 -0
  151. sqlspec/loader.py +30 -49
  152. sqlspec/migrations/base.py +200 -76
  153. sqlspec/migrations/commands.py +591 -62
  154. sqlspec/migrations/context.py +6 -9
  155. sqlspec/migrations/fix.py +199 -0
  156. sqlspec/migrations/loaders.py +47 -19
  157. sqlspec/migrations/runner.py +241 -75
  158. sqlspec/migrations/tracker.py +237 -21
  159. sqlspec/migrations/utils.py +51 -3
  160. sqlspec/migrations/validation.py +177 -0
  161. sqlspec/protocols.py +66 -36
  162. sqlspec/storage/_utils.py +98 -0
  163. sqlspec/storage/backends/fsspec.py +134 -106
  164. sqlspec/storage/backends/local.py +78 -51
  165. sqlspec/storage/backends/obstore.py +278 -162
  166. sqlspec/storage/registry.py +75 -39
  167. sqlspec/typing.py +14 -84
  168. sqlspec/utils/config_resolver.py +6 -6
  169. sqlspec/utils/correlation.py +4 -5
  170. sqlspec/utils/data_transformation.py +3 -2
  171. sqlspec/utils/deprecation.py +9 -8
  172. sqlspec/utils/fixtures.py +4 -4
  173. sqlspec/utils/logging.py +46 -6
  174. sqlspec/utils/module_loader.py +2 -2
  175. sqlspec/utils/schema.py +288 -0
  176. sqlspec/utils/serializers.py +3 -3
  177. sqlspec/utils/sync_tools.py +21 -17
  178. sqlspec/utils/text.py +1 -2
  179. sqlspec/utils/type_guards.py +111 -20
  180. sqlspec/utils/version.py +433 -0
  181. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
  182. sqlspec-0.27.0.dist-info/RECORD +207 -0
  183. sqlspec/builder/mixins/__init__.py +0 -55
  184. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -253
  185. sqlspec/builder/mixins/_delete_operations.py +0 -50
  186. sqlspec/builder/mixins/_insert_operations.py +0 -282
  187. sqlspec/builder/mixins/_merge_operations.py +0 -698
  188. sqlspec/builder/mixins/_order_limit_operations.py +0 -145
  189. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  190. sqlspec/builder/mixins/_select_operations.py +0 -930
  191. sqlspec/builder/mixins/_update_operations.py +0 -199
  192. sqlspec/builder/mixins/_where_clause.py +0 -1298
  193. sqlspec-0.26.0.dist-info/RECORD +0 -157
  194. sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
  195. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
  196. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
  197. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
@@ -20,12 +20,13 @@ Processing:
20
20
  """
21
21
 
22
22
  import re
23
- from collections.abc import Mapping, Sequence
23
+ from collections import OrderedDict
24
+ from collections.abc import Callable, Mapping, Sequence
24
25
  from datetime import date, datetime
25
26
  from decimal import Decimal
26
27
  from enum import Enum
27
28
  from functools import singledispatch
28
- from typing import Any, Callable, Optional
29
+ from typing import Any
29
30
 
30
31
  from mypy_extensions import mypyc_attr
31
32
 
@@ -107,7 +108,7 @@ class TypedParameter:
107
108
 
108
109
  __slots__ = ("_hash", "original_type", "semantic_name", "value")
109
110
 
110
- def __init__(self, value: Any, original_type: Optional[type] = None, semantic_name: Optional[str] = None) -> None:
111
+ def __init__(self, value: Any, original_type: type | None = None, semantic_name: str | None = None) -> None:
111
112
  """Initialize typed parameter wrapper.
112
113
 
113
114
  Args:
@@ -118,7 +119,7 @@ class TypedParameter:
118
119
  self.value = value
119
120
  self.original_type = original_type or type(value)
120
121
  self.semantic_name = semantic_name
121
- self._hash: Optional[int] = None
122
+ self._hash: int | None = None
122
123
 
123
124
  def __hash__(self) -> int:
124
125
  """Cached hash value."""
@@ -144,7 +145,7 @@ class TypedParameter:
144
145
 
145
146
 
146
147
  @singledispatch
147
- def _wrap_parameter_by_type(value: Any, semantic_name: Optional[str] = None) -> Any:
148
+ def _wrap_parameter_by_type(value: Any, semantic_name: str | None = None) -> Any:
148
149
  """Type-specific parameter wrapping using singledispatch.
149
150
 
150
151
  Args:
@@ -158,31 +159,31 @@ def _wrap_parameter_by_type(value: Any, semantic_name: Optional[str] = None) ->
158
159
 
159
160
 
160
161
  @_wrap_parameter_by_type.register
161
- def _(value: bool, semantic_name: Optional[str] = None) -> TypedParameter:
162
+ def _(value: bool, semantic_name: str | None = None) -> TypedParameter:
162
163
  """Wrap boolean values to prevent SQLGlot parsing issues."""
163
164
  return TypedParameter(value, bool, semantic_name)
164
165
 
165
166
 
166
167
  @_wrap_parameter_by_type.register
167
- def _(value: Decimal, semantic_name: Optional[str] = None) -> TypedParameter:
168
+ def _(value: Decimal, semantic_name: str | None = None) -> TypedParameter:
168
169
  """Wrap Decimal values to preserve precision."""
169
170
  return TypedParameter(value, Decimal, semantic_name)
170
171
 
171
172
 
172
173
  @_wrap_parameter_by_type.register
173
- def _(value: datetime, semantic_name: Optional[str] = None) -> TypedParameter:
174
+ def _(value: datetime, semantic_name: str | None = None) -> TypedParameter:
174
175
  """Wrap datetime values for database-specific formatting."""
175
176
  return TypedParameter(value, datetime, semantic_name)
176
177
 
177
178
 
178
179
  @_wrap_parameter_by_type.register
179
- def _(value: date, semantic_name: Optional[str] = None) -> TypedParameter:
180
+ def _(value: date, semantic_name: str | None = None) -> TypedParameter:
180
181
  """Wrap date values for database-specific formatting."""
181
182
  return TypedParameter(value, date, semantic_name)
182
183
 
183
184
 
184
185
  @_wrap_parameter_by_type.register
185
- def _(value: bytes, semantic_name: Optional[str] = None) -> TypedParameter:
186
+ def _(value: bytes, semantic_name: str | None = None) -> TypedParameter:
186
187
  """Wrap bytes values to prevent string conversion issues in ADBC/Arrow."""
187
188
  return TypedParameter(value, bytes, semantic_name)
188
189
 
@@ -204,7 +205,7 @@ class ParameterInfo:
204
205
  __slots__ = ("name", "ordinal", "placeholder_text", "position", "style")
205
206
 
206
207
  def __init__(
207
- self, name: Optional[str], style: ParameterStyle, position: int, ordinal: int, placeholder_text: str
208
+ self, name: str | None, style: ParameterStyle, position: int, ordinal: int, placeholder_text: str
208
209
  ) -> None:
209
210
  """Initialize parameter information.
210
211
 
@@ -234,17 +235,8 @@ class ParameterInfo:
234
235
  class ParameterStyleConfig:
235
236
  """Configuration for parameter style processing.
236
237
 
237
- Provides configuration for parameter processing operations.
238
-
239
- Attributes:
240
- default_parameter_style: Primary parsing style
241
- supported_parameter_styles: All input styles supported
242
- supported_execution_parameter_styles: Styles driver can execute
243
- default_execution_parameter_style: Target execution format
244
- type_coercion_map: Type conversions
245
- output_transformer: Final SQL/parameter transformation hook
246
- preserve_parameter_format: Maintain original parameter structure
247
- needs_static_script_compilation: Embed parameters in SQL
238
+ Provides configuration for parameter processing operations including
239
+ style conversion, type coercion, and parameter format preservation.
248
240
  """
249
241
 
250
242
  __slots__ = (
@@ -265,17 +257,17 @@ class ParameterStyleConfig:
265
257
  def __init__(
266
258
  self,
267
259
  default_parameter_style: ParameterStyle,
268
- supported_parameter_styles: Optional[set[ParameterStyle]] = None,
269
- supported_execution_parameter_styles: Optional[set[ParameterStyle]] = None,
270
- default_execution_parameter_style: Optional[ParameterStyle] = None,
271
- type_coercion_map: Optional[dict[type, Callable[[Any], Any]]] = None,
260
+ supported_parameter_styles: set[ParameterStyle] | None = None,
261
+ supported_execution_parameter_styles: set[ParameterStyle] | None = None,
262
+ default_execution_parameter_style: ParameterStyle | None = None,
263
+ type_coercion_map: dict[type, Callable[[Any], Any]] | None = None,
272
264
  has_native_list_expansion: bool = False,
273
265
  needs_static_script_compilation: bool = False,
274
266
  allow_mixed_parameter_styles: bool = False,
275
267
  preserve_parameter_format: bool = True,
276
268
  preserve_original_params_for_many: bool = False,
277
- output_transformer: Optional[Callable[[str, Any], tuple[str, Any]]] = None,
278
- ast_transformer: Optional[Callable[[Any, Any], tuple[Any, Any]]] = None,
269
+ output_transformer: Callable[[str, Any], tuple[str, Any]] | None = None,
270
+ ast_transformer: Callable[[Any, Any], tuple[Any, Any]] | None = None,
279
271
  ) -> None:
280
272
  """Initialize parameter style configuration.
281
273
 
@@ -343,13 +335,18 @@ class ParameterValidator:
343
335
  compatibility with target dialects.
344
336
  """
345
337
 
346
- __slots__ = ("_parameter_cache",)
338
+ __slots__ = ("_cache_max_size", "_parameter_cache")
347
339
 
348
- def __init__(self) -> None:
349
- """Initialize validator with parameter cache."""
350
- self._parameter_cache: dict[str, list[ParameterInfo]] = {}
340
+ def __init__(self, cache_max_size: int = 5000) -> None:
341
+ """Initialize validator with bounded LRU cache.
342
+
343
+ Args:
344
+ cache_max_size: Maximum number of SQL strings to cache (default: 5000)
345
+ """
346
+ self._parameter_cache: OrderedDict[str, list[ParameterInfo]] = OrderedDict()
347
+ self._cache_max_size = cache_max_size
351
348
 
352
- def _extract_parameter_style(self, match: "re.Match[str]") -> "tuple[Optional[ParameterStyle], Optional[str]]":
349
+ def _extract_parameter_style(self, match: "re.Match[str]") -> "tuple[ParameterStyle | None, str | None]":
353
350
  """Extract parameter style and name from regex match.
354
351
 
355
352
  Args:
@@ -396,8 +393,15 @@ class ParameterValidator:
396
393
  """
397
394
  cached_result = self._parameter_cache.get(sql)
398
395
  if cached_result is not None:
396
+ self._parameter_cache.move_to_end(sql)
399
397
  return cached_result
400
398
 
399
+ if not any(c in sql for c in ("?", "%", ":", "@", "$")):
400
+ if len(self._parameter_cache) >= self._cache_max_size:
401
+ self._parameter_cache.popitem(last=False)
402
+ self._parameter_cache[sql] = []
403
+ return []
404
+
401
405
  parameters: list[ParameterInfo] = []
402
406
  ordinal = 0
403
407
 
@@ -425,10 +429,13 @@ class ParameterValidator:
425
429
  )
426
430
  ordinal += 1
427
431
 
432
+ if len(self._parameter_cache) >= self._cache_max_size:
433
+ self._parameter_cache.popitem(last=False)
434
+
428
435
  self._parameter_cache[sql] = parameters
429
436
  return parameters
430
437
 
431
- def get_sqlglot_incompatible_styles(self, dialect: Optional[str] = None) -> "set[ParameterStyle]":
438
+ def get_sqlglot_incompatible_styles(self, dialect: str | None = None) -> "set[ParameterStyle]":
432
439
  """Get parameter styles incompatible with SQLGlot for dialect.
433
440
 
434
441
  Args:
@@ -491,7 +498,7 @@ class ParameterConverter:
491
498
  ParameterStyle.POSITIONAL_PYFORMAT: lambda _: "%s",
492
499
  }
493
500
 
494
- def normalize_sql_for_parsing(self, sql: str, dialect: Optional[str] = None) -> "tuple[str, list[ParameterInfo]]":
501
+ def normalize_sql_for_parsing(self, sql: str, dialect: str | None = None) -> "tuple[str, list[ParameterInfo]]":
495
502
  """Convert SQL to parsable format.
496
503
 
497
504
  Takes raw SQL with potentially incompatible parameter styles and converts
@@ -1002,13 +1009,37 @@ class ParameterProcessor:
1002
1009
  target_style = self._determine_target_execution_style(original_styles, config)
1003
1010
  return self._converter.convert_placeholder_style(sql, parameters, target_style, is_many)
1004
1011
 
1012
+ def _generate_processor_cache_key(
1013
+ self, sql: str, parameters: Any, config: ParameterStyleConfig, is_many: bool, dialect: "str | None"
1014
+ ) -> str:
1015
+ """Generate optimized cache key for parameter processing.
1016
+
1017
+ Uses parameter fingerprint (type + structure) instead of repr()
1018
+ for better performance on large parameter sets.
1019
+
1020
+ Args:
1021
+ sql: SQL string
1022
+ parameters: Parameter values
1023
+ config: Parameter style configuration
1024
+ is_many: Whether this is execute_many
1025
+ dialect: SQL dialect
1026
+
1027
+ Returns:
1028
+ Cache key string
1029
+ """
1030
+ param_fingerprint = (
1031
+ "none"
1032
+ if parameters is None
1033
+ else f"seq_{len(parameters)}_{type(parameters).__name__}"
1034
+ if isinstance(parameters, (list, tuple))
1035
+ else f"map_{len(parameters)}"
1036
+ if isinstance(parameters, dict)
1037
+ else f"scalar_{type(parameters).__name__}"
1038
+ )
1039
+ return f"{sql}:{param_fingerprint}:{config.default_parameter_style}:{is_many}:{dialect}"
1040
+
1005
1041
  def process(
1006
- self,
1007
- sql: str,
1008
- parameters: Any,
1009
- config: ParameterStyleConfig,
1010
- dialect: Optional[str] = None,
1011
- is_many: bool = False,
1042
+ self, sql: str, parameters: Any, config: ParameterStyleConfig, dialect: str | None = None, is_many: bool = False
1012
1043
  ) -> "tuple[str, Any]":
1013
1044
  """Process parameters through the complete pipeline.
1014
1045
 
@@ -1029,7 +1060,7 @@ class ParameterProcessor:
1029
1060
  Returns:
1030
1061
  Tuple of (final_sql, execution_parameters)
1031
1062
  """
1032
- cache_key = f"{sql}:{hash(repr(parameters))}:{config.default_parameter_style}:{is_many}:{dialect}"
1063
+ cache_key = self._generate_processor_cache_key(sql, parameters, config, is_many, dialect)
1033
1064
  if cache_key in self._cache:
1034
1065
  return self._cache[cache_key]
1035
1066
 
@@ -1082,7 +1113,7 @@ class ParameterProcessor:
1082
1113
  return processed_sql, processed_parameters
1083
1114
 
1084
1115
  def get_sqlglot_compatible_sql(
1085
- self, sql: str, parameters: Any, config: ParameterStyleConfig, dialect: Optional[str] = None
1116
+ self, sql: str, parameters: Any, config: ParameterStyleConfig, dialect: str | None = None
1086
1117
  ) -> "tuple[str, Any]":
1087
1118
  """Get SQL normalized for parsing only (Phase 1 only).
1088
1119
 
@@ -1139,7 +1170,7 @@ class ParameterProcessor:
1139
1170
 
1140
1171
  return True
1141
1172
 
1142
- def _needs_sqlglot_normalization(self, param_info: "list[ParameterInfo]", dialect: Optional[str] = None) -> bool:
1173
+ def _needs_sqlglot_normalization(self, param_info: "list[ParameterInfo]", dialect: str | None = None) -> bool:
1143
1174
  """Check if SQLGlot normalization is needed for this SQL."""
1144
1175
  incompatible_styles = self._validator.get_sqlglot_incompatible_styles(dialect)
1145
1176
  return any(p.style in incompatible_styles for p in param_info)
@@ -1252,7 +1283,7 @@ def is_iterable_parameters(obj: Any) -> bool:
1252
1283
  )
1253
1284
 
1254
1285
 
1255
- def wrap_with_type(value: Any, semantic_name: Optional[str] = None) -> Any:
1286
+ def wrap_with_type(value: Any, semantic_name: str | None = None) -> Any:
1256
1287
  """Public API for type wrapping.
1257
1288
 
1258
1289
  Args:
sqlspec/core/result.py CHANGED
@@ -10,17 +10,19 @@ Classes:
10
10
  """
11
11
 
12
12
  from abc import ABC, abstractmethod
13
- from typing import TYPE_CHECKING, Any, Optional, Union, cast
13
+ from typing import TYPE_CHECKING, Any, Optional, cast, overload
14
14
 
15
15
  from mypy_extensions import mypyc_attr
16
16
  from typing_extensions import TypeVar
17
17
 
18
18
  from sqlspec.core.compiler import OperationType
19
+ from sqlspec.utils.schema import to_schema
19
20
 
20
21
  if TYPE_CHECKING:
21
22
  from collections.abc import Iterator
22
23
 
23
24
  from sqlspec.core.statement import SQL
25
+ from sqlspec.typing import SchemaT
24
26
 
25
27
 
26
28
  __all__ = ("ArrowResult", "SQLResult", "StatementResult")
@@ -52,8 +54,8 @@ class StatementResult(ABC):
52
54
  statement: "SQL",
53
55
  data: Any = None,
54
56
  rows_affected: int = 0,
55
- last_inserted_id: Optional[Union[int, str]] = None,
56
- execution_time: Optional[float] = None,
57
+ last_inserted_id: int | str | None = None,
58
+ execution_time: float | None = None,
57
59
  metadata: Optional["dict[str, Any]"] = None,
58
60
  ) -> None:
59
61
  """Initialize statement result.
@@ -129,19 +131,6 @@ class SQLResult(StatementResult):
129
131
  The operation_type attribute indicates the nature of the operation.
130
132
 
131
133
  For script execution, tracks multiple statement results and errors.
132
-
133
- Attributes:
134
- column_names: Names of columns in the result set.
135
- error: Exception that occurred during execution.
136
- errors: List of error messages for script execution.
137
- has_more: Whether there are additional result pages available.
138
- inserted_ids: List of IDs from INSERT operations.
139
- operation_index: Index of operation in a script.
140
- parameters: Parameters used for the query.
141
- statement_results: Results from individual statements in a script.
142
- successful_statements: Count of successful statements in a script.
143
- total_count: Total number of rows in the complete result set.
144
- total_statements: Total number of statements in a script.
145
134
  """
146
135
 
147
136
  __slots__ = (
@@ -164,19 +153,19 @@ class SQLResult(StatementResult):
164
153
  def __init__(
165
154
  self,
166
155
  statement: "SQL",
167
- data: Optional[list[dict[str, Any]]] = None,
156
+ data: list[dict[str, Any]] | None = None,
168
157
  rows_affected: int = 0,
169
- last_inserted_id: Optional[Union[int, str]] = None,
170
- execution_time: Optional[float] = None,
158
+ last_inserted_id: int | str | None = None,
159
+ execution_time: float | None = None,
171
160
  metadata: Optional["dict[str, Any]"] = None,
172
- error: Optional[Exception] = None,
161
+ error: Exception | None = None,
173
162
  operation_type: OperationType = "SELECT",
174
- operation_index: Optional[int] = None,
175
- parameters: Optional[Any] = None,
163
+ operation_index: int | None = None,
164
+ parameters: Any | None = None,
176
165
  column_names: Optional["list[str]"] = None,
177
- total_count: Optional[int] = None,
166
+ total_count: int | None = None,
178
167
  has_more: bool = False,
179
- inserted_ids: Optional["list[Union[int, str]]"] = None,
168
+ inserted_ids: Optional["list[int | str]"] = None,
180
169
  statement_results: Optional["list[SQLResult]"] = None,
181
170
  errors: Optional["list[str]"] = None,
182
171
  total_statements: int = 0,
@@ -280,15 +269,25 @@ class SQLResult(StatementResult):
280
269
 
281
270
  return False
282
271
 
283
- def get_data(self) -> "list[dict[str,Any]]":
272
+ @overload
273
+ def get_data(self, *, schema_type: "type[SchemaT]") -> "list[SchemaT]": ...
274
+
275
+ @overload
276
+ def get_data(self, *, schema_type: None = None) -> "list[dict[str, Any]]": ...
277
+
278
+ def get_data(self, *, schema_type: "type[SchemaT] | None" = None) -> "list[SchemaT] | list[dict[str, Any]]":
284
279
  """Get the data from the result.
285
280
 
286
281
  For regular operations, returns the list of rows.
287
282
  For script operations, returns a summary dictionary containing
288
283
  execution statistics and results.
289
284
 
285
+ Args:
286
+ schema_type: Optional schema type to transform the data into.
287
+ Supports Pydantic models, dataclasses, msgspec structs, attrs classes, and TypedDict.
288
+
290
289
  Returns:
291
- List of result rows or script summary.
290
+ List of result rows (optionally transformed to schema_type) or script summary.
292
291
  """
293
292
  op_type_upper = self.operation_type.upper()
294
293
  if op_type_upper == "SCRIPT":
@@ -303,7 +302,10 @@ class SQLResult(StatementResult):
303
302
  "total_rows_affected": self.get_total_rows_affected(),
304
303
  }
305
304
  ]
306
- return self.data or []
305
+ data = cast("list[dict[str, Any]]", self.data or [])
306
+ if schema_type:
307
+ return cast("list[SchemaT]", to_schema(data, schema_type=schema_type))
308
+ return data
307
309
 
308
310
  def add_statement_result(self, result: "SQLResult") -> None:
309
311
  """Add a statement result to the script execution results.
@@ -348,13 +350,28 @@ class SQLResult(StatementResult):
348
350
  """
349
351
  return len(self.column_names) if self.column_names else 0
350
352
 
351
- def get_first(self) -> "Optional[dict[str, Any]]":
353
+ @overload
354
+ def get_first(self, *, schema_type: "type[SchemaT]") -> "SchemaT | None": ...
355
+
356
+ @overload
357
+ def get_first(self, *, schema_type: None = None) -> "dict[str, Any] | None": ...
358
+
359
+ def get_first(self, *, schema_type: "type[SchemaT] | None" = None) -> "SchemaT | dict[str, Any] | None":
352
360
  """Get the first row from the result, if any.
353
361
 
362
+ Args:
363
+ schema_type: Optional schema type to transform the data into.
364
+ Supports Pydantic models, dataclasses, msgspec structs, attrs classes, and TypedDict.
365
+
354
366
  Returns:
355
- First row or None if no data.
367
+ First row (optionally transformed to schema_type) or None if no data.
356
368
  """
357
- return self.data[0] if self.data else None
369
+ if not self.data:
370
+ return None
371
+ row = cast("dict[str, Any]", self.data[0])
372
+ if schema_type:
373
+ return to_schema(row, schema_type=schema_type)
374
+ return row
358
375
 
359
376
  def get_count(self) -> int:
360
377
  """Get the number of rows in the current result set (e.g., a page of data).
@@ -434,19 +451,42 @@ class SQLResult(StatementResult):
434
451
  """
435
452
  return iter(self.data or [])
436
453
 
437
- def all(self) -> list[dict[str, Any]]:
454
+ @overload
455
+ def all(self, *, schema_type: "type[SchemaT]") -> "list[SchemaT]": ...
456
+
457
+ @overload
458
+ def all(self, *, schema_type: None = None) -> list[dict[str, Any]]: ...
459
+
460
+ def all(self, *, schema_type: "type[SchemaT] | None" = None) -> "list[SchemaT] | list[dict[str, Any]]":
438
461
  """Return all rows as a list.
439
462
 
463
+ Args:
464
+ schema_type: Optional schema type to transform the data into.
465
+ Supports Pydantic models, dataclasses, msgspec structs, attrs classes, and TypedDict.
466
+
440
467
  Returns:
441
- List of all rows in the result
468
+ List of all rows (optionally transformed to schema_type)
442
469
  """
443
- return self.data or []
470
+ data = cast("list[dict[str, Any]]", self.data or [])
471
+ if schema_type:
472
+ return cast("list[SchemaT]", to_schema(data, schema_type=schema_type))
473
+ return data
474
+
475
+ @overload
476
+ def one(self, *, schema_type: "type[SchemaT]") -> "SchemaT": ...
477
+
478
+ @overload
479
+ def one(self, *, schema_type: None = None) -> "dict[str, Any]": ...
444
480
 
445
- def one(self) -> "dict[str, Any]":
481
+ def one(self, *, schema_type: "type[SchemaT] | None" = None) -> "SchemaT | dict[str, Any]":
446
482
  """Return exactly one row.
447
483
 
484
+ Args:
485
+ schema_type: Optional schema type to transform the data into.
486
+ Supports Pydantic models, dataclasses, msgspec structs, attrs classes, and TypedDict.
487
+
448
488
  Returns:
449
- The single row
489
+ The single row (optionally transformed to schema_type)
450
490
 
451
491
  Raises:
452
492
  ValueError: If no results or more than one result
@@ -463,13 +503,26 @@ class SQLResult(StatementResult):
463
503
  msg = f"Multiple results found ({data_len}), exactly one row expected"
464
504
  raise ValueError(msg)
465
505
 
466
- return cast("dict[str, Any]", self.data[0])
506
+ row = cast("dict[str, Any]", self.data[0])
507
+ if schema_type:
508
+ return to_schema(row, schema_type=schema_type)
509
+ return row
510
+
511
+ @overload
512
+ def one_or_none(self, *, schema_type: "type[SchemaT]") -> "SchemaT | None": ...
467
513
 
468
- def one_or_none(self) -> "Optional[dict[str, Any]]":
514
+ @overload
515
+ def one_or_none(self, *, schema_type: None = None) -> "dict[str, Any] | None": ...
516
+
517
+ def one_or_none(self, *, schema_type: "type[SchemaT] | None" = None) -> "SchemaT | dict[str, Any] | None":
469
518
  """Return at most one row.
470
519
 
520
+ Args:
521
+ schema_type: Optional schema type to transform the data into.
522
+ Supports Pydantic models, dataclasses, msgspec structs, attrs classes, and TypedDict.
523
+
471
524
  Returns:
472
- The single row or None if no results
525
+ The single row (optionally transformed to schema_type) or None if no results
473
526
 
474
527
  Raises:
475
528
  ValueError: If more than one result
@@ -484,7 +537,10 @@ class SQLResult(StatementResult):
484
537
  msg = f"Multiple results found ({data_len}), at most one row expected"
485
538
  raise ValueError(msg)
486
539
 
487
- return cast("dict[str, Any]", self.data[0])
540
+ row = cast("dict[str, Any]", self.data[0])
541
+ if schema_type:
542
+ return to_schema(row, schema_type=schema_type)
543
+ return row
488
544
 
489
545
  def scalar(self) -> Any:
490
546
  """Return the first column of the first row.
@@ -527,8 +583,8 @@ class ArrowResult(StatementResult):
527
583
  statement: "SQL",
528
584
  data: Any,
529
585
  rows_affected: int = 0,
530
- last_inserted_id: Optional[Union[int, str]] = None,
531
- execution_time: Optional[float] = None,
586
+ last_inserted_id: int | str | None = None,
587
+ execution_time: float | None = None,
532
588
  metadata: Optional["dict[str, Any]"] = None,
533
589
  schema: Optional["dict[str, Any]"] = None,
534
590
  ) -> None:
@@ -627,10 +683,10 @@ class ArrowResult(StatementResult):
627
683
 
628
684
  def create_sql_result(
629
685
  statement: "SQL",
630
- data: Optional[list[dict[str, Any]]] = None,
686
+ data: list[dict[str, Any]] | None = None,
631
687
  rows_affected: int = 0,
632
- last_inserted_id: Optional[Union[int, str]] = None,
633
- execution_time: Optional[float] = None,
688
+ last_inserted_id: int | str | None = None,
689
+ execution_time: float | None = None,
634
690
  metadata: Optional["dict[str, Any]"] = None,
635
691
  **kwargs: Any,
636
692
  ) -> SQLResult:
@@ -663,8 +719,8 @@ def create_arrow_result(
663
719
  statement: "SQL",
664
720
  data: Any,
665
721
  rows_affected: int = 0,
666
- last_inserted_id: Optional[Union[int, str]] = None,
667
- execution_time: Optional[float] = None,
722
+ last_inserted_id: int | str | None = None,
723
+ execution_time: float | None = None,
668
724
  metadata: Optional["dict[str, Any]"] = None,
669
725
  schema: Optional["dict[str, Any]"] = None,
670
726
  ) -> ArrowResult:
sqlspec/core/splitter.py CHANGED
@@ -16,13 +16,12 @@ MySQL, SQLite, DuckDB, and BigQuery.
16
16
  import re
17
17
  import threading
18
18
  from abc import ABC, abstractmethod
19
- from collections.abc import Generator
19
+ from collections.abc import Callable, Generator
20
20
  from enum import Enum
21
21
  from re import Pattern
22
- from typing import Any, Callable, Final, Optional, Union, cast
22
+ from typing import Any, Final, TypeAlias, cast
23
23
 
24
24
  from mypy_extensions import mypyc_attr
25
- from typing_extensions import TypeAlias
26
25
 
27
26
  from sqlspec.core.cache import CacheKey, UnifiedCache
28
27
  from sqlspec.utils.logging import get_logger
@@ -98,9 +97,9 @@ class Token:
98
97
  return f"Token({self.type.value}, {self.value!r}, {self.line}:{self.column})"
99
98
 
100
99
 
101
- TokenHandler: TypeAlias = Callable[[str, int, int, int], Optional[Token]]
102
- TokenPattern: TypeAlias = Union[str, TokenHandler]
103
- CompiledTokenPattern: TypeAlias = Union[Pattern[str], TokenHandler]
100
+ TokenHandler: TypeAlias = Callable[[str, int, int, int], Token | None]
101
+ TokenPattern: TypeAlias = str | TokenHandler
102
+ CompiledTokenPattern: TypeAlias = Pattern[str] | TokenHandler
104
103
 
105
104
 
106
105
  @mypyc_attr(allow_interpreted_subclasses=False)
@@ -111,13 +110,13 @@ class DialectConfig(ABC):
111
110
 
112
111
  def __init__(self) -> None:
113
112
  """Initialize dialect configuration."""
114
- self._name: Optional[str] = None
115
- self._block_starters: Optional[set[str]] = None
116
- self._block_enders: Optional[set[str]] = None
117
- self._statement_terminators: Optional[set[str]] = None
118
- self._batch_separators: Optional[set[str]] = None
119
- self._special_terminators: Optional[dict[str, Callable[[list[Token], int], bool]]] = None
120
- self._max_nesting_depth: Optional[int] = None
113
+ self._name: str | None = None
114
+ self._block_starters: set[str] | None = None
115
+ self._block_enders: set[str] | None = None
116
+ self._statement_terminators: set[str] | None = None
117
+ self._batch_separators: set[str] | None = None
118
+ self._special_terminators: dict[str, Callable[[list[Token], int], bool]] | None = None
119
+ self._max_nesting_depth: int | None = None
121
120
 
122
121
  @property
123
122
  @abstractmethod
@@ -436,7 +435,7 @@ class PostgreSQLDialectConfig(DialectConfig):
436
435
  return [(TokenType.STRING_LITERAL, self._handle_dollar_quoted_string)]
437
436
 
438
437
  @staticmethod
439
- def _handle_dollar_quoted_string(text: str, position: int, line: int, column: int) -> Optional[Token]:
438
+ def _handle_dollar_quoted_string(text: str, position: int, line: int, column: int) -> Token | None:
440
439
  """Handle PostgreSQL dollar-quoted string literals.
441
440
 
442
441
  Parses dollar-quoted strings in the format $tag$content$tag$
@@ -613,8 +612,8 @@ class BigQueryDialectConfig(DialectConfig):
613
612
  return self._statement_terminators
614
613
 
615
614
 
616
- _pattern_cache: Optional[UnifiedCache] = None
617
- _result_cache: Optional[UnifiedCache] = None
615
+ _pattern_cache: UnifiedCache | None = None
616
+ _result_cache: UnifiedCache | None = None
618
617
  _cache_lock = threading.Lock()
619
618
 
620
619
 
@@ -878,7 +877,7 @@ class StatementSplitter:
878
877
  return False
879
878
 
880
879
 
881
- def split_sql_script(script: str, dialect: Optional[str] = None, strip_trailing_terminator: bool = False) -> list[str]:
880
+ def split_sql_script(script: str, dialect: str | None = None, strip_trailing_terminator: bool = False) -> list[str]:
882
881
  """Split SQL script into individual statements.
883
882
 
884
883
  Args: