sqlspec 0.25.0__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 +12 -50
- sqlspec/_typing.py +9 -0
- sqlspec/adapters/adbc/config.py +8 -1
- sqlspec/adapters/adbc/data_dictionary.py +290 -0
- sqlspec/adapters/adbc/driver.py +127 -18
- 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 +63 -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 +6 -0
- 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/builder/_base.py +82 -42
- sqlspec/builder/_column.py +57 -24
- sqlspec/builder/_ddl.py +84 -34
- sqlspec/builder/_insert.py +30 -52
- sqlspec/builder/_parsing_utils.py +104 -8
- sqlspec/builder/_select.py +147 -2
- sqlspec/builder/mixins/_cte_and_set_ops.py +1 -2
- sqlspec/builder/mixins/_join_operations.py +14 -30
- sqlspec/builder/mixins/_merge_operations.py +167 -61
- sqlspec/builder/mixins/_order_limit_operations.py +3 -10
- sqlspec/builder/mixins/_select_operations.py +3 -9
- sqlspec/builder/mixins/_update_operations.py +3 -22
- sqlspec/builder/mixins/_where_clause.py +4 -10
- sqlspec/cli.py +246 -140
- sqlspec/config.py +33 -19
- sqlspec/core/cache.py +2 -2
- sqlspec/core/compiler.py +56 -1
- sqlspec/core/parameters.py +7 -3
- sqlspec/core/statement.py +5 -0
- sqlspec/core/type_conversion.py +234 -0
- sqlspec/driver/__init__.py +6 -3
- sqlspec/driver/_async.py +106 -3
- sqlspec/driver/_common.py +156 -4
- sqlspec/driver/_sync.py +106 -3
- sqlspec/exceptions.py +5 -0
- 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/typing.py +2 -0
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/serializers.py +50 -2
- {sqlspec-0.25.0.dist-info → sqlspec-0.26.0.dist-info}/METADATA +1 -1
- sqlspec-0.26.0.dist-info/RECORD +157 -0
- sqlspec-0.25.0.dist-info/RECORD +0 -139
- {sqlspec-0.25.0.dist-info → sqlspec-0.26.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.26.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.26.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.26.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -9,7 +9,7 @@ from typing import Any, Final, Optional, Union, cast
|
|
|
9
9
|
|
|
10
10
|
from sqlglot import exp, maybe_parse, parse_one
|
|
11
11
|
|
|
12
|
-
from sqlspec.core.parameters import ParameterStyle
|
|
12
|
+
from sqlspec.core.parameters import ParameterStyle, ParameterValidator
|
|
13
13
|
from sqlspec.utils.type_guards import (
|
|
14
14
|
has_expression_and_parameters,
|
|
15
15
|
has_expression_and_sql,
|
|
@@ -63,9 +63,7 @@ def parse_column_expression(
|
|
|
63
63
|
if isinstance(column_input, exp.Expression):
|
|
64
64
|
return column_input
|
|
65
65
|
|
|
66
|
-
# Handle SQL objects (from sql.raw with parameters)
|
|
67
66
|
if has_expression_and_sql(column_input):
|
|
68
|
-
# This is likely a SQL object
|
|
69
67
|
expression = getattr(column_input, "expression", None)
|
|
70
68
|
if expression is not None and isinstance(expression, exp.Expression):
|
|
71
69
|
# Merge parameters from SQL object into builder if available
|
|
@@ -74,9 +72,7 @@ def parse_column_expression(
|
|
|
74
72
|
for param_name, param_value in sql_parameters.items():
|
|
75
73
|
builder.add_parameter(param_value, name=param_name)
|
|
76
74
|
return cast("exp.Expression", expression)
|
|
77
|
-
# If expression is None, fall back to parsing the raw SQL
|
|
78
75
|
sql_text = getattr(column_input, "sql", "")
|
|
79
|
-
# Merge parameters even when parsing raw SQL
|
|
80
76
|
if builder and has_expression_and_parameters(column_input) and hasattr(builder, "add_parameter"):
|
|
81
77
|
sql_parameters = getattr(column_input, "parameters", {})
|
|
82
78
|
for param_name, param_value in sql_parameters.items():
|
|
@@ -175,8 +171,6 @@ def parse_condition_expression(
|
|
|
175
171
|
|
|
176
172
|
# Convert database-specific parameter styles to SQLGlot-compatible format
|
|
177
173
|
# This ensures that placeholders like $1, %s, :1 are properly recognized as parameters
|
|
178
|
-
from sqlspec.core.parameters import ParameterValidator
|
|
179
|
-
|
|
180
174
|
validator = ParameterValidator()
|
|
181
175
|
param_info = validator.extract_parameters(condition_input)
|
|
182
176
|
|
|
@@ -205,4 +199,106 @@ def parse_condition_expression(
|
|
|
205
199
|
return exp.condition(condition_input)
|
|
206
200
|
|
|
207
201
|
|
|
208
|
-
|
|
202
|
+
def extract_sql_object_expression(value: Any, builder: Optional[Any] = None) -> exp.Expression:
|
|
203
|
+
"""Extract SQLGlot expression from SQL object value with parameter merging.
|
|
204
|
+
|
|
205
|
+
Handles the common pattern of:
|
|
206
|
+
1. Check if value has expression and SQL attributes
|
|
207
|
+
2. Try to get expression first, merge parameters if available
|
|
208
|
+
3. Fall back to parsing raw SQL text if expression is None
|
|
209
|
+
4. Merge parameters in both cases
|
|
210
|
+
5. Handle callable SQL text
|
|
211
|
+
|
|
212
|
+
This consolidates duplicated logic across builder files that process
|
|
213
|
+
SQL objects (like those from sql.raw() calls).
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
value: The SQL object value to process
|
|
217
|
+
builder: Optional builder instance for parameter merging (must have add_parameter method)
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
SQLGlot Expression extracted from the SQL object
|
|
221
|
+
|
|
222
|
+
Raises:
|
|
223
|
+
ValueError: If the value doesn't appear to be a SQL object
|
|
224
|
+
"""
|
|
225
|
+
if not has_expression_and_sql(value):
|
|
226
|
+
msg = f"Value does not have both expression and sql attributes: {type(value)}"
|
|
227
|
+
raise ValueError(msg)
|
|
228
|
+
|
|
229
|
+
# Try expression attribute first
|
|
230
|
+
expression = getattr(value, "expression", None)
|
|
231
|
+
if expression is not None and isinstance(expression, exp.Expression):
|
|
232
|
+
# Merge parameters if available and builder supports it
|
|
233
|
+
if builder and hasattr(value, "parameters") and hasattr(builder, "add_parameter"):
|
|
234
|
+
sql_parameters = getattr(value, "parameters", {})
|
|
235
|
+
for param_name, param_value in sql_parameters.items():
|
|
236
|
+
builder.add_parameter(param_value, name=param_name)
|
|
237
|
+
return cast("exp.Expression", expression)
|
|
238
|
+
|
|
239
|
+
# Fall back to parsing raw SQL text
|
|
240
|
+
sql_text = getattr(value, "sql", "")
|
|
241
|
+
|
|
242
|
+
# Merge parameters even when parsing raw SQL
|
|
243
|
+
if builder and hasattr(value, "parameters") and hasattr(builder, "add_parameter"):
|
|
244
|
+
sql_parameters = getattr(value, "parameters", {})
|
|
245
|
+
for param_name, param_value in sql_parameters.items():
|
|
246
|
+
builder.add_parameter(param_value, name=param_name)
|
|
247
|
+
|
|
248
|
+
# Handle callable SQL text
|
|
249
|
+
if callable(sql_text):
|
|
250
|
+
sql_text = str(value)
|
|
251
|
+
|
|
252
|
+
# Parse SQL text and return as expression
|
|
253
|
+
return exp.maybe_parse(sql_text) or exp.convert(str(sql_text))
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def extract_expression(value: Any) -> exp.Expression:
|
|
257
|
+
"""Extract SQLGlot expression from value, handling wrapper types.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
value: String, SQLGlot expression, or wrapper type.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Raw SQLGlot expression.
|
|
264
|
+
"""
|
|
265
|
+
from sqlspec.builder._column import Column
|
|
266
|
+
from sqlspec.builder._expression_wrappers import ExpressionWrapper
|
|
267
|
+
from sqlspec.builder.mixins._select_operations import Case
|
|
268
|
+
|
|
269
|
+
if isinstance(value, str):
|
|
270
|
+
return exp.column(value)
|
|
271
|
+
if isinstance(value, Column):
|
|
272
|
+
return value.sqlglot_expression
|
|
273
|
+
if isinstance(value, ExpressionWrapper):
|
|
274
|
+
return value.expression
|
|
275
|
+
if isinstance(value, Case):
|
|
276
|
+
return exp.Case(ifs=value.conditions, default=value.default)
|
|
277
|
+
if isinstance(value, exp.Expression):
|
|
278
|
+
return value
|
|
279
|
+
return exp.convert(value)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def to_expression(value: Any) -> exp.Expression:
|
|
283
|
+
"""Convert a Python value to a raw SQLGlot expression.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
value: Python value or SQLGlot expression to convert.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Raw SQLGlot expression.
|
|
290
|
+
"""
|
|
291
|
+
if isinstance(value, exp.Expression):
|
|
292
|
+
return value
|
|
293
|
+
return exp.convert(value)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
__all__ = (
|
|
297
|
+
"extract_expression",
|
|
298
|
+
"extract_sql_object_expression",
|
|
299
|
+
"parse_column_expression",
|
|
300
|
+
"parse_condition_expression",
|
|
301
|
+
"parse_order_expression",
|
|
302
|
+
"parse_table_expression",
|
|
303
|
+
"to_expression",
|
|
304
|
+
)
|
sqlspec/builder/_select.py
CHANGED
|
@@ -5,7 +5,7 @@ parameter binding and validation.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import re
|
|
8
|
-
from typing import Any, Callable, Final, Optional, Union
|
|
8
|
+
from typing import Any, Callable, Final, Optional, Union, cast
|
|
9
9
|
|
|
10
10
|
from sqlglot import exp
|
|
11
11
|
from typing_extensions import Self
|
|
@@ -24,6 +24,7 @@ from sqlspec.builder.mixins import (
|
|
|
24
24
|
WhereClauseMixin,
|
|
25
25
|
)
|
|
26
26
|
from sqlspec.core.result import SQLResult
|
|
27
|
+
from sqlspec.exceptions import SQLBuilderError
|
|
27
28
|
|
|
28
29
|
__all__ = ("Select",)
|
|
29
30
|
|
|
@@ -73,7 +74,6 @@ class Select(
|
|
|
73
74
|
"""
|
|
74
75
|
super().__init__(**kwargs)
|
|
75
76
|
|
|
76
|
-
# Initialize Select-specific attributes
|
|
77
77
|
self._with_parts: dict[str, Union[exp.CTE, Select]] = {}
|
|
78
78
|
self._hints: list[dict[str, object]] = []
|
|
79
79
|
|
|
@@ -169,3 +169,148 @@ class Select(
|
|
|
169
169
|
)
|
|
170
170
|
|
|
171
171
|
return SafeQuery(sql=modified_sql, parameters=safe_query.parameters, dialect=safe_query.dialect)
|
|
172
|
+
|
|
173
|
+
def _validate_select_expression(self) -> None:
|
|
174
|
+
"""Validate that current expression is a valid SELECT statement.
|
|
175
|
+
|
|
176
|
+
Raises:
|
|
177
|
+
SQLBuilderError: If expression is None or not a SELECT statement
|
|
178
|
+
"""
|
|
179
|
+
if self._expression is None or not isinstance(self._expression, exp.Select):
|
|
180
|
+
msg = "Locking clauses can only be applied to SELECT statements"
|
|
181
|
+
raise SQLBuilderError(msg)
|
|
182
|
+
|
|
183
|
+
def _validate_lock_parameters(self, skip_locked: bool, nowait: bool) -> None:
|
|
184
|
+
"""Validate locking parameters for conflicting options.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
skip_locked: Whether SKIP LOCKED option is enabled
|
|
188
|
+
nowait: Whether NOWAIT option is enabled
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
SQLBuilderError: If both skip_locked and nowait are True
|
|
192
|
+
"""
|
|
193
|
+
if skip_locked and nowait:
|
|
194
|
+
msg = "Cannot use both skip_locked and nowait"
|
|
195
|
+
raise SQLBuilderError(msg)
|
|
196
|
+
|
|
197
|
+
def for_update(
|
|
198
|
+
self, *, skip_locked: bool = False, nowait: bool = False, of: "Optional[Union[str, list[str]]]" = None
|
|
199
|
+
) -> "Self":
|
|
200
|
+
"""Add FOR UPDATE clause to SELECT statement for row-level locking.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
skip_locked: Skip rows that are already locked (SKIP LOCKED)
|
|
204
|
+
nowait: Return immediately if row is locked (NOWAIT)
|
|
205
|
+
of: Table names/aliases to lock (FOR UPDATE OF table)
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Self for method chaining
|
|
209
|
+
"""
|
|
210
|
+
self._validate_select_expression()
|
|
211
|
+
self._validate_lock_parameters(skip_locked, nowait)
|
|
212
|
+
|
|
213
|
+
assert self._expression is not None
|
|
214
|
+
select_expr = cast("exp.Select", self._expression)
|
|
215
|
+
|
|
216
|
+
lock_args = {"update": True}
|
|
217
|
+
|
|
218
|
+
if skip_locked:
|
|
219
|
+
lock_args["wait"] = False
|
|
220
|
+
elif nowait:
|
|
221
|
+
lock_args["wait"] = True
|
|
222
|
+
|
|
223
|
+
if of:
|
|
224
|
+
tables = [of] if isinstance(of, str) else of
|
|
225
|
+
lock_args["expressions"] = [exp.table_(t) for t in tables] # type: ignore[assignment]
|
|
226
|
+
|
|
227
|
+
lock = exp.Lock(**lock_args)
|
|
228
|
+
|
|
229
|
+
current_locks = select_expr.args.get("locks", [])
|
|
230
|
+
current_locks.append(lock)
|
|
231
|
+
select_expr.set("locks", current_locks)
|
|
232
|
+
|
|
233
|
+
return self
|
|
234
|
+
|
|
235
|
+
def for_share(
|
|
236
|
+
self, *, skip_locked: bool = False, nowait: bool = False, of: "Optional[Union[str, list[str]]]" = None
|
|
237
|
+
) -> "Self":
|
|
238
|
+
"""Add FOR SHARE clause for shared row-level locking.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
skip_locked: Skip rows that are already locked (SKIP LOCKED)
|
|
242
|
+
nowait: Return immediately if row is locked (NOWAIT)
|
|
243
|
+
of: Table names/aliases to lock (FOR SHARE OF table)
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Self for method chaining
|
|
247
|
+
"""
|
|
248
|
+
self._validate_select_expression()
|
|
249
|
+
self._validate_lock_parameters(skip_locked, nowait)
|
|
250
|
+
|
|
251
|
+
assert self._expression is not None
|
|
252
|
+
select_expr = cast("exp.Select", self._expression)
|
|
253
|
+
|
|
254
|
+
lock_args = {"update": False}
|
|
255
|
+
|
|
256
|
+
if skip_locked:
|
|
257
|
+
lock_args["wait"] = False
|
|
258
|
+
elif nowait:
|
|
259
|
+
lock_args["wait"] = True
|
|
260
|
+
|
|
261
|
+
if of:
|
|
262
|
+
tables = [of] if isinstance(of, str) else of
|
|
263
|
+
lock_args["expressions"] = [exp.table_(t) for t in tables] # type: ignore[assignment]
|
|
264
|
+
|
|
265
|
+
lock = exp.Lock(**lock_args)
|
|
266
|
+
|
|
267
|
+
current_locks = select_expr.args.get("locks", [])
|
|
268
|
+
current_locks.append(lock)
|
|
269
|
+
select_expr.set("locks", current_locks)
|
|
270
|
+
|
|
271
|
+
return self
|
|
272
|
+
|
|
273
|
+
def for_key_share(self) -> "Self":
|
|
274
|
+
"""Add FOR KEY SHARE clause (PostgreSQL-specific).
|
|
275
|
+
|
|
276
|
+
FOR KEY SHARE is like FOR SHARE, but the lock is weaker:
|
|
277
|
+
SELECT FOR UPDATE is blocked, but not SELECT FOR NO KEY UPDATE.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Self for method chaining
|
|
281
|
+
"""
|
|
282
|
+
self._validate_select_expression()
|
|
283
|
+
|
|
284
|
+
assert self._expression is not None
|
|
285
|
+
select_expr = cast("exp.Select", self._expression)
|
|
286
|
+
|
|
287
|
+
lock = exp.Lock(update=False, key=True)
|
|
288
|
+
|
|
289
|
+
current_locks = select_expr.args.get("locks", [])
|
|
290
|
+
current_locks.append(lock)
|
|
291
|
+
select_expr.set("locks", current_locks)
|
|
292
|
+
|
|
293
|
+
return self
|
|
294
|
+
|
|
295
|
+
def for_no_key_update(self) -> "Self":
|
|
296
|
+
"""Add FOR NO KEY UPDATE clause (PostgreSQL-specific).
|
|
297
|
+
|
|
298
|
+
FOR NO KEY UPDATE is like FOR UPDATE, but the lock is weaker:
|
|
299
|
+
it does not block SELECT FOR KEY SHARE commands that attempt to
|
|
300
|
+
acquire a share lock on the same rows.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
Self for method chaining
|
|
304
|
+
"""
|
|
305
|
+
self._validate_select_expression()
|
|
306
|
+
|
|
307
|
+
assert self._expression is not None
|
|
308
|
+
select_expr = cast("exp.Select", self._expression)
|
|
309
|
+
|
|
310
|
+
lock = exp.Lock(update=True, key=False)
|
|
311
|
+
|
|
312
|
+
current_locks = select_expr.args.get("locks", [])
|
|
313
|
+
current_locks.append(lock)
|
|
314
|
+
select_expr.set("locks", current_locks)
|
|
315
|
+
|
|
316
|
+
return self
|
|
@@ -117,8 +117,7 @@ class CommonTableExpressionMixin:
|
|
|
117
117
|
if recursive:
|
|
118
118
|
existing_with.set("recursive", recursive)
|
|
119
119
|
else:
|
|
120
|
-
|
|
121
|
-
if hasattr(expression, "with_") and isinstance(expression, (exp.Select, exp.Insert, exp.Update)):
|
|
120
|
+
if isinstance(expression, (exp.Select, exp.Insert, exp.Update)):
|
|
122
121
|
updated_expression = expression.with_(cte_alias_expr, as_=name, copy=False)
|
|
123
122
|
builder.set_expression(updated_expression)
|
|
124
123
|
if recursive:
|
|
@@ -82,16 +82,12 @@ class JoinClauseMixin:
|
|
|
82
82
|
self, table: Any, alias: Optional[str], builder: "SQLBuilderProtocol"
|
|
83
83
|
) -> exp.Expression:
|
|
84
84
|
"""Handle table parameters that are query builders."""
|
|
85
|
-
if hasattr(table, "_expression") and
|
|
86
|
-
|
|
87
|
-
if table_expr_value is not None:
|
|
88
|
-
subquery_exp = exp.paren(table_expr_value)
|
|
89
|
-
else:
|
|
90
|
-
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)
|
|
91
87
|
return exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
92
88
|
subquery = table.build()
|
|
93
89
|
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
94
|
-
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=
|
|
90
|
+
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=builder.dialect))
|
|
95
91
|
return exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
96
92
|
|
|
97
93
|
def _parse_on_condition(
|
|
@@ -107,28 +103,20 @@ class JoinClauseMixin:
|
|
|
107
103
|
return self._handle_sql_object_condition(on, builder)
|
|
108
104
|
if isinstance(on, exp.Expression):
|
|
109
105
|
return on
|
|
110
|
-
# Last resort - convert to string and parse
|
|
111
106
|
return exp.condition(str(on))
|
|
112
107
|
|
|
113
108
|
def _handle_sql_object_condition(self, on: Any, builder: "SQLBuilderProtocol") -> exp.Expression:
|
|
114
109
|
"""Handle SQL object conditions with parameter binding."""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if hasattr(on, "parameters") and hasattr(builder, "add_parameter"):
|
|
119
|
-
sql_parameters = getattr(on, "parameters", {})
|
|
120
|
-
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():
|
|
121
113
|
builder.add_parameter(param_value, name=param_name)
|
|
122
|
-
return cast("exp.Expression", expression)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
# Merge parameters even when parsing raw SQL
|
|
126
|
-
if hasattr(on, "parameters") and hasattr(builder, "add_parameter"):
|
|
127
|
-
sql_parameters = getattr(on, "parameters", {})
|
|
128
|
-
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():
|
|
129
117
|
builder.add_parameter(param_value, name=param_name)
|
|
130
|
-
parsed_expr = exp.maybe_parse(
|
|
131
|
-
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))
|
|
132
120
|
|
|
133
121
|
def _create_join_expression(
|
|
134
122
|
self, table_expr: exp.Expression, on_expr: Optional[exp.Expression], join_type: str
|
|
@@ -195,17 +183,13 @@ class JoinClauseMixin:
|
|
|
195
183
|
if isinstance(table, str):
|
|
196
184
|
table_expr = parse_table_expression(table, alias)
|
|
197
185
|
elif has_query_builder_parameters(table):
|
|
198
|
-
if hasattr(table, "_expression") and
|
|
199
|
-
|
|
200
|
-
if table_expr_value is not None:
|
|
201
|
-
subquery_exp = exp.paren(table_expr_value)
|
|
202
|
-
else:
|
|
203
|
-
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)
|
|
204
188
|
table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
205
189
|
else:
|
|
206
190
|
subquery = table.build()
|
|
207
191
|
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
208
|
-
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=
|
|
192
|
+
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=builder.dialect))
|
|
209
193
|
table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
210
194
|
else:
|
|
211
195
|
table_expr = table
|