sqlspec 0.16.1__cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.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.
- 51ff5a9eadfdefd49f98__mypyc.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/__init__.py +92 -0
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +14 -0
- sqlspec/_serialization.py +77 -0
- sqlspec/_sql.py +1780 -0
- sqlspec/_typing.py +680 -0
- sqlspec/adapters/__init__.py +0 -0
- sqlspec/adapters/adbc/__init__.py +5 -0
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +361 -0
- sqlspec/adapters/adbc/driver.py +512 -0
- sqlspec/adapters/aiosqlite/__init__.py +19 -0
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +253 -0
- sqlspec/adapters/aiosqlite/driver.py +248 -0
- sqlspec/adapters/asyncmy/__init__.py +19 -0
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +180 -0
- sqlspec/adapters/asyncmy/driver.py +274 -0
- sqlspec/adapters/asyncpg/__init__.py +21 -0
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +229 -0
- sqlspec/adapters/asyncpg/driver.py +344 -0
- sqlspec/adapters/bigquery/__init__.py +18 -0
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +298 -0
- sqlspec/adapters/bigquery/driver.py +558 -0
- sqlspec/adapters/duckdb/__init__.py +22 -0
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +504 -0
- sqlspec/adapters/duckdb/driver.py +368 -0
- sqlspec/adapters/oracledb/__init__.py +32 -0
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +317 -0
- sqlspec/adapters/oracledb/driver.py +538 -0
- sqlspec/adapters/psqlpy/__init__.py +16 -0
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +214 -0
- sqlspec/adapters/psqlpy/driver.py +530 -0
- sqlspec/adapters/psycopg/__init__.py +32 -0
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +426 -0
- sqlspec/adapters/psycopg/driver.py +796 -0
- sqlspec/adapters/sqlite/__init__.py +15 -0
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +240 -0
- sqlspec/adapters/sqlite/driver.py +294 -0
- sqlspec/base.py +571 -0
- sqlspec/builder/__init__.py +62 -0
- sqlspec/builder/_base.py +473 -0
- sqlspec/builder/_column.py +320 -0
- sqlspec/builder/_ddl.py +1346 -0
- sqlspec/builder/_ddl_utils.py +103 -0
- sqlspec/builder/_delete.py +76 -0
- sqlspec/builder/_insert.py +256 -0
- sqlspec/builder/_merge.py +71 -0
- sqlspec/builder/_parsing_utils.py +140 -0
- sqlspec/builder/_select.py +170 -0
- sqlspec/builder/_update.py +188 -0
- sqlspec/builder/mixins/__init__.py +55 -0
- sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
- sqlspec/builder/mixins/_delete_operations.py +41 -0
- sqlspec/builder/mixins/_insert_operations.py +244 -0
- sqlspec/builder/mixins/_join_operations.py +122 -0
- sqlspec/builder/mixins/_merge_operations.py +476 -0
- sqlspec/builder/mixins/_order_limit_operations.py +135 -0
- sqlspec/builder/mixins/_pivot_operations.py +153 -0
- sqlspec/builder/mixins/_select_operations.py +603 -0
- sqlspec/builder/mixins/_update_operations.py +187 -0
- sqlspec/builder/mixins/_where_clause.py +621 -0
- sqlspec/cli.py +247 -0
- sqlspec/config.py +395 -0
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/cache.py +871 -0
- sqlspec/core/compiler.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/compiler.py +417 -0
- sqlspec/core/filters.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/filters.py +830 -0
- sqlspec/core/hashing.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters.py +1237 -0
- sqlspec/core/result.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/result.py +677 -0
- sqlspec/core/splitter.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/splitter.py +819 -0
- sqlspec/core/statement.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/statement.py +676 -0
- sqlspec/driver/__init__.py +19 -0
- sqlspec/driver/_async.py +502 -0
- sqlspec/driver/_common.py +631 -0
- sqlspec/driver/_sync.py +503 -0
- sqlspec/driver/mixins/__init__.py +6 -0
- sqlspec/driver/mixins/_result_tools.py +193 -0
- sqlspec/driver/mixins/_sql_translator.py +86 -0
- sqlspec/exceptions.py +193 -0
- sqlspec/extensions/__init__.py +0 -0
- sqlspec/extensions/aiosql/__init__.py +10 -0
- sqlspec/extensions/aiosql/adapter.py +461 -0
- sqlspec/extensions/litestar/__init__.py +6 -0
- sqlspec/extensions/litestar/_utils.py +52 -0
- sqlspec/extensions/litestar/cli.py +48 -0
- sqlspec/extensions/litestar/config.py +92 -0
- sqlspec/extensions/litestar/handlers.py +260 -0
- sqlspec/extensions/litestar/plugin.py +145 -0
- sqlspec/extensions/litestar/providers.py +454 -0
- sqlspec/loader.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/loader.py +760 -0
- sqlspec/migrations/__init__.py +35 -0
- sqlspec/migrations/base.py +414 -0
- sqlspec/migrations/commands.py +443 -0
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +213 -0
- sqlspec/migrations/tracker.py +140 -0
- sqlspec/migrations/utils.py +129 -0
- sqlspec/protocols.py +407 -0
- sqlspec/py.typed +0 -0
- sqlspec/storage/__init__.py +23 -0
- sqlspec/storage/backends/__init__.py +0 -0
- sqlspec/storage/backends/base.py +163 -0
- sqlspec/storage/backends/fsspec.py +386 -0
- sqlspec/storage/backends/obstore.py +459 -0
- sqlspec/storage/capabilities.py +102 -0
- sqlspec/storage/registry.py +239 -0
- sqlspec/typing.py +299 -0
- sqlspec/utils/__init__.py +3 -0
- sqlspec/utils/correlation.py +150 -0
- sqlspec/utils/deprecation.py +106 -0
- sqlspec/utils/fixtures.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/fixtures.py +58 -0
- sqlspec/utils/logging.py +127 -0
- sqlspec/utils/module_loader.py +89 -0
- sqlspec/utils/serializers.py +4 -0
- sqlspec/utils/singleton.py +32 -0
- sqlspec/utils/sync_tools.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/sync_tools.py +237 -0
- sqlspec/utils/text.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/text.py +96 -0
- sqlspec/utils/type_guards.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/type_guards.py +1139 -0
- sqlspec-0.16.1.dist-info/METADATA +365 -0
- sqlspec-0.16.1.dist-info/RECORD +148 -0
- sqlspec-0.16.1.dist-info/WHEEL +7 -0
- sqlspec-0.16.1.dist-info/entry_points.txt +2 -0
- sqlspec-0.16.1.dist-info/licenses/LICENSE +21 -0
- sqlspec-0.16.1.dist-info/licenses/NOTICE +29 -0
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
# ruff: noqa: PLR2004
|
|
2
|
+
"""Consolidated WHERE and HAVING clause mixins."""
|
|
3
|
+
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
5
|
+
|
|
6
|
+
from mypy_extensions import trait
|
|
7
|
+
from sqlglot import exp
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from sqlspec.builder._parsing_utils import parse_column_expression, parse_condition_expression
|
|
11
|
+
from sqlspec.exceptions import SQLBuilderError
|
|
12
|
+
from sqlspec.utils.type_guards import has_query_builder_parameters, has_sqlglot_expression, is_iterable_parameters
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _extract_column_name(column: Union[str, exp.Column]) -> str:
|
|
16
|
+
"""Extract column name from column expression for parameter naming.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
column: Column expression (string or SQLGlot Column)
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Column name as string for use as parameter name
|
|
23
|
+
"""
|
|
24
|
+
if isinstance(column, str):
|
|
25
|
+
# Handle simple column names and table.column references
|
|
26
|
+
if "." in column:
|
|
27
|
+
return column.split(".")[-1] # Return just the column part
|
|
28
|
+
return column
|
|
29
|
+
if isinstance(column, exp.Column):
|
|
30
|
+
# Extract the column name from SQLGlot Column expression
|
|
31
|
+
try:
|
|
32
|
+
return str(column.this.this)
|
|
33
|
+
except AttributeError:
|
|
34
|
+
return str(column.this) if column.this else "column"
|
|
35
|
+
return "column"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from sqlspec.builder._column import ColumnExpression
|
|
40
|
+
from sqlspec.protocols import SQLBuilderProtocol
|
|
41
|
+
|
|
42
|
+
__all__ = ("HavingClauseMixin", "WhereClauseMixin")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@trait
|
|
46
|
+
class WhereClauseMixin:
|
|
47
|
+
"""Mixin providing WHERE clause methods for SELECT, UPDATE, and DELETE builders."""
|
|
48
|
+
|
|
49
|
+
__slots__ = ()
|
|
50
|
+
|
|
51
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
52
|
+
_expression: Optional[exp.Expression]
|
|
53
|
+
|
|
54
|
+
def _handle_in_operator(
|
|
55
|
+
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
56
|
+
) -> exp.Expression:
|
|
57
|
+
"""Handle IN operator."""
|
|
58
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
59
|
+
if is_iterable_parameters(value):
|
|
60
|
+
placeholders = []
|
|
61
|
+
for i, v in enumerate(value):
|
|
62
|
+
if len(value) == 1:
|
|
63
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
64
|
+
else:
|
|
65
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
66
|
+
_, param_name = builder.add_parameter(v, name=param_name)
|
|
67
|
+
placeholders.append(exp.Placeholder(this=param_name))
|
|
68
|
+
return exp.In(this=column_exp, expressions=placeholders)
|
|
69
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
70
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
71
|
+
return exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)])
|
|
72
|
+
|
|
73
|
+
def _handle_not_in_operator(
|
|
74
|
+
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
75
|
+
) -> exp.Expression:
|
|
76
|
+
"""Handle NOT IN operator."""
|
|
77
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
78
|
+
if is_iterable_parameters(value):
|
|
79
|
+
placeholders = []
|
|
80
|
+
for i, v in enumerate(value):
|
|
81
|
+
if len(value) == 1:
|
|
82
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
83
|
+
else:
|
|
84
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
85
|
+
_, param_name = builder.add_parameter(v, name=param_name)
|
|
86
|
+
placeholders.append(exp.Placeholder(this=param_name))
|
|
87
|
+
return exp.Not(this=exp.In(this=column_exp, expressions=placeholders))
|
|
88
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
89
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
90
|
+
return exp.Not(this=exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)]))
|
|
91
|
+
|
|
92
|
+
def _handle_is_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
|
|
93
|
+
"""Handle IS operator."""
|
|
94
|
+
value_expr = exp.Null() if value is None else exp.convert(value)
|
|
95
|
+
return exp.Is(this=column_exp, expression=value_expr)
|
|
96
|
+
|
|
97
|
+
def _handle_is_not_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
|
|
98
|
+
"""Handle IS NOT operator."""
|
|
99
|
+
value_expr = exp.Null() if value is None else exp.convert(value)
|
|
100
|
+
return exp.Not(this=exp.Is(this=column_exp, expression=value_expr))
|
|
101
|
+
|
|
102
|
+
def _handle_between_operator(
|
|
103
|
+
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
104
|
+
) -> exp.Expression:
|
|
105
|
+
"""Handle BETWEEN operator."""
|
|
106
|
+
if is_iterable_parameters(value) and len(value) == 2:
|
|
107
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
108
|
+
low, high = value
|
|
109
|
+
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
110
|
+
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
111
|
+
_, low_param = builder.add_parameter(low, name=low_param)
|
|
112
|
+
_, high_param = builder.add_parameter(high, name=high_param)
|
|
113
|
+
return exp.Between(
|
|
114
|
+
this=column_exp, low=exp.Placeholder(this=low_param), high=exp.Placeholder(this=high_param)
|
|
115
|
+
)
|
|
116
|
+
msg = f"BETWEEN operator requires a tuple of two values, got {type(value).__name__}"
|
|
117
|
+
raise SQLBuilderError(msg)
|
|
118
|
+
|
|
119
|
+
def _handle_not_between_operator(
|
|
120
|
+
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
121
|
+
) -> exp.Expression:
|
|
122
|
+
"""Handle NOT BETWEEN operator."""
|
|
123
|
+
if is_iterable_parameters(value) and len(value) == 2:
|
|
124
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
125
|
+
low, high = value
|
|
126
|
+
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
127
|
+
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
128
|
+
_, low_param = builder.add_parameter(low, name=low_param)
|
|
129
|
+
_, high_param = builder.add_parameter(high, name=high_param)
|
|
130
|
+
return exp.Not(
|
|
131
|
+
this=exp.Between(
|
|
132
|
+
this=column_exp, low=exp.Placeholder(this=low_param), high=exp.Placeholder(this=high_param)
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
msg = f"NOT BETWEEN operator requires a tuple of two values, got {type(value).__name__}"
|
|
136
|
+
raise SQLBuilderError(msg)
|
|
137
|
+
|
|
138
|
+
def _process_tuple_condition(self, condition: tuple) -> exp.Expression:
|
|
139
|
+
"""Process tuple-based WHERE conditions."""
|
|
140
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
141
|
+
column_name_raw = str(condition[0])
|
|
142
|
+
column_exp = parse_column_expression(column_name_raw)
|
|
143
|
+
column_name = _extract_column_name(column_name_raw)
|
|
144
|
+
|
|
145
|
+
if len(condition) == 2:
|
|
146
|
+
# (column, value) tuple for equality
|
|
147
|
+
value = condition[1]
|
|
148
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
149
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
150
|
+
return exp.EQ(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
151
|
+
|
|
152
|
+
if len(condition) == 3:
|
|
153
|
+
# (column, operator, value) tuple
|
|
154
|
+
operator = str(condition[1]).upper()
|
|
155
|
+
value = condition[2]
|
|
156
|
+
|
|
157
|
+
if operator == "=":
|
|
158
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
159
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
160
|
+
return exp.EQ(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
161
|
+
if operator in {"!=", "<>"}:
|
|
162
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
163
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
164
|
+
return exp.NEQ(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
165
|
+
if operator == ">":
|
|
166
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
167
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
168
|
+
return exp.GT(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
169
|
+
if operator == ">=":
|
|
170
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
171
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
172
|
+
return exp.GTE(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
173
|
+
if operator == "<":
|
|
174
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
175
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
176
|
+
return exp.LT(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
177
|
+
if operator == "<=":
|
|
178
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
179
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
180
|
+
return exp.LTE(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
181
|
+
if operator == "LIKE":
|
|
182
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
183
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
184
|
+
return exp.Like(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
185
|
+
if operator == "NOT LIKE":
|
|
186
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
187
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
188
|
+
return exp.Not(this=exp.Like(this=column_exp, expression=exp.Placeholder(this=param_name)))
|
|
189
|
+
|
|
190
|
+
if operator == "IN":
|
|
191
|
+
return self._handle_in_operator(column_exp, value, column_name)
|
|
192
|
+
if operator == "NOT IN":
|
|
193
|
+
return self._handle_not_in_operator(column_exp, value, column_name)
|
|
194
|
+
if operator == "IS":
|
|
195
|
+
return self._handle_is_operator(column_exp, value)
|
|
196
|
+
if operator == "IS NOT":
|
|
197
|
+
return self._handle_is_not_operator(column_exp, value)
|
|
198
|
+
if operator == "BETWEEN":
|
|
199
|
+
return self._handle_between_operator(column_exp, value, column_name)
|
|
200
|
+
if operator == "NOT BETWEEN":
|
|
201
|
+
return self._handle_not_between_operator(column_exp, value, column_name)
|
|
202
|
+
|
|
203
|
+
msg = f"Unsupported operator: {operator}"
|
|
204
|
+
raise SQLBuilderError(msg)
|
|
205
|
+
|
|
206
|
+
msg = f"Condition tuple must have 2 or 3 elements, got {len(condition)}"
|
|
207
|
+
raise SQLBuilderError(msg)
|
|
208
|
+
|
|
209
|
+
def where(
|
|
210
|
+
self,
|
|
211
|
+
condition: Union[str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression"],
|
|
212
|
+
value: Optional[Any] = None,
|
|
213
|
+
operator: Optional[str] = None,
|
|
214
|
+
) -> Self:
|
|
215
|
+
"""Add a WHERE clause to the statement.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
condition: The condition for the WHERE clause. Can be:
|
|
219
|
+
- A string condition (when value is None)
|
|
220
|
+
- A string column name (when value is provided)
|
|
221
|
+
- A sqlglot Expression or Condition
|
|
222
|
+
- A 2-tuple (column, value) for equality comparison
|
|
223
|
+
- A 3-tuple (column, operator, value) for custom comparison
|
|
224
|
+
value: Value for comparison (when condition is a column name)
|
|
225
|
+
operator: Operator for comparison (when both condition and value provided)
|
|
226
|
+
|
|
227
|
+
Raises:
|
|
228
|
+
SQLBuilderError: If the current expression is not a supported statement type.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
The current builder instance for method chaining.
|
|
232
|
+
"""
|
|
233
|
+
if self.__class__.__name__ == "Update" and not isinstance(self._expression, exp.Update):
|
|
234
|
+
msg = "Cannot add WHERE clause to non-UPDATE expression"
|
|
235
|
+
raise SQLBuilderError(msg)
|
|
236
|
+
|
|
237
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
238
|
+
if builder._expression is None:
|
|
239
|
+
msg = "Cannot add WHERE clause: expression is not initialized."
|
|
240
|
+
raise SQLBuilderError(msg)
|
|
241
|
+
|
|
242
|
+
if isinstance(builder._expression, exp.Delete) and not builder._expression.args.get("this"):
|
|
243
|
+
msg = "WHERE clause requires a table to be set. Use from() to set the table first."
|
|
244
|
+
raise SQLBuilderError(msg)
|
|
245
|
+
|
|
246
|
+
if value is not None:
|
|
247
|
+
if not isinstance(condition, str):
|
|
248
|
+
msg = "When value is provided, condition must be a column name (string)"
|
|
249
|
+
raise SQLBuilderError(msg)
|
|
250
|
+
|
|
251
|
+
if operator is not None:
|
|
252
|
+
where_expr = self._process_tuple_condition((condition, operator, value))
|
|
253
|
+
else:
|
|
254
|
+
where_expr = self._process_tuple_condition((condition, value))
|
|
255
|
+
elif isinstance(condition, str):
|
|
256
|
+
where_expr = parse_condition_expression(condition)
|
|
257
|
+
elif isinstance(condition, (exp.Expression, exp.Condition)):
|
|
258
|
+
where_expr = condition
|
|
259
|
+
elif isinstance(condition, tuple):
|
|
260
|
+
where_expr = self._process_tuple_condition(condition)
|
|
261
|
+
elif has_query_builder_parameters(condition):
|
|
262
|
+
column_expr_obj = cast("ColumnExpression", condition)
|
|
263
|
+
where_expr = column_expr_obj._expression # pyright: ignore
|
|
264
|
+
elif has_sqlglot_expression(condition):
|
|
265
|
+
raw_expr = condition.sqlglot_expression # pyright: ignore[attr-defined]
|
|
266
|
+
if raw_expr is not None:
|
|
267
|
+
where_expr = builder._parameterize_expression(raw_expr)
|
|
268
|
+
else:
|
|
269
|
+
where_expr = parse_condition_expression(str(condition))
|
|
270
|
+
else:
|
|
271
|
+
msg = f"Unsupported condition type: {type(condition).__name__}"
|
|
272
|
+
raise SQLBuilderError(msg)
|
|
273
|
+
|
|
274
|
+
if isinstance(builder._expression, (exp.Select, exp.Update, exp.Delete)):
|
|
275
|
+
builder._expression = builder._expression.where(where_expr, copy=False)
|
|
276
|
+
else:
|
|
277
|
+
msg = f"WHERE clause not supported for {type(builder._expression).__name__}"
|
|
278
|
+
raise SQLBuilderError(msg)
|
|
279
|
+
return self
|
|
280
|
+
|
|
281
|
+
def where_eq(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
282
|
+
"""Add WHERE column = value clause."""
|
|
283
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
284
|
+
column_name = _extract_column_name(column)
|
|
285
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
286
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
287
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
288
|
+
condition: exp.Expression = col_expr.eq(exp.Placeholder(this=param_name))
|
|
289
|
+
return self.where(condition)
|
|
290
|
+
|
|
291
|
+
def where_neq(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
292
|
+
"""Add WHERE column != value clause."""
|
|
293
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
294
|
+
column_name = _extract_column_name(column)
|
|
295
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
296
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
297
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
298
|
+
condition: exp.Expression = col_expr.neq(exp.Placeholder(this=param_name))
|
|
299
|
+
return self.where(condition)
|
|
300
|
+
|
|
301
|
+
def where_lt(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
302
|
+
"""Add WHERE column < value clause."""
|
|
303
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
304
|
+
column_name = _extract_column_name(column)
|
|
305
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
306
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
307
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
308
|
+
condition: exp.Expression = exp.LT(this=col_expr, expression=exp.Placeholder(this=param_name))
|
|
309
|
+
return self.where(condition)
|
|
310
|
+
|
|
311
|
+
def where_lte(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
312
|
+
"""Add WHERE column <= value clause."""
|
|
313
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
314
|
+
column_name = _extract_column_name(column)
|
|
315
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
316
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
317
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
318
|
+
condition: exp.Expression = exp.LTE(this=col_expr, expression=exp.Placeholder(this=param_name))
|
|
319
|
+
return self.where(condition)
|
|
320
|
+
|
|
321
|
+
def where_gt(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
322
|
+
"""Add WHERE column > value clause."""
|
|
323
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
324
|
+
column_name = _extract_column_name(column)
|
|
325
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
326
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
327
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
328
|
+
condition: exp.Expression = exp.GT(this=col_expr, expression=exp.Placeholder(this=param_name))
|
|
329
|
+
return self.where(condition)
|
|
330
|
+
|
|
331
|
+
def where_gte(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
332
|
+
"""Add WHERE column >= value clause."""
|
|
333
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
334
|
+
column_name = _extract_column_name(column)
|
|
335
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
336
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
337
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
338
|
+
condition: exp.Expression = exp.GTE(this=col_expr, expression=exp.Placeholder(this=param_name))
|
|
339
|
+
return self.where(condition)
|
|
340
|
+
|
|
341
|
+
def where_between(self, column: Union[str, exp.Column], low: Any, high: Any) -> Self:
|
|
342
|
+
"""Add WHERE column BETWEEN low AND high clause."""
|
|
343
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
344
|
+
column_name = _extract_column_name(column)
|
|
345
|
+
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
346
|
+
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
347
|
+
_, low_param = builder.add_parameter(low, name=low_param)
|
|
348
|
+
_, high_param = builder.add_parameter(high, name=high_param)
|
|
349
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
350
|
+
condition: exp.Expression = col_expr.between(exp.Placeholder(this=low_param), exp.Placeholder(this=high_param))
|
|
351
|
+
return self.where(condition)
|
|
352
|
+
|
|
353
|
+
def where_like(self, column: Union[str, exp.Column], pattern: str, escape: Optional[str] = None) -> Self:
|
|
354
|
+
"""Add WHERE column LIKE pattern clause."""
|
|
355
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
356
|
+
column_name = _extract_column_name(column)
|
|
357
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
358
|
+
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
359
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
360
|
+
if escape is not None:
|
|
361
|
+
cond = exp.Like(this=col_expr, expression=exp.Placeholder(this=param_name), escape=exp.convert(str(escape)))
|
|
362
|
+
else:
|
|
363
|
+
cond = col_expr.like(exp.Placeholder(this=param_name))
|
|
364
|
+
condition: exp.Expression = cond
|
|
365
|
+
return self.where(condition)
|
|
366
|
+
|
|
367
|
+
def where_not_like(self, column: Union[str, exp.Column], pattern: str) -> Self:
|
|
368
|
+
"""Add WHERE column NOT LIKE pattern clause."""
|
|
369
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
370
|
+
column_name = _extract_column_name(column)
|
|
371
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
372
|
+
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
373
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
374
|
+
condition: exp.Expression = col_expr.like(exp.Placeholder(this=param_name)).not_()
|
|
375
|
+
return self.where(condition)
|
|
376
|
+
|
|
377
|
+
def where_ilike(self, column: Union[str, exp.Column], pattern: str) -> Self:
|
|
378
|
+
"""Add WHERE column ILIKE pattern clause."""
|
|
379
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
380
|
+
column_name = _extract_column_name(column)
|
|
381
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
382
|
+
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
383
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
384
|
+
condition: exp.Expression = col_expr.ilike(exp.Placeholder(this=param_name))
|
|
385
|
+
return self.where(condition)
|
|
386
|
+
|
|
387
|
+
def where_is_null(self, column: Union[str, exp.Column]) -> Self:
|
|
388
|
+
"""Add WHERE column IS NULL clause."""
|
|
389
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
390
|
+
condition: exp.Expression = col_expr.is_(exp.null())
|
|
391
|
+
return self.where(condition)
|
|
392
|
+
|
|
393
|
+
def where_is_not_null(self, column: Union[str, exp.Column]) -> Self:
|
|
394
|
+
"""Add WHERE column IS NOT NULL clause."""
|
|
395
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
396
|
+
condition: exp.Expression = col_expr.is_(exp.null()).not_()
|
|
397
|
+
return self.where(condition)
|
|
398
|
+
|
|
399
|
+
def where_in(self, column: Union[str, exp.Column], values: Any) -> Self:
|
|
400
|
+
"""Add WHERE column IN (values) clause."""
|
|
401
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
402
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
403
|
+
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
404
|
+
subquery_exp: exp.Expression
|
|
405
|
+
if has_query_builder_parameters(values):
|
|
406
|
+
subquery = values.build() # pyright: ignore
|
|
407
|
+
sql_str = subquery.sql
|
|
408
|
+
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=builder.dialect_name)) # pyright: ignore
|
|
409
|
+
# Merge subquery parameters into parent builder
|
|
410
|
+
if hasattr(subquery, "parameters") and isinstance(subquery.parameters, dict): # pyright: ignore[reportAttributeAccessIssue]
|
|
411
|
+
for param_name, param_value in subquery.parameters.items(): # pyright: ignore[reportAttributeAccessIssue]
|
|
412
|
+
builder.add_parameter(param_value, name=param_name)
|
|
413
|
+
else:
|
|
414
|
+
subquery_exp = values # type: ignore[assignment]
|
|
415
|
+
condition = col_expr.isin(subquery_exp)
|
|
416
|
+
return self.where(condition)
|
|
417
|
+
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
418
|
+
msg = "Unsupported type for 'values' in WHERE IN"
|
|
419
|
+
raise SQLBuilderError(msg)
|
|
420
|
+
column_name = _extract_column_name(column)
|
|
421
|
+
parameters = []
|
|
422
|
+
for i, v in enumerate(values):
|
|
423
|
+
if len(values) == 1:
|
|
424
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
425
|
+
else:
|
|
426
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
427
|
+
_, param_name = builder.add_parameter(v, name=param_name)
|
|
428
|
+
parameters.append(exp.Placeholder(this=param_name))
|
|
429
|
+
condition = col_expr.isin(*parameters)
|
|
430
|
+
return self.where(condition)
|
|
431
|
+
|
|
432
|
+
def where_not_in(self, column: Union[str, exp.Column], values: Any) -> Self:
|
|
433
|
+
"""Add WHERE column NOT IN (values) clause."""
|
|
434
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
435
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
436
|
+
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
437
|
+
subquery_exp: exp.Expression
|
|
438
|
+
if has_query_builder_parameters(values):
|
|
439
|
+
subquery = values.build() # pyright: ignore
|
|
440
|
+
|
|
441
|
+
subquery_exp = exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
|
|
442
|
+
else:
|
|
443
|
+
subquery_exp = values # type: ignore[assignment]
|
|
444
|
+
condition = exp.Not(this=col_expr.isin(subquery_exp))
|
|
445
|
+
return self.where(condition)
|
|
446
|
+
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
447
|
+
msg = "Values for where_not_in must be a non-string iterable or subquery."
|
|
448
|
+
raise SQLBuilderError(msg)
|
|
449
|
+
column_name = _extract_column_name(column)
|
|
450
|
+
parameters = []
|
|
451
|
+
for i, v in enumerate(values):
|
|
452
|
+
if len(values) == 1:
|
|
453
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
454
|
+
else:
|
|
455
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
456
|
+
_, param_name = builder.add_parameter(v, name=param_name)
|
|
457
|
+
parameters.append(exp.Placeholder(this=param_name))
|
|
458
|
+
condition = exp.Not(this=col_expr.isin(*parameters))
|
|
459
|
+
return self.where(condition)
|
|
460
|
+
|
|
461
|
+
def where_null(self, column: Union[str, exp.Column]) -> Self:
|
|
462
|
+
"""Add WHERE column IS NULL clause."""
|
|
463
|
+
return self.where_is_null(column)
|
|
464
|
+
|
|
465
|
+
def where_not_null(self, column: Union[str, exp.Column]) -> Self:
|
|
466
|
+
"""Add WHERE column IS NOT NULL clause."""
|
|
467
|
+
return self.where_is_not_null(column)
|
|
468
|
+
|
|
469
|
+
def where_exists(self, subquery: Union[str, Any]) -> Self:
|
|
470
|
+
"""Add WHERE EXISTS (subquery) clause."""
|
|
471
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
472
|
+
sub_expr: exp.Expression
|
|
473
|
+
if has_query_builder_parameters(subquery):
|
|
474
|
+
subquery_builder_parameters: dict[str, Any] = subquery.parameters
|
|
475
|
+
if subquery_builder_parameters:
|
|
476
|
+
for p_name, p_value in subquery_builder_parameters.items():
|
|
477
|
+
builder.add_parameter(p_value, name=p_name)
|
|
478
|
+
sub_sql_obj = subquery.build() # pyright: ignore
|
|
479
|
+
|
|
480
|
+
sub_expr = exp.maybe_parse(sub_sql_obj.sql, dialect=builder.dialect_name) # pyright: ignore
|
|
481
|
+
else:
|
|
482
|
+
sub_expr = exp.maybe_parse(str(subquery), dialect=builder.dialect_name)
|
|
483
|
+
|
|
484
|
+
if sub_expr is None:
|
|
485
|
+
msg = "Could not parse subquery for EXISTS"
|
|
486
|
+
raise SQLBuilderError(msg)
|
|
487
|
+
|
|
488
|
+
exists_expr = exp.Exists(this=sub_expr)
|
|
489
|
+
return self.where(exists_expr)
|
|
490
|
+
|
|
491
|
+
def where_not_exists(self, subquery: Union[str, Any]) -> Self:
|
|
492
|
+
"""Add WHERE NOT EXISTS (subquery) clause."""
|
|
493
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
494
|
+
sub_expr: exp.Expression
|
|
495
|
+
if has_query_builder_parameters(subquery):
|
|
496
|
+
subquery_builder_parameters: dict[str, Any] = subquery.parameters
|
|
497
|
+
if subquery_builder_parameters:
|
|
498
|
+
for p_name, p_value in subquery_builder_parameters.items():
|
|
499
|
+
builder.add_parameter(p_value, name=p_name)
|
|
500
|
+
sub_sql_obj = subquery.build() # pyright: ignore
|
|
501
|
+
sub_expr = exp.maybe_parse(sub_sql_obj.sql, dialect=builder.dialect_name) # pyright: ignore
|
|
502
|
+
else:
|
|
503
|
+
sub_expr = exp.maybe_parse(str(subquery), dialect=builder.dialect_name)
|
|
504
|
+
|
|
505
|
+
if sub_expr is None:
|
|
506
|
+
msg = "Could not parse subquery for NOT EXISTS"
|
|
507
|
+
raise SQLBuilderError(msg)
|
|
508
|
+
|
|
509
|
+
not_exists_expr = exp.Not(this=exp.Exists(this=sub_expr))
|
|
510
|
+
return self.where(not_exists_expr)
|
|
511
|
+
|
|
512
|
+
def where_any(self, column: Union[str, exp.Column], values: Any) -> Self:
|
|
513
|
+
"""Add WHERE column = ANY(values) clause."""
|
|
514
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
515
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
516
|
+
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
517
|
+
subquery_exp: exp.Expression
|
|
518
|
+
if has_query_builder_parameters(values):
|
|
519
|
+
subquery = values.build() # pyright: ignore
|
|
520
|
+
subquery_exp = exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
|
|
521
|
+
else:
|
|
522
|
+
subquery_exp = values # type: ignore[assignment]
|
|
523
|
+
condition = exp.EQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
524
|
+
return self.where(condition)
|
|
525
|
+
if isinstance(values, str):
|
|
526
|
+
try:
|
|
527
|
+
parsed_expr: Optional[exp.Expression] = exp.maybe_parse(values)
|
|
528
|
+
if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
|
|
529
|
+
subquery_exp = exp.paren(parsed_expr)
|
|
530
|
+
condition = exp.EQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
531
|
+
return self.where(condition)
|
|
532
|
+
except Exception: # noqa: S110
|
|
533
|
+
pass
|
|
534
|
+
msg = "Unsupported type for 'values' in WHERE ANY"
|
|
535
|
+
raise SQLBuilderError(msg)
|
|
536
|
+
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
537
|
+
msg = "Unsupported type for 'values' in WHERE ANY"
|
|
538
|
+
raise SQLBuilderError(msg)
|
|
539
|
+
column_name = _extract_column_name(column)
|
|
540
|
+
parameters = []
|
|
541
|
+
for i, v in enumerate(values):
|
|
542
|
+
if len(values) == 1:
|
|
543
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
544
|
+
else:
|
|
545
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_any_{i + 1}")
|
|
546
|
+
_, param_name = builder.add_parameter(v, name=param_name)
|
|
547
|
+
parameters.append(exp.Placeholder(this=param_name))
|
|
548
|
+
tuple_expr = exp.Tuple(expressions=parameters)
|
|
549
|
+
condition = exp.EQ(this=col_expr, expression=exp.Any(this=tuple_expr))
|
|
550
|
+
return self.where(condition)
|
|
551
|
+
|
|
552
|
+
def where_not_any(self, column: Union[str, exp.Column], values: Any) -> Self:
|
|
553
|
+
"""Add WHERE column <> ANY(values) clause."""
|
|
554
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
555
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
556
|
+
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
557
|
+
subquery_exp: exp.Expression
|
|
558
|
+
if has_query_builder_parameters(values):
|
|
559
|
+
subquery = values.build() # pyright: ignore
|
|
560
|
+
subquery_exp = exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
|
|
561
|
+
else:
|
|
562
|
+
subquery_exp = values # type: ignore[assignment]
|
|
563
|
+
condition = exp.NEQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
564
|
+
return self.where(condition)
|
|
565
|
+
if isinstance(values, str):
|
|
566
|
+
try:
|
|
567
|
+
parsed_expr: Optional[exp.Expression] = exp.maybe_parse(values)
|
|
568
|
+
if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
|
|
569
|
+
subquery_exp = exp.paren(parsed_expr)
|
|
570
|
+
condition = exp.NEQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
571
|
+
return self.where(condition)
|
|
572
|
+
except Exception: # noqa: S110
|
|
573
|
+
pass
|
|
574
|
+
msg = "Unsupported type for 'values' in WHERE NOT ANY"
|
|
575
|
+
raise SQLBuilderError(msg)
|
|
576
|
+
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
577
|
+
msg = "Unsupported type for 'values' in WHERE NOT ANY"
|
|
578
|
+
raise SQLBuilderError(msg)
|
|
579
|
+
column_name = _extract_column_name(column)
|
|
580
|
+
parameters = []
|
|
581
|
+
for i, v in enumerate(values):
|
|
582
|
+
if len(values) == 1:
|
|
583
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
584
|
+
else:
|
|
585
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_not_any_{i + 1}")
|
|
586
|
+
_, param_name = builder.add_parameter(v, name=param_name)
|
|
587
|
+
parameters.append(exp.Placeholder(this=param_name))
|
|
588
|
+
tuple_expr = exp.Tuple(expressions=parameters)
|
|
589
|
+
condition = exp.NEQ(this=col_expr, expression=exp.Any(this=tuple_expr))
|
|
590
|
+
return self.where(condition)
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
@trait
|
|
594
|
+
class HavingClauseMixin:
|
|
595
|
+
"""Mixin providing HAVING clause for SELECT builders."""
|
|
596
|
+
|
|
597
|
+
__slots__ = ()
|
|
598
|
+
|
|
599
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
600
|
+
_expression: Optional[exp.Expression]
|
|
601
|
+
|
|
602
|
+
def having(self, condition: Union[str, exp.Expression]) -> Self:
|
|
603
|
+
"""Add HAVING clause.
|
|
604
|
+
|
|
605
|
+
Args:
|
|
606
|
+
condition: The condition for the HAVING clause.
|
|
607
|
+
|
|
608
|
+
Raises:
|
|
609
|
+
SQLBuilderError: If the current expression is not a SELECT statement.
|
|
610
|
+
|
|
611
|
+
Returns:
|
|
612
|
+
The current builder instance for method chaining.
|
|
613
|
+
"""
|
|
614
|
+
if self._expression is None:
|
|
615
|
+
self._expression = exp.Select()
|
|
616
|
+
if not isinstance(self._expression, exp.Select):
|
|
617
|
+
msg = "Cannot add HAVING to a non-SELECT expression."
|
|
618
|
+
raise SQLBuilderError(msg)
|
|
619
|
+
having_expr = exp.condition(condition) if isinstance(condition, str) else condition
|
|
620
|
+
self._expression = self._expression.having(having_expr, copy=False)
|
|
621
|
+
return self
|