sqlspec 0.24.1__py3-none-any.whl → 0.25.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.
- sqlspec/_sql.py +11 -15
- sqlspec/_typing.py +2 -0
- sqlspec/adapters/adbc/driver.py +2 -2
- sqlspec/adapters/oracledb/driver.py +5 -0
- sqlspec/adapters/psycopg/config.py +2 -4
- sqlspec/base.py +3 -4
- sqlspec/builder/_base.py +55 -13
- sqlspec/builder/_column.py +9 -0
- sqlspec/builder/_ddl.py +7 -7
- sqlspec/builder/_insert.py +10 -6
- sqlspec/builder/_parsing_utils.py +23 -4
- sqlspec/builder/_update.py +1 -1
- sqlspec/builder/mixins/_cte_and_set_ops.py +31 -22
- sqlspec/builder/mixins/_delete_operations.py +12 -7
- sqlspec/builder/mixins/_insert_operations.py +50 -36
- sqlspec/builder/mixins/_join_operations.py +1 -0
- sqlspec/builder/mixins/_merge_operations.py +54 -28
- sqlspec/builder/mixins/_order_limit_operations.py +1 -0
- sqlspec/builder/mixins/_pivot_operations.py +1 -0
- sqlspec/builder/mixins/_select_operations.py +42 -14
- sqlspec/builder/mixins/_update_operations.py +30 -18
- sqlspec/builder/mixins/_where_clause.py +48 -60
- sqlspec/core/__init__.py +3 -2
- sqlspec/core/cache.py +297 -351
- sqlspec/core/compiler.py +5 -3
- sqlspec/core/filters.py +246 -213
- sqlspec/core/hashing.py +9 -11
- sqlspec/core/parameters.py +20 -7
- sqlspec/core/statement.py +67 -12
- sqlspec/driver/_async.py +2 -2
- sqlspec/driver/_common.py +31 -14
- sqlspec/driver/_sync.py +2 -2
- sqlspec/driver/mixins/_result_tools.py +60 -7
- sqlspec/loader.py +8 -9
- sqlspec/storage/backends/fsspec.py +1 -0
- sqlspec/typing.py +2 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/METADATA +1 -1
- {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/RECORD +42 -42
- {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# pyright: reportPrivateUsage=false
|
|
1
2
|
"""INSERT operation mixins.
|
|
2
3
|
|
|
3
4
|
Provides mixins for INSERT statement functionality including
|
|
@@ -25,8 +26,9 @@ class InsertIntoClauseMixin:
|
|
|
25
26
|
|
|
26
27
|
__slots__ = ()
|
|
27
28
|
|
|
28
|
-
# Type
|
|
29
|
-
|
|
29
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
30
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
31
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
30
32
|
|
|
31
33
|
def into(self, table: str) -> Self:
|
|
32
34
|
"""Set the target table for the INSERT statement.
|
|
@@ -40,14 +42,17 @@ class InsertIntoClauseMixin:
|
|
|
40
42
|
Returns:
|
|
41
43
|
The current builder instance for method chaining.
|
|
42
44
|
"""
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
current_expr = self.get_expression()
|
|
46
|
+
if current_expr is None:
|
|
47
|
+
self.set_expression(exp.Insert())
|
|
48
|
+
current_expr = self.get_expression()
|
|
49
|
+
|
|
50
|
+
if not isinstance(current_expr, exp.Insert):
|
|
46
51
|
msg = "Cannot set target table on a non-INSERT expression."
|
|
47
52
|
raise SQLBuilderError(msg)
|
|
48
53
|
|
|
49
54
|
setattr(self, "_table", table)
|
|
50
|
-
|
|
55
|
+
current_expr.set("this", exp.to_table(table))
|
|
51
56
|
return self
|
|
52
57
|
|
|
53
58
|
|
|
@@ -57,8 +62,9 @@ class InsertValuesMixin:
|
|
|
57
62
|
|
|
58
63
|
__slots__ = ()
|
|
59
64
|
|
|
60
|
-
# Type
|
|
61
|
-
|
|
65
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
66
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
67
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
62
68
|
|
|
63
69
|
_columns: Any # Provided by QueryBuilder
|
|
64
70
|
|
|
@@ -74,14 +80,17 @@ class InsertValuesMixin:
|
|
|
74
80
|
|
|
75
81
|
def columns(self, *columns: Union[str, exp.Expression]) -> Self:
|
|
76
82
|
"""Set the columns for the INSERT statement and synchronize the _columns attribute on the builder."""
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
current_expr = self.get_expression()
|
|
84
|
+
if current_expr is None:
|
|
85
|
+
self.set_expression(exp.Insert())
|
|
86
|
+
current_expr = self.get_expression()
|
|
87
|
+
|
|
88
|
+
if not isinstance(current_expr, exp.Insert):
|
|
80
89
|
msg = "Cannot set columns on a non-INSERT expression."
|
|
81
90
|
raise SQLBuilderError(msg)
|
|
82
91
|
|
|
83
92
|
# Get the current table from the expression
|
|
84
|
-
current_this =
|
|
93
|
+
current_this = current_expr.args.get("this")
|
|
85
94
|
if current_this is None:
|
|
86
95
|
msg = "Table must be set using .into() before setting columns."
|
|
87
96
|
raise SQLBuilderError(msg)
|
|
@@ -95,11 +104,11 @@ class InsertValuesMixin:
|
|
|
95
104
|
|
|
96
105
|
# Create Schema object with table and columns
|
|
97
106
|
schema = exp.Schema(this=table_name, expressions=column_identifiers)
|
|
98
|
-
|
|
107
|
+
current_expr.set("this", schema)
|
|
99
108
|
# No columns specified - ensure we have just a Table object
|
|
100
109
|
elif isinstance(current_this, exp.Schema):
|
|
101
110
|
table_name = current_this.this
|
|
102
|
-
|
|
111
|
+
current_expr.set("this", exp.Table(this=table_name))
|
|
103
112
|
|
|
104
113
|
try:
|
|
105
114
|
cols = self._columns
|
|
@@ -126,9 +135,12 @@ class InsertValuesMixin:
|
|
|
126
135
|
Returns:
|
|
127
136
|
The current builder instance for method chaining.
|
|
128
137
|
"""
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
138
|
+
current_expr = self.get_expression()
|
|
139
|
+
if current_expr is None:
|
|
140
|
+
self.set_expression(exp.Insert())
|
|
141
|
+
current_expr = self.get_expression()
|
|
142
|
+
|
|
143
|
+
if not isinstance(current_expr, exp.Insert):
|
|
132
144
|
msg = "Cannot add values to a non-INSERT expression."
|
|
133
145
|
raise SQLBuilderError(msg)
|
|
134
146
|
|
|
@@ -137,8 +149,8 @@ class InsertValuesMixin:
|
|
|
137
149
|
msg = "Cannot mix positional values with keyword values."
|
|
138
150
|
raise SQLBuilderError(msg)
|
|
139
151
|
try:
|
|
140
|
-
|
|
141
|
-
if not
|
|
152
|
+
cols = self._columns
|
|
153
|
+
if not cols:
|
|
142
154
|
self.columns(*kwargs.keys())
|
|
143
155
|
except AttributeError:
|
|
144
156
|
pass
|
|
@@ -156,8 +168,8 @@ class InsertValuesMixin:
|
|
|
156
168
|
elif len(values) == 1 and hasattr(values[0], "items"):
|
|
157
169
|
mapping = values[0]
|
|
158
170
|
try:
|
|
159
|
-
|
|
160
|
-
if not
|
|
171
|
+
cols = self._columns
|
|
172
|
+
if not cols:
|
|
161
173
|
self.columns(*mapping.keys())
|
|
162
174
|
except AttributeError:
|
|
163
175
|
pass
|
|
@@ -174,9 +186,9 @@ class InsertValuesMixin:
|
|
|
174
186
|
row_exprs.append(exp.Placeholder(this=param_name))
|
|
175
187
|
else:
|
|
176
188
|
try:
|
|
177
|
-
|
|
178
|
-
if
|
|
179
|
-
msg = f"Number of values ({len(values)}) does not match the number of specified columns ({len(
|
|
189
|
+
cols = self._columns
|
|
190
|
+
if cols and len(values) != len(cols):
|
|
191
|
+
msg = f"Number of values ({len(values)}) does not match the number of specified columns ({len(cols)})."
|
|
180
192
|
raise SQLBuilderError(msg)
|
|
181
193
|
except AttributeError:
|
|
182
194
|
pass
|
|
@@ -186,11 +198,9 @@ class InsertValuesMixin:
|
|
|
186
198
|
row_exprs.append(v)
|
|
187
199
|
else:
|
|
188
200
|
try:
|
|
189
|
-
|
|
190
|
-
if
|
|
191
|
-
column_name = (
|
|
192
|
-
str(_columns[i]).split(".")[-1] if "." in str(_columns[i]) else str(_columns[i])
|
|
193
|
-
)
|
|
201
|
+
cols = self._columns
|
|
202
|
+
if cols and i < len(cols):
|
|
203
|
+
column_name = str(cols[i]).split(".")[-1] if "." in str(cols[i]) else str(cols[i])
|
|
194
204
|
param_name = self._generate_unique_parameter_name(column_name)
|
|
195
205
|
else:
|
|
196
206
|
param_name = self._generate_unique_parameter_name(f"value_{i + 1}")
|
|
@@ -200,7 +210,7 @@ class InsertValuesMixin:
|
|
|
200
210
|
row_exprs.append(exp.Placeholder(this=param_name))
|
|
201
211
|
|
|
202
212
|
values_expr = exp.Values(expressions=[row_exprs])
|
|
203
|
-
|
|
213
|
+
current_expr.set("expression", values_expr)
|
|
204
214
|
return self
|
|
205
215
|
|
|
206
216
|
def add_values(self, values: Sequence[Any]) -> Self:
|
|
@@ -221,8 +231,9 @@ class InsertFromSelectMixin:
|
|
|
221
231
|
|
|
222
232
|
__slots__ = ()
|
|
223
233
|
|
|
224
|
-
# Type
|
|
225
|
-
|
|
234
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
235
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
236
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
226
237
|
|
|
227
238
|
_table: Any # Provided by QueryBuilder
|
|
228
239
|
|
|
@@ -250,9 +261,12 @@ class InsertFromSelectMixin:
|
|
|
250
261
|
except AttributeError:
|
|
251
262
|
msg = "The target table must be set using .into() before adding values."
|
|
252
263
|
raise SQLBuilderError(msg)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
264
|
+
current_expr = self.get_expression()
|
|
265
|
+
if current_expr is None:
|
|
266
|
+
self.set_expression(exp.Insert())
|
|
267
|
+
current_expr = self.get_expression()
|
|
268
|
+
|
|
269
|
+
if not isinstance(current_expr, exp.Insert):
|
|
256
270
|
msg = "Cannot set INSERT source on a non-INSERT expression."
|
|
257
271
|
raise SQLBuilderError(msg)
|
|
258
272
|
subquery_parameters = select_builder._parameters
|
|
@@ -261,7 +275,7 @@ class InsertFromSelectMixin:
|
|
|
261
275
|
self.add_parameter(p_value, name=p_name)
|
|
262
276
|
select_expr = select_builder._expression
|
|
263
277
|
if select_expr and isinstance(select_expr, exp.Select):
|
|
264
|
-
|
|
278
|
+
current_expr.set("expression", select_expr.copy())
|
|
265
279
|
else:
|
|
266
280
|
msg = "SelectBuilder must have a valid SELECT expression."
|
|
267
281
|
raise SQLBuilderError(msg)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# pyright: reportPrivateUsage=false
|
|
1
2
|
"""MERGE operation mixins.
|
|
2
3
|
|
|
3
4
|
Provides mixins for MERGE statement functionality including INTO,
|
|
@@ -28,7 +29,10 @@ class MergeIntoClauseMixin:
|
|
|
28
29
|
"""Mixin providing INTO clause for MERGE builders."""
|
|
29
30
|
|
|
30
31
|
__slots__ = ()
|
|
31
|
-
|
|
32
|
+
|
|
33
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
34
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
35
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
32
36
|
|
|
33
37
|
def into(self, table: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
|
|
34
38
|
"""Set the target table for the MERGE operation (INTO clause).
|
|
@@ -41,11 +45,14 @@ class MergeIntoClauseMixin:
|
|
|
41
45
|
Returns:
|
|
42
46
|
The current builder instance for method chaining.
|
|
43
47
|
"""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
current_expr = self.get_expression()
|
|
49
|
+
if current_expr is None or not isinstance(current_expr, exp.Merge):
|
|
50
|
+
self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
|
|
51
|
+
current_expr = self.get_expression()
|
|
52
|
+
|
|
53
|
+
# Type guard: current_expr is now guaranteed to be an Expression
|
|
54
|
+
assert current_expr is not None
|
|
55
|
+
current_expr.set("this", exp.to_table(table, alias=alias) if isinstance(table, str) else table)
|
|
49
56
|
return self
|
|
50
57
|
|
|
51
58
|
|
|
@@ -54,7 +61,10 @@ class MergeUsingClauseMixin:
|
|
|
54
61
|
"""Mixin providing USING clause for MERGE builders."""
|
|
55
62
|
|
|
56
63
|
__slots__ = ()
|
|
57
|
-
|
|
64
|
+
|
|
65
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
66
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
67
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
58
68
|
|
|
59
69
|
def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
|
|
60
70
|
"""Add parameter - provided by QueryBuilder."""
|
|
@@ -75,11 +85,13 @@ class MergeUsingClauseMixin:
|
|
|
75
85
|
Raises:
|
|
76
86
|
SQLBuilderError: If the current expression is not a MERGE statement or if the source type is unsupported.
|
|
77
87
|
"""
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
current_expr = self.get_expression()
|
|
89
|
+
if current_expr is None or not isinstance(current_expr, exp.Merge):
|
|
90
|
+
self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
|
|
91
|
+
current_expr = self.get_expression()
|
|
82
92
|
|
|
93
|
+
# Type guard: current_expr is now guaranteed to be an Expression
|
|
94
|
+
assert current_expr is not None
|
|
83
95
|
source_expr: exp.Expression
|
|
84
96
|
if isinstance(source, str):
|
|
85
97
|
source_expr = exp.to_table(source, alias=alias)
|
|
@@ -99,7 +111,7 @@ class MergeUsingClauseMixin:
|
|
|
99
111
|
msg = f"Unsupported source type for USING clause: {type(source)}"
|
|
100
112
|
raise SQLBuilderError(msg)
|
|
101
113
|
|
|
102
|
-
|
|
114
|
+
current_expr.set("using", source_expr)
|
|
103
115
|
return self
|
|
104
116
|
|
|
105
117
|
|
|
@@ -108,7 +120,10 @@ class MergeOnClauseMixin:
|
|
|
108
120
|
"""Mixin providing ON clause for MERGE builders."""
|
|
109
121
|
|
|
110
122
|
__slots__ = ()
|
|
111
|
-
|
|
123
|
+
|
|
124
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
125
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
126
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
112
127
|
|
|
113
128
|
def on(self, condition: Union[str, exp.Expression]) -> Self:
|
|
114
129
|
"""Set the join condition for the MERGE operation (ON clause).
|
|
@@ -123,11 +138,13 @@ class MergeOnClauseMixin:
|
|
|
123
138
|
Raises:
|
|
124
139
|
SQLBuilderError: If the current expression is not a MERGE statement or if the condition type is unsupported.
|
|
125
140
|
"""
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
141
|
+
current_expr = self.get_expression()
|
|
142
|
+
if current_expr is None or not isinstance(current_expr, exp.Merge):
|
|
143
|
+
self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
|
|
144
|
+
current_expr = self.get_expression()
|
|
130
145
|
|
|
146
|
+
# Type guard: current_expr is now guaranteed to be an Expression
|
|
147
|
+
assert current_expr is not None
|
|
131
148
|
condition_expr: exp.Expression
|
|
132
149
|
if isinstance(condition, str):
|
|
133
150
|
parsed_condition: Optional[exp.Expression] = exp.maybe_parse(
|
|
@@ -143,7 +160,7 @@ class MergeOnClauseMixin:
|
|
|
143
160
|
msg = f"Unsupported condition type for ON clause: {type(condition)}"
|
|
144
161
|
raise SQLBuilderError(msg)
|
|
145
162
|
|
|
146
|
-
|
|
163
|
+
current_expr.set("on", condition_expr)
|
|
147
164
|
return self
|
|
148
165
|
|
|
149
166
|
|
|
@@ -152,7 +169,10 @@ class MergeMatchedClauseMixin:
|
|
|
152
169
|
"""Mixin providing WHEN MATCHED THEN ... clauses for MERGE builders."""
|
|
153
170
|
|
|
154
171
|
__slots__ = ()
|
|
155
|
-
|
|
172
|
+
|
|
173
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
174
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
175
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
156
176
|
|
|
157
177
|
def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
|
|
158
178
|
"""Add parameter - provided by QueryBuilder."""
|
|
@@ -170,15 +190,17 @@ class MergeMatchedClauseMixin:
|
|
|
170
190
|
Args:
|
|
171
191
|
when_clause: The WHEN clause to add.
|
|
172
192
|
"""
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
193
|
+
current_expr = self.get_expression()
|
|
194
|
+
if current_expr is None or not isinstance(current_expr, exp.Merge):
|
|
195
|
+
self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
|
|
196
|
+
current_expr = self.get_expression()
|
|
197
|
+
|
|
198
|
+
# Type guard: current_expr is now guaranteed to be an Expression
|
|
199
|
+
assert current_expr is not None
|
|
200
|
+
whens = current_expr.args.get("whens")
|
|
179
201
|
if not whens:
|
|
180
202
|
whens = exp.Whens(expressions=[])
|
|
181
|
-
|
|
203
|
+
current_expr.set("whens", whens)
|
|
182
204
|
|
|
183
205
|
whens.append("expressions", when_clause)
|
|
184
206
|
|
|
@@ -315,7 +337,9 @@ class MergeNotMatchedClauseMixin:
|
|
|
315
337
|
|
|
316
338
|
__slots__ = ()
|
|
317
339
|
|
|
318
|
-
|
|
340
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
341
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
342
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
319
343
|
|
|
320
344
|
def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
|
|
321
345
|
"""Add parameter - provided by QueryBuilder."""
|
|
@@ -415,7 +439,9 @@ class MergeNotMatchedBySourceClauseMixin:
|
|
|
415
439
|
|
|
416
440
|
__slots__ = ()
|
|
417
441
|
|
|
418
|
-
|
|
442
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
443
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
444
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
419
445
|
|
|
420
446
|
def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
|
|
421
447
|
"""Add parameter - provided by QueryBuilder."""
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# pyright: reportPrivateUsage=false
|
|
1
2
|
"""SELECT clause mixins.
|
|
2
3
|
|
|
3
4
|
Provides mixins for SELECT statement functionality including column selection,
|
|
@@ -28,8 +29,9 @@ class SelectClauseMixin:
|
|
|
28
29
|
|
|
29
30
|
__slots__ = ()
|
|
30
31
|
|
|
31
|
-
# Type
|
|
32
|
-
|
|
32
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
33
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
34
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
33
35
|
|
|
34
36
|
def select(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn", "SQL", "Case"]) -> Self:
|
|
35
37
|
"""Add columns to SELECT clause.
|
|
@@ -41,13 +43,17 @@ class SelectClauseMixin:
|
|
|
41
43
|
The current builder instance for method chaining.
|
|
42
44
|
"""
|
|
43
45
|
builder = cast("SQLBuilderProtocol", self)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
current_expr = self.get_expression()
|
|
47
|
+
if current_expr is None:
|
|
48
|
+
self.set_expression(exp.Select())
|
|
49
|
+
current_expr = self.get_expression()
|
|
50
|
+
|
|
51
|
+
if not isinstance(current_expr, exp.Select):
|
|
47
52
|
msg = "Cannot add select columns to a non-SELECT expression."
|
|
48
53
|
raise SQLBuilderError(msg)
|
|
49
54
|
for column in columns:
|
|
50
|
-
|
|
55
|
+
current_expr = current_expr.select(parse_column_expression(column, builder), copy=False)
|
|
56
|
+
self.set_expression(current_expr)
|
|
51
57
|
return cast("Self", builder)
|
|
52
58
|
|
|
53
59
|
def distinct(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn", "SQL"]) -> Self:
|
|
@@ -129,13 +135,13 @@ class SelectClauseMixin:
|
|
|
129
135
|
Returns:
|
|
130
136
|
The current builder instance for method chaining.
|
|
131
137
|
"""
|
|
132
|
-
|
|
138
|
+
current_expr = self.get_expression()
|
|
139
|
+
if current_expr is None or not isinstance(current_expr, exp.Select):
|
|
133
140
|
return self
|
|
134
141
|
|
|
135
142
|
for column in columns:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
)
|
|
143
|
+
current_expr = current_expr.group_by(exp.column(column) if isinstance(column, str) else column, copy=False)
|
|
144
|
+
self.set_expression(current_expr)
|
|
139
145
|
return self
|
|
140
146
|
|
|
141
147
|
def group_by_rollup(self, *columns: Union[str, exp.Expression]) -> Self:
|
|
@@ -480,9 +486,12 @@ class SelectClauseMixin:
|
|
|
480
486
|
Returns:
|
|
481
487
|
The current builder instance for method chaining.
|
|
482
488
|
"""
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
489
|
+
current_expr = self.get_expression()
|
|
490
|
+
if current_expr is None:
|
|
491
|
+
self.set_expression(exp.Select())
|
|
492
|
+
current_expr = self.get_expression()
|
|
493
|
+
|
|
494
|
+
if not isinstance(current_expr, exp.Select):
|
|
486
495
|
msg = "Cannot add window function to a non-SELECT expression."
|
|
487
496
|
raise SQLBuilderError(msg)
|
|
488
497
|
|
|
@@ -525,7 +534,8 @@ class SelectClauseMixin:
|
|
|
525
534
|
over_args["frame"] = frame_expr
|
|
526
535
|
|
|
527
536
|
window_expr = exp.Window(this=func_expr_parsed, **over_args)
|
|
528
|
-
|
|
537
|
+
current_expr = current_expr.select(exp.alias_(window_expr, alias) if alias else window_expr, copy=False)
|
|
538
|
+
self.set_expression(current_expr)
|
|
529
539
|
return self
|
|
530
540
|
|
|
531
541
|
def case_(self, alias: "Optional[str]" = None) -> "CaseBuilder":
|
|
@@ -906,3 +916,21 @@ class Case:
|
|
|
906
916
|
"""
|
|
907
917
|
case_expr = exp.Case(ifs=self._conditions, default=self._default)
|
|
908
918
|
return cast("exp.Alias", exp.alias_(case_expr, alias))
|
|
919
|
+
|
|
920
|
+
@property
|
|
921
|
+
def conditions(self) -> "list[exp.If]":
|
|
922
|
+
"""Get CASE conditions (public API).
|
|
923
|
+
|
|
924
|
+
Returns:
|
|
925
|
+
List of If expressions representing WHEN clauses
|
|
926
|
+
"""
|
|
927
|
+
return self._conditions
|
|
928
|
+
|
|
929
|
+
@property
|
|
930
|
+
def default(self) -> Optional[exp.Expression]:
|
|
931
|
+
"""Get CASE default value (public API).
|
|
932
|
+
|
|
933
|
+
Returns:
|
|
934
|
+
Default expression for the ELSE clause, or None
|
|
935
|
+
"""
|
|
936
|
+
return self._default
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# pyright: reportPrivateUsage=false
|
|
1
2
|
"""UPDATE operation mixins.
|
|
2
3
|
|
|
3
4
|
Provides mixins for UPDATE statement functionality including
|
|
@@ -25,8 +26,9 @@ class UpdateTableClauseMixin:
|
|
|
25
26
|
|
|
26
27
|
__slots__ = ()
|
|
27
28
|
|
|
28
|
-
# Type
|
|
29
|
-
|
|
29
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
30
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
31
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
30
32
|
|
|
31
33
|
def table(self, table_name: str, alias: Optional[str] = None) -> Self:
|
|
32
34
|
"""Set the table to update.
|
|
@@ -38,10 +40,14 @@ class UpdateTableClauseMixin:
|
|
|
38
40
|
Returns:
|
|
39
41
|
The current builder instance for method chaining.
|
|
40
42
|
"""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
current_expr = self.get_expression()
|
|
44
|
+
if current_expr is None or not isinstance(current_expr, exp.Update):
|
|
45
|
+
self.set_expression(exp.Update(this=None, expressions=[], joins=[]))
|
|
46
|
+
current_expr = self.get_expression()
|
|
47
|
+
|
|
48
|
+
assert current_expr is not None
|
|
43
49
|
table_expr: exp.Expression = exp.to_table(table_name, alias=alias)
|
|
44
|
-
|
|
50
|
+
current_expr.set("this", table_expr)
|
|
45
51
|
setattr(self, "_table", table_name)
|
|
46
52
|
return self
|
|
47
53
|
|
|
@@ -52,8 +58,9 @@ class UpdateSetClauseMixin:
|
|
|
52
58
|
|
|
53
59
|
__slots__ = ()
|
|
54
60
|
|
|
55
|
-
# Type
|
|
56
|
-
|
|
61
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
62
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
63
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
57
64
|
|
|
58
65
|
def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
|
|
59
66
|
"""Add parameter - provided by QueryBuilder."""
|
|
@@ -130,9 +137,12 @@ class UpdateSetClauseMixin:
|
|
|
130
137
|
Returns:
|
|
131
138
|
The current builder instance for method chaining.
|
|
132
139
|
"""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
140
|
+
current_expr = self.get_expression()
|
|
141
|
+
if current_expr is None:
|
|
142
|
+
self.set_expression(exp.Update())
|
|
143
|
+
current_expr = self.get_expression()
|
|
144
|
+
|
|
145
|
+
if not isinstance(current_expr, exp.Update):
|
|
136
146
|
msg = "Cannot add SET clause to non-UPDATE expression."
|
|
137
147
|
raise SQLBuilderError(msg)
|
|
138
148
|
assignments = []
|
|
@@ -149,8 +159,8 @@ class UpdateSetClauseMixin:
|
|
|
149
159
|
else:
|
|
150
160
|
msg = "Invalid arguments for set(): use (column, value), mapping, or kwargs."
|
|
151
161
|
raise SQLBuilderError(msg)
|
|
152
|
-
existing =
|
|
153
|
-
|
|
162
|
+
existing = current_expr.args.get("expressions", [])
|
|
163
|
+
current_expr.set("expressions", existing + assignments)
|
|
154
164
|
return self
|
|
155
165
|
|
|
156
166
|
|
|
@@ -160,8 +170,9 @@ class UpdateFromClauseMixin:
|
|
|
160
170
|
|
|
161
171
|
__slots__ = ()
|
|
162
172
|
|
|
163
|
-
# Type
|
|
164
|
-
|
|
173
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
174
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
175
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
165
176
|
|
|
166
177
|
def from_(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
|
|
167
178
|
"""Add a FROM clause to the UPDATE statement.
|
|
@@ -176,7 +187,8 @@ class UpdateFromClauseMixin:
|
|
|
176
187
|
Raises:
|
|
177
188
|
SQLBuilderError: If the current expression is not an UPDATE statement.
|
|
178
189
|
"""
|
|
179
|
-
|
|
190
|
+
current_expr = self.get_expression()
|
|
191
|
+
if current_expr is None or not isinstance(current_expr, exp.Update):
|
|
180
192
|
msg = "Cannot add FROM clause to non-UPDATE expression. Set the main table first."
|
|
181
193
|
raise SQLBuilderError(msg)
|
|
182
194
|
table_expr: exp.Expression
|
|
@@ -194,9 +206,9 @@ class UpdateFromClauseMixin:
|
|
|
194
206
|
else:
|
|
195
207
|
msg = f"Unsupported table type for FROM clause: {type(table)}"
|
|
196
208
|
raise SQLBuilderError(msg)
|
|
197
|
-
if
|
|
198
|
-
|
|
199
|
-
from_clause =
|
|
209
|
+
if current_expr.args.get("from") is None:
|
|
210
|
+
current_expr.set("from", exp.From(expressions=[]))
|
|
211
|
+
from_clause = current_expr.args["from"]
|
|
200
212
|
if hasattr(from_clause, "append"):
|
|
201
213
|
from_clause.append("expressions", table_expr)
|
|
202
214
|
else:
|