sqlspec 0.13.1__py3-none-any.whl → 0.16.2__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 +71 -8
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +1 -3
- sqlspec/_serialization.py +1 -2
- sqlspec/_sql.py +930 -136
- sqlspec/_typing.py +278 -142
- sqlspec/adapters/adbc/__init__.py +4 -3
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +116 -285
- sqlspec/adapters/adbc/driver.py +462 -340
- sqlspec/adapters/aiosqlite/__init__.py +18 -3
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +202 -150
- sqlspec/adapters/aiosqlite/driver.py +226 -247
- sqlspec/adapters/asyncmy/__init__.py +18 -3
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +80 -199
- sqlspec/adapters/asyncmy/driver.py +257 -215
- sqlspec/adapters/asyncpg/__init__.py +19 -4
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +81 -214
- sqlspec/adapters/asyncpg/driver.py +284 -359
- sqlspec/adapters/bigquery/__init__.py +17 -3
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +191 -299
- sqlspec/adapters/bigquery/driver.py +474 -634
- sqlspec/adapters/duckdb/__init__.py +14 -3
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +414 -397
- sqlspec/adapters/duckdb/driver.py +342 -393
- sqlspec/adapters/oracledb/__init__.py +19 -5
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +123 -458
- sqlspec/adapters/oracledb/driver.py +505 -531
- sqlspec/adapters/psqlpy/__init__.py +13 -3
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +93 -307
- sqlspec/adapters/psqlpy/driver.py +504 -213
- sqlspec/adapters/psycopg/__init__.py +19 -5
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +143 -472
- sqlspec/adapters/psycopg/driver.py +704 -825
- sqlspec/adapters/sqlite/__init__.py +14 -3
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +208 -142
- sqlspec/adapters/sqlite/driver.py +263 -278
- sqlspec/base.py +105 -9
- sqlspec/{statement/builder → builder}/__init__.py +12 -14
- sqlspec/{statement/builder/base.py → builder/_base.py} +184 -86
- sqlspec/{statement/builder/column.py → builder/_column.py} +97 -60
- sqlspec/{statement/builder/ddl.py → builder/_ddl.py} +61 -131
- sqlspec/{statement/builder → builder}/_ddl_utils.py +4 -10
- sqlspec/{statement/builder/delete.py → builder/_delete.py} +10 -30
- sqlspec/builder/_insert.py +421 -0
- sqlspec/builder/_merge.py +71 -0
- sqlspec/{statement/builder → builder}/_parsing_utils.py +49 -26
- sqlspec/builder/_select.py +170 -0
- sqlspec/{statement/builder/update.py → builder/_update.py} +16 -20
- sqlspec/builder/mixins/__init__.py +55 -0
- sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
- sqlspec/{statement/builder/mixins/_delete_from.py → builder/mixins/_delete_operations.py} +8 -1
- sqlspec/builder/mixins/_insert_operations.py +244 -0
- sqlspec/{statement/builder/mixins/_join.py → builder/mixins/_join_operations.py} +45 -13
- sqlspec/{statement/builder/mixins/_merge_clauses.py → builder/mixins/_merge_operations.py} +188 -30
- sqlspec/builder/mixins/_order_limit_operations.py +135 -0
- sqlspec/builder/mixins/_pivot_operations.py +153 -0
- sqlspec/builder/mixins/_select_operations.py +604 -0
- sqlspec/builder/mixins/_update_operations.py +202 -0
- sqlspec/builder/mixins/_where_clause.py +644 -0
- sqlspec/cli.py +247 -0
- sqlspec/config.py +183 -138
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.py +871 -0
- sqlspec/core/compiler.py +417 -0
- sqlspec/core/filters.py +830 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.py +1237 -0
- sqlspec/core/result.py +677 -0
- sqlspec/{statement → core}/splitter.py +321 -191
- sqlspec/core/statement.py +676 -0
- sqlspec/driver/__init__.py +7 -10
- sqlspec/driver/_async.py +422 -163
- sqlspec/driver/_common.py +545 -287
- sqlspec/driver/_sync.py +426 -160
- sqlspec/driver/mixins/__init__.py +2 -13
- sqlspec/driver/mixins/_result_tools.py +193 -0
- sqlspec/driver/mixins/_sql_translator.py +65 -14
- sqlspec/exceptions.py +5 -252
- sqlspec/extensions/aiosql/adapter.py +93 -96
- sqlspec/extensions/litestar/__init__.py +2 -1
- sqlspec/extensions/litestar/cli.py +48 -0
- sqlspec/extensions/litestar/config.py +0 -1
- sqlspec/extensions/litestar/handlers.py +15 -26
- sqlspec/extensions/litestar/plugin.py +21 -16
- sqlspec/extensions/litestar/providers.py +17 -52
- sqlspec/loader.py +423 -104
- sqlspec/migrations/__init__.py +35 -0
- sqlspec/migrations/base.py +414 -0
- sqlspec/migrations/commands.py +443 -0
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +213 -0
- sqlspec/migrations/tracker.py +140 -0
- sqlspec/migrations/utils.py +129 -0
- sqlspec/protocols.py +51 -186
- sqlspec/storage/__init__.py +1 -1
- sqlspec/storage/backends/base.py +37 -40
- sqlspec/storage/backends/fsspec.py +136 -112
- sqlspec/storage/backends/obstore.py +138 -160
- sqlspec/storage/capabilities.py +5 -4
- sqlspec/storage/registry.py +57 -106
- sqlspec/typing.py +136 -115
- sqlspec/utils/__init__.py +2 -2
- sqlspec/utils/correlation.py +0 -3
- sqlspec/utils/deprecation.py +6 -6
- sqlspec/utils/fixtures.py +6 -6
- sqlspec/utils/logging.py +0 -2
- sqlspec/utils/module_loader.py +7 -12
- sqlspec/utils/singleton.py +0 -1
- sqlspec/utils/sync_tools.py +17 -38
- sqlspec/utils/text.py +12 -51
- sqlspec/utils/type_guards.py +482 -235
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/METADATA +7 -2
- sqlspec-0.16.2.dist-info/RECORD +134 -0
- sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
- sqlspec/driver/connection.py +0 -207
- sqlspec/driver/mixins/_csv_writer.py +0 -91
- sqlspec/driver/mixins/_pipeline.py +0 -512
- sqlspec/driver/mixins/_result_utils.py +0 -140
- sqlspec/driver/mixins/_storage.py +0 -926
- sqlspec/driver/mixins/_type_coercion.py +0 -130
- sqlspec/driver/parameters.py +0 -138
- sqlspec/service/__init__.py +0 -4
- sqlspec/service/_util.py +0 -147
- sqlspec/service/base.py +0 -1131
- sqlspec/service/pagination.py +0 -26
- sqlspec/statement/__init__.py +0 -21
- sqlspec/statement/builder/insert.py +0 -288
- sqlspec/statement/builder/merge.py +0 -95
- sqlspec/statement/builder/mixins/__init__.py +0 -65
- sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
- sqlspec/statement/builder/mixins/_case_builder.py +0 -91
- sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
- sqlspec/statement/builder/mixins/_from.py +0 -63
- sqlspec/statement/builder/mixins/_group_by.py +0 -118
- sqlspec/statement/builder/mixins/_having.py +0 -35
- sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
- sqlspec/statement/builder/mixins/_insert_into.py +0 -36
- sqlspec/statement/builder/mixins/_insert_values.py +0 -67
- sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
- sqlspec/statement/builder/mixins/_order_by.py +0 -46
- sqlspec/statement/builder/mixins/_pivot.py +0 -79
- sqlspec/statement/builder/mixins/_returning.py +0 -37
- sqlspec/statement/builder/mixins/_select_columns.py +0 -61
- sqlspec/statement/builder/mixins/_set_ops.py +0 -122
- sqlspec/statement/builder/mixins/_unpivot.py +0 -77
- sqlspec/statement/builder/mixins/_update_from.py +0 -55
- sqlspec/statement/builder/mixins/_update_set.py +0 -94
- sqlspec/statement/builder/mixins/_update_table.py +0 -29
- sqlspec/statement/builder/mixins/_where.py +0 -401
- sqlspec/statement/builder/mixins/_window_functions.py +0 -86
- sqlspec/statement/builder/select.py +0 -221
- sqlspec/statement/filters.py +0 -596
- sqlspec/statement/parameter_manager.py +0 -220
- sqlspec/statement/parameters.py +0 -867
- sqlspec/statement/pipelines/__init__.py +0 -210
- sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
- sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
- sqlspec/statement/pipelines/context.py +0 -115
- sqlspec/statement/pipelines/transformers/__init__.py +0 -7
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
- sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
- sqlspec/statement/pipelines/validators/__init__.py +0 -23
- sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
- sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
- sqlspec/statement/pipelines/validators/_performance.py +0 -718
- sqlspec/statement/pipelines/validators/_security.py +0 -967
- sqlspec/statement/result.py +0 -435
- sqlspec/statement/sql.py +0 -1704
- sqlspec/statement/sql_compiler.py +0 -140
- sqlspec/utils/cached_property.py +0 -25
- sqlspec-0.13.1.dist-info/RECORD +0 -150
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/WHEEL +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/NOTICE +0 -0
sqlspec/statement/filters.py
DELETED
|
@@ -1,596 +0,0 @@
|
|
|
1
|
-
"""Collection filter datastructures."""
|
|
2
|
-
|
|
3
|
-
from abc import ABC, abstractmethod
|
|
4
|
-
from collections import abc
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from datetime import datetime
|
|
7
|
-
from typing import TYPE_CHECKING, Any, Generic, Literal, Optional, Protocol, Union, runtime_checkable
|
|
8
|
-
|
|
9
|
-
from sqlglot import exp
|
|
10
|
-
from typing_extensions import TypeAlias, TypeVar
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from sqlglot.expressions import Condition
|
|
14
|
-
|
|
15
|
-
from sqlspec.statement import SQL
|
|
16
|
-
|
|
17
|
-
__all__ = (
|
|
18
|
-
"AnyCollectionFilter",
|
|
19
|
-
"BeforeAfterFilter",
|
|
20
|
-
"FilterTypeT",
|
|
21
|
-
"FilterTypes",
|
|
22
|
-
"InAnyFilter",
|
|
23
|
-
"InCollectionFilter",
|
|
24
|
-
"LimitOffsetFilter",
|
|
25
|
-
"NotAnyCollectionFilter",
|
|
26
|
-
"NotInCollectionFilter",
|
|
27
|
-
"NotInSearchFilter",
|
|
28
|
-
"OnBeforeAfterFilter",
|
|
29
|
-
"OrderByFilter",
|
|
30
|
-
"PaginationFilter",
|
|
31
|
-
"SearchFilter",
|
|
32
|
-
"StatementFilter",
|
|
33
|
-
"apply_filter",
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
T = TypeVar("T")
|
|
37
|
-
FilterTypeT = TypeVar("FilterTypeT", bound="StatementFilter")
|
|
38
|
-
"""Type variable for filter types.
|
|
39
|
-
|
|
40
|
-
:class:`~advanced_alchemy.filters.StatementFilter`
|
|
41
|
-
"""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@runtime_checkable
|
|
45
|
-
class StatementFilter(Protocol):
|
|
46
|
-
"""Protocol for filters that can be appended to a statement."""
|
|
47
|
-
|
|
48
|
-
@abstractmethod
|
|
49
|
-
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
50
|
-
"""Append the filter to the statement.
|
|
51
|
-
|
|
52
|
-
This method should modify the SQL expression only, not the parameters.
|
|
53
|
-
Parameters should be provided via extract_parameters().
|
|
54
|
-
"""
|
|
55
|
-
...
|
|
56
|
-
|
|
57
|
-
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
58
|
-
"""Extract parameters that this filter contributes.
|
|
59
|
-
|
|
60
|
-
Returns:
|
|
61
|
-
Tuple of (positional_params, named_params) where:
|
|
62
|
-
- positional_params: List of positional parameter values
|
|
63
|
-
- named_params: Dict of parameter name to value
|
|
64
|
-
"""
|
|
65
|
-
return [], {}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
@dataclass
|
|
69
|
-
class BeforeAfterFilter(StatementFilter):
|
|
70
|
-
"""Data required to filter a query on a ``datetime`` column.
|
|
71
|
-
|
|
72
|
-
Note:
|
|
73
|
-
After applying this filter, only the filter's parameters (e.g., before/after) will be present in the resulting SQL statement's parameters. Original parameters from the statement are not preserved in the result.
|
|
74
|
-
"""
|
|
75
|
-
|
|
76
|
-
field_name: str
|
|
77
|
-
"""Name of the model attribute to filter on."""
|
|
78
|
-
before: Optional[datetime] = None
|
|
79
|
-
"""Filter results where field earlier than this."""
|
|
80
|
-
after: Optional[datetime] = None
|
|
81
|
-
"""Filter results where field later than this."""
|
|
82
|
-
|
|
83
|
-
def __post_init__(self) -> None:
|
|
84
|
-
"""Initialize parameter names."""
|
|
85
|
-
self._param_name_before: Optional[str] = None
|
|
86
|
-
self._param_name_after: Optional[str] = None
|
|
87
|
-
|
|
88
|
-
if self.before:
|
|
89
|
-
self._param_name_before = f"{self.field_name}_before"
|
|
90
|
-
if self.after:
|
|
91
|
-
self._param_name_after = f"{self.field_name}_after"
|
|
92
|
-
|
|
93
|
-
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
94
|
-
"""Extract filter parameters."""
|
|
95
|
-
named_params = {}
|
|
96
|
-
if self.before and self._param_name_before:
|
|
97
|
-
named_params[self._param_name_before] = self.before
|
|
98
|
-
if self.after and self._param_name_after:
|
|
99
|
-
named_params[self._param_name_after] = self.after
|
|
100
|
-
return [], named_params
|
|
101
|
-
|
|
102
|
-
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
103
|
-
"""Apply filter to SQL expression only."""
|
|
104
|
-
conditions: list[Condition] = []
|
|
105
|
-
col_expr = exp.column(self.field_name)
|
|
106
|
-
|
|
107
|
-
if self.before and self._param_name_before:
|
|
108
|
-
conditions.append(exp.LT(this=col_expr, expression=exp.Placeholder(this=self._param_name_before)))
|
|
109
|
-
if self.after and self._param_name_after:
|
|
110
|
-
conditions.append(exp.GT(this=col_expr, expression=exp.Placeholder(this=self._param_name_after)))
|
|
111
|
-
|
|
112
|
-
if conditions:
|
|
113
|
-
final_condition = conditions[0]
|
|
114
|
-
for cond in conditions[1:]:
|
|
115
|
-
final_condition = exp.And(this=final_condition, expression=cond)
|
|
116
|
-
result = statement.where(final_condition)
|
|
117
|
-
_, named_params = self.extract_parameters()
|
|
118
|
-
for name, value in named_params.items():
|
|
119
|
-
result = result.add_named_parameter(name, value)
|
|
120
|
-
return result
|
|
121
|
-
return statement
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@dataclass
|
|
125
|
-
class OnBeforeAfterFilter(StatementFilter):
|
|
126
|
-
"""Data required to filter a query on a ``datetime`` column."""
|
|
127
|
-
|
|
128
|
-
field_name: str
|
|
129
|
-
"""Name of the model attribute to filter on."""
|
|
130
|
-
on_or_before: Optional[datetime] = None
|
|
131
|
-
"""Filter results where field is on or earlier than this."""
|
|
132
|
-
on_or_after: Optional[datetime] = None
|
|
133
|
-
"""Filter results where field on or later than this."""
|
|
134
|
-
|
|
135
|
-
def __post_init__(self) -> None:
|
|
136
|
-
"""Initialize parameter names."""
|
|
137
|
-
self._param_name_on_or_before: Optional[str] = None
|
|
138
|
-
self._param_name_on_or_after: Optional[str] = None
|
|
139
|
-
|
|
140
|
-
if self.on_or_before:
|
|
141
|
-
self._param_name_on_or_before = f"{self.field_name}_on_or_before"
|
|
142
|
-
if self.on_or_after:
|
|
143
|
-
self._param_name_on_or_after = f"{self.field_name}_on_or_after"
|
|
144
|
-
|
|
145
|
-
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
146
|
-
"""Extract filter parameters."""
|
|
147
|
-
named_params = {}
|
|
148
|
-
if self.on_or_before and self._param_name_on_or_before:
|
|
149
|
-
named_params[self._param_name_on_or_before] = self.on_or_before
|
|
150
|
-
if self.on_or_after and self._param_name_on_or_after:
|
|
151
|
-
named_params[self._param_name_on_or_after] = self.on_or_after
|
|
152
|
-
return [], named_params
|
|
153
|
-
|
|
154
|
-
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
155
|
-
conditions: list[Condition] = []
|
|
156
|
-
|
|
157
|
-
if self.on_or_before and self._param_name_on_or_before:
|
|
158
|
-
conditions.append(
|
|
159
|
-
exp.LTE(
|
|
160
|
-
this=exp.column(self.field_name), expression=exp.Placeholder(this=self._param_name_on_or_before)
|
|
161
|
-
)
|
|
162
|
-
)
|
|
163
|
-
if self.on_or_after and self._param_name_on_or_after:
|
|
164
|
-
conditions.append(
|
|
165
|
-
exp.GTE(this=exp.column(self.field_name), expression=exp.Placeholder(this=self._param_name_on_or_after))
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
if conditions:
|
|
169
|
-
final_condition = conditions[0]
|
|
170
|
-
for cond in conditions[1:]:
|
|
171
|
-
final_condition = exp.And(this=final_condition, expression=cond)
|
|
172
|
-
result = statement.where(final_condition)
|
|
173
|
-
_, named_params = self.extract_parameters()
|
|
174
|
-
for name, value in named_params.items():
|
|
175
|
-
result = result.add_named_parameter(name, value)
|
|
176
|
-
return result
|
|
177
|
-
return statement
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
class InAnyFilter(StatementFilter, ABC, Generic[T]):
|
|
181
|
-
"""Subclass for methods that have a `prefer_any` attribute."""
|
|
182
|
-
|
|
183
|
-
@abstractmethod
|
|
184
|
-
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
185
|
-
raise NotImplementedError
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
@dataclass
|
|
189
|
-
class InCollectionFilter(InAnyFilter[T]):
|
|
190
|
-
"""Data required to construct a ``WHERE ... IN (...)`` clause.
|
|
191
|
-
|
|
192
|
-
Note:
|
|
193
|
-
After applying this filter, only the filter's parameters (e.g., the generated IN parameters) will be present in the resulting SQL statement's parameters. Original parameters from the statement are not preserved in the result.
|
|
194
|
-
"""
|
|
195
|
-
|
|
196
|
-
field_name: str
|
|
197
|
-
"""Name of the model attribute to filter on."""
|
|
198
|
-
values: Optional[abc.Collection[T]]
|
|
199
|
-
"""Values for ``IN`` clause.
|
|
200
|
-
|
|
201
|
-
An empty list will return an empty result set, however, if ``None``, the filter is not applied to the query, and all rows are returned. """
|
|
202
|
-
|
|
203
|
-
def __post_init__(self) -> None:
|
|
204
|
-
"""Initialize parameter names."""
|
|
205
|
-
self._param_names: list[str] = []
|
|
206
|
-
if self.values:
|
|
207
|
-
for i, _ in enumerate(self.values):
|
|
208
|
-
self._param_names.append(f"{self.field_name}_in_{i}")
|
|
209
|
-
|
|
210
|
-
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
211
|
-
"""Extract filter parameters."""
|
|
212
|
-
named_params = {}
|
|
213
|
-
if self.values:
|
|
214
|
-
for i, value in enumerate(self.values):
|
|
215
|
-
named_params[self._param_names[i]] = value
|
|
216
|
-
return [], named_params
|
|
217
|
-
|
|
218
|
-
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
219
|
-
if self.values is None:
|
|
220
|
-
return statement
|
|
221
|
-
|
|
222
|
-
if not self.values:
|
|
223
|
-
return statement.where(exp.false())
|
|
224
|
-
|
|
225
|
-
placeholder_expressions: list[exp.Placeholder] = [
|
|
226
|
-
exp.Placeholder(this=param_name) for param_name in self._param_names
|
|
227
|
-
]
|
|
228
|
-
|
|
229
|
-
result = statement.where(exp.In(this=exp.column(self.field_name), expressions=placeholder_expressions))
|
|
230
|
-
_, named_params = self.extract_parameters()
|
|
231
|
-
for name, value in named_params.items():
|
|
232
|
-
result = result.add_named_parameter(name, value)
|
|
233
|
-
return result
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
@dataclass
|
|
237
|
-
class NotInCollectionFilter(InAnyFilter[T]):
|
|
238
|
-
"""Data required to construct a ``WHERE ... NOT IN (...)`` clause."""
|
|
239
|
-
|
|
240
|
-
field_name: str
|
|
241
|
-
"""Name of the model attribute to filter on."""
|
|
242
|
-
values: Optional[abc.Collection[T]]
|
|
243
|
-
"""Values for ``NOT IN`` clause.
|
|
244
|
-
|
|
245
|
-
An empty list or ``None`` will return all rows."""
|
|
246
|
-
|
|
247
|
-
def __post_init__(self) -> None:
|
|
248
|
-
"""Initialize parameter names."""
|
|
249
|
-
self._param_names: list[str] = []
|
|
250
|
-
if self.values:
|
|
251
|
-
for i, _ in enumerate(self.values):
|
|
252
|
-
self._param_names.append(f"{self.field_name}_notin_{i}_{id(self)}")
|
|
253
|
-
|
|
254
|
-
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
255
|
-
"""Extract filter parameters."""
|
|
256
|
-
named_params = {}
|
|
257
|
-
if self.values:
|
|
258
|
-
for i, value in enumerate(self.values):
|
|
259
|
-
named_params[self._param_names[i]] = value
|
|
260
|
-
return [], named_params
|
|
261
|
-
|
|
262
|
-
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
263
|
-
if self.values is None or not self.values:
|
|
264
|
-
return statement
|
|
265
|
-
|
|
266
|
-
placeholder_expressions: list[exp.Placeholder] = [
|
|
267
|
-
exp.Placeholder(this=param_name) for param_name in self._param_names
|
|
268
|
-
]
|
|
269
|
-
|
|
270
|
-
result = statement.where(
|
|
271
|
-
exp.Not(this=exp.In(this=exp.column(self.field_name), expressions=placeholder_expressions))
|
|
272
|
-
)
|
|
273
|
-
_, named_params = self.extract_parameters()
|
|
274
|
-
for name, value in named_params.items():
|
|
275
|
-
result = result.add_named_parameter(name, value)
|
|
276
|
-
return result
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
@dataclass
|
|
280
|
-
class AnyCollectionFilter(InAnyFilter[T]):
|
|
281
|
-
"""Data required to construct a ``WHERE column_name = ANY (array_expression)`` clause."""
|
|
282
|
-
|
|
283
|
-
field_name: str
|
|
284
|
-
"""Name of the model attribute to filter on."""
|
|
285
|
-
values: Optional[abc.Collection[T]]
|
|
286
|
-
"""Values for ``= ANY (...)`` clause.
|
|
287
|
-
|
|
288
|
-
An empty list will result in a condition that is always false (no rows returned).
|
|
289
|
-
If ``None``, the filter is not applied to the query, and all rows are returned.
|
|
290
|
-
"""
|
|
291
|
-
|
|
292
|
-
def __post_init__(self) -> None:
|
|
293
|
-
"""Initialize parameter names."""
|
|
294
|
-
self._param_names: list[str] = []
|
|
295
|
-
if self.values:
|
|
296
|
-
for i, _ in enumerate(self.values):
|
|
297
|
-
self._param_names.append(f"{self.field_name}_any_{i}")
|
|
298
|
-
|
|
299
|
-
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
300
|
-
"""Extract filter parameters."""
|
|
301
|
-
named_params = {}
|
|
302
|
-
if self.values:
|
|
303
|
-
for i, value in enumerate(self.values):
|
|
304
|
-
named_params[self._param_names[i]] = value
|
|
305
|
-
return [], named_params
|
|
306
|
-
|
|
307
|
-
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
308
|
-
if self.values is None:
|
|
309
|
-
return statement
|
|
310
|
-
|
|
311
|
-
if not self.values:
|
|
312
|
-
# column = ANY (empty_array) is generally false
|
|
313
|
-
return statement.where(exp.false())
|
|
314
|
-
|
|
315
|
-
placeholder_expressions: list[exp.Expression] = [
|
|
316
|
-
exp.Placeholder(this=param_name) for param_name in self._param_names
|
|
317
|
-
]
|
|
318
|
-
|
|
319
|
-
array_expr = exp.Array(expressions=placeholder_expressions)
|
|
320
|
-
# Generates SQL like: self.field_name = ANY(ARRAY[?, ?, ...])
|
|
321
|
-
result = statement.where(exp.EQ(this=exp.column(self.field_name), expression=exp.Any(this=array_expr)))
|
|
322
|
-
_, named_params = self.extract_parameters()
|
|
323
|
-
for name, value in named_params.items():
|
|
324
|
-
result = result.add_named_parameter(name, value)
|
|
325
|
-
return result
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
@dataclass
|
|
329
|
-
class NotAnyCollectionFilter(InAnyFilter[T]):
|
|
330
|
-
"""Data required to construct a ``WHERE NOT (column_name = ANY (array_expression))`` clause."""
|
|
331
|
-
|
|
332
|
-
field_name: str
|
|
333
|
-
"""Name of the model attribute to filter on."""
|
|
334
|
-
values: Optional[abc.Collection[T]]
|
|
335
|
-
"""Values for ``NOT (... = ANY (...))`` clause.
|
|
336
|
-
|
|
337
|
-
An empty list will result in a condition that is always true (all rows returned, filter effectively ignored).
|
|
338
|
-
If ``None``, the filter is not applied to the query, and all rows are returned.
|
|
339
|
-
"""
|
|
340
|
-
|
|
341
|
-
def __post_init__(self) -> None:
|
|
342
|
-
"""Initialize parameter names."""
|
|
343
|
-
self._param_names: list[str] = []
|
|
344
|
-
if self.values:
|
|
345
|
-
for i, _ in enumerate(self.values):
|
|
346
|
-
self._param_names.append(f"{self.field_name}_notany_{i}")
|
|
347
|
-
|
|
348
|
-
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
349
|
-
"""Extract filter parameters."""
|
|
350
|
-
named_params = {}
|
|
351
|
-
if self.values:
|
|
352
|
-
for i, value in enumerate(self.values):
|
|
353
|
-
named_params[self._param_names[i]] = value
|
|
354
|
-
return [], named_params
|
|
355
|
-
|
|
356
|
-
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
357
|
-
if self.values is None or not self.values:
|
|
358
|
-
# NOT (column = ANY (empty_array)) is generally true
|
|
359
|
-
# So, if values is empty or None, this filter should not restrict results.
|
|
360
|
-
return statement
|
|
361
|
-
|
|
362
|
-
placeholder_expressions: list[exp.Expression] = [
|
|
363
|
-
exp.Placeholder(this=param_name) for param_name in self._param_names
|
|
364
|
-
]
|
|
365
|
-
|
|
366
|
-
array_expr = exp.Array(expressions=placeholder_expressions)
|
|
367
|
-
# Generates SQL like: NOT (self.field_name = ANY(ARRAY[?, ?, ...]))
|
|
368
|
-
condition = exp.EQ(this=exp.column(self.field_name), expression=exp.Any(this=array_expr))
|
|
369
|
-
result = statement.where(exp.Not(this=condition))
|
|
370
|
-
_, named_params = self.extract_parameters()
|
|
371
|
-
for name, value in named_params.items():
|
|
372
|
-
result = result.add_named_parameter(name, value)
|
|
373
|
-
return result
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
class PaginationFilter(StatementFilter, ABC):
|
|
377
|
-
"""Subclass for methods that function as a pagination type."""
|
|
378
|
-
|
|
379
|
-
@abstractmethod
|
|
380
|
-
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
381
|
-
raise NotImplementedError
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
@dataclass
|
|
385
|
-
class LimitOffsetFilter(PaginationFilter):
|
|
386
|
-
"""Data required to add limit/offset filtering to a query."""
|
|
387
|
-
|
|
388
|
-
limit: int
|
|
389
|
-
"""Value for ``LIMIT`` clause of query."""
|
|
390
|
-
offset: int
|
|
391
|
-
"""Value for ``OFFSET`` clause of query."""
|
|
392
|
-
|
|
393
|
-
def __post_init__(self) -> None:
|
|
394
|
-
"""Initialize parameter names."""
|
|
395
|
-
# Generate unique parameter names to avoid conflicts
|
|
396
|
-
import uuid
|
|
397
|
-
|
|
398
|
-
unique_suffix = str(uuid.uuid4()).replace("-", "")[:8]
|
|
399
|
-
self._limit_param_name = f"limit_{unique_suffix}"
|
|
400
|
-
self._offset_param_name = f"offset_{unique_suffix}"
|
|
401
|
-
|
|
402
|
-
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
403
|
-
"""Extract filter parameters."""
|
|
404
|
-
return [], {self._limit_param_name: self.limit, self._offset_param_name: self.offset}
|
|
405
|
-
|
|
406
|
-
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
407
|
-
# Create limit and offset expressions using our pre-generated parameter names
|
|
408
|
-
from sqlglot import exp
|
|
409
|
-
|
|
410
|
-
limit_placeholder = exp.Placeholder(this=self._limit_param_name)
|
|
411
|
-
offset_placeholder = exp.Placeholder(this=self._offset_param_name)
|
|
412
|
-
|
|
413
|
-
# Apply LIMIT and OFFSET to the statement
|
|
414
|
-
result = statement
|
|
415
|
-
|
|
416
|
-
# Check if the statement supports LIMIT directly
|
|
417
|
-
if isinstance(result._statement, exp.Select):
|
|
418
|
-
new_statement = result._statement.limit(limit_placeholder)
|
|
419
|
-
else:
|
|
420
|
-
# Wrap in a SELECT if the statement doesn't support LIMIT directly
|
|
421
|
-
new_statement = exp.Select().from_(result._statement).limit(limit_placeholder)
|
|
422
|
-
|
|
423
|
-
# Add OFFSET
|
|
424
|
-
if isinstance(new_statement, exp.Select):
|
|
425
|
-
new_statement = new_statement.offset(offset_placeholder)
|
|
426
|
-
|
|
427
|
-
result = result.copy(statement=new_statement)
|
|
428
|
-
|
|
429
|
-
# Add the parameters to the result
|
|
430
|
-
_, named_params = self.extract_parameters()
|
|
431
|
-
for name, value in named_params.items():
|
|
432
|
-
result = result.add_named_parameter(name, value)
|
|
433
|
-
|
|
434
|
-
return result
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
@dataclass
|
|
438
|
-
class OrderByFilter(StatementFilter):
|
|
439
|
-
"""Data required to construct a ``ORDER BY ...`` clause."""
|
|
440
|
-
|
|
441
|
-
field_name: str
|
|
442
|
-
"""Name of the model attribute to sort on."""
|
|
443
|
-
sort_order: Literal["asc", "desc"] = "asc"
|
|
444
|
-
"""Sort ascending or descending"""
|
|
445
|
-
|
|
446
|
-
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
447
|
-
"""Extract filter parameters."""
|
|
448
|
-
# ORDER BY doesn't use parameters, only column names and sort direction
|
|
449
|
-
return [], {}
|
|
450
|
-
|
|
451
|
-
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
452
|
-
normalized_sort_order = self.sort_order.lower()
|
|
453
|
-
if normalized_sort_order not in {"asc", "desc"}:
|
|
454
|
-
normalized_sort_order = "asc"
|
|
455
|
-
if normalized_sort_order == "desc":
|
|
456
|
-
return statement.order_by(exp.column(self.field_name).desc())
|
|
457
|
-
return statement.order_by(exp.column(self.field_name).asc())
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
@dataclass
|
|
461
|
-
class SearchFilter(StatementFilter):
|
|
462
|
-
"""Data required to construct a ``WHERE field_name LIKE '%' || :value || '%'`` clause.
|
|
463
|
-
|
|
464
|
-
Note:
|
|
465
|
-
After applying this filter, only the filter's parameters (e.g., the generated search parameter) will be present in the resulting SQL statement's parameters. Original parameters from the statement are not preserved in the result.
|
|
466
|
-
"""
|
|
467
|
-
|
|
468
|
-
field_name: Union[str, set[str]]
|
|
469
|
-
"""Name of the model attribute to search on."""
|
|
470
|
-
value: str
|
|
471
|
-
"""Search value."""
|
|
472
|
-
ignore_case: Optional[bool] = False
|
|
473
|
-
"""Should the search be case insensitive."""
|
|
474
|
-
|
|
475
|
-
def __post_init__(self) -> None:
|
|
476
|
-
"""Initialize parameter names."""
|
|
477
|
-
self._param_name: Optional[str] = None
|
|
478
|
-
if self.value:
|
|
479
|
-
if isinstance(self.field_name, str):
|
|
480
|
-
self._param_name = f"{self.field_name}_search"
|
|
481
|
-
else:
|
|
482
|
-
self._param_name = "search_value"
|
|
483
|
-
|
|
484
|
-
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
485
|
-
"""Extract filter parameters."""
|
|
486
|
-
named_params = {}
|
|
487
|
-
if self.value and self._param_name:
|
|
488
|
-
search_value_with_wildcards = f"%{self.value}%"
|
|
489
|
-
named_params[self._param_name] = search_value_with_wildcards
|
|
490
|
-
return [], named_params
|
|
491
|
-
|
|
492
|
-
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
493
|
-
if not self.value or not self._param_name:
|
|
494
|
-
return statement
|
|
495
|
-
|
|
496
|
-
pattern_expr = exp.Placeholder(this=self._param_name)
|
|
497
|
-
like_op = exp.ILike if self.ignore_case else exp.Like
|
|
498
|
-
|
|
499
|
-
result = statement
|
|
500
|
-
if isinstance(self.field_name, str):
|
|
501
|
-
result = statement.where(like_op(this=exp.column(self.field_name), expression=pattern_expr))
|
|
502
|
-
elif isinstance(self.field_name, set) and self.field_name:
|
|
503
|
-
field_conditions: list[Condition] = [
|
|
504
|
-
like_op(this=exp.column(field), expression=pattern_expr) for field in self.field_name
|
|
505
|
-
]
|
|
506
|
-
if not field_conditions:
|
|
507
|
-
return statement
|
|
508
|
-
|
|
509
|
-
final_condition: Condition = field_conditions[0]
|
|
510
|
-
if len(field_conditions) > 1:
|
|
511
|
-
for cond in field_conditions[1:]:
|
|
512
|
-
final_condition = exp.Or(this=final_condition, expression=cond)
|
|
513
|
-
result = statement.where(final_condition)
|
|
514
|
-
|
|
515
|
-
_, named_params = self.extract_parameters()
|
|
516
|
-
for name, value in named_params.items():
|
|
517
|
-
result = result.add_named_parameter(name, value)
|
|
518
|
-
return result
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
@dataclass
|
|
522
|
-
class NotInSearchFilter(SearchFilter):
|
|
523
|
-
"""Data required to construct a ``WHERE field_name NOT LIKE '%' || :value || '%'`` clause."""
|
|
524
|
-
|
|
525
|
-
def __post_init__(self) -> None:
|
|
526
|
-
"""Initialize parameter names."""
|
|
527
|
-
self._param_name: Optional[str] = None
|
|
528
|
-
if self.value:
|
|
529
|
-
if isinstance(self.field_name, str):
|
|
530
|
-
self._param_name = f"{self.field_name}_not_search"
|
|
531
|
-
else:
|
|
532
|
-
self._param_name = "not_search_value"
|
|
533
|
-
|
|
534
|
-
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
535
|
-
"""Extract filter parameters."""
|
|
536
|
-
named_params = {}
|
|
537
|
-
if self.value and self._param_name:
|
|
538
|
-
search_value_with_wildcards = f"%{self.value}%"
|
|
539
|
-
named_params[self._param_name] = search_value_with_wildcards
|
|
540
|
-
return [], named_params
|
|
541
|
-
|
|
542
|
-
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
543
|
-
if not self.value or not self._param_name:
|
|
544
|
-
return statement
|
|
545
|
-
|
|
546
|
-
pattern_expr = exp.Placeholder(this=self._param_name)
|
|
547
|
-
like_op = exp.ILike if self.ignore_case else exp.Like
|
|
548
|
-
|
|
549
|
-
result = statement
|
|
550
|
-
if isinstance(self.field_name, str):
|
|
551
|
-
result = statement.where(exp.Not(this=like_op(this=exp.column(self.field_name), expression=pattern_expr)))
|
|
552
|
-
elif isinstance(self.field_name, set) and self.field_name:
|
|
553
|
-
field_conditions: list[Condition] = [
|
|
554
|
-
exp.Not(this=like_op(this=exp.column(field), expression=pattern_expr)) for field in self.field_name
|
|
555
|
-
]
|
|
556
|
-
if not field_conditions:
|
|
557
|
-
return statement
|
|
558
|
-
|
|
559
|
-
final_condition: Condition = field_conditions[0]
|
|
560
|
-
if len(field_conditions) > 1:
|
|
561
|
-
for cond in field_conditions[1:]:
|
|
562
|
-
final_condition = exp.And(this=final_condition, expression=cond)
|
|
563
|
-
result = statement.where(final_condition)
|
|
564
|
-
|
|
565
|
-
_, named_params = self.extract_parameters()
|
|
566
|
-
for name, value in named_params.items():
|
|
567
|
-
result = result.add_named_parameter(name, value)
|
|
568
|
-
return result
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
def apply_filter(statement: "SQL", filter_obj: StatementFilter) -> "SQL":
|
|
572
|
-
"""Apply a statement filter to a SQL query object.
|
|
573
|
-
|
|
574
|
-
Args:
|
|
575
|
-
statement: The SQL query object to modify.
|
|
576
|
-
filter_obj: The filter to apply.
|
|
577
|
-
|
|
578
|
-
Returns:
|
|
579
|
-
The modified query object.
|
|
580
|
-
"""
|
|
581
|
-
return filter_obj.append_to_statement(statement)
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
FilterTypes: TypeAlias = Union[
|
|
585
|
-
BeforeAfterFilter,
|
|
586
|
-
OnBeforeAfterFilter,
|
|
587
|
-
InCollectionFilter[Any],
|
|
588
|
-
LimitOffsetFilter,
|
|
589
|
-
OrderByFilter,
|
|
590
|
-
SearchFilter,
|
|
591
|
-
NotInCollectionFilter[Any],
|
|
592
|
-
NotInSearchFilter,
|
|
593
|
-
AnyCollectionFilter[Any],
|
|
594
|
-
NotAnyCollectionFilter[Any],
|
|
595
|
-
]
|
|
596
|
-
"""Aggregate type alias of the types supported for collection filtering."""
|