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
@@ -0,0 +1,388 @@
1
+ """Reusable mixins for INSERT/UPDATE/DELETE builders."""
2
+
3
+ from collections.abc import Mapping, Sequence
4
+ from typing import Any, cast
5
+
6
+ from mypy_extensions import trait
7
+ from sqlglot import exp
8
+ from typing_extensions import Self
9
+
10
+ from sqlspec.builder._parsing_utils import extract_sql_object_expression
11
+ from sqlspec.exceptions import SQLBuilderError
12
+ from sqlspec.protocols import SQLBuilderProtocol
13
+ from sqlspec.utils.type_guards import has_expression_and_sql, has_query_builder_parameters
14
+
15
+ __all__ = (
16
+ "DeleteFromClauseMixin",
17
+ "InsertFromSelectMixin",
18
+ "InsertIntoClauseMixin",
19
+ "InsertValuesMixin",
20
+ "UpdateFromClauseMixin",
21
+ "UpdateSetClauseMixin",
22
+ "UpdateTableClauseMixin",
23
+ )
24
+
25
+ ARG_PAIR_COUNT = 2
26
+ SINGLE_VALUE_COUNT = 1
27
+
28
+
29
+ # ---------------------------------------------------------------------------
30
+ # DELETE helpers
31
+ # ---------------------------------------------------------------------------
32
+
33
+
34
+ @trait
35
+ class DeleteFromClauseMixin:
36
+ """Mixin providing FROM clause support for DELETE builders."""
37
+
38
+ __slots__ = ()
39
+
40
+ def get_expression(self) -> exp.Expression | None: ...
41
+ def set_expression(self, expression: exp.Expression) -> None: ...
42
+
43
+ def from_(self, table: str) -> Self:
44
+ current_expr = self.get_expression()
45
+ if current_expr is None:
46
+ self.set_expression(exp.Delete())
47
+ current_expr = self.get_expression()
48
+
49
+ if not isinstance(current_expr, exp.Delete):
50
+ msg = f"Base expression for Delete is {type(current_expr).__name__}, expected Delete."
51
+ raise SQLBuilderError(msg)
52
+
53
+ assert current_expr is not None
54
+ setattr(self, "_table", table)
55
+ current_expr.set("this", exp.to_table(table))
56
+ return self
57
+
58
+
59
+ # ---------------------------------------------------------------------------
60
+ # INSERT helpers
61
+ # ---------------------------------------------------------------------------
62
+
63
+
64
+ @trait
65
+ class InsertIntoClauseMixin:
66
+ __slots__ = ()
67
+
68
+ def get_expression(self) -> exp.Expression | None: ...
69
+ def set_expression(self, expression: exp.Expression) -> None: ...
70
+
71
+ def into(self, table: str) -> Self:
72
+ current_expr = self.get_expression()
73
+ if current_expr is None:
74
+ self.set_expression(exp.Insert())
75
+ current_expr = self.get_expression()
76
+
77
+ if not isinstance(current_expr, exp.Insert):
78
+ msg = "Cannot set target table on a non-INSERT expression."
79
+ raise SQLBuilderError(msg)
80
+
81
+ assert current_expr is not None
82
+ setattr(self, "_table", table)
83
+ current_expr.set("this", exp.to_table(table))
84
+ return self
85
+
86
+
87
+ @trait
88
+ class InsertValuesMixin:
89
+ __slots__ = ()
90
+
91
+ def get_expression(self) -> exp.Expression | None: ...
92
+ def set_expression(self, expression: exp.Expression) -> None: ...
93
+
94
+ _columns: Any
95
+
96
+ def add_parameter(self, value: Any, name: str | None = None) -> tuple[Any, str]:
97
+ msg = "Method must be provided by QueryBuilder subclass"
98
+ raise NotImplementedError(msg)
99
+
100
+ def _generate_unique_parameter_name(self, base_name: str) -> str:
101
+ msg = "Method must be provided by QueryBuilder subclass"
102
+ raise NotImplementedError(msg)
103
+
104
+ def columns(self, *columns: str | exp.Expression) -> Self:
105
+ current_expr = self.get_expression()
106
+ if current_expr is None:
107
+ self.set_expression(exp.Insert())
108
+ current_expr = self.get_expression()
109
+
110
+ if not isinstance(current_expr, exp.Insert):
111
+ msg = "Cannot set columns on a non-INSERT expression."
112
+ raise SQLBuilderError(msg)
113
+
114
+ assert current_expr is not None
115
+ current_this = current_expr.args.get("this")
116
+ if current_this is None:
117
+ msg = "Table must be set using .into() before setting columns."
118
+ raise SQLBuilderError(msg)
119
+
120
+ if columns:
121
+ identifiers = [exp.to_identifier(col) if isinstance(col, str) else col for col in columns]
122
+ table_name = current_this.this
123
+ current_expr.set("this", exp.Schema(this=table_name, expressions=identifiers))
124
+ elif isinstance(current_this, exp.Schema):
125
+ table_name = current_this.this
126
+ current_expr.set("this", exp.Table(this=table_name))
127
+
128
+ try:
129
+ cols = self._columns
130
+ if not columns:
131
+ cols.clear()
132
+ else:
133
+ cols[:] = [col if isinstance(col, str) else str(col) for col in columns]
134
+ except AttributeError:
135
+ pass
136
+ return self
137
+
138
+ def values(self, *values: Any, **kwargs: Any) -> Self:
139
+ current_expr = self.get_expression()
140
+ if current_expr is None:
141
+ self.set_expression(exp.Insert())
142
+ current_expr = self.get_expression()
143
+
144
+ if not isinstance(current_expr, exp.Insert):
145
+ msg = "Cannot add values to a non-INSERT expression."
146
+ raise SQLBuilderError(msg)
147
+
148
+ assert current_expr is not None
149
+ table_name = cast("str | None", self._table) # type: ignore[attr-defined]
150
+ if not table_name:
151
+ msg = "The target table must be set using .into() before adding values."
152
+ raise SQLBuilderError(msg)
153
+
154
+ positional_values = list(values)
155
+ if len(positional_values) == SINGLE_VALUE_COUNT and hasattr(positional_values[0], "items") and not kwargs:
156
+ kwargs = cast("dict[str, Any]", positional_values[0])
157
+ positional_values = []
158
+
159
+ if kwargs and positional_values:
160
+ msg = "Cannot mix positional values with keyword values."
161
+ raise SQLBuilderError(msg)
162
+
163
+ row_expressions: list[exp.Expression] = []
164
+ column_defs = cast("list[str]", self._columns or [])
165
+
166
+ if kwargs:
167
+ if not column_defs:
168
+ self.columns(*kwargs.keys())
169
+ column_defs = cast("list[str]", self._columns)
170
+ for col, val in kwargs.items():
171
+ if isinstance(val, exp.Expression):
172
+ row_expressions.append(val)
173
+ continue
174
+ if has_expression_and_sql(val):
175
+ row_expressions.append(extract_sql_object_expression(val, builder=self))
176
+ continue
177
+ column_name = str(col).split(".")[-1]
178
+ unique_name = self._generate_unique_parameter_name(column_name)
179
+ _, unique_name = self.add_parameter(val, name=unique_name)
180
+ row_expressions.append(exp.Placeholder(this=unique_name))
181
+ else:
182
+ if column_defs and len(positional_values) != len(column_defs):
183
+ msg = (
184
+ f"Number of values ({len(positional_values)}) does not match the number of specified columns "
185
+ f"({len(column_defs)})."
186
+ )
187
+ raise SQLBuilderError(msg)
188
+
189
+ for index, raw_value in enumerate(positional_values):
190
+ if isinstance(raw_value, exp.Expression):
191
+ row_expressions.append(raw_value)
192
+ elif has_expression_and_sql(raw_value):
193
+ row_expressions.append(extract_sql_object_expression(raw_value, builder=self))
194
+ else:
195
+ if column_defs and index < len(column_defs):
196
+ column_token = column_defs[index]
197
+ column_name = column_token.rsplit(".", maxsplit=1)[-1]
198
+ else:
199
+ column_name = f"value_{index + 1}"
200
+ unique_name = self._generate_unique_parameter_name(column_name)
201
+ _, unique_name = self.add_parameter(raw_value, name=unique_name)
202
+ row_expressions.append(exp.Placeholder(this=unique_name))
203
+
204
+ values_node = current_expr.args.get("expression")
205
+ tuple_expression = exp.Tuple(expressions=row_expressions)
206
+ if isinstance(values_node, exp.Values):
207
+ values_node.expressions.append(tuple_expression)
208
+ else:
209
+ current_expr.set("expression", exp.Values(expressions=[tuple_expression]))
210
+ return self
211
+
212
+ def add_values(self, values: Sequence[Any]) -> Self:
213
+ return self.values(*values)
214
+
215
+
216
+ @trait
217
+ class InsertFromSelectMixin:
218
+ __slots__ = ()
219
+
220
+ def get_expression(self) -> exp.Expression | None: ...
221
+ def set_expression(self, expression: exp.Expression) -> None: ...
222
+
223
+ _table: Any
224
+
225
+ def add_parameter(self, value: Any, name: str | None = None) -> tuple[Any, str]:
226
+ msg = "Method must be provided by QueryBuilder subclass"
227
+ raise NotImplementedError(msg)
228
+
229
+ def from_select(self, select_builder: SQLBuilderProtocol) -> Self:
230
+ if not getattr(self, "_table", None):
231
+ msg = "The target table must be set using .into() before adding values."
232
+ raise SQLBuilderError(msg)
233
+
234
+ current_expr = self.get_expression()
235
+ if current_expr is None:
236
+ self.set_expression(exp.Insert())
237
+ current_expr = self.get_expression()
238
+
239
+ if not isinstance(current_expr, exp.Insert):
240
+ msg = "Cannot set INSERT source on a non-INSERT expression."
241
+ raise SQLBuilderError(msg)
242
+
243
+ assert current_expr is not None
244
+ subquery_parameters = getattr(select_builder, "_parameters", None)
245
+ if isinstance(subquery_parameters, dict):
246
+ builder_with_params = cast("SQLBuilderProtocol", self)
247
+ for param_name, param_value in subquery_parameters.items():
248
+ builder_with_params.add_parameter(param_value, name=param_name)
249
+
250
+ select_expr = getattr(select_builder, "_expression", None)
251
+ if select_expr and isinstance(select_expr, exp.Select):
252
+ current_expr.set("expression", select_expr.copy())
253
+ else:
254
+ msg = "SelectBuilder must have a valid SELECT expression."
255
+ raise SQLBuilderError(msg)
256
+ return self
257
+
258
+
259
+ # ---------------------------------------------------------------------------
260
+ # UPDATE helpers
261
+ # ---------------------------------------------------------------------------
262
+
263
+
264
+ @trait
265
+ class UpdateTableClauseMixin:
266
+ __slots__ = ()
267
+
268
+ def get_expression(self) -> exp.Expression | None: ...
269
+ def set_expression(self, expression: exp.Expression) -> None: ...
270
+
271
+ def table(self, table_name: str, alias: str | None = None) -> Self:
272
+ current_expr = self.get_expression()
273
+ if current_expr is None or not isinstance(current_expr, exp.Update):
274
+ self.set_expression(exp.Update(this=None, expressions=[], joins=[]))
275
+ current_expr = self.get_expression()
276
+
277
+ assert current_expr is not None
278
+
279
+ table_expr: exp.Expression = exp.to_table(table_name, alias=alias)
280
+ current_expr.set("this", table_expr)
281
+ setattr(self, "_table", table_name)
282
+ return self
283
+
284
+
285
+ @trait
286
+ class UpdateSetClauseMixin:
287
+ __slots__ = ()
288
+
289
+ def get_expression(self) -> exp.Expression | None: ...
290
+ def set_expression(self, expression: exp.Expression) -> None: ...
291
+
292
+ def add_parameter(self, value: Any, name: str | None = None) -> tuple[Any, str]:
293
+ msg = "Method must be provided by QueryBuilder subclass"
294
+ raise NotImplementedError(msg)
295
+
296
+ def _generate_unique_parameter_name(self, base_name: str) -> str:
297
+ msg = "Method must be provided by QueryBuilder subclass"
298
+ raise NotImplementedError(msg)
299
+
300
+ def _process_update_value(self, val: Any, col: Any) -> exp.Expression:
301
+ if isinstance(val, exp.Expression):
302
+ return val
303
+ if has_query_builder_parameters(val):
304
+ subquery = val.build()
305
+ sql_text = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
306
+ value_expr = exp.paren(exp.maybe_parse(sql_text, dialect=getattr(self, "dialect", None)))
307
+ for p_name, p_value in getattr(val, "parameters", {}).items():
308
+ self.add_parameter(p_value, name=p_name)
309
+ return value_expr
310
+ if hasattr(val, "expression") and hasattr(val, "sql"):
311
+ return extract_sql_object_expression(val, builder=self)
312
+ column_name = col if isinstance(col, str) else str(col)
313
+ if "." in column_name:
314
+ column_name = column_name.split(".")[-1]
315
+ param_name = self._generate_unique_parameter_name(column_name)
316
+ param_name = self.add_parameter(val, name=param_name)[1]
317
+ return exp.Placeholder(this=param_name)
318
+
319
+ def set(self, *args: Any, **kwargs: Any) -> Self:
320
+ current_expr = self.get_expression()
321
+ if current_expr is None:
322
+ self.set_expression(exp.Update())
323
+ current_expr = self.get_expression()
324
+
325
+ if not isinstance(current_expr, exp.Update):
326
+ msg = "Cannot add SET clause to non-UPDATE expression."
327
+ raise SQLBuilderError(msg)
328
+
329
+ assert current_expr is not None
330
+
331
+ assignments: list[exp.Expression] = []
332
+ if len(args) == ARG_PAIR_COUNT and not kwargs:
333
+ col, val = args
334
+ col_expr = col if isinstance(col, exp.Column) else exp.column(col)
335
+ assignments.append(exp.EQ(this=col_expr, expression=self._process_update_value(val, col)))
336
+ elif (len(args) == SINGLE_VALUE_COUNT and isinstance(args[0], Mapping)) or kwargs:
337
+ all_values = dict(args[0] if args else {}, **kwargs)
338
+ for col, val in all_values.items():
339
+ assignments.append(exp.EQ(this=exp.column(col), expression=self._process_update_value(val, col)))
340
+ else:
341
+ msg = "Invalid arguments for set(): use (column, value), mapping, or kwargs."
342
+ raise SQLBuilderError(msg)
343
+
344
+ existing = current_expr.args.get("expressions", [])
345
+ current_expr.set("expressions", existing + assignments)
346
+ return self
347
+
348
+
349
+ @trait
350
+ class UpdateFromClauseMixin:
351
+ __slots__ = ()
352
+
353
+ def get_expression(self) -> exp.Expression | None: ...
354
+ def set_expression(self, expression: exp.Expression) -> None: ...
355
+
356
+ def from_(self, table: str | exp.Expression | Any, alias: str | None = None) -> Self:
357
+ current_expr = self.get_expression()
358
+ if current_expr is None or not isinstance(current_expr, exp.Update):
359
+ msg = "Cannot add FROM clause to non-UPDATE expression. Set the main table first."
360
+ raise SQLBuilderError(msg)
361
+
362
+ assert current_expr is not None
363
+ table_expr: exp.Expression
364
+ if isinstance(table, str):
365
+ table_expr = exp.to_table(table, alias=alias)
366
+ elif has_query_builder_parameters(table):
367
+ subquery_params = getattr(table, "_parameters", None)
368
+ if isinstance(subquery_params, dict):
369
+ builder_with_params = cast("SQLBuilderProtocol", self)
370
+ for param_name, param_value in subquery_params.items():
371
+ builder_with_params.add_parameter(param_value, name=param_name)
372
+ raw_expression = getattr(table, "_expression", None)
373
+ subquery_source = raw_expression if isinstance(raw_expression, exp.Expression) else exp.select()
374
+ subquery_exp = exp.paren(subquery_source)
375
+ table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
376
+ elif isinstance(table, exp.Expression):
377
+ table_expr = exp.alias_(table, alias) if alias else table
378
+ else:
379
+ msg = f"Unsupported table type for FROM clause: {type(table)}"
380
+ raise SQLBuilderError(msg)
381
+
382
+ from_clause = current_expr.args.get("from")
383
+ if from_clause is None:
384
+ from_clause = exp.From(expressions=[])
385
+ current_expr.set("from", from_clause)
386
+
387
+ from_clause.append("expressions", table_expr)
388
+ return self
@@ -4,16 +4,16 @@ Provides statement builders (select, insert, update, etc.) and column expression
4
4
  """
5
5
 
6
6
  import logging
7
- from typing import TYPE_CHECKING, Any, Optional, Union
7
+ from typing import TYPE_CHECKING, Any, Union
8
8
 
9
9
  import sqlglot
10
10
  from sqlglot import exp
11
11
  from sqlglot.dialects.dialect import DialectType
12
12
  from sqlglot.errors import ParseError as SQLGlotParseError
13
13
 
14
- from sqlspec.builder import (
14
+ from sqlspec.builder._column import Column
15
+ from sqlspec.builder._ddl import (
15
16
  AlterTable,
16
- Column,
17
17
  CommentOn,
18
18
  CreateIndex,
19
19
  CreateMaterializedView,
@@ -21,18 +21,14 @@ from sqlspec.builder import (
21
21
  CreateTable,
22
22
  CreateTableAsSelect,
23
23
  CreateView,
24
- Delete,
25
24
  DropIndex,
26
25
  DropSchema,
27
26
  DropTable,
28
27
  DropView,
29
- Insert,
30
- Merge,
31
28
  RenameTable,
32
- Select,
33
29
  Truncate,
34
- Update,
35
30
  )
31
+ from sqlspec.builder._delete import Delete
36
32
  from sqlspec.builder._expression_wrappers import (
37
33
  AggregateExpression,
38
34
  ConversionExpression,
@@ -40,9 +36,12 @@ from sqlspec.builder._expression_wrappers import (
40
36
  MathExpression,
41
37
  StringExpression,
42
38
  )
39
+ from sqlspec.builder._insert import Insert
40
+ from sqlspec.builder._join import JoinBuilder
41
+ from sqlspec.builder._merge import Merge
43
42
  from sqlspec.builder._parsing_utils import extract_expression, to_expression
44
- from sqlspec.builder.mixins._join_operations import JoinBuilder
45
- from sqlspec.builder.mixins._select_operations import Case, SubqueryBuilder, WindowFunctionBuilder
43
+ from sqlspec.builder._select import Case, Select, SubqueryBuilder, WindowFunctionBuilder
44
+ from sqlspec.builder._update import Update
46
45
  from sqlspec.core.statement import SQL
47
46
  from sqlspec.exceptions import SQLBuilderError
48
47
 
@@ -205,7 +204,7 @@ class SQLFactory:
205
204
  select_builder.select(*columns_or_sql)
206
205
  return select_builder
207
206
 
208
- def insert(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "Insert":
207
+ def insert(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Insert":
209
208
  builder_dialect = dialect or self.dialect
210
209
  builder = Insert(dialect=builder_dialect)
211
210
  if table_or_sql:
@@ -222,7 +221,7 @@ class SQLFactory:
222
221
  return builder.into(table_or_sql)
223
222
  return builder
224
223
 
225
- def update(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "Update":
224
+ def update(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Update":
226
225
  builder_dialect = dialect or self.dialect
227
226
  builder = Update(dialect=builder_dialect)
228
227
  if table_or_sql:
@@ -238,7 +237,7 @@ class SQLFactory:
238
237
  return builder.table(table_or_sql)
239
238
  return builder
240
239
 
241
- def delete(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "Delete":
240
+ def delete(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Delete":
242
241
  builder_dialect = dialect or self.dialect
243
242
  builder = Delete(dialect=builder_dialect)
244
243
  if table_or_sql and self._looks_like_sql(table_or_sql):
@@ -252,7 +251,7 @@ class SQLFactory:
252
251
  return self._populate_delete_from_sql(builder, table_or_sql)
253
252
  return builder
254
253
 
255
- def merge(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "Merge":
254
+ def merge(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Merge":
256
255
  builder_dialect = dialect or self.dialect
257
256
  builder = Merge(dialect=builder_dialect)
258
257
  if table_or_sql:
@@ -423,7 +422,7 @@ class SQLFactory:
423
422
  return CommentOn(dialect=dialect or self.dialect)
424
423
 
425
424
  @staticmethod
426
- def _looks_like_sql(candidate: str, expected_type: Optional[str] = None) -> bool:
425
+ def _looks_like_sql(candidate: str, expected_type: str | None = None) -> bool:
427
426
  """Determine if a string looks like SQL.
428
427
 
429
428
  Args:
@@ -525,7 +524,7 @@ class SQLFactory:
525
524
  logger.warning("Failed to parse MERGE SQL, falling back to traditional mode: %s", e)
526
525
  return builder
527
526
 
528
- def column(self, name: str, table: Optional[str] = None) -> Column:
527
+ def column(self, name: str, table: str | None = None) -> Column:
529
528
  """Create a column reference.
530
529
 
531
530
  Args:
@@ -683,7 +682,7 @@ class SQLFactory:
683
682
  return Column(name)
684
683
 
685
684
  @staticmethod
686
- def raw(sql_fragment: str, **parameters: Any) -> "Union[exp.Expression, SQL]":
685
+ def raw(sql_fragment: str, **parameters: Any) -> "exp.Expression | SQL":
687
686
  """Create a raw SQL expression from a string fragment with optional parameters.
688
687
 
689
688
  Args:
@@ -818,7 +817,7 @@ class SQLFactory:
818
817
  return AggregateExpression(exp.Min(this=col_expr))
819
818
 
820
819
  @staticmethod
821
- def rollup(*columns: Union[str, exp.Expression]) -> FunctionExpression:
820
+ def rollup(*columns: str | exp.Expression) -> FunctionExpression:
822
821
  """Create a ROLLUP expression for GROUP BY clauses.
823
822
 
824
823
  Args:
@@ -840,7 +839,7 @@ class SQLFactory:
840
839
  return FunctionExpression(exp.Rollup(expressions=column_exprs))
841
840
 
842
841
  @staticmethod
843
- def cube(*columns: Union[str, exp.Expression]) -> FunctionExpression:
842
+ def cube(*columns: str | exp.Expression) -> FunctionExpression:
844
843
  """Create a CUBE expression for GROUP BY clauses.
845
844
 
846
845
  Args:
@@ -862,7 +861,7 @@ class SQLFactory:
862
861
  return FunctionExpression(exp.Cube(expressions=column_exprs))
863
862
 
864
863
  @staticmethod
865
- def grouping_sets(*column_sets: Union[tuple[str, ...], list[str]]) -> FunctionExpression:
864
+ def grouping_sets(*column_sets: tuple[str, ...] | list[str]) -> FunctionExpression:
866
865
  """Create a GROUPING SETS expression for GROUP BY clauses.
867
866
 
868
867
  Args:
@@ -896,7 +895,7 @@ class SQLFactory:
896
895
  return FunctionExpression(exp.GroupingSets(expressions=set_expressions))
897
896
 
898
897
  @staticmethod
899
- def any(values: Union[list[Any], exp.Expression, str]) -> FunctionExpression:
898
+ def any(values: list[Any] | exp.Expression | str) -> FunctionExpression:
900
899
  """Create an ANY expression for use with comparison operators.
901
900
 
902
901
  Args:
@@ -924,7 +923,7 @@ class SQLFactory:
924
923
  return FunctionExpression(exp.Any(this=values))
925
924
 
926
925
  @staticmethod
927
- def not_any_(values: Union[list[Any], exp.Expression, str]) -> FunctionExpression:
926
+ def not_any_(values: list[Any] | exp.Expression | str) -> FunctionExpression:
928
927
  """Create a NOT ANY expression for use with comparison operators.
929
928
 
930
929
  Args:
@@ -946,7 +945,7 @@ class SQLFactory:
946
945
  return SQLFactory.any(values)
947
946
 
948
947
  @staticmethod
949
- def concat(*expressions: Union[str, exp.Expression]) -> StringExpression:
948
+ def concat(*expressions: str | exp.Expression) -> StringExpression:
950
949
  """Create a CONCAT expression.
951
950
 
952
951
  Args:
@@ -959,7 +958,7 @@ class SQLFactory:
959
958
  return StringExpression(exp.Concat(expressions=exprs))
960
959
 
961
960
  @staticmethod
962
- def upper(column: Union[str, exp.Expression]) -> StringExpression:
961
+ def upper(column: str | exp.Expression) -> StringExpression:
963
962
  """Create an UPPER expression.
964
963
 
965
964
  Args:
@@ -972,7 +971,7 @@ class SQLFactory:
972
971
  return StringExpression(exp.Upper(this=col_expr))
973
972
 
974
973
  @staticmethod
975
- def lower(column: Union[str, exp.Expression]) -> StringExpression:
974
+ def lower(column: str | exp.Expression) -> StringExpression:
976
975
  """Create a LOWER expression.
977
976
 
978
977
  Args:
@@ -985,7 +984,7 @@ class SQLFactory:
985
984
  return StringExpression(exp.Lower(this=col_expr))
986
985
 
987
986
  @staticmethod
988
- def length(column: Union[str, exp.Expression]) -> StringExpression:
987
+ def length(column: str | exp.Expression) -> StringExpression:
989
988
  """Create a LENGTH expression.
990
989
 
991
990
  Args:
@@ -998,7 +997,7 @@ class SQLFactory:
998
997
  return StringExpression(exp.Length(this=col_expr))
999
998
 
1000
999
  @staticmethod
1001
- def round(column: Union[str, exp.Expression], decimals: int = 0) -> MathExpression:
1000
+ def round(column: str | exp.Expression, decimals: int = 0) -> MathExpression:
1002
1001
  """Create a ROUND expression.
1003
1002
 
1004
1003
  Args:
@@ -1036,7 +1035,7 @@ class SQLFactory:
1036
1035
  return FunctionExpression(exp.convert(value))
1037
1036
 
1038
1037
  @staticmethod
1039
- def decode(column: Union[str, exp.Expression], *args: Union[str, exp.Expression, Any]) -> FunctionExpression:
1038
+ def decode(column: str | exp.Expression, *args: str | exp.Expression | Any) -> FunctionExpression:
1040
1039
  """Create a DECODE expression (Oracle-style conditional logic).
1041
1040
 
1042
1041
  DECODE compares column to each search value and returns the corresponding result.
@@ -1086,7 +1085,7 @@ class SQLFactory:
1086
1085
  return FunctionExpression(exp.Case(ifs=conditions, default=default))
1087
1086
 
1088
1087
  @staticmethod
1089
- def cast(column: Union[str, exp.Expression], data_type: str) -> ConversionExpression:
1088
+ def cast(column: str | exp.Expression, data_type: str) -> ConversionExpression:
1090
1089
  """Create a CAST expression for type conversion.
1091
1090
 
1092
1091
  Args:
@@ -1100,7 +1099,7 @@ class SQLFactory:
1100
1099
  return ConversionExpression(exp.Cast(this=col_expr, to=exp.DataType.build(data_type)))
1101
1100
 
1102
1101
  @staticmethod
1103
- def coalesce(*expressions: Union[str, exp.Expression]) -> ConversionExpression:
1102
+ def coalesce(*expressions: str | exp.Expression) -> ConversionExpression:
1104
1103
  """Create a COALESCE expression.
1105
1104
 
1106
1105
  Args:
@@ -1113,9 +1112,7 @@ class SQLFactory:
1113
1112
  return ConversionExpression(exp.Coalesce(expressions=exprs))
1114
1113
 
1115
1114
  @staticmethod
1116
- def nvl(
1117
- column: Union[str, exp.Expression], substitute_value: Union[str, exp.Expression, Any]
1118
- ) -> ConversionExpression:
1115
+ def nvl(column: str | exp.Expression, substitute_value: str | exp.Expression | Any) -> ConversionExpression:
1119
1116
  """Create an NVL (Oracle-style) expression using COALESCE.
1120
1117
 
1121
1118
  Args:
@@ -1131,9 +1128,9 @@ class SQLFactory:
1131
1128
 
1132
1129
  @staticmethod
1133
1130
  def nvl2(
1134
- column: Union[str, exp.Expression],
1135
- value_if_not_null: Union[str, exp.Expression, Any],
1136
- value_if_null: Union[str, exp.Expression, Any],
1131
+ column: str | exp.Expression,
1132
+ value_if_not_null: str | exp.Expression | Any,
1133
+ value_if_null: str | exp.Expression | Any,
1137
1134
  ) -> ConversionExpression:
1138
1135
  """Create an NVL2 (Oracle-style) expression using CASE.
1139
1136
 
@@ -1246,8 +1243,8 @@ class SQLFactory:
1246
1243
 
1247
1244
  def row_number(
1248
1245
  self,
1249
- partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
1250
- order_by: Optional[Union[str, list[str], exp.Expression]] = None,
1246
+ partition_by: str | list[str] | exp.Expression | None = None,
1247
+ order_by: str | list[str] | exp.Expression | None = None,
1251
1248
  ) -> FunctionExpression:
1252
1249
  """Create a ROW_NUMBER() window function.
1253
1250
 
@@ -1262,8 +1259,8 @@ class SQLFactory:
1262
1259
 
1263
1260
  def rank(
1264
1261
  self,
1265
- partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
1266
- order_by: Optional[Union[str, list[str], exp.Expression]] = None,
1262
+ partition_by: str | list[str] | exp.Expression | None = None,
1263
+ order_by: str | list[str] | exp.Expression | None = None,
1267
1264
  ) -> FunctionExpression:
1268
1265
  """Create a RANK() window function.
1269
1266
 
@@ -1278,8 +1275,8 @@ class SQLFactory:
1278
1275
 
1279
1276
  def dense_rank(
1280
1277
  self,
1281
- partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
1282
- order_by: Optional[Union[str, list[str], exp.Expression]] = None,
1278
+ partition_by: str | list[str] | exp.Expression | None = None,
1279
+ order_by: str | list[str] | exp.Expression | None = None,
1283
1280
  ) -> FunctionExpression:
1284
1281
  """Create a DENSE_RANK() window function.
1285
1282
 
@@ -1296,8 +1293,8 @@ class SQLFactory:
1296
1293
  def _create_window_function(
1297
1294
  func_name: str,
1298
1295
  func_args: list[exp.Expression],
1299
- partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
1300
- order_by: Optional[Union[str, list[str], exp.Expression]] = None,
1296
+ partition_by: str | list[str] | exp.Expression | None = None,
1297
+ order_by: str | list[str] | exp.Expression | None = None,
1301
1298
  ) -> FunctionExpression:
1302
1299
  """Helper to create window function expressions.
1303
1300