sqlspec 0.15.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 +699 -43
- 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 +61 -35
- sqlspec/builder/_merge.py +17 -2
- sqlspec/builder/_parsing_utils.py +16 -12
- 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 -24
- sqlspec/builder/mixins/_join_operations.py +11 -4
- sqlspec/builder/mixins/_merge_operations.py +91 -19
- sqlspec/builder/mixins/_order_limit_operations.py +15 -3
- sqlspec/builder/mixins/_pivot_operations.py +11 -2
- sqlspec/builder/mixins/_select_operations.py +16 -10
- sqlspec/builder/mixins/_update_operations.py +43 -10
- sqlspec/builder/mixins/_where_clause.py +177 -65
- sqlspec/core/cache.py +26 -28
- sqlspec/core/compiler.py +58 -37
- sqlspec/core/filters.py +12 -10
- sqlspec/core/parameters.py +80 -52
- sqlspec/core/result.py +30 -17
- sqlspec/core/statement.py +47 -22
- sqlspec/driver/_async.py +76 -46
- sqlspec/driver/_common.py +25 -6
- sqlspec/driver/_sync.py +73 -43
- sqlspec/driver/mixins/_result_tools.py +62 -37
- sqlspec/driver/mixins/_sql_translator.py +61 -11
- sqlspec/extensions/litestar/cli.py +1 -1
- sqlspec/extensions/litestar/plugin.py +2 -2
- sqlspec/protocols.py +7 -0
- sqlspec/utils/sync_tools.py +1 -1
- sqlspec/utils/type_guards.py +7 -3
- {sqlspec-0.15.0.dist-info → sqlspec-0.16.1.dist-info}/METADATA +1 -1
- {sqlspec-0.15.0.dist-info → sqlspec-0.16.1.dist-info}/RECORD +43 -43
- {sqlspec-0.15.0.dist-info → sqlspec-0.16.1.dist-info}/WHEEL +0 -0
- {sqlspec-0.15.0.dist-info → sqlspec-0.16.1.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.15.0.dist-info → sqlspec-0.16.1.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.15.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,27 +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
|
-
|
|
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
|
+
|
|
87
178
|
values_expr = exp.Values(expressions=[row_exprs])
|
|
88
179
|
self._expression.set("expression", values_expr)
|
|
89
180
|
return self
|
|
@@ -100,10 +191,21 @@ class InsertValuesMixin:
|
|
|
100
191
|
return self.values(*values)
|
|
101
192
|
|
|
102
193
|
|
|
194
|
+
@trait
|
|
103
195
|
class InsertFromSelectMixin:
|
|
104
196
|
"""Mixin providing INSERT ... SELECT support for INSERT builders."""
|
|
105
197
|
|
|
106
|
-
|
|
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)
|
|
107
209
|
|
|
108
210
|
def from_select(self, select_builder: Any) -> Self:
|
|
109
211
|
"""Sets the INSERT source to a SELECT statement.
|
|
@@ -118,7 +220,7 @@ class InsertFromSelectMixin:
|
|
|
118
220
|
SQLBuilderError: If the table is not set or the select_builder is invalid.
|
|
119
221
|
"""
|
|
120
222
|
try:
|
|
121
|
-
if not self._table:
|
|
223
|
+
if not self._table:
|
|
122
224
|
msg = "The target table must be set using .into() before adding values."
|
|
123
225
|
raise SQLBuilderError(msg)
|
|
124
226
|
except AttributeError:
|
|
@@ -129,11 +231,11 @@ class InsertFromSelectMixin:
|
|
|
129
231
|
if not isinstance(self._expression, exp.Insert):
|
|
130
232
|
msg = "Cannot set INSERT source on a non-INSERT expression."
|
|
131
233
|
raise SQLBuilderError(msg)
|
|
132
|
-
subquery_parameters = select_builder._parameters
|
|
234
|
+
subquery_parameters = select_builder._parameters
|
|
133
235
|
if subquery_parameters:
|
|
134
236
|
for p_name, p_value in subquery_parameters.items():
|
|
135
|
-
self.add_parameter(p_value, name=p_name)
|
|
136
|
-
select_expr = select_builder._expression
|
|
237
|
+
self.add_parameter(p_value, name=p_name)
|
|
238
|
+
select_expr = select_builder._expression
|
|
137
239
|
if select_expr and isinstance(select_expr, exp.Select):
|
|
138
240
|
self._expression.set("expression", select_expr.copy())
|
|
139
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:
|
|
@@ -172,7 +196,11 @@ class MergeMatchedClauseMixin:
|
|
|
172
196
|
"""
|
|
173
197
|
update_expressions: list[exp.EQ] = []
|
|
174
198
|
for col, val in set_values.items():
|
|
175
|
-
|
|
199
|
+
column_name = col if isinstance(col, str) else str(col)
|
|
200
|
+
if "." in column_name:
|
|
201
|
+
column_name = column_name.split(".")[-1]
|
|
202
|
+
param_name = self._generate_unique_parameter_name(column_name)
|
|
203
|
+
param_name = self.add_parameter(val, name=param_name)[1]
|
|
176
204
|
update_expressions.append(exp.EQ(this=exp.column(col), expression=exp.var(param_name)))
|
|
177
205
|
|
|
178
206
|
when_args: dict[str, Any] = {"matched": True, "then": exp.Update(expressions=update_expressions)}
|
|
@@ -234,10 +262,28 @@ class MergeMatchedClauseMixin:
|
|
|
234
262
|
return self
|
|
235
263
|
|
|
236
264
|
|
|
265
|
+
@trait
|
|
237
266
|
class MergeNotMatchedClauseMixin:
|
|
238
267
|
"""Mixin providing WHEN NOT MATCHED THEN ... clauses for MERGE builders."""
|
|
239
268
|
|
|
240
|
-
|
|
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)
|
|
241
287
|
|
|
242
288
|
def when_not_matched_then_insert(
|
|
243
289
|
self,
|
|
@@ -270,8 +316,12 @@ class MergeNotMatchedClauseMixin:
|
|
|
270
316
|
raise SQLBuilderError(msg)
|
|
271
317
|
|
|
272
318
|
parameterized_values: list[exp.Expression] = []
|
|
273
|
-
for val in values:
|
|
274
|
-
|
|
319
|
+
for i, val in enumerate(values):
|
|
320
|
+
column_name = columns[i] if isinstance(columns[i], str) else str(columns[i])
|
|
321
|
+
if "." in column_name:
|
|
322
|
+
column_name = column_name.split(".")[-1]
|
|
323
|
+
param_name = self._generate_unique_parameter_name(column_name)
|
|
324
|
+
param_name = self.add_parameter(val, name=param_name)[1]
|
|
275
325
|
parameterized_values.append(exp.var(param_name))
|
|
276
326
|
|
|
277
327
|
insert_args["this"] = exp.Tuple(expressions=[exp.column(c) for c in columns])
|
|
@@ -308,14 +358,32 @@ class MergeNotMatchedClauseMixin:
|
|
|
308
358
|
when_args["this"] = condition_expr
|
|
309
359
|
|
|
310
360
|
when_clause = exp.When(**when_args)
|
|
311
|
-
self._add_when_clause(when_clause)
|
|
361
|
+
self._add_when_clause(when_clause)
|
|
312
362
|
return self
|
|
313
363
|
|
|
314
364
|
|
|
365
|
+
@trait
|
|
315
366
|
class MergeNotMatchedBySourceClauseMixin:
|
|
316
367
|
"""Mixin providing WHEN NOT MATCHED BY SOURCE THEN ... clauses for MERGE builders."""
|
|
317
368
|
|
|
318
|
-
|
|
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)
|
|
319
387
|
|
|
320
388
|
def when_not_matched_by_source_then_update(
|
|
321
389
|
self, set_values: dict[str, Any], condition: Optional[Union[str, exp.Expression]] = None
|
|
@@ -336,7 +404,11 @@ class MergeNotMatchedBySourceClauseMixin:
|
|
|
336
404
|
"""
|
|
337
405
|
update_expressions: list[exp.EQ] = []
|
|
338
406
|
for col, val in set_values.items():
|
|
339
|
-
|
|
407
|
+
column_name = col if isinstance(col, str) else str(col)
|
|
408
|
+
if "." in column_name:
|
|
409
|
+
column_name = column_name.split(".")[-1]
|
|
410
|
+
param_name = self._generate_unique_parameter_name(column_name)
|
|
411
|
+
param_name = self.add_parameter(val, name=param_name)[1]
|
|
340
412
|
update_expressions.append(exp.EQ(this=exp.column(col), expression=exp.var(param_name)))
|
|
341
413
|
|
|
342
414
|
when_args: dict[str, Any] = {
|
|
@@ -363,7 +435,7 @@ class MergeNotMatchedBySourceClauseMixin:
|
|
|
363
435
|
when_args["this"] = condition_expr
|
|
364
436
|
|
|
365
437
|
when_clause = exp.When(**when_args)
|
|
366
|
-
self._add_when_clause(when_clause)
|
|
438
|
+
self._add_when_clause(when_clause)
|
|
367
439
|
return self
|
|
368
440
|
|
|
369
441
|
def when_not_matched_by_source_then_delete(self, condition: Optional[Union[str, exp.Expression]] = None) -> Self:
|
|
@@ -400,5 +472,5 @@ class MergeNotMatchedBySourceClauseMixin:
|
|
|
400
472
|
when_args["this"] = condition_expr
|
|
401
473
|
|
|
402
474
|
when_clause = exp.When(**when_args)
|
|
403
|
-
self._add_when_clause(when_clause)
|
|
475
|
+
self._add_when_clause(when_clause)
|
|
404
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(
|