sqlspec 0.24.0__py3-none-any.whl → 0.25.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/_sql.py +21 -23
- sqlspec/_typing.py +2 -0
- sqlspec/adapters/adbc/driver.py +2 -2
- sqlspec/adapters/oracledb/driver.py +5 -0
- sqlspec/adapters/psycopg/config.py +2 -4
- sqlspec/base.py +3 -4
- sqlspec/builder/_base.py +55 -13
- sqlspec/builder/_column.py +9 -0
- sqlspec/builder/_ddl.py +7 -7
- sqlspec/builder/_insert.py +10 -6
- sqlspec/builder/_parsing_utils.py +23 -4
- sqlspec/builder/_update.py +1 -1
- sqlspec/builder/mixins/_cte_and_set_ops.py +31 -22
- sqlspec/builder/mixins/_delete_operations.py +12 -7
- sqlspec/builder/mixins/_insert_operations.py +50 -36
- sqlspec/builder/mixins/_join_operations.py +1 -0
- sqlspec/builder/mixins/_merge_operations.py +54 -28
- sqlspec/builder/mixins/_order_limit_operations.py +1 -0
- sqlspec/builder/mixins/_pivot_operations.py +1 -0
- sqlspec/builder/mixins/_select_operations.py +42 -14
- sqlspec/builder/mixins/_update_operations.py +30 -18
- sqlspec/builder/mixins/_where_clause.py +48 -60
- sqlspec/core/__init__.py +3 -2
- sqlspec/core/cache.py +297 -351
- sqlspec/core/compiler.py +5 -3
- sqlspec/core/filters.py +246 -213
- sqlspec/core/hashing.py +9 -11
- sqlspec/core/parameters.py +20 -7
- sqlspec/core/statement.py +67 -12
- sqlspec/driver/_async.py +2 -2
- sqlspec/driver/_common.py +31 -14
- sqlspec/driver/_sync.py +2 -2
- sqlspec/driver/mixins/_result_tools.py +60 -7
- sqlspec/loader.py +8 -9
- sqlspec/storage/backends/fsspec.py +1 -0
- sqlspec/typing.py +2 -0
- {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/METADATA +1 -1
- {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/RECORD +42 -42
- {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/core/filters.py
CHANGED
|
@@ -52,6 +52,8 @@ __all__ = (
|
|
|
52
52
|
"SearchFilter",
|
|
53
53
|
"StatementFilter",
|
|
54
54
|
"apply_filter",
|
|
55
|
+
"canonicalize_filters",
|
|
56
|
+
"create_filters",
|
|
55
57
|
)
|
|
56
58
|
|
|
57
59
|
T = TypeVar("T")
|
|
@@ -71,7 +73,7 @@ class StatementFilter(ABC):
|
|
|
71
73
|
Parameters should be provided via extract_parameters().
|
|
72
74
|
"""
|
|
73
75
|
|
|
74
|
-
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
76
|
+
def extract_parameters(self) -> "tuple[list[Any], dict[str, Any]]":
|
|
75
77
|
"""Extract parameters that this filter contributes.
|
|
76
78
|
|
|
77
79
|
Returns:
|
|
@@ -91,7 +93,7 @@ class StatementFilter(ABC):
|
|
|
91
93
|
Returns:
|
|
92
94
|
List of resolved parameter names (same length as proposed_names)
|
|
93
95
|
"""
|
|
94
|
-
existing_params = set(statement.
|
|
96
|
+
existing_params = set(statement.named_parameters.keys())
|
|
95
97
|
existing_params.update(statement.parameters.keys() if isinstance(statement.parameters, dict) else [])
|
|
96
98
|
|
|
97
99
|
resolved_names = []
|
|
@@ -121,39 +123,44 @@ class BeforeAfterFilter(StatementFilter):
|
|
|
121
123
|
Applies WHERE clauses for before/after datetime filtering.
|
|
122
124
|
"""
|
|
123
125
|
|
|
124
|
-
__slots__ = ("
|
|
125
|
-
|
|
126
|
-
field_name: str
|
|
127
|
-
before: Optional[datetime]
|
|
128
|
-
after: Optional[datetime]
|
|
126
|
+
__slots__ = ("_after", "_before", "_field_name")
|
|
129
127
|
|
|
130
128
|
def __init__(self, field_name: str, before: Optional[datetime] = None, after: Optional[datetime] = None) -> None:
|
|
131
|
-
|
|
129
|
+
self._field_name = field_name
|
|
130
|
+
self._before = before
|
|
131
|
+
self._after = after
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
self.
|
|
140
|
-
self.after = after
|
|
133
|
+
@property
|
|
134
|
+
def field_name(self) -> str:
|
|
135
|
+
return self._field_name
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def before(self) -> Optional[datetime]:
|
|
139
|
+
return self._before
|
|
141
140
|
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
@property
|
|
142
|
+
def after(self) -> Optional[datetime]:
|
|
143
|
+
return self._after
|
|
144
144
|
|
|
145
|
+
def get_param_names(self) -> list[str]:
|
|
146
|
+
"""Get parameter names without storing them."""
|
|
147
|
+
names = []
|
|
145
148
|
if self.before:
|
|
146
|
-
|
|
149
|
+
names.append(f"{self.field_name}_before")
|
|
147
150
|
if self.after:
|
|
148
|
-
|
|
151
|
+
names.append(f"{self.field_name}_after")
|
|
152
|
+
return names
|
|
149
153
|
|
|
150
154
|
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
151
155
|
"""Extract filter parameters."""
|
|
152
156
|
named_parameters = {}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if self.
|
|
156
|
-
named_parameters[
|
|
157
|
+
param_names = self.get_param_names()
|
|
158
|
+
param_idx = 0
|
|
159
|
+
if self.before:
|
|
160
|
+
named_parameters[param_names[param_idx]] = self.before
|
|
161
|
+
param_idx += 1
|
|
162
|
+
if self.after:
|
|
163
|
+
named_parameters[param_names[param_idx]] = self.after
|
|
157
164
|
return [], named_parameters
|
|
158
165
|
|
|
159
166
|
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
@@ -161,12 +168,7 @@ class BeforeAfterFilter(StatementFilter):
|
|
|
161
168
|
conditions: list[Condition] = []
|
|
162
169
|
col_expr = exp.column(self.field_name)
|
|
163
170
|
|
|
164
|
-
proposed_names =
|
|
165
|
-
if self.before and self._param_name_before:
|
|
166
|
-
proposed_names.append(self._param_name_before)
|
|
167
|
-
if self.after and self._param_name_after:
|
|
168
|
-
proposed_names.append(self._param_name_after)
|
|
169
|
-
|
|
171
|
+
proposed_names = self.get_param_names()
|
|
170
172
|
if not proposed_names:
|
|
171
173
|
return statement
|
|
172
174
|
|
|
@@ -174,13 +176,13 @@ class BeforeAfterFilter(StatementFilter):
|
|
|
174
176
|
|
|
175
177
|
param_idx = 0
|
|
176
178
|
result = statement
|
|
177
|
-
if self.before
|
|
179
|
+
if self.before:
|
|
178
180
|
before_param_name = resolved_names[param_idx]
|
|
179
181
|
param_idx += 1
|
|
180
182
|
conditions.append(exp.LT(this=col_expr, expression=exp.Placeholder(this=before_param_name)))
|
|
181
183
|
result = result.add_named_parameter(before_param_name, self.before)
|
|
182
184
|
|
|
183
|
-
if self.after
|
|
185
|
+
if self.after:
|
|
184
186
|
after_param_name = resolved_names[param_idx]
|
|
185
187
|
conditions.append(exp.GT(this=col_expr, expression=exp.Placeholder(this=after_param_name)))
|
|
186
188
|
result = result.add_named_parameter(after_param_name, self.after)
|
|
@@ -201,52 +203,52 @@ class OnBeforeAfterFilter(StatementFilter):
|
|
|
201
203
|
Applies WHERE clauses for on-or-before/on-or-after datetime filtering.
|
|
202
204
|
"""
|
|
203
205
|
|
|
204
|
-
__slots__ = ("
|
|
205
|
-
|
|
206
|
-
field_name: str
|
|
207
|
-
on_or_before: Optional[datetime]
|
|
208
|
-
on_or_after: Optional[datetime]
|
|
206
|
+
__slots__ = ("_field_name", "_on_or_after", "_on_or_before")
|
|
209
207
|
|
|
210
208
|
def __init__(
|
|
211
209
|
self, field_name: str, on_or_before: Optional[datetime] = None, on_or_after: Optional[datetime] = None
|
|
212
210
|
) -> None:
|
|
213
|
-
|
|
211
|
+
self._field_name = field_name
|
|
212
|
+
self._on_or_before = on_or_before
|
|
213
|
+
self._on_or_after = on_or_after
|
|
214
214
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
self.
|
|
222
|
-
self.on_or_after = on_or_after
|
|
215
|
+
@property
|
|
216
|
+
def field_name(self) -> str:
|
|
217
|
+
return self._field_name
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def on_or_before(self) -> Optional[datetime]:
|
|
221
|
+
return self._on_or_before
|
|
223
222
|
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
@property
|
|
224
|
+
def on_or_after(self) -> Optional[datetime]:
|
|
225
|
+
return self._on_or_after
|
|
226
226
|
|
|
227
|
+
def get_param_names(self) -> list[str]:
|
|
228
|
+
"""Get parameter names without storing them."""
|
|
229
|
+
names = []
|
|
227
230
|
if self.on_or_before:
|
|
228
|
-
|
|
231
|
+
names.append(f"{self.field_name}_on_or_before")
|
|
229
232
|
if self.on_or_after:
|
|
230
|
-
|
|
233
|
+
names.append(f"{self.field_name}_on_or_after")
|
|
234
|
+
return names
|
|
231
235
|
|
|
232
236
|
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
233
237
|
"""Extract filter parameters."""
|
|
234
238
|
named_parameters = {}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if self.
|
|
238
|
-
named_parameters[
|
|
239
|
+
param_names = self.get_param_names()
|
|
240
|
+
param_idx = 0
|
|
241
|
+
if self.on_or_before:
|
|
242
|
+
named_parameters[param_names[param_idx]] = self.on_or_before
|
|
243
|
+
param_idx += 1
|
|
244
|
+
if self.on_or_after:
|
|
245
|
+
named_parameters[param_names[param_idx]] = self.on_or_after
|
|
239
246
|
return [], named_parameters
|
|
240
247
|
|
|
241
248
|
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
242
249
|
conditions: list[Condition] = []
|
|
243
250
|
|
|
244
|
-
proposed_names =
|
|
245
|
-
if self.on_or_before and self._param_name_on_or_before:
|
|
246
|
-
proposed_names.append(self._param_name_on_or_before)
|
|
247
|
-
if self.on_or_after and self._param_name_on_or_after:
|
|
248
|
-
proposed_names.append(self._param_name_on_or_after)
|
|
249
|
-
|
|
251
|
+
proposed_names = self.get_param_names()
|
|
250
252
|
if not proposed_names:
|
|
251
253
|
return statement
|
|
252
254
|
|
|
@@ -254,7 +256,7 @@ class OnBeforeAfterFilter(StatementFilter):
|
|
|
254
256
|
|
|
255
257
|
param_idx = 0
|
|
256
258
|
result = statement
|
|
257
|
-
if self.on_or_before
|
|
259
|
+
if self.on_or_before:
|
|
258
260
|
before_param_name = resolved_names[param_idx]
|
|
259
261
|
param_idx += 1
|
|
260
262
|
conditions.append(
|
|
@@ -262,7 +264,7 @@ class OnBeforeAfterFilter(StatementFilter):
|
|
|
262
264
|
)
|
|
263
265
|
result = result.add_named_parameter(before_param_name, self.on_or_before)
|
|
264
266
|
|
|
265
|
-
if self.on_or_after
|
|
267
|
+
if self.on_or_after:
|
|
266
268
|
after_param_name = resolved_names[param_idx]
|
|
267
269
|
conditions.append(
|
|
268
270
|
exp.GTE(this=exp.column(self.field_name), expression=exp.Placeholder(this=after_param_name))
|
|
@@ -294,33 +296,33 @@ class InCollectionFilter(InAnyFilter[T]):
|
|
|
294
296
|
Constructs WHERE ... IN (...) clauses.
|
|
295
297
|
"""
|
|
296
298
|
|
|
297
|
-
__slots__ = ("
|
|
299
|
+
__slots__ = ("_field_name", "_values")
|
|
298
300
|
|
|
299
|
-
field_name: str
|
|
300
|
-
|
|
301
|
+
def __init__(self, field_name: str, values: Optional[abc.Collection[T]] = None) -> None:
|
|
302
|
+
self._field_name = field_name
|
|
303
|
+
self._values = values
|
|
301
304
|
|
|
302
|
-
|
|
303
|
-
|
|
305
|
+
@property
|
|
306
|
+
def field_name(self) -> str:
|
|
307
|
+
return self._field_name
|
|
304
308
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
however, if ``None``, the filter is not applied to the query, and all rows are returned.
|
|
309
|
-
"""
|
|
310
|
-
self.field_name = field_name
|
|
311
|
-
self.values = values
|
|
309
|
+
@property
|
|
310
|
+
def values(self) -> Optional[abc.Collection[T]]:
|
|
311
|
+
return self._values
|
|
312
312
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
313
|
+
def get_param_names(self) -> list[str]:
|
|
314
|
+
"""Get parameter names without storing them."""
|
|
315
|
+
if not self.values:
|
|
316
|
+
return []
|
|
317
|
+
return [f"{self.field_name}_in_{i}" for i, _ in enumerate(self.values)]
|
|
317
318
|
|
|
318
319
|
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
319
320
|
"""Extract filter parameters."""
|
|
320
321
|
named_parameters = {}
|
|
321
322
|
if self.values:
|
|
323
|
+
param_names = self.get_param_names()
|
|
322
324
|
for i, value in enumerate(self.values):
|
|
323
|
-
named_parameters[
|
|
325
|
+
named_parameters[param_names[i]] = value
|
|
324
326
|
return [], named_parameters
|
|
325
327
|
|
|
326
328
|
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
@@ -330,7 +332,7 @@ class InCollectionFilter(InAnyFilter[T]):
|
|
|
330
332
|
if not self.values:
|
|
331
333
|
return statement.where(exp.false())
|
|
332
334
|
|
|
333
|
-
resolved_names = self._resolve_parameter_conflicts(statement, self.
|
|
335
|
+
resolved_names = self._resolve_parameter_conflicts(statement, self.get_param_names())
|
|
334
336
|
|
|
335
337
|
placeholder_expressions: list[exp.Placeholder] = [
|
|
336
338
|
exp.Placeholder(this=param_name) for param_name in resolved_names
|
|
@@ -354,39 +356,41 @@ class NotInCollectionFilter(InAnyFilter[T]):
|
|
|
354
356
|
Constructs WHERE ... NOT IN (...) clauses.
|
|
355
357
|
"""
|
|
356
358
|
|
|
357
|
-
__slots__ = ("
|
|
359
|
+
__slots__ = ("_field_name", "_values")
|
|
358
360
|
|
|
359
|
-
field_name: str
|
|
360
|
-
|
|
361
|
+
def __init__(self, field_name: str, values: Optional[abc.Collection[T]] = None) -> None:
|
|
362
|
+
self._field_name = field_name
|
|
363
|
+
self._values = values
|
|
361
364
|
|
|
362
|
-
|
|
363
|
-
|
|
365
|
+
@property
|
|
366
|
+
def field_name(self) -> str:
|
|
367
|
+
return self._field_name
|
|
364
368
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
"""
|
|
369
|
-
self.field_name = field_name
|
|
370
|
-
self.values = values
|
|
369
|
+
@property
|
|
370
|
+
def values(self) -> Optional[abc.Collection[T]]:
|
|
371
|
+
return self._values
|
|
371
372
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
373
|
+
def get_param_names(self) -> list[str]:
|
|
374
|
+
"""Get parameter names without storing them."""
|
|
375
|
+
if not self.values:
|
|
376
|
+
return []
|
|
377
|
+
# Use object id to ensure uniqueness between instances
|
|
378
|
+
return [f"{self.field_name}_notin_{i}_{id(self)}" for i, _ in enumerate(self.values)]
|
|
376
379
|
|
|
377
380
|
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
378
381
|
"""Extract filter parameters."""
|
|
379
382
|
named_parameters = {}
|
|
380
383
|
if self.values:
|
|
384
|
+
param_names = self.get_param_names()
|
|
381
385
|
for i, value in enumerate(self.values):
|
|
382
|
-
named_parameters[
|
|
386
|
+
named_parameters[param_names[i]] = value
|
|
383
387
|
return [], named_parameters
|
|
384
388
|
|
|
385
389
|
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
386
390
|
if self.values is None or not self.values:
|
|
387
391
|
return statement
|
|
388
392
|
|
|
389
|
-
resolved_names = self._resolve_parameter_conflicts(statement, self.
|
|
393
|
+
resolved_names = self._resolve_parameter_conflicts(statement, self.get_param_names())
|
|
390
394
|
|
|
391
395
|
placeholder_expressions: list[exp.Placeholder] = [
|
|
392
396
|
exp.Placeholder(this=param_name) for param_name in resolved_names
|
|
@@ -412,34 +416,33 @@ class AnyCollectionFilter(InAnyFilter[T]):
|
|
|
412
416
|
Constructs WHERE column_name = ANY (array_expression) clauses.
|
|
413
417
|
"""
|
|
414
418
|
|
|
415
|
-
__slots__ = ("
|
|
419
|
+
__slots__ = ("_field_name", "_values")
|
|
416
420
|
|
|
417
|
-
field_name: str
|
|
418
|
-
|
|
421
|
+
def __init__(self, field_name: str, values: Optional[abc.Collection[T]] = None) -> None:
|
|
422
|
+
self._field_name = field_name
|
|
423
|
+
self._values = values
|
|
419
424
|
|
|
420
|
-
|
|
421
|
-
|
|
425
|
+
@property
|
|
426
|
+
def field_name(self) -> str:
|
|
427
|
+
return self._field_name
|
|
422
428
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
that is always false (no rows returned). If ``None``, the filter is not applied
|
|
427
|
-
to the query, and all rows are returned.
|
|
428
|
-
"""
|
|
429
|
-
self.field_name = field_name
|
|
430
|
-
self.values = values
|
|
429
|
+
@property
|
|
430
|
+
def values(self) -> Optional[abc.Collection[T]]:
|
|
431
|
+
return self._values
|
|
431
432
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
433
|
+
def get_param_names(self) -> list[str]:
|
|
434
|
+
"""Get parameter names without storing them."""
|
|
435
|
+
if not self.values:
|
|
436
|
+
return []
|
|
437
|
+
return [f"{self.field_name}_any_{i}" for i, _ in enumerate(self.values)]
|
|
436
438
|
|
|
437
439
|
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
438
440
|
"""Extract filter parameters."""
|
|
439
441
|
named_parameters = {}
|
|
440
442
|
if self.values:
|
|
443
|
+
param_names = self.get_param_names()
|
|
441
444
|
for i, value in enumerate(self.values):
|
|
442
|
-
named_parameters[
|
|
445
|
+
named_parameters[param_names[i]] = value
|
|
443
446
|
return [], named_parameters
|
|
444
447
|
|
|
445
448
|
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
@@ -449,7 +452,7 @@ class AnyCollectionFilter(InAnyFilter[T]):
|
|
|
449
452
|
if not self.values:
|
|
450
453
|
return statement.where(exp.false())
|
|
451
454
|
|
|
452
|
-
resolved_names = self._resolve_parameter_conflicts(statement, self.
|
|
455
|
+
resolved_names = self._resolve_parameter_conflicts(statement, self.get_param_names())
|
|
453
456
|
|
|
454
457
|
placeholder_expressions: list[exp.Expression] = [
|
|
455
458
|
exp.Placeholder(this=param_name) for param_name in resolved_names
|
|
@@ -474,38 +477,40 @@ class NotAnyCollectionFilter(InAnyFilter[T]):
|
|
|
474
477
|
Constructs WHERE NOT (column_name = ANY (array_expression)) clauses.
|
|
475
478
|
"""
|
|
476
479
|
|
|
477
|
-
__slots__ = ("
|
|
480
|
+
__slots__ = ("_field_name", "_values")
|
|
478
481
|
|
|
479
|
-
def __init__(self, field_name: str, values: Optional[abc.Collection[T]]) -> None:
|
|
480
|
-
|
|
482
|
+
def __init__(self, field_name: str, values: Optional[abc.Collection[T]] = None) -> None:
|
|
483
|
+
self._field_name = field_name
|
|
484
|
+
self._values = values
|
|
481
485
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
condition that is always true (all rows returned, filter effectively ignored).
|
|
486
|
-
If ``None``, the filter is not applied to the query, and all rows are returned.
|
|
487
|
-
"""
|
|
488
|
-
self.field_name = field_name
|
|
489
|
-
self.values = values
|
|
486
|
+
@property
|
|
487
|
+
def field_name(self) -> str:
|
|
488
|
+
return self._field_name
|
|
490
489
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
490
|
+
@property
|
|
491
|
+
def values(self) -> Optional[abc.Collection[T]]:
|
|
492
|
+
return self._values
|
|
493
|
+
|
|
494
|
+
def get_param_names(self) -> list[str]:
|
|
495
|
+
"""Get parameter names without storing them."""
|
|
496
|
+
if not self.values:
|
|
497
|
+
return []
|
|
498
|
+
return [f"{self.field_name}_not_any_{i}" for i, _ in enumerate(self.values)]
|
|
495
499
|
|
|
496
500
|
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
497
501
|
"""Extract filter parameters."""
|
|
498
502
|
named_parameters = {}
|
|
499
503
|
if self.values:
|
|
504
|
+
param_names = self.get_param_names()
|
|
500
505
|
for i, value in enumerate(self.values):
|
|
501
|
-
named_parameters[
|
|
506
|
+
named_parameters[param_names[i]] = value
|
|
502
507
|
return [], named_parameters
|
|
503
508
|
|
|
504
509
|
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
505
510
|
if self.values is None or not self.values:
|
|
506
511
|
return statement
|
|
507
512
|
|
|
508
|
-
resolved_names = self._resolve_parameter_conflicts(statement, self.
|
|
513
|
+
resolved_names = self._resolve_parameter_conflicts(statement, self.get_param_names())
|
|
509
514
|
|
|
510
515
|
placeholder_expressions: list[exp.Expression] = [
|
|
511
516
|
exp.Placeholder(this=param_name) for param_name in resolved_names
|
|
@@ -541,39 +546,40 @@ class LimitOffsetFilter(PaginationFilter):
|
|
|
541
546
|
Adds pagination support through LIMIT/OFFSET SQL clauses.
|
|
542
547
|
"""
|
|
543
548
|
|
|
544
|
-
__slots__ = ("
|
|
545
|
-
|
|
546
|
-
limit: int
|
|
547
|
-
offset: int
|
|
549
|
+
__slots__ = ("_limit", "_offset")
|
|
548
550
|
|
|
549
551
|
def __init__(self, limit: int, offset: int) -> None:
|
|
550
|
-
|
|
552
|
+
self._limit = limit
|
|
553
|
+
self._offset = offset
|
|
551
554
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
555
|
+
@property
|
|
556
|
+
def limit(self) -> int:
|
|
557
|
+
return self._limit
|
|
558
|
+
|
|
559
|
+
@property
|
|
560
|
+
def offset(self) -> int:
|
|
561
|
+
return self._offset
|
|
558
562
|
|
|
559
|
-
|
|
560
|
-
|
|
563
|
+
def get_param_names(self) -> list[str]:
|
|
564
|
+
"""Get parameter names without storing them."""
|
|
565
|
+
return ["limit", "offset"]
|
|
561
566
|
|
|
562
567
|
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
563
568
|
"""Extract filter parameters."""
|
|
564
|
-
|
|
569
|
+
param_names = self.get_param_names()
|
|
570
|
+
return [], {param_names[0]: self.limit, param_names[1]: self.offset}
|
|
565
571
|
|
|
566
572
|
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
567
|
-
resolved_names = self._resolve_parameter_conflicts(statement,
|
|
573
|
+
resolved_names = self._resolve_parameter_conflicts(statement, self.get_param_names())
|
|
568
574
|
limit_param_name, offset_param_name = resolved_names
|
|
569
575
|
|
|
570
576
|
limit_placeholder = exp.Placeholder(this=limit_param_name)
|
|
571
577
|
offset_placeholder = exp.Placeholder(this=offset_param_name)
|
|
572
578
|
|
|
573
579
|
try:
|
|
574
|
-
current_statement = sqlglot.parse_one(statement.
|
|
580
|
+
current_statement = sqlglot.parse_one(statement.raw_sql, dialect=statement.dialect)
|
|
575
581
|
except Exception:
|
|
576
|
-
current_statement = exp.Select().from_(f"({statement.
|
|
582
|
+
current_statement = exp.Select().from_(f"({statement.raw_sql})")
|
|
577
583
|
|
|
578
584
|
if isinstance(current_statement, exp.Select):
|
|
579
585
|
new_statement = current_statement.limit(limit_placeholder).offset(offset_placeholder)
|
|
@@ -596,20 +602,19 @@ class OrderByFilter(StatementFilter):
|
|
|
596
602
|
Adds sorting capability to SQL queries.
|
|
597
603
|
"""
|
|
598
604
|
|
|
599
|
-
__slots__ = ("
|
|
600
|
-
|
|
601
|
-
field_name: str
|
|
602
|
-
sort_order: Literal["asc", "desc"]
|
|
605
|
+
__slots__ = ("_field_name", "_sort_order")
|
|
603
606
|
|
|
604
607
|
def __init__(self, field_name: str, sort_order: Literal["asc", "desc"] = "asc") -> None:
|
|
605
|
-
|
|
608
|
+
self._field_name = field_name
|
|
609
|
+
self._sort_order = sort_order
|
|
606
610
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
611
|
+
@property
|
|
612
|
+
def field_name(self) -> str:
|
|
613
|
+
return self._field_name
|
|
614
|
+
|
|
615
|
+
@property
|
|
616
|
+
def sort_order(self) -> Literal["asc", "desc"]:
|
|
617
|
+
return self._sort_order # pyright: ignore
|
|
613
618
|
|
|
614
619
|
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
615
620
|
"""Extract filter parameters."""
|
|
@@ -623,12 +628,12 @@ class OrderByFilter(StatementFilter):
|
|
|
623
628
|
col_expr = exp.column(self.field_name)
|
|
624
629
|
order_expr = col_expr.desc() if converted_sort_order == "desc" else col_expr.asc()
|
|
625
630
|
|
|
626
|
-
if statement.
|
|
631
|
+
if statement.statement_expression is None:
|
|
627
632
|
new_statement = exp.Select().order_by(order_expr)
|
|
628
|
-
elif isinstance(statement.
|
|
629
|
-
new_statement = statement.
|
|
633
|
+
elif isinstance(statement.statement_expression, exp.Select):
|
|
634
|
+
new_statement = statement.statement_expression.order_by(order_expr)
|
|
630
635
|
else:
|
|
631
|
-
new_statement = exp.Select().from_(statement.
|
|
636
|
+
new_statement = exp.Select().from_(statement.statement_expression).order_by(order_expr)
|
|
632
637
|
|
|
633
638
|
return statement.copy(statement=new_statement)
|
|
634
639
|
|
|
@@ -643,44 +648,48 @@ class SearchFilter(StatementFilter):
|
|
|
643
648
|
Constructs WHERE field_name LIKE '%value%' clauses.
|
|
644
649
|
"""
|
|
645
650
|
|
|
646
|
-
__slots__ = ("
|
|
647
|
-
|
|
648
|
-
field_name: Union[str, set[str]]
|
|
649
|
-
value: str
|
|
650
|
-
ignore_case: Optional[bool]
|
|
651
|
+
__slots__ = ("_field_name", "_ignore_case", "_value")
|
|
651
652
|
|
|
652
653
|
def __init__(self, field_name: Union[str, set[str]], value: str, ignore_case: Optional[bool] = False) -> None:
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
654
|
+
self._field_name = field_name
|
|
655
|
+
self._value = value
|
|
656
|
+
self._ignore_case = ignore_case
|
|
657
|
+
|
|
658
|
+
@property
|
|
659
|
+
def field_name(self) -> Union[str, set[str]]:
|
|
660
|
+
return self._field_name
|
|
661
|
+
|
|
662
|
+
@property
|
|
663
|
+
def value(self) -> str:
|
|
664
|
+
return self._value
|
|
665
|
+
|
|
666
|
+
@property
|
|
667
|
+
def ignore_case(self) -> Optional[bool]:
|
|
668
|
+
return self._ignore_case
|
|
669
|
+
|
|
670
|
+
def get_param_name(self) -> Optional[str]:
|
|
671
|
+
"""Get parameter name without storing it."""
|
|
672
|
+
if not self.value:
|
|
673
|
+
return None
|
|
674
|
+
if isinstance(self.field_name, str):
|
|
675
|
+
return f"{self.field_name}_search"
|
|
676
|
+
return "search_value"
|
|
670
677
|
|
|
671
678
|
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
672
679
|
"""Extract filter parameters."""
|
|
673
680
|
named_parameters = {}
|
|
674
|
-
|
|
681
|
+
param_name = self.get_param_name()
|
|
682
|
+
if self.value and param_name:
|
|
675
683
|
search_value_with_wildcards = f"%{self.value}%"
|
|
676
|
-
named_parameters[
|
|
684
|
+
named_parameters[param_name] = search_value_with_wildcards
|
|
677
685
|
return [], named_parameters
|
|
678
686
|
|
|
679
687
|
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
680
|
-
|
|
688
|
+
param_name = self.get_param_name()
|
|
689
|
+
if not self.value or not param_name:
|
|
681
690
|
return statement
|
|
682
691
|
|
|
683
|
-
resolved_names = self._resolve_parameter_conflicts(statement, [
|
|
692
|
+
resolved_names = self._resolve_parameter_conflicts(statement, [param_name])
|
|
684
693
|
param_name = resolved_names[0]
|
|
685
694
|
|
|
686
695
|
pattern_expr = exp.Placeholder(this=param_name)
|
|
@@ -717,38 +726,29 @@ class NotInSearchFilter(SearchFilter):
|
|
|
717
726
|
Constructs WHERE field_name NOT LIKE '%value%' clauses.
|
|
718
727
|
"""
|
|
719
728
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
value: Search value.
|
|
728
|
-
ignore_case: Should the search be case insensitive.
|
|
729
|
-
"""
|
|
730
|
-
super().__init__(field_name, value, ignore_case)
|
|
731
|
-
|
|
732
|
-
self._param_name: Optional[str] = None
|
|
733
|
-
if self.value:
|
|
734
|
-
if isinstance(self.field_name, str):
|
|
735
|
-
self._param_name = f"{self.field_name}_not_search"
|
|
736
|
-
else:
|
|
737
|
-
self._param_name = "not_search_value"
|
|
729
|
+
def get_param_name(self) -> Optional[str]:
|
|
730
|
+
"""Get parameter name without storing it."""
|
|
731
|
+
if not self.value:
|
|
732
|
+
return None
|
|
733
|
+
if isinstance(self.field_name, str):
|
|
734
|
+
return f"{self.field_name}_not_search"
|
|
735
|
+
return "not_search_value"
|
|
738
736
|
|
|
739
737
|
def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
|
740
738
|
"""Extract filter parameters."""
|
|
741
739
|
named_parameters = {}
|
|
742
|
-
|
|
740
|
+
param_name = self.get_param_name()
|
|
741
|
+
if self.value and param_name:
|
|
743
742
|
search_value_with_wildcards = f"%{self.value}%"
|
|
744
|
-
named_parameters[
|
|
743
|
+
named_parameters[param_name] = search_value_with_wildcards
|
|
745
744
|
return [], named_parameters
|
|
746
745
|
|
|
747
746
|
def append_to_statement(self, statement: "SQL") -> "SQL":
|
|
748
|
-
|
|
747
|
+
param_name = self.get_param_name()
|
|
748
|
+
if not self.value or not param_name:
|
|
749
749
|
return statement
|
|
750
750
|
|
|
751
|
-
resolved_names = self._resolve_parameter_conflicts(statement, [
|
|
751
|
+
resolved_names = self._resolve_parameter_conflicts(statement, [param_name])
|
|
752
752
|
param_name = resolved_names[0]
|
|
753
753
|
|
|
754
754
|
pattern_expr = exp.Placeholder(this=param_name)
|
|
@@ -829,3 +829,36 @@ FilterTypes: TypeAlias = Union[
|
|
|
829
829
|
AnyCollectionFilter[Any],
|
|
830
830
|
NotAnyCollectionFilter[Any],
|
|
831
831
|
]
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def create_filters(filters: "list[StatementFilter]") -> tuple["StatementFilter", ...]:
|
|
835
|
+
"""Convert mutable filters to immutable tuple.
|
|
836
|
+
|
|
837
|
+
Since StatementFilter classes are now immutable (with read-only properties),
|
|
838
|
+
we just need to convert to a tuple for consistent sharing.
|
|
839
|
+
|
|
840
|
+
Args:
|
|
841
|
+
filters: List of StatementFilter objects (already immutable)
|
|
842
|
+
|
|
843
|
+
Returns:
|
|
844
|
+
Tuple of StatementFilter objects
|
|
845
|
+
"""
|
|
846
|
+
return tuple(filters)
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
def canonicalize_filters(filters: "list[StatementFilter]") -> tuple["StatementFilter", ...]:
|
|
850
|
+
"""Sort filters by type and field_name for consistent hashing.
|
|
851
|
+
|
|
852
|
+
Args:
|
|
853
|
+
filters: List of StatementFilter objects
|
|
854
|
+
|
|
855
|
+
Returns:
|
|
856
|
+
Canonically sorted tuple of filters
|
|
857
|
+
"""
|
|
858
|
+
|
|
859
|
+
def sort_key(f: "StatementFilter") -> tuple[str, str]:
|
|
860
|
+
class_name = type(f).__name__
|
|
861
|
+
field_name = getattr(f, "field_name", "")
|
|
862
|
+
return (class_name, str(field_name))
|
|
863
|
+
|
|
864
|
+
return tuple(sorted(filters, key=sort_key))
|