sqlspec 0.16.0__py3-none-any.whl → 0.16.1__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 +448 -15
- sqlspec/builder/_base.py +77 -44
- sqlspec/builder/_column.py +0 -4
- sqlspec/builder/_ddl.py +15 -52
- sqlspec/builder/_ddl_utils.py +0 -1
- sqlspec/builder/_delete.py +4 -5
- sqlspec/builder/_insert.py +59 -44
- sqlspec/builder/_merge.py +17 -2
- sqlspec/builder/_parsing_utils.py +11 -11
- sqlspec/builder/_select.py +29 -33
- sqlspec/builder/_update.py +4 -2
- sqlspec/builder/mixins/_cte_and_set_ops.py +47 -20
- sqlspec/builder/mixins/_delete_operations.py +6 -1
- sqlspec/builder/mixins/_insert_operations.py +126 -34
- sqlspec/builder/mixins/_join_operations.py +11 -4
- sqlspec/builder/mixins/_merge_operations.py +81 -21
- sqlspec/builder/mixins/_order_limit_operations.py +15 -3
- sqlspec/builder/mixins/_pivot_operations.py +11 -2
- sqlspec/builder/mixins/_select_operations.py +12 -8
- sqlspec/builder/mixins/_update_operations.py +37 -14
- sqlspec/builder/mixins/_where_clause.py +55 -43
- sqlspec/core/cache.py +26 -28
- sqlspec/core/compiler.py +58 -37
- sqlspec/core/parameters.py +80 -52
- sqlspec/core/result.py +30 -17
- sqlspec/core/statement.py +31 -21
- sqlspec/driver/_async.py +76 -46
- sqlspec/driver/_common.py +25 -6
- sqlspec/driver/_sync.py +73 -43
- sqlspec/driver/mixins/_result_tools.py +51 -22
- sqlspec/driver/mixins/_sql_translator.py +61 -11
- sqlspec/protocols.py +7 -0
- sqlspec/utils/type_guards.py +7 -3
- {sqlspec-0.16.0.dist-info → sqlspec-0.16.1.dist-info}/METADATA +1 -1
- {sqlspec-0.16.0.dist-info → sqlspec-0.16.1.dist-info}/RECORD +39 -39
- {sqlspec-0.16.0.dist-info → sqlspec-0.16.1.dist-info}/WHEEL +0 -0
- {sqlspec-0.16.0.dist-info → sqlspec-0.16.1.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.16.0.dist-info → sqlspec-0.16.1.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.16.0.dist-info → sqlspec-0.16.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
"""Insert operation mixins for SQL builders."""
|
|
2
2
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
|
-
from typing import Any, Optional, Union
|
|
4
|
+
from typing import Any, Optional, TypeVar, Union
|
|
5
5
|
|
|
6
|
+
from mypy_extensions import trait
|
|
6
7
|
from sqlglot import exp
|
|
7
8
|
from typing_extensions import Self
|
|
8
9
|
|
|
9
10
|
from sqlspec.exceptions import SQLBuilderError
|
|
11
|
+
from sqlspec.protocols import SQLBuilderProtocol
|
|
12
|
+
|
|
13
|
+
BuilderT = TypeVar("BuilderT", bound=SQLBuilderProtocol)
|
|
10
14
|
|
|
11
15
|
__all__ = ("InsertFromSelectMixin", "InsertIntoClauseMixin", "InsertValuesMixin")
|
|
12
16
|
|
|
13
17
|
|
|
18
|
+
@trait
|
|
14
19
|
class InsertIntoClauseMixin:
|
|
15
20
|
"""Mixin providing INTO clause for INSERT builders."""
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
__slots__ = ()
|
|
23
|
+
|
|
24
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
25
|
+
_expression: Optional[exp.Expression]
|
|
18
26
|
|
|
19
27
|
def into(self, table: str) -> Self:
|
|
20
28
|
"""Set the target table for the INSERT statement.
|
|
@@ -39,10 +47,26 @@ class InsertIntoClauseMixin:
|
|
|
39
47
|
return self
|
|
40
48
|
|
|
41
49
|
|
|
50
|
+
@trait
|
|
42
51
|
class InsertValuesMixin:
|
|
43
52
|
"""Mixin providing VALUES and columns methods for INSERT builders."""
|
|
44
53
|
|
|
45
|
-
|
|
54
|
+
__slots__ = ()
|
|
55
|
+
|
|
56
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
57
|
+
_expression: Optional[exp.Expression]
|
|
58
|
+
|
|
59
|
+
_columns: Any # Provided by QueryBuilder
|
|
60
|
+
|
|
61
|
+
def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
|
|
62
|
+
"""Add parameter - provided by QueryBuilder."""
|
|
63
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
64
|
+
raise NotImplementedError(msg)
|
|
65
|
+
|
|
66
|
+
def _generate_unique_parameter_name(self, base_name: str) -> str:
|
|
67
|
+
"""Generate unique parameter name - provided by QueryBuilder."""
|
|
68
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
69
|
+
raise NotImplementedError(msg)
|
|
46
70
|
|
|
47
71
|
def columns(self, *columns: Union[str, exp.Expression]) -> Self:
|
|
48
72
|
"""Set the columns for the INSERT statement and synchronize the _columns attribute on the builder."""
|
|
@@ -54,7 +78,7 @@ class InsertValuesMixin:
|
|
|
54
78
|
column_exprs = [exp.column(col) if isinstance(col, str) else col for col in columns]
|
|
55
79
|
self._expression.set("columns", column_exprs)
|
|
56
80
|
try:
|
|
57
|
-
cols = self._columns
|
|
81
|
+
cols = self._columns
|
|
58
82
|
if not columns:
|
|
59
83
|
cols.clear()
|
|
60
84
|
else:
|
|
@@ -63,37 +87,94 @@ class InsertValuesMixin:
|
|
|
63
87
|
pass
|
|
64
88
|
return self
|
|
65
89
|
|
|
66
|
-
def values(self, *values: Any) -> Self:
|
|
67
|
-
"""Add a row of values to the INSERT statement
|
|
90
|
+
def values(self, *values: Any, **kwargs: Any) -> Self:
|
|
91
|
+
"""Add a row of values to the INSERT statement.
|
|
92
|
+
|
|
93
|
+
Supports:
|
|
94
|
+
- values(val1, val2, val3)
|
|
95
|
+
- values(col1=val1, col2=val2)
|
|
96
|
+
- values(mapping)
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
*values: Either positional values or a single mapping.
|
|
100
|
+
**kwargs: Column-value pairs.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
The current builder instance for method chaining.
|
|
104
|
+
"""
|
|
68
105
|
if self._expression is None:
|
|
69
106
|
self._expression = exp.Insert()
|
|
70
107
|
if not isinstance(self._expression, exp.Insert):
|
|
71
108
|
msg = "Cannot add values to a non-INSERT expression."
|
|
72
109
|
raise SQLBuilderError(msg)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if
|
|
76
|
-
msg =
|
|
110
|
+
|
|
111
|
+
if kwargs:
|
|
112
|
+
if values:
|
|
113
|
+
msg = "Cannot mix positional values with keyword values."
|
|
77
114
|
raise SQLBuilderError(msg)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
115
|
+
try:
|
|
116
|
+
_columns = self._columns
|
|
117
|
+
if not _columns:
|
|
118
|
+
self.columns(*kwargs.keys())
|
|
119
|
+
except AttributeError:
|
|
120
|
+
pass
|
|
121
|
+
row_exprs = []
|
|
122
|
+
for col, val in kwargs.items():
|
|
123
|
+
if isinstance(val, exp.Expression):
|
|
124
|
+
row_exprs.append(val)
|
|
125
|
+
else:
|
|
126
|
+
column_name = col if isinstance(col, str) else str(col)
|
|
127
|
+
if "." in column_name:
|
|
128
|
+
column_name = column_name.split(".")[-1]
|
|
129
|
+
param_name = self._generate_unique_parameter_name(column_name)
|
|
130
|
+
_, param_name = self.add_parameter(val, name=param_name)
|
|
131
|
+
row_exprs.append(exp.var(param_name))
|
|
132
|
+
elif len(values) == 1 and hasattr(values[0], "items"):
|
|
133
|
+
mapping = values[0]
|
|
134
|
+
try:
|
|
135
|
+
_columns = self._columns
|
|
136
|
+
if not _columns:
|
|
137
|
+
self.columns(*mapping.keys())
|
|
138
|
+
except AttributeError:
|
|
139
|
+
pass
|
|
140
|
+
row_exprs = []
|
|
141
|
+
for col, val in mapping.items():
|
|
142
|
+
if isinstance(val, exp.Expression):
|
|
143
|
+
row_exprs.append(val)
|
|
144
|
+
else:
|
|
145
|
+
column_name = col if isinstance(col, str) else str(col)
|
|
146
|
+
if "." in column_name:
|
|
147
|
+
column_name = column_name.split(".")[-1]
|
|
148
|
+
param_name = self._generate_unique_parameter_name(column_name)
|
|
149
|
+
_, param_name = self.add_parameter(val, name=param_name)
|
|
150
|
+
row_exprs.append(exp.var(param_name))
|
|
151
|
+
else:
|
|
152
|
+
try:
|
|
153
|
+
_columns = self._columns
|
|
154
|
+
if _columns and len(values) != len(_columns):
|
|
155
|
+
msg = f"Number of values ({len(values)}) does not match the number of specified columns ({len(_columns)})."
|
|
156
|
+
raise SQLBuilderError(msg)
|
|
157
|
+
except AttributeError:
|
|
158
|
+
pass
|
|
159
|
+
row_exprs = []
|
|
160
|
+
for i, v in enumerate(values):
|
|
161
|
+
if isinstance(v, exp.Expression):
|
|
162
|
+
row_exprs.append(v)
|
|
163
|
+
else:
|
|
164
|
+
try:
|
|
165
|
+
_columns = self._columns
|
|
166
|
+
if _columns and i < len(_columns):
|
|
167
|
+
column_name = (
|
|
168
|
+
str(_columns[i]).split(".")[-1] if "." in str(_columns[i]) else str(_columns[i])
|
|
169
|
+
)
|
|
170
|
+
param_name = self._generate_unique_parameter_name(column_name)
|
|
171
|
+
else:
|
|
172
|
+
param_name = self._generate_unique_parameter_name(f"value_{i + 1}")
|
|
173
|
+
except AttributeError:
|
|
174
|
+
param_name = self._generate_unique_parameter_name(f"value_{i + 1}")
|
|
175
|
+
_, param_name = self.add_parameter(v, name=param_name)
|
|
176
|
+
row_exprs.append(exp.var(param_name))
|
|
177
|
+
|
|
97
178
|
values_expr = exp.Values(expressions=[row_exprs])
|
|
98
179
|
self._expression.set("expression", values_expr)
|
|
99
180
|
return self
|
|
@@ -110,10 +191,21 @@ class InsertValuesMixin:
|
|
|
110
191
|
return self.values(*values)
|
|
111
192
|
|
|
112
193
|
|
|
194
|
+
@trait
|
|
113
195
|
class InsertFromSelectMixin:
|
|
114
196
|
"""Mixin providing INSERT ... SELECT support for INSERT builders."""
|
|
115
197
|
|
|
116
|
-
|
|
198
|
+
__slots__ = ()
|
|
199
|
+
|
|
200
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
201
|
+
_expression: Optional[exp.Expression]
|
|
202
|
+
|
|
203
|
+
_table: Any # Provided by QueryBuilder
|
|
204
|
+
|
|
205
|
+
def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
|
|
206
|
+
"""Add parameter - provided by QueryBuilder."""
|
|
207
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
208
|
+
raise NotImplementedError(msg)
|
|
117
209
|
|
|
118
210
|
def from_select(self, select_builder: Any) -> Self:
|
|
119
211
|
"""Sets the INSERT source to a SELECT statement.
|
|
@@ -128,7 +220,7 @@ class InsertFromSelectMixin:
|
|
|
128
220
|
SQLBuilderError: If the table is not set or the select_builder is invalid.
|
|
129
221
|
"""
|
|
130
222
|
try:
|
|
131
|
-
if not self._table:
|
|
223
|
+
if not self._table:
|
|
132
224
|
msg = "The target table must be set using .into() before adding values."
|
|
133
225
|
raise SQLBuilderError(msg)
|
|
134
226
|
except AttributeError:
|
|
@@ -139,11 +231,11 @@ class InsertFromSelectMixin:
|
|
|
139
231
|
if not isinstance(self._expression, exp.Insert):
|
|
140
232
|
msg = "Cannot set INSERT source on a non-INSERT expression."
|
|
141
233
|
raise SQLBuilderError(msg)
|
|
142
|
-
subquery_parameters = select_builder._parameters
|
|
234
|
+
subquery_parameters = select_builder._parameters
|
|
143
235
|
if subquery_parameters:
|
|
144
236
|
for p_name, p_value in subquery_parameters.items():
|
|
145
|
-
self.add_parameter(p_value, name=p_name)
|
|
146
|
-
select_expr = select_builder._expression
|
|
237
|
+
self.add_parameter(p_value, name=p_name)
|
|
238
|
+
select_expr = select_builder._expression
|
|
147
239
|
if select_expr and isinstance(select_expr, exp.Select):
|
|
148
240
|
self._expression.set("expression", select_expr.copy())
|
|
149
241
|
else:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
2
2
|
|
|
3
|
+
from mypy_extensions import trait
|
|
3
4
|
from sqlglot import exp
|
|
4
5
|
from typing_extensions import Self
|
|
5
6
|
|
|
@@ -13,9 +14,15 @@ if TYPE_CHECKING:
|
|
|
13
14
|
__all__ = ("JoinClauseMixin",)
|
|
14
15
|
|
|
15
16
|
|
|
17
|
+
@trait
|
|
16
18
|
class JoinClauseMixin:
|
|
17
19
|
"""Mixin providing JOIN clause methods for SELECT builders."""
|
|
18
20
|
|
|
21
|
+
__slots__ = ()
|
|
22
|
+
|
|
23
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
24
|
+
_expression: Optional[exp.Expression]
|
|
25
|
+
|
|
19
26
|
def join(
|
|
20
27
|
self,
|
|
21
28
|
table: Union[str, exp.Expression, Any],
|
|
@@ -36,12 +43,12 @@ class JoinClauseMixin:
|
|
|
36
43
|
if hasattr(table, "_expression") and getattr(table, "_expression", None) is not None:
|
|
37
44
|
table_expr_value = getattr(table, "_expression", None)
|
|
38
45
|
if table_expr_value is not None:
|
|
39
|
-
subquery_exp = exp.paren(table_expr_value
|
|
46
|
+
subquery_exp = exp.paren(table_expr_value)
|
|
40
47
|
else:
|
|
41
48
|
subquery_exp = exp.paren(exp.Anonymous(this=""))
|
|
42
49
|
table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
43
50
|
else:
|
|
44
|
-
subquery = table.build()
|
|
51
|
+
subquery = table.build()
|
|
45
52
|
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
46
53
|
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect", None)))
|
|
47
54
|
table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
@@ -99,12 +106,12 @@ class JoinClauseMixin:
|
|
|
99
106
|
if hasattr(table, "_expression") and getattr(table, "_expression", None) is not None:
|
|
100
107
|
table_expr_value = getattr(table, "_expression", None)
|
|
101
108
|
if table_expr_value is not None:
|
|
102
|
-
subquery_exp = exp.paren(table_expr_value
|
|
109
|
+
subquery_exp = exp.paren(table_expr_value)
|
|
103
110
|
else:
|
|
104
111
|
subquery_exp = exp.paren(exp.Anonymous(this=""))
|
|
105
112
|
table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
106
113
|
else:
|
|
107
|
-
subquery = table.build()
|
|
114
|
+
subquery = table.build()
|
|
108
115
|
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
109
116
|
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect", None)))
|
|
110
117
|
table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, Optional, Union
|
|
4
4
|
|
|
5
|
+
from mypy_extensions import trait
|
|
5
6
|
from sqlglot import exp
|
|
6
7
|
from typing_extensions import Self
|
|
7
8
|
|
|
@@ -18,10 +19,12 @@ __all__ = (
|
|
|
18
19
|
)
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
@trait
|
|
21
23
|
class MergeIntoClauseMixin:
|
|
22
24
|
"""Mixin providing INTO clause for MERGE builders."""
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
__slots__ = ()
|
|
27
|
+
_expression: Optional[exp.Expression]
|
|
25
28
|
|
|
26
29
|
def into(self, table: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
|
|
27
30
|
"""Set the target table for the MERGE operation (INTO clause).
|
|
@@ -35,17 +38,24 @@ class MergeIntoClauseMixin:
|
|
|
35
38
|
The current builder instance for method chaining.
|
|
36
39
|
"""
|
|
37
40
|
if self._expression is None:
|
|
38
|
-
self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
|
|
39
|
-
if not isinstance(self._expression, exp.Merge):
|
|
40
|
-
self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
|
|
41
|
+
self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
|
|
42
|
+
if not isinstance(self._expression, exp.Merge):
|
|
43
|
+
self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
|
|
41
44
|
self._expression.set("this", exp.to_table(table, alias=alias) if isinstance(table, str) else table)
|
|
42
45
|
return self
|
|
43
46
|
|
|
44
47
|
|
|
48
|
+
@trait
|
|
45
49
|
class MergeUsingClauseMixin:
|
|
46
50
|
"""Mixin providing USING clause for MERGE builders."""
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
__slots__ = ()
|
|
53
|
+
_expression: Optional[exp.Expression]
|
|
54
|
+
|
|
55
|
+
def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
|
|
56
|
+
"""Add parameter - provided by QueryBuilder."""
|
|
57
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
58
|
+
raise NotImplementedError(msg)
|
|
49
59
|
|
|
50
60
|
def using(self, source: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
|
|
51
61
|
"""Set the source data for the MERGE operation (USING clause).
|
|
@@ -73,7 +83,7 @@ class MergeUsingClauseMixin:
|
|
|
73
83
|
subquery_builder_parameters = source.parameters
|
|
74
84
|
if subquery_builder_parameters:
|
|
75
85
|
for p_name, p_value in subquery_builder_parameters.items():
|
|
76
|
-
self.add_parameter(p_value, name=p_name)
|
|
86
|
+
self.add_parameter(p_value, name=p_name)
|
|
77
87
|
|
|
78
88
|
subquery_exp = exp.paren(getattr(source, "_expression", exp.select()))
|
|
79
89
|
source_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
@@ -89,10 +99,12 @@ class MergeUsingClauseMixin:
|
|
|
89
99
|
return self
|
|
90
100
|
|
|
91
101
|
|
|
102
|
+
@trait
|
|
92
103
|
class MergeOnClauseMixin:
|
|
93
104
|
"""Mixin providing ON clause for MERGE builders."""
|
|
94
105
|
|
|
95
|
-
|
|
106
|
+
__slots__ = ()
|
|
107
|
+
_expression: Optional[exp.Expression]
|
|
96
108
|
|
|
97
109
|
def on(self, condition: Union[str, exp.Expression]) -> Self:
|
|
98
110
|
"""Set the join condition for the MERGE operation (ON clause).
|
|
@@ -131,10 +143,22 @@ class MergeOnClauseMixin:
|
|
|
131
143
|
return self
|
|
132
144
|
|
|
133
145
|
|
|
146
|
+
@trait
|
|
134
147
|
class MergeMatchedClauseMixin:
|
|
135
148
|
"""Mixin providing WHEN MATCHED THEN ... clauses for MERGE builders."""
|
|
136
149
|
|
|
137
|
-
|
|
150
|
+
__slots__ = ()
|
|
151
|
+
_expression: Optional[exp.Expression]
|
|
152
|
+
|
|
153
|
+
def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
|
|
154
|
+
"""Add parameter - provided by QueryBuilder."""
|
|
155
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
156
|
+
raise NotImplementedError(msg)
|
|
157
|
+
|
|
158
|
+
def _generate_unique_parameter_name(self, base_name: str) -> str:
|
|
159
|
+
"""Generate unique parameter name - provided by QueryBuilder."""
|
|
160
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
161
|
+
raise NotImplementedError(msg)
|
|
138
162
|
|
|
139
163
|
def _add_when_clause(self, when_clause: exp.When) -> None:
|
|
140
164
|
"""Helper to add a WHEN clause to the MERGE statement.
|
|
@@ -143,9 +167,9 @@ class MergeMatchedClauseMixin:
|
|
|
143
167
|
when_clause: The WHEN clause to add.
|
|
144
168
|
"""
|
|
145
169
|
if self._expression is None:
|
|
146
|
-
self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
|
|
170
|
+
self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])) # type: ignore[misc]
|
|
147
171
|
if not isinstance(self._expression, exp.Merge):
|
|
148
|
-
self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
|
|
172
|
+
self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])) # type: ignore[misc]
|
|
149
173
|
|
|
150
174
|
whens = self._expression.args.get("whens")
|
|
151
175
|
if not whens:
|
|
@@ -175,8 +199,8 @@ class MergeMatchedClauseMixin:
|
|
|
175
199
|
column_name = col if isinstance(col, str) else str(col)
|
|
176
200
|
if "." in column_name:
|
|
177
201
|
column_name = column_name.split(".")[-1]
|
|
178
|
-
param_name = self._generate_unique_parameter_name(column_name)
|
|
179
|
-
param_name = self.add_parameter(val, name=param_name)[1]
|
|
202
|
+
param_name = self._generate_unique_parameter_name(column_name)
|
|
203
|
+
param_name = self.add_parameter(val, name=param_name)[1]
|
|
180
204
|
update_expressions.append(exp.EQ(this=exp.column(col), expression=exp.var(param_name)))
|
|
181
205
|
|
|
182
206
|
when_args: dict[str, Any] = {"matched": True, "then": exp.Update(expressions=update_expressions)}
|
|
@@ -238,10 +262,28 @@ class MergeMatchedClauseMixin:
|
|
|
238
262
|
return self
|
|
239
263
|
|
|
240
264
|
|
|
265
|
+
@trait
|
|
241
266
|
class MergeNotMatchedClauseMixin:
|
|
242
267
|
"""Mixin providing WHEN NOT MATCHED THEN ... clauses for MERGE builders."""
|
|
243
268
|
|
|
244
|
-
|
|
269
|
+
__slots__ = ()
|
|
270
|
+
|
|
271
|
+
_expression: Optional[exp.Expression]
|
|
272
|
+
|
|
273
|
+
def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
|
|
274
|
+
"""Add parameter - provided by QueryBuilder."""
|
|
275
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
276
|
+
raise NotImplementedError(msg)
|
|
277
|
+
|
|
278
|
+
def _generate_unique_parameter_name(self, base_name: str) -> str:
|
|
279
|
+
"""Generate unique parameter name - provided by QueryBuilder."""
|
|
280
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
281
|
+
raise NotImplementedError(msg)
|
|
282
|
+
|
|
283
|
+
def _add_when_clause(self, when_clause: exp.When) -> None:
|
|
284
|
+
"""Helper to add a WHEN clause to the MERGE statement - provided by QueryBuilder."""
|
|
285
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
286
|
+
raise NotImplementedError(msg)
|
|
245
287
|
|
|
246
288
|
def when_not_matched_then_insert(
|
|
247
289
|
self,
|
|
@@ -278,8 +320,8 @@ class MergeNotMatchedClauseMixin:
|
|
|
278
320
|
column_name = columns[i] if isinstance(columns[i], str) else str(columns[i])
|
|
279
321
|
if "." in column_name:
|
|
280
322
|
column_name = column_name.split(".")[-1]
|
|
281
|
-
param_name = self._generate_unique_parameter_name(column_name)
|
|
282
|
-
param_name = self.add_parameter(val, name=param_name)[1]
|
|
323
|
+
param_name = self._generate_unique_parameter_name(column_name)
|
|
324
|
+
param_name = self.add_parameter(val, name=param_name)[1]
|
|
283
325
|
parameterized_values.append(exp.var(param_name))
|
|
284
326
|
|
|
285
327
|
insert_args["this"] = exp.Tuple(expressions=[exp.column(c) for c in columns])
|
|
@@ -316,14 +358,32 @@ class MergeNotMatchedClauseMixin:
|
|
|
316
358
|
when_args["this"] = condition_expr
|
|
317
359
|
|
|
318
360
|
when_clause = exp.When(**when_args)
|
|
319
|
-
self._add_when_clause(when_clause)
|
|
361
|
+
self._add_when_clause(when_clause)
|
|
320
362
|
return self
|
|
321
363
|
|
|
322
364
|
|
|
365
|
+
@trait
|
|
323
366
|
class MergeNotMatchedBySourceClauseMixin:
|
|
324
367
|
"""Mixin providing WHEN NOT MATCHED BY SOURCE THEN ... clauses for MERGE builders."""
|
|
325
368
|
|
|
326
|
-
|
|
369
|
+
__slots__ = ()
|
|
370
|
+
|
|
371
|
+
_expression: Optional[exp.Expression]
|
|
372
|
+
|
|
373
|
+
def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
|
|
374
|
+
"""Add parameter - provided by QueryBuilder."""
|
|
375
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
376
|
+
raise NotImplementedError(msg)
|
|
377
|
+
|
|
378
|
+
def _generate_unique_parameter_name(self, base_name: str) -> str:
|
|
379
|
+
"""Generate unique parameter name - provided by QueryBuilder."""
|
|
380
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
381
|
+
raise NotImplementedError(msg)
|
|
382
|
+
|
|
383
|
+
def _add_when_clause(self, when_clause: exp.When) -> None:
|
|
384
|
+
"""Helper to add a WHEN clause to the MERGE statement - provided by QueryBuilder."""
|
|
385
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
386
|
+
raise NotImplementedError(msg)
|
|
327
387
|
|
|
328
388
|
def when_not_matched_by_source_then_update(
|
|
329
389
|
self, set_values: dict[str, Any], condition: Optional[Union[str, exp.Expression]] = None
|
|
@@ -347,8 +407,8 @@ class MergeNotMatchedBySourceClauseMixin:
|
|
|
347
407
|
column_name = col if isinstance(col, str) else str(col)
|
|
348
408
|
if "." in column_name:
|
|
349
409
|
column_name = column_name.split(".")[-1]
|
|
350
|
-
param_name = self._generate_unique_parameter_name(column_name)
|
|
351
|
-
param_name = self.add_parameter(val, name=param_name)[1]
|
|
410
|
+
param_name = self._generate_unique_parameter_name(column_name)
|
|
411
|
+
param_name = self.add_parameter(val, name=param_name)[1]
|
|
352
412
|
update_expressions.append(exp.EQ(this=exp.column(col), expression=exp.var(param_name)))
|
|
353
413
|
|
|
354
414
|
when_args: dict[str, Any] = {
|
|
@@ -375,7 +435,7 @@ class MergeNotMatchedBySourceClauseMixin:
|
|
|
375
435
|
when_args["this"] = condition_expr
|
|
376
436
|
|
|
377
437
|
when_clause = exp.When(**when_args)
|
|
378
|
-
self._add_when_clause(when_clause)
|
|
438
|
+
self._add_when_clause(when_clause)
|
|
379
439
|
return self
|
|
380
440
|
|
|
381
441
|
def when_not_matched_by_source_then_delete(self, condition: Optional[Union[str, exp.Expression]] = None) -> Self:
|
|
@@ -412,5 +472,5 @@ class MergeNotMatchedBySourceClauseMixin:
|
|
|
412
472
|
when_args["this"] = condition_expr
|
|
413
473
|
|
|
414
474
|
when_clause = exp.When(**when_args)
|
|
415
|
-
self._add_when_clause(when_clause)
|
|
475
|
+
self._add_when_clause(when_clause)
|
|
416
476
|
return self
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Optional, Union, cast
|
|
4
4
|
|
|
5
|
+
from mypy_extensions import trait
|
|
5
6
|
from sqlglot import exp
|
|
6
7
|
from typing_extensions import Self
|
|
7
8
|
|
|
@@ -14,10 +15,14 @@ if TYPE_CHECKING:
|
|
|
14
15
|
__all__ = ("LimitOffsetClauseMixin", "OrderByClauseMixin", "ReturningClauseMixin")
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
@trait
|
|
17
19
|
class OrderByClauseMixin:
|
|
18
20
|
"""Mixin providing ORDER BY clause."""
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
__slots__ = ()
|
|
23
|
+
|
|
24
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
25
|
+
_expression: Optional[exp.Expression]
|
|
21
26
|
|
|
22
27
|
def order_by(self, *items: Union[str, exp.Ordered], desc: bool = False) -> Self:
|
|
23
28
|
"""Add ORDER BY clause.
|
|
@@ -50,10 +55,14 @@ class OrderByClauseMixin:
|
|
|
50
55
|
return cast("Self", builder)
|
|
51
56
|
|
|
52
57
|
|
|
58
|
+
@trait
|
|
53
59
|
class LimitOffsetClauseMixin:
|
|
54
60
|
"""Mixin providing LIMIT and OFFSET clauses."""
|
|
55
61
|
|
|
56
|
-
|
|
62
|
+
__slots__ = ()
|
|
63
|
+
|
|
64
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
65
|
+
_expression: Optional[exp.Expression]
|
|
57
66
|
|
|
58
67
|
def limit(self, value: int) -> Self:
|
|
59
68
|
"""Add LIMIT clause.
|
|
@@ -94,10 +103,13 @@ class LimitOffsetClauseMixin:
|
|
|
94
103
|
return cast("Self", builder)
|
|
95
104
|
|
|
96
105
|
|
|
106
|
+
@trait
|
|
97
107
|
class ReturningClauseMixin:
|
|
98
108
|
"""Mixin providing RETURNING clause."""
|
|
99
109
|
|
|
100
|
-
|
|
110
|
+
__slots__ = ()
|
|
111
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
112
|
+
_expression: Optional[exp.Expression]
|
|
101
113
|
|
|
102
114
|
def returning(self, *columns: Union[str, exp.Expression]) -> Self:
|
|
103
115
|
"""Add RETURNING clause to the statement.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Optional, Union, cast
|
|
4
4
|
|
|
5
|
+
from mypy_extensions import trait
|
|
5
6
|
from sqlglot import exp
|
|
6
7
|
|
|
7
8
|
if TYPE_CHECKING:
|
|
@@ -12,10 +13,14 @@ if TYPE_CHECKING:
|
|
|
12
13
|
__all__ = ("PivotClauseMixin", "UnpivotClauseMixin")
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
@trait
|
|
15
17
|
class PivotClauseMixin:
|
|
16
18
|
"""Mixin class to add PIVOT functionality to a Select."""
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
__slots__ = ()
|
|
21
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
22
|
+
_expression: Optional[exp.Expression]
|
|
23
|
+
|
|
19
24
|
dialect: "DialectType" = None
|
|
20
25
|
|
|
21
26
|
def pivot(
|
|
@@ -79,10 +84,14 @@ class PivotClauseMixin:
|
|
|
79
84
|
return cast("Select", self)
|
|
80
85
|
|
|
81
86
|
|
|
87
|
+
@trait
|
|
82
88
|
class UnpivotClauseMixin:
|
|
83
89
|
"""Mixin class to add UNPIVOT functionality to a Select."""
|
|
84
90
|
|
|
85
|
-
|
|
91
|
+
__slots__ = ()
|
|
92
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
93
|
+
_expression: Optional[exp.Expression]
|
|
94
|
+
|
|
86
95
|
dialect: "DialectType" = None
|
|
87
96
|
|
|
88
97
|
def unpivot(
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
5
5
|
|
|
6
|
+
from mypy_extensions import trait
|
|
6
7
|
from sqlglot import exp
|
|
7
8
|
from typing_extensions import Self
|
|
8
9
|
|
|
@@ -11,17 +12,20 @@ from sqlspec.exceptions import SQLBuilderError
|
|
|
11
12
|
from sqlspec.utils.type_guards import has_query_builder_parameters, is_expression
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
|
-
from sqlspec.builder._base import QueryBuilder
|
|
15
15
|
from sqlspec.builder._column import Column, FunctionColumn
|
|
16
16
|
from sqlspec.protocols import SelectBuilderProtocol, SQLBuilderProtocol
|
|
17
17
|
|
|
18
18
|
__all__ = ("CaseBuilder", "SelectClauseMixin")
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
@trait
|
|
21
22
|
class SelectClauseMixin:
|
|
22
23
|
"""Consolidated mixin providing all SELECT-related clauses and functionality."""
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
__slots__ = ()
|
|
26
|
+
|
|
27
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
28
|
+
_expression: Optional[exp.Expression]
|
|
25
29
|
|
|
26
30
|
def select(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn"]) -> Self:
|
|
27
31
|
"""Add columns to SELECT clause.
|
|
@@ -529,7 +533,7 @@ class SelectClauseMixin:
|
|
|
529
533
|
Returns:
|
|
530
534
|
CaseBuilder: A CaseBuilder instance for building the CASE expression.
|
|
531
535
|
"""
|
|
532
|
-
builder = cast("
|
|
536
|
+
builder = cast("SelectBuilderProtocol", self)
|
|
533
537
|
return CaseBuilder(builder, alias)
|
|
534
538
|
|
|
535
539
|
|
|
@@ -537,15 +541,15 @@ class SelectClauseMixin:
|
|
|
537
541
|
class CaseBuilder:
|
|
538
542
|
"""Builder for CASE expressions."""
|
|
539
543
|
|
|
540
|
-
_parent: "
|
|
544
|
+
_parent: "SelectBuilderProtocol"
|
|
541
545
|
_alias: Optional[str]
|
|
542
546
|
_case_expr: exp.Case
|
|
543
547
|
|
|
544
|
-
def __init__(self, parent: "
|
|
548
|
+
def __init__(self, parent: "SelectBuilderProtocol", alias: "Optional[str]" = None) -> None:
|
|
545
549
|
"""Initialize CaseBuilder.
|
|
546
550
|
|
|
547
551
|
Args:
|
|
548
|
-
parent: The parent builder.
|
|
552
|
+
parent: The parent builder with select capabilities.
|
|
549
553
|
alias: Optional alias for the CASE expression.
|
|
550
554
|
"""
|
|
551
555
|
self._parent = parent
|
|
@@ -589,11 +593,11 @@ class CaseBuilder:
|
|
|
589
593
|
self._case_expr.set("default", value_expr)
|
|
590
594
|
return self
|
|
591
595
|
|
|
592
|
-
def end(self) -> "
|
|
596
|
+
def end(self) -> "SelectBuilderProtocol":
|
|
593
597
|
"""Finalize the CASE expression and add it to the SELECT clause.
|
|
594
598
|
|
|
595
599
|
Returns:
|
|
596
600
|
The parent builder instance.
|
|
597
601
|
"""
|
|
598
602
|
select_expr = exp.alias_(self._case_expr, self._alias) if self._alias else self._case_expr
|
|
599
|
-
return
|
|
603
|
+
return self._parent.select(select_expr)
|