sqlspec 0.15.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.

Files changed (43) hide show
  1. sqlspec/_sql.py +699 -43
  2. sqlspec/builder/_base.py +77 -44
  3. sqlspec/builder/_column.py +0 -4
  4. sqlspec/builder/_ddl.py +15 -52
  5. sqlspec/builder/_ddl_utils.py +0 -1
  6. sqlspec/builder/_delete.py +4 -5
  7. sqlspec/builder/_insert.py +61 -35
  8. sqlspec/builder/_merge.py +17 -2
  9. sqlspec/builder/_parsing_utils.py +16 -12
  10. sqlspec/builder/_select.py +29 -33
  11. sqlspec/builder/_update.py +4 -2
  12. sqlspec/builder/mixins/_cte_and_set_ops.py +47 -20
  13. sqlspec/builder/mixins/_delete_operations.py +6 -1
  14. sqlspec/builder/mixins/_insert_operations.py +126 -24
  15. sqlspec/builder/mixins/_join_operations.py +11 -4
  16. sqlspec/builder/mixins/_merge_operations.py +91 -19
  17. sqlspec/builder/mixins/_order_limit_operations.py +15 -3
  18. sqlspec/builder/mixins/_pivot_operations.py +11 -2
  19. sqlspec/builder/mixins/_select_operations.py +16 -10
  20. sqlspec/builder/mixins/_update_operations.py +43 -10
  21. sqlspec/builder/mixins/_where_clause.py +177 -65
  22. sqlspec/core/cache.py +26 -28
  23. sqlspec/core/compiler.py +58 -37
  24. sqlspec/core/filters.py +12 -10
  25. sqlspec/core/parameters.py +80 -52
  26. sqlspec/core/result.py +30 -17
  27. sqlspec/core/statement.py +47 -22
  28. sqlspec/driver/_async.py +76 -46
  29. sqlspec/driver/_common.py +25 -6
  30. sqlspec/driver/_sync.py +73 -43
  31. sqlspec/driver/mixins/_result_tools.py +62 -37
  32. sqlspec/driver/mixins/_sql_translator.py +61 -11
  33. sqlspec/extensions/litestar/cli.py +1 -1
  34. sqlspec/extensions/litestar/plugin.py +2 -2
  35. sqlspec/protocols.py +7 -0
  36. sqlspec/utils/sync_tools.py +1 -1
  37. sqlspec/utils/type_guards.py +7 -3
  38. {sqlspec-0.15.0.dist-info → sqlspec-0.16.1.dist-info}/METADATA +1 -1
  39. {sqlspec-0.15.0.dist-info → sqlspec-0.16.1.dist-info}/RECORD +43 -43
  40. {sqlspec-0.15.0.dist-info → sqlspec-0.16.1.dist-info}/WHEEL +0 -0
  41. {sqlspec-0.15.0.dist-info → sqlspec-0.16.1.dist-info}/entry_points.txt +0 -0
  42. {sqlspec-0.15.0.dist-info → sqlspec-0.16.1.dist-info}/licenses/LICENSE +0 -0
  43. {sqlspec-0.15.0.dist-info → sqlspec-0.16.1.dist-info}/licenses/NOTICE +0 -0
@@ -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
 
@@ -10,6 +11,30 @@ from sqlspec.builder._parsing_utils import parse_column_expression, parse_condit
10
11
  from sqlspec.exceptions import SQLBuilderError
11
12
  from sqlspec.utils.type_guards import has_query_builder_parameters, has_sqlglot_expression, is_iterable_parameters
12
13
 
14
+
15
+ def _extract_column_name(column: Union[str, exp.Column]) -> str:
16
+ """Extract column name from column expression for parameter naming.
17
+
18
+ Args:
19
+ column: Column expression (string or SQLGlot Column)
20
+
21
+ Returns:
22
+ Column name as string for use as parameter name
23
+ """
24
+ if isinstance(column, str):
25
+ # Handle simple column names and table.column references
26
+ if "." in column:
27
+ return column.split(".")[-1] # Return just the column part
28
+ return column
29
+ if isinstance(column, exp.Column):
30
+ # Extract the column name from SQLGlot Column expression
31
+ try:
32
+ return str(column.this.this)
33
+ except AttributeError:
34
+ return str(column.this) if column.this else "column"
35
+ return "column"
36
+
37
+
13
38
  if TYPE_CHECKING:
14
39
  from sqlspec.builder._column import ColumnExpression
15
40
  from sqlspec.protocols import SQLBuilderProtocol
@@ -17,31 +42,51 @@ if TYPE_CHECKING:
17
42
  __all__ = ("HavingClauseMixin", "WhereClauseMixin")
18
43
 
19
44
 
45
+ @trait
20
46
  class WhereClauseMixin:
21
47
  """Mixin providing WHERE clause methods for SELECT, UPDATE, and DELETE builders."""
22
48
 
23
- def _handle_in_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
49
+ __slots__ = ()
50
+
51
+ # Type annotation for PyRight - this will be provided by the base class
52
+ _expression: Optional[exp.Expression]
53
+
54
+ def _handle_in_operator(
55
+ self, column_exp: exp.Expression, value: Any, column_name: str = "column"
56
+ ) -> exp.Expression:
24
57
  """Handle IN operator."""
25
58
  builder = cast("SQLBuilderProtocol", self)
26
59
  if is_iterable_parameters(value):
27
60
  placeholders = []
28
- for v in value:
29
- _, param_name = builder.add_parameter(v)
61
+ for i, v in enumerate(value):
62
+ if len(value) == 1:
63
+ param_name = builder._generate_unique_parameter_name(column_name)
64
+ else:
65
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
66
+ _, param_name = builder.add_parameter(v, name=param_name)
30
67
  placeholders.append(exp.Placeholder(this=param_name))
31
68
  return exp.In(this=column_exp, expressions=placeholders)
32
- _, param_name = builder.add_parameter(value)
69
+ param_name = builder._generate_unique_parameter_name(column_name)
70
+ _, param_name = builder.add_parameter(value, name=param_name)
33
71
  return exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)])
34
72
 
35
- def _handle_not_in_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
73
+ def _handle_not_in_operator(
74
+ self, column_exp: exp.Expression, value: Any, column_name: str = "column"
75
+ ) -> exp.Expression:
36
76
  """Handle NOT IN operator."""
37
77
  builder = cast("SQLBuilderProtocol", self)
38
78
  if is_iterable_parameters(value):
39
79
  placeholders = []
40
- for v in value:
41
- _, param_name = builder.add_parameter(v)
80
+ for i, v in enumerate(value):
81
+ if len(value) == 1:
82
+ param_name = builder._generate_unique_parameter_name(column_name)
83
+ else:
84
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
85
+ _, param_name = builder.add_parameter(v, name=param_name)
42
86
  placeholders.append(exp.Placeholder(this=param_name))
43
87
  return exp.Not(this=exp.In(this=column_exp, expressions=placeholders))
44
- _, param_name = builder.add_parameter(value)
88
+ param_name = builder._generate_unique_parameter_name(column_name)
89
+ _, param_name = builder.add_parameter(value, name=param_name)
45
90
  return exp.Not(this=exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)]))
46
91
 
47
92
  def _handle_is_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
@@ -54,26 +99,34 @@ class WhereClauseMixin:
54
99
  value_expr = exp.Null() if value is None else exp.convert(value)
55
100
  return exp.Not(this=exp.Is(this=column_exp, expression=value_expr))
56
101
 
57
- def _handle_between_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
102
+ def _handle_between_operator(
103
+ self, column_exp: exp.Expression, value: Any, column_name: str = "column"
104
+ ) -> exp.Expression:
58
105
  """Handle BETWEEN operator."""
59
106
  if is_iterable_parameters(value) and len(value) == 2:
60
107
  builder = cast("SQLBuilderProtocol", self)
61
108
  low, high = value
62
- _, low_param = builder.add_parameter(low)
63
- _, high_param = builder.add_parameter(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")
111
+ _, low_param = builder.add_parameter(low, name=low_param)
112
+ _, high_param = builder.add_parameter(high, name=high_param)
64
113
  return exp.Between(
65
114
  this=column_exp, low=exp.Placeholder(this=low_param), high=exp.Placeholder(this=high_param)
66
115
  )
67
116
  msg = f"BETWEEN operator requires a tuple of two values, got {type(value).__name__}"
68
117
  raise SQLBuilderError(msg)
69
118
 
70
- def _handle_not_between_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
119
+ def _handle_not_between_operator(
120
+ self, column_exp: exp.Expression, value: Any, column_name: str = "column"
121
+ ) -> exp.Expression:
71
122
  """Handle NOT BETWEEN operator."""
72
123
  if is_iterable_parameters(value) and len(value) == 2:
73
124
  builder = cast("SQLBuilderProtocol", self)
74
125
  low, high = value
75
- _, low_param = builder.add_parameter(low)
76
- _, high_param = builder.add_parameter(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")
128
+ _, low_param = builder.add_parameter(low, name=low_param)
129
+ _, high_param = builder.add_parameter(high, name=high_param)
77
130
  return exp.Not(
78
131
  this=exp.Between(
79
132
  this=column_exp, low=exp.Placeholder(this=low_param), high=exp.Placeholder(this=high_param)
@@ -85,13 +138,15 @@ class WhereClauseMixin:
85
138
  def _process_tuple_condition(self, condition: tuple) -> exp.Expression:
86
139
  """Process tuple-based WHERE conditions."""
87
140
  builder = cast("SQLBuilderProtocol", self)
88
- column_name = str(condition[0])
89
- column_exp = parse_column_expression(column_name)
141
+ column_name_raw = str(condition[0])
142
+ column_exp = parse_column_expression(column_name_raw)
143
+ column_name = _extract_column_name(column_name_raw)
90
144
 
91
145
  if len(condition) == 2:
92
146
  # (column, value) tuple for equality
93
147
  value = condition[1]
94
- _, param_name = builder.add_parameter(value)
148
+ param_name = builder._generate_unique_parameter_name(column_name)
149
+ _, param_name = builder.add_parameter(value, name=param_name)
95
150
  return exp.EQ(this=column_exp, expression=exp.Placeholder(this=param_name))
96
151
 
97
152
  if len(condition) == 3:
@@ -100,42 +155,50 @@ class WhereClauseMixin:
100
155
  value = condition[2]
101
156
 
102
157
  if operator == "=":
103
- _, param_name = builder.add_parameter(value)
158
+ param_name = builder._generate_unique_parameter_name(column_name)
159
+ _, param_name = builder.add_parameter(value, name=param_name)
104
160
  return exp.EQ(this=column_exp, expression=exp.Placeholder(this=param_name))
105
161
  if operator in {"!=", "<>"}:
106
- _, param_name = builder.add_parameter(value)
162
+ param_name = builder._generate_unique_parameter_name(column_name)
163
+ _, param_name = builder.add_parameter(value, name=param_name)
107
164
  return exp.NEQ(this=column_exp, expression=exp.Placeholder(this=param_name))
108
165
  if operator == ">":
109
- _, param_name = builder.add_parameter(value)
166
+ param_name = builder._generate_unique_parameter_name(column_name)
167
+ _, param_name = builder.add_parameter(value, name=param_name)
110
168
  return exp.GT(this=column_exp, expression=exp.Placeholder(this=param_name))
111
169
  if operator == ">=":
112
- _, param_name = builder.add_parameter(value)
170
+ param_name = builder._generate_unique_parameter_name(column_name)
171
+ _, param_name = builder.add_parameter(value, name=param_name)
113
172
  return exp.GTE(this=column_exp, expression=exp.Placeholder(this=param_name))
114
173
  if operator == "<":
115
- _, param_name = builder.add_parameter(value)
174
+ param_name = builder._generate_unique_parameter_name(column_name)
175
+ _, param_name = builder.add_parameter(value, name=param_name)
116
176
  return exp.LT(this=column_exp, expression=exp.Placeholder(this=param_name))
117
177
  if operator == "<=":
118
- _, param_name = builder.add_parameter(value)
178
+ param_name = builder._generate_unique_parameter_name(column_name)
179
+ _, param_name = builder.add_parameter(value, name=param_name)
119
180
  return exp.LTE(this=column_exp, expression=exp.Placeholder(this=param_name))
120
181
  if operator == "LIKE":
121
- _, param_name = builder.add_parameter(value)
182
+ param_name = builder._generate_unique_parameter_name(column_name)
183
+ _, param_name = builder.add_parameter(value, name=param_name)
122
184
  return exp.Like(this=column_exp, expression=exp.Placeholder(this=param_name))
123
185
  if operator == "NOT LIKE":
124
- _, param_name = builder.add_parameter(value)
186
+ param_name = builder._generate_unique_parameter_name(column_name)
187
+ _, param_name = builder.add_parameter(value, name=param_name)
125
188
  return exp.Not(this=exp.Like(this=column_exp, expression=exp.Placeholder(this=param_name)))
126
189
 
127
190
  if operator == "IN":
128
- return self._handle_in_operator(column_exp, value)
191
+ return self._handle_in_operator(column_exp, value, column_name)
129
192
  if operator == "NOT IN":
130
- return self._handle_not_in_operator(column_exp, value)
193
+ return self._handle_not_in_operator(column_exp, value, column_name)
131
194
  if operator == "IS":
132
195
  return self._handle_is_operator(column_exp, value)
133
196
  if operator == "IS NOT":
134
197
  return self._handle_is_not_operator(column_exp, value)
135
198
  if operator == "BETWEEN":
136
- return self._handle_between_operator(column_exp, value)
199
+ return self._handle_between_operator(column_exp, value, column_name)
137
200
  if operator == "NOT BETWEEN":
138
- return self._handle_not_between_operator(column_exp, value)
201
+ return self._handle_not_between_operator(column_exp, value, column_name)
139
202
 
140
203
  msg = f"Unsupported operator: {operator}"
141
204
  raise SQLBuilderError(msg)
@@ -167,7 +230,7 @@ class WhereClauseMixin:
167
230
  Returns:
168
231
  The current builder instance for method chaining.
169
232
  """
170
- if self.__class__.__name__ == "Update" and not isinstance(self._expression, exp.Update): # type: ignore[attr-defined]
233
+ if self.__class__.__name__ == "Update" and not isinstance(self._expression, exp.Update):
171
234
  msg = "Cannot add WHERE clause to non-UPDATE expression"
172
235
  raise SQLBuilderError(msg)
173
236
 
@@ -218,86 +281,107 @@ class WhereClauseMixin:
218
281
  def where_eq(self, column: Union[str, exp.Column], value: Any) -> Self:
219
282
  """Add WHERE column = value clause."""
220
283
  builder = cast("SQLBuilderProtocol", self)
221
- _, param_name = builder.add_parameter(value)
284
+ column_name = _extract_column_name(column)
285
+ param_name = builder._generate_unique_parameter_name(column_name)
286
+ _, param_name = builder.add_parameter(value, name=param_name)
222
287
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
223
- condition: exp.Expression = col_expr.eq(exp.var(param_name))
288
+ condition: exp.Expression = col_expr.eq(exp.Placeholder(this=param_name))
224
289
  return self.where(condition)
225
290
 
226
291
  def where_neq(self, column: Union[str, exp.Column], value: Any) -> Self:
227
292
  """Add WHERE column != value clause."""
228
293
  builder = cast("SQLBuilderProtocol", self)
229
- _, param_name = builder.add_parameter(value)
294
+ column_name = _extract_column_name(column)
295
+ param_name = builder._generate_unique_parameter_name(column_name)
296
+ _, param_name = builder.add_parameter(value, name=param_name)
230
297
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
231
- condition: exp.Expression = col_expr.neq(exp.var(param_name))
298
+ condition: exp.Expression = col_expr.neq(exp.Placeholder(this=param_name))
232
299
  return self.where(condition)
233
300
 
234
301
  def where_lt(self, column: Union[str, exp.Column], value: Any) -> Self:
235
302
  """Add WHERE column < value clause."""
236
303
  builder = cast("SQLBuilderProtocol", self)
237
- _, param_name = builder.add_parameter(value)
304
+ column_name = _extract_column_name(column)
305
+ param_name = builder._generate_unique_parameter_name(column_name)
306
+ _, param_name = builder.add_parameter(value, name=param_name)
238
307
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
239
- condition: exp.Expression = exp.LT(this=col_expr, expression=exp.var(param_name))
308
+ condition: exp.Expression = exp.LT(this=col_expr, expression=exp.Placeholder(this=param_name))
240
309
  return self.where(condition)
241
310
 
242
311
  def where_lte(self, column: Union[str, exp.Column], value: Any) -> Self:
243
312
  """Add WHERE column <= value clause."""
244
313
  builder = cast("SQLBuilderProtocol", self)
245
- _, param_name = builder.add_parameter(value)
314
+ column_name = _extract_column_name(column)
315
+ param_name = builder._generate_unique_parameter_name(column_name)
316
+ _, param_name = builder.add_parameter(value, name=param_name)
246
317
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
247
- condition: exp.Expression = exp.LTE(this=col_expr, expression=exp.var(param_name))
318
+ condition: exp.Expression = exp.LTE(this=col_expr, expression=exp.Placeholder(this=param_name))
248
319
  return self.where(condition)
249
320
 
250
321
  def where_gt(self, column: Union[str, exp.Column], value: Any) -> Self:
251
322
  """Add WHERE column > value clause."""
252
323
  builder = cast("SQLBuilderProtocol", self)
253
- _, param_name = builder.add_parameter(value)
324
+ column_name = _extract_column_name(column)
325
+ param_name = builder._generate_unique_parameter_name(column_name)
326
+ _, param_name = builder.add_parameter(value, name=param_name)
254
327
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
255
- condition: exp.Expression = exp.GT(this=col_expr, expression=exp.var(param_name))
328
+ condition: exp.Expression = exp.GT(this=col_expr, expression=exp.Placeholder(this=param_name))
256
329
  return self.where(condition)
257
330
 
258
331
  def where_gte(self, column: Union[str, exp.Column], value: Any) -> Self:
259
332
  """Add WHERE column >= value clause."""
260
333
  builder = cast("SQLBuilderProtocol", self)
261
- _, param_name = builder.add_parameter(value)
334
+ column_name = _extract_column_name(column)
335
+ param_name = builder._generate_unique_parameter_name(column_name)
336
+ _, param_name = builder.add_parameter(value, name=param_name)
262
337
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
263
- condition: exp.Expression = exp.GTE(this=col_expr, expression=exp.var(param_name))
338
+ condition: exp.Expression = exp.GTE(this=col_expr, expression=exp.Placeholder(this=param_name))
264
339
  return self.where(condition)
265
340
 
266
341
  def where_between(self, column: Union[str, exp.Column], low: Any, high: Any) -> Self:
267
342
  """Add WHERE column BETWEEN low AND high clause."""
268
343
  builder = cast("SQLBuilderProtocol", self)
269
- _, low_param = builder.add_parameter(low)
270
- _, high_param = builder.add_parameter(high)
344
+ column_name = _extract_column_name(column)
345
+ low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
346
+ high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
347
+ _, low_param = builder.add_parameter(low, name=low_param)
348
+ _, high_param = builder.add_parameter(high, name=high_param)
271
349
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
272
- condition: exp.Expression = col_expr.between(exp.var(low_param), exp.var(high_param))
350
+ condition: exp.Expression = col_expr.between(exp.Placeholder(this=low_param), exp.Placeholder(this=high_param))
273
351
  return self.where(condition)
274
352
 
275
353
  def where_like(self, column: Union[str, exp.Column], pattern: str, escape: Optional[str] = None) -> Self:
276
354
  """Add WHERE column LIKE pattern clause."""
277
355
  builder = cast("SQLBuilderProtocol", self)
278
- _, param_name = builder.add_parameter(pattern)
356
+ column_name = _extract_column_name(column)
357
+ param_name = builder._generate_unique_parameter_name(column_name)
358
+ _, param_name = builder.add_parameter(pattern, name=param_name)
279
359
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
280
360
  if escape is not None:
281
- cond = exp.Like(this=col_expr, expression=exp.var(param_name), escape=exp.convert(str(escape)))
361
+ cond = exp.Like(this=col_expr, expression=exp.Placeholder(this=param_name), escape=exp.convert(str(escape)))
282
362
  else:
283
- cond = col_expr.like(exp.var(param_name))
363
+ cond = col_expr.like(exp.Placeholder(this=param_name))
284
364
  condition: exp.Expression = cond
285
365
  return self.where(condition)
286
366
 
287
367
  def where_not_like(self, column: Union[str, exp.Column], pattern: str) -> Self:
288
368
  """Add WHERE column NOT LIKE pattern clause."""
289
369
  builder = cast("SQLBuilderProtocol", self)
290
- _, param_name = builder.add_parameter(pattern)
370
+ column_name = _extract_column_name(column)
371
+ param_name = builder._generate_unique_parameter_name(column_name)
372
+ _, param_name = builder.add_parameter(pattern, name=param_name)
291
373
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
292
- condition: exp.Expression = col_expr.like(exp.var(param_name)).not_()
374
+ condition: exp.Expression = col_expr.like(exp.Placeholder(this=param_name)).not_()
293
375
  return self.where(condition)
294
376
 
295
377
  def where_ilike(self, column: Union[str, exp.Column], pattern: str) -> Self:
296
378
  """Add WHERE column ILIKE pattern clause."""
297
379
  builder = cast("SQLBuilderProtocol", self)
298
- _, param_name = builder.add_parameter(pattern)
380
+ column_name = _extract_column_name(column)
381
+ param_name = builder._generate_unique_parameter_name(column_name)
382
+ _, param_name = builder.add_parameter(pattern, name=param_name)
299
383
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
300
- condition: exp.Expression = col_expr.ilike(exp.var(param_name))
384
+ condition: exp.Expression = col_expr.ilike(exp.Placeholder(this=param_name))
301
385
  return self.where(condition)
302
386
 
303
387
  def where_is_null(self, column: Union[str, exp.Column]) -> Self:
@@ -322,6 +406,10 @@ class WhereClauseMixin:
322
406
  subquery = values.build() # pyright: ignore
323
407
  sql_str = subquery.sql
324
408
  subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=builder.dialect_name)) # pyright: ignore
409
+ # Merge subquery parameters into parent builder
410
+ if hasattr(subquery, "parameters") and isinstance(subquery.parameters, dict): # pyright: ignore[reportAttributeAccessIssue]
411
+ for param_name, param_value in subquery.parameters.items(): # pyright: ignore[reportAttributeAccessIssue]
412
+ builder.add_parameter(param_value, name=param_name)
325
413
  else:
326
414
  subquery_exp = values # type: ignore[assignment]
327
415
  condition = col_expr.isin(subquery_exp)
@@ -329,10 +417,15 @@ class WhereClauseMixin:
329
417
  if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
330
418
  msg = "Unsupported type for 'values' in WHERE IN"
331
419
  raise SQLBuilderError(msg)
420
+ column_name = _extract_column_name(column)
332
421
  parameters = []
333
- for v in values:
334
- _, param_name = builder.add_parameter(v)
335
- parameters.append(exp.var(param_name))
422
+ for i, v in enumerate(values):
423
+ if len(values) == 1:
424
+ param_name = builder._generate_unique_parameter_name(column_name)
425
+ else:
426
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
427
+ _, param_name = builder.add_parameter(v, name=param_name)
428
+ parameters.append(exp.Placeholder(this=param_name))
336
429
  condition = col_expr.isin(*parameters)
337
430
  return self.where(condition)
338
431
 
@@ -353,10 +446,15 @@ class WhereClauseMixin:
353
446
  if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
354
447
  msg = "Values for where_not_in must be a non-string iterable or subquery."
355
448
  raise SQLBuilderError(msg)
449
+ column_name = _extract_column_name(column)
356
450
  parameters = []
357
- for v in values:
358
- _, param_name = builder.add_parameter(v)
359
- parameters.append(exp.var(param_name))
451
+ for i, v in enumerate(values):
452
+ if len(values) == 1:
453
+ param_name = builder._generate_unique_parameter_name(column_name)
454
+ else:
455
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
456
+ _, param_name = builder.add_parameter(v, name=param_name)
457
+ parameters.append(exp.Placeholder(this=param_name))
360
458
  condition = exp.Not(this=col_expr.isin(*parameters))
361
459
  return self.where(condition)
362
460
 
@@ -438,10 +536,15 @@ class WhereClauseMixin:
438
536
  if not is_iterable_parameters(values) or isinstance(values, bytes):
439
537
  msg = "Unsupported type for 'values' in WHERE ANY"
440
538
  raise SQLBuilderError(msg)
539
+ column_name = _extract_column_name(column)
441
540
  parameters = []
442
- for v in values:
443
- _, param_name = builder.add_parameter(v)
444
- parameters.append(exp.var(param_name))
541
+ for i, v in enumerate(values):
542
+ if len(values) == 1:
543
+ param_name = builder._generate_unique_parameter_name(column_name)
544
+ else:
545
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_any_{i + 1}")
546
+ _, param_name = builder.add_parameter(v, name=param_name)
547
+ parameters.append(exp.Placeholder(this=param_name))
445
548
  tuple_expr = exp.Tuple(expressions=parameters)
446
549
  condition = exp.EQ(this=col_expr, expression=exp.Any(this=tuple_expr))
447
550
  return self.where(condition)
@@ -473,19 +576,28 @@ class WhereClauseMixin:
473
576
  if not is_iterable_parameters(values) or isinstance(values, bytes):
474
577
  msg = "Unsupported type for 'values' in WHERE NOT ANY"
475
578
  raise SQLBuilderError(msg)
579
+ column_name = _extract_column_name(column)
476
580
  parameters = []
477
- for v in values:
478
- _, param_name = builder.add_parameter(v)
479
- parameters.append(exp.var(param_name))
581
+ for i, v in enumerate(values):
582
+ if len(values) == 1:
583
+ param_name = builder._generate_unique_parameter_name(column_name)
584
+ else:
585
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_not_any_{i + 1}")
586
+ _, param_name = builder.add_parameter(v, name=param_name)
587
+ parameters.append(exp.Placeholder(this=param_name))
480
588
  tuple_expr = exp.Tuple(expressions=parameters)
481
589
  condition = exp.NEQ(this=col_expr, expression=exp.Any(this=tuple_expr))
482
590
  return self.where(condition)
483
591
 
484
592
 
593
+ @trait
485
594
  class HavingClauseMixin:
486
595
  """Mixin providing HAVING clause for SELECT builders."""
487
596
 
488
- _expression: Optional[exp.Expression] = None
597
+ __slots__ = ()
598
+
599
+ # Type annotation for PyRight - this will be provided by the base class
600
+ _expression: Optional[exp.Expression]
489
601
 
490
602
  def having(self, condition: Union[str, exp.Expression]) -> Self:
491
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: Optional[CacheNode] = self._cache.get(key)
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
- current_time: float = time.time()
254
- ttl: Optional[int] = self._ttl
255
- if ttl is not None and (current_time - node.timestamp) > ttl:
256
- self._remove_node(node)
257
- del self._cache[key]
258
- self._stats.record_miss()
259
- self._stats.record_eviction()
260
- return None
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: Optional[CacheNode] = self._cache.get(key)
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: CacheNode = CacheNode(key, value)
285
+ new_node = CacheNode(key, value)
284
286
  self._cache[key] = new_node
285
287
  self._add_to_head(new_node)
286
288
 
287
- cache_size: int = len(self._cache)
288
- max_size: int = self._max_size
289
- if cache_size > max_size:
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: Optional[CacheNode] = self._cache.get(key)
365
+ node = self._cache.get(key)
365
366
  if node is None:
366
367
  return False
367
368
 
368
- ttl: Optional[int] = self._ttl
369
- if ttl is not None:
370
- current_time: float = time.time()
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
- key_data = ("parameters", param_key, config_hash)
564
- return CacheKey(key_data)
562
+ return CacheKey(("parameters", param_key, config_hash))
565
563
  except (TypeError, ValueError):
566
- param_key = (str(params), type(params).__name__) # type: ignore[assignment]
567
- key_data = ("parameters", param_key, config_hash)
568
- return CacheKey(key_data)
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."""