sqlspec 0.15.0__py3-none-any.whl → 0.16.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sqlspec might be problematic. Click here for more details.

Files changed (43) hide show
  1. sqlspec/_sql.py +702 -44
  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 +235 -44
  8. sqlspec/builder/_merge.py +17 -2
  9. sqlspec/builder/_parsing_utils.py +42 -14
  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 +44 -10
  16. sqlspec/builder/mixins/_merge_operations.py +183 -25
  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 +21 -14
  20. sqlspec/builder/mixins/_update_operations.py +80 -32
  21. sqlspec/builder/mixins/_where_clause.py +201 -66
  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.2.dist-info}/METADATA +1 -1
  39. {sqlspec-0.15.0.dist-info → sqlspec-0.16.2.dist-info}/RECORD +43 -43
  40. {sqlspec-0.15.0.dist-info → sqlspec-0.16.2.dist-info}/WHEEL +0 -0
  41. {sqlspec-0.15.0.dist-info → sqlspec-0.16.2.dist-info}/entry_points.txt +0 -0
  42. {sqlspec-0.15.0.dist-info → sqlspec-0.16.2.dist-info}/licenses/LICENSE +0 -0
  43. {sqlspec-0.15.0.dist-info → sqlspec-0.16.2.dist-info}/licenses/NOTICE +0 -0
@@ -3,6 +3,10 @@
3
3
 
4
4
  from typing import TYPE_CHECKING, Any, Optional, Union, cast
5
5
 
6
+ if TYPE_CHECKING:
7
+ from sqlspec.core.statement import SQL
8
+
9
+ from mypy_extensions import trait
6
10
  from sqlglot import exp
7
11
  from typing_extensions import Self
8
12
 
@@ -10,6 +14,30 @@ from sqlspec.builder._parsing_utils import parse_column_expression, parse_condit
10
14
  from sqlspec.exceptions import SQLBuilderError
11
15
  from sqlspec.utils.type_guards import has_query_builder_parameters, has_sqlglot_expression, is_iterable_parameters
12
16
 
17
+
18
+ def _extract_column_name(column: Union[str, exp.Column]) -> str:
19
+ """Extract column name from column expression for parameter naming.
20
+
21
+ Args:
22
+ column: Column expression (string or SQLGlot Column)
23
+
24
+ Returns:
25
+ Column name as string for use as parameter name
26
+ """
27
+ if isinstance(column, str):
28
+ # Handle simple column names and table.column references
29
+ if "." in column:
30
+ return column.split(".")[-1] # Return just the column part
31
+ return column
32
+ if isinstance(column, exp.Column):
33
+ # Extract the column name from SQLGlot Column expression
34
+ try:
35
+ return str(column.this.this)
36
+ except AttributeError:
37
+ return str(column.this) if column.this else "column"
38
+ return "column"
39
+
40
+
13
41
  if TYPE_CHECKING:
14
42
  from sqlspec.builder._column import ColumnExpression
15
43
  from sqlspec.protocols import SQLBuilderProtocol
@@ -17,31 +45,51 @@ if TYPE_CHECKING:
17
45
  __all__ = ("HavingClauseMixin", "WhereClauseMixin")
18
46
 
19
47
 
48
+ @trait
20
49
  class WhereClauseMixin:
21
50
  """Mixin providing WHERE clause methods for SELECT, UPDATE, and DELETE builders."""
22
51
 
23
- def _handle_in_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
52
+ __slots__ = ()
53
+
54
+ # Type annotation for PyRight - this will be provided by the base class
55
+ _expression: Optional[exp.Expression]
56
+
57
+ def _handle_in_operator(
58
+ self, column_exp: exp.Expression, value: Any, column_name: str = "column"
59
+ ) -> exp.Expression:
24
60
  """Handle IN operator."""
25
61
  builder = cast("SQLBuilderProtocol", self)
26
62
  if is_iterable_parameters(value):
27
63
  placeholders = []
28
- for v in value:
29
- _, param_name = builder.add_parameter(v)
64
+ for i, v in enumerate(value):
65
+ if len(value) == 1:
66
+ param_name = builder._generate_unique_parameter_name(column_name)
67
+ else:
68
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
69
+ _, param_name = builder.add_parameter(v, name=param_name)
30
70
  placeholders.append(exp.Placeholder(this=param_name))
31
71
  return exp.In(this=column_exp, expressions=placeholders)
32
- _, param_name = builder.add_parameter(value)
72
+ param_name = builder._generate_unique_parameter_name(column_name)
73
+ _, param_name = builder.add_parameter(value, name=param_name)
33
74
  return exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)])
34
75
 
35
- def _handle_not_in_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
76
+ def _handle_not_in_operator(
77
+ self, column_exp: exp.Expression, value: Any, column_name: str = "column"
78
+ ) -> exp.Expression:
36
79
  """Handle NOT IN operator."""
37
80
  builder = cast("SQLBuilderProtocol", self)
38
81
  if is_iterable_parameters(value):
39
82
  placeholders = []
40
- for v in value:
41
- _, param_name = builder.add_parameter(v)
83
+ for i, v in enumerate(value):
84
+ if len(value) == 1:
85
+ param_name = builder._generate_unique_parameter_name(column_name)
86
+ else:
87
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
88
+ _, param_name = builder.add_parameter(v, name=param_name)
42
89
  placeholders.append(exp.Placeholder(this=param_name))
43
90
  return exp.Not(this=exp.In(this=column_exp, expressions=placeholders))
44
- _, param_name = builder.add_parameter(value)
91
+ param_name = builder._generate_unique_parameter_name(column_name)
92
+ _, param_name = builder.add_parameter(value, name=param_name)
45
93
  return exp.Not(this=exp.In(this=column_exp, expressions=[exp.Placeholder(this=param_name)]))
46
94
 
47
95
  def _handle_is_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
@@ -54,26 +102,34 @@ class WhereClauseMixin:
54
102
  value_expr = exp.Null() if value is None else exp.convert(value)
55
103
  return exp.Not(this=exp.Is(this=column_exp, expression=value_expr))
56
104
 
57
- def _handle_between_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
105
+ def _handle_between_operator(
106
+ self, column_exp: exp.Expression, value: Any, column_name: str = "column"
107
+ ) -> exp.Expression:
58
108
  """Handle BETWEEN operator."""
59
109
  if is_iterable_parameters(value) and len(value) == 2:
60
110
  builder = cast("SQLBuilderProtocol", self)
61
111
  low, high = value
62
- _, low_param = builder.add_parameter(low)
63
- _, high_param = builder.add_parameter(high)
112
+ low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
113
+ high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
114
+ _, low_param = builder.add_parameter(low, name=low_param)
115
+ _, high_param = builder.add_parameter(high, name=high_param)
64
116
  return exp.Between(
65
117
  this=column_exp, low=exp.Placeholder(this=low_param), high=exp.Placeholder(this=high_param)
66
118
  )
67
119
  msg = f"BETWEEN operator requires a tuple of two values, got {type(value).__name__}"
68
120
  raise SQLBuilderError(msg)
69
121
 
70
- def _handle_not_between_operator(self, column_exp: exp.Expression, value: Any) -> exp.Expression:
122
+ def _handle_not_between_operator(
123
+ self, column_exp: exp.Expression, value: Any, column_name: str = "column"
124
+ ) -> exp.Expression:
71
125
  """Handle NOT BETWEEN operator."""
72
126
  if is_iterable_parameters(value) and len(value) == 2:
73
127
  builder = cast("SQLBuilderProtocol", self)
74
128
  low, high = value
75
- _, low_param = builder.add_parameter(low)
76
- _, high_param = builder.add_parameter(high)
129
+ low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
130
+ high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
131
+ _, low_param = builder.add_parameter(low, name=low_param)
132
+ _, high_param = builder.add_parameter(high, name=high_param)
77
133
  return exp.Not(
78
134
  this=exp.Between(
79
135
  this=column_exp, low=exp.Placeholder(this=low_param), high=exp.Placeholder(this=high_param)
@@ -85,13 +141,15 @@ class WhereClauseMixin:
85
141
  def _process_tuple_condition(self, condition: tuple) -> exp.Expression:
86
142
  """Process tuple-based WHERE conditions."""
87
143
  builder = cast("SQLBuilderProtocol", self)
88
- column_name = str(condition[0])
89
- column_exp = parse_column_expression(column_name)
144
+ column_name_raw = str(condition[0])
145
+ column_exp = parse_column_expression(column_name_raw)
146
+ column_name = _extract_column_name(column_name_raw)
90
147
 
91
148
  if len(condition) == 2:
92
149
  # (column, value) tuple for equality
93
150
  value = condition[1]
94
- _, param_name = builder.add_parameter(value)
151
+ param_name = builder._generate_unique_parameter_name(column_name)
152
+ _, param_name = builder.add_parameter(value, name=param_name)
95
153
  return exp.EQ(this=column_exp, expression=exp.Placeholder(this=param_name))
96
154
 
97
155
  if len(condition) == 3:
@@ -100,42 +158,50 @@ class WhereClauseMixin:
100
158
  value = condition[2]
101
159
 
102
160
  if operator == "=":
103
- _, param_name = builder.add_parameter(value)
161
+ param_name = builder._generate_unique_parameter_name(column_name)
162
+ _, param_name = builder.add_parameter(value, name=param_name)
104
163
  return exp.EQ(this=column_exp, expression=exp.Placeholder(this=param_name))
105
164
  if operator in {"!=", "<>"}:
106
- _, param_name = builder.add_parameter(value)
165
+ param_name = builder._generate_unique_parameter_name(column_name)
166
+ _, param_name = builder.add_parameter(value, name=param_name)
107
167
  return exp.NEQ(this=column_exp, expression=exp.Placeholder(this=param_name))
108
168
  if operator == ">":
109
- _, param_name = builder.add_parameter(value)
169
+ param_name = builder._generate_unique_parameter_name(column_name)
170
+ _, param_name = builder.add_parameter(value, name=param_name)
110
171
  return exp.GT(this=column_exp, expression=exp.Placeholder(this=param_name))
111
172
  if operator == ">=":
112
- _, param_name = builder.add_parameter(value)
173
+ param_name = builder._generate_unique_parameter_name(column_name)
174
+ _, param_name = builder.add_parameter(value, name=param_name)
113
175
  return exp.GTE(this=column_exp, expression=exp.Placeholder(this=param_name))
114
176
  if operator == "<":
115
- _, param_name = builder.add_parameter(value)
177
+ param_name = builder._generate_unique_parameter_name(column_name)
178
+ _, param_name = builder.add_parameter(value, name=param_name)
116
179
  return exp.LT(this=column_exp, expression=exp.Placeholder(this=param_name))
117
180
  if operator == "<=":
118
- _, param_name = builder.add_parameter(value)
181
+ param_name = builder._generate_unique_parameter_name(column_name)
182
+ _, param_name = builder.add_parameter(value, name=param_name)
119
183
  return exp.LTE(this=column_exp, expression=exp.Placeholder(this=param_name))
120
184
  if operator == "LIKE":
121
- _, param_name = builder.add_parameter(value)
185
+ param_name = builder._generate_unique_parameter_name(column_name)
186
+ _, param_name = builder.add_parameter(value, name=param_name)
122
187
  return exp.Like(this=column_exp, expression=exp.Placeholder(this=param_name))
123
188
  if operator == "NOT LIKE":
124
- _, param_name = builder.add_parameter(value)
189
+ param_name = builder._generate_unique_parameter_name(column_name)
190
+ _, param_name = builder.add_parameter(value, name=param_name)
125
191
  return exp.Not(this=exp.Like(this=column_exp, expression=exp.Placeholder(this=param_name)))
126
192
 
127
193
  if operator == "IN":
128
- return self._handle_in_operator(column_exp, value)
194
+ return self._handle_in_operator(column_exp, value, column_name)
129
195
  if operator == "NOT IN":
130
- return self._handle_not_in_operator(column_exp, value)
196
+ return self._handle_not_in_operator(column_exp, value, column_name)
131
197
  if operator == "IS":
132
198
  return self._handle_is_operator(column_exp, value)
133
199
  if operator == "IS NOT":
134
200
  return self._handle_is_not_operator(column_exp, value)
135
201
  if operator == "BETWEEN":
136
- return self._handle_between_operator(column_exp, value)
202
+ return self._handle_between_operator(column_exp, value, column_name)
137
203
  if operator == "NOT BETWEEN":
138
- return self._handle_not_between_operator(column_exp, value)
204
+ return self._handle_not_between_operator(column_exp, value, column_name)
139
205
 
140
206
  msg = f"Unsupported operator: {operator}"
141
207
  raise SQLBuilderError(msg)
@@ -145,7 +211,9 @@ class WhereClauseMixin:
145
211
 
146
212
  def where(
147
213
  self,
148
- condition: Union[str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression"],
214
+ condition: Union[
215
+ str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", "SQL"
216
+ ],
149
217
  value: Optional[Any] = None,
150
218
  operator: Optional[str] = None,
151
219
  ) -> Self:
@@ -167,7 +235,7 @@ class WhereClauseMixin:
167
235
  Returns:
168
236
  The current builder instance for method chaining.
169
237
  """
170
- if self.__class__.__name__ == "Update" and not isinstance(self._expression, exp.Update): # type: ignore[attr-defined]
238
+ if self.__class__.__name__ == "Update" and not isinstance(self._expression, exp.Update):
171
239
  msg = "Cannot add WHERE clause to non-UPDATE expression"
172
240
  raise SQLBuilderError(msg)
173
241
 
@@ -204,6 +272,25 @@ class WhereClauseMixin:
204
272
  where_expr = builder._parameterize_expression(raw_expr)
205
273
  else:
206
274
  where_expr = parse_condition_expression(str(condition))
275
+ elif hasattr(condition, "expression") and hasattr(condition, "sql"):
276
+ # Handle SQL objects (from sql.raw with parameters)
277
+ expression = getattr(condition, "expression", None)
278
+ if expression is not None and isinstance(expression, exp.Expression):
279
+ # Merge parameters from SQL object into builder
280
+ if hasattr(condition, "parameters") and hasattr(builder, "add_parameter"):
281
+ sql_parameters = getattr(condition, "parameters", {})
282
+ for param_name, param_value in sql_parameters.items():
283
+ builder.add_parameter(param_value, name=param_name)
284
+ where_expr = expression
285
+ else:
286
+ # If expression is None, fall back to parsing the raw SQL
287
+ sql_text = getattr(condition, "sql", "")
288
+ # Merge parameters even when parsing raw SQL
289
+ if hasattr(condition, "parameters") and hasattr(builder, "add_parameter"):
290
+ sql_parameters = getattr(condition, "parameters", {})
291
+ for param_name, param_value in sql_parameters.items():
292
+ builder.add_parameter(param_value, name=param_name)
293
+ where_expr = parse_condition_expression(sql_text)
207
294
  else:
208
295
  msg = f"Unsupported condition type: {type(condition).__name__}"
209
296
  raise SQLBuilderError(msg)
@@ -218,86 +305,107 @@ class WhereClauseMixin:
218
305
  def where_eq(self, column: Union[str, exp.Column], value: Any) -> Self:
219
306
  """Add WHERE column = value clause."""
220
307
  builder = cast("SQLBuilderProtocol", self)
221
- _, param_name = builder.add_parameter(value)
308
+ column_name = _extract_column_name(column)
309
+ param_name = builder._generate_unique_parameter_name(column_name)
310
+ _, param_name = builder.add_parameter(value, name=param_name)
222
311
  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))
312
+ condition: exp.Expression = col_expr.eq(exp.Placeholder(this=param_name))
224
313
  return self.where(condition)
225
314
 
226
315
  def where_neq(self, column: Union[str, exp.Column], value: Any) -> Self:
227
316
  """Add WHERE column != value clause."""
228
317
  builder = cast("SQLBuilderProtocol", self)
229
- _, param_name = builder.add_parameter(value)
318
+ column_name = _extract_column_name(column)
319
+ param_name = builder._generate_unique_parameter_name(column_name)
320
+ _, param_name = builder.add_parameter(value, name=param_name)
230
321
  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))
322
+ condition: exp.Expression = col_expr.neq(exp.Placeholder(this=param_name))
232
323
  return self.where(condition)
233
324
 
234
325
  def where_lt(self, column: Union[str, exp.Column], value: Any) -> Self:
235
326
  """Add WHERE column < value clause."""
236
327
  builder = cast("SQLBuilderProtocol", self)
237
- _, param_name = builder.add_parameter(value)
328
+ column_name = _extract_column_name(column)
329
+ param_name = builder._generate_unique_parameter_name(column_name)
330
+ _, param_name = builder.add_parameter(value, name=param_name)
238
331
  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))
332
+ condition: exp.Expression = exp.LT(this=col_expr, expression=exp.Placeholder(this=param_name))
240
333
  return self.where(condition)
241
334
 
242
335
  def where_lte(self, column: Union[str, exp.Column], value: Any) -> Self:
243
336
  """Add WHERE column <= value clause."""
244
337
  builder = cast("SQLBuilderProtocol", self)
245
- _, param_name = builder.add_parameter(value)
338
+ column_name = _extract_column_name(column)
339
+ param_name = builder._generate_unique_parameter_name(column_name)
340
+ _, param_name = builder.add_parameter(value, name=param_name)
246
341
  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))
342
+ condition: exp.Expression = exp.LTE(this=col_expr, expression=exp.Placeholder(this=param_name))
248
343
  return self.where(condition)
249
344
 
250
345
  def where_gt(self, column: Union[str, exp.Column], value: Any) -> Self:
251
346
  """Add WHERE column > value clause."""
252
347
  builder = cast("SQLBuilderProtocol", self)
253
- _, param_name = builder.add_parameter(value)
348
+ column_name = _extract_column_name(column)
349
+ param_name = builder._generate_unique_parameter_name(column_name)
350
+ _, param_name = builder.add_parameter(value, name=param_name)
254
351
  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))
352
+ condition: exp.Expression = exp.GT(this=col_expr, expression=exp.Placeholder(this=param_name))
256
353
  return self.where(condition)
257
354
 
258
355
  def where_gte(self, column: Union[str, exp.Column], value: Any) -> Self:
259
356
  """Add WHERE column >= value clause."""
260
357
  builder = cast("SQLBuilderProtocol", self)
261
- _, param_name = builder.add_parameter(value)
358
+ column_name = _extract_column_name(column)
359
+ param_name = builder._generate_unique_parameter_name(column_name)
360
+ _, param_name = builder.add_parameter(value, name=param_name)
262
361
  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))
362
+ condition: exp.Expression = exp.GTE(this=col_expr, expression=exp.Placeholder(this=param_name))
264
363
  return self.where(condition)
265
364
 
266
365
  def where_between(self, column: Union[str, exp.Column], low: Any, high: Any) -> Self:
267
366
  """Add WHERE column BETWEEN low AND high clause."""
268
367
  builder = cast("SQLBuilderProtocol", self)
269
- _, low_param = builder.add_parameter(low)
270
- _, high_param = builder.add_parameter(high)
368
+ column_name = _extract_column_name(column)
369
+ low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
370
+ high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
371
+ _, low_param = builder.add_parameter(low, name=low_param)
372
+ _, high_param = builder.add_parameter(high, name=high_param)
271
373
  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))
374
+ condition: exp.Expression = col_expr.between(exp.Placeholder(this=low_param), exp.Placeholder(this=high_param))
273
375
  return self.where(condition)
274
376
 
275
377
  def where_like(self, column: Union[str, exp.Column], pattern: str, escape: Optional[str] = None) -> Self:
276
378
  """Add WHERE column LIKE pattern clause."""
277
379
  builder = cast("SQLBuilderProtocol", self)
278
- _, 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)
279
383
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
280
384
  if escape is not None:
281
- cond = exp.Like(this=col_expr, expression=exp.var(param_name), escape=exp.convert(str(escape)))
385
+ cond = exp.Like(this=col_expr, expression=exp.Placeholder(this=param_name), escape=exp.convert(str(escape)))
282
386
  else:
283
- cond = col_expr.like(exp.var(param_name))
387
+ cond = col_expr.like(exp.Placeholder(this=param_name))
284
388
  condition: exp.Expression = cond
285
389
  return self.where(condition)
286
390
 
287
391
  def where_not_like(self, column: Union[str, exp.Column], pattern: str) -> Self:
288
392
  """Add WHERE column NOT LIKE pattern clause."""
289
393
  builder = cast("SQLBuilderProtocol", self)
290
- _, param_name = builder.add_parameter(pattern)
394
+ column_name = _extract_column_name(column)
395
+ param_name = builder._generate_unique_parameter_name(column_name)
396
+ _, param_name = builder.add_parameter(pattern, name=param_name)
291
397
  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_()
398
+ condition: exp.Expression = col_expr.like(exp.Placeholder(this=param_name)).not_()
293
399
  return self.where(condition)
294
400
 
295
401
  def where_ilike(self, column: Union[str, exp.Column], pattern: str) -> Self:
296
402
  """Add WHERE column ILIKE pattern clause."""
297
403
  builder = cast("SQLBuilderProtocol", self)
298
- _, param_name = builder.add_parameter(pattern)
404
+ column_name = _extract_column_name(column)
405
+ param_name = builder._generate_unique_parameter_name(column_name)
406
+ _, param_name = builder.add_parameter(pattern, name=param_name)
299
407
  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))
408
+ condition: exp.Expression = col_expr.ilike(exp.Placeholder(this=param_name))
301
409
  return self.where(condition)
302
410
 
303
411
  def where_is_null(self, column: Union[str, exp.Column]) -> Self:
@@ -322,6 +430,10 @@ class WhereClauseMixin:
322
430
  subquery = values.build() # pyright: ignore
323
431
  sql_str = subquery.sql
324
432
  subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=builder.dialect_name)) # pyright: ignore
433
+ # Merge subquery parameters into parent builder
434
+ if hasattr(subquery, "parameters") and isinstance(subquery.parameters, dict): # pyright: ignore[reportAttributeAccessIssue]
435
+ for param_name, param_value in subquery.parameters.items(): # pyright: ignore[reportAttributeAccessIssue]
436
+ builder.add_parameter(param_value, name=param_name)
325
437
  else:
326
438
  subquery_exp = values # type: ignore[assignment]
327
439
  condition = col_expr.isin(subquery_exp)
@@ -329,10 +441,15 @@ class WhereClauseMixin:
329
441
  if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
330
442
  msg = "Unsupported type for 'values' in WHERE IN"
331
443
  raise SQLBuilderError(msg)
444
+ column_name = _extract_column_name(column)
332
445
  parameters = []
333
- for v in values:
334
- _, param_name = builder.add_parameter(v)
335
- parameters.append(exp.var(param_name))
446
+ for i, v in enumerate(values):
447
+ if len(values) == 1:
448
+ param_name = builder._generate_unique_parameter_name(column_name)
449
+ else:
450
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
451
+ _, param_name = builder.add_parameter(v, name=param_name)
452
+ parameters.append(exp.Placeholder(this=param_name))
336
453
  condition = col_expr.isin(*parameters)
337
454
  return self.where(condition)
338
455
 
@@ -353,10 +470,15 @@ class WhereClauseMixin:
353
470
  if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
354
471
  msg = "Values for where_not_in must be a non-string iterable or subquery."
355
472
  raise SQLBuilderError(msg)
473
+ column_name = _extract_column_name(column)
356
474
  parameters = []
357
- for v in values:
358
- _, param_name = builder.add_parameter(v)
359
- parameters.append(exp.var(param_name))
475
+ for i, v in enumerate(values):
476
+ if len(values) == 1:
477
+ param_name = builder._generate_unique_parameter_name(column_name)
478
+ else:
479
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
480
+ _, param_name = builder.add_parameter(v, name=param_name)
481
+ parameters.append(exp.Placeholder(this=param_name))
360
482
  condition = exp.Not(this=col_expr.isin(*parameters))
361
483
  return self.where(condition)
362
484
 
@@ -438,10 +560,15 @@ class WhereClauseMixin:
438
560
  if not is_iterable_parameters(values) or isinstance(values, bytes):
439
561
  msg = "Unsupported type for 'values' in WHERE ANY"
440
562
  raise SQLBuilderError(msg)
563
+ column_name = _extract_column_name(column)
441
564
  parameters = []
442
- for v in values:
443
- _, param_name = builder.add_parameter(v)
444
- parameters.append(exp.var(param_name))
565
+ for i, v in enumerate(values):
566
+ if len(values) == 1:
567
+ param_name = builder._generate_unique_parameter_name(column_name)
568
+ else:
569
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_any_{i + 1}")
570
+ _, param_name = builder.add_parameter(v, name=param_name)
571
+ parameters.append(exp.Placeholder(this=param_name))
445
572
  tuple_expr = exp.Tuple(expressions=parameters)
446
573
  condition = exp.EQ(this=col_expr, expression=exp.Any(this=tuple_expr))
447
574
  return self.where(condition)
@@ -473,19 +600,27 @@ class WhereClauseMixin:
473
600
  if not is_iterable_parameters(values) or isinstance(values, bytes):
474
601
  msg = "Unsupported type for 'values' in WHERE NOT ANY"
475
602
  raise SQLBuilderError(msg)
603
+ column_name = _extract_column_name(column)
476
604
  parameters = []
477
- for v in values:
478
- _, param_name = builder.add_parameter(v)
479
- parameters.append(exp.var(param_name))
605
+ for i, v in enumerate(values):
606
+ if len(values) == 1:
607
+ param_name = builder._generate_unique_parameter_name(column_name)
608
+ else:
609
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_not_any_{i + 1}")
610
+ _, param_name = builder.add_parameter(v, name=param_name)
611
+ parameters.append(exp.Placeholder(this=param_name))
480
612
  tuple_expr = exp.Tuple(expressions=parameters)
481
613
  condition = exp.NEQ(this=col_expr, expression=exp.Any(this=tuple_expr))
482
614
  return self.where(condition)
483
615
 
484
616
 
617
+ @trait
485
618
  class HavingClauseMixin:
486
619
  """Mixin providing HAVING clause for SELECT builders."""
487
620
 
488
- _expression: Optional[exp.Expression] = None
621
+ __slots__ = ()
622
+
623
+ _expression: Optional[exp.Expression]
489
624
 
490
625
  def having(self, condition: Union[str, exp.Expression]) -> Self:
491
626
  """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."""