sqlspec 0.26.0__py3-none-any.whl → 0.27.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 +7 -15
- sqlspec/_serialization.py +55 -25
- sqlspec/_typing.py +62 -52
- sqlspec/adapters/adbc/_types.py +1 -1
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +870 -0
- sqlspec/adapters/adbc/config.py +62 -12
- sqlspec/adapters/adbc/data_dictionary.py +52 -2
- sqlspec/adapters/adbc/driver.py +144 -45
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +44 -50
- sqlspec/adapters/aiosqlite/_types.py +1 -1
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +527 -0
- sqlspec/adapters/aiosqlite/config.py +86 -16
- sqlspec/adapters/aiosqlite/data_dictionary.py +34 -2
- sqlspec/adapters/aiosqlite/driver.py +127 -38
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
- sqlspec/adapters/aiosqlite/pool.py +7 -7
- sqlspec/adapters/asyncmy/__init__.py +7 -1
- sqlspec/adapters/asyncmy/_types.py +1 -1
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +493 -0
- sqlspec/adapters/asyncmy/config.py +59 -17
- sqlspec/adapters/asyncmy/data_dictionary.py +41 -2
- sqlspec/adapters/asyncmy/driver.py +293 -62
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +2 -1
- sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
- sqlspec/adapters/asyncpg/_types.py +11 -7
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +450 -0
- sqlspec/adapters/asyncpg/config.py +57 -36
- sqlspec/adapters/asyncpg/data_dictionary.py +41 -2
- sqlspec/adapters/asyncpg/driver.py +153 -23
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +253 -0
- sqlspec/adapters/bigquery/_types.py +1 -1
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +576 -0
- sqlspec/adapters/bigquery/config.py +25 -11
- sqlspec/adapters/bigquery/data_dictionary.py +42 -2
- sqlspec/adapters/bigquery/driver.py +352 -144
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +55 -23
- sqlspec/adapters/duckdb/_types.py +2 -2
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +553 -0
- sqlspec/adapters/duckdb/config.py +79 -21
- sqlspec/adapters/duckdb/data_dictionary.py +41 -2
- sqlspec/adapters/duckdb/driver.py +138 -43
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +5 -5
- sqlspec/adapters/duckdb/type_converter.py +51 -21
- sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
- sqlspec/adapters/oracledb/_types.py +20 -2
- sqlspec/adapters/oracledb/adk/__init__.py +5 -0
- sqlspec/adapters/oracledb/adk/store.py +1745 -0
- sqlspec/adapters/oracledb/config.py +120 -36
- sqlspec/adapters/oracledb/data_dictionary.py +87 -20
- sqlspec/adapters/oracledb/driver.py +292 -84
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +767 -0
- sqlspec/adapters/oracledb/migrations.py +316 -25
- sqlspec/adapters/oracledb/type_converter.py +91 -16
- sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
- sqlspec/adapters/psqlpy/_types.py +2 -1
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +482 -0
- sqlspec/adapters/psqlpy/config.py +45 -19
- sqlspec/adapters/psqlpy/data_dictionary.py +41 -2
- sqlspec/adapters/psqlpy/driver.py +101 -31
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +40 -11
- sqlspec/adapters/psycopg/_type_handlers.py +80 -0
- sqlspec/adapters/psycopg/_types.py +2 -1
- sqlspec/adapters/psycopg/adk/__init__.py +5 -0
- sqlspec/adapters/psycopg/adk/store.py +944 -0
- sqlspec/adapters/psycopg/config.py +65 -37
- sqlspec/adapters/psycopg/data_dictionary.py +77 -3
- sqlspec/adapters/psycopg/driver.py +200 -78
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/sqlite/__init__.py +2 -1
- sqlspec/adapters/sqlite/_type_handlers.py +86 -0
- sqlspec/adapters/sqlite/_types.py +1 -1
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +572 -0
- sqlspec/adapters/sqlite/config.py +85 -16
- sqlspec/adapters/sqlite/data_dictionary.py +34 -2
- sqlspec/adapters/sqlite/driver.py +120 -52
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +5 -5
- sqlspec/base.py +45 -26
- sqlspec/builder/__init__.py +73 -4
- sqlspec/builder/_base.py +91 -58
- sqlspec/builder/_column.py +5 -5
- sqlspec/builder/_ddl.py +98 -89
- sqlspec/builder/_delete.py +5 -4
- sqlspec/builder/_dml.py +388 -0
- sqlspec/{_sql.py → builder/_factory.py} +41 -44
- sqlspec/builder/_insert.py +5 -82
- sqlspec/builder/{mixins/_join_operations.py → _join.py} +145 -143
- sqlspec/builder/_merge.py +446 -11
- sqlspec/builder/_parsing_utils.py +9 -11
- sqlspec/builder/_select.py +1313 -25
- sqlspec/builder/_update.py +11 -42
- sqlspec/cli.py +76 -69
- sqlspec/config.py +231 -60
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +18 -18
- sqlspec/core/compiler.py +6 -8
- sqlspec/core/filters.py +37 -37
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +76 -45
- sqlspec/core/result.py +102 -46
- sqlspec/core/splitter.py +16 -17
- sqlspec/core/statement.py +32 -31
- sqlspec/core/type_conversion.py +3 -2
- sqlspec/driver/__init__.py +1 -3
- sqlspec/driver/_async.py +95 -161
- sqlspec/driver/_common.py +133 -80
- sqlspec/driver/_sync.py +95 -162
- sqlspec/driver/mixins/_result_tools.py +20 -236
- sqlspec/driver/mixins/_sql_translator.py +4 -4
- sqlspec/exceptions.py +70 -7
- sqlspec/extensions/adk/__init__.py +53 -0
- sqlspec/extensions/adk/_types.py +51 -0
- sqlspec/extensions/adk/converters.py +172 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +181 -0
- sqlspec/extensions/adk/store.py +536 -0
- sqlspec/extensions/aiosql/adapter.py +73 -53
- sqlspec/extensions/litestar/__init__.py +21 -4
- sqlspec/extensions/litestar/cli.py +54 -10
- sqlspec/extensions/litestar/config.py +59 -266
- sqlspec/extensions/litestar/handlers.py +46 -17
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +324 -223
- sqlspec/extensions/litestar/providers.py +25 -25
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/loader.py +30 -49
- sqlspec/migrations/base.py +200 -76
- sqlspec/migrations/commands.py +591 -62
- sqlspec/migrations/context.py +6 -9
- sqlspec/migrations/fix.py +199 -0
- sqlspec/migrations/loaders.py +47 -19
- sqlspec/migrations/runner.py +241 -75
- sqlspec/migrations/tracker.py +237 -21
- sqlspec/migrations/utils.py +51 -3
- sqlspec/migrations/validation.py +177 -0
- sqlspec/protocols.py +66 -36
- sqlspec/storage/_utils.py +98 -0
- sqlspec/storage/backends/fsspec.py +134 -106
- sqlspec/storage/backends/local.py +78 -51
- sqlspec/storage/backends/obstore.py +278 -162
- sqlspec/storage/registry.py +75 -39
- sqlspec/typing.py +14 -84
- sqlspec/utils/config_resolver.py +6 -6
- sqlspec/utils/correlation.py +4 -5
- sqlspec/utils/data_transformation.py +3 -2
- sqlspec/utils/deprecation.py +9 -8
- sqlspec/utils/fixtures.py +4 -4
- sqlspec/utils/logging.py +46 -6
- sqlspec/utils/module_loader.py +2 -2
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +3 -3
- sqlspec/utils/sync_tools.py +21 -17
- sqlspec/utils/text.py +1 -2
- sqlspec/utils/type_guards.py +111 -20
- sqlspec/utils/version.py +433 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
- sqlspec-0.27.0.dist-info/RECORD +207 -0
- sqlspec/builder/mixins/__init__.py +0 -55
- sqlspec/builder/mixins/_cte_and_set_ops.py +0 -253
- sqlspec/builder/mixins/_delete_operations.py +0 -50
- sqlspec/builder/mixins/_insert_operations.py +0 -282
- sqlspec/builder/mixins/_merge_operations.py +0 -698
- sqlspec/builder/mixins/_order_limit_operations.py +0 -145
- sqlspec/builder/mixins/_pivot_operations.py +0 -157
- sqlspec/builder/mixins/_select_operations.py +0 -930
- sqlspec/builder/mixins/_update_operations.py +0 -199
- sqlspec/builder/mixins/_where_clause.py +0 -1298
- sqlspec-0.26.0.dist-info/RECORD +0 -157
- sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,1298 +0,0 @@
|
|
|
1
|
-
# ruff: noqa: PLR2004
|
|
2
|
-
# pyright: reportPrivateUsage=false, reportPrivateImportUsage=false
|
|
3
|
-
"""WHERE and HAVING clause mixins.
|
|
4
|
-
|
|
5
|
-
Provides mixins for WHERE and HAVING clause functionality with
|
|
6
|
-
parameter binding and various condition operators.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from typing import TYPE_CHECKING, Any, Callable, Optional, Union, cast
|
|
10
|
-
|
|
11
|
-
if TYPE_CHECKING:
|
|
12
|
-
from sqlspec.core.statement import SQL
|
|
13
|
-
|
|
14
|
-
from mypy_extensions import trait
|
|
15
|
-
from sqlglot import exp
|
|
16
|
-
from typing_extensions import Self
|
|
17
|
-
|
|
18
|
-
from sqlspec.builder._parsing_utils import extract_column_name, parse_column_expression, parse_condition_expression
|
|
19
|
-
from sqlspec.core.parameters import ParameterStyle, ParameterValidator
|
|
20
|
-
from sqlspec.core.statement import SQL
|
|
21
|
-
from sqlspec.exceptions import SQLBuilderError
|
|
22
|
-
from sqlspec.utils.type_guards import (
|
|
23
|
-
has_expression_and_parameters,
|
|
24
|
-
has_expression_and_sql,
|
|
25
|
-
has_query_builder_parameters,
|
|
26
|
-
has_sqlglot_expression,
|
|
27
|
-
is_iterable_parameters,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
if TYPE_CHECKING:
|
|
31
|
-
from sqlspec.builder._column import ColumnExpression
|
|
32
|
-
from sqlspec.protocols import SQLBuilderProtocol
|
|
33
|
-
|
|
34
|
-
__all__ = ("HavingClauseMixin", "WhereClauseMixin")
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@trait
|
|
38
|
-
class WhereClauseMixin:
|
|
39
|
-
"""Mixin providing WHERE clause methods for SELECT, UPDATE, and DELETE builders."""
|
|
40
|
-
|
|
41
|
-
__slots__ = ()
|
|
42
|
-
|
|
43
|
-
# Type annotations for PyRight - these will be provided by the base class
|
|
44
|
-
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
45
|
-
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
46
|
-
|
|
47
|
-
def _create_parameterized_condition(
|
|
48
|
-
self,
|
|
49
|
-
column: Union[str, exp.Column],
|
|
50
|
-
value: Any,
|
|
51
|
-
condition_factory: "Callable[[exp.Expression, exp.Placeholder], exp.Expression]",
|
|
52
|
-
) -> exp.Expression:
|
|
53
|
-
"""Create a parameterized condition using the provided factory function.
|
|
54
|
-
|
|
55
|
-
Args:
|
|
56
|
-
column: Column expression
|
|
57
|
-
value: Parameter value
|
|
58
|
-
condition_factory: Function that creates the condition expression
|
|
59
|
-
|
|
60
|
-
Returns:
|
|
61
|
-
The created condition expression
|
|
62
|
-
"""
|
|
63
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
64
|
-
column_name = extract_column_name(column)
|
|
65
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
66
|
-
_, param_name = builder.add_parameter(value, name=param_name)
|
|
67
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
68
|
-
placeholder = exp.Placeholder(this=param_name)
|
|
69
|
-
return condition_factory(col_expr, placeholder)
|
|
70
|
-
|
|
71
|
-
def _merge_sql_object_parameters(self, sql_obj: Any) -> None:
|
|
72
|
-
"""Merge parameters from a SQL object into the builder.
|
|
73
|
-
|
|
74
|
-
Args:
|
|
75
|
-
sql_obj: Object with parameters attribute containing parameter mappings
|
|
76
|
-
"""
|
|
77
|
-
if not has_expression_and_parameters(sql_obj):
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
81
|
-
sql_parameters = getattr(sql_obj, "parameters", {})
|
|
82
|
-
for param_name, param_value in sql_parameters.items():
|
|
83
|
-
unique_name = builder._generate_unique_parameter_name(param_name)
|
|
84
|
-
builder.add_parameter(param_value, name=unique_name)
|
|
85
|
-
|
|
86
|
-
def _apply_or_where(self, where_method: "Callable[..., Self]", *args: Any, **kwargs: Any) -> Self:
|
|
87
|
-
"""Apply a where method but use OR logic instead of AND.
|
|
88
|
-
|
|
89
|
-
This allows reusing all where_* methods for or_where_* functionality.
|
|
90
|
-
|
|
91
|
-
Args:
|
|
92
|
-
where_method: The where method to apply (e.g., self.where_eq)
|
|
93
|
-
*args: Arguments to pass to the where method
|
|
94
|
-
**kwargs: Keyword arguments to pass to the where method
|
|
95
|
-
|
|
96
|
-
Returns:
|
|
97
|
-
Self with OR condition applied
|
|
98
|
-
"""
|
|
99
|
-
# Create a temporary clone to capture the condition
|
|
100
|
-
original_expr = self.get_expression()
|
|
101
|
-
|
|
102
|
-
# Apply the where method to get the condition
|
|
103
|
-
where_method(*args, **kwargs)
|
|
104
|
-
|
|
105
|
-
# Get the last condition added by extracting it from the modified expression
|
|
106
|
-
current_expr = self.get_expression()
|
|
107
|
-
if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)) and original_expr != current_expr:
|
|
108
|
-
last_where = current_expr.find(exp.Where)
|
|
109
|
-
if last_where and last_where.this:
|
|
110
|
-
condition = last_where.this
|
|
111
|
-
# Restore original expression
|
|
112
|
-
if original_expr is not None:
|
|
113
|
-
self.set_expression(original_expr)
|
|
114
|
-
# Apply as OR
|
|
115
|
-
return self.or_where(condition)
|
|
116
|
-
|
|
117
|
-
return self
|
|
118
|
-
|
|
119
|
-
def _handle_in_operator(
|
|
120
|
-
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
121
|
-
) -> exp.Expression:
|
|
122
|
-
"""Handle IN operator."""
|
|
123
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
124
|
-
if is_iterable_parameters(value):
|
|
125
|
-
placeholders = []
|
|
126
|
-
for i, v in enumerate(value):
|
|
127
|
-
if len(value) == 1:
|
|
128
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
129
|
-
else:
|
|
130
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
131
|
-
_, param_name = builder.add_parameter(v, name=param_name)
|
|
132
|
-
placeholders.append(exp.Placeholder(this=param_name))
|
|
133
|
-
return exp.In(this=column_exp, expressions=placeholders)
|
|
134
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
135
|
-
_, param_name = builder.add_parameter(value, name=param_name)
|
|
136
|
-
return exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)])
|
|
137
|
-
|
|
138
|
-
def _handle_not_in_operator(
|
|
139
|
-
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
140
|
-
) -> exp.Expression:
|
|
141
|
-
"""Handle NOT IN operator."""
|
|
142
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
143
|
-
if is_iterable_parameters(value):
|
|
144
|
-
placeholders = []
|
|
145
|
-
for i, v in enumerate(value):
|
|
146
|
-
if len(value) == 1:
|
|
147
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
148
|
-
else:
|
|
149
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
150
|
-
_, param_name = builder.add_parameter(v, name=param_name)
|
|
151
|
-
placeholders.append(exp.Placeholder(this=param_name))
|
|
152
|
-
return exp.Not(this=exp.In(this=column_exp, expressions=placeholders))
|
|
153
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
154
|
-
_, param_name = builder.add_parameter(value, name=param_name)
|
|
155
|
-
return exp.Not(this=exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)]))
|
|
156
|
-
|
|
157
|
-
def _handle_is_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
|
|
158
|
-
"""Handle IS operator."""
|
|
159
|
-
value_expr = exp.Null() if value is None else exp.convert(value)
|
|
160
|
-
return exp.Is(this=column_exp, expression=value_expr)
|
|
161
|
-
|
|
162
|
-
def _handle_is_not_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
|
|
163
|
-
"""Handle IS NOT operator."""
|
|
164
|
-
value_expr = exp.Null() if value is None else exp.convert(value)
|
|
165
|
-
return exp.Not(this=exp.Is(this=column_exp, expression=value_expr))
|
|
166
|
-
|
|
167
|
-
def _handle_between_operator(
|
|
168
|
-
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
169
|
-
) -> exp.Expression:
|
|
170
|
-
"""Handle BETWEEN operator."""
|
|
171
|
-
if is_iterable_parameters(value) and len(value) == 2:
|
|
172
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
173
|
-
low, high = value
|
|
174
|
-
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
175
|
-
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
176
|
-
_, low_param = builder.add_parameter(low, name=low_param)
|
|
177
|
-
_, high_param = builder.add_parameter(high, name=high_param)
|
|
178
|
-
return exp.Between(
|
|
179
|
-
this=column_exp, low=exp.Placeholder(this=low_param), high=exp.Placeholder(this=high_param)
|
|
180
|
-
)
|
|
181
|
-
msg = f"BETWEEN operator requires a tuple of two values, got {type(value).__name__}"
|
|
182
|
-
raise SQLBuilderError(msg)
|
|
183
|
-
|
|
184
|
-
def _handle_not_between_operator(
|
|
185
|
-
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
186
|
-
) -> exp.Expression:
|
|
187
|
-
"""Handle NOT BETWEEN operator."""
|
|
188
|
-
if is_iterable_parameters(value) and len(value) == 2:
|
|
189
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
190
|
-
low, high = value
|
|
191
|
-
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
192
|
-
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
193
|
-
_, low_param = builder.add_parameter(low, name=low_param)
|
|
194
|
-
_, high_param = builder.add_parameter(high, name=high_param)
|
|
195
|
-
return exp.Not(
|
|
196
|
-
this=exp.Between(
|
|
197
|
-
this=column_exp, low=exp.Placeholder(this=low_param), high=exp.Placeholder(this=high_param)
|
|
198
|
-
)
|
|
199
|
-
)
|
|
200
|
-
msg = f"NOT BETWEEN operator requires a tuple of two values, got {type(value).__name__}"
|
|
201
|
-
raise SQLBuilderError(msg)
|
|
202
|
-
|
|
203
|
-
def _process_tuple_condition(self, condition: "tuple[Any, ...]") -> exp.Expression:
|
|
204
|
-
"""Process tuple-based WHERE conditions."""
|
|
205
|
-
if len(condition) == 2:
|
|
206
|
-
# (column, value) tuple for equality
|
|
207
|
-
column, value = condition
|
|
208
|
-
return self._create_parameterized_condition(
|
|
209
|
-
column, value, lambda col, placeholder: exp.EQ(this=col, expression=placeholder)
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
if len(condition) != 3:
|
|
213
|
-
msg = f"Condition tuple must have 2 or 3 elements, got {len(condition)}"
|
|
214
|
-
raise SQLBuilderError(msg)
|
|
215
|
-
|
|
216
|
-
# (column, operator, value) tuple
|
|
217
|
-
column_name_raw, operator, value = condition
|
|
218
|
-
operator = str(operator).upper()
|
|
219
|
-
column_exp = parse_column_expression(column_name_raw)
|
|
220
|
-
column_name = extract_column_name(column_name_raw)
|
|
221
|
-
|
|
222
|
-
# Simple operators that use direct parameterization
|
|
223
|
-
simple_operators = {
|
|
224
|
-
"=": lambda col, placeholder: exp.EQ(this=col, expression=placeholder),
|
|
225
|
-
"!=": lambda col, placeholder: exp.NEQ(this=col, expression=placeholder),
|
|
226
|
-
"<>": lambda col, placeholder: exp.NEQ(this=col, expression=placeholder),
|
|
227
|
-
">": lambda col, placeholder: exp.GT(this=col, expression=placeholder),
|
|
228
|
-
">=": lambda col, placeholder: exp.GTE(this=col, expression=placeholder),
|
|
229
|
-
"<": lambda col, placeholder: exp.LT(this=col, expression=placeholder),
|
|
230
|
-
"<=": lambda col, placeholder: exp.LTE(this=col, expression=placeholder),
|
|
231
|
-
"LIKE": lambda col, placeholder: exp.Like(this=col, expression=placeholder),
|
|
232
|
-
"NOT LIKE": lambda col, placeholder: exp.Not(this=exp.Like(this=col, expression=placeholder)),
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if operator in simple_operators:
|
|
236
|
-
return self._create_parameterized_condition(column_name_raw, value, simple_operators[operator])
|
|
237
|
-
|
|
238
|
-
# Complex operators that need special handling
|
|
239
|
-
if operator == "IN":
|
|
240
|
-
return self._handle_in_operator(column_exp, value, column_name)
|
|
241
|
-
if operator == "NOT IN":
|
|
242
|
-
return self._handle_not_in_operator(column_exp, value, column_name)
|
|
243
|
-
if operator == "IS":
|
|
244
|
-
return self._handle_is_operator(column_exp, value)
|
|
245
|
-
if operator == "IS NOT":
|
|
246
|
-
return self._handle_is_not_operator(column_exp, value)
|
|
247
|
-
if operator == "BETWEEN":
|
|
248
|
-
return self._handle_between_operator(column_exp, value, column_name)
|
|
249
|
-
if operator == "NOT BETWEEN":
|
|
250
|
-
return self._handle_not_between_operator(column_exp, value, column_name)
|
|
251
|
-
|
|
252
|
-
msg = f"Unsupported operator: {operator}"
|
|
253
|
-
raise SQLBuilderError(msg)
|
|
254
|
-
|
|
255
|
-
def where(
|
|
256
|
-
self,
|
|
257
|
-
condition: Union[
|
|
258
|
-
str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", "SQL"
|
|
259
|
-
],
|
|
260
|
-
*values: Any,
|
|
261
|
-
operator: Optional[str] = None,
|
|
262
|
-
**kwargs: Any,
|
|
263
|
-
) -> Self:
|
|
264
|
-
"""Add a WHERE clause to the statement.
|
|
265
|
-
|
|
266
|
-
Args:
|
|
267
|
-
condition: The condition for the WHERE clause. Can be:
|
|
268
|
-
- A string condition with or without parameter placeholders
|
|
269
|
-
- A string column name (when values are provided)
|
|
270
|
-
- A sqlglot Expression or Condition
|
|
271
|
-
- A 2-tuple (column, value) for equality comparison
|
|
272
|
-
- A 3-tuple (column, operator, value) for custom comparison
|
|
273
|
-
*values: Positional values for parameter binding (when condition contains placeholders or is a column name)
|
|
274
|
-
operator: Operator for comparison (when condition is a column name)
|
|
275
|
-
**kwargs: Named parameters for parameter binding (when condition contains named placeholders)
|
|
276
|
-
|
|
277
|
-
Raises:
|
|
278
|
-
SQLBuilderError: If the current expression is not a supported statement type.
|
|
279
|
-
|
|
280
|
-
Returns:
|
|
281
|
-
The current builder instance for method chaining.
|
|
282
|
-
"""
|
|
283
|
-
current_expr = self.get_expression()
|
|
284
|
-
if self.__class__.__name__ == "Update" and not isinstance(current_expr, exp.Update):
|
|
285
|
-
msg = "Cannot add WHERE clause to non-UPDATE expression"
|
|
286
|
-
raise SQLBuilderError(msg)
|
|
287
|
-
|
|
288
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
289
|
-
if current_expr is None:
|
|
290
|
-
msg = "Cannot add WHERE clause: expression is not initialized."
|
|
291
|
-
raise SQLBuilderError(msg)
|
|
292
|
-
|
|
293
|
-
if isinstance(current_expr, exp.Delete) and not current_expr.args.get("this"):
|
|
294
|
-
msg = "WHERE clause requires a table to be set. Use from() to set the table first."
|
|
295
|
-
raise SQLBuilderError(msg)
|
|
296
|
-
|
|
297
|
-
# Handle string conditions with external parameters
|
|
298
|
-
if values or kwargs:
|
|
299
|
-
if not isinstance(condition, str):
|
|
300
|
-
msg = "When values are provided, condition must be a string"
|
|
301
|
-
raise SQLBuilderError(msg)
|
|
302
|
-
|
|
303
|
-
# Check if condition contains parameter placeholders
|
|
304
|
-
validator = ParameterValidator()
|
|
305
|
-
param_info = validator.extract_parameters(condition)
|
|
306
|
-
|
|
307
|
-
if param_info:
|
|
308
|
-
# String condition with placeholders - create SQL object with parameters
|
|
309
|
-
# Create parameter mapping based on the detected parameter info
|
|
310
|
-
param_dict = dict(kwargs) # Start with named parameters
|
|
311
|
-
|
|
312
|
-
# Handle positional parameters - these are ordinal-based ($1, $2, :1, :2, ?)
|
|
313
|
-
positional_params = [
|
|
314
|
-
param
|
|
315
|
-
for param in param_info
|
|
316
|
-
if param.style in {ParameterStyle.NUMERIC, ParameterStyle.POSITIONAL_COLON, ParameterStyle.QMARK}
|
|
317
|
-
]
|
|
318
|
-
|
|
319
|
-
# Map positional values to positional parameters
|
|
320
|
-
if len(values) != len(positional_params):
|
|
321
|
-
msg = f"Parameter count mismatch: condition has {len(positional_params)} positional placeholders, got {len(values)} values"
|
|
322
|
-
raise SQLBuilderError(msg)
|
|
323
|
-
|
|
324
|
-
for i, value in enumerate(values):
|
|
325
|
-
param_dict[f"param_{i}"] = value
|
|
326
|
-
|
|
327
|
-
# Create SQL object with parameters that will be processed correctly
|
|
328
|
-
condition = SQL(condition, param_dict)
|
|
329
|
-
# Fall through to existing SQL object handling logic
|
|
330
|
-
|
|
331
|
-
elif len(values) == 1 and not kwargs:
|
|
332
|
-
# Single value - treat as column = value
|
|
333
|
-
if operator is not None:
|
|
334
|
-
where_expr = self._process_tuple_condition((condition, operator, values[0]))
|
|
335
|
-
else:
|
|
336
|
-
where_expr = self._process_tuple_condition((condition, values[0]))
|
|
337
|
-
# Process this condition and skip the rest
|
|
338
|
-
if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)):
|
|
339
|
-
updated_expr = current_expr.where(where_expr, copy=False)
|
|
340
|
-
self.set_expression(updated_expr)
|
|
341
|
-
else:
|
|
342
|
-
msg = f"WHERE clause not supported for {type(current_expr).__name__}"
|
|
343
|
-
raise SQLBuilderError(msg)
|
|
344
|
-
return self
|
|
345
|
-
else:
|
|
346
|
-
msg = f"Cannot bind parameters to condition without placeholders: {condition}"
|
|
347
|
-
raise SQLBuilderError(msg)
|
|
348
|
-
|
|
349
|
-
# Handle all condition types (including SQL objects created above)
|
|
350
|
-
if isinstance(condition, str):
|
|
351
|
-
where_expr = parse_condition_expression(condition)
|
|
352
|
-
elif isinstance(condition, (exp.Expression, exp.Condition)):
|
|
353
|
-
where_expr = condition
|
|
354
|
-
elif isinstance(condition, tuple):
|
|
355
|
-
where_expr = self._process_tuple_condition(condition)
|
|
356
|
-
elif has_query_builder_parameters(condition):
|
|
357
|
-
column_expr_obj = cast("ColumnExpression", condition)
|
|
358
|
-
where_expr = column_expr_obj._expression # pyright: ignore
|
|
359
|
-
elif has_sqlglot_expression(condition):
|
|
360
|
-
raw_expr = condition.sqlglot_expression # pyright: ignore[attr-defined]
|
|
361
|
-
if raw_expr is not None:
|
|
362
|
-
where_expr = builder._parameterize_expression(raw_expr)
|
|
363
|
-
else:
|
|
364
|
-
where_expr = parse_condition_expression(str(condition))
|
|
365
|
-
elif has_expression_and_sql(condition):
|
|
366
|
-
# Handle SQL objects (from sql.raw with parameters)
|
|
367
|
-
expression = getattr(condition, "expression", None)
|
|
368
|
-
if expression is not None and isinstance(expression, exp.Expression):
|
|
369
|
-
# Merge parameters from SQL object into builder
|
|
370
|
-
self._merge_sql_object_parameters(condition)
|
|
371
|
-
where_expr = expression
|
|
372
|
-
else:
|
|
373
|
-
# If expression is None, fall back to parsing the raw SQL
|
|
374
|
-
sql_text = getattr(condition, "sql", "")
|
|
375
|
-
# Merge parameters even when parsing raw SQL
|
|
376
|
-
self._merge_sql_object_parameters(condition)
|
|
377
|
-
where_expr = parse_condition_expression(sql_text)
|
|
378
|
-
else:
|
|
379
|
-
msg = f"Unsupported condition type: {type(condition).__name__}"
|
|
380
|
-
raise SQLBuilderError(msg)
|
|
381
|
-
|
|
382
|
-
if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)):
|
|
383
|
-
updated_expr = current_expr.where(where_expr, copy=False)
|
|
384
|
-
self.set_expression(updated_expr)
|
|
385
|
-
else:
|
|
386
|
-
msg = f"WHERE clause not supported for {type(current_expr).__name__}"
|
|
387
|
-
raise SQLBuilderError(msg)
|
|
388
|
-
return self
|
|
389
|
-
|
|
390
|
-
def where_eq(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
391
|
-
"""Add WHERE column = value clause."""
|
|
392
|
-
condition = self._create_parameterized_condition(column, value, lambda col, placeholder: col.eq(placeholder))
|
|
393
|
-
return self.where(condition)
|
|
394
|
-
|
|
395
|
-
def where_neq(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
396
|
-
"""Add WHERE column != value clause."""
|
|
397
|
-
condition = self._create_parameterized_condition(column, value, lambda col, placeholder: col.neq(placeholder))
|
|
398
|
-
return self.where(condition)
|
|
399
|
-
|
|
400
|
-
def where_lt(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
401
|
-
"""Add WHERE column < value clause."""
|
|
402
|
-
condition = self._create_parameterized_condition(
|
|
403
|
-
column, value, lambda col, placeholder: exp.LT(this=col, expression=placeholder)
|
|
404
|
-
)
|
|
405
|
-
return self.where(condition)
|
|
406
|
-
|
|
407
|
-
def where_lte(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
408
|
-
"""Add WHERE column <= value clause."""
|
|
409
|
-
condition = self._create_parameterized_condition(
|
|
410
|
-
column, value, lambda col, placeholder: exp.LTE(this=col, expression=placeholder)
|
|
411
|
-
)
|
|
412
|
-
return self.where(condition)
|
|
413
|
-
|
|
414
|
-
def where_gt(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
415
|
-
"""Add WHERE column > value clause."""
|
|
416
|
-
condition = self._create_parameterized_condition(
|
|
417
|
-
column, value, lambda col, placeholder: exp.GT(this=col, expression=placeholder)
|
|
418
|
-
)
|
|
419
|
-
return self.where(condition)
|
|
420
|
-
|
|
421
|
-
def where_gte(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
422
|
-
"""Add WHERE column >= value clause."""
|
|
423
|
-
condition = self._create_parameterized_condition(
|
|
424
|
-
column, value, lambda col, placeholder: exp.GTE(this=col, expression=placeholder)
|
|
425
|
-
)
|
|
426
|
-
return self.where(condition)
|
|
427
|
-
|
|
428
|
-
def where_between(self, column: Union[str, exp.Column], low: Any, high: Any) -> Self:
|
|
429
|
-
"""Add WHERE column BETWEEN low AND high clause."""
|
|
430
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
431
|
-
column_name = extract_column_name(column)
|
|
432
|
-
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
433
|
-
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
434
|
-
_, low_param = builder.add_parameter(low, name=low_param)
|
|
435
|
-
_, high_param = builder.add_parameter(high, name=high_param)
|
|
436
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
437
|
-
condition: exp.Expression = col_expr.between(exp.Placeholder(this=low_param), exp.Placeholder(this=high_param))
|
|
438
|
-
return self.where(condition)
|
|
439
|
-
|
|
440
|
-
def where_like(self, column: Union[str, exp.Column], pattern: str, escape: Optional[str] = None) -> Self:
|
|
441
|
-
"""Add WHERE column LIKE pattern clause."""
|
|
442
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
443
|
-
column_name = extract_column_name(column)
|
|
444
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
445
|
-
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
446
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
447
|
-
if escape is not None:
|
|
448
|
-
cond = exp.Like(this=col_expr, expression=exp.Placeholder(this=param_name), escape=exp.convert(str(escape)))
|
|
449
|
-
else:
|
|
450
|
-
cond = col_expr.like(exp.Placeholder(this=param_name))
|
|
451
|
-
condition: exp.Expression = cond
|
|
452
|
-
return self.where(condition)
|
|
453
|
-
|
|
454
|
-
def where_not_like(self, column: Union[str, exp.Column], pattern: str) -> Self:
|
|
455
|
-
"""Add WHERE column NOT LIKE pattern clause."""
|
|
456
|
-
condition = self._create_parameterized_condition(
|
|
457
|
-
column, pattern, lambda col, placeholder: col.like(placeholder).not_()
|
|
458
|
-
)
|
|
459
|
-
return self.where(condition)
|
|
460
|
-
|
|
461
|
-
def where_ilike(self, column: Union[str, exp.Column], pattern: str) -> Self:
|
|
462
|
-
"""Add WHERE column ILIKE pattern clause."""
|
|
463
|
-
condition = self._create_parameterized_condition(
|
|
464
|
-
column, pattern, lambda col, placeholder: col.ilike(placeholder)
|
|
465
|
-
)
|
|
466
|
-
return self.where(condition)
|
|
467
|
-
|
|
468
|
-
def where_is_null(self, column: Union[str, exp.Column]) -> Self:
|
|
469
|
-
"""Add WHERE column IS NULL clause."""
|
|
470
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
471
|
-
condition: exp.Expression = col_expr.is_(exp.null())
|
|
472
|
-
return self.where(condition)
|
|
473
|
-
|
|
474
|
-
def where_is_not_null(self, column: Union[str, exp.Column]) -> Self:
|
|
475
|
-
"""Add WHERE column IS NOT NULL clause."""
|
|
476
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
477
|
-
condition: exp.Expression = col_expr.is_(exp.null()).not_()
|
|
478
|
-
return self.where(condition)
|
|
479
|
-
|
|
480
|
-
def where_in(self, column: Union[str, exp.Column], values: Any) -> Self:
|
|
481
|
-
"""Add WHERE column IN (values) clause."""
|
|
482
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
483
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
484
|
-
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
485
|
-
subquery_exp: exp.Expression
|
|
486
|
-
if has_query_builder_parameters(values):
|
|
487
|
-
subquery = values.build() # pyright: ignore
|
|
488
|
-
sql_str = subquery.sql
|
|
489
|
-
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=builder.dialect_name)) # pyright: ignore
|
|
490
|
-
# Merge subquery parameters into parent builder with unique naming
|
|
491
|
-
if hasattr(subquery, "parameters") and isinstance(subquery.parameters, dict): # pyright: ignore[reportAttributeAccessIssue]
|
|
492
|
-
for param_name, param_value in subquery.parameters.items(): # pyright: ignore[reportAttributeAccessIssue]
|
|
493
|
-
unique_name = builder._generate_unique_parameter_name(param_name)
|
|
494
|
-
builder.add_parameter(param_value, name=unique_name)
|
|
495
|
-
else:
|
|
496
|
-
subquery_exp = values # type: ignore[assignment]
|
|
497
|
-
condition = col_expr.isin(subquery_exp)
|
|
498
|
-
return self.where(condition)
|
|
499
|
-
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
500
|
-
msg = "Unsupported type for 'values' in WHERE IN"
|
|
501
|
-
raise SQLBuilderError(msg)
|
|
502
|
-
column_name = extract_column_name(column)
|
|
503
|
-
parameters = []
|
|
504
|
-
for i, v in enumerate(values):
|
|
505
|
-
if len(values) == 1:
|
|
506
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
507
|
-
else:
|
|
508
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
509
|
-
_, param_name = builder.add_parameter(v, name=param_name)
|
|
510
|
-
parameters.append(exp.Placeholder(this=param_name))
|
|
511
|
-
condition = col_expr.isin(*parameters)
|
|
512
|
-
return self.where(condition)
|
|
513
|
-
|
|
514
|
-
def where_not_in(self, column: Union[str, exp.Column], values: Any) -> Self:
|
|
515
|
-
"""Add WHERE column NOT IN (values) clause."""
|
|
516
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
517
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
518
|
-
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
519
|
-
subquery_exp: exp.Expression
|
|
520
|
-
if has_query_builder_parameters(values):
|
|
521
|
-
subquery = values.build() # pyright: ignore
|
|
522
|
-
|
|
523
|
-
subquery_exp = exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
|
|
524
|
-
else:
|
|
525
|
-
subquery_exp = values # type: ignore[assignment]
|
|
526
|
-
condition = exp.Not(this=col_expr.isin(subquery_exp))
|
|
527
|
-
return self.where(condition)
|
|
528
|
-
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
529
|
-
msg = "Values for where_not_in must be a non-string iterable or subquery."
|
|
530
|
-
raise SQLBuilderError(msg)
|
|
531
|
-
column_name = extract_column_name(column)
|
|
532
|
-
parameters = []
|
|
533
|
-
for i, v in enumerate(values):
|
|
534
|
-
if len(values) == 1:
|
|
535
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
536
|
-
else:
|
|
537
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
538
|
-
_, param_name = builder.add_parameter(v, name=param_name)
|
|
539
|
-
parameters.append(exp.Placeholder(this=param_name))
|
|
540
|
-
condition = exp.Not(this=col_expr.isin(*parameters))
|
|
541
|
-
return self.where(condition)
|
|
542
|
-
|
|
543
|
-
def where_null(self, column: Union[str, exp.Column]) -> Self:
|
|
544
|
-
"""Add WHERE column IS NULL clause."""
|
|
545
|
-
return self.where_is_null(column)
|
|
546
|
-
|
|
547
|
-
def where_not_null(self, column: Union[str, exp.Column]) -> Self:
|
|
548
|
-
"""Add WHERE column IS NOT NULL clause."""
|
|
549
|
-
return self.where_is_not_null(column)
|
|
550
|
-
|
|
551
|
-
def where_exists(self, subquery: Union[str, Any]) -> Self:
|
|
552
|
-
"""Add WHERE EXISTS (subquery) clause."""
|
|
553
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
554
|
-
sub_expr: exp.Expression
|
|
555
|
-
if has_query_builder_parameters(subquery):
|
|
556
|
-
subquery_builder_parameters: dict[str, Any] = subquery.parameters
|
|
557
|
-
if subquery_builder_parameters:
|
|
558
|
-
for p_name, p_value in subquery_builder_parameters.items():
|
|
559
|
-
builder.add_parameter(p_value, name=p_name)
|
|
560
|
-
sub_sql_obj = subquery.build() # pyright: ignore
|
|
561
|
-
|
|
562
|
-
sub_expr = exp.maybe_parse(sub_sql_obj.sql, dialect=builder.dialect_name) # pyright: ignore
|
|
563
|
-
else:
|
|
564
|
-
sub_expr = exp.maybe_parse(str(subquery), dialect=builder.dialect_name)
|
|
565
|
-
|
|
566
|
-
if sub_expr is None:
|
|
567
|
-
msg = "Could not parse subquery for EXISTS"
|
|
568
|
-
raise SQLBuilderError(msg)
|
|
569
|
-
|
|
570
|
-
exists_expr = exp.Exists(this=sub_expr)
|
|
571
|
-
return self.where(exists_expr)
|
|
572
|
-
|
|
573
|
-
def where_not_exists(self, subquery: Union[str, Any]) -> Self:
|
|
574
|
-
"""Add WHERE NOT EXISTS (subquery) clause."""
|
|
575
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
576
|
-
sub_expr: exp.Expression
|
|
577
|
-
if has_query_builder_parameters(subquery):
|
|
578
|
-
subquery_builder_parameters: dict[str, Any] = subquery.parameters
|
|
579
|
-
if subquery_builder_parameters:
|
|
580
|
-
for p_name, p_value in subquery_builder_parameters.items():
|
|
581
|
-
builder.add_parameter(p_value, name=p_name)
|
|
582
|
-
sub_sql_obj = subquery.build() # pyright: ignore
|
|
583
|
-
sub_expr = exp.maybe_parse(sub_sql_obj.sql, dialect=builder.dialect_name) # pyright: ignore
|
|
584
|
-
else:
|
|
585
|
-
sub_expr = exp.maybe_parse(str(subquery), dialect=builder.dialect_name)
|
|
586
|
-
|
|
587
|
-
if sub_expr is None:
|
|
588
|
-
msg = "Could not parse subquery for NOT EXISTS"
|
|
589
|
-
raise SQLBuilderError(msg)
|
|
590
|
-
|
|
591
|
-
not_exists_expr = exp.Not(this=exp.Exists(this=sub_expr))
|
|
592
|
-
return self.where(not_exists_expr)
|
|
593
|
-
|
|
594
|
-
def where_any(self, column: Union[str, exp.Column], values: Any) -> Self:
|
|
595
|
-
"""Add WHERE column = ANY(values) clause."""
|
|
596
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
597
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
598
|
-
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
599
|
-
subquery_exp: exp.Expression
|
|
600
|
-
if has_query_builder_parameters(values):
|
|
601
|
-
subquery = values.build() # pyright: ignore
|
|
602
|
-
subquery_exp = exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
|
|
603
|
-
else:
|
|
604
|
-
subquery_exp = values # type: ignore[assignment]
|
|
605
|
-
condition = exp.EQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
606
|
-
return self.where(condition)
|
|
607
|
-
if isinstance(values, str):
|
|
608
|
-
try:
|
|
609
|
-
parsed_expr: Optional[exp.Expression] = exp.maybe_parse(values)
|
|
610
|
-
if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
|
|
611
|
-
subquery_exp = exp.paren(parsed_expr)
|
|
612
|
-
condition = exp.EQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
613
|
-
return self.where(condition)
|
|
614
|
-
except Exception: # noqa: S110
|
|
615
|
-
pass
|
|
616
|
-
msg = "Unsupported type for 'values' in WHERE ANY"
|
|
617
|
-
raise SQLBuilderError(msg)
|
|
618
|
-
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
619
|
-
msg = "Unsupported type for 'values' in WHERE ANY"
|
|
620
|
-
raise SQLBuilderError(msg)
|
|
621
|
-
column_name = extract_column_name(column)
|
|
622
|
-
parameters = []
|
|
623
|
-
for i, v in enumerate(values):
|
|
624
|
-
if len(values) == 1:
|
|
625
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
626
|
-
else:
|
|
627
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_any_{i + 1}")
|
|
628
|
-
_, param_name = builder.add_parameter(v, name=param_name)
|
|
629
|
-
parameters.append(exp.Placeholder(this=param_name))
|
|
630
|
-
tuple_expr = exp.Tuple(expressions=parameters)
|
|
631
|
-
condition = exp.EQ(this=col_expr, expression=exp.Any(this=tuple_expr))
|
|
632
|
-
return self.where(condition)
|
|
633
|
-
|
|
634
|
-
def where_not_any(self, column: Union[str, exp.Column], values: Any) -> Self:
|
|
635
|
-
"""Add WHERE column <> ANY(values) clause."""
|
|
636
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
637
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
638
|
-
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
639
|
-
subquery_exp: exp.Expression
|
|
640
|
-
if has_query_builder_parameters(values):
|
|
641
|
-
subquery = values.build() # pyright: ignore
|
|
642
|
-
subquery_exp = exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
|
|
643
|
-
else:
|
|
644
|
-
subquery_exp = values # type: ignore[assignment]
|
|
645
|
-
condition = exp.NEQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
646
|
-
return self.where(condition)
|
|
647
|
-
if isinstance(values, str):
|
|
648
|
-
try:
|
|
649
|
-
parsed_expr: Optional[exp.Expression] = exp.maybe_parse(values)
|
|
650
|
-
if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
|
|
651
|
-
subquery_exp = exp.paren(parsed_expr)
|
|
652
|
-
condition = exp.NEQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
653
|
-
return self.where(condition)
|
|
654
|
-
except Exception: # noqa: S110
|
|
655
|
-
pass
|
|
656
|
-
msg = "Unsupported type for 'values' in WHERE NOT ANY"
|
|
657
|
-
raise SQLBuilderError(msg)
|
|
658
|
-
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
659
|
-
msg = "Unsupported type for 'values' in WHERE NOT ANY"
|
|
660
|
-
raise SQLBuilderError(msg)
|
|
661
|
-
column_name = extract_column_name(column)
|
|
662
|
-
parameters = []
|
|
663
|
-
for i, v in enumerate(values):
|
|
664
|
-
if len(values) == 1:
|
|
665
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
666
|
-
else:
|
|
667
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_not_any_{i + 1}")
|
|
668
|
-
_, param_name = builder.add_parameter(v, name=param_name)
|
|
669
|
-
parameters.append(exp.Placeholder(this=param_name))
|
|
670
|
-
tuple_expr = exp.Tuple(expressions=parameters)
|
|
671
|
-
condition = exp.NEQ(this=col_expr, expression=exp.Any(this=tuple_expr))
|
|
672
|
-
return self.where(condition)
|
|
673
|
-
|
|
674
|
-
def or_where(
|
|
675
|
-
self,
|
|
676
|
-
condition: Union[
|
|
677
|
-
str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", "SQL"
|
|
678
|
-
],
|
|
679
|
-
*values: Any,
|
|
680
|
-
operator: Optional[str] = None,
|
|
681
|
-
**kwargs: Any,
|
|
682
|
-
) -> Self:
|
|
683
|
-
"""Add an OR condition to the existing WHERE clause.
|
|
684
|
-
|
|
685
|
-
Args:
|
|
686
|
-
condition: The condition for the OR WHERE clause. Can be:
|
|
687
|
-
- A string condition with or without parameter placeholders
|
|
688
|
-
- A string column name (when values are provided)
|
|
689
|
-
- A sqlglot Expression or Condition
|
|
690
|
-
- A 2-tuple (column, value) for equality comparison
|
|
691
|
-
- A 3-tuple (column, operator, value) for custom comparison
|
|
692
|
-
*values: Positional values for parameter binding (when condition contains placeholders or is a column name)
|
|
693
|
-
operator: Operator for comparison (when condition is a column name)
|
|
694
|
-
**kwargs: Named parameters for parameter binding (when condition contains named placeholders)
|
|
695
|
-
|
|
696
|
-
Raises:
|
|
697
|
-
SQLBuilderError: If the current expression is not a supported statement type or no existing WHERE clause.
|
|
698
|
-
|
|
699
|
-
Returns:
|
|
700
|
-
The current builder instance for method chaining.
|
|
701
|
-
"""
|
|
702
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
703
|
-
if builder._expression is None:
|
|
704
|
-
msg = "Cannot add OR WHERE clause: expression is not initialized."
|
|
705
|
-
raise SQLBuilderError(msg)
|
|
706
|
-
|
|
707
|
-
# Get the existing WHERE condition
|
|
708
|
-
existing_where = None
|
|
709
|
-
if isinstance(builder._expression, (exp.Select, exp.Update, exp.Delete)):
|
|
710
|
-
existing_where = builder._expression.find(exp.Where)
|
|
711
|
-
if existing_where:
|
|
712
|
-
existing_where = existing_where.this
|
|
713
|
-
|
|
714
|
-
if existing_where is None:
|
|
715
|
-
msg = "Cannot add OR WHERE clause: no existing WHERE clause found. Use where() first."
|
|
716
|
-
raise SQLBuilderError(msg)
|
|
717
|
-
|
|
718
|
-
# Process the new condition (reuse existing logic from where method)
|
|
719
|
-
new_condition = self._process_where_condition(condition, values, operator, kwargs)
|
|
720
|
-
|
|
721
|
-
# Combine with existing WHERE using OR
|
|
722
|
-
or_condition = exp.Or(this=existing_where, expression=new_condition)
|
|
723
|
-
|
|
724
|
-
# Update the WHERE clause by modifying the existing WHERE node
|
|
725
|
-
if isinstance(builder._expression, (exp.Select, exp.Update, exp.Delete)):
|
|
726
|
-
where_node = builder._expression.find(exp.Where)
|
|
727
|
-
if where_node:
|
|
728
|
-
where_node.set("this", or_condition)
|
|
729
|
-
else:
|
|
730
|
-
# This shouldn't happen since we checked for existing_where above
|
|
731
|
-
builder._expression = builder._expression.where(or_condition, copy=False)
|
|
732
|
-
else:
|
|
733
|
-
msg = f"OR WHERE clause not supported for {type(builder._expression).__name__}"
|
|
734
|
-
raise SQLBuilderError(msg)
|
|
735
|
-
|
|
736
|
-
return self
|
|
737
|
-
|
|
738
|
-
def where_or(self, *conditions: Union[str, "tuple[Any, ...]", exp.Expression]) -> Self:
|
|
739
|
-
"""Combine multiple conditions with OR logic.
|
|
740
|
-
|
|
741
|
-
Args:
|
|
742
|
-
*conditions: Multiple conditions to combine with OR. Each condition can be:
|
|
743
|
-
- A string condition
|
|
744
|
-
- A 2-tuple (column, value) for equality comparison
|
|
745
|
-
- A 3-tuple (column, operator, value) for custom comparison
|
|
746
|
-
- A sqlglot Expression or Condition
|
|
747
|
-
|
|
748
|
-
Raises:
|
|
749
|
-
SQLBuilderError: If no conditions provided or current expression not supported.
|
|
750
|
-
|
|
751
|
-
Returns:
|
|
752
|
-
The current builder instance for method chaining.
|
|
753
|
-
|
|
754
|
-
Examples:
|
|
755
|
-
query.where_or(
|
|
756
|
-
("name", "John"),
|
|
757
|
-
("email", "john@email.com"),
|
|
758
|
-
"age > 25"
|
|
759
|
-
)
|
|
760
|
-
# Produces: WHERE (name = :name OR email = :email OR age > 25)
|
|
761
|
-
"""
|
|
762
|
-
if not conditions:
|
|
763
|
-
msg = "where_or() requires at least one condition"
|
|
764
|
-
raise SQLBuilderError(msg)
|
|
765
|
-
|
|
766
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
767
|
-
if builder._expression is None:
|
|
768
|
-
msg = "Cannot add WHERE OR clause: expression is not initialized."
|
|
769
|
-
raise SQLBuilderError(msg)
|
|
770
|
-
|
|
771
|
-
# Process all conditions
|
|
772
|
-
processed_conditions = []
|
|
773
|
-
for condition in conditions:
|
|
774
|
-
processed_condition = self._process_where_condition(condition, (), None, {})
|
|
775
|
-
processed_conditions.append(processed_condition)
|
|
776
|
-
|
|
777
|
-
# Create OR expression from all conditions
|
|
778
|
-
or_condition = self._create_or_expression(processed_conditions)
|
|
779
|
-
|
|
780
|
-
# Apply the OR condition
|
|
781
|
-
if isinstance(builder._expression, (exp.Select, exp.Update, exp.Delete)):
|
|
782
|
-
builder._expression = builder._expression.where(or_condition, copy=False)
|
|
783
|
-
else:
|
|
784
|
-
msg = f"WHERE OR clause not supported for {type(builder._expression).__name__}"
|
|
785
|
-
raise SQLBuilderError(msg)
|
|
786
|
-
|
|
787
|
-
return self
|
|
788
|
-
|
|
789
|
-
def _process_where_condition(
|
|
790
|
-
self,
|
|
791
|
-
condition: Union[
|
|
792
|
-
str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", "SQL"
|
|
793
|
-
],
|
|
794
|
-
values: tuple[Any, ...],
|
|
795
|
-
operator: Optional[str],
|
|
796
|
-
kwargs: dict[str, Any],
|
|
797
|
-
) -> exp.Expression:
|
|
798
|
-
"""Process a WHERE condition into a sqlglot expression.
|
|
799
|
-
|
|
800
|
-
This is extracted from the where() method to be reusable by OR methods.
|
|
801
|
-
|
|
802
|
-
Args:
|
|
803
|
-
condition: The condition to process
|
|
804
|
-
values: Positional values for parameter binding
|
|
805
|
-
operator: Operator for comparison
|
|
806
|
-
kwargs: Named parameters for parameter binding
|
|
807
|
-
|
|
808
|
-
Returns:
|
|
809
|
-
Processed sqlglot expression
|
|
810
|
-
"""
|
|
811
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
812
|
-
|
|
813
|
-
# Handle string conditions with external parameters
|
|
814
|
-
if values or kwargs:
|
|
815
|
-
if not isinstance(condition, str):
|
|
816
|
-
msg = "When values are provided, condition must be a string"
|
|
817
|
-
raise SQLBuilderError(msg)
|
|
818
|
-
|
|
819
|
-
# Check if condition contains parameter placeholders
|
|
820
|
-
validator = ParameterValidator()
|
|
821
|
-
param_info = validator.extract_parameters(condition)
|
|
822
|
-
|
|
823
|
-
if param_info:
|
|
824
|
-
# String condition with placeholders - create SQL object with parameters
|
|
825
|
-
# Create parameter mapping based on the detected parameter info
|
|
826
|
-
param_dict = dict(kwargs) # Start with named parameters
|
|
827
|
-
|
|
828
|
-
# Handle positional parameters - these are ordinal-based ($1, $2, :1, :2, ?)
|
|
829
|
-
positional_params = [
|
|
830
|
-
param
|
|
831
|
-
for param in param_info
|
|
832
|
-
if param.style in {ParameterStyle.NUMERIC, ParameterStyle.POSITIONAL_COLON, ParameterStyle.QMARK}
|
|
833
|
-
]
|
|
834
|
-
|
|
835
|
-
# Map positional values to positional parameters
|
|
836
|
-
if len(values) != len(positional_params):
|
|
837
|
-
msg = f"Parameter count mismatch: condition has {len(positional_params)} positional placeholders, got {len(values)} values"
|
|
838
|
-
raise SQLBuilderError(msg)
|
|
839
|
-
|
|
840
|
-
for i, value in enumerate(values):
|
|
841
|
-
param_dict[f"param_{i}"] = value
|
|
842
|
-
|
|
843
|
-
# Create SQL object with parameters that will be processed correctly
|
|
844
|
-
condition = SQL(condition, param_dict)
|
|
845
|
-
# Fall through to existing SQL object handling logic
|
|
846
|
-
|
|
847
|
-
elif len(values) == 1 and not kwargs:
|
|
848
|
-
# Single value - treat as column = value
|
|
849
|
-
if operator is not None:
|
|
850
|
-
return self._process_tuple_condition((condition, operator, values[0]))
|
|
851
|
-
return self._process_tuple_condition((condition, values[0]))
|
|
852
|
-
else:
|
|
853
|
-
msg = f"Cannot bind parameters to condition without placeholders: {condition}"
|
|
854
|
-
raise SQLBuilderError(msg)
|
|
855
|
-
|
|
856
|
-
# Handle all condition types (including SQL objects created above)
|
|
857
|
-
if isinstance(condition, str):
|
|
858
|
-
return parse_condition_expression(condition)
|
|
859
|
-
if isinstance(condition, (exp.Expression, exp.Condition)):
|
|
860
|
-
return condition
|
|
861
|
-
if isinstance(condition, tuple):
|
|
862
|
-
return self._process_tuple_condition(condition)
|
|
863
|
-
if has_query_builder_parameters(condition):
|
|
864
|
-
column_expr_obj = cast("ColumnExpression", condition)
|
|
865
|
-
return column_expr_obj._expression # pyright: ignore
|
|
866
|
-
if has_sqlglot_expression(condition):
|
|
867
|
-
raw_expr = condition.sqlglot_expression # pyright: ignore[attr-defined]
|
|
868
|
-
if raw_expr is not None:
|
|
869
|
-
return builder._parameterize_expression(raw_expr)
|
|
870
|
-
return parse_condition_expression(str(condition))
|
|
871
|
-
if hasattr(condition, "expression") and hasattr(condition, "sql"):
|
|
872
|
-
# Handle SQL objects (from sql.raw with parameters)
|
|
873
|
-
expression = getattr(condition, "expression", None)
|
|
874
|
-
if expression is not None and isinstance(expression, exp.Expression):
|
|
875
|
-
# Merge parameters from SQL object into builder
|
|
876
|
-
if hasattr(condition, "parameters") and hasattr(builder, "add_parameter"):
|
|
877
|
-
sql_parameters = getattr(condition, "parameters", {})
|
|
878
|
-
for param_name, param_value in sql_parameters.items():
|
|
879
|
-
unique_name = builder._generate_unique_parameter_name(param_name)
|
|
880
|
-
builder.add_parameter(param_value, name=unique_name)
|
|
881
|
-
return cast("exp.Expression", expression)
|
|
882
|
-
# If expression is None, fall back to parsing the raw SQL
|
|
883
|
-
sql_text = getattr(condition, "sql", "")
|
|
884
|
-
# Merge parameters even when parsing raw SQL
|
|
885
|
-
if hasattr(condition, "parameters") and hasattr(builder, "add_parameter"):
|
|
886
|
-
sql_parameters = getattr(condition, "parameters", {})
|
|
887
|
-
for param_name, param_value in sql_parameters.items():
|
|
888
|
-
unique_name = builder._generate_unique_parameter_name(param_name)
|
|
889
|
-
builder.add_parameter(param_value, name=unique_name)
|
|
890
|
-
return parse_condition_expression(sql_text)
|
|
891
|
-
msg = f"Unsupported condition type: {type(condition).__name__}"
|
|
892
|
-
raise SQLBuilderError(msg)
|
|
893
|
-
|
|
894
|
-
def _create_or_expression(self, conditions: list[exp.Expression]) -> exp.Expression:
|
|
895
|
-
"""Create OR expression from multiple conditions.
|
|
896
|
-
|
|
897
|
-
Args:
|
|
898
|
-
conditions: List of sqlglot expressions to combine with OR
|
|
899
|
-
|
|
900
|
-
Returns:
|
|
901
|
-
Combined OR expression, or single condition if only one provided
|
|
902
|
-
"""
|
|
903
|
-
if len(conditions) == 1:
|
|
904
|
-
return conditions[0]
|
|
905
|
-
|
|
906
|
-
result = conditions[0]
|
|
907
|
-
for condition in conditions[1:]:
|
|
908
|
-
result = exp.Or(this=result, expression=condition)
|
|
909
|
-
return result
|
|
910
|
-
|
|
911
|
-
# OR helper methods for consistency with existing where_* methods
|
|
912
|
-
def or_where_eq(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
913
|
-
"""Add OR column = value clause."""
|
|
914
|
-
condition = self._create_parameterized_condition(column, value, lambda col, placeholder: col.eq(placeholder))
|
|
915
|
-
return self.or_where(condition)
|
|
916
|
-
|
|
917
|
-
def or_where_neq(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
918
|
-
"""Add OR column != value clause."""
|
|
919
|
-
condition = self._create_parameterized_condition(column, value, lambda col, placeholder: col.neq(placeholder))
|
|
920
|
-
return self.or_where(condition)
|
|
921
|
-
|
|
922
|
-
def or_where_in(self, column: Union[str, exp.Column], values: Any) -> Self:
|
|
923
|
-
"""Add OR column IN (values) clause."""
|
|
924
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
925
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
926
|
-
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
927
|
-
subquery_exp: exp.Expression
|
|
928
|
-
if has_query_builder_parameters(values):
|
|
929
|
-
subquery_builder_parameters: dict[str, Any] = values.parameters # pyright: ignore
|
|
930
|
-
param_mapping = {}
|
|
931
|
-
if subquery_builder_parameters:
|
|
932
|
-
for p_name, p_value in subquery_builder_parameters.items():
|
|
933
|
-
unique_name = builder._generate_unique_parameter_name(p_name)
|
|
934
|
-
param_mapping[p_name] = unique_name
|
|
935
|
-
builder.add_parameter(p_value, name=unique_name)
|
|
936
|
-
subquery = values.build() # pyright: ignore
|
|
937
|
-
subquery_parsed = exp.maybe_parse(subquery.sql, dialect=builder.dialect_name) # pyright: ignore
|
|
938
|
-
if param_mapping and subquery_parsed:
|
|
939
|
-
subquery_parsed = cast("Any", builder)._update_placeholders_in_expression(
|
|
940
|
-
subquery_parsed, param_mapping
|
|
941
|
-
)
|
|
942
|
-
subquery_exp = (
|
|
943
|
-
exp.paren(subquery_parsed)
|
|
944
|
-
if subquery_parsed
|
|
945
|
-
else exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
|
|
946
|
-
) # pyright: ignore
|
|
947
|
-
else:
|
|
948
|
-
subquery_exp = values # type: ignore[assignment]
|
|
949
|
-
condition = col_expr.isin(subquery_exp)
|
|
950
|
-
return self.or_where(condition)
|
|
951
|
-
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
952
|
-
msg = "Unsupported type for 'values' in OR WHERE IN"
|
|
953
|
-
raise SQLBuilderError(msg)
|
|
954
|
-
column_name = extract_column_name(column)
|
|
955
|
-
parameters = []
|
|
956
|
-
for i, v in enumerate(values):
|
|
957
|
-
if len(values) == 1:
|
|
958
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
959
|
-
else:
|
|
960
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
961
|
-
_, param_name = builder.add_parameter(v, name=param_name)
|
|
962
|
-
parameters.append(exp.Placeholder(this=param_name))
|
|
963
|
-
condition = col_expr.isin(*parameters)
|
|
964
|
-
return self.or_where(condition)
|
|
965
|
-
|
|
966
|
-
def or_where_like(self, column: Union[str, exp.Column], pattern: str, escape: Optional[str] = None) -> Self:
|
|
967
|
-
"""Add OR column LIKE pattern clause."""
|
|
968
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
969
|
-
column_name = extract_column_name(column)
|
|
970
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
971
|
-
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
972
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
973
|
-
if escape is not None:
|
|
974
|
-
cond = exp.Like(this=col_expr, expression=exp.Placeholder(this=param_name), escape=exp.convert(str(escape)))
|
|
975
|
-
else:
|
|
976
|
-
cond = col_expr.like(exp.Placeholder(this=param_name))
|
|
977
|
-
condition: exp.Expression = cond
|
|
978
|
-
return self.or_where(condition)
|
|
979
|
-
|
|
980
|
-
def or_where_is_null(self, column: Union[str, exp.Column]) -> Self:
|
|
981
|
-
"""Add OR column IS NULL clause."""
|
|
982
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
983
|
-
condition: exp.Expression = col_expr.is_(exp.null())
|
|
984
|
-
return self.or_where(condition)
|
|
985
|
-
|
|
986
|
-
def or_where_is_not_null(self, column: Union[str, exp.Column]) -> Self:
|
|
987
|
-
"""Add OR column IS NOT NULL clause."""
|
|
988
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
989
|
-
condition: exp.Expression = col_expr.is_(exp.null()).not_()
|
|
990
|
-
return self.or_where(condition)
|
|
991
|
-
|
|
992
|
-
def or_where_lt(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
993
|
-
"""Add OR column < value clause."""
|
|
994
|
-
condition = self._create_parameterized_condition(
|
|
995
|
-
column, value, lambda col, placeholder: exp.LT(this=col, expression=placeholder)
|
|
996
|
-
)
|
|
997
|
-
return self.or_where(condition)
|
|
998
|
-
|
|
999
|
-
def or_where_lte(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
1000
|
-
"""Add OR column <= value clause."""
|
|
1001
|
-
condition = self._create_parameterized_condition(
|
|
1002
|
-
column, value, lambda col, placeholder: exp.LTE(this=col, expression=placeholder)
|
|
1003
|
-
)
|
|
1004
|
-
return self.or_where(condition)
|
|
1005
|
-
|
|
1006
|
-
def or_where_gt(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
1007
|
-
"""Add OR column > value clause."""
|
|
1008
|
-
condition = self._create_parameterized_condition(
|
|
1009
|
-
column, value, lambda col, placeholder: exp.GT(this=col, expression=placeholder)
|
|
1010
|
-
)
|
|
1011
|
-
return self.or_where(condition)
|
|
1012
|
-
|
|
1013
|
-
def or_where_gte(self, column: Union[str, exp.Column], value: Any) -> Self:
|
|
1014
|
-
"""Add OR column >= value clause."""
|
|
1015
|
-
condition = self._create_parameterized_condition(
|
|
1016
|
-
column, value, lambda col, placeholder: exp.GTE(this=col, expression=placeholder)
|
|
1017
|
-
)
|
|
1018
|
-
return self.or_where(condition)
|
|
1019
|
-
|
|
1020
|
-
def or_where_between(self, column: Union[str, exp.Column], low: Any, high: Any) -> Self:
|
|
1021
|
-
"""Add OR column BETWEEN low AND high clause."""
|
|
1022
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
1023
|
-
column_name = extract_column_name(column)
|
|
1024
|
-
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
1025
|
-
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
1026
|
-
_, low_param = builder.add_parameter(low, name=low_param)
|
|
1027
|
-
_, high_param = builder.add_parameter(high, name=high_param)
|
|
1028
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1029
|
-
condition: exp.Expression = col_expr.between(exp.Placeholder(this=low_param), exp.Placeholder(this=high_param))
|
|
1030
|
-
return self.or_where(condition)
|
|
1031
|
-
|
|
1032
|
-
def or_where_not_like(self, column: Union[str, exp.Column], pattern: str) -> Self:
|
|
1033
|
-
"""Add OR column NOT LIKE pattern clause."""
|
|
1034
|
-
condition = self._create_parameterized_condition(
|
|
1035
|
-
column, pattern, lambda col, placeholder: col.like(placeholder).not_()
|
|
1036
|
-
)
|
|
1037
|
-
return self.or_where(condition)
|
|
1038
|
-
|
|
1039
|
-
def or_where_not_in(self, column: Union[str, exp.Column], values: Any) -> Self:
|
|
1040
|
-
"""Add OR column NOT IN (values) clause."""
|
|
1041
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
1042
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1043
|
-
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
1044
|
-
subquery_exp: exp.Expression
|
|
1045
|
-
if has_query_builder_parameters(values):
|
|
1046
|
-
subquery_builder_parameters: dict[str, Any] = values.parameters # pyright: ignore
|
|
1047
|
-
param_mapping = {}
|
|
1048
|
-
if subquery_builder_parameters:
|
|
1049
|
-
for p_name, p_value in subquery_builder_parameters.items():
|
|
1050
|
-
unique_name = builder._generate_unique_parameter_name(p_name)
|
|
1051
|
-
param_mapping[p_name] = unique_name
|
|
1052
|
-
builder.add_parameter(p_value, name=unique_name)
|
|
1053
|
-
subquery = values.build() # pyright: ignore
|
|
1054
|
-
subquery_parsed = exp.maybe_parse(subquery.sql, dialect=builder.dialect_name) # pyright: ignore
|
|
1055
|
-
if param_mapping and subquery_parsed:
|
|
1056
|
-
subquery_parsed = cast("Any", builder)._update_placeholders_in_expression(
|
|
1057
|
-
subquery_parsed, param_mapping
|
|
1058
|
-
)
|
|
1059
|
-
subquery_exp = (
|
|
1060
|
-
exp.paren(subquery_parsed)
|
|
1061
|
-
if subquery_parsed
|
|
1062
|
-
else exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
|
|
1063
|
-
) # pyright: ignore
|
|
1064
|
-
else:
|
|
1065
|
-
subquery_exp = values # type: ignore[assignment]
|
|
1066
|
-
condition = exp.Not(this=col_expr.isin(subquery_exp))
|
|
1067
|
-
return self.or_where(condition)
|
|
1068
|
-
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
1069
|
-
msg = "Values for or_where_not_in must be a non-string iterable or subquery."
|
|
1070
|
-
raise SQLBuilderError(msg)
|
|
1071
|
-
column_name = extract_column_name(column)
|
|
1072
|
-
parameters = []
|
|
1073
|
-
for i, v in enumerate(values):
|
|
1074
|
-
if len(values) == 1:
|
|
1075
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
1076
|
-
else:
|
|
1077
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
1078
|
-
_, param_name = builder.add_parameter(v, name=param_name)
|
|
1079
|
-
parameters.append(exp.Placeholder(this=param_name))
|
|
1080
|
-
condition = exp.Not(this=col_expr.isin(*parameters))
|
|
1081
|
-
return self.or_where(condition)
|
|
1082
|
-
|
|
1083
|
-
def or_where_ilike(self, column: Union[str, exp.Column], pattern: str) -> Self:
|
|
1084
|
-
"""Add OR column ILIKE pattern clause."""
|
|
1085
|
-
condition = self._create_parameterized_condition(
|
|
1086
|
-
column, pattern, lambda col, placeholder: col.ilike(placeholder)
|
|
1087
|
-
)
|
|
1088
|
-
return self.or_where(condition)
|
|
1089
|
-
|
|
1090
|
-
def or_where_null(self, column: Union[str, exp.Column]) -> Self:
|
|
1091
|
-
"""Add OR column IS NULL clause."""
|
|
1092
|
-
return self.or_where_is_null(column)
|
|
1093
|
-
|
|
1094
|
-
def or_where_not_null(self, column: Union[str, exp.Column]) -> Self:
|
|
1095
|
-
"""Add OR column IS NOT NULL clause."""
|
|
1096
|
-
return self.or_where_is_not_null(column)
|
|
1097
|
-
|
|
1098
|
-
def or_where_exists(self, subquery: Union[str, Any]) -> Self:
|
|
1099
|
-
"""Add OR EXISTS (subquery) clause."""
|
|
1100
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
1101
|
-
sub_expr: exp.Expression
|
|
1102
|
-
if has_query_builder_parameters(subquery):
|
|
1103
|
-
subquery_builder_parameters: dict[str, Any] = subquery.parameters
|
|
1104
|
-
param_mapping = {}
|
|
1105
|
-
if subquery_builder_parameters:
|
|
1106
|
-
for p_name, p_value in subquery_builder_parameters.items():
|
|
1107
|
-
unique_name = builder._generate_unique_parameter_name(p_name)
|
|
1108
|
-
param_mapping[p_name] = unique_name
|
|
1109
|
-
builder.add_parameter(p_value, name=unique_name)
|
|
1110
|
-
sub_sql_obj = subquery.build() # pyright: ignore
|
|
1111
|
-
sub_expr = exp.maybe_parse(sub_sql_obj.sql, dialect=builder.dialect_name) # pyright: ignore
|
|
1112
|
-
# Update placeholders to use unique parameter names
|
|
1113
|
-
if param_mapping and sub_expr:
|
|
1114
|
-
sub_expr = cast("Any", builder)._update_placeholders_in_expression(sub_expr, param_mapping)
|
|
1115
|
-
else:
|
|
1116
|
-
sub_expr = exp.maybe_parse(str(subquery), dialect=builder.dialect_name)
|
|
1117
|
-
|
|
1118
|
-
if sub_expr is None:
|
|
1119
|
-
msg = "Could not parse subquery for OR EXISTS"
|
|
1120
|
-
raise SQLBuilderError(msg)
|
|
1121
|
-
|
|
1122
|
-
exists_expr = exp.Exists(this=sub_expr)
|
|
1123
|
-
return self.or_where(exists_expr)
|
|
1124
|
-
|
|
1125
|
-
def or_where_not_exists(self, subquery: Union[str, Any]) -> Self:
|
|
1126
|
-
"""Add OR NOT EXISTS (subquery) clause."""
|
|
1127
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
1128
|
-
sub_expr: exp.Expression
|
|
1129
|
-
if has_query_builder_parameters(subquery):
|
|
1130
|
-
subquery_builder_parameters: dict[str, Any] = subquery.parameters
|
|
1131
|
-
param_mapping = {}
|
|
1132
|
-
if subquery_builder_parameters:
|
|
1133
|
-
for p_name, p_value in subquery_builder_parameters.items():
|
|
1134
|
-
unique_name = builder._generate_unique_parameter_name(p_name)
|
|
1135
|
-
param_mapping[p_name] = unique_name
|
|
1136
|
-
builder.add_parameter(p_value, name=unique_name)
|
|
1137
|
-
sub_sql_obj = subquery.build() # pyright: ignore
|
|
1138
|
-
sub_expr = exp.maybe_parse(sub_sql_obj.sql, dialect=builder.dialect_name) # pyright: ignore
|
|
1139
|
-
# Update placeholders to use unique parameter names
|
|
1140
|
-
if param_mapping and sub_expr:
|
|
1141
|
-
sub_expr = cast("Any", builder)._update_placeholders_in_expression(sub_expr, param_mapping)
|
|
1142
|
-
else:
|
|
1143
|
-
sub_expr = exp.maybe_parse(str(subquery), dialect=builder.dialect_name)
|
|
1144
|
-
|
|
1145
|
-
if sub_expr is None:
|
|
1146
|
-
msg = "Could not parse subquery for OR NOT EXISTS"
|
|
1147
|
-
raise SQLBuilderError(msg)
|
|
1148
|
-
|
|
1149
|
-
not_exists_expr = exp.Not(this=exp.Exists(this=sub_expr))
|
|
1150
|
-
return self.or_where(not_exists_expr)
|
|
1151
|
-
|
|
1152
|
-
def or_where_any(self, column: Union[str, exp.Column], values: Any) -> Self:
|
|
1153
|
-
"""Add OR column = ANY(values) clause."""
|
|
1154
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
1155
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1156
|
-
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
1157
|
-
subquery_exp: exp.Expression
|
|
1158
|
-
if has_query_builder_parameters(values):
|
|
1159
|
-
subquery_builder_parameters: dict[str, Any] = values.parameters # pyright: ignore
|
|
1160
|
-
param_mapping = {}
|
|
1161
|
-
if subquery_builder_parameters:
|
|
1162
|
-
for p_name, p_value in subquery_builder_parameters.items():
|
|
1163
|
-
unique_name = builder._generate_unique_parameter_name(p_name)
|
|
1164
|
-
param_mapping[p_name] = unique_name
|
|
1165
|
-
builder.add_parameter(p_value, name=unique_name)
|
|
1166
|
-
subquery = values.build() # pyright: ignore
|
|
1167
|
-
subquery_parsed = exp.maybe_parse(subquery.sql, dialect=builder.dialect_name) # pyright: ignore
|
|
1168
|
-
if param_mapping and subquery_parsed:
|
|
1169
|
-
subquery_parsed = cast("Any", builder)._update_placeholders_in_expression(
|
|
1170
|
-
subquery_parsed, param_mapping
|
|
1171
|
-
)
|
|
1172
|
-
subquery_exp = (
|
|
1173
|
-
exp.paren(subquery_parsed)
|
|
1174
|
-
if subquery_parsed
|
|
1175
|
-
else exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
|
|
1176
|
-
) # pyright: ignore
|
|
1177
|
-
else:
|
|
1178
|
-
subquery_exp = values # type: ignore[assignment]
|
|
1179
|
-
condition = exp.EQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
1180
|
-
return self.or_where(condition)
|
|
1181
|
-
if isinstance(values, str):
|
|
1182
|
-
try:
|
|
1183
|
-
parsed_expr: Optional[exp.Expression] = exp.maybe_parse(values)
|
|
1184
|
-
if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
|
|
1185
|
-
subquery_exp = exp.paren(parsed_expr)
|
|
1186
|
-
condition = exp.EQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
1187
|
-
return self.or_where(condition)
|
|
1188
|
-
except Exception: # noqa: S110
|
|
1189
|
-
pass
|
|
1190
|
-
msg = "Unsupported type for 'values' in OR WHERE ANY"
|
|
1191
|
-
raise SQLBuilderError(msg)
|
|
1192
|
-
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
1193
|
-
msg = "Unsupported type for 'values' in OR WHERE ANY"
|
|
1194
|
-
raise SQLBuilderError(msg)
|
|
1195
|
-
column_name = extract_column_name(column)
|
|
1196
|
-
parameters = []
|
|
1197
|
-
for i, v in enumerate(values):
|
|
1198
|
-
if len(values) == 1:
|
|
1199
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
1200
|
-
else:
|
|
1201
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_any_{i + 1}")
|
|
1202
|
-
_, param_name = builder.add_parameter(v, name=param_name)
|
|
1203
|
-
parameters.append(exp.Placeholder(this=param_name))
|
|
1204
|
-
tuple_expr = exp.Tuple(expressions=parameters)
|
|
1205
|
-
condition = exp.EQ(this=col_expr, expression=exp.Any(this=tuple_expr))
|
|
1206
|
-
return self.or_where(condition)
|
|
1207
|
-
|
|
1208
|
-
def or_where_not_any(self, column: Union[str, exp.Column], values: Any) -> Self:
|
|
1209
|
-
"""Add OR column <> ANY(values) clause."""
|
|
1210
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
1211
|
-
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1212
|
-
if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
|
|
1213
|
-
subquery_exp: exp.Expression
|
|
1214
|
-
if has_query_builder_parameters(values):
|
|
1215
|
-
subquery_builder_parameters: dict[str, Any] = values.parameters # pyright: ignore
|
|
1216
|
-
param_mapping = {}
|
|
1217
|
-
if subquery_builder_parameters:
|
|
1218
|
-
for p_name, p_value in subquery_builder_parameters.items():
|
|
1219
|
-
unique_name = builder._generate_unique_parameter_name(p_name)
|
|
1220
|
-
param_mapping[p_name] = unique_name
|
|
1221
|
-
builder.add_parameter(p_value, name=unique_name)
|
|
1222
|
-
subquery = values.build() # pyright: ignore
|
|
1223
|
-
subquery_parsed = exp.maybe_parse(subquery.sql, dialect=builder.dialect_name) # pyright: ignore
|
|
1224
|
-
if param_mapping and subquery_parsed:
|
|
1225
|
-
subquery_parsed = cast("Any", builder)._update_placeholders_in_expression(
|
|
1226
|
-
subquery_parsed, param_mapping
|
|
1227
|
-
)
|
|
1228
|
-
subquery_exp = (
|
|
1229
|
-
exp.paren(subquery_parsed)
|
|
1230
|
-
if subquery_parsed
|
|
1231
|
-
else exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
|
|
1232
|
-
) # pyright: ignore
|
|
1233
|
-
else:
|
|
1234
|
-
subquery_exp = values # type: ignore[assignment]
|
|
1235
|
-
condition = exp.NEQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
1236
|
-
return self.or_where(condition)
|
|
1237
|
-
if isinstance(values, str):
|
|
1238
|
-
try:
|
|
1239
|
-
parsed_expr: Optional[exp.Expression] = exp.maybe_parse(values)
|
|
1240
|
-
if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
|
|
1241
|
-
subquery_exp = exp.paren(parsed_expr)
|
|
1242
|
-
condition = exp.NEQ(this=col_expr, expression=exp.Any(this=subquery_exp))
|
|
1243
|
-
return self.or_where(condition)
|
|
1244
|
-
except Exception: # noqa: S110
|
|
1245
|
-
pass
|
|
1246
|
-
msg = "Unsupported type for 'values' in OR WHERE NOT ANY"
|
|
1247
|
-
raise SQLBuilderError(msg)
|
|
1248
|
-
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
1249
|
-
msg = "Unsupported type for 'values' in OR WHERE NOT ANY"
|
|
1250
|
-
raise SQLBuilderError(msg)
|
|
1251
|
-
column_name = extract_column_name(column)
|
|
1252
|
-
parameters = []
|
|
1253
|
-
for i, v in enumerate(values):
|
|
1254
|
-
if len(values) == 1:
|
|
1255
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
1256
|
-
else:
|
|
1257
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_not_any_{i + 1}")
|
|
1258
|
-
_, param_name = builder.add_parameter(v, name=param_name)
|
|
1259
|
-
parameters.append(exp.Placeholder(this=param_name))
|
|
1260
|
-
tuple_expr = exp.Tuple(expressions=parameters)
|
|
1261
|
-
condition = exp.NEQ(this=col_expr, expression=exp.Any(this=tuple_expr))
|
|
1262
|
-
return self.or_where(condition)
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
@trait
|
|
1266
|
-
class HavingClauseMixin:
|
|
1267
|
-
"""Mixin providing HAVING clause for SELECT builders."""
|
|
1268
|
-
|
|
1269
|
-
__slots__ = ()
|
|
1270
|
-
|
|
1271
|
-
# Type annotations for PyRight - these will be provided by the base class
|
|
1272
|
-
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
1273
|
-
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
1274
|
-
|
|
1275
|
-
def having(self, condition: Union[str, exp.Expression]) -> Self:
|
|
1276
|
-
"""Add HAVING clause.
|
|
1277
|
-
|
|
1278
|
-
Args:
|
|
1279
|
-
condition: The condition for the HAVING clause.
|
|
1280
|
-
|
|
1281
|
-
Raises:
|
|
1282
|
-
SQLBuilderError: If the current expression is not a SELECT statement.
|
|
1283
|
-
|
|
1284
|
-
Returns:
|
|
1285
|
-
The current builder instance for method chaining.
|
|
1286
|
-
"""
|
|
1287
|
-
current_expr = self.get_expression()
|
|
1288
|
-
if current_expr is None:
|
|
1289
|
-
self.set_expression(exp.Select())
|
|
1290
|
-
current_expr = self.get_expression()
|
|
1291
|
-
|
|
1292
|
-
if not isinstance(current_expr, exp.Select):
|
|
1293
|
-
msg = "Cannot add HAVING to a non-SELECT expression."
|
|
1294
|
-
raise SQLBuilderError(msg)
|
|
1295
|
-
having_expr = exp.condition(condition) if isinstance(condition, str) else condition
|
|
1296
|
-
updated_expr = current_expr.having(having_expr, copy=False)
|
|
1297
|
-
self.set_expression(updated_expr)
|
|
1298
|
-
return self
|