sqlspec 0.23.0__py3-none-any.whl → 0.24.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 +10 -8
- sqlspec/builder/_base.py +68 -21
- sqlspec/builder/_ddl.py +14 -13
- sqlspec/builder/_insert.py +8 -16
- sqlspec/builder/_parsing_utils.py +16 -18
- sqlspec/builder/_update.py +1 -1
- sqlspec/builder/mixins/_cte_and_set_ops.py +20 -1
- sqlspec/builder/mixins/_where_clause.py +743 -124
- sqlspec/utils/type_guards.py +31 -0
- {sqlspec-0.23.0.dist-info → sqlspec-0.24.1.dist-info}/METADATA +1 -1
- {sqlspec-0.23.0.dist-info → sqlspec-0.24.1.dist-info}/RECORD +15 -15
- {sqlspec-0.23.0.dist-info → sqlspec-0.24.1.dist-info}/WHEEL +0 -0
- {sqlspec-0.23.0.dist-info → sqlspec-0.24.1.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.23.0.dist-info → sqlspec-0.24.1.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.23.0.dist-info → sqlspec-0.24.1.dist-info}/licenses/NOTICE +0 -0
sqlspec/_sql.py
CHANGED
|
@@ -137,21 +137,18 @@ class SQLFactory:
|
|
|
137
137
|
self.dialect = dialect
|
|
138
138
|
|
|
139
139
|
def __call__(self, statement: str, dialect: DialectType = None) -> "Any":
|
|
140
|
-
"""Create a SelectBuilder from a SQL string,
|
|
140
|
+
"""Create a SelectBuilder from a SQL string, or SQL object for DML with RETURNING.
|
|
141
141
|
|
|
142
142
|
Args:
|
|
143
143
|
statement: The SQL statement string.
|
|
144
|
-
parameters: Optional parameters for the query.
|
|
145
|
-
*filters: Optional filters.
|
|
146
|
-
config: Optional config.
|
|
147
144
|
dialect: Optional SQL dialect.
|
|
148
|
-
**kwargs: Additional parameters.
|
|
149
145
|
|
|
150
146
|
Returns:
|
|
151
|
-
SelectBuilder instance
|
|
147
|
+
SelectBuilder instance for SELECT/WITH statements,
|
|
148
|
+
SQL object for DML statements with RETURNING clause.
|
|
152
149
|
|
|
153
150
|
Raises:
|
|
154
|
-
SQLBuilderError: If the SQL is not a SELECT/CTE statement.
|
|
151
|
+
SQLBuilderError: If the SQL is not a SELECT/CTE/DML+RETURNING statement.
|
|
155
152
|
"""
|
|
156
153
|
|
|
157
154
|
try:
|
|
@@ -175,8 +172,13 @@ class SQLFactory:
|
|
|
175
172
|
builder = Select(dialect=dialect or self.dialect)
|
|
176
173
|
builder._expression = parsed_expr
|
|
177
174
|
return builder
|
|
175
|
+
|
|
176
|
+
if actual_type_str in {"INSERT", "UPDATE", "DELETE"} and parsed_expr.args.get("returning") is not None:
|
|
177
|
+
return SQL(statement)
|
|
178
|
+
|
|
178
179
|
msg = (
|
|
179
|
-
f"sql(...) only supports SELECT statements
|
|
180
|
+
f"sql(...) only supports SELECT statements or DML statements with RETURNING clause. "
|
|
181
|
+
f"Detected type: {actual_type_str}. "
|
|
180
182
|
f"Use sql.{actual_type_str.lower()}() instead."
|
|
181
183
|
)
|
|
182
184
|
raise SQLBuilderError(msg)
|
sqlspec/builder/_base.py
CHANGED
|
@@ -19,7 +19,7 @@ from sqlspec.core.parameters import ParameterStyle, ParameterStyleConfig
|
|
|
19
19
|
from sqlspec.core.statement import SQL, StatementConfig
|
|
20
20
|
from sqlspec.exceptions import SQLBuilderError
|
|
21
21
|
from sqlspec.utils.logging import get_logger
|
|
22
|
-
from sqlspec.utils.type_guards import has_sql_method, has_with_method
|
|
22
|
+
from sqlspec.utils.type_guards import has_expression_and_parameters, has_sql_method, has_with_method
|
|
23
23
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
25
|
from sqlspec.core.result import SQLResult
|
|
@@ -208,6 +208,43 @@ class QueryBuilder(ABC):
|
|
|
208
208
|
|
|
209
209
|
return f"{base_name}_{uuid.uuid4().hex[:8]}"
|
|
210
210
|
|
|
211
|
+
def _merge_cte_parameters(self, cte_name: str, parameters: dict[str, Any]) -> dict[str, str]:
|
|
212
|
+
"""Merge CTE parameters with unique naming to prevent collisions.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
cte_name: The name of the CTE for parameter prefixing
|
|
216
|
+
parameters: The CTE's parameter dictionary
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Mapping of old parameter names to new unique names
|
|
220
|
+
"""
|
|
221
|
+
param_mapping = {}
|
|
222
|
+
for old_name, value in parameters.items():
|
|
223
|
+
new_name = self._generate_unique_parameter_name(f"{cte_name}_{old_name}")
|
|
224
|
+
param_mapping[old_name] = new_name
|
|
225
|
+
self.add_parameter(value, name=new_name)
|
|
226
|
+
return param_mapping
|
|
227
|
+
|
|
228
|
+
def _update_placeholders_in_expression(
|
|
229
|
+
self, expression: exp.Expression, param_mapping: dict[str, str]
|
|
230
|
+
) -> exp.Expression:
|
|
231
|
+
"""Update parameter placeholders in expression to use new names.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
expression: The SQLGlot expression to update
|
|
235
|
+
param_mapping: Mapping of old parameter names to new names
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Updated expression with new placeholder names
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def placeholder_replacer(node: exp.Expression) -> exp.Expression:
|
|
242
|
+
if isinstance(node, exp.Placeholder) and str(node.this) in param_mapping:
|
|
243
|
+
return exp.Placeholder(this=param_mapping[str(node.this)])
|
|
244
|
+
return node
|
|
245
|
+
|
|
246
|
+
return expression.transform(placeholder_replacer, copy=False)
|
|
247
|
+
|
|
211
248
|
def _generate_builder_cache_key(self, config: "Optional[StatementConfig]" = None) -> str:
|
|
212
249
|
"""Generate cache key based on builder state and configuration.
|
|
213
250
|
|
|
@@ -276,9 +313,12 @@ class QueryBuilder(ABC):
|
|
|
276
313
|
msg = f"CTE query builder expression must be a Select, got {type(query._expression).__name__}."
|
|
277
314
|
self._raise_sql_builder_error(msg)
|
|
278
315
|
cte_select_expression = query._expression
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
316
|
+
param_mapping = self._merge_cte_parameters(alias, query.parameters)
|
|
317
|
+
updated_expression = self._update_placeholders_in_expression(cte_select_expression, param_mapping)
|
|
318
|
+
if not isinstance(updated_expression, exp.Select):
|
|
319
|
+
msg = f"Updated CTE expression must be a Select, got {type(updated_expression).__name__}."
|
|
320
|
+
self._raise_sql_builder_error(msg)
|
|
321
|
+
cte_select_expression = updated_expression
|
|
282
322
|
|
|
283
323
|
elif isinstance(query, str):
|
|
284
324
|
try:
|
|
@@ -297,7 +337,6 @@ class QueryBuilder(ABC):
|
|
|
297
337
|
else:
|
|
298
338
|
msg = f"Invalid query type for CTE: {type(query).__name__}"
|
|
299
339
|
self._raise_sql_builder_error(msg)
|
|
300
|
-
return self
|
|
301
340
|
|
|
302
341
|
self._with_ctes[alias] = exp.CTE(this=cte_select_expression, alias=exp.to_table(alias))
|
|
303
342
|
return self
|
|
@@ -416,7 +455,7 @@ class QueryBuilder(ABC):
|
|
|
416
455
|
|
|
417
456
|
if isinstance(safe_query.parameters, dict):
|
|
418
457
|
kwargs = safe_query.parameters
|
|
419
|
-
parameters: Optional[tuple] = None
|
|
458
|
+
parameters: Optional[tuple[Any, ...]] = None
|
|
420
459
|
else:
|
|
421
460
|
kwargs = None
|
|
422
461
|
parameters = (
|
|
@@ -440,7 +479,7 @@ class QueryBuilder(ABC):
|
|
|
440
479
|
config.dialect is not None
|
|
441
480
|
and config.dialect != safe_query.dialect
|
|
442
481
|
and self._expression is not None
|
|
443
|
-
and
|
|
482
|
+
and has_sql_method(self._expression)
|
|
444
483
|
):
|
|
445
484
|
try:
|
|
446
485
|
sql_string = self._expression.sql(dialect=config.dialect, pretty=True)
|
|
@@ -459,26 +498,34 @@ class QueryBuilder(ABC):
|
|
|
459
498
|
Returns:
|
|
460
499
|
str: The SQL string for this query.
|
|
461
500
|
"""
|
|
462
|
-
|
|
463
|
-
return self.build().sql
|
|
464
|
-
except Exception:
|
|
465
|
-
return super().__str__()
|
|
501
|
+
return self.build().sql
|
|
466
502
|
|
|
467
503
|
@property
|
|
468
504
|
def dialect_name(self) -> "Optional[str]":
|
|
469
505
|
"""Returns the name of the dialect, if set."""
|
|
470
506
|
if isinstance(self.dialect, str):
|
|
471
507
|
return self.dialect
|
|
472
|
-
if self.dialect is
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
508
|
+
if self.dialect is None:
|
|
509
|
+
return None
|
|
510
|
+
if isinstance(self.dialect, type) and issubclass(self.dialect, Dialect):
|
|
511
|
+
return self.dialect.__name__.lower()
|
|
512
|
+
if isinstance(self.dialect, Dialect):
|
|
513
|
+
return type(self.dialect).__name__.lower()
|
|
514
|
+
return getattr(self.dialect, "__name__", str(self.dialect)).lower()
|
|
515
|
+
|
|
516
|
+
def _merge_sql_object_parameters(self, sql_obj: Any) -> None:
|
|
517
|
+
"""Merge parameters from a SQL object into the builder.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
sql_obj: Object with parameters attribute containing parameter mappings
|
|
521
|
+
"""
|
|
522
|
+
if not has_expression_and_parameters(sql_obj):
|
|
523
|
+
return
|
|
524
|
+
|
|
525
|
+
sql_parameters = getattr(sql_obj, "parameters", {})
|
|
526
|
+
for param_name, param_value in sql_parameters.items():
|
|
527
|
+
unique_name = self._generate_unique_parameter_name(param_name)
|
|
528
|
+
self.add_parameter(param_value, name=unique_name)
|
|
482
529
|
|
|
483
530
|
@property
|
|
484
531
|
def parameters(self) -> dict[str, Any]:
|
sqlspec/builder/_ddl.py
CHANGED
|
@@ -12,6 +12,7 @@ from typing_extensions import Self
|
|
|
12
12
|
|
|
13
13
|
from sqlspec.builder._base import QueryBuilder, SafeQuery
|
|
14
14
|
from sqlspec.core.result import SQLResult
|
|
15
|
+
from sqlspec.utils.type_guards import has_sqlglot_expression, has_with_method
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
17
18
|
from sqlspec.builder._column import ColumnExpression
|
|
@@ -436,8 +437,8 @@ class CreateTable(DDLBuilder):
|
|
|
436
437
|
self._raise_sql_builder_error("Check constraint must have a condition")
|
|
437
438
|
|
|
438
439
|
condition_str: str
|
|
439
|
-
if
|
|
440
|
-
sqlglot_expr =
|
|
440
|
+
if has_sqlglot_expression(condition):
|
|
441
|
+
sqlglot_expr = condition.sqlglot_expression
|
|
441
442
|
condition_str = sqlglot_expr.sql(dialect=self.dialect) if sqlglot_expr else str(condition)
|
|
442
443
|
else:
|
|
443
444
|
condition_str = str(condition)
|
|
@@ -970,15 +971,15 @@ class CreateTableAsSelect(DDLBuilder):
|
|
|
970
971
|
|
|
971
972
|
if isinstance(self._select_query, SQL):
|
|
972
973
|
select_expr = self._select_query.expression
|
|
973
|
-
select_parameters =
|
|
974
|
+
select_parameters = self._select_query.parameters
|
|
974
975
|
elif isinstance(self._select_query, Select):
|
|
975
|
-
select_expr =
|
|
976
|
-
select_parameters =
|
|
976
|
+
select_expr = self._select_query._expression
|
|
977
|
+
select_parameters = self._select_query._parameters
|
|
977
978
|
|
|
978
|
-
with_ctes =
|
|
979
|
+
with_ctes = self._select_query._with_ctes
|
|
979
980
|
if with_ctes and select_expr and isinstance(select_expr, exp.Select):
|
|
980
981
|
for alias, cte in with_ctes.items():
|
|
981
|
-
if
|
|
982
|
+
if has_with_method(select_expr):
|
|
982
983
|
select_expr = select_expr.with_(cte.this, as_=alias, copy=False)
|
|
983
984
|
elif isinstance(self._select_query, str):
|
|
984
985
|
select_expr = exp.maybe_parse(self._select_query)
|
|
@@ -1097,10 +1098,10 @@ class CreateMaterializedView(DDLBuilder):
|
|
|
1097
1098
|
|
|
1098
1099
|
if isinstance(self._select_query, SQL):
|
|
1099
1100
|
select_expr = self._select_query.expression
|
|
1100
|
-
select_parameters =
|
|
1101
|
+
select_parameters = self._select_query.parameters
|
|
1101
1102
|
elif isinstance(self._select_query, Select):
|
|
1102
|
-
select_expr =
|
|
1103
|
-
select_parameters =
|
|
1103
|
+
select_expr = self._select_query._expression
|
|
1104
|
+
select_parameters = self._select_query._parameters
|
|
1104
1105
|
elif isinstance(self._select_query, str):
|
|
1105
1106
|
select_expr = exp.maybe_parse(self._select_query)
|
|
1106
1107
|
select_parameters = None
|
|
@@ -1195,10 +1196,10 @@ class CreateView(DDLBuilder):
|
|
|
1195
1196
|
|
|
1196
1197
|
if isinstance(self._select_query, SQL):
|
|
1197
1198
|
select_expr = self._select_query.expression
|
|
1198
|
-
select_parameters =
|
|
1199
|
+
select_parameters = self._select_query.parameters
|
|
1199
1200
|
elif isinstance(self._select_query, Select):
|
|
1200
|
-
select_expr =
|
|
1201
|
-
select_parameters =
|
|
1201
|
+
select_expr = self._select_query._expression
|
|
1202
|
+
select_parameters = self._select_query._parameters
|
|
1202
1203
|
elif isinstance(self._select_query, str):
|
|
1203
1204
|
select_expr = exp.maybe_parse(self._select_query)
|
|
1204
1205
|
select_parameters = None
|
sqlspec/builder/_insert.py
CHANGED
|
@@ -13,6 +13,7 @@ from sqlspec.builder._base import QueryBuilder
|
|
|
13
13
|
from sqlspec.builder.mixins import InsertFromSelectMixin, InsertIntoClauseMixin, InsertValuesMixin, ReturningClauseMixin
|
|
14
14
|
from sqlspec.core.result import SQLResult
|
|
15
15
|
from sqlspec.exceptions import SQLBuilderError
|
|
16
|
+
from sqlspec.utils.type_guards import has_expression_and_sql
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
18
19
|
from collections.abc import Mapping, Sequence
|
|
@@ -124,12 +125,9 @@ class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSe
|
|
|
124
125
|
return self.values_from_dict(kwargs)
|
|
125
126
|
|
|
126
127
|
if len(values) == 1:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return self.values_from_dict(values_0)
|
|
131
|
-
except (AttributeError, TypeError):
|
|
132
|
-
pass
|
|
128
|
+
values_0 = values[0]
|
|
129
|
+
if hasattr(values_0, "items") and hasattr(values_0, "keys"):
|
|
130
|
+
return self.values_from_dict(values_0)
|
|
133
131
|
|
|
134
132
|
insert_expr = self._get_insert_expression()
|
|
135
133
|
|
|
@@ -141,24 +139,18 @@ class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSe
|
|
|
141
139
|
for i, value in enumerate(values):
|
|
142
140
|
if isinstance(value, exp.Expression):
|
|
143
141
|
value_placeholders.append(value)
|
|
144
|
-
elif
|
|
142
|
+
elif has_expression_and_sql(value):
|
|
145
143
|
# Handle SQL objects (from sql.raw with parameters)
|
|
146
144
|
expression = getattr(value, "expression", None)
|
|
147
145
|
if expression is not None and isinstance(expression, exp.Expression):
|
|
148
146
|
# Merge parameters from SQL object into builder
|
|
149
|
-
|
|
150
|
-
sql_parameters = getattr(value, "parameters", {})
|
|
151
|
-
for param_name, param_value in sql_parameters.items():
|
|
152
|
-
self.add_parameter(param_value, name=param_name)
|
|
147
|
+
self._merge_sql_object_parameters(value)
|
|
153
148
|
value_placeholders.append(expression)
|
|
154
149
|
else:
|
|
155
150
|
# If expression is None, fall back to parsing the raw SQL
|
|
156
151
|
sql_text = getattr(value, "sql", "")
|
|
157
152
|
# Merge parameters even when parsing raw SQL
|
|
158
|
-
|
|
159
|
-
sql_parameters = getattr(value, "parameters", {})
|
|
160
|
-
for param_name, param_value in sql_parameters.items():
|
|
161
|
-
self.add_parameter(param_value, name=param_name)
|
|
153
|
+
self._merge_sql_object_parameters(value)
|
|
162
154
|
# Check if sql_text is callable (like Expression.sql method)
|
|
163
155
|
if callable(sql_text):
|
|
164
156
|
sql_text = str(value)
|
|
@@ -376,7 +368,7 @@ class ConflictBuilder:
|
|
|
376
368
|
# Create SET expressions for the UPDATE
|
|
377
369
|
set_expressions = []
|
|
378
370
|
for col, val in kwargs.items():
|
|
379
|
-
if
|
|
371
|
+
if has_expression_and_sql(val):
|
|
380
372
|
# Handle SQL objects (from sql.raw with parameters)
|
|
381
373
|
expression = getattr(val, "expression", None)
|
|
382
374
|
if expression is not None and isinstance(expression, exp.Expression):
|
|
@@ -10,7 +10,12 @@ from typing import Any, Final, Optional, Union, cast
|
|
|
10
10
|
from sqlglot import exp, maybe_parse, parse_one
|
|
11
11
|
|
|
12
12
|
from sqlspec.core.parameters import ParameterStyle
|
|
13
|
-
from sqlspec.utils.type_guards import
|
|
13
|
+
from sqlspec.utils.type_guards import (
|
|
14
|
+
has_expression_and_parameters,
|
|
15
|
+
has_expression_and_sql,
|
|
16
|
+
has_expression_attr,
|
|
17
|
+
has_parameter_builder,
|
|
18
|
+
)
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
def parse_column_expression(
|
|
@@ -38,12 +43,12 @@ def parse_column_expression(
|
|
|
38
43
|
return column_input
|
|
39
44
|
|
|
40
45
|
# Handle SQL objects (from sql.raw with parameters)
|
|
41
|
-
if
|
|
46
|
+
if has_expression_and_sql(column_input):
|
|
42
47
|
# This is likely a SQL object
|
|
43
48
|
expression = getattr(column_input, "expression", None)
|
|
44
49
|
if expression is not None and isinstance(expression, exp.Expression):
|
|
45
50
|
# Merge parameters from SQL object into builder if available
|
|
46
|
-
if builder and
|
|
51
|
+
if builder and has_expression_and_parameters(column_input) and hasattr(builder, "add_parameter"):
|
|
47
52
|
sql_parameters = getattr(column_input, "parameters", {})
|
|
48
53
|
for param_name, param_value in sql_parameters.items():
|
|
49
54
|
builder.add_parameter(param_value, name=param_name)
|
|
@@ -51,19 +56,16 @@ def parse_column_expression(
|
|
|
51
56
|
# If expression is None, fall back to parsing the raw SQL
|
|
52
57
|
sql_text = getattr(column_input, "sql", "")
|
|
53
58
|
# Merge parameters even when parsing raw SQL
|
|
54
|
-
if builder and
|
|
59
|
+
if builder and has_expression_and_parameters(column_input) and hasattr(builder, "add_parameter"):
|
|
55
60
|
sql_parameters = getattr(column_input, "parameters", {})
|
|
56
61
|
for param_name, param_value in sql_parameters.items():
|
|
57
62
|
builder.add_parameter(param_value, name=param_name)
|
|
58
63
|
return exp.maybe_parse(sql_text) or exp.column(str(sql_text))
|
|
59
64
|
|
|
60
65
|
if has_expression_attr(column_input):
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return attr_value
|
|
65
|
-
except AttributeError:
|
|
66
|
-
pass
|
|
66
|
+
attr_value = getattr(column_input, "_expression", None)
|
|
67
|
+
if isinstance(attr_value, exp.Expression):
|
|
68
|
+
return attr_value
|
|
67
69
|
|
|
68
70
|
return exp.maybe_parse(column_input) or exp.column(str(column_input))
|
|
69
71
|
|
|
@@ -178,14 +180,10 @@ def parse_condition_expression(
|
|
|
178
180
|
)
|
|
179
181
|
condition_input = converted_condition
|
|
180
182
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
parsed = exp.maybe_parse(condition_input) # type: ignore[var-annotated]
|
|
186
|
-
return parsed or exp.condition(condition_input)
|
|
187
|
-
except Exception:
|
|
188
|
-
return exp.condition(condition_input)
|
|
183
|
+
parsed: Optional[exp.Expression] = exp.maybe_parse(condition_input)
|
|
184
|
+
if parsed:
|
|
185
|
+
return parsed
|
|
186
|
+
return exp.condition(condition_input)
|
|
189
187
|
|
|
190
188
|
|
|
191
189
|
__all__ = ("parse_column_expression", "parse_condition_expression", "parse_order_expression", "parse_table_expression")
|
sqlspec/builder/_update.py
CHANGED
|
@@ -176,7 +176,7 @@ class Update(
|
|
|
176
176
|
msg = "No UPDATE expression to build or expression is of the wrong type."
|
|
177
177
|
raise SQLBuilderError(msg)
|
|
178
178
|
|
|
179
|
-
if
|
|
179
|
+
if self._expression.this is None:
|
|
180
180
|
msg = "No table specified for UPDATE statement."
|
|
181
181
|
raise SQLBuilderError(msg)
|
|
182
182
|
|
|
@@ -31,6 +31,18 @@ class CommonTableExpressionMixin:
|
|
|
31
31
|
msg = "Method must be provided by QueryBuilder subclass"
|
|
32
32
|
raise NotImplementedError(msg)
|
|
33
33
|
|
|
34
|
+
def _generate_unique_parameter_name(self, base_name: str) -> str:
|
|
35
|
+
"""Generate unique parameter name - provided by QueryBuilder."""
|
|
36
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
37
|
+
raise NotImplementedError(msg)
|
|
38
|
+
|
|
39
|
+
def _update_placeholders_in_expression(
|
|
40
|
+
self, expression: exp.Expression, param_mapping: dict[str, str]
|
|
41
|
+
) -> exp.Expression:
|
|
42
|
+
"""Update parameter placeholders - provided by QueryBuilder."""
|
|
43
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
44
|
+
raise NotImplementedError(msg)
|
|
45
|
+
|
|
34
46
|
def with_(
|
|
35
47
|
self, name: str, query: Union[Any, str], recursive: bool = False, columns: Optional[list[str]] = None
|
|
36
48
|
) -> Self:
|
|
@@ -69,8 +81,15 @@ class CommonTableExpressionMixin:
|
|
|
69
81
|
parameters = built_query.parameters
|
|
70
82
|
if parameters:
|
|
71
83
|
if isinstance(parameters, dict):
|
|
84
|
+
param_mapping = {}
|
|
72
85
|
for param_name, param_value in parameters.items():
|
|
73
|
-
self.
|
|
86
|
+
unique_name = self._generate_unique_parameter_name(f"{name}_{param_name}")
|
|
87
|
+
param_mapping[param_name] = unique_name
|
|
88
|
+
self.add_parameter(param_value, name=unique_name)
|
|
89
|
+
|
|
90
|
+
# Update placeholders in the parsed expression
|
|
91
|
+
if cte_expr and param_mapping:
|
|
92
|
+
cte_expr = self._update_placeholders_in_expression(cte_expr, param_mapping)
|
|
74
93
|
elif isinstance(parameters, (list, tuple)):
|
|
75
94
|
for param_value in parameters:
|
|
76
95
|
self.add_parameter(param_value)
|