sqlspec 0.25.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 (199) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +256 -24
  3. sqlspec/_typing.py +71 -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 +69 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +340 -0
  9. sqlspec/adapters/adbc/driver.py +266 -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 +153 -0
  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 +88 -15
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
  18. sqlspec/adapters/aiosqlite/driver.py +143 -40
  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 +2 -2
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +493 -0
  26. sqlspec/adapters/asyncmy/config.py +68 -23
  27. sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
  28. sqlspec/adapters/asyncmy/driver.py +313 -58
  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 +59 -35
  37. sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
  38. sqlspec/adapters/asyncpg/driver.py +170 -25
  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 +27 -10
  45. sqlspec/adapters/bigquery/data_dictionary.py +149 -0
  46. sqlspec/adapters/bigquery/driver.py +368 -142
  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 +125 -0
  50. sqlspec/adapters/duckdb/_types.py +1 -1
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +553 -0
  53. sqlspec/adapters/duckdb/config.py +80 -20
  54. sqlspec/adapters/duckdb/data_dictionary.py +163 -0
  55. sqlspec/adapters/duckdb/driver.py +167 -45
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +4 -4
  59. sqlspec/adapters/duckdb/type_converter.py +133 -0
  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 +122 -32
  65. sqlspec/adapters/oracledb/data_dictionary.py +509 -0
  66. sqlspec/adapters/oracledb/driver.py +353 -91
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +767 -0
  69. sqlspec/adapters/oracledb/migrations.py +348 -73
  70. sqlspec/adapters/oracledb/type_converter.py +207 -0
  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 +46 -17
  76. sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
  77. sqlspec/adapters/psqlpy/driver.py +123 -209
  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 +102 -0
  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 +69 -35
  86. sqlspec/adapters/psycopg/data_dictionary.py +331 -0
  87. sqlspec/adapters/psycopg/driver.py +238 -81
  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 +87 -15
  96. sqlspec/adapters/sqlite/data_dictionary.py +149 -0
  97. sqlspec/adapters/sqlite/driver.py +137 -54
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +18 -9
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +162 -89
  104. sqlspec/builder/_column.py +62 -29
  105. sqlspec/builder/_ddl.py +180 -121
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +53 -94
  109. sqlspec/builder/_insert.py +32 -131
  110. sqlspec/builder/_join.py +375 -0
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +111 -17
  113. sqlspec/builder/_select.py +1457 -24
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +307 -194
  116. sqlspec/config.py +252 -67
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +17 -17
  119. sqlspec/core/compiler.py +62 -9
  120. sqlspec/core/filters.py +37 -37
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +83 -48
  123. sqlspec/core/result.py +102 -46
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +36 -30
  126. sqlspec/core/type_conversion.py +235 -0
  127. sqlspec/driver/__init__.py +7 -6
  128. sqlspec/driver/_async.py +188 -151
  129. sqlspec/driver/_common.py +285 -80
  130. sqlspec/driver/_sync.py +188 -152
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +75 -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/__init__.py +4 -3
  153. sqlspec/migrations/base.py +302 -39
  154. sqlspec/migrations/commands.py +611 -144
  155. sqlspec/migrations/context.py +142 -0
  156. sqlspec/migrations/fix.py +199 -0
  157. sqlspec/migrations/loaders.py +68 -23
  158. sqlspec/migrations/runner.py +543 -107
  159. sqlspec/migrations/tracker.py +237 -21
  160. sqlspec/migrations/utils.py +51 -3
  161. sqlspec/migrations/validation.py +177 -0
  162. sqlspec/protocols.py +66 -36
  163. sqlspec/storage/_utils.py +98 -0
  164. sqlspec/storage/backends/fsspec.py +134 -106
  165. sqlspec/storage/backends/local.py +78 -51
  166. sqlspec/storage/backends/obstore.py +278 -162
  167. sqlspec/storage/registry.py +75 -39
  168. sqlspec/typing.py +16 -84
  169. sqlspec/utils/config_resolver.py +153 -0
  170. sqlspec/utils/correlation.py +4 -5
  171. sqlspec/utils/data_transformation.py +3 -2
  172. sqlspec/utils/deprecation.py +9 -8
  173. sqlspec/utils/fixtures.py +4 -4
  174. sqlspec/utils/logging.py +46 -6
  175. sqlspec/utils/module_loader.py +2 -2
  176. sqlspec/utils/schema.py +288 -0
  177. sqlspec/utils/serializers.py +50 -2
  178. sqlspec/utils/sync_tools.py +21 -17
  179. sqlspec/utils/text.py +1 -2
  180. sqlspec/utils/type_guards.py +111 -20
  181. sqlspec/utils/version.py +433 -0
  182. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
  183. sqlspec-0.27.0.dist-info/RECORD +207 -0
  184. sqlspec/builder/mixins/__init__.py +0 -55
  185. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
  186. sqlspec/builder/mixins/_delete_operations.py +0 -50
  187. sqlspec/builder/mixins/_insert_operations.py +0 -282
  188. sqlspec/builder/mixins/_join_operations.py +0 -389
  189. sqlspec/builder/mixins/_merge_operations.py +0 -592
  190. sqlspec/builder/mixins/_order_limit_operations.py +0 -152
  191. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  192. sqlspec/builder/mixins/_select_operations.py +0 -936
  193. sqlspec/builder/mixins/_update_operations.py +0 -218
  194. sqlspec/builder/mixins/_where_clause.py +0 -1304
  195. sqlspec-0.25.0.dist-info/RECORD +0 -139
  196. sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
  197. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
  198. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
  199. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
@@ -4,12 +4,13 @@ Provides a fluent interface for building SQL DELETE queries with
4
4
  parameter binding and validation.
5
5
  """
6
6
 
7
- from typing import Any, Optional
7
+ from typing import Any
8
8
 
9
9
  from sqlglot import exp
10
10
 
11
11
  from sqlspec.builder._base import QueryBuilder, SafeQuery
12
- from sqlspec.builder.mixins import DeleteFromClauseMixin, ReturningClauseMixin, WhereClauseMixin
12
+ from sqlspec.builder._dml import DeleteFromClauseMixin
13
+ from sqlspec.builder._select import ReturningClauseMixin, WhereClauseMixin
13
14
  from sqlspec.core.result import SQLResult
14
15
  from sqlspec.exceptions import SQLBuilderError
15
16
 
@@ -24,9 +25,9 @@ class Delete(QueryBuilder, WhereClauseMixin, ReturningClauseMixin, DeleteFromCla
24
25
  """
25
26
 
26
27
  __slots__ = ("_table",)
27
- _expression: Optional[exp.Expression]
28
+ _expression: exp.Expression | None
28
29
 
29
- def __init__(self, table: Optional[str] = None, **kwargs: Any) -> None:
30
+ def __init__(self, table: str | None = None, **kwargs: Any) -> None:
30
31
  """Initialize DELETE with optional table.
31
32
 
32
33
  Args:
@@ -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