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
sqlspec/builder/_select.py
CHANGED
|
@@ -4,29 +4,1323 @@ Provides a fluent interface for building SQL SELECT queries with
|
|
|
4
4
|
parameter binding and validation.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
# pyright: reportPrivateUsage=false, reportPrivateImportUsage=false
|
|
8
|
+
|
|
7
9
|
import re
|
|
8
|
-
from typing import
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Final, Union, cast
|
|
9
11
|
|
|
12
|
+
from mypy_extensions import trait
|
|
10
13
|
from sqlglot import exp
|
|
11
14
|
from typing_extensions import Self
|
|
12
15
|
|
|
13
16
|
from sqlspec.builder._base import QueryBuilder, SafeQuery
|
|
14
|
-
from sqlspec.builder.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
UnpivotClauseMixin,
|
|
24
|
-
WhereClauseMixin,
|
|
17
|
+
from sqlspec.builder._join import JoinClauseMixin
|
|
18
|
+
from sqlspec.builder._parsing_utils import (
|
|
19
|
+
extract_column_name,
|
|
20
|
+
extract_expression,
|
|
21
|
+
parse_column_expression,
|
|
22
|
+
parse_condition_expression,
|
|
23
|
+
parse_order_expression,
|
|
24
|
+
parse_table_expression,
|
|
25
|
+
to_expression,
|
|
25
26
|
)
|
|
27
|
+
from sqlspec.core.parameters import ParameterStyle, ParameterValidator
|
|
26
28
|
from sqlspec.core.result import SQLResult
|
|
29
|
+
from sqlspec.core.statement import SQL
|
|
27
30
|
from sqlspec.exceptions import SQLBuilderError
|
|
31
|
+
from sqlspec.utils.type_guards import (
|
|
32
|
+
has_expression_and_parameters,
|
|
33
|
+
has_expression_and_sql,
|
|
34
|
+
has_query_builder_parameters,
|
|
35
|
+
has_sqlglot_expression,
|
|
36
|
+
is_expression,
|
|
37
|
+
is_iterable_parameters,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
BETWEEN_BOUND_COUNT = 2
|
|
41
|
+
PAIR_LENGTH = 2
|
|
42
|
+
TRIPLE_LENGTH = 3
|
|
43
|
+
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from collections.abc import Callable
|
|
46
|
+
|
|
47
|
+
from sqlspec.builder._column import Column, ColumnExpression, FunctionColumn
|
|
48
|
+
from sqlspec.builder._expression_wrappers import ExpressionWrapper
|
|
49
|
+
from sqlspec.protocols import SQLBuilderProtocol
|
|
50
|
+
|
|
51
|
+
__all__ = (
|
|
52
|
+
"Case",
|
|
53
|
+
"CaseBuilder",
|
|
54
|
+
"CommonTableExpressionMixin",
|
|
55
|
+
"HavingClauseMixin",
|
|
56
|
+
"LimitOffsetClauseMixin",
|
|
57
|
+
"OrderByClauseMixin",
|
|
58
|
+
"PivotClauseMixin",
|
|
59
|
+
"ReturningClauseMixin",
|
|
60
|
+
"Select",
|
|
61
|
+
"SelectClauseMixin",
|
|
62
|
+
"SetOperationMixin",
|
|
63
|
+
"SubqueryBuilder",
|
|
64
|
+
"UnpivotClauseMixin",
|
|
65
|
+
"WhereClauseMixin",
|
|
66
|
+
"WindowFunctionBuilder",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Case:
|
|
71
|
+
"""Represent a SQL CASE expression with structured components."""
|
|
72
|
+
|
|
73
|
+
__slots__ = ("conditions", "default")
|
|
74
|
+
|
|
75
|
+
def __init__(self, *ifs: exp.Expression, default: exp.Expression | None = None) -> None:
|
|
76
|
+
self.conditions = list(ifs)
|
|
77
|
+
self.default = default
|
|
78
|
+
|
|
79
|
+
def when(self, condition: str | exp.Expression, result: Any) -> "Case":
|
|
80
|
+
condition_expr = parse_condition_expression(condition)
|
|
81
|
+
result_expr = to_expression(result)
|
|
82
|
+
self.conditions.append(exp.If(this=condition_expr, true=result_expr))
|
|
83
|
+
return self
|
|
84
|
+
|
|
85
|
+
def else_(self, value: Any) -> "Case":
|
|
86
|
+
self.default = to_expression(value)
|
|
87
|
+
return self
|
|
88
|
+
|
|
89
|
+
def end(self) -> "Case":
|
|
90
|
+
return self
|
|
91
|
+
|
|
92
|
+
def as_(self, alias: str) -> exp.Alias:
|
|
93
|
+
return cast("exp.Alias", exp.alias_(self.expression, alias))
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def expression(self) -> exp.Case:
|
|
97
|
+
return exp.Case(ifs=self.conditions, default=self.default)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class CaseBuilder:
|
|
101
|
+
"""Fluent builder for CASE expressions used within SELECT clauses."""
|
|
102
|
+
|
|
103
|
+
__slots__ = ()
|
|
104
|
+
|
|
105
|
+
def __call__(self, *args: Any, default: Any | None = None) -> Case:
|
|
106
|
+
conditions = [to_expression(arg) for arg in args]
|
|
107
|
+
default_expr = to_expression(default) if default is not None else None
|
|
108
|
+
return Case(*conditions, default=default_expr)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class SubqueryBuilder:
|
|
112
|
+
"""Helper to build subquery expressions for EXISTS/IN/ANY/ALL operations."""
|
|
113
|
+
|
|
114
|
+
__slots__ = ("_operation",)
|
|
115
|
+
|
|
116
|
+
def __init__(self, operation: str) -> None:
|
|
117
|
+
self._operation = operation
|
|
118
|
+
|
|
119
|
+
def __call__(self, subquery: Any) -> exp.Expression:
|
|
120
|
+
if isinstance(subquery, exp.Expression):
|
|
121
|
+
subquery_expr = subquery
|
|
122
|
+
elif hasattr(subquery, "build") and callable(getattr(subquery, "build", None)):
|
|
123
|
+
built_query = subquery.build()
|
|
124
|
+
sql_text = built_query.sql if hasattr(built_query, "sql") else str(built_query)
|
|
125
|
+
parsed_expr: exp.Expression | None = exp.maybe_parse(sql_text)
|
|
126
|
+
if parsed_expr is None:
|
|
127
|
+
msg = f"Could not parse subquery SQL: {sql_text}"
|
|
128
|
+
raise SQLBuilderError(msg)
|
|
129
|
+
subquery_expr = parsed_expr
|
|
130
|
+
else:
|
|
131
|
+
parsed_expr = exp.maybe_parse(str(subquery))
|
|
132
|
+
if parsed_expr is None:
|
|
133
|
+
msg = f"Could not convert subquery to expression: {subquery}"
|
|
134
|
+
raise SQLBuilderError(msg)
|
|
135
|
+
subquery_expr = parsed_expr
|
|
136
|
+
|
|
137
|
+
if self._operation == "exists":
|
|
138
|
+
return exp.Exists(this=subquery_expr)
|
|
139
|
+
if self._operation == "in":
|
|
140
|
+
return exp.In(expressions=[subquery_expr])
|
|
141
|
+
if self._operation == "any":
|
|
142
|
+
return exp.Any(this=subquery_expr)
|
|
143
|
+
if self._operation == "all":
|
|
144
|
+
return exp.All(this=subquery_expr)
|
|
145
|
+
msg = f"Unknown subquery operation: {self._operation}"
|
|
146
|
+
raise SQLBuilderError(msg)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class WindowFunctionBuilder:
|
|
150
|
+
"""Helper to fluently construct window function expressions."""
|
|
151
|
+
|
|
152
|
+
__slots__ = ("_function_args", "_function_name", "_order_by", "_partition_by")
|
|
153
|
+
|
|
154
|
+
def __init__(self, function_name: str, *function_args: Any) -> None:
|
|
155
|
+
self._function_name = function_name
|
|
156
|
+
self._function_args: list[exp.Expression] = [to_expression(arg) for arg in function_args]
|
|
157
|
+
self._partition_by: list[exp.Expression] = []
|
|
158
|
+
self._order_by: list[exp.Ordered] = []
|
|
159
|
+
|
|
160
|
+
def __call__(self, *function_args: Any) -> "WindowFunctionBuilder":
|
|
161
|
+
self._function_args = [to_expression(arg) for arg in function_args]
|
|
162
|
+
return self
|
|
163
|
+
|
|
164
|
+
def partition_by(self, *columns: str | exp.Expression) -> "WindowFunctionBuilder":
|
|
165
|
+
self._partition_by = [exp.column(column) if isinstance(column, str) else column for column in columns]
|
|
166
|
+
return self
|
|
167
|
+
|
|
168
|
+
def order_by(self, *columns: str | exp.Expression) -> "WindowFunctionBuilder":
|
|
169
|
+
ordered_columns: list[exp.Ordered] = []
|
|
170
|
+
for column in columns:
|
|
171
|
+
if isinstance(column, str):
|
|
172
|
+
ordered_columns.append(exp.column(column).asc())
|
|
173
|
+
elif isinstance(column, exp.Ordered):
|
|
174
|
+
ordered_columns.append(column)
|
|
175
|
+
else:
|
|
176
|
+
ordered_columns.append(exp.Ordered(this=column, desc=False))
|
|
177
|
+
self._order_by = ordered_columns
|
|
178
|
+
return self
|
|
179
|
+
|
|
180
|
+
def _build_function_expression(self) -> exp.Expression:
|
|
181
|
+
expressions = self._function_args or []
|
|
182
|
+
return exp.Anonymous(this=self._function_name, expressions=expressions)
|
|
183
|
+
|
|
184
|
+
def build(self) -> exp.Window:
|
|
185
|
+
over_args: dict[str, Any] = {}
|
|
186
|
+
if self._partition_by:
|
|
187
|
+
over_args["partition_by"] = self._partition_by
|
|
188
|
+
if self._order_by:
|
|
189
|
+
over_args["order"] = exp.Order(expressions=self._order_by)
|
|
190
|
+
return exp.Window(this=self._build_function_expression(), **over_args)
|
|
191
|
+
|
|
192
|
+
def as_(self, alias: str) -> exp.Alias:
|
|
193
|
+
return cast("exp.Alias", exp.alias_(self.build(), alias))
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _ensure_select_expression(
|
|
197
|
+
mixin: "SQLBuilderProtocol", *, error_message: str, initialize: bool = True
|
|
198
|
+
) -> exp.Select:
|
|
199
|
+
expression = mixin.get_expression()
|
|
200
|
+
if expression is None and initialize:
|
|
201
|
+
mixin.set_expression(exp.Select())
|
|
202
|
+
expression = mixin.get_expression()
|
|
203
|
+
|
|
204
|
+
if not isinstance(expression, exp.Select):
|
|
205
|
+
raise SQLBuilderError(error_message)
|
|
206
|
+
|
|
207
|
+
return expression
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@trait
|
|
211
|
+
class SelectClauseMixin:
|
|
212
|
+
"""Mixin providing SELECT clause methods."""
|
|
213
|
+
|
|
214
|
+
__slots__ = ()
|
|
215
|
+
|
|
216
|
+
def get_expression(self) -> exp.Expression | None: ...
|
|
217
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
218
|
+
|
|
219
|
+
def select(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn", SQL, Case]) -> Self:
|
|
220
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
221
|
+
select_expr = _ensure_select_expression(builder, error_message="Cannot add columns to non-SELECT expression.")
|
|
222
|
+
for column in columns:
|
|
223
|
+
column_expr = column.expression if isinstance(column, Case) else parse_column_expression(column, builder)
|
|
224
|
+
select_expr = select_expr.select(column_expr, copy=False)
|
|
225
|
+
self.set_expression(select_expr)
|
|
226
|
+
return cast("Self", builder)
|
|
227
|
+
|
|
228
|
+
def distinct(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn", SQL]) -> Self:
|
|
229
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
230
|
+
select_expr = _ensure_select_expression(builder, error_message="Cannot add DISTINCT to non-SELECT expression.")
|
|
231
|
+
if not columns:
|
|
232
|
+
select_expr.set("distinct", exp.Distinct())
|
|
233
|
+
else:
|
|
234
|
+
distinct_columns = [parse_column_expression(column, builder) for column in columns]
|
|
235
|
+
select_expr.set("distinct", exp.Distinct(expressions=distinct_columns))
|
|
236
|
+
builder.set_expression(select_expr)
|
|
237
|
+
return cast("Self", builder)
|
|
238
|
+
|
|
239
|
+
def from_(self, table: str | exp.Expression | Any, alias: str | None = None) -> Self:
|
|
240
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
241
|
+
select_expr = _ensure_select_expression(builder, error_message="FROM clause only valid for SELECT.")
|
|
242
|
+
if isinstance(table, str):
|
|
243
|
+
from_expr = parse_table_expression(table, alias)
|
|
244
|
+
elif is_expression(table):
|
|
245
|
+
from_expr = exp.alias_(table, alias) if alias else table
|
|
246
|
+
elif has_query_builder_parameters(table):
|
|
247
|
+
subquery = table.build()
|
|
248
|
+
sql_text = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
249
|
+
subquery_exp = exp.paren(exp.maybe_parse(sql_text, dialect=getattr(builder, "dialect", None)))
|
|
250
|
+
from_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
251
|
+
else:
|
|
252
|
+
from_expr = table
|
|
253
|
+
builder.set_expression(select_expr.from_(from_expr, copy=False))
|
|
254
|
+
return cast("Self", builder)
|
|
255
|
+
|
|
256
|
+
def group_by(self, *columns: str | exp.Expression) -> Self:
|
|
257
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
258
|
+
select_expr = builder.get_expression()
|
|
259
|
+
if select_expr is None or not isinstance(select_expr, exp.Select):
|
|
260
|
+
return cast("Self", builder)
|
|
261
|
+
|
|
262
|
+
for column in columns:
|
|
263
|
+
column_expr = exp.column(column) if isinstance(column, str) else column
|
|
264
|
+
select_expr = select_expr.group_by(column_expr, copy=False)
|
|
265
|
+
builder.set_expression(select_expr)
|
|
266
|
+
return cast("Self", builder)
|
|
267
|
+
|
|
268
|
+
def group_by_rollup(self, *columns: str | exp.Expression) -> Self:
|
|
269
|
+
column_exprs = [exp.column(column) if isinstance(column, str) else column for column in columns]
|
|
270
|
+
rollup_expr = exp.Rollup(expressions=column_exprs)
|
|
271
|
+
return self.group_by(rollup_expr)
|
|
272
|
+
|
|
273
|
+
def group_by_cube(self, *columns: str | exp.Expression) -> Self:
|
|
274
|
+
column_exprs = [exp.column(column) if isinstance(column, str) else column for column in columns]
|
|
275
|
+
cube_expr = exp.Cube(expressions=column_exprs)
|
|
276
|
+
return self.group_by(cube_expr)
|
|
277
|
+
|
|
278
|
+
def group_by_grouping_sets(self, *column_sets: tuple[str, ...] | list[str]) -> Self:
|
|
279
|
+
grouping_sets = [
|
|
280
|
+
exp.Tuple(expressions=[exp.column(col) if isinstance(col, str) else col for col in column_set])
|
|
281
|
+
for column_set in column_sets
|
|
282
|
+
]
|
|
283
|
+
grouping_expr = exp.GroupingSets(expressions=grouping_sets)
|
|
284
|
+
return self.group_by(grouping_expr)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@trait
|
|
288
|
+
class OrderByClauseMixin:
|
|
289
|
+
__slots__ = ()
|
|
290
|
+
|
|
291
|
+
_expression: exp.Expression | None
|
|
292
|
+
|
|
293
|
+
def order_by(self, *items: Union[str, exp.Ordered, "Column"], desc: bool = False) -> Self:
|
|
294
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
295
|
+
select_expr = _ensure_select_expression(builder, error_message="ORDER BY only valid for SELECT.")
|
|
296
|
+
|
|
297
|
+
current_expr = select_expr
|
|
298
|
+
for item in items:
|
|
299
|
+
if isinstance(item, str):
|
|
300
|
+
order_item = parse_order_expression(item)
|
|
301
|
+
if desc:
|
|
302
|
+
order_item = order_item.desc()
|
|
303
|
+
else:
|
|
304
|
+
extracted_item = extract_expression(item)
|
|
305
|
+
order_item = extracted_item.desc() if desc and not isinstance(item, exp.Ordered) else extracted_item
|
|
306
|
+
current_expr = current_expr.order_by(order_item, copy=False)
|
|
307
|
+
builder.set_expression(current_expr)
|
|
308
|
+
return cast("Self", builder)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@trait
|
|
312
|
+
class LimitOffsetClauseMixin:
|
|
313
|
+
__slots__ = ()
|
|
314
|
+
|
|
315
|
+
_expression: exp.Expression | None
|
|
316
|
+
|
|
317
|
+
def limit(self, value: int) -> Self:
|
|
318
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
319
|
+
select_expr = _ensure_select_expression(builder, error_message="LIMIT only valid for SELECT.")
|
|
320
|
+
builder.set_expression(select_expr.limit(exp.convert(value), copy=False))
|
|
321
|
+
return cast("Self", builder)
|
|
322
|
+
|
|
323
|
+
def offset(self, value: int) -> Self:
|
|
324
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
325
|
+
select_expr = _ensure_select_expression(builder, error_message="OFFSET only valid for SELECT.")
|
|
326
|
+
builder.set_expression(select_expr.offset(exp.convert(value), copy=False))
|
|
327
|
+
return cast("Self", builder)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@trait
|
|
331
|
+
class ReturningClauseMixin:
|
|
332
|
+
__slots__ = ()
|
|
333
|
+
|
|
334
|
+
_expression: exp.Expression | None
|
|
335
|
+
|
|
336
|
+
def returning(self, *columns: Union[str, exp.Expression, "Column", "ExpressionWrapper", Case]) -> Self:
|
|
337
|
+
if self._expression is None:
|
|
338
|
+
msg = "Cannot add RETURNING: expression not initialized."
|
|
339
|
+
raise SQLBuilderError(msg)
|
|
340
|
+
if not isinstance(self._expression, (exp.Insert, exp.Update, exp.Delete)):
|
|
341
|
+
msg = "RETURNING only supported for INSERT, UPDATE, DELETE statements."
|
|
342
|
+
raise SQLBuilderError(msg)
|
|
343
|
+
returning_exprs = [extract_expression(col) for col in columns]
|
|
344
|
+
self._expression.set("returning", exp.Returning(expressions=returning_exprs))
|
|
345
|
+
return self
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@trait
|
|
349
|
+
class WhereClauseMixin:
|
|
350
|
+
__slots__ = ()
|
|
351
|
+
|
|
352
|
+
def get_expression(self) -> exp.Expression | None: ...
|
|
353
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
354
|
+
|
|
355
|
+
def _create_parameterized_condition(
|
|
356
|
+
self,
|
|
357
|
+
column: str | exp.Column,
|
|
358
|
+
value: Any,
|
|
359
|
+
condition_factory: "Callable[[exp.Expression, exp.Placeholder], exp.Expression]",
|
|
360
|
+
) -> exp.Expression:
|
|
361
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
362
|
+
column_name = extract_column_name(column)
|
|
363
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
364
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
365
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
366
|
+
placeholder = exp.Placeholder(this=param_name)
|
|
367
|
+
return condition_factory(col_expr, placeholder)
|
|
368
|
+
|
|
369
|
+
def _merge_sql_object_parameters(self, sql_obj: Any) -> None:
|
|
370
|
+
if not has_expression_and_parameters(sql_obj):
|
|
371
|
+
return
|
|
372
|
+
|
|
373
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
374
|
+
sql_parameters = getattr(sql_obj, "parameters", {})
|
|
375
|
+
for param_name, param_value in sql_parameters.items():
|
|
376
|
+
unique_name = builder._generate_unique_parameter_name(param_name)
|
|
377
|
+
builder.add_parameter(param_value, name=unique_name)
|
|
378
|
+
|
|
379
|
+
def _get_existing_where_clause(self) -> exp.Where | None:
|
|
380
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
381
|
+
expression = builder.get_expression()
|
|
382
|
+
if isinstance(expression, (exp.Select, exp.Update, exp.Delete)):
|
|
383
|
+
where_clause = expression.args.get("where")
|
|
384
|
+
if isinstance(where_clause, exp.Where):
|
|
385
|
+
return where_clause
|
|
386
|
+
return None
|
|
387
|
+
|
|
388
|
+
def _combine_with_or(self, new_condition: exp.Expression) -> Self:
|
|
389
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
390
|
+
expression = builder.get_expression()
|
|
391
|
+
if expression is None or not isinstance(expression, (exp.Select, exp.Update, exp.Delete)):
|
|
392
|
+
msg = "OR WHERE clause not supported for current expression. Use where() first."
|
|
393
|
+
raise SQLBuilderError(msg)
|
|
394
|
+
|
|
395
|
+
where_clause = self._get_existing_where_clause()
|
|
396
|
+
if where_clause is None or where_clause.this is None:
|
|
397
|
+
msg = "Cannot add OR WHERE clause: no existing WHERE clause found. Use where() before or_where()."
|
|
398
|
+
raise SQLBuilderError(msg)
|
|
399
|
+
|
|
400
|
+
combined_condition = exp.Or(this=where_clause.this, expression=new_condition)
|
|
401
|
+
where_clause.set("this", combined_condition)
|
|
402
|
+
builder.set_expression(expression)
|
|
403
|
+
return cast("Self", builder)
|
|
404
|
+
|
|
405
|
+
def _handle_in_operator(
|
|
406
|
+
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
407
|
+
) -> exp.Expression:
|
|
408
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
409
|
+
if has_query_builder_parameters(value) or isinstance(value, exp.Expression):
|
|
410
|
+
subquery_expr = self._normalize_subquery_expression(value, builder)
|
|
411
|
+
return exp.In(this=column_exp, expressions=[subquery_expr])
|
|
412
|
+
if is_iterable_parameters(value):
|
|
413
|
+
placeholders = []
|
|
414
|
+
for index, element in enumerate(value):
|
|
415
|
+
name_seed = column_name if len(value) == 1 else f"{column_name}_{index + 1}"
|
|
416
|
+
param_name = builder._generate_unique_parameter_name(name_seed)
|
|
417
|
+
_, param_name = builder.add_parameter(element, name=param_name)
|
|
418
|
+
placeholders.append(exp.Placeholder(this=param_name))
|
|
419
|
+
return exp.In(this=column_exp, expressions=placeholders)
|
|
420
|
+
|
|
421
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
422
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
423
|
+
return exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)])
|
|
424
|
+
|
|
425
|
+
def _handle_not_in_operator(
|
|
426
|
+
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
427
|
+
) -> exp.Expression:
|
|
428
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
429
|
+
if has_query_builder_parameters(value) or isinstance(value, exp.Expression):
|
|
430
|
+
subquery_expr = self._normalize_subquery_expression(value, builder)
|
|
431
|
+
return exp.Not(this=exp.In(this=column_exp, expressions=[subquery_expr]))
|
|
432
|
+
if is_iterable_parameters(value):
|
|
433
|
+
placeholders = []
|
|
434
|
+
for index, element in enumerate(value):
|
|
435
|
+
name_seed = column_name if len(value) == 1 else f"{column_name}_{index + 1}"
|
|
436
|
+
param_name = builder._generate_unique_parameter_name(name_seed)
|
|
437
|
+
_, param_name = builder.add_parameter(element, name=param_name)
|
|
438
|
+
placeholders.append(exp.Placeholder(this=param_name))
|
|
439
|
+
return exp.Not(this=exp.In(this=column_exp, expressions=placeholders))
|
|
440
|
+
|
|
441
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
442
|
+
_, param_name = builder.add_parameter(value, name=param_name)
|
|
443
|
+
return exp.Not(this=exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)]))
|
|
444
|
+
|
|
445
|
+
def _handle_is_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
|
|
446
|
+
value_expr = exp.Null() if value is None else exp.convert(value)
|
|
447
|
+
return exp.Is(this=column_exp, expression=value_expr)
|
|
448
|
+
|
|
449
|
+
def _handle_is_not_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
|
|
450
|
+
value_expr = exp.Null() if value is None else exp.convert(value)
|
|
451
|
+
return exp.Not(this=exp.Is(this=column_exp, expression=value_expr))
|
|
452
|
+
|
|
453
|
+
def _handle_between_operator(
|
|
454
|
+
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
455
|
+
) -> exp.Expression:
|
|
456
|
+
if is_iterable_parameters(value) and len(value) == BETWEEN_BOUND_COUNT:
|
|
457
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
458
|
+
low, high = value
|
|
459
|
+
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
460
|
+
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
461
|
+
_, low_param = builder.add_parameter(low, name=low_param)
|
|
462
|
+
_, high_param = builder.add_parameter(high, name=high_param)
|
|
463
|
+
return exp.Between(
|
|
464
|
+
this=column_exp, low=exp.Placeholder(this=low_param), high=exp.Placeholder(this=high_param)
|
|
465
|
+
)
|
|
466
|
+
msg = f"BETWEEN operator requires a tuple of two values, got {type(value).__name__}"
|
|
467
|
+
raise SQLBuilderError(msg)
|
|
468
|
+
|
|
469
|
+
def _handle_not_between_operator(
|
|
470
|
+
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
471
|
+
) -> exp.Expression:
|
|
472
|
+
if is_iterable_parameters(value) and len(value) == BETWEEN_BOUND_COUNT:
|
|
473
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
474
|
+
low, high = value
|
|
475
|
+
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
476
|
+
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
477
|
+
_, low_param = builder.add_parameter(low, name=low_param)
|
|
478
|
+
_, high_param = builder.add_parameter(high, name=high_param)
|
|
479
|
+
return exp.Not(
|
|
480
|
+
this=exp.Between(
|
|
481
|
+
this=column_exp, low=exp.Placeholder(this=low_param), high=exp.Placeholder(this=high_param)
|
|
482
|
+
)
|
|
483
|
+
)
|
|
484
|
+
msg = f"NOT BETWEEN operator requires a tuple of two values, got {type(value).__name__}"
|
|
485
|
+
raise SQLBuilderError(msg)
|
|
486
|
+
|
|
487
|
+
def _create_any_condition(self, column_expr: exp.Expression, values: Any, column_name: str) -> exp.Expression:
|
|
488
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
489
|
+
if has_query_builder_parameters(values):
|
|
490
|
+
subquery_expr = self._normalize_subquery_expression(values, builder)
|
|
491
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=subquery_expr))
|
|
492
|
+
if isinstance(values, exp.Expression):
|
|
493
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=values))
|
|
494
|
+
if has_sqlglot_expression(values):
|
|
495
|
+
raw_expr = getattr(values, "sqlglot_expression", None)
|
|
496
|
+
if isinstance(raw_expr, exp.Expression):
|
|
497
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=raw_expr))
|
|
498
|
+
parsed_expr: exp.Expression | None = exp.maybe_parse(str(values), dialect=builder.dialect)
|
|
499
|
+
if parsed_expr is not None:
|
|
500
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=parsed_expr))
|
|
501
|
+
if has_expression_and_sql(values):
|
|
502
|
+
self._merge_sql_object_parameters(values)
|
|
503
|
+
expression_attr = getattr(values, "expression", None)
|
|
504
|
+
if isinstance(expression_attr, exp.Expression):
|
|
505
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=expression_attr))
|
|
506
|
+
sql_text = getattr(values, "sql", "")
|
|
507
|
+
parsed_expr = exp.maybe_parse(sql_text, dialect=builder.dialect)
|
|
508
|
+
if parsed_expr is not None:
|
|
509
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=parsed_expr))
|
|
510
|
+
if isinstance(values, str):
|
|
511
|
+
parsed_expr = exp.maybe_parse(values, dialect=builder.dialect)
|
|
512
|
+
if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
|
|
513
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=exp.paren(parsed_expr)))
|
|
514
|
+
msg = "Unsupported type for 'values' in WHERE ANY"
|
|
515
|
+
raise SQLBuilderError(msg)
|
|
516
|
+
if not is_iterable_parameters(values) or isinstance(values, (bytes, bytearray)):
|
|
517
|
+
msg = "Unsupported type for 'values' in WHERE ANY"
|
|
518
|
+
raise SQLBuilderError(msg)
|
|
519
|
+
placeholders: list[exp.Expression] = []
|
|
520
|
+
values_list = list(values)
|
|
521
|
+
for index, element in enumerate(values_list):
|
|
522
|
+
if len(values_list) == 1:
|
|
523
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
524
|
+
else:
|
|
525
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_any_{index + 1}")
|
|
526
|
+
_, param_name = builder.add_parameter(element, name=param_name)
|
|
527
|
+
placeholders.append(exp.Placeholder(this=param_name))
|
|
528
|
+
tuple_expr = exp.Tuple(expressions=placeholders)
|
|
529
|
+
return exp.EQ(this=column_expr, expression=exp.Any(this=tuple_expr))
|
|
530
|
+
|
|
531
|
+
def _create_not_any_condition(self, column_expr: exp.Expression, values: Any, column_name: str) -> exp.Expression:
|
|
532
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
533
|
+
if has_query_builder_parameters(values):
|
|
534
|
+
subquery_expr = self._normalize_subquery_expression(values, builder)
|
|
535
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=subquery_expr))
|
|
536
|
+
if isinstance(values, exp.Expression):
|
|
537
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=values))
|
|
538
|
+
if has_sqlglot_expression(values):
|
|
539
|
+
raw_expr = getattr(values, "sqlglot_expression", None)
|
|
540
|
+
if isinstance(raw_expr, exp.Expression):
|
|
541
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=raw_expr))
|
|
542
|
+
parsed_expr: exp.Expression | None = exp.maybe_parse(str(values), dialect=builder.dialect)
|
|
543
|
+
if parsed_expr is not None:
|
|
544
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=parsed_expr))
|
|
545
|
+
if has_expression_and_sql(values):
|
|
546
|
+
self._merge_sql_object_parameters(values)
|
|
547
|
+
expression_attr = getattr(values, "expression", None)
|
|
548
|
+
if isinstance(expression_attr, exp.Expression):
|
|
549
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=expression_attr))
|
|
550
|
+
sql_text = getattr(values, "sql", "")
|
|
551
|
+
parsed_expr = exp.maybe_parse(sql_text, dialect=builder.dialect)
|
|
552
|
+
if parsed_expr is not None:
|
|
553
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=parsed_expr))
|
|
554
|
+
if isinstance(values, str):
|
|
555
|
+
parsed_expr = exp.maybe_parse(values, dialect=builder.dialect)
|
|
556
|
+
if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
|
|
557
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=exp.paren(parsed_expr)))
|
|
558
|
+
msg = "Unsupported type for 'values' in WHERE NOT ANY"
|
|
559
|
+
raise SQLBuilderError(msg)
|
|
560
|
+
if not is_iterable_parameters(values) or isinstance(values, (bytes, bytearray)):
|
|
561
|
+
msg = "Unsupported type for 'values' in WHERE NOT ANY"
|
|
562
|
+
raise SQLBuilderError(msg)
|
|
563
|
+
placeholders: list[exp.Expression] = []
|
|
564
|
+
values_list = list(values)
|
|
565
|
+
for index, element in enumerate(values_list):
|
|
566
|
+
if len(values_list) == 1:
|
|
567
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
568
|
+
else:
|
|
569
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_not_any_{index + 1}")
|
|
570
|
+
_, param_name = builder.add_parameter(element, name=param_name)
|
|
571
|
+
placeholders.append(exp.Placeholder(this=param_name))
|
|
572
|
+
tuple_expr = exp.Tuple(expressions=placeholders)
|
|
573
|
+
return exp.NEQ(this=column_expr, expression=exp.Any(this=tuple_expr))
|
|
574
|
+
|
|
575
|
+
def _normalize_subquery_expression(self, subquery: Any, builder: "SQLBuilderProtocol") -> exp.Expression:
|
|
576
|
+
if has_query_builder_parameters(subquery):
|
|
577
|
+
subquery_builder = cast("QueryBuilder", subquery)
|
|
578
|
+
safe_query: SafeQuery = subquery_builder.build()
|
|
579
|
+
parsed_subquery: exp.Expression | None = exp.maybe_parse(safe_query.sql, dialect=builder.dialect)
|
|
580
|
+
if parsed_subquery is None:
|
|
581
|
+
msg = f"Could not parse subquery SQL: {safe_query.sql}"
|
|
582
|
+
raise SQLBuilderError(msg)
|
|
583
|
+
subquery_expr = exp.paren(parsed_subquery)
|
|
584
|
+
parameters: Any = safe_query.parameters
|
|
585
|
+
if isinstance(parameters, dict):
|
|
586
|
+
param_mapping: dict[str, str] = {}
|
|
587
|
+
query_builder = cast("QueryBuilder", builder)
|
|
588
|
+
for param_name, param_value in parameters.items():
|
|
589
|
+
unique_name = query_builder._generate_unique_parameter_name(param_name)
|
|
590
|
+
param_mapping[param_name] = unique_name
|
|
591
|
+
query_builder.add_parameter(param_value, name=unique_name)
|
|
592
|
+
if param_mapping:
|
|
593
|
+
updated = query_builder._update_placeholders_in_expression(parsed_subquery, param_mapping)
|
|
594
|
+
subquery_expr = exp.paren(updated)
|
|
595
|
+
elif isinstance(parameters, (list, tuple)):
|
|
596
|
+
for param_value in parameters:
|
|
597
|
+
builder.add_parameter(param_value)
|
|
598
|
+
elif parameters is not None:
|
|
599
|
+
builder.add_parameter(parameters)
|
|
600
|
+
return subquery_expr
|
|
601
|
+
|
|
602
|
+
if has_expression_and_sql(subquery):
|
|
603
|
+
self._merge_sql_object_parameters(subquery)
|
|
604
|
+
expression_attr = getattr(subquery, "expression", None)
|
|
605
|
+
if isinstance(expression_attr, exp.Expression):
|
|
606
|
+
return expression_attr
|
|
607
|
+
sql_text = getattr(subquery, "sql", "")
|
|
608
|
+
parsed_from_sql: exp.Expression | None = exp.maybe_parse(sql_text, dialect=builder.dialect)
|
|
609
|
+
if parsed_from_sql is None:
|
|
610
|
+
msg = f"Could not parse subquery SQL: {sql_text}"
|
|
611
|
+
raise SQLBuilderError(msg)
|
|
612
|
+
return parsed_from_sql
|
|
613
|
+
|
|
614
|
+
if isinstance(subquery, exp.Expression):
|
|
615
|
+
return subquery
|
|
616
|
+
|
|
617
|
+
if isinstance(subquery, str):
|
|
618
|
+
parsed_expression_from_str: exp.Expression | None = exp.maybe_parse(subquery, dialect=builder.dialect)
|
|
619
|
+
if parsed_expression_from_str is None:
|
|
620
|
+
msg = f"Could not parse subquery SQL: {subquery}"
|
|
621
|
+
raise SQLBuilderError(msg)
|
|
622
|
+
return parsed_expression_from_str
|
|
623
|
+
|
|
624
|
+
converted_expr: exp.Expression = exp.convert(subquery)
|
|
625
|
+
return converted_expr
|
|
626
|
+
|
|
627
|
+
def _create_or_expression(self, conditions: "list[exp.Expression]") -> exp.Expression:
|
|
628
|
+
if not conditions:
|
|
629
|
+
msg = "OR expression requires at least one condition"
|
|
630
|
+
raise SQLBuilderError(msg)
|
|
631
|
+
|
|
632
|
+
or_condition = conditions[0]
|
|
633
|
+
for condition in conditions[1:]:
|
|
634
|
+
or_condition = exp.Or(this=or_condition, expression=condition)
|
|
635
|
+
return or_condition
|
|
636
|
+
|
|
637
|
+
def _process_tuple_condition(self, condition: "tuple[Any, ...]") -> exp.Expression:
|
|
638
|
+
if len(condition) == PAIR_LENGTH:
|
|
639
|
+
column, value = condition
|
|
640
|
+
return self._create_parameterized_condition(
|
|
641
|
+
column, value, lambda col, placeholder: exp.EQ(this=col, expression=placeholder)
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
if len(condition) != TRIPLE_LENGTH:
|
|
645
|
+
msg = f"Condition tuple must have 2 or 3 elements, got {len(condition)}"
|
|
646
|
+
raise SQLBuilderError(msg)
|
|
647
|
+
|
|
648
|
+
column_raw, operator, value = condition
|
|
649
|
+
operator_upper = str(operator).upper()
|
|
650
|
+
column_expr = parse_column_expression(column_raw)
|
|
651
|
+
column_name = extract_column_name(column_raw)
|
|
652
|
+
|
|
653
|
+
simple_operator_map: dict[str, Callable[[exp.Expression, exp.Placeholder], exp.Expression]] = {
|
|
654
|
+
"=": lambda col, placeholder: exp.EQ(this=col, expression=placeholder),
|
|
655
|
+
"==": lambda col, placeholder: exp.EQ(this=col, expression=placeholder),
|
|
656
|
+
"!=": lambda col, placeholder: exp.NEQ(this=col, expression=placeholder),
|
|
657
|
+
"<>": lambda col, placeholder: exp.NEQ(this=col, expression=placeholder),
|
|
658
|
+
">": lambda col, placeholder: exp.GT(this=col, expression=placeholder),
|
|
659
|
+
">=": lambda col, placeholder: exp.GTE(this=col, expression=placeholder),
|
|
660
|
+
"<": lambda col, placeholder: exp.LT(this=col, expression=placeholder),
|
|
661
|
+
"<=": lambda col, placeholder: exp.LTE(this=col, expression=placeholder),
|
|
662
|
+
"LIKE": lambda col, placeholder: exp.Like(this=col, expression=placeholder),
|
|
663
|
+
"NOT LIKE": lambda col, placeholder: exp.Not(this=exp.Like(this=col, expression=placeholder)),
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if operator_upper in simple_operator_map:
|
|
667
|
+
return self._create_parameterized_condition(column_raw, value, simple_operator_map[operator_upper])
|
|
668
|
+
|
|
669
|
+
if operator_upper == "IN":
|
|
670
|
+
return self._handle_in_operator(column_expr, value, column_name)
|
|
671
|
+
if operator_upper == "NOT IN":
|
|
672
|
+
return self._handle_not_in_operator(column_expr, value, column_name)
|
|
673
|
+
if operator_upper == "IS":
|
|
674
|
+
return self._handle_is_operator(column_expr, value)
|
|
675
|
+
if operator_upper == "IS NOT":
|
|
676
|
+
return self._handle_is_not_operator(column_expr, value)
|
|
677
|
+
if operator_upper == "BETWEEN":
|
|
678
|
+
return self._handle_between_operator(column_expr, value, column_name)
|
|
679
|
+
if operator_upper == "NOT BETWEEN":
|
|
680
|
+
return self._handle_not_between_operator(column_expr, value, column_name)
|
|
681
|
+
|
|
682
|
+
msg = f"Unsupported operator: {operator}"
|
|
683
|
+
raise SQLBuilderError(msg)
|
|
684
|
+
|
|
685
|
+
def _process_where_condition(
|
|
686
|
+
self,
|
|
687
|
+
condition: Union[
|
|
688
|
+
str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", SQL
|
|
689
|
+
],
|
|
690
|
+
values: tuple[Any, ...],
|
|
691
|
+
operator: str | None,
|
|
692
|
+
kwargs: dict[str, Any],
|
|
693
|
+
) -> exp.Expression:
|
|
694
|
+
if values or kwargs:
|
|
695
|
+
if not isinstance(condition, str):
|
|
696
|
+
msg = "When values are provided, condition must be a string"
|
|
697
|
+
raise SQLBuilderError(msg)
|
|
698
|
+
|
|
699
|
+
validator = ParameterValidator()
|
|
700
|
+
param_info = validator.extract_parameters(condition)
|
|
701
|
+
|
|
702
|
+
if param_info:
|
|
703
|
+
param_dict = dict(kwargs)
|
|
704
|
+
positional_params = [
|
|
705
|
+
info
|
|
706
|
+
for info in param_info
|
|
707
|
+
if info.style in {ParameterStyle.NUMERIC, ParameterStyle.POSITIONAL_COLON, ParameterStyle.QMARK}
|
|
708
|
+
]
|
|
709
|
+
|
|
710
|
+
if len(values) != len(positional_params):
|
|
711
|
+
msg = (
|
|
712
|
+
"Parameter count mismatch: condition has "
|
|
713
|
+
f"{len(positional_params)} positional placeholders, got {len(values)} values"
|
|
714
|
+
)
|
|
715
|
+
raise SQLBuilderError(msg)
|
|
716
|
+
|
|
717
|
+
for index, value in enumerate(values):
|
|
718
|
+
param_dict[f"param_{index}"] = value
|
|
719
|
+
|
|
720
|
+
condition = SQL(condition, param_dict)
|
|
721
|
+
elif len(values) == 1 and not kwargs:
|
|
722
|
+
if operator is not None:
|
|
723
|
+
return self._process_tuple_condition((condition, operator, values[0]))
|
|
724
|
+
return self._process_tuple_condition((condition, values[0]))
|
|
725
|
+
else:
|
|
726
|
+
msg = f"Cannot bind parameters to condition without placeholders: {condition}"
|
|
727
|
+
raise SQLBuilderError(msg)
|
|
728
|
+
|
|
729
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
730
|
+
|
|
731
|
+
if isinstance(condition, str):
|
|
732
|
+
return parse_condition_expression(condition)
|
|
733
|
+
if isinstance(condition, (exp.Expression, exp.Condition)):
|
|
734
|
+
return condition
|
|
735
|
+
if isinstance(condition, tuple):
|
|
736
|
+
return self._process_tuple_condition(condition)
|
|
737
|
+
if has_query_builder_parameters(condition):
|
|
738
|
+
column_expr_obj = cast("ColumnExpression", condition)
|
|
739
|
+
expression_attr = cast("exp.Expression | None", getattr(column_expr_obj, "_expression", None))
|
|
740
|
+
if expression_attr is None:
|
|
741
|
+
msg = "Column expression is missing underlying sqlglot expression."
|
|
742
|
+
raise SQLBuilderError(msg)
|
|
743
|
+
return expression_attr
|
|
744
|
+
if has_sqlglot_expression(condition):
|
|
745
|
+
raw_expr = getattr(condition, "sqlglot_expression", None)
|
|
746
|
+
if isinstance(raw_expr, exp.Expression):
|
|
747
|
+
return builder._parameterize_expression(raw_expr)
|
|
748
|
+
return parse_condition_expression(str(condition))
|
|
749
|
+
if has_expression_and_sql(condition):
|
|
750
|
+
expression_attr = getattr(condition, "expression", None)
|
|
751
|
+
if isinstance(expression_attr, exp.Expression):
|
|
752
|
+
self._merge_sql_object_parameters(condition)
|
|
753
|
+
return expression_attr
|
|
754
|
+
sql_text = getattr(condition, "sql", "")
|
|
755
|
+
self._merge_sql_object_parameters(condition)
|
|
756
|
+
return parse_condition_expression(sql_text)
|
|
757
|
+
|
|
758
|
+
msg = f"Unsupported condition type: {type(condition).__name__}"
|
|
759
|
+
raise SQLBuilderError(msg)
|
|
760
|
+
|
|
761
|
+
def where(
|
|
762
|
+
self,
|
|
763
|
+
condition: Union[
|
|
764
|
+
str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", SQL
|
|
765
|
+
],
|
|
766
|
+
*values: Any,
|
|
767
|
+
operator: str | None = None,
|
|
768
|
+
**kwargs: Any,
|
|
769
|
+
) -> Self:
|
|
770
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
771
|
+
current_expr = builder.get_expression()
|
|
772
|
+
if current_expr is None:
|
|
773
|
+
msg = "Cannot add WHERE clause: expression is not initialized."
|
|
774
|
+
raise SQLBuilderError(msg)
|
|
775
|
+
|
|
776
|
+
if isinstance(current_expr, exp.Delete) and not current_expr.args.get("this"):
|
|
777
|
+
msg = "WHERE clause requires a table to be set. Use from() to set the table first."
|
|
778
|
+
raise SQLBuilderError(msg)
|
|
779
|
+
|
|
780
|
+
where_expr = self._process_where_condition(condition, values, operator, kwargs)
|
|
781
|
+
|
|
782
|
+
if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)):
|
|
783
|
+
updated_expr = current_expr.where(where_expr, copy=False)
|
|
784
|
+
builder.set_expression(updated_expr)
|
|
785
|
+
return cast("Self", builder)
|
|
786
|
+
msg = f"WHERE clause not supported for {type(current_expr).__name__}"
|
|
787
|
+
raise SQLBuilderError(msg)
|
|
788
|
+
|
|
789
|
+
def where_eq(self, column: str | exp.Column, value: Any) -> Self:
|
|
790
|
+
condition = self._create_parameterized_condition(
|
|
791
|
+
column, value, lambda col, placeholder: exp.EQ(this=col, expression=placeholder)
|
|
792
|
+
)
|
|
793
|
+
return self.where(condition)
|
|
794
|
+
|
|
795
|
+
def where_neq(self, column: str | exp.Column, value: Any) -> Self:
|
|
796
|
+
condition = self._create_parameterized_condition(
|
|
797
|
+
column, value, lambda col, placeholder: exp.NEQ(this=col, expression=placeholder)
|
|
798
|
+
)
|
|
799
|
+
return self.where(condition)
|
|
800
|
+
|
|
801
|
+
def where_lt(self, column: str | exp.Column, value: Any) -> Self:
|
|
802
|
+
condition = self._create_parameterized_condition(
|
|
803
|
+
column, value, lambda col, placeholder: exp.LT(this=col, expression=placeholder)
|
|
804
|
+
)
|
|
805
|
+
return self.where(condition)
|
|
806
|
+
|
|
807
|
+
def where_lte(self, column: str | exp.Column, value: Any) -> Self:
|
|
808
|
+
condition = self._create_parameterized_condition(
|
|
809
|
+
column, value, lambda col, placeholder: exp.LTE(this=col, expression=placeholder)
|
|
810
|
+
)
|
|
811
|
+
return self.where(condition)
|
|
812
|
+
|
|
813
|
+
def where_gt(self, column: str | exp.Column, value: Any) -> Self:
|
|
814
|
+
condition = self._create_parameterized_condition(
|
|
815
|
+
column, value, lambda col, placeholder: exp.GT(this=col, expression=placeholder)
|
|
816
|
+
)
|
|
817
|
+
return self.where(condition)
|
|
818
|
+
|
|
819
|
+
def where_gte(self, column: str | exp.Column, value: Any) -> Self:
|
|
820
|
+
condition = self._create_parameterized_condition(
|
|
821
|
+
column, value, lambda col, placeholder: exp.GTE(this=col, expression=placeholder)
|
|
822
|
+
)
|
|
823
|
+
return self.where(condition)
|
|
824
|
+
|
|
825
|
+
def where_between(self, column: str | exp.Column, low: Any, high: Any) -> Self:
|
|
826
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
827
|
+
column_name = extract_column_name(column)
|
|
828
|
+
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
829
|
+
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
830
|
+
_, low_param = builder.add_parameter(low, name=low_param)
|
|
831
|
+
_, high_param = builder.add_parameter(high, name=high_param)
|
|
832
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
833
|
+
condition: exp.Expression = col_expr.between(exp.Placeholder(this=low_param), exp.Placeholder(this=high_param))
|
|
834
|
+
return self.where(condition)
|
|
835
|
+
|
|
836
|
+
def where_like(self, column: str | exp.Column, pattern: str, escape: str | None = None) -> Self:
|
|
837
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
838
|
+
column_name = extract_column_name(column)
|
|
839
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
840
|
+
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
841
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
842
|
+
if escape is not None:
|
|
843
|
+
condition = exp.Like(
|
|
844
|
+
this=col_expr, expression=exp.Placeholder(this=param_name), escape=exp.convert(str(escape))
|
|
845
|
+
)
|
|
846
|
+
else:
|
|
847
|
+
condition = col_expr.like(exp.Placeholder(this=param_name))
|
|
848
|
+
return self.where(condition)
|
|
849
|
+
|
|
850
|
+
def where_not_like(self, column: str | exp.Column, pattern: str) -> Self:
|
|
851
|
+
condition = self._create_parameterized_condition(
|
|
852
|
+
column, pattern, lambda col, placeholder: exp.Not(this=exp.Like(this=col, expression=placeholder))
|
|
853
|
+
)
|
|
854
|
+
return self.where(condition)
|
|
855
|
+
|
|
856
|
+
def where_ilike(self, column: str | exp.Column, pattern: str) -> Self:
|
|
857
|
+
condition = self._create_parameterized_condition(
|
|
858
|
+
column, pattern, lambda col, placeholder: col.ilike(placeholder)
|
|
859
|
+
)
|
|
860
|
+
return self.where(condition)
|
|
861
|
+
|
|
862
|
+
def where_is_null(self, column: str | exp.Column) -> Self:
|
|
863
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
864
|
+
condition: exp.Expression = col_expr.is_(exp.null())
|
|
865
|
+
return self.where(condition)
|
|
866
|
+
|
|
867
|
+
def where_is_not_null(self, column: str | exp.Column) -> Self:
|
|
868
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
869
|
+
condition: exp.Expression = col_expr.is_(exp.null()).not_()
|
|
870
|
+
return self.where(condition)
|
|
871
|
+
|
|
872
|
+
def where_in(self, column: str | exp.Column, values: Any) -> Self:
|
|
873
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
874
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
875
|
+
if has_query_builder_parameters(values) or isinstance(values, (exp.Expression, str)):
|
|
876
|
+
subquery_exp = self._normalize_subquery_expression(values, builder)
|
|
877
|
+
return self.where(exp.In(this=col_expr, expressions=[subquery_exp]))
|
|
878
|
+
|
|
879
|
+
condition = self._handle_in_operator(col_expr, values, extract_column_name(column))
|
|
880
|
+
return self.where(condition)
|
|
881
|
+
|
|
882
|
+
def where_not_in(self, column: str | exp.Column, values: Any) -> Self:
|
|
883
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
884
|
+
condition = self._handle_not_in_operator(col_expr, values, extract_column_name(column))
|
|
885
|
+
return self.where(condition)
|
|
886
|
+
|
|
887
|
+
def where_any(self, column: str | exp.Column, subquery: Any) -> Self:
|
|
888
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
889
|
+
column_name = extract_column_name(column)
|
|
890
|
+
condition = self._create_any_condition(col_expr, subquery, column_name)
|
|
891
|
+
return self.where(condition)
|
|
892
|
+
|
|
893
|
+
def where_not_any(self, column: str | exp.Column, subquery: Any) -> Self:
|
|
894
|
+
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
895
|
+
column_name = extract_column_name(column)
|
|
896
|
+
condition = self._create_not_any_condition(col_expr, subquery, column_name)
|
|
897
|
+
return self.where(condition)
|
|
898
|
+
|
|
899
|
+
def where_exists(self, subquery: Any) -> Self:
|
|
900
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
901
|
+
subquery_expr = self._normalize_subquery_expression(subquery, builder)
|
|
902
|
+
return self.where(exp.Exists(this=subquery_expr))
|
|
903
|
+
|
|
904
|
+
def where_not_exists(self, subquery: Any) -> Self:
|
|
905
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
906
|
+
subquery_expr = self._normalize_subquery_expression(subquery, builder)
|
|
907
|
+
return self.where(exp.Not(this=exp.Exists(this=subquery_expr)))
|
|
908
|
+
|
|
909
|
+
def where_like_any(self, column: str | exp.Column, patterns: list[str]) -> Self:
|
|
910
|
+
conditions = [
|
|
911
|
+
self._create_parameterized_condition(column, pattern, lambda col, placeholder: col.like(placeholder))
|
|
912
|
+
for pattern in patterns
|
|
913
|
+
]
|
|
914
|
+
or_condition = self._create_or_expression(conditions)
|
|
915
|
+
return self.where(or_condition)
|
|
916
|
+
|
|
917
|
+
def or_where_eq(self, column: str | exp.Column, value: Any) -> Self:
|
|
918
|
+
condition = self._create_parameterized_condition(
|
|
919
|
+
column, value, lambda col, placeholder: exp.EQ(this=col, expression=placeholder)
|
|
920
|
+
)
|
|
921
|
+
return self._combine_with_or(condition)
|
|
922
|
+
|
|
923
|
+
def or_where_neq(self, column: str | exp.Column, value: Any) -> Self:
|
|
924
|
+
condition = self._create_parameterized_condition(
|
|
925
|
+
column, value, lambda col, placeholder: exp.NEQ(this=col, expression=placeholder)
|
|
926
|
+
)
|
|
927
|
+
return self._combine_with_or(condition)
|
|
928
|
+
|
|
929
|
+
def or_where_lt(self, column: str | exp.Column, value: Any) -> Self:
|
|
930
|
+
condition = self._create_parameterized_condition(
|
|
931
|
+
column, value, lambda col, placeholder: exp.LT(this=col, expression=placeholder)
|
|
932
|
+
)
|
|
933
|
+
return self._combine_with_or(condition)
|
|
934
|
+
|
|
935
|
+
def or_where_lte(self, column: str | exp.Column, value: Any) -> Self:
|
|
936
|
+
condition = self._create_parameterized_condition(
|
|
937
|
+
column, value, lambda col, placeholder: exp.LTE(this=col, expression=placeholder)
|
|
938
|
+
)
|
|
939
|
+
return self._combine_with_or(condition)
|
|
940
|
+
|
|
941
|
+
def or_where_gt(self, column: str | exp.Column, value: Any) -> Self:
|
|
942
|
+
condition = self._create_parameterized_condition(
|
|
943
|
+
column, value, lambda col, placeholder: exp.GT(this=col, expression=placeholder)
|
|
944
|
+
)
|
|
945
|
+
return self._combine_with_or(condition)
|
|
946
|
+
|
|
947
|
+
def or_where_gte(self, column: str | exp.Column, value: Any) -> Self:
|
|
948
|
+
condition = self._create_parameterized_condition(
|
|
949
|
+
column, value, lambda col, placeholder: exp.GTE(this=col, expression=placeholder)
|
|
950
|
+
)
|
|
951
|
+
return self._combine_with_or(condition)
|
|
952
|
+
|
|
953
|
+
def or_where_between(self, column: str | exp.Column, low: Any, high: Any) -> Self:
|
|
954
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
955
|
+
condition = self._handle_between_operator(column_expr, (low, high), extract_column_name(column))
|
|
956
|
+
return self._combine_with_or(condition)
|
|
957
|
+
|
|
958
|
+
def or_where_like(self, column: str | exp.Column, pattern: str, escape: str | None = None) -> Self:
|
|
959
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
960
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
961
|
+
column_name = extract_column_name(column)
|
|
962
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
963
|
+
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
964
|
+
placeholder = exp.Placeholder(this=param_name)
|
|
965
|
+
if escape is not None:
|
|
966
|
+
condition = exp.Like(this=column_expr, expression=placeholder, escape=exp.convert(str(escape)))
|
|
967
|
+
else:
|
|
968
|
+
condition = column_expr.like(placeholder)
|
|
969
|
+
return self._combine_with_or(cast("exp.Expression", condition))
|
|
970
|
+
|
|
971
|
+
def or_where_not_like(self, column: str | exp.Column, pattern: str) -> Self:
|
|
972
|
+
condition = self._create_parameterized_condition(
|
|
973
|
+
column, pattern, lambda col, placeholder: col.like(placeholder).not_()
|
|
974
|
+
)
|
|
975
|
+
return self._combine_with_or(condition)
|
|
976
|
+
|
|
977
|
+
def or_where_ilike(self, column: str | exp.Column, pattern: str) -> Self:
|
|
978
|
+
condition = self._create_parameterized_condition(
|
|
979
|
+
column, pattern, lambda col, placeholder: col.ilike(placeholder)
|
|
980
|
+
)
|
|
981
|
+
return self._combine_with_or(condition)
|
|
982
|
+
|
|
983
|
+
def or_where_is_null(self, column: str | exp.Column) -> Self:
|
|
984
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
985
|
+
condition: exp.Expression = column_expr.is_(exp.null())
|
|
986
|
+
return self._combine_with_or(condition)
|
|
987
|
+
|
|
988
|
+
def or_where_is_not_null(self, column: str | exp.Column) -> Self:
|
|
989
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
990
|
+
condition: exp.Expression = column_expr.is_(exp.null()).not_()
|
|
991
|
+
return self._combine_with_or(condition)
|
|
992
|
+
|
|
993
|
+
def or_where_null(self, column: str | exp.Column) -> Self:
|
|
994
|
+
return self.or_where_is_null(column)
|
|
995
|
+
|
|
996
|
+
def or_where_not_null(self, column: str | exp.Column) -> Self:
|
|
997
|
+
return self.or_where_is_not_null(column)
|
|
998
|
+
|
|
999
|
+
def or_where_in(self, column: str | exp.Column, values: Any) -> Self:
|
|
1000
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1001
|
+
condition = self._handle_in_operator(column_expr, values, extract_column_name(column))
|
|
1002
|
+
return self._combine_with_or(condition)
|
|
1003
|
+
|
|
1004
|
+
def or_where_not_in(self, column: str | exp.Column, values: Any) -> Self:
|
|
1005
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1006
|
+
condition = self._handle_not_in_operator(column_expr, values, extract_column_name(column))
|
|
1007
|
+
return self._combine_with_or(condition)
|
|
1008
|
+
|
|
1009
|
+
def or_where_any(self, column: str | exp.Column, subquery: Any) -> Self:
|
|
1010
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1011
|
+
condition = self._create_any_condition(column_expr, subquery, extract_column_name(column))
|
|
1012
|
+
return self._combine_with_or(condition)
|
|
1013
|
+
|
|
1014
|
+
def or_where_not_any(self, column: str | exp.Column, subquery: Any) -> Self:
|
|
1015
|
+
column_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
1016
|
+
condition = self._create_not_any_condition(column_expr, subquery, extract_column_name(column))
|
|
1017
|
+
return self._combine_with_or(condition)
|
|
1018
|
+
|
|
1019
|
+
def or_where_exists(self, subquery: Any) -> Self:
|
|
1020
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
1021
|
+
subquery_expr = self._normalize_subquery_expression(subquery, builder)
|
|
1022
|
+
condition = exp.Exists(this=subquery_expr)
|
|
1023
|
+
return self._combine_with_or(condition)
|
|
1024
|
+
|
|
1025
|
+
def or_where_not_exists(self, subquery: Any) -> Self:
|
|
1026
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
1027
|
+
subquery_expr = self._normalize_subquery_expression(subquery, builder)
|
|
1028
|
+
condition = exp.Not(this=exp.Exists(this=subquery_expr))
|
|
1029
|
+
return self._combine_with_or(condition)
|
|
1030
|
+
|
|
1031
|
+
def where_or(self, *conditions: str | tuple[str, Any] | tuple[str, str, Any] | exp.Expression) -> Self:
|
|
1032
|
+
if not conditions:
|
|
1033
|
+
msg = "where_or() requires at least one condition"
|
|
1034
|
+
raise SQLBuilderError(msg)
|
|
1035
|
+
|
|
1036
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
1037
|
+
if builder.get_expression() is None:
|
|
1038
|
+
msg = "Cannot add WHERE OR clause: expression is not initialized."
|
|
1039
|
+
raise SQLBuilderError(msg)
|
|
1040
|
+
|
|
1041
|
+
processed_conditions = [self._process_where_condition(condition, (), None, {}) for condition in conditions]
|
|
1042
|
+
or_condition = self._create_or_expression(processed_conditions)
|
|
1043
|
+
return self.where(or_condition)
|
|
1044
|
+
|
|
1045
|
+
def or_where(
|
|
1046
|
+
self,
|
|
1047
|
+
condition: Union[
|
|
1048
|
+
str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", SQL
|
|
1049
|
+
],
|
|
1050
|
+
*values: Any,
|
|
1051
|
+
operator: str | None = None,
|
|
1052
|
+
**kwargs: Any,
|
|
1053
|
+
) -> Self:
|
|
1054
|
+
or_condition = self._process_where_condition(condition, values, operator, kwargs)
|
|
1055
|
+
return self._combine_with_or(or_condition)
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
@trait
|
|
1059
|
+
class HavingClauseMixin:
|
|
1060
|
+
__slots__ = ()
|
|
1061
|
+
|
|
1062
|
+
def having(self, condition: str | exp.Expression | exp.Condition | tuple[str, Any] | tuple[str, str, Any]) -> Self:
|
|
1063
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
1064
|
+
current_expr = builder.get_expression()
|
|
1065
|
+
if current_expr is None or not isinstance(current_expr, exp.Select):
|
|
1066
|
+
return cast("Self", builder)
|
|
1067
|
+
|
|
1068
|
+
if isinstance(condition, tuple):
|
|
1069
|
+
where_mixin = cast("WhereClauseMixin", self)
|
|
1070
|
+
having_expr = where_mixin._process_tuple_condition(condition)
|
|
1071
|
+
else:
|
|
1072
|
+
having_expr = parse_condition_expression(condition)
|
|
1073
|
+
|
|
1074
|
+
builder.set_expression(current_expr.having(having_expr, copy=False))
|
|
1075
|
+
return cast("Self", builder)
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
@trait
|
|
1079
|
+
class PivotClauseMixin:
|
|
1080
|
+
__slots__ = ()
|
|
1081
|
+
|
|
1082
|
+
def pivot(
|
|
1083
|
+
self,
|
|
1084
|
+
aggregate_function: str | exp.Expression,
|
|
1085
|
+
aggregate_column: str | exp.Expression,
|
|
1086
|
+
pivot_column: str | exp.Expression,
|
|
1087
|
+
pivot_values: list[str | int | float | exp.Expression],
|
|
1088
|
+
alias: str | None = None,
|
|
1089
|
+
) -> "Select":
|
|
1090
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
1091
|
+
current_expr = builder.get_expression()
|
|
1092
|
+
if not isinstance(current_expr, exp.Select):
|
|
1093
|
+
msg = "Pivot can only be applied to a Select expression managed by SelectBuilder."
|
|
1094
|
+
raise TypeError(msg)
|
|
1095
|
+
|
|
1096
|
+
agg_name = aggregate_function if isinstance(aggregate_function, str) else aggregate_function.name
|
|
1097
|
+
agg_column = exp.column(aggregate_column) if isinstance(aggregate_column, str) else aggregate_column
|
|
1098
|
+
pivot_col_expr = exp.column(pivot_column) if isinstance(pivot_column, str) else pivot_column
|
|
1099
|
+
|
|
1100
|
+
pivot_agg_expr = exp.func(agg_name, agg_column)
|
|
1101
|
+
|
|
1102
|
+
pivot_value_exprs: list[exp.Expression] = []
|
|
1103
|
+
for raw_value in pivot_values:
|
|
1104
|
+
if isinstance(raw_value, exp.Expression):
|
|
1105
|
+
pivot_value_exprs.append(raw_value)
|
|
1106
|
+
elif isinstance(raw_value, (str, int, float)):
|
|
1107
|
+
pivot_value_exprs.append(exp.convert(raw_value))
|
|
1108
|
+
else:
|
|
1109
|
+
pivot_value_exprs.append(exp.convert(str(raw_value)))
|
|
1110
|
+
|
|
1111
|
+
in_expr = exp.In(this=pivot_col_expr, expressions=pivot_value_exprs)
|
|
1112
|
+
pivot_node = exp.Pivot(expressions=[pivot_agg_expr], fields=[in_expr], unpivot=False)
|
|
1113
|
+
|
|
1114
|
+
if alias:
|
|
1115
|
+
pivot_node.set("alias", exp.TableAlias(this=exp.to_identifier(alias)))
|
|
1116
|
+
|
|
1117
|
+
from_clause = current_expr.args.get("from")
|
|
1118
|
+
if from_clause and isinstance(from_clause, exp.From):
|
|
1119
|
+
table = from_clause.this
|
|
1120
|
+
if isinstance(table, exp.Table):
|
|
1121
|
+
existing = table.args.get("pivots", [])
|
|
1122
|
+
existing.append(pivot_node)
|
|
1123
|
+
table.set("pivots", existing)
|
|
1124
|
+
|
|
1125
|
+
return cast("Select", self)
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
@trait
|
|
1129
|
+
class UnpivotClauseMixin:
|
|
1130
|
+
__slots__ = ()
|
|
1131
|
+
|
|
1132
|
+
def unpivot(
|
|
1133
|
+
self,
|
|
1134
|
+
value_column_name: str,
|
|
1135
|
+
name_column_name: str,
|
|
1136
|
+
columns_to_unpivot: list[str | exp.Expression],
|
|
1137
|
+
alias: str | None = None,
|
|
1138
|
+
) -> "Select":
|
|
1139
|
+
builder = cast("SQLBuilderProtocol", self)
|
|
1140
|
+
current_expr = builder.get_expression()
|
|
1141
|
+
if not isinstance(current_expr, exp.Select):
|
|
1142
|
+
msg = "Unpivot can only be applied to a Select expression managed by Select."
|
|
1143
|
+
raise TypeError(msg)
|
|
1144
|
+
|
|
1145
|
+
value_identifier = exp.to_identifier(value_column_name)
|
|
1146
|
+
name_identifier = exp.to_identifier(name_column_name)
|
|
1147
|
+
|
|
1148
|
+
unpivot_columns: list[exp.Expression] = []
|
|
1149
|
+
for column in columns_to_unpivot:
|
|
1150
|
+
if isinstance(column, exp.Expression):
|
|
1151
|
+
unpivot_columns.append(column)
|
|
1152
|
+
elif isinstance(column, str):
|
|
1153
|
+
unpivot_columns.append(exp.column(column))
|
|
1154
|
+
else:
|
|
1155
|
+
unpivot_columns.append(exp.column(str(column)))
|
|
1156
|
+
|
|
1157
|
+
in_expr = exp.In(this=name_identifier, expressions=unpivot_columns)
|
|
1158
|
+
unpivot_node = exp.Pivot(expressions=[value_identifier], fields=[in_expr], unpivot=True)
|
|
1159
|
+
|
|
1160
|
+
if alias:
|
|
1161
|
+
unpivot_node.set("alias", exp.TableAlias(this=exp.to_identifier(alias)))
|
|
1162
|
+
|
|
1163
|
+
from_clause = current_expr.args.get("from")
|
|
1164
|
+
if from_clause and isinstance(from_clause, exp.From):
|
|
1165
|
+
table = from_clause.this
|
|
1166
|
+
if isinstance(table, exp.Table):
|
|
1167
|
+
existing = table.args.get("pivots", [])
|
|
1168
|
+
existing.append(unpivot_node)
|
|
1169
|
+
table.set("pivots", existing)
|
|
1170
|
+
|
|
1171
|
+
return cast("Select", self)
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
@trait
|
|
1175
|
+
class CommonTableExpressionMixin:
|
|
1176
|
+
__slots__ = ()
|
|
1177
|
+
|
|
1178
|
+
def get_expression(self) -> exp.Expression | None: ...
|
|
1179
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
1180
|
+
|
|
1181
|
+
_with_ctes: Any
|
|
1182
|
+
dialect: Any
|
|
1183
|
+
|
|
1184
|
+
def add_parameter(self, value: Any, name: str | None = None) -> tuple[Any, str]:
|
|
1185
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
1186
|
+
raise NotImplementedError(msg)
|
|
1187
|
+
|
|
1188
|
+
def _generate_unique_parameter_name(self, base_name: str) -> str:
|
|
1189
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
1190
|
+
raise NotImplementedError(msg)
|
|
1191
|
+
|
|
1192
|
+
def _update_placeholders_in_expression(
|
|
1193
|
+
self, expression: exp.Expression, param_mapping: dict[str, str]
|
|
1194
|
+
) -> exp.Expression:
|
|
1195
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
1196
|
+
raise NotImplementedError(msg)
|
|
1197
|
+
|
|
1198
|
+
def with_(self, name: str, query: Any | str, recursive: bool = False, columns: list[str] | None = None) -> Self:
|
|
1199
|
+
builder = cast("QueryBuilder", self)
|
|
1200
|
+
expression = builder.get_expression()
|
|
1201
|
+
if expression is None:
|
|
1202
|
+
msg = "Cannot add WITH clause: expression not initialized."
|
|
1203
|
+
raise SQLBuilderError(msg)
|
|
1204
|
+
|
|
1205
|
+
if not isinstance(expression, (exp.Select, exp.Insert, exp.Update, exp.Delete)):
|
|
1206
|
+
msg = f"Cannot add WITH clause to {type(expression).__name__} expression."
|
|
1207
|
+
raise SQLBuilderError(msg)
|
|
1208
|
+
|
|
1209
|
+
cte_select: exp.Expression | None
|
|
1210
|
+
if isinstance(query, str):
|
|
1211
|
+
cte_select = exp.maybe_parse(query, dialect=self.dialect)
|
|
1212
|
+
elif isinstance(query, exp.Expression):
|
|
1213
|
+
cte_select = query
|
|
1214
|
+
else:
|
|
1215
|
+
built_query = query.to_statement()
|
|
1216
|
+
cte_sql = built_query.sql
|
|
1217
|
+
cte_select = exp.maybe_parse(cte_sql, dialect=self.dialect)
|
|
1218
|
+
|
|
1219
|
+
parameters = built_query.parameters
|
|
1220
|
+
if isinstance(parameters, dict):
|
|
1221
|
+
param_mapping: dict[str, str] = {}
|
|
1222
|
+
for param_name, param_value in parameters.items():
|
|
1223
|
+
unique_name = self._generate_unique_parameter_name(f"{name}_{param_name}")
|
|
1224
|
+
param_mapping[param_name] = unique_name
|
|
1225
|
+
self.add_parameter(param_value, name=unique_name)
|
|
1226
|
+
if cte_select is not None:
|
|
1227
|
+
cte_select = self._update_placeholders_in_expression(cte_select, param_mapping)
|
|
1228
|
+
elif isinstance(parameters, (list, tuple)):
|
|
1229
|
+
for param_value in parameters:
|
|
1230
|
+
self.add_parameter(param_value)
|
|
1231
|
+
elif parameters is not None:
|
|
1232
|
+
self.add_parameter(parameters)
|
|
1233
|
+
|
|
1234
|
+
if cte_select is None:
|
|
1235
|
+
msg = f"Could not parse CTE query: {query}"
|
|
1236
|
+
raise SQLBuilderError(msg)
|
|
1237
|
+
|
|
1238
|
+
if columns:
|
|
1239
|
+
cte_alias_expr = exp.alias_(cte_select, name, table=[exp.to_identifier(col) for col in columns])
|
|
1240
|
+
else:
|
|
1241
|
+
cte_alias_expr = exp.alias_(cte_select, name)
|
|
1242
|
+
|
|
1243
|
+
existing_with = expression.args.get("with")
|
|
1244
|
+
if existing_with:
|
|
1245
|
+
existing_with.expressions.append(cte_alias_expr)
|
|
1246
|
+
if recursive:
|
|
1247
|
+
existing_with.set("recursive", recursive)
|
|
1248
|
+
else:
|
|
1249
|
+
if isinstance(expression, (exp.Select, exp.Insert, exp.Update)):
|
|
1250
|
+
updated = expression.with_(cte_alias_expr, as_=name, copy=False)
|
|
1251
|
+
builder.set_expression(updated)
|
|
1252
|
+
if recursive:
|
|
1253
|
+
with_clause = updated.find(exp.With)
|
|
1254
|
+
if with_clause:
|
|
1255
|
+
with_clause.set("recursive", recursive)
|
|
1256
|
+
builder._with_ctes[name] = exp.CTE(this=cte_select, alias=exp.to_table(name))
|
|
1257
|
+
|
|
1258
|
+
return cast("Self", builder)
|
|
1259
|
+
|
|
1260
|
+
|
|
1261
|
+
@trait
|
|
1262
|
+
class SetOperationMixin:
|
|
1263
|
+
__slots__ = ()
|
|
28
1264
|
|
|
29
|
-
|
|
1265
|
+
def get_expression(self) -> exp.Expression | None: ...
|
|
1266
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
1267
|
+
def set_parameters(self, parameters: dict[str, Any]) -> None: ...
|
|
1268
|
+
|
|
1269
|
+
dialect: Any = None
|
|
1270
|
+
|
|
1271
|
+
def union(self, other: Any, all_: bool = False) -> Self:
|
|
1272
|
+
return self._combine_with_other(other, operator="union", distinct=not all_)
|
|
1273
|
+
|
|
1274
|
+
def intersect(self, other: Any) -> Self:
|
|
1275
|
+
return self._combine_with_other(other, operator="intersect", distinct=True)
|
|
1276
|
+
|
|
1277
|
+
def except_(self, other: Any) -> Self:
|
|
1278
|
+
return self._combine_with_other(other, operator="except", distinct=True)
|
|
1279
|
+
|
|
1280
|
+
def _combine_with_other(self, other: Any, *, operator: str, distinct: bool) -> Self:
|
|
1281
|
+
builder = cast("QueryBuilder", self)
|
|
1282
|
+
|
|
1283
|
+
if not hasattr(other, "_build_final_expression") or not hasattr(other, "parameters"):
|
|
1284
|
+
msg = "Set operations require another SQLSpec query builder."
|
|
1285
|
+
raise SQLBuilderError(msg)
|
|
1286
|
+
|
|
1287
|
+
other_builder = cast("QueryBuilder", other)
|
|
1288
|
+
left_expr = builder._build_final_expression(copy=True)
|
|
1289
|
+
right_expr = other_builder._build_final_expression(copy=True)
|
|
1290
|
+
|
|
1291
|
+
merged_parameters: dict[str, Any] = dict(builder.parameters)
|
|
1292
|
+
rename_map: dict[str, str] = {}
|
|
1293
|
+
for param_name, param_value in other_builder.parameters.items():
|
|
1294
|
+
target_name = param_name
|
|
1295
|
+
if target_name in merged_parameters:
|
|
1296
|
+
counter = 1
|
|
1297
|
+
while True:
|
|
1298
|
+
candidate = f"{param_name}_right_{counter}"
|
|
1299
|
+
if candidate not in merged_parameters:
|
|
1300
|
+
target_name = candidate
|
|
1301
|
+
break
|
|
1302
|
+
counter += 1
|
|
1303
|
+
rename_map[param_name] = target_name
|
|
1304
|
+
merged_parameters[target_name] = param_value
|
|
1305
|
+
|
|
1306
|
+
if rename_map:
|
|
1307
|
+
right_expr = builder._update_placeholders_in_expression(right_expr, rename_map)
|
|
1308
|
+
|
|
1309
|
+
combined: exp.Expression
|
|
1310
|
+
if operator == "union":
|
|
1311
|
+
combined = exp.union(left_expr, right_expr, distinct=distinct)
|
|
1312
|
+
elif operator == "intersect":
|
|
1313
|
+
combined = exp.intersect(left_expr, right_expr, distinct=distinct)
|
|
1314
|
+
elif operator == "except":
|
|
1315
|
+
combined = exp.except_(left_expr, right_expr)
|
|
1316
|
+
else: # pragma: no cover - defensive
|
|
1317
|
+
msg = f"Unsupported set operation: {operator}"
|
|
1318
|
+
raise SQLBuilderError(msg)
|
|
1319
|
+
|
|
1320
|
+
new_builder = builder._spawn_like_self()
|
|
1321
|
+
new_builder.set_expression(combined)
|
|
1322
|
+
new_builder.set_parameters(merged_parameters)
|
|
1323
|
+
return cast("Self", new_builder)
|
|
30
1324
|
|
|
31
1325
|
|
|
32
1326
|
TABLE_HINT_PATTERN: Final[str] = r"\b{}\b(\s+AS\s+\w+)?"
|
|
@@ -58,8 +1352,8 @@ class Select(
|
|
|
58
1352
|
>>> result = driver.execute(builder)
|
|
59
1353
|
"""
|
|
60
1354
|
|
|
61
|
-
__slots__ = ("_hints",
|
|
62
|
-
_expression:
|
|
1355
|
+
__slots__ = ("_hints",)
|
|
1356
|
+
_expression: exp.Expression | None
|
|
63
1357
|
|
|
64
1358
|
def __init__(self, *columns: str, **kwargs: Any) -> None:
|
|
65
1359
|
"""Initialize SELECT with optional columns.
|
|
@@ -74,7 +1368,6 @@ class Select(
|
|
|
74
1368
|
"""
|
|
75
1369
|
super().__init__(**kwargs)
|
|
76
1370
|
|
|
77
|
-
self._with_parts: dict[str, Union[exp.CTE, Select]] = {}
|
|
78
1371
|
self._hints: list[dict[str, object]] = []
|
|
79
1372
|
|
|
80
1373
|
self._initialize_expression()
|
|
@@ -98,12 +1391,7 @@ class Select(
|
|
|
98
1391
|
return self._expression
|
|
99
1392
|
|
|
100
1393
|
def with_hint(
|
|
101
|
-
self,
|
|
102
|
-
hint: "str",
|
|
103
|
-
*,
|
|
104
|
-
location: "str" = "statement",
|
|
105
|
-
table: "Optional[str]" = None,
|
|
106
|
-
dialect: "Optional[str]" = None,
|
|
1394
|
+
self, hint: "str", *, location: "str" = "statement", table: "str | None" = None, dialect: "str | None" = None
|
|
107
1395
|
) -> "Self":
|
|
108
1396
|
"""Attach an optimizer or dialect-specific hint to the query.
|
|
109
1397
|
|
|
@@ -139,7 +1427,7 @@ class Select(
|
|
|
139
1427
|
def parse_hint_safely(hint: Any) -> exp.Expression:
|
|
140
1428
|
try:
|
|
141
1429
|
hint_str = str(hint)
|
|
142
|
-
hint_expr:
|
|
1430
|
+
hint_expr: exp.Expression | None = exp.maybe_parse(hint_str, dialect=self.dialect_name)
|
|
143
1431
|
return hint_expr or exp.Anonymous(this=hint_str)
|
|
144
1432
|
except Exception:
|
|
145
1433
|
return exp.Anonymous(this=str(hint))
|
|
@@ -195,7 +1483,7 @@ class Select(
|
|
|
195
1483
|
raise SQLBuilderError(msg)
|
|
196
1484
|
|
|
197
1485
|
def for_update(
|
|
198
|
-
self, *, skip_locked: bool = False, nowait: bool = False, of: "
|
|
1486
|
+
self, *, skip_locked: bool = False, nowait: bool = False, of: "str | list[str] | None" = None
|
|
199
1487
|
) -> "Self":
|
|
200
1488
|
"""Add FOR UPDATE clause to SELECT statement for row-level locking.
|
|
201
1489
|
|
|
@@ -233,7 +1521,7 @@ class Select(
|
|
|
233
1521
|
return self
|
|
234
1522
|
|
|
235
1523
|
def for_share(
|
|
236
|
-
self, *, skip_locked: bool = False, nowait: bool = False, of: "
|
|
1524
|
+
self, *, skip_locked: bool = False, nowait: bool = False, of: "str | list[str] | None" = None
|
|
237
1525
|
) -> "Self":
|
|
238
1526
|
"""Add FOR SHARE clause for shared row-level locking.
|
|
239
1527
|
|