sqlspec 0.13.1__py3-none-any.whl → 0.14.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 +39 -1
- sqlspec/adapters/adbc/config.py +4 -40
- sqlspec/adapters/adbc/driver.py +29 -16
- sqlspec/adapters/aiosqlite/config.py +2 -20
- sqlspec/adapters/aiosqlite/driver.py +36 -18
- sqlspec/adapters/asyncmy/config.py +2 -33
- sqlspec/adapters/asyncmy/driver.py +23 -16
- sqlspec/adapters/asyncpg/config.py +5 -39
- sqlspec/adapters/asyncpg/driver.py +41 -18
- sqlspec/adapters/bigquery/config.py +2 -43
- sqlspec/adapters/bigquery/driver.py +26 -14
- sqlspec/adapters/duckdb/config.py +2 -49
- sqlspec/adapters/duckdb/driver.py +35 -16
- sqlspec/adapters/oracledb/config.py +4 -83
- sqlspec/adapters/oracledb/driver.py +54 -27
- sqlspec/adapters/psqlpy/config.py +2 -55
- sqlspec/adapters/psqlpy/driver.py +28 -8
- sqlspec/adapters/psycopg/config.py +4 -73
- sqlspec/adapters/psycopg/driver.py +69 -24
- sqlspec/adapters/sqlite/config.py +3 -21
- sqlspec/adapters/sqlite/driver.py +50 -26
- sqlspec/cli.py +248 -0
- sqlspec/config.py +18 -20
- sqlspec/driver/_async.py +28 -10
- sqlspec/driver/_common.py +5 -4
- sqlspec/driver/_sync.py +28 -10
- sqlspec/driver/mixins/__init__.py +6 -0
- sqlspec/driver/mixins/_cache.py +114 -0
- sqlspec/driver/mixins/_pipeline.py +0 -4
- sqlspec/{service/base.py → driver/mixins/_query_tools.py} +86 -421
- sqlspec/driver/mixins/_result_utils.py +0 -2
- sqlspec/driver/mixins/_sql_translator.py +0 -2
- sqlspec/driver/mixins/_storage.py +4 -18
- sqlspec/driver/mixins/_type_coercion.py +0 -2
- sqlspec/driver/parameters.py +4 -4
- sqlspec/extensions/aiosql/adapter.py +4 -4
- sqlspec/extensions/litestar/__init__.py +2 -1
- sqlspec/extensions/litestar/cli.py +48 -0
- sqlspec/extensions/litestar/plugin.py +3 -0
- sqlspec/loader.py +1 -1
- sqlspec/migrations/__init__.py +23 -0
- sqlspec/migrations/base.py +390 -0
- sqlspec/migrations/commands.py +525 -0
- sqlspec/migrations/runner.py +215 -0
- sqlspec/migrations/tracker.py +153 -0
- sqlspec/migrations/utils.py +89 -0
- sqlspec/protocols.py +37 -3
- sqlspec/statement/builder/__init__.py +8 -8
- sqlspec/statement/builder/{column.py → _column.py} +82 -52
- sqlspec/statement/builder/{ddl.py → _ddl.py} +5 -5
- sqlspec/statement/builder/_ddl_utils.py +1 -1
- sqlspec/statement/builder/{delete.py → _delete.py} +1 -1
- sqlspec/statement/builder/{insert.py → _insert.py} +1 -1
- sqlspec/statement/builder/{merge.py → _merge.py} +1 -1
- sqlspec/statement/builder/_parsing_utils.py +5 -3
- sqlspec/statement/builder/{select.py → _select.py} +59 -61
- sqlspec/statement/builder/{update.py → _update.py} +2 -2
- sqlspec/statement/builder/mixins/__init__.py +24 -30
- sqlspec/statement/builder/mixins/{_set_ops.py → _cte_and_set_ops.py} +86 -2
- sqlspec/statement/builder/mixins/{_delete_from.py → _delete_operations.py} +2 -0
- sqlspec/statement/builder/mixins/{_insert_values.py → _insert_operations.py} +70 -1
- sqlspec/statement/builder/mixins/{_merge_clauses.py → _merge_operations.py} +2 -0
- sqlspec/statement/builder/mixins/_order_limit_operations.py +123 -0
- sqlspec/statement/builder/mixins/{_pivot.py → _pivot_operations.py} +71 -2
- sqlspec/statement/builder/mixins/_select_operations.py +612 -0
- sqlspec/statement/builder/mixins/{_update_set.py → _update_operations.py} +73 -2
- sqlspec/statement/builder/mixins/_where_clause.py +536 -0
- sqlspec/statement/cache.py +50 -0
- sqlspec/statement/filters.py +37 -8
- sqlspec/statement/parameters.py +154 -25
- sqlspec/statement/pipelines/__init__.py +1 -1
- sqlspec/statement/pipelines/context.py +4 -4
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +3 -3
- sqlspec/statement/pipelines/validators/_parameter_style.py +22 -22
- sqlspec/statement/pipelines/validators/_performance.py +1 -5
- sqlspec/statement/sql.py +246 -176
- sqlspec/utils/__init__.py +2 -1
- sqlspec/utils/statement_hashing.py +203 -0
- sqlspec/utils/type_guards.py +32 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.14.0.dist-info}/METADATA +1 -1
- sqlspec-0.14.0.dist-info/RECORD +143 -0
- sqlspec-0.14.0.dist-info/entry_points.txt +2 -0
- sqlspec/service/__init__.py +0 -4
- sqlspec/service/_util.py +0 -147
- sqlspec/service/pagination.py +0 -26
- sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
- sqlspec/statement/builder/mixins/_case_builder.py +0 -91
- sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
- sqlspec/statement/builder/mixins/_from.py +0 -63
- sqlspec/statement/builder/mixins/_group_by.py +0 -118
- sqlspec/statement/builder/mixins/_having.py +0 -35
- sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
- sqlspec/statement/builder/mixins/_insert_into.py +0 -36
- sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
- sqlspec/statement/builder/mixins/_order_by.py +0 -46
- sqlspec/statement/builder/mixins/_returning.py +0 -37
- sqlspec/statement/builder/mixins/_select_columns.py +0 -61
- sqlspec/statement/builder/mixins/_unpivot.py +0 -77
- sqlspec/statement/builder/mixins/_update_from.py +0 -55
- sqlspec/statement/builder/mixins/_update_table.py +0 -29
- sqlspec/statement/builder/mixins/_where.py +0 -401
- sqlspec/statement/builder/mixins/_window_functions.py +0 -86
- sqlspec/statement/parameter_manager.py +0 -220
- sqlspec/statement/sql_compiler.py +0 -140
- sqlspec-0.13.1.dist-info/RECORD +0 -150
- /sqlspec/statement/builder/{base.py → _base.py} +0 -0
- /sqlspec/statement/builder/mixins/{_join.py → _join_operations.py} +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.14.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.14.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.14.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
# ruff: noqa: PLR2004
|
|
2
|
-
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
3
|
-
|
|
4
|
-
from sqlglot import exp, parse_one
|
|
5
|
-
from typing_extensions import Self
|
|
6
|
-
|
|
7
|
-
from sqlspec.exceptions import SQLBuilderError
|
|
8
|
-
from sqlspec.statement.builder._parsing_utils import parse_column_expression, parse_condition_expression
|
|
9
|
-
from sqlspec.utils.type_guards import has_query_builder_parameters, is_iterable_parameters
|
|
10
|
-
|
|
11
|
-
if TYPE_CHECKING:
|
|
12
|
-
from sqlspec.protocols import SQLBuilderProtocol
|
|
13
|
-
from sqlspec.statement.builder.column import ColumnExpression
|
|
14
|
-
|
|
15
|
-
__all__ = ("WhereClauseMixin",)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class WhereClauseMixin:
|
|
19
|
-
"""Mixin providing WHERE clause methods for SELECT, UPDATE, and DELETE builders."""
|
|
20
|
-
|
|
21
|
-
def where(
|
|
22
|
-
self,
|
|
23
|
-
condition: Union[str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression"],
|
|
24
|
-
) -> Self:
|
|
25
|
-
"""Add a WHERE clause to the statement.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
condition: The condition for the WHERE clause. Can be:
|
|
29
|
-
- A string condition
|
|
30
|
-
- A sqlglot Expression or Condition
|
|
31
|
-
- A 2-tuple (column, value) for equality comparison
|
|
32
|
-
- A 3-tuple (column, operator, value) for custom comparison
|
|
33
|
-
|
|
34
|
-
Raises:
|
|
35
|
-
SQLBuilderError: If the current expression is not a supported statement type.
|
|
36
|
-
|
|
37
|
-
Returns:
|
|
38
|
-
The current builder instance for method chaining.
|
|
39
|
-
"""
|
|
40
|
-
# Special case: if this is an Update and _expression is not exp.Update, raise the expected error for test coverage
|
|
41
|
-
|
|
42
|
-
if self.__class__.__name__ == "Update" and not (
|
|
43
|
-
hasattr(self, "_expression") and isinstance(getattr(self, "_expression", None), exp.Update)
|
|
44
|
-
):
|
|
45
|
-
msg = "Cannot add WHERE clause to non-UPDATE expression"
|
|
46
|
-
raise SQLBuilderError(msg)
|
|
47
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
48
|
-
if builder._expression is None:
|
|
49
|
-
msg = "Cannot add WHERE clause: expression is not initialized."
|
|
50
|
-
raise SQLBuilderError(msg)
|
|
51
|
-
valid_types = (exp.Select, exp.Update, exp.Delete)
|
|
52
|
-
if not isinstance(builder._expression, valid_types):
|
|
53
|
-
msg = f"Cannot add WHERE clause to unsupported expression type: {type(builder._expression).__name__}."
|
|
54
|
-
raise SQLBuilderError(msg)
|
|
55
|
-
|
|
56
|
-
if isinstance(builder._expression, exp.Delete) and not builder._expression.args.get("this"):
|
|
57
|
-
msg = "WHERE clause requires a table to be set. Use from() to set the table first."
|
|
58
|
-
raise SQLBuilderError(msg)
|
|
59
|
-
|
|
60
|
-
# Normalize the condition using enhanced parsing
|
|
61
|
-
condition_expr: exp.Expression
|
|
62
|
-
if isinstance(condition, tuple):
|
|
63
|
-
if len(condition) == 2:
|
|
64
|
-
# 2-tuple: (column, value) -> column = value
|
|
65
|
-
param_name = builder.add_parameter(condition[1])[1]
|
|
66
|
-
condition_expr = exp.EQ(
|
|
67
|
-
this=parse_column_expression(condition[0]), expression=exp.Placeholder(this=param_name)
|
|
68
|
-
)
|
|
69
|
-
elif len(condition) == 3:
|
|
70
|
-
# 3-tuple: (column, operator, value) -> column operator value
|
|
71
|
-
column, operator, value = condition
|
|
72
|
-
param_name = builder.add_parameter(value)[1]
|
|
73
|
-
col_expr = parse_column_expression(column)
|
|
74
|
-
placeholder_expr = exp.Placeholder(this=param_name)
|
|
75
|
-
|
|
76
|
-
# Map operator strings to sqlglot expression types
|
|
77
|
-
operator_map = {
|
|
78
|
-
"=": exp.EQ,
|
|
79
|
-
"==": exp.EQ,
|
|
80
|
-
"!=": exp.NEQ,
|
|
81
|
-
"<>": exp.NEQ,
|
|
82
|
-
"<": exp.LT,
|
|
83
|
-
"<=": exp.LTE,
|
|
84
|
-
">": exp.GT,
|
|
85
|
-
">=": exp.GTE,
|
|
86
|
-
"like": exp.Like,
|
|
87
|
-
"in": exp.In,
|
|
88
|
-
"any": exp.Any,
|
|
89
|
-
}
|
|
90
|
-
operator = operator.lower()
|
|
91
|
-
if operator == "not like":
|
|
92
|
-
condition_expr = exp.Not(this=exp.Like(this=col_expr, expression=placeholder_expr))
|
|
93
|
-
elif operator == "not in":
|
|
94
|
-
condition_expr = exp.Not(this=exp.In(this=col_expr, expression=placeholder_expr))
|
|
95
|
-
elif operator == "not any":
|
|
96
|
-
condition_expr = exp.Not(this=exp.Any(this=col_expr, expression=placeholder_expr))
|
|
97
|
-
else:
|
|
98
|
-
expr_class = operator_map.get(operator)
|
|
99
|
-
if expr_class is None:
|
|
100
|
-
msg = f"Unsupported operator in WHERE condition: {operator}"
|
|
101
|
-
raise SQLBuilderError(msg)
|
|
102
|
-
|
|
103
|
-
condition_expr = expr_class(this=col_expr, expression=placeholder_expr)
|
|
104
|
-
else:
|
|
105
|
-
msg = f"WHERE tuple must have 2 or 3 elements, got {len(condition)}"
|
|
106
|
-
raise SQLBuilderError(msg)
|
|
107
|
-
# Handle ColumnExpression objects
|
|
108
|
-
elif hasattr(condition, "sqlglot_expression"):
|
|
109
|
-
# This is a ColumnExpression from our new Column syntax
|
|
110
|
-
raw_expr = getattr(condition, "sqlglot_expression", None)
|
|
111
|
-
if raw_expr is not None:
|
|
112
|
-
condition_expr = builder._parameterize_expression(raw_expr)
|
|
113
|
-
else:
|
|
114
|
-
# Fallback if attribute exists but is None
|
|
115
|
-
condition_expr = parse_condition_expression(str(condition))
|
|
116
|
-
else:
|
|
117
|
-
# Existing logic for strings and raw SQLGlot expressions
|
|
118
|
-
# Convert to string if it's not a recognized type
|
|
119
|
-
if not isinstance(condition, (str, exp.Expression, tuple)):
|
|
120
|
-
condition = str(condition)
|
|
121
|
-
condition_expr = parse_condition_expression(condition)
|
|
122
|
-
|
|
123
|
-
# Use dialect if available for Delete
|
|
124
|
-
if isinstance(builder._expression, exp.Delete):
|
|
125
|
-
builder._expression = builder._expression.where(
|
|
126
|
-
condition_expr, dialect=getattr(builder, "dialect_name", None)
|
|
127
|
-
)
|
|
128
|
-
else:
|
|
129
|
-
builder._expression = builder._expression.where(condition_expr, copy=False)
|
|
130
|
-
return cast("Self", builder)
|
|
131
|
-
|
|
132
|
-
# The following methods are moved from the old WhereClauseMixin in _base.py
|
|
133
|
-
def where_eq(self, column: "Union[str, exp.Column]", value: Any) -> "Self":
|
|
134
|
-
_, param_name = self.add_parameter(value) # type: ignore[attr-defined]
|
|
135
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
136
|
-
condition: exp.Expression = col_expr.eq(exp.var(param_name))
|
|
137
|
-
return self.where(condition)
|
|
138
|
-
|
|
139
|
-
def where_neq(self, column: "Union[str, exp.Column]", value: Any) -> "Self":
|
|
140
|
-
_, param_name = self.add_parameter(value) # type: ignore[attr-defined]
|
|
141
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
142
|
-
condition: exp.Expression = col_expr.neq(exp.var(param_name))
|
|
143
|
-
return self.where(condition)
|
|
144
|
-
|
|
145
|
-
def where_lt(self, column: "Union[str, exp.Column]", value: Any) -> "Self":
|
|
146
|
-
_, param_name = self.add_parameter(value) # type: ignore[attr-defined]
|
|
147
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
148
|
-
condition: exp.Expression = exp.LT(this=col_expr, expression=exp.var(param_name))
|
|
149
|
-
return self.where(condition)
|
|
150
|
-
|
|
151
|
-
def where_lte(self, column: "Union[str, exp.Column]", value: Any) -> "Self":
|
|
152
|
-
_, param_name = self.add_parameter(value) # type: ignore[attr-defined]
|
|
153
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
154
|
-
condition: exp.Expression = exp.LTE(this=col_expr, expression=exp.var(param_name))
|
|
155
|
-
return self.where(condition)
|
|
156
|
-
|
|
157
|
-
def where_gt(self, column: "Union[str, exp.Column]", value: Any) -> "Self":
|
|
158
|
-
_, param_name = self.add_parameter(value) # type: ignore[attr-defined]
|
|
159
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
160
|
-
condition: exp.Expression = exp.GT(this=col_expr, expression=exp.var(param_name))
|
|
161
|
-
return self.where(condition)
|
|
162
|
-
|
|
163
|
-
def where_gte(self, column: "Union[str, exp.Column]", value: Any) -> "Self":
|
|
164
|
-
_, param_name = self.add_parameter(value) # type: ignore[attr-defined]
|
|
165
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
166
|
-
condition: exp.Expression = exp.GTE(this=col_expr, expression=exp.var(param_name))
|
|
167
|
-
return self.where(condition)
|
|
168
|
-
|
|
169
|
-
def where_between(self, column: "Union[str, exp.Column]", low: Any, high: Any) -> "Self":
|
|
170
|
-
_, low_param = self.add_parameter(low) # type: ignore[attr-defined]
|
|
171
|
-
_, high_param = self.add_parameter(high) # type: ignore[attr-defined]
|
|
172
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
173
|
-
condition: exp.Expression = col_expr.between(exp.var(low_param), exp.var(high_param))
|
|
174
|
-
return self.where(condition)
|
|
175
|
-
|
|
176
|
-
def where_like(self, column: "Union[str, exp.Column]", pattern: str, escape: Optional[str] = None) -> "Self":
|
|
177
|
-
_, param_name = self.add_parameter(pattern) # type: ignore[attr-defined]
|
|
178
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
179
|
-
if escape is not None:
|
|
180
|
-
cond = exp.Like(this=col_expr, expression=exp.var(param_name), escape=exp.Literal.string(str(escape)))
|
|
181
|
-
else:
|
|
182
|
-
cond = col_expr.like(exp.var(param_name))
|
|
183
|
-
condition: exp.Expression = cond
|
|
184
|
-
return self.where(condition)
|
|
185
|
-
|
|
186
|
-
def where_not_like(self, column: "Union[str, exp.Column]", pattern: str) -> "Self":
|
|
187
|
-
_, param_name = self.add_parameter(pattern) # type: ignore[attr-defined]
|
|
188
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
189
|
-
condition: exp.Expression = col_expr.like(exp.var(param_name)).not_()
|
|
190
|
-
return self.where(condition)
|
|
191
|
-
|
|
192
|
-
def where_ilike(self, column: "Union[str, exp.Column]", pattern: str) -> "Self":
|
|
193
|
-
_, param_name = self.add_parameter(pattern) # type: ignore[attr-defined]
|
|
194
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
195
|
-
condition: exp.Expression = col_expr.ilike(exp.var(param_name))
|
|
196
|
-
return self.where(condition)
|
|
197
|
-
|
|
198
|
-
def where_is_null(self, column: "Union[str, exp.Column]") -> "Self":
|
|
199
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
200
|
-
condition: exp.Expression = col_expr.is_(exp.null())
|
|
201
|
-
return self.where(condition)
|
|
202
|
-
|
|
203
|
-
def where_is_not_null(self, column: "Union[str, exp.Column]") -> "Self":
|
|
204
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
205
|
-
condition: exp.Expression = col_expr.is_(exp.null()).not_()
|
|
206
|
-
return self.where(condition)
|
|
207
|
-
|
|
208
|
-
def where_exists(self, subquery: "Union[str, Any]") -> "Self":
|
|
209
|
-
sub_expr: exp.Expression
|
|
210
|
-
if has_query_builder_parameters(subquery):
|
|
211
|
-
subquery_builder_params: dict[str, Any] = subquery.parameters
|
|
212
|
-
if subquery_builder_params:
|
|
213
|
-
for p_name, p_value in subquery_builder_params.items():
|
|
214
|
-
self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
|
|
215
|
-
sub_sql_obj = subquery.build() # pyright: ignore
|
|
216
|
-
sql_str = (
|
|
217
|
-
sub_sql_obj.sql if hasattr(sub_sql_obj, "sql") and not callable(sub_sql_obj.sql) else str(sub_sql_obj)
|
|
218
|
-
)
|
|
219
|
-
sub_expr = exp.maybe_parse(sql_str, dialect=getattr(self, "dialect_name", None))
|
|
220
|
-
else:
|
|
221
|
-
sub_expr = exp.maybe_parse(str(subquery), dialect=getattr(self, "dialect_name", None))
|
|
222
|
-
|
|
223
|
-
if sub_expr is None:
|
|
224
|
-
msg = "Could not parse subquery for EXISTS"
|
|
225
|
-
raise SQLBuilderError(msg)
|
|
226
|
-
|
|
227
|
-
exists_expr = exp.Exists(this=sub_expr)
|
|
228
|
-
return self.where(exists_expr)
|
|
229
|
-
|
|
230
|
-
def where_not_exists(self, subquery: "Union[str, Any]") -> "Self":
|
|
231
|
-
sub_expr: exp.Expression
|
|
232
|
-
if has_query_builder_parameters(subquery):
|
|
233
|
-
subquery_builder_params: dict[str, Any] = subquery.parameters
|
|
234
|
-
if subquery_builder_params:
|
|
235
|
-
for p_name, p_value in subquery_builder_params.items():
|
|
236
|
-
self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
|
|
237
|
-
sub_sql_obj = subquery.build() # pyright: ignore
|
|
238
|
-
sql_str = (
|
|
239
|
-
sub_sql_obj.sql if hasattr(sub_sql_obj, "sql") and not callable(sub_sql_obj.sql) else str(sub_sql_obj)
|
|
240
|
-
)
|
|
241
|
-
sub_expr = exp.maybe_parse(sql_str, dialect=getattr(self, "dialect_name", None))
|
|
242
|
-
else:
|
|
243
|
-
sub_expr = exp.maybe_parse(str(subquery), dialect=getattr(self, "dialect_name", None))
|
|
244
|
-
|
|
245
|
-
if sub_expr is None:
|
|
246
|
-
msg = "Could not parse subquery for NOT EXISTS"
|
|
247
|
-
raise SQLBuilderError(msg)
|
|
248
|
-
|
|
249
|
-
not_exists_expr = exp.Not(this=exp.Exists(this=sub_expr))
|
|
250
|
-
return self.where(not_exists_expr)
|
|
251
|
-
|
|
252
|
-
def where_not_null(self, column: "Union[str, exp.Column]") -> "Self":
|
|
253
|
-
"""Alias for where_is_not_null for compatibility with test expectations."""
|
|
254
|
-
return self.where_is_not_null(column)
|
|
255
|
-
|
|
256
|
-
def where_in(self, column: "Union[str, exp.Column]", values: Any) -> "Self":
|
|
257
|
-
"""Add a WHERE ... IN (...) clause. Supports subqueries and iterables."""
|
|
258
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
259
|
-
# Subquery support
|
|
260
|
-
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
261
|
-
subquery_exp: exp.Expression
|
|
262
|
-
if has_query_builder_parameters(values):
|
|
263
|
-
subquery = values.build() # pyright: ignore
|
|
264
|
-
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
265
|
-
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect_name", None)))
|
|
266
|
-
else:
|
|
267
|
-
subquery_exp = values # type: ignore[assignment]
|
|
268
|
-
condition = col_expr.isin(subquery_exp)
|
|
269
|
-
return self.where(condition)
|
|
270
|
-
# Iterable of values
|
|
271
|
-
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
272
|
-
msg = "Unsupported type for 'values' in WHERE IN"
|
|
273
|
-
raise SQLBuilderError(msg)
|
|
274
|
-
params = []
|
|
275
|
-
for v in values:
|
|
276
|
-
_, param_name = self.add_parameter(v) # type: ignore[attr-defined]
|
|
277
|
-
params.append(exp.var(param_name))
|
|
278
|
-
condition = col_expr.isin(*params)
|
|
279
|
-
return self.where(condition)
|
|
280
|
-
|
|
281
|
-
def where_not_in(self, column: "Union[str, exp.Column]", values: Any) -> "Self":
|
|
282
|
-
"""Add a WHERE ... NOT IN (...) clause. Supports subqueries and iterables."""
|
|
283
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
284
|
-
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
285
|
-
subquery_exp: exp.Expression
|
|
286
|
-
if has_query_builder_parameters(values):
|
|
287
|
-
subquery = values.build() # pyright: ignore
|
|
288
|
-
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
289
|
-
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect_name", None)))
|
|
290
|
-
else:
|
|
291
|
-
subquery_exp = values # type: ignore[assignment]
|
|
292
|
-
condition = exp.Not(this=col_expr.isin(subquery_exp))
|
|
293
|
-
return self.where(condition)
|
|
294
|
-
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
295
|
-
msg = "Values for where_not_in must be a non-string iterable or subquery."
|
|
296
|
-
raise SQLBuilderError(msg)
|
|
297
|
-
params = []
|
|
298
|
-
for v in values:
|
|
299
|
-
_, param_name = self.add_parameter(v) # type: ignore[attr-defined]
|
|
300
|
-
params.append(exp.var(param_name))
|
|
301
|
-
condition = exp.Not(this=col_expr.isin(*params))
|
|
302
|
-
return self.where(condition)
|
|
303
|
-
|
|
304
|
-
def where_null(self, column: "Union[str, exp.Column]") -> "Self":
|
|
305
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
306
|
-
condition: exp.Expression = col_expr.is_(exp.null())
|
|
307
|
-
return self.where(condition)
|
|
308
|
-
|
|
309
|
-
def where_any(self, column: "Union[str, exp.Column]", values: Any) -> "Self":
|
|
310
|
-
"""Add a WHERE ... = ANY (...) clause. Supports subqueries and iterables.
|
|
311
|
-
|
|
312
|
-
Args:
|
|
313
|
-
column: The column to compare.
|
|
314
|
-
values: A subquery or iterable of values.
|
|
315
|
-
|
|
316
|
-
Returns:
|
|
317
|
-
The current builder instance for method chaining.
|
|
318
|
-
"""
|
|
319
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
320
|
-
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
321
|
-
subquery_exp: exp.Expression
|
|
322
|
-
if has_query_builder_parameters(values):
|
|
323
|
-
subquery = values.build() # pyright: ignore
|
|
324
|
-
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
325
|
-
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect_name", None)))
|
|
326
|
-
else:
|
|
327
|
-
subquery_exp = values # type: ignore[assignment]
|
|
328
|
-
condition = exp.EQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
329
|
-
return self.where(condition)
|
|
330
|
-
if isinstance(values, str):
|
|
331
|
-
# Try to parse as subquery expression with enhanced parsing
|
|
332
|
-
try:
|
|
333
|
-
# Parse as a subquery expression
|
|
334
|
-
parsed_expr = parse_one(values)
|
|
335
|
-
if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
|
|
336
|
-
subquery_exp = exp.paren(parsed_expr)
|
|
337
|
-
condition = exp.EQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
338
|
-
return self.where(condition)
|
|
339
|
-
except Exception: # noqa: S110
|
|
340
|
-
# Subquery parsing failed for WHERE ANY
|
|
341
|
-
pass
|
|
342
|
-
# If parsing fails, fall through to error
|
|
343
|
-
msg = "Unsupported type for 'values' in WHERE ANY"
|
|
344
|
-
raise SQLBuilderError(msg)
|
|
345
|
-
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
346
|
-
msg = "Unsupported type for 'values' in WHERE ANY"
|
|
347
|
-
raise SQLBuilderError(msg)
|
|
348
|
-
params = []
|
|
349
|
-
for v in values:
|
|
350
|
-
_, param_name = self.add_parameter(v) # type: ignore[attr-defined]
|
|
351
|
-
params.append(exp.var(param_name))
|
|
352
|
-
tuple_expr = exp.Tuple(expressions=params)
|
|
353
|
-
condition = exp.EQ(this=col_expr, expression=exp.Any(this=tuple_expr))
|
|
354
|
-
return self.where(condition)
|
|
355
|
-
|
|
356
|
-
def where_not_any(self, column: "Union[str, exp.Column]", values: Any) -> "Self":
|
|
357
|
-
"""Add a WHERE ... <> ANY (...) (or NOT = ANY) clause. Supports subqueries and iterables.
|
|
358
|
-
|
|
359
|
-
Args:
|
|
360
|
-
column: The column to compare.
|
|
361
|
-
values: A subquery or iterable of values.
|
|
362
|
-
|
|
363
|
-
Returns:
|
|
364
|
-
The current builder instance for method chaining.
|
|
365
|
-
"""
|
|
366
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
367
|
-
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
368
|
-
subquery_exp: exp.Expression
|
|
369
|
-
if has_query_builder_parameters(values):
|
|
370
|
-
subquery = values.build() # pyright: ignore
|
|
371
|
-
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
372
|
-
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect_name", None)))
|
|
373
|
-
else:
|
|
374
|
-
subquery_exp = values # type: ignore[assignment]
|
|
375
|
-
condition = exp.NEQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
376
|
-
return self.where(condition)
|
|
377
|
-
if isinstance(values, str):
|
|
378
|
-
# Try to parse as subquery expression with enhanced parsing
|
|
379
|
-
try:
|
|
380
|
-
# Parse as a subquery expression
|
|
381
|
-
parsed_expr = parse_one(values)
|
|
382
|
-
if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
|
|
383
|
-
subquery_exp = exp.paren(parsed_expr)
|
|
384
|
-
condition = exp.NEQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
385
|
-
return self.where(condition)
|
|
386
|
-
except Exception: # noqa: S110
|
|
387
|
-
# Subquery parsing failed for WHERE NOT ANY
|
|
388
|
-
pass
|
|
389
|
-
# If parsing fails, fall through to error
|
|
390
|
-
msg = "Unsupported type for 'values' in WHERE NOT ANY"
|
|
391
|
-
raise SQLBuilderError(msg)
|
|
392
|
-
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
393
|
-
msg = "Unsupported type for 'values' in WHERE NOT ANY"
|
|
394
|
-
raise SQLBuilderError(msg)
|
|
395
|
-
params = []
|
|
396
|
-
for v in values:
|
|
397
|
-
_, param_name = self.add_parameter(v) # type: ignore[attr-defined]
|
|
398
|
-
params.append(exp.var(param_name))
|
|
399
|
-
tuple_expr = exp.Tuple(expressions=params)
|
|
400
|
-
condition = exp.NEQ(this=col_expr, expression=exp.Any(this=tuple_expr))
|
|
401
|
-
return self.where(condition)
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
from typing import Any, Optional, Union
|
|
2
|
-
|
|
3
|
-
from sqlglot import exp
|
|
4
|
-
from typing_extensions import Self
|
|
5
|
-
|
|
6
|
-
from sqlspec.exceptions import SQLBuilderError
|
|
7
|
-
|
|
8
|
-
__all__ = ("WindowFunctionsMixin",)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class WindowFunctionsMixin:
|
|
12
|
-
"""Mixin providing window function methods for SQL builders."""
|
|
13
|
-
|
|
14
|
-
_expression: Optional[exp.Expression] = None
|
|
15
|
-
|
|
16
|
-
def window(
|
|
17
|
-
self,
|
|
18
|
-
function_expr: Union[str, exp.Expression],
|
|
19
|
-
partition_by: Optional[Union[str, list[str], exp.Expression, list[exp.Expression]]] = None,
|
|
20
|
-
order_by: Optional[Union[str, list[str], exp.Expression, list[exp.Expression]]] = None,
|
|
21
|
-
frame: Optional[str] = None,
|
|
22
|
-
alias: Optional[str] = None,
|
|
23
|
-
) -> Self:
|
|
24
|
-
"""Add a window function to the SELECT clause.
|
|
25
|
-
|
|
26
|
-
Args:
|
|
27
|
-
function_expr: The window function expression (e.g., "COUNT(*)", "ROW_NUMBER()").
|
|
28
|
-
partition_by: Column(s) to partition by.
|
|
29
|
-
order_by: Column(s) to order by within the window.
|
|
30
|
-
frame: Window frame specification (e.g., "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW").
|
|
31
|
-
alias: Optional alias for the window function.
|
|
32
|
-
|
|
33
|
-
Raises:
|
|
34
|
-
SQLBuilderError: If the current expression is not a SELECT statement or function parsing fails.
|
|
35
|
-
|
|
36
|
-
Returns:
|
|
37
|
-
The current builder instance for method chaining.
|
|
38
|
-
"""
|
|
39
|
-
if self._expression is None:
|
|
40
|
-
self._expression = exp.Select()
|
|
41
|
-
if not isinstance(self._expression, exp.Select):
|
|
42
|
-
msg = "Cannot add window function to a non-SELECT expression."
|
|
43
|
-
raise SQLBuilderError(msg)
|
|
44
|
-
|
|
45
|
-
func_expr_parsed: exp.Expression
|
|
46
|
-
if isinstance(function_expr, str):
|
|
47
|
-
parsed: Optional[exp.Expression] = exp.maybe_parse(function_expr, dialect=getattr(self, "dialect", None))
|
|
48
|
-
if not parsed:
|
|
49
|
-
msg = f"Could not parse function expression: {function_expr}"
|
|
50
|
-
raise SQLBuilderError(msg)
|
|
51
|
-
func_expr_parsed = parsed
|
|
52
|
-
else:
|
|
53
|
-
func_expr_parsed = function_expr
|
|
54
|
-
|
|
55
|
-
over_args: dict[str, Any] = {} # Stringified dict
|
|
56
|
-
if partition_by:
|
|
57
|
-
if isinstance(partition_by, str):
|
|
58
|
-
over_args["partition_by"] = [exp.column(partition_by)]
|
|
59
|
-
elif isinstance(partition_by, list): # Check for list
|
|
60
|
-
over_args["partition_by"] = [exp.column(col) if isinstance(col, str) else col for col in partition_by]
|
|
61
|
-
elif isinstance(partition_by, exp.Expression): # Check for exp.Expression
|
|
62
|
-
over_args["partition_by"] = [partition_by]
|
|
63
|
-
|
|
64
|
-
if order_by:
|
|
65
|
-
if isinstance(order_by, str):
|
|
66
|
-
over_args["order"] = exp.column(order_by).asc()
|
|
67
|
-
elif isinstance(order_by, list):
|
|
68
|
-
# Properly handle multiple ORDER BY columns using Order expression
|
|
69
|
-
order_expressions: list[Union[exp.Expression, exp.Column]] = []
|
|
70
|
-
for col in order_by:
|
|
71
|
-
if isinstance(col, str):
|
|
72
|
-
order_expressions.append(exp.column(col).asc())
|
|
73
|
-
else:
|
|
74
|
-
order_expressions.append(col)
|
|
75
|
-
over_args["order"] = exp.Order(expressions=order_expressions)
|
|
76
|
-
elif isinstance(order_by, exp.Expression):
|
|
77
|
-
over_args["order"] = order_by
|
|
78
|
-
|
|
79
|
-
if frame:
|
|
80
|
-
frame_expr: Optional[exp.Expression] = exp.maybe_parse(frame, dialect=getattr(self, "dialect", None))
|
|
81
|
-
if frame_expr:
|
|
82
|
-
over_args["frame"] = frame_expr
|
|
83
|
-
|
|
84
|
-
window_expr = exp.Window(this=func_expr_parsed, **over_args)
|
|
85
|
-
self._expression.select(exp.alias_(window_expr, alias) if alias else window_expr, copy=False)
|
|
86
|
-
return self
|