sqlspec 0.24.1__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 +11 -15
- 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.1.dist-info → sqlspec-0.25.0.dist-info}/METADATA +1 -1
- {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/RECORD +42 -42
- {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# ruff: noqa: PLR2004
|
|
2
|
+
# pyright: reportPrivateUsage=false, reportPrivateImportUsage=false
|
|
2
3
|
"""WHERE and HAVING clause mixins.
|
|
3
4
|
|
|
4
5
|
Provides mixins for WHERE and HAVING clause functionality with
|
|
@@ -14,7 +15,7 @@ from mypy_extensions import trait
|
|
|
14
15
|
from sqlglot import exp
|
|
15
16
|
from typing_extensions import Self
|
|
16
17
|
|
|
17
|
-
from sqlspec.builder._parsing_utils import parse_column_expression, parse_condition_expression
|
|
18
|
+
from sqlspec.builder._parsing_utils import extract_column_name, parse_column_expression, parse_condition_expression
|
|
18
19
|
from sqlspec.exceptions import SQLBuilderError
|
|
19
20
|
from sqlspec.utils.type_guards import (
|
|
20
21
|
has_expression_and_parameters,
|
|
@@ -24,31 +25,6 @@ from sqlspec.utils.type_guards import (
|
|
|
24
25
|
is_iterable_parameters,
|
|
25
26
|
)
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
def _extract_column_name(column: Union[str, exp.Column]) -> str:
|
|
29
|
-
"""Extract column name from column expression for parameter naming.
|
|
30
|
-
|
|
31
|
-
Args:
|
|
32
|
-
column: Column expression (string or SQLGlot Column)
|
|
33
|
-
|
|
34
|
-
Returns:
|
|
35
|
-
Column name as string for use as parameter name
|
|
36
|
-
"""
|
|
37
|
-
if isinstance(column, str):
|
|
38
|
-
# Handle simple column names and table.column references
|
|
39
|
-
if "." in column:
|
|
40
|
-
return column.split(".")[-1] # Return just the column part
|
|
41
|
-
return column
|
|
42
|
-
if isinstance(column, exp.Column):
|
|
43
|
-
# Extract the column name from SQLGlot Column expression
|
|
44
|
-
try:
|
|
45
|
-
return str(column.this.this)
|
|
46
|
-
except AttributeError:
|
|
47
|
-
return str(column.this) if column.this else "column"
|
|
48
|
-
# Fallback for any unexpected types (defensive programming)
|
|
49
|
-
return "column"
|
|
50
|
-
|
|
51
|
-
|
|
52
28
|
if TYPE_CHECKING:
|
|
53
29
|
from sqlspec.builder._column import ColumnExpression
|
|
54
30
|
from sqlspec.protocols import SQLBuilderProtocol
|
|
@@ -62,8 +38,9 @@ class WhereClauseMixin:
|
|
|
62
38
|
|
|
63
39
|
__slots__ = ()
|
|
64
40
|
|
|
65
|
-
# Type
|
|
66
|
-
|
|
41
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
42
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
43
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
67
44
|
|
|
68
45
|
def _create_parameterized_condition(
|
|
69
46
|
self,
|
|
@@ -82,7 +59,7 @@ class WhereClauseMixin:
|
|
|
82
59
|
The created condition expression
|
|
83
60
|
"""
|
|
84
61
|
builder = cast("SQLBuilderProtocol", self)
|
|
85
|
-
column_name =
|
|
62
|
+
column_name = extract_column_name(column)
|
|
86
63
|
param_name = builder._generate_unique_parameter_name(column_name)
|
|
87
64
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
88
65
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
@@ -118,18 +95,20 @@ class WhereClauseMixin:
|
|
|
118
95
|
Self with OR condition applied
|
|
119
96
|
"""
|
|
120
97
|
# Create a temporary clone to capture the condition
|
|
121
|
-
original_expr = self.
|
|
98
|
+
original_expr = self.get_expression()
|
|
122
99
|
|
|
123
100
|
# Apply the where method to get the condition
|
|
124
101
|
where_method(*args, **kwargs)
|
|
125
102
|
|
|
126
103
|
# Get the last condition added by extracting it from the modified expression
|
|
127
|
-
|
|
128
|
-
|
|
104
|
+
current_expr = self.get_expression()
|
|
105
|
+
if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)) and original_expr != current_expr:
|
|
106
|
+
last_where = current_expr.find(exp.Where)
|
|
129
107
|
if last_where and last_where.this:
|
|
130
108
|
condition = last_where.this
|
|
131
109
|
# Restore original expression
|
|
132
|
-
|
|
110
|
+
if original_expr is not None:
|
|
111
|
+
self.set_expression(original_expr)
|
|
133
112
|
# Apply as OR
|
|
134
113
|
return self.or_where(condition)
|
|
135
114
|
|
|
@@ -236,7 +215,7 @@ class WhereClauseMixin:
|
|
|
236
215
|
column_name_raw, operator, value = condition
|
|
237
216
|
operator = str(operator).upper()
|
|
238
217
|
column_exp = parse_column_expression(column_name_raw)
|
|
239
|
-
column_name =
|
|
218
|
+
column_name = extract_column_name(column_name_raw)
|
|
240
219
|
|
|
241
220
|
# Simple operators that use direct parameterization
|
|
242
221
|
simple_operators = {
|
|
@@ -299,16 +278,17 @@ class WhereClauseMixin:
|
|
|
299
278
|
Returns:
|
|
300
279
|
The current builder instance for method chaining.
|
|
301
280
|
"""
|
|
302
|
-
|
|
281
|
+
current_expr = self.get_expression()
|
|
282
|
+
if self.__class__.__name__ == "Update" and not isinstance(current_expr, exp.Update):
|
|
303
283
|
msg = "Cannot add WHERE clause to non-UPDATE expression"
|
|
304
284
|
raise SQLBuilderError(msg)
|
|
305
285
|
|
|
306
286
|
builder = cast("SQLBuilderProtocol", self)
|
|
307
|
-
if
|
|
287
|
+
if current_expr is None:
|
|
308
288
|
msg = "Cannot add WHERE clause: expression is not initialized."
|
|
309
289
|
raise SQLBuilderError(msg)
|
|
310
290
|
|
|
311
|
-
if isinstance(
|
|
291
|
+
if isinstance(current_expr, exp.Delete) and not current_expr.args.get("this"):
|
|
312
292
|
msg = "WHERE clause requires a table to be set. Use from() to set the table first."
|
|
313
293
|
raise SQLBuilderError(msg)
|
|
314
294
|
|
|
@@ -357,10 +337,11 @@ class WhereClauseMixin:
|
|
|
357
337
|
else:
|
|
358
338
|
where_expr = self._process_tuple_condition((condition, values[0]))
|
|
359
339
|
# Process this condition and skip the rest
|
|
360
|
-
if isinstance(
|
|
361
|
-
|
|
340
|
+
if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)):
|
|
341
|
+
updated_expr = current_expr.where(where_expr, copy=False)
|
|
342
|
+
self.set_expression(updated_expr)
|
|
362
343
|
else:
|
|
363
|
-
msg = f"WHERE clause not supported for {type(
|
|
344
|
+
msg = f"WHERE clause not supported for {type(current_expr).__name__}"
|
|
364
345
|
raise SQLBuilderError(msg)
|
|
365
346
|
return self
|
|
366
347
|
else:
|
|
@@ -400,10 +381,11 @@ class WhereClauseMixin:
|
|
|
400
381
|
msg = f"Unsupported condition type: {type(condition).__name__}"
|
|
401
382
|
raise SQLBuilderError(msg)
|
|
402
383
|
|
|
403
|
-
if isinstance(
|
|
404
|
-
|
|
384
|
+
if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)):
|
|
385
|
+
updated_expr = current_expr.where(where_expr, copy=False)
|
|
386
|
+
self.set_expression(updated_expr)
|
|
405
387
|
else:
|
|
406
|
-
msg = f"WHERE clause not supported for {type(
|
|
388
|
+
msg = f"WHERE clause not supported for {type(current_expr).__name__}"
|
|
407
389
|
raise SQLBuilderError(msg)
|
|
408
390
|
return self
|
|
409
391
|
|
|
@@ -448,7 +430,7 @@ class WhereClauseMixin:
|
|
|
448
430
|
def where_between(self, column: Union[str, exp.Column], low: Any, high: Any) -> Self:
|
|
449
431
|
"""Add WHERE column BETWEEN low AND high clause."""
|
|
450
432
|
builder = cast("SQLBuilderProtocol", self)
|
|
451
|
-
column_name =
|
|
433
|
+
column_name = extract_column_name(column)
|
|
452
434
|
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
453
435
|
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
454
436
|
_, low_param = builder.add_parameter(low, name=low_param)
|
|
@@ -460,7 +442,7 @@ class WhereClauseMixin:
|
|
|
460
442
|
def where_like(self, column: Union[str, exp.Column], pattern: str, escape: Optional[str] = None) -> Self:
|
|
461
443
|
"""Add WHERE column LIKE pattern clause."""
|
|
462
444
|
builder = cast("SQLBuilderProtocol", self)
|
|
463
|
-
column_name =
|
|
445
|
+
column_name = extract_column_name(column)
|
|
464
446
|
param_name = builder._generate_unique_parameter_name(column_name)
|
|
465
447
|
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
466
448
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
@@ -519,7 +501,7 @@ class WhereClauseMixin:
|
|
|
519
501
|
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
520
502
|
msg = "Unsupported type for 'values' in WHERE IN"
|
|
521
503
|
raise SQLBuilderError(msg)
|
|
522
|
-
column_name =
|
|
504
|
+
column_name = extract_column_name(column)
|
|
523
505
|
parameters = []
|
|
524
506
|
for i, v in enumerate(values):
|
|
525
507
|
if len(values) == 1:
|
|
@@ -548,7 +530,7 @@ class WhereClauseMixin:
|
|
|
548
530
|
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
549
531
|
msg = "Values for where_not_in must be a non-string iterable or subquery."
|
|
550
532
|
raise SQLBuilderError(msg)
|
|
551
|
-
column_name =
|
|
533
|
+
column_name = extract_column_name(column)
|
|
552
534
|
parameters = []
|
|
553
535
|
for i, v in enumerate(values):
|
|
554
536
|
if len(values) == 1:
|
|
@@ -638,7 +620,7 @@ class WhereClauseMixin:
|
|
|
638
620
|
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
639
621
|
msg = "Unsupported type for 'values' in WHERE ANY"
|
|
640
622
|
raise SQLBuilderError(msg)
|
|
641
|
-
column_name =
|
|
623
|
+
column_name = extract_column_name(column)
|
|
642
624
|
parameters = []
|
|
643
625
|
for i, v in enumerate(values):
|
|
644
626
|
if len(values) == 1:
|
|
@@ -678,7 +660,7 @@ class WhereClauseMixin:
|
|
|
678
660
|
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
679
661
|
msg = "Unsupported type for 'values' in WHERE NOT ANY"
|
|
680
662
|
raise SQLBuilderError(msg)
|
|
681
|
-
column_name =
|
|
663
|
+
column_name = extract_column_name(column)
|
|
682
664
|
parameters = []
|
|
683
665
|
for i, v in enumerate(values):
|
|
684
666
|
if len(values) == 1:
|
|
@@ -975,7 +957,7 @@ class WhereClauseMixin:
|
|
|
975
957
|
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
976
958
|
msg = "Unsupported type for 'values' in OR WHERE IN"
|
|
977
959
|
raise SQLBuilderError(msg)
|
|
978
|
-
column_name =
|
|
960
|
+
column_name = extract_column_name(column)
|
|
979
961
|
parameters = []
|
|
980
962
|
for i, v in enumerate(values):
|
|
981
963
|
if len(values) == 1:
|
|
@@ -990,7 +972,7 @@ class WhereClauseMixin:
|
|
|
990
972
|
def or_where_like(self, column: Union[str, exp.Column], pattern: str, escape: Optional[str] = None) -> Self:
|
|
991
973
|
"""Add OR column LIKE pattern clause."""
|
|
992
974
|
builder = cast("SQLBuilderProtocol", self)
|
|
993
|
-
column_name =
|
|
975
|
+
column_name = extract_column_name(column)
|
|
994
976
|
param_name = builder._generate_unique_parameter_name(column_name)
|
|
995
977
|
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
996
978
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
@@ -1044,7 +1026,7 @@ class WhereClauseMixin:
|
|
|
1044
1026
|
def or_where_between(self, column: Union[str, exp.Column], low: Any, high: Any) -> Self:
|
|
1045
1027
|
"""Add OR column BETWEEN low AND high clause."""
|
|
1046
1028
|
builder = cast("SQLBuilderProtocol", self)
|
|
1047
|
-
column_name =
|
|
1029
|
+
column_name = extract_column_name(column)
|
|
1048
1030
|
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
1049
1031
|
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
1050
1032
|
_, low_param = builder.add_parameter(low, name=low_param)
|
|
@@ -1092,7 +1074,7 @@ class WhereClauseMixin:
|
|
|
1092
1074
|
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
1093
1075
|
msg = "Values for or_where_not_in must be a non-string iterable or subquery."
|
|
1094
1076
|
raise SQLBuilderError(msg)
|
|
1095
|
-
column_name =
|
|
1077
|
+
column_name = extract_column_name(column)
|
|
1096
1078
|
parameters = []
|
|
1097
1079
|
for i, v in enumerate(values):
|
|
1098
1080
|
if len(values) == 1:
|
|
@@ -1216,7 +1198,7 @@ class WhereClauseMixin:
|
|
|
1216
1198
|
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
1217
1199
|
msg = "Unsupported type for 'values' in OR WHERE ANY"
|
|
1218
1200
|
raise SQLBuilderError(msg)
|
|
1219
|
-
column_name =
|
|
1201
|
+
column_name = extract_column_name(column)
|
|
1220
1202
|
parameters = []
|
|
1221
1203
|
for i, v in enumerate(values):
|
|
1222
1204
|
if len(values) == 1:
|
|
@@ -1272,7 +1254,7 @@ class WhereClauseMixin:
|
|
|
1272
1254
|
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
1273
1255
|
msg = "Unsupported type for 'values' in OR WHERE NOT ANY"
|
|
1274
1256
|
raise SQLBuilderError(msg)
|
|
1275
|
-
column_name =
|
|
1257
|
+
column_name = extract_column_name(column)
|
|
1276
1258
|
parameters = []
|
|
1277
1259
|
for i, v in enumerate(values):
|
|
1278
1260
|
if len(values) == 1:
|
|
@@ -1292,7 +1274,9 @@ class HavingClauseMixin:
|
|
|
1292
1274
|
|
|
1293
1275
|
__slots__ = ()
|
|
1294
1276
|
|
|
1295
|
-
|
|
1277
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
1278
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
1279
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
1296
1280
|
|
|
1297
1281
|
def having(self, condition: Union[str, exp.Expression]) -> Self:
|
|
1298
1282
|
"""Add HAVING clause.
|
|
@@ -1306,11 +1290,15 @@ class HavingClauseMixin:
|
|
|
1306
1290
|
Returns:
|
|
1307
1291
|
The current builder instance for method chaining.
|
|
1308
1292
|
"""
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1293
|
+
current_expr = self.get_expression()
|
|
1294
|
+
if current_expr is None:
|
|
1295
|
+
self.set_expression(exp.Select())
|
|
1296
|
+
current_expr = self.get_expression()
|
|
1297
|
+
|
|
1298
|
+
if not isinstance(current_expr, exp.Select):
|
|
1312
1299
|
msg = "Cannot add HAVING to a non-SELECT expression."
|
|
1313
1300
|
raise SQLBuilderError(msg)
|
|
1314
1301
|
having_expr = exp.condition(condition) if isinstance(condition, str) else condition
|
|
1315
|
-
|
|
1302
|
+
updated_expr = current_expr.having(having_expr, copy=False)
|
|
1303
|
+
self.set_expression(updated_expr)
|
|
1316
1304
|
return self
|
sqlspec/core/__init__.py
CHANGED
|
@@ -90,7 +90,7 @@ Example Usage:
|
|
|
90
90
|
"""
|
|
91
91
|
|
|
92
92
|
from sqlspec.core import filters
|
|
93
|
-
from sqlspec.core.cache import CacheConfig, CacheStats, UnifiedCache,
|
|
93
|
+
from sqlspec.core.cache import CacheConfig, CacheStats, MultiLevelCache, UnifiedCache, get_cache
|
|
94
94
|
from sqlspec.core.compiler import OperationType, SQLProcessor
|
|
95
95
|
from sqlspec.core.filters import StatementFilter
|
|
96
96
|
from sqlspec.core.hashing import (
|
|
@@ -115,6 +115,7 @@ __all__ = (
|
|
|
115
115
|
"ArrowResult",
|
|
116
116
|
"CacheConfig",
|
|
117
117
|
"CacheStats",
|
|
118
|
+
"MultiLevelCache",
|
|
118
119
|
"OperationType",
|
|
119
120
|
"ParameterConverter",
|
|
120
121
|
"ParameterProcessor",
|
|
@@ -129,7 +130,7 @@ __all__ = (
|
|
|
129
130
|
"TypedParameter",
|
|
130
131
|
"UnifiedCache",
|
|
131
132
|
"filters",
|
|
132
|
-
"
|
|
133
|
+
"get_cache",
|
|
133
134
|
"hash_expression",
|
|
134
135
|
"hash_expression_node",
|
|
135
136
|
"hash_optimized_expression",
|