sqlspec 0.16.0__py3-none-any.whl → 0.16.1__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 +448 -15
- sqlspec/builder/_base.py +77 -44
- sqlspec/builder/_column.py +0 -4
- sqlspec/builder/_ddl.py +15 -52
- sqlspec/builder/_ddl_utils.py +0 -1
- sqlspec/builder/_delete.py +4 -5
- sqlspec/builder/_insert.py +59 -44
- sqlspec/builder/_merge.py +17 -2
- sqlspec/builder/_parsing_utils.py +11 -11
- sqlspec/builder/_select.py +29 -33
- sqlspec/builder/_update.py +4 -2
- sqlspec/builder/mixins/_cte_and_set_ops.py +47 -20
- sqlspec/builder/mixins/_delete_operations.py +6 -1
- sqlspec/builder/mixins/_insert_operations.py +126 -34
- sqlspec/builder/mixins/_join_operations.py +11 -4
- sqlspec/builder/mixins/_merge_operations.py +81 -21
- sqlspec/builder/mixins/_order_limit_operations.py +15 -3
- sqlspec/builder/mixins/_pivot_operations.py +11 -2
- sqlspec/builder/mixins/_select_operations.py +12 -8
- sqlspec/builder/mixins/_update_operations.py +37 -14
- sqlspec/builder/mixins/_where_clause.py +55 -43
- sqlspec/core/cache.py +26 -28
- sqlspec/core/compiler.py +58 -37
- sqlspec/core/parameters.py +80 -52
- sqlspec/core/result.py +30 -17
- sqlspec/core/statement.py +31 -21
- sqlspec/driver/_async.py +76 -46
- sqlspec/driver/_common.py +25 -6
- sqlspec/driver/_sync.py +73 -43
- sqlspec/driver/mixins/_result_tools.py +51 -22
- sqlspec/driver/mixins/_sql_translator.py +61 -11
- sqlspec/protocols.py +7 -0
- sqlspec/utils/type_guards.py +7 -3
- {sqlspec-0.16.0.dist-info → sqlspec-0.16.1.dist-info}/METADATA +1 -1
- {sqlspec-0.16.0.dist-info → sqlspec-0.16.1.dist-info}/RECORD +39 -39
- {sqlspec-0.16.0.dist-info → sqlspec-0.16.1.dist-info}/WHEEL +0 -0
- {sqlspec-0.16.0.dist-info → sqlspec-0.16.1.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.16.0.dist-info → sqlspec-0.16.1.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.16.0.dist-info → sqlspec-0.16.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from collections.abc import Mapping
|
|
4
4
|
from typing import Any, Optional, Union
|
|
5
5
|
|
|
6
|
+
from mypy_extensions import trait
|
|
6
7
|
from sqlglot import exp
|
|
7
8
|
from typing_extensions import Self
|
|
8
9
|
|
|
@@ -14,10 +15,14 @@ __all__ = ("UpdateFromClauseMixin", "UpdateSetClauseMixin", "UpdateTableClauseMi
|
|
|
14
15
|
MIN_SET_ARGS = 2
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
@trait
|
|
17
19
|
class UpdateTableClauseMixin:
|
|
18
20
|
"""Mixin providing TABLE clause for UPDATE builders."""
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
__slots__ = ()
|
|
23
|
+
|
|
24
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
25
|
+
_expression: Optional[exp.Expression]
|
|
21
26
|
|
|
22
27
|
def table(self, table_name: str, alias: Optional[str] = None) -> Self:
|
|
23
28
|
"""Set the table to update.
|
|
@@ -37,10 +42,24 @@ class UpdateTableClauseMixin:
|
|
|
37
42
|
return self
|
|
38
43
|
|
|
39
44
|
|
|
45
|
+
@trait
|
|
40
46
|
class UpdateSetClauseMixin:
|
|
41
47
|
"""Mixin providing SET clause for UPDATE builders."""
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
__slots__ = ()
|
|
50
|
+
|
|
51
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
52
|
+
_expression: Optional[exp.Expression]
|
|
53
|
+
|
|
54
|
+
def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
|
|
55
|
+
"""Add parameter - provided by QueryBuilder."""
|
|
56
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
57
|
+
raise NotImplementedError(msg)
|
|
58
|
+
|
|
59
|
+
def _generate_unique_parameter_name(self, base_name: str) -> str:
|
|
60
|
+
"""Generate unique parameter name - provided by QueryBuilder."""
|
|
61
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
62
|
+
raise NotImplementedError(msg)
|
|
44
63
|
|
|
45
64
|
def set(self, *args: Any, **kwargs: Any) -> Self:
|
|
46
65
|
"""Set columns and values for the UPDATE statement.
|
|
@@ -79,14 +98,13 @@ class UpdateSetClauseMixin:
|
|
|
79
98
|
value_expr = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect", None)))
|
|
80
99
|
if has_query_builder_parameters(val):
|
|
81
100
|
for p_name, p_value in val.parameters.items():
|
|
82
|
-
self.add_parameter(p_value, name=p_name)
|
|
101
|
+
self.add_parameter(p_value, name=p_name)
|
|
83
102
|
else:
|
|
84
103
|
column_name = col if isinstance(col, str) else str(col)
|
|
85
|
-
# Extract just the column part if table.column format
|
|
86
104
|
if "." in column_name:
|
|
87
105
|
column_name = column_name.split(".")[-1]
|
|
88
|
-
param_name = self._generate_unique_parameter_name(column_name)
|
|
89
|
-
param_name = self.add_parameter(val, name=param_name)[1]
|
|
106
|
+
param_name = self._generate_unique_parameter_name(column_name)
|
|
107
|
+
param_name = self.add_parameter(val, name=param_name)[1]
|
|
90
108
|
value_expr = exp.Placeholder(this=param_name)
|
|
91
109
|
assignments.append(exp.EQ(this=col_expr, expression=value_expr))
|
|
92
110
|
elif (len(args) == 1 and isinstance(args[0], Mapping)) or kwargs:
|
|
@@ -100,14 +118,13 @@ class UpdateSetClauseMixin:
|
|
|
100
118
|
value_expr = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect", None)))
|
|
101
119
|
if has_query_builder_parameters(val):
|
|
102
120
|
for p_name, p_value in val.parameters.items():
|
|
103
|
-
self.add_parameter(p_value, name=p_name)
|
|
121
|
+
self.add_parameter(p_value, name=p_name)
|
|
104
122
|
else:
|
|
105
|
-
# Extract column name for parameter naming
|
|
106
123
|
column_name = col if isinstance(col, str) else str(col)
|
|
107
124
|
if "." in column_name:
|
|
108
125
|
column_name = column_name.split(".")[-1]
|
|
109
|
-
param_name = self._generate_unique_parameter_name(column_name)
|
|
110
|
-
param_name = self.add_parameter(val, name=param_name)[1]
|
|
126
|
+
param_name = self._generate_unique_parameter_name(column_name)
|
|
127
|
+
param_name = self.add_parameter(val, name=param_name)[1]
|
|
111
128
|
value_expr = exp.Placeholder(this=param_name)
|
|
112
129
|
assignments.append(exp.EQ(this=exp.column(col), expression=value_expr))
|
|
113
130
|
else:
|
|
@@ -118,9 +135,15 @@ class UpdateSetClauseMixin:
|
|
|
118
135
|
return self
|
|
119
136
|
|
|
120
137
|
|
|
138
|
+
@trait
|
|
121
139
|
class UpdateFromClauseMixin:
|
|
122
140
|
"""Mixin providing FROM clause for UPDATE builders (e.g., PostgreSQL style)."""
|
|
123
141
|
|
|
142
|
+
__slots__ = ()
|
|
143
|
+
|
|
144
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
145
|
+
_expression: Optional[exp.Expression]
|
|
146
|
+
|
|
124
147
|
def from_(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
|
|
125
148
|
"""Add a FROM clause to the UPDATE statement.
|
|
126
149
|
|
|
@@ -134,7 +157,7 @@ class UpdateFromClauseMixin:
|
|
|
134
157
|
Raises:
|
|
135
158
|
SQLBuilderError: If the current expression is not an UPDATE statement.
|
|
136
159
|
"""
|
|
137
|
-
if self._expression is None or not isinstance(self._expression, exp.Update):
|
|
160
|
+
if self._expression is None or not isinstance(self._expression, exp.Update):
|
|
138
161
|
msg = "Cannot add FROM clause to non-UPDATE expression. Set the main table first."
|
|
139
162
|
raise SQLBuilderError(msg)
|
|
140
163
|
table_expr: exp.Expression
|
|
@@ -152,9 +175,9 @@ class UpdateFromClauseMixin:
|
|
|
152
175
|
else:
|
|
153
176
|
msg = f"Unsupported table type for FROM clause: {type(table)}"
|
|
154
177
|
raise SQLBuilderError(msg)
|
|
155
|
-
if self._expression.args.get("from") is None:
|
|
156
|
-
self._expression.set("from", exp.From(expressions=[]))
|
|
157
|
-
from_clause = self._expression.args["from"]
|
|
178
|
+
if self._expression.args.get("from") is None:
|
|
179
|
+
self._expression.set("from", exp.From(expressions=[]))
|
|
180
|
+
from_clause = self._expression.args["from"]
|
|
158
181
|
if hasattr(from_clause, "append"):
|
|
159
182
|
from_clause.append("expressions", table_expr)
|
|
160
183
|
else:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
5
5
|
|
|
6
|
+
from mypy_extensions import trait
|
|
6
7
|
from sqlglot import exp
|
|
7
8
|
from typing_extensions import Self
|
|
8
9
|
|
|
@@ -27,9 +28,10 @@ def _extract_column_name(column: Union[str, exp.Column]) -> str:
|
|
|
27
28
|
return column
|
|
28
29
|
if isinstance(column, exp.Column):
|
|
29
30
|
# Extract the column name from SQLGlot Column expression
|
|
30
|
-
|
|
31
|
+
try:
|
|
31
32
|
return str(column.this.this)
|
|
32
|
-
|
|
33
|
+
except AttributeError:
|
|
34
|
+
return str(column.this) if column.this else "column"
|
|
33
35
|
return "column"
|
|
34
36
|
|
|
35
37
|
|
|
@@ -40,9 +42,15 @@ if TYPE_CHECKING:
|
|
|
40
42
|
__all__ = ("HavingClauseMixin", "WhereClauseMixin")
|
|
41
43
|
|
|
42
44
|
|
|
45
|
+
@trait
|
|
43
46
|
class WhereClauseMixin:
|
|
44
47
|
"""Mixin providing WHERE clause methods for SELECT, UPDATE, and DELETE builders."""
|
|
45
48
|
|
|
49
|
+
__slots__ = ()
|
|
50
|
+
|
|
51
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
52
|
+
_expression: Optional[exp.Expression]
|
|
53
|
+
|
|
46
54
|
def _handle_in_operator(
|
|
47
55
|
self, column_exp: exp.Expression, value: Any, column_name: str = "column"
|
|
48
56
|
) -> exp.Expression:
|
|
@@ -52,13 +60,13 @@ class WhereClauseMixin:
|
|
|
52
60
|
placeholders = []
|
|
53
61
|
for i, v in enumerate(value):
|
|
54
62
|
if len(value) == 1:
|
|
55
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
63
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
56
64
|
else:
|
|
57
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
65
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
58
66
|
_, param_name = builder.add_parameter(v, name=param_name)
|
|
59
67
|
placeholders.append(exp.Placeholder(this=param_name))
|
|
60
68
|
return exp.In(this=column_exp, expressions=placeholders)
|
|
61
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
69
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
62
70
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
63
71
|
return exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)])
|
|
64
72
|
|
|
@@ -71,13 +79,13 @@ class WhereClauseMixin:
|
|
|
71
79
|
placeholders = []
|
|
72
80
|
for i, v in enumerate(value):
|
|
73
81
|
if len(value) == 1:
|
|
74
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
82
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
75
83
|
else:
|
|
76
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
84
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
77
85
|
_, param_name = builder.add_parameter(v, name=param_name)
|
|
78
86
|
placeholders.append(exp.Placeholder(this=param_name))
|
|
79
87
|
return exp.Not(this=exp.In(this=column_exp, expressions=placeholders))
|
|
80
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
88
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
81
89
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
82
90
|
return exp.Not(this=exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)]))
|
|
83
91
|
|
|
@@ -98,8 +106,8 @@ class WhereClauseMixin:
|
|
|
98
106
|
if is_iterable_parameters(value) and len(value) == 2:
|
|
99
107
|
builder = cast("SQLBuilderProtocol", self)
|
|
100
108
|
low, high = value
|
|
101
|
-
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
102
|
-
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
109
|
+
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
110
|
+
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
103
111
|
_, low_param = builder.add_parameter(low, name=low_param)
|
|
104
112
|
_, high_param = builder.add_parameter(high, name=high_param)
|
|
105
113
|
return exp.Between(
|
|
@@ -115,8 +123,8 @@ class WhereClauseMixin:
|
|
|
115
123
|
if is_iterable_parameters(value) and len(value) == 2:
|
|
116
124
|
builder = cast("SQLBuilderProtocol", self)
|
|
117
125
|
low, high = value
|
|
118
|
-
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
119
|
-
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
126
|
+
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
127
|
+
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
120
128
|
_, low_param = builder.add_parameter(low, name=low_param)
|
|
121
129
|
_, high_param = builder.add_parameter(high, name=high_param)
|
|
122
130
|
return exp.Not(
|
|
@@ -137,7 +145,7 @@ class WhereClauseMixin:
|
|
|
137
145
|
if len(condition) == 2:
|
|
138
146
|
# (column, value) tuple for equality
|
|
139
147
|
value = condition[1]
|
|
140
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
148
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
141
149
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
142
150
|
return exp.EQ(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
143
151
|
|
|
@@ -147,35 +155,35 @@ class WhereClauseMixin:
|
|
|
147
155
|
value = condition[2]
|
|
148
156
|
|
|
149
157
|
if operator == "=":
|
|
150
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
158
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
151
159
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
152
160
|
return exp.EQ(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
153
161
|
if operator in {"!=", "<>"}:
|
|
154
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
162
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
155
163
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
156
164
|
return exp.NEQ(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
157
165
|
if operator == ">":
|
|
158
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
166
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
159
167
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
160
168
|
return exp.GT(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
161
169
|
if operator == ">=":
|
|
162
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
170
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
163
171
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
164
172
|
return exp.GTE(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
165
173
|
if operator == "<":
|
|
166
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
174
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
167
175
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
168
176
|
return exp.LT(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
169
177
|
if operator == "<=":
|
|
170
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
178
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
171
179
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
172
180
|
return exp.LTE(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
173
181
|
if operator == "LIKE":
|
|
174
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
182
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
175
183
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
176
184
|
return exp.Like(this=column_exp, expression=exp.Placeholder(this=param_name))
|
|
177
185
|
if operator == "NOT LIKE":
|
|
178
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
186
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
179
187
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
180
188
|
return exp.Not(this=exp.Like(this=column_exp, expression=exp.Placeholder(this=param_name)))
|
|
181
189
|
|
|
@@ -222,7 +230,7 @@ class WhereClauseMixin:
|
|
|
222
230
|
Returns:
|
|
223
231
|
The current builder instance for method chaining.
|
|
224
232
|
"""
|
|
225
|
-
if self.__class__.__name__ == "Update" and not isinstance(self._expression, exp.Update):
|
|
233
|
+
if self.__class__.__name__ == "Update" and not isinstance(self._expression, exp.Update):
|
|
226
234
|
msg = "Cannot add WHERE clause to non-UPDATE expression"
|
|
227
235
|
raise SQLBuilderError(msg)
|
|
228
236
|
|
|
@@ -274,7 +282,7 @@ class WhereClauseMixin:
|
|
|
274
282
|
"""Add WHERE column = value clause."""
|
|
275
283
|
builder = cast("SQLBuilderProtocol", self)
|
|
276
284
|
column_name = _extract_column_name(column)
|
|
277
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
285
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
278
286
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
279
287
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
280
288
|
condition: exp.Expression = col_expr.eq(exp.Placeholder(this=param_name))
|
|
@@ -284,7 +292,7 @@ class WhereClauseMixin:
|
|
|
284
292
|
"""Add WHERE column != value clause."""
|
|
285
293
|
builder = cast("SQLBuilderProtocol", self)
|
|
286
294
|
column_name = _extract_column_name(column)
|
|
287
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
295
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
288
296
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
289
297
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
290
298
|
condition: exp.Expression = col_expr.neq(exp.Placeholder(this=param_name))
|
|
@@ -294,7 +302,7 @@ class WhereClauseMixin:
|
|
|
294
302
|
"""Add WHERE column < value clause."""
|
|
295
303
|
builder = cast("SQLBuilderProtocol", self)
|
|
296
304
|
column_name = _extract_column_name(column)
|
|
297
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
305
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
298
306
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
299
307
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
300
308
|
condition: exp.Expression = exp.LT(this=col_expr, expression=exp.Placeholder(this=param_name))
|
|
@@ -304,7 +312,7 @@ class WhereClauseMixin:
|
|
|
304
312
|
"""Add WHERE column <= value clause."""
|
|
305
313
|
builder = cast("SQLBuilderProtocol", self)
|
|
306
314
|
column_name = _extract_column_name(column)
|
|
307
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
315
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
308
316
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
309
317
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
310
318
|
condition: exp.Expression = exp.LTE(this=col_expr, expression=exp.Placeholder(this=param_name))
|
|
@@ -314,7 +322,7 @@ class WhereClauseMixin:
|
|
|
314
322
|
"""Add WHERE column > value clause."""
|
|
315
323
|
builder = cast("SQLBuilderProtocol", self)
|
|
316
324
|
column_name = _extract_column_name(column)
|
|
317
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
325
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
318
326
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
319
327
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
320
328
|
condition: exp.Expression = exp.GT(this=col_expr, expression=exp.Placeholder(this=param_name))
|
|
@@ -324,7 +332,7 @@ class WhereClauseMixin:
|
|
|
324
332
|
"""Add WHERE column >= value clause."""
|
|
325
333
|
builder = cast("SQLBuilderProtocol", self)
|
|
326
334
|
column_name = _extract_column_name(column)
|
|
327
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
335
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
328
336
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
329
337
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
330
338
|
condition: exp.Expression = exp.GTE(this=col_expr, expression=exp.Placeholder(this=param_name))
|
|
@@ -334,8 +342,8 @@ class WhereClauseMixin:
|
|
|
334
342
|
"""Add WHERE column BETWEEN low AND high clause."""
|
|
335
343
|
builder = cast("SQLBuilderProtocol", self)
|
|
336
344
|
column_name = _extract_column_name(column)
|
|
337
|
-
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
338
|
-
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
345
|
+
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
346
|
+
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
339
347
|
_, low_param = builder.add_parameter(low, name=low_param)
|
|
340
348
|
_, high_param = builder.add_parameter(high, name=high_param)
|
|
341
349
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
@@ -346,7 +354,7 @@ class WhereClauseMixin:
|
|
|
346
354
|
"""Add WHERE column LIKE pattern clause."""
|
|
347
355
|
builder = cast("SQLBuilderProtocol", self)
|
|
348
356
|
column_name = _extract_column_name(column)
|
|
349
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
357
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
350
358
|
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
351
359
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
352
360
|
if escape is not None:
|
|
@@ -360,7 +368,7 @@ class WhereClauseMixin:
|
|
|
360
368
|
"""Add WHERE column NOT LIKE pattern clause."""
|
|
361
369
|
builder = cast("SQLBuilderProtocol", self)
|
|
362
370
|
column_name = _extract_column_name(column)
|
|
363
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
371
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
364
372
|
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
365
373
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
366
374
|
condition: exp.Expression = col_expr.like(exp.Placeholder(this=param_name)).not_()
|
|
@@ -370,7 +378,7 @@ class WhereClauseMixin:
|
|
|
370
378
|
"""Add WHERE column ILIKE pattern clause."""
|
|
371
379
|
builder = cast("SQLBuilderProtocol", self)
|
|
372
380
|
column_name = _extract_column_name(column)
|
|
373
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
381
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
374
382
|
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
375
383
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
376
384
|
condition: exp.Expression = col_expr.ilike(exp.Placeholder(this=param_name))
|
|
@@ -399,7 +407,7 @@ class WhereClauseMixin:
|
|
|
399
407
|
sql_str = subquery.sql
|
|
400
408
|
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=builder.dialect_name)) # pyright: ignore
|
|
401
409
|
# Merge subquery parameters into parent builder
|
|
402
|
-
if hasattr(subquery, "parameters"):
|
|
410
|
+
if hasattr(subquery, "parameters") and isinstance(subquery.parameters, dict): # pyright: ignore[reportAttributeAccessIssue]
|
|
403
411
|
for param_name, param_value in subquery.parameters.items(): # pyright: ignore[reportAttributeAccessIssue]
|
|
404
412
|
builder.add_parameter(param_value, name=param_name)
|
|
405
413
|
else:
|
|
@@ -413,9 +421,9 @@ class WhereClauseMixin:
|
|
|
413
421
|
parameters = []
|
|
414
422
|
for i, v in enumerate(values):
|
|
415
423
|
if len(values) == 1:
|
|
416
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
424
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
417
425
|
else:
|
|
418
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
426
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
419
427
|
_, param_name = builder.add_parameter(v, name=param_name)
|
|
420
428
|
parameters.append(exp.Placeholder(this=param_name))
|
|
421
429
|
condition = col_expr.isin(*parameters)
|
|
@@ -442,9 +450,9 @@ class WhereClauseMixin:
|
|
|
442
450
|
parameters = []
|
|
443
451
|
for i, v in enumerate(values):
|
|
444
452
|
if len(values) == 1:
|
|
445
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
453
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
446
454
|
else:
|
|
447
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
455
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
|
|
448
456
|
_, param_name = builder.add_parameter(v, name=param_name)
|
|
449
457
|
parameters.append(exp.Placeholder(this=param_name))
|
|
450
458
|
condition = exp.Not(this=col_expr.isin(*parameters))
|
|
@@ -532,9 +540,9 @@ class WhereClauseMixin:
|
|
|
532
540
|
parameters = []
|
|
533
541
|
for i, v in enumerate(values):
|
|
534
542
|
if len(values) == 1:
|
|
535
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
543
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
536
544
|
else:
|
|
537
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_any_{i + 1}")
|
|
545
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_any_{i + 1}")
|
|
538
546
|
_, param_name = builder.add_parameter(v, name=param_name)
|
|
539
547
|
parameters.append(exp.Placeholder(this=param_name))
|
|
540
548
|
tuple_expr = exp.Tuple(expressions=parameters)
|
|
@@ -572,9 +580,9 @@ class WhereClauseMixin:
|
|
|
572
580
|
parameters = []
|
|
573
581
|
for i, v in enumerate(values):
|
|
574
582
|
if len(values) == 1:
|
|
575
|
-
param_name = builder._generate_unique_parameter_name(column_name)
|
|
583
|
+
param_name = builder._generate_unique_parameter_name(column_name)
|
|
576
584
|
else:
|
|
577
|
-
param_name = builder._generate_unique_parameter_name(f"{column_name}_not_any_{i + 1}")
|
|
585
|
+
param_name = builder._generate_unique_parameter_name(f"{column_name}_not_any_{i + 1}")
|
|
578
586
|
_, param_name = builder.add_parameter(v, name=param_name)
|
|
579
587
|
parameters.append(exp.Placeholder(this=param_name))
|
|
580
588
|
tuple_expr = exp.Tuple(expressions=parameters)
|
|
@@ -582,10 +590,14 @@ class WhereClauseMixin:
|
|
|
582
590
|
return self.where(condition)
|
|
583
591
|
|
|
584
592
|
|
|
593
|
+
@trait
|
|
585
594
|
class HavingClauseMixin:
|
|
586
595
|
"""Mixin providing HAVING clause for SELECT builders."""
|
|
587
596
|
|
|
588
|
-
|
|
597
|
+
__slots__ = ()
|
|
598
|
+
|
|
599
|
+
# Type annotation for PyRight - this will be provided by the base class
|
|
600
|
+
_expression: Optional[exp.Expression]
|
|
589
601
|
|
|
590
602
|
def having(self, condition: Union[str, exp.Expression]) -> Self:
|
|
591
603
|
"""Add HAVING clause.
|
sqlspec/core/cache.py
CHANGED
|
@@ -245,19 +245,21 @@ class UnifiedCache(Generic[CacheValueT]):
|
|
|
245
245
|
Cached value or None if not found or expired
|
|
246
246
|
"""
|
|
247
247
|
with self._lock:
|
|
248
|
-
node
|
|
248
|
+
node = self._cache.get(key)
|
|
249
249
|
if node is None:
|
|
250
250
|
self._stats.record_miss()
|
|
251
251
|
return None
|
|
252
252
|
|
|
253
|
-
|
|
254
|
-
ttl
|
|
255
|
-
if ttl is not None
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
253
|
+
# Optimize TTL check with early variable assignment
|
|
254
|
+
ttl = self._ttl
|
|
255
|
+
if ttl is not None:
|
|
256
|
+
current_time = time.time()
|
|
257
|
+
if (current_time - node.timestamp) > ttl:
|
|
258
|
+
self._remove_node(node)
|
|
259
|
+
del self._cache[key]
|
|
260
|
+
self._stats.record_miss()
|
|
261
|
+
self._stats.record_eviction()
|
|
262
|
+
return None
|
|
261
263
|
|
|
262
264
|
self._move_to_head(node)
|
|
263
265
|
node.access_count += 1
|
|
@@ -272,7 +274,7 @@ class UnifiedCache(Generic[CacheValueT]):
|
|
|
272
274
|
value: Value to cache
|
|
273
275
|
"""
|
|
274
276
|
with self._lock:
|
|
275
|
-
existing_node
|
|
277
|
+
existing_node = self._cache.get(key)
|
|
276
278
|
if existing_node is not None:
|
|
277
279
|
existing_node.value = value
|
|
278
280
|
existing_node.timestamp = time.time()
|
|
@@ -280,14 +282,13 @@ class UnifiedCache(Generic[CacheValueT]):
|
|
|
280
282
|
self._move_to_head(existing_node)
|
|
281
283
|
return
|
|
282
284
|
|
|
283
|
-
new_node
|
|
285
|
+
new_node = CacheNode(key, value)
|
|
284
286
|
self._cache[key] = new_node
|
|
285
287
|
self._add_to_head(new_node)
|
|
286
288
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
tail_node: Optional[CacheNode] = self._tail.prev
|
|
289
|
+
# Optimize size check with cached length
|
|
290
|
+
if len(self._cache) > self._max_size:
|
|
291
|
+
tail_node = self._tail.prev
|
|
291
292
|
if tail_node is not None and tail_node is not self._head:
|
|
292
293
|
self._remove_node(tail_node)
|
|
293
294
|
del self._cache[tail_node.key]
|
|
@@ -361,17 +362,13 @@ class UnifiedCache(Generic[CacheValueT]):
|
|
|
361
362
|
def __contains__(self, key: CacheKey) -> bool:
|
|
362
363
|
"""Check if key exists in cache."""
|
|
363
364
|
with self._lock:
|
|
364
|
-
node
|
|
365
|
+
node = self._cache.get(key)
|
|
365
366
|
if node is None:
|
|
366
367
|
return False
|
|
367
368
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (current_time - node.timestamp) > ttl:
|
|
372
|
-
return False
|
|
373
|
-
|
|
374
|
-
return True
|
|
369
|
+
# Optimize TTL check
|
|
370
|
+
ttl = self._ttl
|
|
371
|
+
return not (ttl is not None and time.time() - node.timestamp > ttl)
|
|
375
372
|
|
|
376
373
|
|
|
377
374
|
@mypyc_attr(allow_interpreted_subclasses=False)
|
|
@@ -553,6 +550,8 @@ class ParameterCache:
|
|
|
553
550
|
"""
|
|
554
551
|
# Create stable key from parameters and configuration
|
|
555
552
|
try:
|
|
553
|
+
# Optimize type checking order
|
|
554
|
+
param_key: tuple[Any, ...]
|
|
556
555
|
if isinstance(params, dict):
|
|
557
556
|
param_key = tuple(sorted(params.items()))
|
|
558
557
|
elif isinstance(params, (list, tuple)):
|
|
@@ -560,12 +559,11 @@ class ParameterCache:
|
|
|
560
559
|
else:
|
|
561
560
|
param_key = (params,)
|
|
562
561
|
|
|
563
|
-
|
|
564
|
-
return CacheKey(key_data)
|
|
562
|
+
return CacheKey(("parameters", param_key, config_hash))
|
|
565
563
|
except (TypeError, ValueError):
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
return CacheKey(
|
|
564
|
+
# Fallback for unhashable types
|
|
565
|
+
param_key_fallback = (str(params), type(params).__name__)
|
|
566
|
+
return CacheKey(("parameters", param_key_fallback, config_hash))
|
|
569
567
|
|
|
570
568
|
def clear(self) -> None:
|
|
571
569
|
"""Clear parameter cache."""
|