sqlspec 0.14.1__py3-none-any.whl → 0.15.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/__init__.py +50 -25
- sqlspec/__main__.py +1 -1
- sqlspec/__metadata__.py +1 -3
- sqlspec/_serialization.py +1 -2
- sqlspec/_sql.py +256 -120
- sqlspec/_typing.py +278 -142
- sqlspec/adapters/adbc/__init__.py +4 -3
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +115 -260
- sqlspec/adapters/adbc/driver.py +462 -367
- sqlspec/adapters/aiosqlite/__init__.py +18 -3
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +199 -129
- sqlspec/adapters/aiosqlite/driver.py +230 -269
- sqlspec/adapters/asyncmy/__init__.py +18 -3
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +80 -168
- sqlspec/adapters/asyncmy/driver.py +260 -225
- sqlspec/adapters/asyncpg/__init__.py +19 -4
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +82 -181
- sqlspec/adapters/asyncpg/driver.py +285 -383
- sqlspec/adapters/bigquery/__init__.py +17 -3
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +191 -258
- sqlspec/adapters/bigquery/driver.py +474 -646
- sqlspec/adapters/duckdb/__init__.py +14 -3
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +415 -351
- sqlspec/adapters/duckdb/driver.py +343 -413
- sqlspec/adapters/oracledb/__init__.py +19 -5
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +123 -379
- sqlspec/adapters/oracledb/driver.py +507 -560
- sqlspec/adapters/psqlpy/__init__.py +13 -3
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +93 -254
- sqlspec/adapters/psqlpy/driver.py +505 -234
- sqlspec/adapters/psycopg/__init__.py +19 -5
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +143 -403
- sqlspec/adapters/psycopg/driver.py +706 -872
- sqlspec/adapters/sqlite/__init__.py +14 -3
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +202 -118
- sqlspec/adapters/sqlite/driver.py +264 -303
- sqlspec/base.py +105 -9
- sqlspec/{statement/builder → builder}/__init__.py +12 -14
- sqlspec/{statement/builder → builder}/_base.py +120 -55
- sqlspec/{statement/builder → builder}/_column.py +17 -6
- sqlspec/{statement/builder → builder}/_ddl.py +46 -79
- sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
- sqlspec/{statement/builder → builder}/_delete.py +6 -25
- sqlspec/{statement/builder → builder}/_insert.py +6 -64
- sqlspec/builder/_merge.py +56 -0
- sqlspec/{statement/builder → builder}/_parsing_utils.py +3 -10
- sqlspec/{statement/builder → builder}/_select.py +11 -56
- sqlspec/{statement/builder → builder}/_update.py +12 -18
- sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
- sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
- sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +22 -16
- sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
- sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +3 -5
- sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
- sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
- sqlspec/{statement/builder → builder}/mixins/_select_operations.py +21 -36
- sqlspec/{statement/builder → builder}/mixins/_update_operations.py +3 -14
- sqlspec/{statement/builder → builder}/mixins/_where_clause.py +52 -79
- sqlspec/cli.py +4 -5
- sqlspec/config.py +180 -133
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.py +873 -0
- sqlspec/core/compiler.py +396 -0
- sqlspec/core/filters.py +828 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.py +1209 -0
- sqlspec/core/result.py +664 -0
- sqlspec/{statement → core}/splitter.py +321 -191
- sqlspec/core/statement.py +651 -0
- sqlspec/driver/__init__.py +7 -10
- sqlspec/driver/_async.py +387 -176
- sqlspec/driver/_common.py +527 -289
- sqlspec/driver/_sync.py +390 -172
- sqlspec/driver/mixins/__init__.py +2 -19
- sqlspec/driver/mixins/_result_tools.py +168 -0
- sqlspec/driver/mixins/_sql_translator.py +6 -3
- sqlspec/exceptions.py +5 -252
- sqlspec/extensions/aiosql/adapter.py +93 -96
- sqlspec/extensions/litestar/config.py +0 -1
- sqlspec/extensions/litestar/handlers.py +15 -26
- sqlspec/extensions/litestar/plugin.py +16 -14
- sqlspec/extensions/litestar/providers.py +17 -52
- sqlspec/loader.py +424 -105
- sqlspec/migrations/__init__.py +12 -0
- sqlspec/migrations/base.py +92 -68
- sqlspec/migrations/commands.py +24 -106
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +49 -51
- sqlspec/migrations/tracker.py +31 -44
- sqlspec/migrations/utils.py +64 -24
- sqlspec/protocols.py +7 -183
- sqlspec/storage/__init__.py +1 -1
- sqlspec/storage/backends/base.py +37 -40
- sqlspec/storage/backends/fsspec.py +136 -112
- sqlspec/storage/backends/obstore.py +138 -160
- sqlspec/storage/capabilities.py +5 -4
- sqlspec/storage/registry.py +57 -106
- sqlspec/typing.py +136 -115
- sqlspec/utils/__init__.py +2 -3
- sqlspec/utils/correlation.py +0 -3
- sqlspec/utils/deprecation.py +6 -6
- sqlspec/utils/fixtures.py +6 -6
- sqlspec/utils/logging.py +0 -2
- sqlspec/utils/module_loader.py +7 -12
- sqlspec/utils/singleton.py +0 -1
- sqlspec/utils/sync_tools.py +16 -37
- sqlspec/utils/text.py +12 -51
- sqlspec/utils/type_guards.py +443 -232
- {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/METADATA +7 -2
- sqlspec-0.15.0.dist-info/RECORD +134 -0
- sqlspec/adapters/adbc/transformers.py +0 -108
- sqlspec/driver/connection.py +0 -207
- sqlspec/driver/mixins/_cache.py +0 -114
- sqlspec/driver/mixins/_csv_writer.py +0 -91
- sqlspec/driver/mixins/_pipeline.py +0 -508
- sqlspec/driver/mixins/_query_tools.py +0 -796
- sqlspec/driver/mixins/_result_utils.py +0 -138
- sqlspec/driver/mixins/_storage.py +0 -912
- sqlspec/driver/mixins/_type_coercion.py +0 -128
- sqlspec/driver/parameters.py +0 -138
- sqlspec/statement/__init__.py +0 -21
- sqlspec/statement/builder/_merge.py +0 -95
- sqlspec/statement/cache.py +0 -50
- sqlspec/statement/filters.py +0 -625
- sqlspec/statement/parameters.py +0 -956
- sqlspec/statement/pipelines/__init__.py +0 -210
- sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
- sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
- sqlspec/statement/pipelines/context.py +0 -109
- sqlspec/statement/pipelines/transformers/__init__.py +0 -7
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
- sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
- sqlspec/statement/pipelines/validators/__init__.py +0 -23
- sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
- sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
- sqlspec/statement/pipelines/validators/_performance.py +0 -714
- sqlspec/statement/pipelines/validators/_security.py +0 -967
- sqlspec/statement/result.py +0 -435
- sqlspec/statement/sql.py +0 -1774
- sqlspec/utils/cached_property.py +0 -25
- sqlspec/utils/statement_hashing.py +0 -203
- sqlspec-0.14.1.dist-info/RECORD +0 -145
- /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -7,7 +7,7 @@ from sqlglot import exp
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from sqlglot.dialects.dialect import DialectType
|
|
9
9
|
|
|
10
|
-
from sqlspec.
|
|
10
|
+
from sqlspec.builder._select import Select
|
|
11
11
|
|
|
12
12
|
__all__ = ("PivotClauseMixin", "UnpivotClauseMixin")
|
|
13
13
|
|
|
@@ -56,12 +56,10 @@ class PivotClauseMixin:
|
|
|
56
56
|
for val in pivot_values:
|
|
57
57
|
if isinstance(val, exp.Expression):
|
|
58
58
|
pivot_value_exprs.append(val)
|
|
59
|
-
elif isinstance(val, str):
|
|
60
|
-
pivot_value_exprs.append(exp.
|
|
61
|
-
elif isinstance(val, (int, float)):
|
|
62
|
-
pivot_value_exprs.append(exp.Literal.number(val))
|
|
59
|
+
elif isinstance(val, (str, int, float)):
|
|
60
|
+
pivot_value_exprs.append(exp.convert(val))
|
|
63
61
|
else:
|
|
64
|
-
pivot_value_exprs.append(exp.
|
|
62
|
+
pivot_value_exprs.append(exp.convert(str(val)))
|
|
65
63
|
|
|
66
64
|
in_expr = exp.In(this=pivot_col_expr, expressions=pivot_value_exprs)
|
|
67
65
|
|
|
@@ -113,7 +111,6 @@ class UnpivotClauseMixin:
|
|
|
113
111
|
"""
|
|
114
112
|
current_expr = self._expression
|
|
115
113
|
if not isinstance(current_expr, exp.Select):
|
|
116
|
-
# SelectBuilder's __init__ ensures _expression is exp.Select.
|
|
117
114
|
msg = "Unpivot can only be applied to a Select expression managed by Select."
|
|
118
115
|
raise TypeError(msg)
|
|
119
116
|
|
|
@@ -127,7 +124,6 @@ class UnpivotClauseMixin:
|
|
|
127
124
|
elif isinstance(col_name_or_expr, str):
|
|
128
125
|
unpivot_cols_exprs.append(exp.column(col_name_or_expr))
|
|
129
126
|
else:
|
|
130
|
-
# Fallback for other types, should ideally be an error or more specific handling
|
|
131
127
|
unpivot_cols_exprs.append(exp.column(str(col_name_or_expr)))
|
|
132
128
|
|
|
133
129
|
in_expr = exp.In(this=name_col_ident, expressions=unpivot_cols_exprs)
|
|
@@ -6,15 +6,14 @@ from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
|
6
6
|
from sqlglot import exp
|
|
7
7
|
from typing_extensions import Self
|
|
8
8
|
|
|
9
|
+
from sqlspec.builder._parsing_utils import parse_column_expression, parse_table_expression
|
|
9
10
|
from sqlspec.exceptions import SQLBuilderError
|
|
10
|
-
from sqlspec.statement.builder._parsing_utils import parse_column_expression, parse_table_expression
|
|
11
11
|
from sqlspec.utils.type_guards import has_query_builder_parameters, is_expression
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
|
+
from sqlspec.builder._base import QueryBuilder
|
|
15
|
+
from sqlspec.builder._column import Column, FunctionColumn
|
|
14
16
|
from sqlspec.protocols import SelectBuilderProtocol, SQLBuilderProtocol
|
|
15
|
-
from sqlspec.statement.builder._base import QueryBuilder
|
|
16
|
-
from sqlspec.statement.builder._column import Column, FunctionColumn
|
|
17
|
-
from sqlspec.typing import RowT
|
|
18
17
|
|
|
19
18
|
__all__ = ("CaseBuilder", "SelectClauseMixin")
|
|
20
19
|
|
|
@@ -24,7 +23,6 @@ class SelectClauseMixin:
|
|
|
24
23
|
|
|
25
24
|
_expression: Optional[exp.Expression] = None
|
|
26
25
|
|
|
27
|
-
# SELECT and DISTINCT methods
|
|
28
26
|
def select(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn"]) -> Self:
|
|
29
27
|
"""Add columns to SELECT clause.
|
|
30
28
|
|
|
@@ -69,7 +67,6 @@ class SelectClauseMixin:
|
|
|
69
67
|
builder._expression.set("distinct", exp.Distinct(expressions=distinct_columns))
|
|
70
68
|
return cast("Self", builder)
|
|
71
69
|
|
|
72
|
-
# FROM clause method
|
|
73
70
|
def from_(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
|
|
74
71
|
"""Add FROM clause.
|
|
75
72
|
|
|
@@ -93,30 +90,27 @@ class SelectClauseMixin:
|
|
|
93
90
|
if isinstance(table, str):
|
|
94
91
|
from_expr = parse_table_expression(table, alias)
|
|
95
92
|
elif is_expression(table):
|
|
96
|
-
# Direct sqlglot expression - use as is
|
|
97
93
|
from_expr = exp.alias_(table, alias) if alias else table
|
|
98
94
|
elif has_query_builder_parameters(table):
|
|
99
|
-
# Query builder with build() method
|
|
100
95
|
subquery = table.build()
|
|
101
96
|
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
102
97
|
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect", None)))
|
|
103
98
|
from_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
parameters=
|
|
110
|
-
args=
|
|
111
|
-
kwargs=
|
|
99
|
+
current_parameters = getattr(builder, "_parameters", None)
|
|
100
|
+
merged_parameters = getattr(type(builder), "ParameterConverter", None)
|
|
101
|
+
if merged_parameters and hasattr(subquery, "parameters"):
|
|
102
|
+
subquery_parameters = getattr(subquery, "parameters", {})
|
|
103
|
+
merged_parameters = merged_parameters.merge_parameters(
|
|
104
|
+
parameters=subquery_parameters,
|
|
105
|
+
args=current_parameters if isinstance(current_parameters, list) else None,
|
|
106
|
+
kwargs=current_parameters if isinstance(current_parameters, dict) else {},
|
|
112
107
|
)
|
|
113
|
-
setattr(builder, "_parameters",
|
|
108
|
+
setattr(builder, "_parameters", merged_parameters)
|
|
114
109
|
else:
|
|
115
110
|
from_expr = table
|
|
116
111
|
builder._expression = builder._expression.from_(from_expr, copy=False)
|
|
117
112
|
return cast("Self", builder)
|
|
118
113
|
|
|
119
|
-
# GROUP BY methods
|
|
120
114
|
def group_by(self, *columns: Union[str, exp.Expression]) -> Self:
|
|
121
115
|
"""Add GROUP BY clause.
|
|
122
116
|
|
|
@@ -149,7 +143,6 @@ class SelectClauseMixin:
|
|
|
149
143
|
|
|
150
144
|
Example:
|
|
151
145
|
```python
|
|
152
|
-
# GROUP BY ROLLUP(product, region)
|
|
153
146
|
query = (
|
|
154
147
|
sql.select("product", "region", sql.sum("sales"))
|
|
155
148
|
.from_("sales_data")
|
|
@@ -174,7 +167,6 @@ class SelectClauseMixin:
|
|
|
174
167
|
|
|
175
168
|
Example:
|
|
176
169
|
```python
|
|
177
|
-
# GROUP BY CUBE(product, region)
|
|
178
170
|
query = (
|
|
179
171
|
sql.select("product", "region", sql.sum("sales"))
|
|
180
172
|
.from_("sales_data")
|
|
@@ -200,7 +192,6 @@ class SelectClauseMixin:
|
|
|
200
192
|
|
|
201
193
|
Example:
|
|
202
194
|
```python
|
|
203
|
-
# GROUP BY GROUPING SETS ((product), (region), ())
|
|
204
195
|
query = (
|
|
205
196
|
sql.select("product", "region", sql.sum("sales"))
|
|
206
197
|
.from_("sales_data")
|
|
@@ -217,13 +208,11 @@ class SelectClauseMixin:
|
|
|
217
208
|
columns = [exp.column(col) for col in column_set]
|
|
218
209
|
set_expressions.append(exp.Tuple(expressions=columns))
|
|
219
210
|
else:
|
|
220
|
-
# Single column
|
|
221
211
|
set_expressions.append(exp.column(column_set))
|
|
222
212
|
|
|
223
213
|
grouping_sets_expr = exp.GroupingSets(expressions=set_expressions)
|
|
224
214
|
return self.group_by(grouping_sets_expr)
|
|
225
215
|
|
|
226
|
-
# Aggregate function methods
|
|
227
216
|
def count_(self, column: "Union[str, exp.Expression]" = "*", alias: Optional[str] = None) -> Self:
|
|
228
217
|
"""Add COUNT function to SELECT clause.
|
|
229
218
|
|
|
@@ -440,8 +429,7 @@ class SelectClauseMixin:
|
|
|
440
429
|
"""
|
|
441
430
|
builder = cast("SelectBuilderProtocol", self)
|
|
442
431
|
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
443
|
-
|
|
444
|
-
string_agg_expr = exp.GroupConcat(this=col_expr, separator=exp.Literal.string(separator))
|
|
432
|
+
string_agg_expr = exp.GroupConcat(this=col_expr, separator=exp.convert(separator))
|
|
445
433
|
select_expr = exp.alias_(string_agg_expr, alias) if alias else string_agg_expr
|
|
446
434
|
return cast("Self", builder.select(select_expr))
|
|
447
435
|
|
|
@@ -461,7 +449,6 @@ class SelectClauseMixin:
|
|
|
461
449
|
select_expr = exp.alias_(json_agg_expr, alias) if alias else json_agg_expr
|
|
462
450
|
return cast("Self", builder.select(select_expr))
|
|
463
451
|
|
|
464
|
-
# Window function method
|
|
465
452
|
def window(
|
|
466
453
|
self,
|
|
467
454
|
function_expr: Union[str, exp.Expression],
|
|
@@ -501,20 +488,19 @@ class SelectClauseMixin:
|
|
|
501
488
|
else:
|
|
502
489
|
func_expr_parsed = function_expr
|
|
503
490
|
|
|
504
|
-
over_args: dict[str, Any] = {}
|
|
491
|
+
over_args: dict[str, Any] = {}
|
|
505
492
|
if partition_by:
|
|
506
493
|
if isinstance(partition_by, str):
|
|
507
494
|
over_args["partition_by"] = [exp.column(partition_by)]
|
|
508
|
-
elif isinstance(partition_by, list):
|
|
495
|
+
elif isinstance(partition_by, list):
|
|
509
496
|
over_args["partition_by"] = [exp.column(col) if isinstance(col, str) else col for col in partition_by]
|
|
510
|
-
elif isinstance(partition_by, exp.Expression):
|
|
497
|
+
elif isinstance(partition_by, exp.Expression):
|
|
511
498
|
over_args["partition_by"] = [partition_by]
|
|
512
499
|
|
|
513
500
|
if order_by:
|
|
514
501
|
if isinstance(order_by, str):
|
|
515
502
|
over_args["order"] = exp.column(order_by).asc()
|
|
516
503
|
elif isinstance(order_by, list):
|
|
517
|
-
# Properly handle multiple ORDER BY columns using Order expression
|
|
518
504
|
order_expressions: list[Union[exp.Expression, exp.Column]] = []
|
|
519
505
|
for col in order_by:
|
|
520
506
|
if isinstance(col, str):
|
|
@@ -534,7 +520,6 @@ class SelectClauseMixin:
|
|
|
534
520
|
self._expression.select(exp.alias_(window_expr, alias) if alias else window_expr, copy=False)
|
|
535
521
|
return self
|
|
536
522
|
|
|
537
|
-
# CASE expression method
|
|
538
523
|
def case_(self, alias: "Optional[str]" = None) -> "CaseBuilder":
|
|
539
524
|
"""Create a CASE expression for the SELECT clause.
|
|
540
525
|
|
|
@@ -544,7 +529,7 @@ class SelectClauseMixin:
|
|
|
544
529
|
Returns:
|
|
545
530
|
CaseBuilder: A CaseBuilder instance for building the CASE expression.
|
|
546
531
|
"""
|
|
547
|
-
builder = cast("QueryBuilder
|
|
532
|
+
builder = cast("QueryBuilder", self) # pyright: ignore
|
|
548
533
|
return CaseBuilder(builder, alias)
|
|
549
534
|
|
|
550
535
|
|
|
@@ -552,11 +537,11 @@ class SelectClauseMixin:
|
|
|
552
537
|
class CaseBuilder:
|
|
553
538
|
"""Builder for CASE expressions."""
|
|
554
539
|
|
|
555
|
-
_parent: "QueryBuilder
|
|
540
|
+
_parent: "QueryBuilder" # pyright: ignore
|
|
556
541
|
_alias: Optional[str]
|
|
557
542
|
_case_expr: exp.Case
|
|
558
543
|
|
|
559
|
-
def __init__(self, parent: "QueryBuilder
|
|
544
|
+
def __init__(self, parent: "QueryBuilder", alias: "Optional[str]" = None) -> None:
|
|
560
545
|
"""Initialize CaseBuilder.
|
|
561
546
|
|
|
562
547
|
Args:
|
|
@@ -602,11 +587,11 @@ class CaseBuilder:
|
|
|
602
587
|
self._case_expr.set("default", value_expr)
|
|
603
588
|
return self
|
|
604
589
|
|
|
605
|
-
def end(self) -> "QueryBuilder
|
|
590
|
+
def end(self) -> "QueryBuilder":
|
|
606
591
|
"""Finalize the CASE expression and add it to the SELECT clause.
|
|
607
592
|
|
|
608
593
|
Returns:
|
|
609
594
|
The parent builder instance.
|
|
610
595
|
"""
|
|
611
596
|
select_expr = exp.alias_(self._case_expr, self._alias) if self._alias else self._case_expr
|
|
612
|
-
return cast("QueryBuilder
|
|
597
|
+
return cast("QueryBuilder", self._parent.select(select_expr)) # type: ignore[attr-defined]
|
|
@@ -68,20 +68,15 @@ class UpdateSetClauseMixin:
|
|
|
68
68
|
msg = "Cannot add SET clause to non-UPDATE expression."
|
|
69
69
|
raise SQLBuilderError(msg)
|
|
70
70
|
assignments = []
|
|
71
|
-
# (column, value) signature
|
|
72
71
|
if len(args) == MIN_SET_ARGS and not kwargs:
|
|
73
72
|
col, val = args
|
|
74
73
|
col_expr = col if isinstance(col, exp.Column) else exp.column(col)
|
|
75
|
-
# If value is an expression, use it directly
|
|
76
74
|
if isinstance(val, exp.Expression):
|
|
77
75
|
value_expr = val
|
|
78
76
|
elif has_query_builder_parameters(val):
|
|
79
|
-
# It's a builder (like SelectBuilder), convert to subquery
|
|
80
77
|
subquery = val.build()
|
|
81
|
-
# Parse the SQL and use as expression
|
|
82
78
|
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
83
79
|
value_expr = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect", None)))
|
|
84
|
-
# Merge parameters from subquery
|
|
85
80
|
if has_query_builder_parameters(val):
|
|
86
81
|
for p_name, p_value in val.parameters.items():
|
|
87
82
|
self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
|
|
@@ -89,20 +84,15 @@ class UpdateSetClauseMixin:
|
|
|
89
84
|
param_name = self.add_parameter(val)[1] # type: ignore[attr-defined]
|
|
90
85
|
value_expr = exp.Placeholder(this=param_name)
|
|
91
86
|
assignments.append(exp.EQ(this=col_expr, expression=value_expr))
|
|
92
|
-
# mapping and/or kwargs
|
|
93
87
|
elif (len(args) == 1 and isinstance(args[0], Mapping)) or kwargs:
|
|
94
88
|
all_values = dict(args[0] if args else {}, **kwargs)
|
|
95
89
|
for col, val in all_values.items():
|
|
96
|
-
# If value is an expression, use it directly
|
|
97
90
|
if isinstance(val, exp.Expression):
|
|
98
91
|
value_expr = val
|
|
99
92
|
elif has_query_builder_parameters(val):
|
|
100
|
-
# It's a builder (like SelectBuilder), convert to subquery
|
|
101
93
|
subquery = val.build()
|
|
102
|
-
# Parse the SQL and use as expression
|
|
103
94
|
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
104
95
|
value_expr = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect", None)))
|
|
105
|
-
# Merge parameters from subquery
|
|
106
96
|
if has_query_builder_parameters(val):
|
|
107
97
|
for p_name, p_value in val.parameters.items():
|
|
108
98
|
self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
|
|
@@ -113,7 +103,6 @@ class UpdateSetClauseMixin:
|
|
|
113
103
|
else:
|
|
114
104
|
msg = "Invalid arguments for set(): use (column, value), mapping, or kwargs."
|
|
115
105
|
raise SQLBuilderError(msg)
|
|
116
|
-
# Append to existing expressions instead of replacing
|
|
117
106
|
existing = self._expression.args.get("expressions", [])
|
|
118
107
|
self._expression.set("expressions", existing + assignments)
|
|
119
108
|
return self
|
|
@@ -142,9 +131,9 @@ class UpdateFromClauseMixin:
|
|
|
142
131
|
if isinstance(table, str):
|
|
143
132
|
table_expr = exp.to_table(table, alias=alias)
|
|
144
133
|
elif has_query_builder_parameters(table):
|
|
145
|
-
|
|
146
|
-
if
|
|
147
|
-
for p_name, p_value in
|
|
134
|
+
subquery_builder_parameters = getattr(table, "_parameters", None)
|
|
135
|
+
if subquery_builder_parameters:
|
|
136
|
+
for p_name, p_value in subquery_builder_parameters.items():
|
|
148
137
|
self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
|
|
149
138
|
subquery_exp = exp.paren(getattr(table, "_expression", exp.select()))
|
|
150
139
|
table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# ruff: noqa: PLR2004
|
|
2
2
|
"""Consolidated WHERE and HAVING clause mixins."""
|
|
3
3
|
|
|
4
|
-
from typing import TYPE_CHECKING, Any,
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
5
5
|
|
|
6
6
|
from sqlglot import exp
|
|
7
7
|
from typing_extensions import Self
|
|
8
8
|
|
|
9
|
+
from sqlspec.builder._parsing_utils import parse_column_expression, parse_condition_expression
|
|
9
10
|
from sqlspec.exceptions import SQLBuilderError
|
|
10
|
-
from sqlspec.statement.builder._parsing_utils import parse_column_expression, parse_condition_expression
|
|
11
11
|
from sqlspec.utils.type_guards import has_query_builder_parameters, has_sqlglot_expression, is_iterable_parameters
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
|
+
from sqlspec.builder._column import ColumnExpression
|
|
14
15
|
from sqlspec.protocols import SQLBuilderProtocol
|
|
15
|
-
from sqlspec.statement.builder._column import ColumnExpression
|
|
16
16
|
|
|
17
17
|
__all__ = ("HavingClauseMixin", "WhereClauseMixin")
|
|
18
18
|
|
|
@@ -20,33 +20,6 @@ __all__ = ("HavingClauseMixin", "WhereClauseMixin")
|
|
|
20
20
|
class WhereClauseMixin:
|
|
21
21
|
"""Mixin providing WHERE clause methods for SELECT, UPDATE, and DELETE builders."""
|
|
22
22
|
|
|
23
|
-
def _create_operator_handler(self, operator_class: type[exp.Expression]) -> Callable:
|
|
24
|
-
"""Create a handler that properly parameterizes values."""
|
|
25
|
-
|
|
26
|
-
def handler(self: "SQLBuilderProtocol", column_exp: exp.Expression, value: Any) -> exp.Expression:
|
|
27
|
-
_, param_name = self.add_parameter(value)
|
|
28
|
-
return operator_class(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
29
|
-
|
|
30
|
-
return handler
|
|
31
|
-
|
|
32
|
-
def _create_like_handler(self) -> Callable:
|
|
33
|
-
"""Create LIKE handler."""
|
|
34
|
-
|
|
35
|
-
def handler(self: "SQLBuilderProtocol", column_exp: exp.Expression, value: Any) -> exp.Expression:
|
|
36
|
-
_, param_name = self.add_parameter(value)
|
|
37
|
-
return exp.Like(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
38
|
-
|
|
39
|
-
return handler
|
|
40
|
-
|
|
41
|
-
def _create_not_like_handler(self) -> Callable:
|
|
42
|
-
"""Create NOT LIKE handler."""
|
|
43
|
-
|
|
44
|
-
def handler(self: "SQLBuilderProtocol", column_exp: exp.Expression, value: Any) -> exp.Expression:
|
|
45
|
-
_, param_name = self.add_parameter(value)
|
|
46
|
-
return exp.Not(this=exp.Like(this=column_exp, expression=exp.Placeholder(this=param_name)))
|
|
47
|
-
|
|
48
|
-
return handler
|
|
49
|
-
|
|
50
23
|
def _handle_in_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
|
|
51
24
|
"""Handle IN operator."""
|
|
52
25
|
builder = cast("SQLBuilderProtocol", self)
|
|
@@ -126,7 +99,6 @@ class WhereClauseMixin:
|
|
|
126
99
|
operator = str(condition[1]).upper()
|
|
127
100
|
value = condition[2]
|
|
128
101
|
|
|
129
|
-
# Handle simple operators
|
|
130
102
|
if operator == "=":
|
|
131
103
|
_, param_name = builder.add_parameter(value)
|
|
132
104
|
return exp.EQ(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
@@ -152,7 +124,6 @@ class WhereClauseMixin:
|
|
|
152
124
|
_, param_name = builder.add_parameter(value)
|
|
153
125
|
return exp.Not(this=exp.Like(this=column_exp, expression=exp.Placeholder(this=param_name)))
|
|
154
126
|
|
|
155
|
-
# Handle complex operators
|
|
156
127
|
if operator == "IN":
|
|
157
128
|
return self._handle_in_operator(column_exp, value)
|
|
158
129
|
if operator == "NOT IN":
|
|
@@ -175,15 +146,20 @@ class WhereClauseMixin:
|
|
|
175
146
|
def where(
|
|
176
147
|
self,
|
|
177
148
|
condition: Union[str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression"],
|
|
149
|
+
value: Optional[Any] = None,
|
|
150
|
+
operator: Optional[str] = None,
|
|
178
151
|
) -> Self:
|
|
179
152
|
"""Add a WHERE clause to the statement.
|
|
180
153
|
|
|
181
154
|
Args:
|
|
182
155
|
condition: The condition for the WHERE clause. Can be:
|
|
183
|
-
- A string condition
|
|
156
|
+
- A string condition (when value is None)
|
|
157
|
+
- A string column name (when value is provided)
|
|
184
158
|
- A sqlglot Expression or Condition
|
|
185
159
|
- A 2-tuple (column, value) for equality comparison
|
|
186
160
|
- A 3-tuple (column, operator, value) for custom comparison
|
|
161
|
+
value: Value for comparison (when condition is a column name)
|
|
162
|
+
operator: Operator for comparison (when both condition and value provided)
|
|
187
163
|
|
|
188
164
|
Raises:
|
|
189
165
|
SQLBuilderError: If the current expression is not a supported statement type.
|
|
@@ -191,10 +167,7 @@ class WhereClauseMixin:
|
|
|
191
167
|
Returns:
|
|
192
168
|
The current builder instance for method chaining.
|
|
193
169
|
"""
|
|
194
|
-
|
|
195
|
-
if self.__class__.__name__ == "Update" and not (
|
|
196
|
-
hasattr(self, "_expression") and isinstance(getattr(self, "_expression", None), exp.Update)
|
|
197
|
-
):
|
|
170
|
+
if self.__class__.__name__ == "Update" and not isinstance(self._expression, exp.Update): # type: ignore[attr-defined]
|
|
198
171
|
msg = "Cannot add WHERE clause to non-UPDATE expression"
|
|
199
172
|
raise SQLBuilderError(msg)
|
|
200
173
|
|
|
@@ -203,35 +176,38 @@ class WhereClauseMixin:
|
|
|
203
176
|
msg = "Cannot add WHERE clause: expression is not initialized."
|
|
204
177
|
raise SQLBuilderError(msg)
|
|
205
178
|
|
|
206
|
-
# Check if DELETE has a table set
|
|
207
179
|
if isinstance(builder._expression, exp.Delete) and not builder._expression.args.get("this"):
|
|
208
180
|
msg = "WHERE clause requires a table to be set. Use from() to set the table first."
|
|
209
181
|
raise SQLBuilderError(msg)
|
|
210
182
|
|
|
211
|
-
|
|
212
|
-
|
|
183
|
+
if value is not None:
|
|
184
|
+
if not isinstance(condition, str):
|
|
185
|
+
msg = "When value is provided, condition must be a column name (string)"
|
|
186
|
+
raise SQLBuilderError(msg)
|
|
187
|
+
|
|
188
|
+
if operator is not None:
|
|
189
|
+
where_expr = self._process_tuple_condition((condition, operator, value))
|
|
190
|
+
else:
|
|
191
|
+
where_expr = self._process_tuple_condition((condition, value))
|
|
192
|
+
elif isinstance(condition, str):
|
|
213
193
|
where_expr = parse_condition_expression(condition)
|
|
214
194
|
elif isinstance(condition, (exp.Expression, exp.Condition)):
|
|
215
195
|
where_expr = condition
|
|
216
196
|
elif isinstance(condition, tuple):
|
|
217
197
|
where_expr = self._process_tuple_condition(condition)
|
|
218
198
|
elif has_query_builder_parameters(condition):
|
|
219
|
-
# Handle ColumnExpression objects
|
|
220
199
|
column_expr_obj = cast("ColumnExpression", condition)
|
|
221
200
|
where_expr = column_expr_obj._expression # pyright: ignore
|
|
222
201
|
elif has_sqlglot_expression(condition):
|
|
223
|
-
|
|
224
|
-
raw_expr = getattr(condition, "sqlglot_expression", None)
|
|
202
|
+
raw_expr = condition.sqlglot_expression # pyright: ignore[attr-defined]
|
|
225
203
|
if raw_expr is not None:
|
|
226
204
|
where_expr = builder._parameterize_expression(raw_expr)
|
|
227
205
|
else:
|
|
228
|
-
# Fallback if attribute exists but is None
|
|
229
206
|
where_expr = parse_condition_expression(str(condition))
|
|
230
207
|
else:
|
|
231
208
|
msg = f"Unsupported condition type: {type(condition).__name__}"
|
|
232
209
|
raise SQLBuilderError(msg)
|
|
233
210
|
|
|
234
|
-
# Apply WHERE clause based on statement type
|
|
235
211
|
if isinstance(builder._expression, (exp.Select, exp.Update, exp.Delete)):
|
|
236
212
|
builder._expression = builder._expression.where(where_expr, copy=False)
|
|
237
213
|
else:
|
|
@@ -302,7 +278,7 @@ class WhereClauseMixin:
|
|
|
302
278
|
_, param_name = builder.add_parameter(pattern)
|
|
303
279
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
304
280
|
if escape is not None:
|
|
305
|
-
cond = exp.Like(this=col_expr, expression=exp.var(param_name), escape=exp.
|
|
281
|
+
cond = exp.Like(this=col_expr, expression=exp.var(param_name), escape=exp.convert(str(escape)))
|
|
306
282
|
else:
|
|
307
283
|
cond = col_expr.like(exp.var(param_name))
|
|
308
284
|
condition: exp.Expression = cond
|
|
@@ -344,8 +320,8 @@ class WhereClauseMixin:
|
|
|
344
320
|
subquery_exp: exp.Expression
|
|
345
321
|
if has_query_builder_parameters(values):
|
|
346
322
|
subquery = values.build() # pyright: ignore
|
|
347
|
-
sql_str =
|
|
348
|
-
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=
|
|
323
|
+
sql_str = subquery.sql
|
|
324
|
+
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=builder.dialect_name)) # pyright: ignore
|
|
349
325
|
else:
|
|
350
326
|
subquery_exp = values # type: ignore[assignment]
|
|
351
327
|
condition = col_expr.isin(subquery_exp)
|
|
@@ -353,11 +329,11 @@ class WhereClauseMixin:
|
|
|
353
329
|
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
354
330
|
msg = "Unsupported type for 'values' in WHERE IN"
|
|
355
331
|
raise SQLBuilderError(msg)
|
|
356
|
-
|
|
332
|
+
parameters = []
|
|
357
333
|
for v in values:
|
|
358
334
|
_, param_name = builder.add_parameter(v)
|
|
359
|
-
|
|
360
|
-
condition = col_expr.isin(*
|
|
335
|
+
parameters.append(exp.var(param_name))
|
|
336
|
+
condition = col_expr.isin(*parameters)
|
|
361
337
|
return self.where(condition)
|
|
362
338
|
|
|
363
339
|
def where_not_in(self, column: Union[str, exp.Column], values: Any) -> Self:
|
|
@@ -368,8 +344,8 @@ class WhereClauseMixin:
|
|
|
368
344
|
subquery_exp: exp.Expression
|
|
369
345
|
if has_query_builder_parameters(values):
|
|
370
346
|
subquery = values.build() # pyright: ignore
|
|
371
|
-
|
|
372
|
-
subquery_exp = exp.paren(exp.maybe_parse(
|
|
347
|
+
|
|
348
|
+
subquery_exp = exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
|
|
373
349
|
else:
|
|
374
350
|
subquery_exp = values # type: ignore[assignment]
|
|
375
351
|
condition = exp.Not(this=col_expr.isin(subquery_exp))
|
|
@@ -377,11 +353,11 @@ class WhereClauseMixin:
|
|
|
377
353
|
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
378
354
|
msg = "Values for where_not_in must be a non-string iterable or subquery."
|
|
379
355
|
raise SQLBuilderError(msg)
|
|
380
|
-
|
|
356
|
+
parameters = []
|
|
381
357
|
for v in values:
|
|
382
358
|
_, param_name = builder.add_parameter(v)
|
|
383
|
-
|
|
384
|
-
condition = exp.Not(this=col_expr.isin(*
|
|
359
|
+
parameters.append(exp.var(param_name))
|
|
360
|
+
condition = exp.Not(this=col_expr.isin(*parameters))
|
|
385
361
|
return self.where(condition)
|
|
386
362
|
|
|
387
363
|
def where_null(self, column: Union[str, exp.Column]) -> Self:
|
|
@@ -397,15 +373,15 @@ class WhereClauseMixin:
|
|
|
397
373
|
builder = cast("SQLBuilderProtocol", self)
|
|
398
374
|
sub_expr: exp.Expression
|
|
399
375
|
if has_query_builder_parameters(subquery):
|
|
400
|
-
|
|
401
|
-
if
|
|
402
|
-
for p_name, p_value in
|
|
376
|
+
subquery_builder_parameters: dict[str, Any] = subquery.parameters
|
|
377
|
+
if subquery_builder_parameters:
|
|
378
|
+
for p_name, p_value in subquery_builder_parameters.items():
|
|
403
379
|
builder.add_parameter(p_value, name=p_name)
|
|
404
380
|
sub_sql_obj = subquery.build() # pyright: ignore
|
|
405
|
-
|
|
406
|
-
sub_expr = exp.maybe_parse(
|
|
381
|
+
|
|
382
|
+
sub_expr = exp.maybe_parse(sub_sql_obj.sql, dialect=builder.dialect_name) # pyright: ignore
|
|
407
383
|
else:
|
|
408
|
-
sub_expr = exp.maybe_parse(str(subquery), dialect=
|
|
384
|
+
sub_expr = exp.maybe_parse(str(subquery), dialect=builder.dialect_name)
|
|
409
385
|
|
|
410
386
|
if sub_expr is None:
|
|
411
387
|
msg = "Could not parse subquery for EXISTS"
|
|
@@ -419,15 +395,14 @@ class WhereClauseMixin:
|
|
|
419
395
|
builder = cast("SQLBuilderProtocol", self)
|
|
420
396
|
sub_expr: exp.Expression
|
|
421
397
|
if has_query_builder_parameters(subquery):
|
|
422
|
-
|
|
423
|
-
if
|
|
424
|
-
for p_name, p_value in
|
|
398
|
+
subquery_builder_parameters: dict[str, Any] = subquery.parameters
|
|
399
|
+
if subquery_builder_parameters:
|
|
400
|
+
for p_name, p_value in subquery_builder_parameters.items():
|
|
425
401
|
builder.add_parameter(p_value, name=p_name)
|
|
426
402
|
sub_sql_obj = subquery.build() # pyright: ignore
|
|
427
|
-
|
|
428
|
-
sub_expr = exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect_name", None))
|
|
403
|
+
sub_expr = exp.maybe_parse(sub_sql_obj.sql, dialect=builder.dialect_name) # pyright: ignore
|
|
429
404
|
else:
|
|
430
|
-
sub_expr = exp.maybe_parse(str(subquery), dialect=
|
|
405
|
+
sub_expr = exp.maybe_parse(str(subquery), dialect=builder.dialect_name)
|
|
431
406
|
|
|
432
407
|
if sub_expr is None:
|
|
433
408
|
msg = "Could not parse subquery for NOT EXISTS"
|
|
@@ -444,8 +419,7 @@ class WhereClauseMixin:
|
|
|
444
419
|
subquery_exp: exp.Expression
|
|
445
420
|
if has_query_builder_parameters(values):
|
|
446
421
|
subquery = values.build() # pyright: ignore
|
|
447
|
-
|
|
448
|
-
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect_name", None)))
|
|
422
|
+
subquery_exp = exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
|
|
449
423
|
else:
|
|
450
424
|
subquery_exp = values # type: ignore[assignment]
|
|
451
425
|
condition = exp.EQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
@@ -464,24 +438,23 @@ class WhereClauseMixin:
|
|
|
464
438
|
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
465
439
|
msg = "Unsupported type for 'values' in WHERE ANY"
|
|
466
440
|
raise SQLBuilderError(msg)
|
|
467
|
-
|
|
441
|
+
parameters = []
|
|
468
442
|
for v in values:
|
|
469
443
|
_, param_name = builder.add_parameter(v)
|
|
470
|
-
|
|
471
|
-
tuple_expr = exp.Tuple(expressions=
|
|
444
|
+
parameters.append(exp.var(param_name))
|
|
445
|
+
tuple_expr = exp.Tuple(expressions=parameters)
|
|
472
446
|
condition = exp.EQ(this=col_expr, expression=exp.Any(this=tuple_expr))
|
|
473
447
|
return self.where(condition)
|
|
474
448
|
|
|
475
449
|
def where_not_any(self, column: Union[str, exp.Column], values: Any) -> Self:
|
|
476
|
-
"""Add WHERE column
|
|
450
|
+
"""Add WHERE column <> ANY(values) clause."""
|
|
477
451
|
builder = cast("SQLBuilderProtocol", self)
|
|
478
452
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
479
453
|
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
480
454
|
subquery_exp: exp.Expression
|
|
481
455
|
if has_query_builder_parameters(values):
|
|
482
456
|
subquery = values.build() # pyright: ignore
|
|
483
|
-
|
|
484
|
-
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect_name", None)))
|
|
457
|
+
subquery_exp = exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
|
|
485
458
|
else:
|
|
486
459
|
subquery_exp = values # type: ignore[assignment]
|
|
487
460
|
condition = exp.NEQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
@@ -500,11 +473,11 @@ class WhereClauseMixin:
|
|
|
500
473
|
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
501
474
|
msg = "Unsupported type for 'values' in WHERE NOT ANY"
|
|
502
475
|
raise SQLBuilderError(msg)
|
|
503
|
-
|
|
476
|
+
parameters = []
|
|
504
477
|
for v in values:
|
|
505
478
|
_, param_name = builder.add_parameter(v)
|
|
506
|
-
|
|
507
|
-
tuple_expr = exp.Tuple(expressions=
|
|
479
|
+
parameters.append(exp.var(param_name))
|
|
480
|
+
tuple_expr = exp.Tuple(expressions=parameters)
|
|
508
481
|
condition = exp.NEQ(this=col_expr, expression=exp.Any(this=tuple_expr))
|
|
509
482
|
return self.where(condition)
|
|
510
483
|
|
sqlspec/cli.py
CHANGED
|
@@ -32,7 +32,7 @@ def get_sqlspec_group() -> "Group":
|
|
|
32
32
|
@click.group(name="sqlspec")
|
|
33
33
|
@click.option(
|
|
34
34
|
"--config",
|
|
35
|
-
help="Dotted path to
|
|
35
|
+
help="Dotted path to SQLSpec config(s) (e.g. 'myapp.config.sqlspec_configs')",
|
|
36
36
|
required=True,
|
|
37
37
|
type=str,
|
|
38
38
|
)
|
|
@@ -87,7 +87,7 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
|
|
|
87
87
|
database_group = get_sqlspec_group()
|
|
88
88
|
|
|
89
89
|
bind_key_option = click.option(
|
|
90
|
-
"--bind-key", help="Specify which
|
|
90
|
+
"--bind-key", help="Specify which SQLSpec config to use by bind key", type=str, default=None
|
|
91
91
|
)
|
|
92
92
|
verbose_option = click.option("--verbose", help="Enable verbose output.", type=bool, default=False, is_flag=True)
|
|
93
93
|
no_prompt_option = click.option(
|
|
@@ -103,21 +103,20 @@ def add_migration_commands(database_group: Optional["Group"] = None) -> "Group":
|
|
|
103
103
|
def get_config_by_bind_key(
|
|
104
104
|
ctx: "click.Context", bind_key: Optional[str]
|
|
105
105
|
) -> "Union[AsyncDatabaseConfig[Any, Any, Any], SyncDatabaseConfig[Any, Any, Any]]":
|
|
106
|
-
"""Get the
|
|
106
|
+
"""Get the SQLSpec config for the specified bind key.
|
|
107
107
|
|
|
108
108
|
Args:
|
|
109
109
|
ctx: The click context.
|
|
110
110
|
bind_key: The bind key to get the config for.
|
|
111
111
|
|
|
112
112
|
Returns:
|
|
113
|
-
The
|
|
113
|
+
The SQLSpec config for the specified bind key.
|
|
114
114
|
"""
|
|
115
115
|
configs = ctx.obj["configs"]
|
|
116
116
|
if bind_key is None:
|
|
117
117
|
return cast("Union[AsyncDatabaseConfig[Any, Any, Any], SyncDatabaseConfig[Any, Any, Any]]", configs[0])
|
|
118
118
|
|
|
119
119
|
for config in configs:
|
|
120
|
-
# Check if config has a name or identifier attribute
|
|
121
120
|
config_name = getattr(config, "name", None) or getattr(config, "bind_key", None)
|
|
122
121
|
if config_name == bind_key:
|
|
123
122
|
return cast("Union[AsyncDatabaseConfig[Any, Any, Any], SyncDatabaseConfig[Any, Any, Any]]", config)
|