sqlspec 0.24.1__py3-none-any.whl → 0.26.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/_serialization.py +223 -21
- sqlspec/_sql.py +20 -62
- sqlspec/_typing.py +11 -0
- sqlspec/adapters/adbc/config.py +8 -1
- sqlspec/adapters/adbc/data_dictionary.py +290 -0
- sqlspec/adapters/adbc/driver.py +129 -20
- sqlspec/adapters/adbc/type_converter.py +159 -0
- sqlspec/adapters/aiosqlite/config.py +3 -0
- sqlspec/adapters/aiosqlite/data_dictionary.py +117 -0
- sqlspec/adapters/aiosqlite/driver.py +17 -3
- sqlspec/adapters/asyncmy/_types.py +1 -1
- sqlspec/adapters/asyncmy/config.py +11 -8
- sqlspec/adapters/asyncmy/data_dictionary.py +122 -0
- sqlspec/adapters/asyncmy/driver.py +31 -7
- sqlspec/adapters/asyncpg/config.py +3 -0
- sqlspec/adapters/asyncpg/data_dictionary.py +134 -0
- sqlspec/adapters/asyncpg/driver.py +19 -4
- sqlspec/adapters/bigquery/config.py +3 -0
- sqlspec/adapters/bigquery/data_dictionary.py +109 -0
- sqlspec/adapters/bigquery/driver.py +21 -3
- sqlspec/adapters/bigquery/type_converter.py +93 -0
- sqlspec/adapters/duckdb/_types.py +1 -1
- sqlspec/adapters/duckdb/config.py +2 -0
- sqlspec/adapters/duckdb/data_dictionary.py +124 -0
- sqlspec/adapters/duckdb/driver.py +32 -5
- sqlspec/adapters/duckdb/pool.py +1 -1
- sqlspec/adapters/duckdb/type_converter.py +103 -0
- sqlspec/adapters/oracledb/config.py +6 -0
- sqlspec/adapters/oracledb/data_dictionary.py +442 -0
- sqlspec/adapters/oracledb/driver.py +68 -9
- sqlspec/adapters/oracledb/migrations.py +51 -67
- sqlspec/adapters/oracledb/type_converter.py +132 -0
- sqlspec/adapters/psqlpy/config.py +3 -0
- sqlspec/adapters/psqlpy/data_dictionary.py +133 -0
- sqlspec/adapters/psqlpy/driver.py +23 -179
- sqlspec/adapters/psqlpy/type_converter.py +73 -0
- sqlspec/adapters/psycopg/config.py +8 -4
- sqlspec/adapters/psycopg/data_dictionary.py +257 -0
- sqlspec/adapters/psycopg/driver.py +40 -5
- sqlspec/adapters/sqlite/config.py +3 -0
- sqlspec/adapters/sqlite/data_dictionary.py +117 -0
- sqlspec/adapters/sqlite/driver.py +18 -3
- sqlspec/adapters/sqlite/pool.py +13 -4
- sqlspec/base.py +3 -4
- sqlspec/builder/_base.py +130 -48
- sqlspec/builder/_column.py +66 -24
- sqlspec/builder/_ddl.py +91 -41
- sqlspec/builder/_insert.py +40 -58
- sqlspec/builder/_parsing_utils.py +127 -12
- sqlspec/builder/_select.py +147 -2
- sqlspec/builder/_update.py +1 -1
- sqlspec/builder/mixins/_cte_and_set_ops.py +31 -23
- sqlspec/builder/mixins/_delete_operations.py +12 -7
- sqlspec/builder/mixins/_insert_operations.py +50 -36
- sqlspec/builder/mixins/_join_operations.py +15 -30
- sqlspec/builder/mixins/_merge_operations.py +210 -78
- sqlspec/builder/mixins/_order_limit_operations.py +4 -10
- sqlspec/builder/mixins/_pivot_operations.py +1 -0
- sqlspec/builder/mixins/_select_operations.py +44 -22
- sqlspec/builder/mixins/_update_operations.py +30 -37
- sqlspec/builder/mixins/_where_clause.py +52 -70
- sqlspec/cli.py +246 -140
- sqlspec/config.py +33 -19
- sqlspec/core/__init__.py +3 -2
- sqlspec/core/cache.py +298 -352
- sqlspec/core/compiler.py +61 -4
- sqlspec/core/filters.py +246 -213
- sqlspec/core/hashing.py +9 -11
- sqlspec/core/parameters.py +27 -10
- sqlspec/core/statement.py +72 -12
- sqlspec/core/type_conversion.py +234 -0
- sqlspec/driver/__init__.py +6 -3
- sqlspec/driver/_async.py +108 -5
- sqlspec/driver/_common.py +186 -17
- sqlspec/driver/_sync.py +108 -5
- sqlspec/driver/mixins/_result_tools.py +60 -7
- sqlspec/exceptions.py +5 -0
- sqlspec/loader.py +8 -9
- sqlspec/migrations/__init__.py +4 -3
- sqlspec/migrations/base.py +153 -14
- sqlspec/migrations/commands.py +34 -96
- sqlspec/migrations/context.py +145 -0
- sqlspec/migrations/loaders.py +25 -8
- sqlspec/migrations/runner.py +352 -82
- sqlspec/storage/backends/fsspec.py +1 -0
- sqlspec/typing.py +4 -0
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/serializers.py +50 -2
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/METADATA +1 -1
- sqlspec-0.26.0.dist-info/RECORD +157 -0
- sqlspec-0.24.1.dist-info/RECORD +0 -139
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
# pyright: reportPrivateUsage=false
|
|
1
2
|
"""CTE and set operation mixins.
|
|
2
3
|
|
|
3
4
|
Provides mixins for Common Table Expressions (WITH clause) and
|
|
4
5
|
set operations (UNION, INTERSECT, EXCEPT).
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
|
-
from typing import Any, Optional, Union
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
8
9
|
|
|
9
10
|
from mypy_extensions import trait
|
|
10
11
|
from sqlglot import exp
|
|
@@ -12,6 +13,9 @@ from typing_extensions import Self
|
|
|
12
13
|
|
|
13
14
|
from sqlspec.exceptions import SQLBuilderError
|
|
14
15
|
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from sqlspec.builder._base import QueryBuilder
|
|
18
|
+
|
|
15
19
|
__all__ = ("CommonTableExpressionMixin", "SetOperationMixin")
|
|
16
20
|
|
|
17
21
|
|
|
@@ -20,8 +24,10 @@ class CommonTableExpressionMixin:
|
|
|
20
24
|
"""Mixin providing WITH clause (Common Table Expressions) support for SQL builders."""
|
|
21
25
|
|
|
22
26
|
__slots__ = ()
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
|
|
28
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
29
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
30
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
25
31
|
|
|
26
32
|
_with_ctes: Any # Provided by QueryBuilder
|
|
27
33
|
dialect: Any # Provided by QueryBuilder
|
|
@@ -60,12 +66,14 @@ class CommonTableExpressionMixin:
|
|
|
60
66
|
Returns:
|
|
61
67
|
The current builder instance for method chaining.
|
|
62
68
|
"""
|
|
63
|
-
|
|
69
|
+
builder = cast("QueryBuilder", self)
|
|
70
|
+
expression = builder.get_expression()
|
|
71
|
+
if expression is None:
|
|
64
72
|
msg = "Cannot add WITH clause: expression not initialized."
|
|
65
73
|
raise SQLBuilderError(msg)
|
|
66
74
|
|
|
67
|
-
if not isinstance(
|
|
68
|
-
msg = f"Cannot add WITH clause to {type(
|
|
75
|
+
if not isinstance(expression, (exp.Select, exp.Insert, exp.Update, exp.Delete)):
|
|
76
|
+
msg = f"Cannot add WITH clause to {type(expression).__name__} expression."
|
|
69
77
|
raise SQLBuilderError(msg)
|
|
70
78
|
|
|
71
79
|
cte_expr: Optional[exp.Expression] = None
|
|
@@ -103,19 +111,17 @@ class CommonTableExpressionMixin:
|
|
|
103
111
|
else:
|
|
104
112
|
cte_alias_expr = exp.alias_(cte_expr, name)
|
|
105
113
|
|
|
106
|
-
existing_with =
|
|
114
|
+
existing_with = expression.args.get("with")
|
|
107
115
|
if existing_with:
|
|
108
116
|
existing_with.expressions.append(cte_alias_expr)
|
|
109
117
|
if recursive:
|
|
110
118
|
existing_with.set("recursive", recursive)
|
|
111
119
|
else:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
):
|
|
116
|
-
self._expression = self._expression.with_(cte_alias_expr, as_=name, copy=False)
|
|
120
|
+
if isinstance(expression, (exp.Select, exp.Insert, exp.Update)):
|
|
121
|
+
updated_expression = expression.with_(cte_alias_expr, as_=name, copy=False)
|
|
122
|
+
builder.set_expression(updated_expression)
|
|
117
123
|
if recursive:
|
|
118
|
-
with_clause =
|
|
124
|
+
with_clause = updated_expression.find(exp.With)
|
|
119
125
|
if with_clause:
|
|
120
126
|
with_clause.set("recursive", recursive)
|
|
121
127
|
self._with_ctes[name] = exp.CTE(this=cte_expr, alias=exp.to_table(name))
|
|
@@ -128,10 +134,12 @@ class SetOperationMixin:
|
|
|
128
134
|
"""Mixin providing set operations (UNION, INTERSECT, EXCEPT) for SELECT builders."""
|
|
129
135
|
|
|
130
136
|
__slots__ = ()
|
|
131
|
-
# Type annotation for PyRight - this will be provided by the base class
|
|
132
|
-
_expression: Optional[exp.Expression]
|
|
133
137
|
|
|
134
|
-
|
|
138
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
139
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
140
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
141
|
+
def set_parameters(self, parameters: "dict[str, Any]") -> None: ...
|
|
142
|
+
|
|
135
143
|
dialect: Any = None
|
|
136
144
|
|
|
137
145
|
def build(self) -> Any:
|
|
@@ -162,7 +170,7 @@ class SetOperationMixin:
|
|
|
162
170
|
union_expr = exp.union(left_expr, right_expr, distinct=not all_)
|
|
163
171
|
new_builder = type(self)()
|
|
164
172
|
new_builder.dialect = self.dialect
|
|
165
|
-
new_builder.
|
|
173
|
+
cast("QueryBuilder", new_builder).set_expression(union_expr)
|
|
166
174
|
merged_parameters = dict(left_query.parameters)
|
|
167
175
|
for param_name, param_value in right_query.parameters.items():
|
|
168
176
|
if param_name in merged_parameters:
|
|
@@ -181,11 +189,11 @@ class SetOperationMixin:
|
|
|
181
189
|
|
|
182
190
|
right_expr = right_expr.transform(rename_parameter)
|
|
183
191
|
union_expr = exp.union(left_expr, right_expr, distinct=not all_)
|
|
184
|
-
new_builder.
|
|
192
|
+
cast("QueryBuilder", new_builder).set_expression(union_expr)
|
|
185
193
|
merged_parameters[new_param_name] = param_value
|
|
186
194
|
else:
|
|
187
195
|
merged_parameters[param_name] = param_value
|
|
188
|
-
new_builder.
|
|
196
|
+
new_builder.set_parameters(merged_parameters)
|
|
189
197
|
return new_builder
|
|
190
198
|
|
|
191
199
|
def intersect(self, other: Any) -> Self:
|
|
@@ -210,10 +218,10 @@ class SetOperationMixin:
|
|
|
210
218
|
intersect_expr = exp.intersect(left_expr, right_expr, distinct=True)
|
|
211
219
|
new_builder = type(self)()
|
|
212
220
|
new_builder.dialect = self.dialect
|
|
213
|
-
new_builder.
|
|
221
|
+
cast("QueryBuilder", new_builder).set_expression(intersect_expr)
|
|
214
222
|
merged_parameters = dict(left_query.parameters)
|
|
215
223
|
merged_parameters.update(right_query.parameters)
|
|
216
|
-
new_builder.
|
|
224
|
+
new_builder.set_parameters(merged_parameters)
|
|
217
225
|
return new_builder
|
|
218
226
|
|
|
219
227
|
def except_(self, other: Any) -> Self:
|
|
@@ -238,8 +246,8 @@ class SetOperationMixin:
|
|
|
238
246
|
except_expr = exp.except_(left_expr, right_expr)
|
|
239
247
|
new_builder = type(self)()
|
|
240
248
|
new_builder.dialect = self.dialect
|
|
241
|
-
new_builder.
|
|
249
|
+
cast("QueryBuilder", new_builder).set_expression(except_expr)
|
|
242
250
|
merged_parameters = dict(left_query.parameters)
|
|
243
251
|
merged_parameters.update(right_query.parameters)
|
|
244
|
-
new_builder.
|
|
252
|
+
new_builder.set_parameters(merged_parameters)
|
|
245
253
|
return new_builder
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# pyright: reportPrivateUsage=false
|
|
1
2
|
"""DELETE operation mixins.
|
|
2
3
|
|
|
3
4
|
Provides mixins for DELETE statement functionality including
|
|
@@ -21,8 +22,9 @@ class DeleteFromClauseMixin:
|
|
|
21
22
|
|
|
22
23
|
__slots__ = ()
|
|
23
24
|
|
|
24
|
-
# Type
|
|
25
|
-
|
|
25
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
26
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
27
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
26
28
|
|
|
27
29
|
def from_(self, table: str) -> Self:
|
|
28
30
|
"""Set the target table for the DELETE statement.
|
|
@@ -33,13 +35,16 @@ class DeleteFromClauseMixin:
|
|
|
33
35
|
Returns:
|
|
34
36
|
The current builder instance for method chaining.
|
|
35
37
|
"""
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
current_expr = self.get_expression()
|
|
39
|
+
if current_expr is None:
|
|
40
|
+
self.set_expression(exp.Delete())
|
|
41
|
+
current_expr = self.get_expression()
|
|
42
|
+
|
|
43
|
+
if not isinstance(current_expr, exp.Delete):
|
|
44
|
+
current_expr_type = type(current_expr).__name__
|
|
40
45
|
msg = f"Base expression for Delete is {current_expr_type}, expected Delete."
|
|
41
46
|
raise SQLBuilderError(msg)
|
|
42
47
|
|
|
43
48
|
setattr(self, "_table", table)
|
|
44
|
-
|
|
49
|
+
current_expr.set("this", exp.to_table(table))
|
|
45
50
|
return self
|
|
@@ -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
|
"""JOIN operation mixins.
|
|
2
3
|
|
|
3
4
|
Provides mixins for JOIN operations in SELECT statements.
|
|
@@ -81,16 +82,12 @@ class JoinClauseMixin:
|
|
|
81
82
|
self, table: Any, alias: Optional[str], builder: "SQLBuilderProtocol"
|
|
82
83
|
) -> exp.Expression:
|
|
83
84
|
"""Handle table parameters that are query builders."""
|
|
84
|
-
if hasattr(table, "_expression") and
|
|
85
|
-
|
|
86
|
-
if table_expr_value is not None:
|
|
87
|
-
subquery_exp = exp.paren(table_expr_value)
|
|
88
|
-
else:
|
|
89
|
-
subquery_exp = exp.paren(exp.Anonymous(this=""))
|
|
85
|
+
if hasattr(table, "_expression") and table._expression is not None:
|
|
86
|
+
subquery_exp = exp.paren(table._expression)
|
|
90
87
|
return exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
91
88
|
subquery = table.build()
|
|
92
89
|
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
93
|
-
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=
|
|
90
|
+
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=builder.dialect))
|
|
94
91
|
return exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
95
92
|
|
|
96
93
|
def _parse_on_condition(
|
|
@@ -106,28 +103,20 @@ class JoinClauseMixin:
|
|
|
106
103
|
return self._handle_sql_object_condition(on, builder)
|
|
107
104
|
if isinstance(on, exp.Expression):
|
|
108
105
|
return on
|
|
109
|
-
# Last resort - convert to string and parse
|
|
110
106
|
return exp.condition(str(on))
|
|
111
107
|
|
|
112
108
|
def _handle_sql_object_condition(self, on: Any, builder: "SQLBuilderProtocol") -> exp.Expression:
|
|
113
109
|
"""Handle SQL object conditions with parameter binding."""
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if hasattr(on, "parameters") and hasattr(builder, "add_parameter"):
|
|
118
|
-
sql_parameters = getattr(on, "parameters", {})
|
|
119
|
-
for param_name, param_value in sql_parameters.items():
|
|
110
|
+
if hasattr(on, "expression") and on.expression is not None:
|
|
111
|
+
if hasattr(on, "parameters"):
|
|
112
|
+
for param_name, param_value in on.parameters.items():
|
|
120
113
|
builder.add_parameter(param_value, name=param_name)
|
|
121
|
-
return cast("exp.Expression", expression)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
# Merge parameters even when parsing raw SQL
|
|
125
|
-
if hasattr(on, "parameters") and hasattr(builder, "add_parameter"):
|
|
126
|
-
sql_parameters = getattr(on, "parameters", {})
|
|
127
|
-
for param_name, param_value in sql_parameters.items():
|
|
114
|
+
return cast("exp.Expression", on.expression)
|
|
115
|
+
if hasattr(on, "parameters"):
|
|
116
|
+
for param_name, param_value in on.parameters.items():
|
|
128
117
|
builder.add_parameter(param_value, name=param_name)
|
|
129
|
-
parsed_expr = exp.maybe_parse(
|
|
130
|
-
return parsed_expr if parsed_expr is not None else exp.condition(str(
|
|
118
|
+
parsed_expr = exp.maybe_parse(on.sql)
|
|
119
|
+
return parsed_expr if parsed_expr is not None else exp.condition(str(on.sql))
|
|
131
120
|
|
|
132
121
|
def _create_join_expression(
|
|
133
122
|
self, table_expr: exp.Expression, on_expr: Optional[exp.Expression], join_type: str
|
|
@@ -194,17 +183,13 @@ class JoinClauseMixin:
|
|
|
194
183
|
if isinstance(table, str):
|
|
195
184
|
table_expr = parse_table_expression(table, alias)
|
|
196
185
|
elif has_query_builder_parameters(table):
|
|
197
|
-
if hasattr(table, "_expression") and
|
|
198
|
-
|
|
199
|
-
if table_expr_value is not None:
|
|
200
|
-
subquery_exp = exp.paren(table_expr_value)
|
|
201
|
-
else:
|
|
202
|
-
subquery_exp = exp.paren(exp.Anonymous(this=""))
|
|
186
|
+
if hasattr(table, "_expression") and table._expression is not None:
|
|
187
|
+
subquery_exp = exp.paren(table._expression)
|
|
203
188
|
table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
204
189
|
else:
|
|
205
190
|
subquery = table.build()
|
|
206
191
|
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
207
|
-
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=
|
|
192
|
+
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=builder.dialect))
|
|
208
193
|
table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
209
194
|
else:
|
|
210
195
|
table_expr = table
|