sqlspec 0.26.0__py3-none-any.whl → 0.28.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 (212) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +55 -25
  3. sqlspec/_typing.py +155 -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 +880 -0
  7. sqlspec/adapters/adbc/config.py +62 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +74 -2
  9. sqlspec/adapters/adbc/driver.py +226 -58
  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 +536 -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 +503 -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 +460 -0
  36. sqlspec/adapters/asyncpg/config.py +57 -36
  37. sqlspec/adapters/asyncpg/data_dictionary.py +48 -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 +585 -0
  44. sqlspec/adapters/bigquery/config.py +36 -11
  45. sqlspec/adapters/bigquery/data_dictionary.py +42 -2
  46. sqlspec/adapters/bigquery/driver.py +489 -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 +563 -0
  53. sqlspec/adapters/duckdb/config.py +79 -21
  54. sqlspec/adapters/duckdb/data_dictionary.py +41 -2
  55. sqlspec/adapters/duckdb/driver.py +225 -44
  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 +1628 -0
  64. sqlspec/adapters/oracledb/config.py +120 -36
  65. sqlspec/adapters/oracledb/data_dictionary.py +87 -20
  66. sqlspec/adapters/oracledb/driver.py +475 -86
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +765 -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 +483 -0
  75. sqlspec/adapters/psqlpy/config.py +45 -19
  76. sqlspec/adapters/psqlpy/data_dictionary.py +48 -2
  77. sqlspec/adapters/psqlpy/driver.py +108 -41
  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 +962 -0
  85. sqlspec/adapters/psycopg/config.py +65 -37
  86. sqlspec/adapters/psycopg/data_dictionary.py +91 -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 +582 -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 +331 -62
  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 +55 -47
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +76 -45
  123. sqlspec/core/result.py +234 -47
  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 +183 -160
  129. sqlspec/driver/_common.py +197 -109
  130. sqlspec/driver/_sync.py +189 -161
  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 +69 -61
  142. sqlspec/extensions/fastapi/__init__.py +21 -0
  143. sqlspec/extensions/fastapi/extension.py +331 -0
  144. sqlspec/extensions/fastapi/providers.py +543 -0
  145. sqlspec/extensions/flask/__init__.py +36 -0
  146. sqlspec/extensions/flask/_state.py +71 -0
  147. sqlspec/extensions/flask/_utils.py +40 -0
  148. sqlspec/extensions/flask/extension.py +389 -0
  149. sqlspec/extensions/litestar/__init__.py +21 -4
  150. sqlspec/extensions/litestar/cli.py +54 -10
  151. sqlspec/extensions/litestar/config.py +56 -266
  152. sqlspec/extensions/litestar/handlers.py +46 -17
  153. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  154. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  155. sqlspec/extensions/litestar/plugin.py +349 -224
  156. sqlspec/extensions/litestar/providers.py +25 -25
  157. sqlspec/extensions/litestar/store.py +265 -0
  158. sqlspec/extensions/starlette/__init__.py +10 -0
  159. sqlspec/extensions/starlette/_state.py +25 -0
  160. sqlspec/extensions/starlette/_utils.py +52 -0
  161. sqlspec/extensions/starlette/extension.py +254 -0
  162. sqlspec/extensions/starlette/middleware.py +154 -0
  163. sqlspec/loader.py +30 -49
  164. sqlspec/migrations/base.py +200 -76
  165. sqlspec/migrations/commands.py +591 -62
  166. sqlspec/migrations/context.py +6 -9
  167. sqlspec/migrations/fix.py +199 -0
  168. sqlspec/migrations/loaders.py +47 -19
  169. sqlspec/migrations/runner.py +241 -75
  170. sqlspec/migrations/tracker.py +237 -21
  171. sqlspec/migrations/utils.py +51 -3
  172. sqlspec/migrations/validation.py +177 -0
  173. sqlspec/protocols.py +106 -36
  174. sqlspec/storage/_utils.py +85 -0
  175. sqlspec/storage/backends/fsspec.py +133 -107
  176. sqlspec/storage/backends/local.py +78 -51
  177. sqlspec/storage/backends/obstore.py +276 -168
  178. sqlspec/storage/registry.py +75 -39
  179. sqlspec/typing.py +30 -84
  180. sqlspec/utils/__init__.py +25 -4
  181. sqlspec/utils/arrow_helpers.py +81 -0
  182. sqlspec/utils/config_resolver.py +6 -6
  183. sqlspec/utils/correlation.py +4 -5
  184. sqlspec/utils/data_transformation.py +3 -2
  185. sqlspec/utils/deprecation.py +9 -8
  186. sqlspec/utils/fixtures.py +4 -4
  187. sqlspec/utils/logging.py +46 -6
  188. sqlspec/utils/module_loader.py +205 -5
  189. sqlspec/utils/portal.py +311 -0
  190. sqlspec/utils/schema.py +288 -0
  191. sqlspec/utils/serializers.py +113 -4
  192. sqlspec/utils/sync_tools.py +36 -22
  193. sqlspec/utils/text.py +1 -2
  194. sqlspec/utils/type_guards.py +136 -20
  195. sqlspec/utils/version.py +433 -0
  196. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/METADATA +41 -22
  197. sqlspec-0.28.0.dist-info/RECORD +221 -0
  198. sqlspec/builder/mixins/__init__.py +0 -55
  199. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -253
  200. sqlspec/builder/mixins/_delete_operations.py +0 -50
  201. sqlspec/builder/mixins/_insert_operations.py +0 -282
  202. sqlspec/builder/mixins/_merge_operations.py +0 -698
  203. sqlspec/builder/mixins/_order_limit_operations.py +0 -145
  204. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  205. sqlspec/builder/mixins/_select_operations.py +0 -930
  206. sqlspec/builder/mixins/_update_operations.py +0 -199
  207. sqlspec/builder/mixins/_where_clause.py +0 -1298
  208. sqlspec-0.26.0.dist-info/RECORD +0 -157
  209. sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
  210. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/WHEEL +0 -0
  211. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/entry_points.txt +0 -0
  212. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/licenses/LICENSE +0 -0
sqlspec/builder/_base.py CHANGED
@@ -6,7 +6,7 @@ Provides abstract base classes and core functionality for SQL query builders.
6
6
  import hashlib
7
7
  import uuid
8
8
  from abc import ABC, abstractmethod
9
- from typing import TYPE_CHECKING, Any, NoReturn, Optional, Union, cast
9
+ from typing import TYPE_CHECKING, Any, NoReturn, cast
10
10
 
11
11
  import sqlglot
12
12
  from sqlglot import Dialect, exp
@@ -38,9 +38,7 @@ class SafeQuery:
38
38
 
39
39
  __slots__ = ("dialect", "parameters", "sql")
40
40
 
41
- def __init__(
42
- self, sql: str, parameters: Optional[dict[str, Any]] = None, dialect: Optional[DialectType] = None
43
- ) -> None:
41
+ def __init__(self, sql: str, parameters: dict[str, Any] | None = None, dialect: DialectType | None = None) -> None:
44
42
  self.sql = sql
45
43
  self.parameters = parameters if parameters is not None else {}
46
44
  self.dialect = dialect
@@ -68,8 +66,8 @@ class QueryBuilder(ABC):
68
66
 
69
67
  def __init__(
70
68
  self,
71
- dialect: Optional[DialectType] = None,
72
- schema: Optional[dict[str, dict[str, str]]] = None,
69
+ dialect: DialectType | None = None,
70
+ schema: dict[str, dict[str, str]] | None = None,
73
71
  enable_optimization: bool = True,
74
72
  optimize_joins: bool = True,
75
73
  optimize_predicates: bool = True,
@@ -82,7 +80,7 @@ class QueryBuilder(ABC):
82
80
  self.optimize_predicates = optimize_predicates
83
81
  self.simplify_expressions = simplify_expressions
84
82
 
85
- self._expression: Optional[exp.Expression] = None
83
+ self._expression: exp.Expression | None = None
86
84
  self._parameters: dict[str, Any] = {}
87
85
  self._parameter_counter: int = 0
88
86
  self._with_ctes: dict[str, exp.CTE] = {}
@@ -95,7 +93,7 @@ class QueryBuilder(ABC):
95
93
  "QueryBuilder._create_base_expression must return a valid sqlglot expression."
96
94
  )
97
95
 
98
- def get_expression(self) -> Optional[exp.Expression]:
96
+ def get_expression(self) -> exp.Expression | None:
99
97
  """Get expression reference (no copy).
100
98
 
101
99
  Returns:
@@ -139,7 +137,7 @@ class QueryBuilder(ABC):
139
137
  """
140
138
 
141
139
  @staticmethod
142
- def _raise_sql_builder_error(message: str, cause: Optional[BaseException] = None) -> NoReturn:
140
+ def _raise_sql_builder_error(message: str, cause: BaseException | None = None) -> NoReturn:
143
141
  """Helper to raise SQLBuilderError, potentially with a cause.
144
142
 
145
143
  Args:
@@ -191,7 +189,81 @@ class QueryBuilder(ABC):
191
189
  msg = f"Failed to parse CTE query: {cause!s}"
192
190
  raise SQLBuilderError(msg) from cause
193
191
 
194
- def _add_parameter(self, value: Any, context: Optional[str] = None) -> str:
192
+ def _build_final_expression(self, *, copy: bool = False) -> exp.Expression:
193
+ """Construct the current expression with attached CTEs.
194
+
195
+ Args:
196
+ copy: Whether to copy the underlying expression tree before
197
+ applying transformations.
198
+
199
+ Returns:
200
+ Expression representing the current builder state with CTEs applied.
201
+ """
202
+ if self._expression is None:
203
+ self._raise_sql_builder_error("QueryBuilder expression not initialized.")
204
+
205
+ base_expression = self._expression.copy() if copy else self._expression
206
+
207
+ if not self._with_ctes:
208
+ return base_expression
209
+
210
+ final_expression: exp.Expression = base_expression
211
+ if has_with_method(final_expression):
212
+ for alias, cte_node in self._with_ctes.items():
213
+ final_expression = cast("Any", final_expression).with_(cte_node.args["this"], as_=alias, copy=False)
214
+ return cast("exp.Expression", final_expression)
215
+
216
+ if isinstance(final_expression, (exp.Select, exp.Insert, exp.Update, exp.Delete, exp.Union)):
217
+ return exp.With(expressions=list(self._with_ctes.values()), this=final_expression)
218
+
219
+ return final_expression
220
+
221
+ def _spawn_like_self(self: Self) -> Self:
222
+ """Create a new builder instance with matching configuration."""
223
+ return type(self)(
224
+ dialect=self.dialect,
225
+ schema=self.schema,
226
+ enable_optimization=self.enable_optimization,
227
+ optimize_joins=self.optimize_joins,
228
+ optimize_predicates=self.optimize_predicates,
229
+ simplify_expressions=self.simplify_expressions,
230
+ )
231
+
232
+ def _resolve_cte_query(self, alias: str, query: "QueryBuilder | exp.Select | str") -> exp.Select:
233
+ """Resolve a CTE query into a Select expression with merged parameters."""
234
+ if isinstance(query, QueryBuilder):
235
+ query_expr = query.get_expression()
236
+ if query_expr is None:
237
+ self._raise_cte_query_error(alias, "query builder has no expression")
238
+ if not isinstance(query_expr, exp.Select):
239
+ self._raise_cte_query_error(alias, f"expression must be a Select, got {type(query_expr).__name__}")
240
+ cte_select_expression = query_expr.copy()
241
+ param_mapping = self._merge_cte_parameters(alias, query.parameters)
242
+ updated_expression = self._update_placeholders_in_expression(cte_select_expression, param_mapping)
243
+ if not isinstance(updated_expression, exp.Select): # pragma: no cover - defensive
244
+ msg = "CTE placeholder update produced non-select expression"
245
+ raise SQLBuilderError(msg)
246
+ return updated_expression
247
+
248
+ if isinstance(query, str):
249
+ try:
250
+ parsed_expression = sqlglot.parse_one(query, read=self.dialect_name)
251
+ except SQLGlotParseError as e: # pragma: no cover - defensive
252
+ self._raise_cte_parse_error(e)
253
+ if not isinstance(parsed_expression, exp.Select):
254
+ self._raise_cte_query_error(
255
+ alias, f"query string must parse to SELECT, got {type(parsed_expression).__name__}"
256
+ )
257
+ return parsed_expression
258
+
259
+ if isinstance(query, exp.Select):
260
+ return query
261
+
262
+ self._raise_cte_query_error(alias, f"invalid query type: {type(query).__name__}")
263
+ msg = "Unreachable"
264
+ raise AssertionError(msg)
265
+
266
+ def _add_parameter(self, value: Any, context: str | None = None) -> str:
195
267
  """Adds a parameter to the query and returns its placeholder name.
196
268
 
197
269
  Args:
@@ -232,7 +304,7 @@ class QueryBuilder(ABC):
232
304
 
233
305
  return expression.transform(replacer, copy=False)
234
306
 
235
- def add_parameter(self: Self, value: Any, name: Optional[str] = None) -> tuple[Self, str]:
307
+ def add_parameter(self: Self, value: Any, name: str | None = None) -> tuple[Self, str]:
236
308
  """Explicitly adds a parameter to the query.
237
309
 
238
310
  This is useful for parameters that are not directly tied to a
@@ -313,7 +385,7 @@ class QueryBuilder(ABC):
313
385
 
314
386
  return expression.transform(placeholder_replacer, copy=False)
315
387
 
316
- def _generate_builder_cache_key(self, config: "Optional[StatementConfig]" = None) -> str:
388
+ def _generate_builder_cache_key(self, config: "StatementConfig | None" = None) -> str:
317
389
  """Generate cache key based on builder state and configuration.
318
390
 
319
391
  Args:
@@ -356,7 +428,7 @@ class QueryBuilder(ABC):
356
428
  state_string = "|".join(state_parts)
357
429
  return f"builder:{hashlib.sha256(state_string.encode()).hexdigest()[:16]}"
358
430
 
359
- def with_cte(self: Self, alias: str, query: "Union[QueryBuilder, exp.Select, str]") -> Self:
431
+ def with_cte(self: Self, alias: str, query: "QueryBuilder | exp.Select | str") -> Self:
360
432
  """Adds a Common Table Expression (CTE) to the query.
361
433
 
362
434
  Args:
@@ -370,35 +442,7 @@ class QueryBuilder(ABC):
370
442
  if alias in self._with_ctes:
371
443
  self._raise_sql_builder_error(f"CTE with alias '{alias}' already exists.")
372
444
 
373
- cte_select_expression: exp.Select
374
-
375
- if isinstance(query, QueryBuilder):
376
- query_expr = query.get_expression()
377
- if query_expr is None:
378
- self._raise_cte_query_error(alias, "query builder has no expression")
379
- if not isinstance(query_expr, exp.Select):
380
- self._raise_cte_query_error(alias, f"expression must be a Select, got {type(query_expr).__name__}")
381
- cte_select_expression = query_expr
382
- param_mapping = self._merge_cte_parameters(alias, query.parameters)
383
- cte_select_expression = cast(
384
- "exp.Select", self._update_placeholders_in_expression(cte_select_expression, param_mapping)
385
- )
386
-
387
- elif isinstance(query, str):
388
- try:
389
- parsed_expression = sqlglot.parse_one(query, read=self.dialect_name)
390
- if not isinstance(parsed_expression, exp.Select):
391
- self._raise_cte_query_error(
392
- alias, f"query string must parse to SELECT, got {type(parsed_expression).__name__}"
393
- )
394
- cte_select_expression = parsed_expression
395
- except SQLGlotParseError as e:
396
- self._raise_cte_parse_error(e)
397
- elif isinstance(query, exp.Select):
398
- cte_select_expression = query
399
- else:
400
- self._raise_cte_query_error(alias, f"invalid query type: {type(query).__name__}")
401
-
445
+ cte_select_expression = self._resolve_cte_query(alias, query)
402
446
  self._with_ctes[alias] = exp.CTE(this=cte_select_expression, alias=exp.to_table(alias))
403
447
  return self
404
448
 
@@ -408,18 +452,7 @@ class QueryBuilder(ABC):
408
452
  Returns:
409
453
  SafeQuery: A dataclass containing the SQL string and parameters.
410
454
  """
411
- if self._expression is None:
412
- self._raise_sql_builder_error("QueryBuilder expression not initialized.")
413
-
414
- if self._with_ctes:
415
- final_expression = self._expression
416
- if has_with_method(final_expression):
417
- for alias, cte_node in self._with_ctes.items():
418
- final_expression = cast("Any", final_expression).with_(cte_node.args["this"], as_=alias, copy=False)
419
- elif isinstance(final_expression, (exp.Select, exp.Insert, exp.Update, exp.Delete, exp.Union)):
420
- final_expression = exp.With(expressions=list(self._with_ctes.values()), this=final_expression)
421
- else:
422
- final_expression = self._expression
455
+ final_expression = self._build_final_expression()
423
456
 
424
457
  if self.enable_optimization and isinstance(final_expression, exp.Expression):
425
458
  final_expression = self._optimize_expression(final_expression)
@@ -475,7 +508,7 @@ class QueryBuilder(ABC):
475
508
  else:
476
509
  return optimized
477
510
 
478
- def to_statement(self, config: "Optional[StatementConfig]" = None) -> "SQL":
511
+ def to_statement(self, config: "StatementConfig | None" = None) -> "SQL":
479
512
  """Converts the built query into a SQL statement object.
480
513
 
481
514
  Args:
@@ -500,7 +533,7 @@ class QueryBuilder(ABC):
500
533
 
501
534
  return sql_statement
502
535
 
503
- def _to_statement(self, config: "Optional[StatementConfig]" = None) -> "SQL":
536
+ def _to_statement(self, config: "StatementConfig | None" = None) -> "SQL":
504
537
  """Internal method to create SQL statement.
505
538
 
506
539
  Args:
@@ -541,7 +574,7 @@ class QueryBuilder(ABC):
541
574
 
542
575
  def _extract_statement_parameters(
543
576
  self, raw_parameters: Any
544
- ) -> "tuple[Optional[dict[str, Any]], Optional[tuple[Any, ...]]]":
577
+ ) -> "tuple[dict[str, Any] | None, tuple[Any, ...] | None]":
545
578
  """Extract parameters for SQL statement creation.
546
579
 
547
580
  Args:
@@ -570,7 +603,7 @@ class QueryBuilder(ABC):
570
603
  return self.build().sql
571
604
 
572
605
  @property
573
- def dialect_name(self) -> "Optional[str]":
606
+ def dialect_name(self) -> "str | None":
574
607
  """Returns the name of the dialect, if set."""
575
608
  if isinstance(self.dialect, str):
576
609
  return self.dialect
@@ -6,7 +6,7 @@ SQL conditions with parameter binding.
6
6
 
7
7
  from collections.abc import Iterable
8
8
  from datetime import date, datetime
9
- from typing import Any, Optional, cast
9
+ from typing import Any, cast
10
10
 
11
11
  from sqlglot import exp
12
12
 
@@ -77,7 +77,7 @@ class Column:
77
77
 
78
78
  __slots__ = ("_expression", "name", "table")
79
79
 
80
- def __init__(self, name: str, table: Optional[str] = None) -> None:
80
+ def __init__(self, name: str, table: str | None = None) -> None:
81
81
  self.name = name
82
82
  self.table = table
83
83
 
@@ -122,7 +122,7 @@ class Column:
122
122
  """Apply NOT operator (~)."""
123
123
  return ColumnExpression(exp.Not(this=self._expression))
124
124
 
125
- def like(self, pattern: str, escape: Optional[str] = None) -> ColumnExpression:
125
+ def like(self, pattern: str, escape: str | None = None) -> ColumnExpression:
126
126
  """SQL LIKE pattern matching."""
127
127
  if escape:
128
128
  like_expr = exp.Like(
@@ -159,7 +159,7 @@ class Column:
159
159
  """SQL IS NOT NULL."""
160
160
  return ColumnExpression(exp.Not(this=exp.Is(this=self._expression, expression=exp.Null())))
161
161
 
162
- def not_like(self, pattern: str, escape: Optional[str] = None) -> ColumnExpression:
162
+ def not_like(self, pattern: str, escape: str | None = None) -> ColumnExpression:
163
163
  """SQL NOT LIKE pattern matching."""
164
164
  return ~self.like(pattern, escape)
165
165
 
@@ -211,7 +211,7 @@ class Column:
211
211
  """SQL CEIL() function."""
212
212
  return FunctionColumn(exp.Ceil(this=self._expression))
213
213
 
214
- def substring(self, start: int, length: Optional[int] = None) -> "FunctionColumn":
214
+ def substring(self, start: int, length: int | None = None) -> "FunctionColumn":
215
215
  """SQL SUBSTRING() function."""
216
216
  args = [self._convert_value(start)]
217
217
  if length is not None: