sqlspec 0.23.0__py3-none-any.whl → 0.24.0__py3-none-any.whl

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

Potentially problematic release.


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

@@ -5,7 +5,7 @@ Provides mixins for WHERE and HAVING clause functionality with
5
5
  parameter binding and various condition operators.
6
6
  """
7
7
 
8
- from typing import TYPE_CHECKING, Any, Optional, Union, cast
8
+ from typing import TYPE_CHECKING, Any, Callable, Optional, Union, cast
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  from sqlspec.core.statement import SQL
@@ -16,7 +16,13 @@ from typing_extensions import Self
16
16
 
17
17
  from sqlspec.builder._parsing_utils import parse_column_expression, parse_condition_expression
18
18
  from sqlspec.exceptions import SQLBuilderError
19
- from sqlspec.utils.type_guards import has_query_builder_parameters, has_sqlglot_expression, is_iterable_parameters
19
+ from sqlspec.utils.type_guards import (
20
+ has_expression_and_parameters,
21
+ has_expression_and_sql,
22
+ has_query_builder_parameters,
23
+ has_sqlglot_expression,
24
+ is_iterable_parameters,
25
+ )
20
26
 
21
27
 
22
28
  def _extract_column_name(column: Union[str, exp.Column]) -> str:
@@ -39,6 +45,7 @@ def _extract_column_name(column: Union[str, exp.Column]) -> str:
39
45
  return str(column.this.this)
40
46
  except AttributeError:
41
47
  return str(column.this) if column.this else "column"
48
+ # Fallback for any unexpected types (defensive programming)
42
49
  return "column"
43
50
 
44
51
 
@@ -58,6 +65,76 @@ class WhereClauseMixin:
58
65
  # Type annotation for PyRight - this will be provided by the base class
59
66
  _expression: Optional[exp.Expression]
60
67
 
68
+ def _create_parameterized_condition(
69
+ self,
70
+ column: Union[str, exp.Column],
71
+ value: Any,
72
+ condition_factory: "Callable[[exp.Expression, exp.Placeholder], exp.Expression]",
73
+ ) -> exp.Expression:
74
+ """Create a parameterized condition using the provided factory function.
75
+
76
+ Args:
77
+ column: Column expression
78
+ value: Parameter value
79
+ condition_factory: Function that creates the condition expression
80
+
81
+ Returns:
82
+ The created condition expression
83
+ """
84
+ builder = cast("SQLBuilderProtocol", self)
85
+ column_name = _extract_column_name(column)
86
+ param_name = builder._generate_unique_parameter_name(column_name)
87
+ _, param_name = builder.add_parameter(value, name=param_name)
88
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
89
+ placeholder = exp.Placeholder(this=param_name)
90
+ return condition_factory(col_expr, placeholder)
91
+
92
+ def _merge_sql_object_parameters(self, sql_obj: Any) -> None:
93
+ """Merge parameters from a SQL object into the builder.
94
+
95
+ Args:
96
+ sql_obj: Object with parameters attribute containing parameter mappings
97
+ """
98
+ if not has_expression_and_parameters(sql_obj):
99
+ return
100
+
101
+ builder = cast("SQLBuilderProtocol", self)
102
+ sql_parameters = getattr(sql_obj, "parameters", {})
103
+ for param_name, param_value in sql_parameters.items():
104
+ unique_name = builder._generate_unique_parameter_name(param_name)
105
+ builder.add_parameter(param_value, name=unique_name)
106
+
107
+ def _apply_or_where(self, where_method: "Callable[..., Self]", *args: Any, **kwargs: Any) -> Self:
108
+ """Apply a where method but use OR logic instead of AND.
109
+
110
+ This allows reusing all where_* methods for or_where_* functionality.
111
+
112
+ Args:
113
+ where_method: The where method to apply (e.g., self.where_eq)
114
+ *args: Arguments to pass to the where method
115
+ **kwargs: Keyword arguments to pass to the where method
116
+
117
+ Returns:
118
+ Self with OR condition applied
119
+ """
120
+ # Create a temporary clone to capture the condition
121
+ original_expr = self._expression
122
+
123
+ # Apply the where method to get the condition
124
+ where_method(*args, **kwargs)
125
+
126
+ # Get the last condition added by extracting it from the modified expression
127
+ if isinstance(self._expression, (exp.Select, exp.Update, exp.Delete)) and original_expr != self._expression:
128
+ last_where = self._expression.find(exp.Where)
129
+ if last_where and last_where.this:
130
+ condition = last_where.this
131
+ # Restore original expression
132
+ self._expression = original_expr
133
+ # Apply as OR
134
+ return self.or_where(condition)
135
+
136
+ return self
137
+
61
138
  def _handle_in_operator(
62
139
  self, column_exp: exp.Expression, value: Any, column_name: str = "column"
63
140
  ) -> exp.Expression:
@@ -142,75 +219,56 @@ class WhereClauseMixin:
142
219
  msg = f"NOT BETWEEN operator requires a tuple of two values, got {type(value).__name__}"
143
220
  raise SQLBuilderError(msg)
144
221
 
145
- def _process_tuple_condition(self, condition: tuple) -> exp.Expression:
222
+ def _process_tuple_condition(self, condition: "tuple[Any, ...]") -> exp.Expression:
146
223
  """Process tuple-based WHERE conditions."""
147
- builder = cast("SQLBuilderProtocol", self)
148
- column_name_raw = str(condition[0])
149
- column_exp = parse_column_expression(column_name_raw)
150
- column_name = _extract_column_name(column_name_raw)
151
-
152
224
  if len(condition) == 2:
153
225
  # (column, value) tuple for equality
154
- value = condition[1]
155
- param_name = builder._generate_unique_parameter_name(column_name)
156
- _, param_name = builder.add_parameter(value, name=param_name)
157
- return exp.EQ(this=column_exp, expression=exp.Placeholder(this=param_name))
158
-
159
- if len(condition) == 3:
160
- # (column, operator, value) tuple
161
- operator = str(condition[1]).upper()
162
- value = condition[2]
226
+ column, value = condition
227
+ return self._create_parameterized_condition(
228
+ column, value, lambda col, placeholder: exp.EQ(this=col, expression=placeholder)
229
+ )
163
230
 
164
- if operator == "=":
165
- param_name = builder._generate_unique_parameter_name(column_name)
166
- _, param_name = builder.add_parameter(value, name=param_name)
167
- return exp.EQ(this=column_exp, expression=exp.Placeholder(this=param_name))
168
- if operator in {"!=", "<>"}:
169
- param_name = builder._generate_unique_parameter_name(column_name)
170
- _, param_name = builder.add_parameter(value, name=param_name)
171
- return exp.NEQ(this=column_exp, expression=exp.Placeholder(this=param_name))
172
- if operator == ">":
173
- param_name = builder._generate_unique_parameter_name(column_name)
174
- _, param_name = builder.add_parameter(value, name=param_name)
175
- return exp.GT(this=column_exp, expression=exp.Placeholder(this=param_name))
176
- if operator == ">=":
177
- param_name = builder._generate_unique_parameter_name(column_name)
178
- _, param_name = builder.add_parameter(value, name=param_name)
179
- return exp.GTE(this=column_exp, expression=exp.Placeholder(this=param_name))
180
- if operator == "<":
181
- param_name = builder._generate_unique_parameter_name(column_name)
182
- _, param_name = builder.add_parameter(value, name=param_name)
183
- return exp.LT(this=column_exp, expression=exp.Placeholder(this=param_name))
184
- if operator == "<=":
185
- param_name = builder._generate_unique_parameter_name(column_name)
186
- _, param_name = builder.add_parameter(value, name=param_name)
187
- return exp.LTE(this=column_exp, expression=exp.Placeholder(this=param_name))
188
- if operator == "LIKE":
189
- param_name = builder._generate_unique_parameter_name(column_name)
190
- _, param_name = builder.add_parameter(value, name=param_name)
191
- return exp.Like(this=column_exp, expression=exp.Placeholder(this=param_name))
192
- if operator == "NOT LIKE":
193
- param_name = builder._generate_unique_parameter_name(column_name)
194
- _, param_name = builder.add_parameter(value, name=param_name)
195
- return exp.Not(this=exp.Like(this=column_exp, expression=exp.Placeholder(this=param_name)))
196
-
197
- if operator == "IN":
198
- return self._handle_in_operator(column_exp, value, column_name)
199
- if operator == "NOT IN":
200
- return self._handle_not_in_operator(column_exp, value, column_name)
201
- if operator == "IS":
202
- return self._handle_is_operator(column_exp, value)
203
- if operator == "IS NOT":
204
- return self._handle_is_not_operator(column_exp, value)
205
- if operator == "BETWEEN":
206
- return self._handle_between_operator(column_exp, value, column_name)
207
- if operator == "NOT BETWEEN":
208
- return self._handle_not_between_operator(column_exp, value, column_name)
209
-
210
- msg = f"Unsupported operator: {operator}"
231
+ if len(condition) != 3:
232
+ msg = f"Condition tuple must have 2 or 3 elements, got {len(condition)}"
211
233
  raise SQLBuilderError(msg)
212
234
 
213
- msg = f"Condition tuple must have 2 or 3 elements, got {len(condition)}"
235
+ # (column, operator, value) tuple
236
+ column_name_raw, operator, value = condition
237
+ operator = str(operator).upper()
238
+ column_exp = parse_column_expression(column_name_raw)
239
+ column_name = _extract_column_name(column_name_raw)
240
+
241
+ # Simple operators that use direct parameterization
242
+ simple_operators = {
243
+ "=": lambda col, placeholder: exp.EQ(this=col, expression=placeholder),
244
+ "!=": lambda col, placeholder: exp.NEQ(this=col, expression=placeholder),
245
+ "<>": lambda col, placeholder: exp.NEQ(this=col, expression=placeholder),
246
+ ">": lambda col, placeholder: exp.GT(this=col, expression=placeholder),
247
+ ">=": lambda col, placeholder: exp.GTE(this=col, expression=placeholder),
248
+ "<": lambda col, placeholder: exp.LT(this=col, expression=placeholder),
249
+ "<=": lambda col, placeholder: exp.LTE(this=col, expression=placeholder),
250
+ "LIKE": lambda col, placeholder: exp.Like(this=col, expression=placeholder),
251
+ "NOT LIKE": lambda col, placeholder: exp.Not(this=exp.Like(this=col, expression=placeholder)),
252
+ }
253
+
254
+ if operator in simple_operators:
255
+ return self._create_parameterized_condition(column_name_raw, value, simple_operators[operator])
256
+
257
+ # Complex operators that need special handling
258
+ if operator == "IN":
259
+ return self._handle_in_operator(column_exp, value, column_name)
260
+ if operator == "NOT IN":
261
+ return self._handle_not_in_operator(column_exp, value, column_name)
262
+ if operator == "IS":
263
+ return self._handle_is_operator(column_exp, value)
264
+ if operator == "IS NOT":
265
+ return self._handle_is_not_operator(column_exp, value)
266
+ if operator == "BETWEEN":
267
+ return self._handle_between_operator(column_exp, value, column_name)
268
+ if operator == "NOT BETWEEN":
269
+ return self._handle_not_between_operator(column_exp, value, column_name)
270
+
271
+ msg = f"Unsupported operator: {operator}"
214
272
  raise SQLBuilderError(msg)
215
273
 
216
274
  def where(
@@ -325,24 +383,18 @@ class WhereClauseMixin:
325
383
  where_expr = builder._parameterize_expression(raw_expr)
326
384
  else:
327
385
  where_expr = parse_condition_expression(str(condition))
328
- elif hasattr(condition, "expression") and hasattr(condition, "sql"):
386
+ elif has_expression_and_sql(condition):
329
387
  # Handle SQL objects (from sql.raw with parameters)
330
388
  expression = getattr(condition, "expression", None)
331
389
  if expression is not None and isinstance(expression, exp.Expression):
332
390
  # Merge parameters from SQL object into builder
333
- if hasattr(condition, "parameters") and hasattr(builder, "add_parameter"):
334
- sql_parameters = getattr(condition, "parameters", {})
335
- for param_name, param_value in sql_parameters.items():
336
- builder.add_parameter(param_value, name=param_name)
391
+ self._merge_sql_object_parameters(condition)
337
392
  where_expr = expression
338
393
  else:
339
394
  # If expression is None, fall back to parsing the raw SQL
340
395
  sql_text = getattr(condition, "sql", "")
341
396
  # Merge parameters even when parsing raw SQL
342
- if hasattr(condition, "parameters") and hasattr(builder, "add_parameter"):
343
- sql_parameters = getattr(condition, "parameters", {})
344
- for param_name, param_value in sql_parameters.items():
345
- builder.add_parameter(param_value, name=param_name)
397
+ self._merge_sql_object_parameters(condition)
346
398
  where_expr = parse_condition_expression(sql_text)
347
399
  else:
348
400
  msg = f"Unsupported condition type: {type(condition).__name__}"
@@ -357,62 +409,40 @@ class WhereClauseMixin:
357
409
 
358
410
  def where_eq(self, column: Union[str, exp.Column], value: Any) -> Self:
359
411
  """Add WHERE column = value clause."""
360
- builder = cast("SQLBuilderProtocol", self)
361
- column_name = _extract_column_name(column)
362
- param_name = builder._generate_unique_parameter_name(column_name)
363
- _, param_name = builder.add_parameter(value, name=param_name)
364
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
365
- condition: exp.Expression = col_expr.eq(exp.Placeholder(this=param_name))
412
+ condition = self._create_parameterized_condition(column, value, lambda col, placeholder: col.eq(placeholder))
366
413
  return self.where(condition)
367
414
 
368
415
  def where_neq(self, column: Union[str, exp.Column], value: Any) -> Self:
369
416
  """Add WHERE column != value clause."""
370
- builder = cast("SQLBuilderProtocol", self)
371
- column_name = _extract_column_name(column)
372
- param_name = builder._generate_unique_parameter_name(column_name)
373
- _, param_name = builder.add_parameter(value, name=param_name)
374
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
375
- condition: exp.Expression = col_expr.neq(exp.Placeholder(this=param_name))
417
+ condition = self._create_parameterized_condition(column, value, lambda col, placeholder: col.neq(placeholder))
376
418
  return self.where(condition)
377
419
 
378
420
  def where_lt(self, column: Union[str, exp.Column], value: Any) -> Self:
379
421
  """Add WHERE column < value clause."""
380
- builder = cast("SQLBuilderProtocol", self)
381
- column_name = _extract_column_name(column)
382
- param_name = builder._generate_unique_parameter_name(column_name)
383
- _, param_name = builder.add_parameter(value, name=param_name)
384
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
385
- condition: exp.Expression = exp.LT(this=col_expr, expression=exp.Placeholder(this=param_name))
422
+ condition = self._create_parameterized_condition(
423
+ column, value, lambda col, placeholder: exp.LT(this=col, expression=placeholder)
424
+ )
386
425
  return self.where(condition)
387
426
 
388
427
  def where_lte(self, column: Union[str, exp.Column], value: Any) -> Self:
389
428
  """Add WHERE column <= value clause."""
390
- builder = cast("SQLBuilderProtocol", self)
391
- column_name = _extract_column_name(column)
392
- param_name = builder._generate_unique_parameter_name(column_name)
393
- _, param_name = builder.add_parameter(value, name=param_name)
394
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
395
- condition: exp.Expression = exp.LTE(this=col_expr, expression=exp.Placeholder(this=param_name))
429
+ condition = self._create_parameterized_condition(
430
+ column, value, lambda col, placeholder: exp.LTE(this=col, expression=placeholder)
431
+ )
396
432
  return self.where(condition)
397
433
 
398
434
  def where_gt(self, column: Union[str, exp.Column], value: Any) -> Self:
399
435
  """Add WHERE column > value clause."""
400
- builder = cast("SQLBuilderProtocol", self)
401
- column_name = _extract_column_name(column)
402
- param_name = builder._generate_unique_parameter_name(column_name)
403
- _, param_name = builder.add_parameter(value, name=param_name)
404
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
405
- condition: exp.Expression = exp.GT(this=col_expr, expression=exp.Placeholder(this=param_name))
436
+ condition = self._create_parameterized_condition(
437
+ column, value, lambda col, placeholder: exp.GT(this=col, expression=placeholder)
438
+ )
406
439
  return self.where(condition)
407
440
 
408
441
  def where_gte(self, column: Union[str, exp.Column], value: Any) -> Self:
409
442
  """Add WHERE column >= value clause."""
410
- builder = cast("SQLBuilderProtocol", self)
411
- column_name = _extract_column_name(column)
412
- param_name = builder._generate_unique_parameter_name(column_name)
413
- _, param_name = builder.add_parameter(value, name=param_name)
414
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
415
- condition: exp.Expression = exp.GTE(this=col_expr, expression=exp.Placeholder(this=param_name))
443
+ condition = self._create_parameterized_condition(
444
+ column, value, lambda col, placeholder: exp.GTE(this=col, expression=placeholder)
445
+ )
416
446
  return self.where(condition)
417
447
 
418
448
  def where_between(self, column: Union[str, exp.Column], low: Any, high: Any) -> Self:
@@ -443,22 +473,16 @@ class WhereClauseMixin:
443
473
 
444
474
  def where_not_like(self, column: Union[str, exp.Column], pattern: str) -> Self:
445
475
  """Add WHERE column NOT LIKE pattern clause."""
446
- builder = cast("SQLBuilderProtocol", self)
447
- column_name = _extract_column_name(column)
448
- param_name = builder._generate_unique_parameter_name(column_name)
449
- _, param_name = builder.add_parameter(pattern, name=param_name)
450
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
451
- condition: exp.Expression = col_expr.like(exp.Placeholder(this=param_name)).not_()
476
+ condition = self._create_parameterized_condition(
477
+ column, pattern, lambda col, placeholder: col.like(placeholder).not_()
478
+ )
452
479
  return self.where(condition)
453
480
 
454
481
  def where_ilike(self, column: Union[str, exp.Column], pattern: str) -> Self:
455
482
  """Add WHERE column ILIKE pattern clause."""
456
- builder = cast("SQLBuilderProtocol", self)
457
- column_name = _extract_column_name(column)
458
- param_name = builder._generate_unique_parameter_name(column_name)
459
- _, param_name = builder.add_parameter(pattern, name=param_name)
460
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
461
- condition: exp.Expression = col_expr.ilike(exp.Placeholder(this=param_name))
483
+ condition = self._create_parameterized_condition(
484
+ column, pattern, lambda col, placeholder: col.ilike(placeholder)
485
+ )
462
486
  return self.where(condition)
463
487
 
464
488
  def where_is_null(self, column: Union[str, exp.Column]) -> Self:
@@ -483,10 +507,11 @@ class WhereClauseMixin:
483
507
  subquery = values.build() # pyright: ignore
484
508
  sql_str = subquery.sql
485
509
  subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=builder.dialect_name)) # pyright: ignore
486
- # Merge subquery parameters into parent builder
510
+ # Merge subquery parameters into parent builder with unique naming
487
511
  if hasattr(subquery, "parameters") and isinstance(subquery.parameters, dict): # pyright: ignore[reportAttributeAccessIssue]
488
512
  for param_name, param_value in subquery.parameters.items(): # pyright: ignore[reportAttributeAccessIssue]
489
- builder.add_parameter(param_value, name=param_name)
513
+ unique_name = builder._generate_unique_parameter_name(param_name)
514
+ builder.add_parameter(param_value, name=unique_name)
490
515
  else:
491
516
  subquery_exp = values # type: ignore[assignment]
492
517
  condition = col_expr.isin(subquery_exp)
@@ -666,6 +691,600 @@ class WhereClauseMixin:
666
691
  condition = exp.NEQ(this=col_expr, expression=exp.Any(this=tuple_expr))
667
692
  return self.where(condition)
668
693
 
694
+ def or_where(
695
+ self,
696
+ condition: Union[
697
+ str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", "SQL"
698
+ ],
699
+ *values: Any,
700
+ operator: Optional[str] = None,
701
+ **kwargs: Any,
702
+ ) -> Self:
703
+ """Add an OR condition to the existing WHERE clause.
704
+
705
+ Args:
706
+ condition: The condition for the OR WHERE clause. Can be:
707
+ - A string condition with or without parameter placeholders
708
+ - A string column name (when values are provided)
709
+ - A sqlglot Expression or Condition
710
+ - A 2-tuple (column, value) for equality comparison
711
+ - A 3-tuple (column, operator, value) for custom comparison
712
+ *values: Positional values for parameter binding (when condition contains placeholders or is a column name)
713
+ operator: Operator for comparison (when condition is a column name)
714
+ **kwargs: Named parameters for parameter binding (when condition contains named placeholders)
715
+
716
+ Raises:
717
+ SQLBuilderError: If the current expression is not a supported statement type or no existing WHERE clause.
718
+
719
+ Returns:
720
+ The current builder instance for method chaining.
721
+ """
722
+ builder = cast("SQLBuilderProtocol", self)
723
+ if builder._expression is None:
724
+ msg = "Cannot add OR WHERE clause: expression is not initialized."
725
+ raise SQLBuilderError(msg)
726
+
727
+ # Get the existing WHERE condition
728
+ existing_where = None
729
+ if isinstance(builder._expression, (exp.Select, exp.Update, exp.Delete)):
730
+ existing_where = builder._expression.find(exp.Where)
731
+ if existing_where:
732
+ existing_where = existing_where.this
733
+
734
+ if existing_where is None:
735
+ msg = "Cannot add OR WHERE clause: no existing WHERE clause found. Use where() first."
736
+ raise SQLBuilderError(msg)
737
+
738
+ # Process the new condition (reuse existing logic from where method)
739
+ new_condition = self._process_where_condition(condition, values, operator, kwargs)
740
+
741
+ # Combine with existing WHERE using OR
742
+ or_condition = exp.Or(this=existing_where, expression=new_condition)
743
+
744
+ # Update the WHERE clause by modifying the existing WHERE node
745
+ if isinstance(builder._expression, (exp.Select, exp.Update, exp.Delete)):
746
+ where_node = builder._expression.find(exp.Where)
747
+ if where_node:
748
+ where_node.set("this", or_condition)
749
+ else:
750
+ # This shouldn't happen since we checked for existing_where above
751
+ builder._expression = builder._expression.where(or_condition, copy=False)
752
+ else:
753
+ msg = f"OR WHERE clause not supported for {type(builder._expression).__name__}"
754
+ raise SQLBuilderError(msg)
755
+
756
+ return self
757
+
758
+ def where_or(self, *conditions: Union[str, "tuple[Any, ...]", exp.Expression]) -> Self:
759
+ """Combine multiple conditions with OR logic.
760
+
761
+ Args:
762
+ *conditions: Multiple conditions to combine with OR. Each condition can be:
763
+ - A string condition
764
+ - A 2-tuple (column, value) for equality comparison
765
+ - A 3-tuple (column, operator, value) for custom comparison
766
+ - A sqlglot Expression or Condition
767
+
768
+ Raises:
769
+ SQLBuilderError: If no conditions provided or current expression not supported.
770
+
771
+ Returns:
772
+ The current builder instance for method chaining.
773
+
774
+ Examples:
775
+ query.where_or(
776
+ ("name", "John"),
777
+ ("email", "john@email.com"),
778
+ "age > 25"
779
+ )
780
+ # Produces: WHERE (name = :name OR email = :email OR age > 25)
781
+ """
782
+ if not conditions:
783
+ msg = "where_or() requires at least one condition"
784
+ raise SQLBuilderError(msg)
785
+
786
+ builder = cast("SQLBuilderProtocol", self)
787
+ if builder._expression is None:
788
+ msg = "Cannot add WHERE OR clause: expression is not initialized."
789
+ raise SQLBuilderError(msg)
790
+
791
+ # Process all conditions
792
+ processed_conditions = []
793
+ for condition in conditions:
794
+ processed_condition = self._process_where_condition(condition, (), None, {})
795
+ processed_conditions.append(processed_condition)
796
+
797
+ # Create OR expression from all conditions
798
+ or_condition = self._create_or_expression(processed_conditions)
799
+
800
+ # Apply the OR condition
801
+ if isinstance(builder._expression, (exp.Select, exp.Update, exp.Delete)):
802
+ builder._expression = builder._expression.where(or_condition, copy=False)
803
+ else:
804
+ msg = f"WHERE OR clause not supported for {type(builder._expression).__name__}"
805
+ raise SQLBuilderError(msg)
806
+
807
+ return self
808
+
809
+ def _process_where_condition(
810
+ self,
811
+ condition: Union[
812
+ str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", "SQL"
813
+ ],
814
+ values: tuple[Any, ...],
815
+ operator: Optional[str],
816
+ kwargs: dict[str, Any],
817
+ ) -> exp.Expression:
818
+ """Process a WHERE condition into a sqlglot expression.
819
+
820
+ This is extracted from the where() method to be reusable by OR methods.
821
+
822
+ Args:
823
+ condition: The condition to process
824
+ values: Positional values for parameter binding
825
+ operator: Operator for comparison
826
+ kwargs: Named parameters for parameter binding
827
+
828
+ Returns:
829
+ Processed sqlglot expression
830
+ """
831
+ builder = cast("SQLBuilderProtocol", self)
832
+
833
+ # Handle string conditions with external parameters
834
+ if values or kwargs:
835
+ if not isinstance(condition, str):
836
+ msg = "When values are provided, condition must be a string"
837
+ raise SQLBuilderError(msg)
838
+
839
+ # Check if condition contains parameter placeholders
840
+ from sqlspec.core.parameters import ParameterStyle, ParameterValidator
841
+
842
+ validator = ParameterValidator()
843
+ param_info = validator.extract_parameters(condition)
844
+
845
+ if param_info:
846
+ # String condition with placeholders - create SQL object with parameters
847
+ from sqlspec import sql as sql_factory
848
+
849
+ # Create parameter mapping based on the detected parameter info
850
+ param_dict = dict(kwargs) # Start with named parameters
851
+
852
+ # Handle positional parameters - these are ordinal-based ($1, $2, :1, :2, ?)
853
+ positional_params = [
854
+ param
855
+ for param in param_info
856
+ if param.style in {ParameterStyle.NUMERIC, ParameterStyle.POSITIONAL_COLON, ParameterStyle.QMARK}
857
+ ]
858
+
859
+ # Map positional values to positional parameters
860
+ if len(values) != len(positional_params):
861
+ msg = f"Parameter count mismatch: condition has {len(positional_params)} positional placeholders, got {len(values)} values"
862
+ raise SQLBuilderError(msg)
863
+
864
+ for i, value in enumerate(values):
865
+ param_dict[f"param_{i}"] = value
866
+
867
+ # Create SQL object with parameters that will be processed correctly
868
+ condition = sql_factory.raw(condition, **param_dict)
869
+ # Fall through to existing SQL object handling logic
870
+
871
+ elif len(values) == 1 and not kwargs:
872
+ # Single value - treat as column = value
873
+ if operator is not None:
874
+ return self._process_tuple_condition((condition, operator, values[0]))
875
+ return self._process_tuple_condition((condition, values[0]))
876
+ else:
877
+ msg = f"Cannot bind parameters to condition without placeholders: {condition}"
878
+ raise SQLBuilderError(msg)
879
+
880
+ # Handle all condition types (including SQL objects created above)
881
+ if isinstance(condition, str):
882
+ return parse_condition_expression(condition)
883
+ if isinstance(condition, (exp.Expression, exp.Condition)):
884
+ return condition
885
+ if isinstance(condition, tuple):
886
+ return self._process_tuple_condition(condition)
887
+ if has_query_builder_parameters(condition):
888
+ column_expr_obj = cast("ColumnExpression", condition)
889
+ return column_expr_obj._expression # pyright: ignore
890
+ if has_sqlglot_expression(condition):
891
+ raw_expr = condition.sqlglot_expression # pyright: ignore[attr-defined]
892
+ if raw_expr is not None:
893
+ return builder._parameterize_expression(raw_expr)
894
+ return parse_condition_expression(str(condition))
895
+ if hasattr(condition, "expression") and hasattr(condition, "sql"):
896
+ # Handle SQL objects (from sql.raw with parameters)
897
+ expression = getattr(condition, "expression", None)
898
+ if expression is not None and isinstance(expression, exp.Expression):
899
+ # Merge parameters from SQL object into builder
900
+ if hasattr(condition, "parameters") and hasattr(builder, "add_parameter"):
901
+ sql_parameters = getattr(condition, "parameters", {})
902
+ for param_name, param_value in sql_parameters.items():
903
+ unique_name = builder._generate_unique_parameter_name(param_name)
904
+ builder.add_parameter(param_value, name=unique_name)
905
+ return cast("exp.Expression", expression)
906
+ # If expression is None, fall back to parsing the raw SQL
907
+ sql_text = getattr(condition, "sql", "")
908
+ # Merge parameters even when parsing raw SQL
909
+ if hasattr(condition, "parameters") and hasattr(builder, "add_parameter"):
910
+ sql_parameters = getattr(condition, "parameters", {})
911
+ for param_name, param_value in sql_parameters.items():
912
+ unique_name = builder._generate_unique_parameter_name(param_name)
913
+ builder.add_parameter(param_value, name=unique_name)
914
+ return parse_condition_expression(sql_text)
915
+ msg = f"Unsupported condition type: {type(condition).__name__}"
916
+ raise SQLBuilderError(msg)
917
+
918
+ def _create_or_expression(self, conditions: list[exp.Expression]) -> exp.Expression:
919
+ """Create OR expression from multiple conditions.
920
+
921
+ Args:
922
+ conditions: List of sqlglot expressions to combine with OR
923
+
924
+ Returns:
925
+ Combined OR expression, or single condition if only one provided
926
+ """
927
+ if len(conditions) == 1:
928
+ return conditions[0]
929
+
930
+ result = conditions[0]
931
+ for condition in conditions[1:]:
932
+ result = exp.Or(this=result, expression=condition)
933
+ return result
934
+
935
+ # OR helper methods for consistency with existing where_* methods
936
+ def or_where_eq(self, column: Union[str, exp.Column], value: Any) -> Self:
937
+ """Add OR column = value clause."""
938
+ condition = self._create_parameterized_condition(column, value, lambda col, placeholder: col.eq(placeholder))
939
+ return self.or_where(condition)
940
+
941
+ def or_where_neq(self, column: Union[str, exp.Column], value: Any) -> Self:
942
+ """Add OR column != value clause."""
943
+ condition = self._create_parameterized_condition(column, value, lambda col, placeholder: col.neq(placeholder))
944
+ return self.or_where(condition)
945
+
946
+ def or_where_in(self, column: Union[str, exp.Column], values: Any) -> Self:
947
+ """Add OR column IN (values) clause."""
948
+ builder = cast("SQLBuilderProtocol", self)
949
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
950
+ if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
951
+ subquery_exp: exp.Expression
952
+ if has_query_builder_parameters(values):
953
+ subquery_builder_parameters: dict[str, Any] = values.parameters # pyright: ignore
954
+ param_mapping = {}
955
+ if subquery_builder_parameters:
956
+ for p_name, p_value in subquery_builder_parameters.items():
957
+ unique_name = builder._generate_unique_parameter_name(p_name)
958
+ param_mapping[p_name] = unique_name
959
+ builder.add_parameter(p_value, name=unique_name)
960
+ subquery = values.build() # pyright: ignore
961
+ subquery_parsed = exp.maybe_parse(subquery.sql, dialect=builder.dialect_name) # pyright: ignore
962
+ if param_mapping and subquery_parsed:
963
+ subquery_parsed = cast("Any", builder)._update_placeholders_in_expression(
964
+ subquery_parsed, param_mapping
965
+ )
966
+ subquery_exp = (
967
+ exp.paren(subquery_parsed)
968
+ if subquery_parsed
969
+ else exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
970
+ ) # pyright: ignore
971
+ else:
972
+ subquery_exp = values # type: ignore[assignment]
973
+ condition = col_expr.isin(subquery_exp)
974
+ return self.or_where(condition)
975
+ if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
976
+ msg = "Unsupported type for 'values' in OR WHERE IN"
977
+ raise SQLBuilderError(msg)
978
+ column_name = _extract_column_name(column)
979
+ parameters = []
980
+ for i, v in enumerate(values):
981
+ if len(values) == 1:
982
+ param_name = builder._generate_unique_parameter_name(column_name)
983
+ else:
984
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
985
+ _, param_name = builder.add_parameter(v, name=param_name)
986
+ parameters.append(exp.Placeholder(this=param_name))
987
+ condition = col_expr.isin(*parameters)
988
+ return self.or_where(condition)
989
+
990
+ def or_where_like(self, column: Union[str, exp.Column], pattern: str, escape: Optional[str] = None) -> Self:
991
+ """Add OR column LIKE pattern clause."""
992
+ builder = cast("SQLBuilderProtocol", self)
993
+ column_name = _extract_column_name(column)
994
+ param_name = builder._generate_unique_parameter_name(column_name)
995
+ _, param_name = builder.add_parameter(pattern, name=param_name)
996
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
997
+ if escape is not None:
998
+ cond = exp.Like(this=col_expr, expression=exp.Placeholder(this=param_name), escape=exp.convert(str(escape)))
999
+ else:
1000
+ cond = col_expr.like(exp.Placeholder(this=param_name))
1001
+ condition: exp.Expression = cond
1002
+ return self.or_where(condition)
1003
+
1004
+ def or_where_is_null(self, column: Union[str, exp.Column]) -> Self:
1005
+ """Add OR column IS NULL clause."""
1006
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
1007
+ condition: exp.Expression = col_expr.is_(exp.null())
1008
+ return self.or_where(condition)
1009
+
1010
+ def or_where_is_not_null(self, column: Union[str, exp.Column]) -> Self:
1011
+ """Add OR column IS NOT NULL clause."""
1012
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
1013
+ condition: exp.Expression = col_expr.is_(exp.null()).not_()
1014
+ return self.or_where(condition)
1015
+
1016
+ def or_where_lt(self, column: Union[str, exp.Column], value: Any) -> Self:
1017
+ """Add OR column < value clause."""
1018
+ condition = self._create_parameterized_condition(
1019
+ column, value, lambda col, placeholder: exp.LT(this=col, expression=placeholder)
1020
+ )
1021
+ return self.or_where(condition)
1022
+
1023
+ def or_where_lte(self, column: Union[str, exp.Column], value: Any) -> Self:
1024
+ """Add OR column <= value clause."""
1025
+ condition = self._create_parameterized_condition(
1026
+ column, value, lambda col, placeholder: exp.LTE(this=col, expression=placeholder)
1027
+ )
1028
+ return self.or_where(condition)
1029
+
1030
+ def or_where_gt(self, column: Union[str, exp.Column], value: Any) -> Self:
1031
+ """Add OR column > value clause."""
1032
+ condition = self._create_parameterized_condition(
1033
+ column, value, lambda col, placeholder: exp.GT(this=col, expression=placeholder)
1034
+ )
1035
+ return self.or_where(condition)
1036
+
1037
+ def or_where_gte(self, column: Union[str, exp.Column], value: Any) -> Self:
1038
+ """Add OR column >= value clause."""
1039
+ condition = self._create_parameterized_condition(
1040
+ column, value, lambda col, placeholder: exp.GTE(this=col, expression=placeholder)
1041
+ )
1042
+ return self.or_where(condition)
1043
+
1044
+ def or_where_between(self, column: Union[str, exp.Column], low: Any, high: Any) -> Self:
1045
+ """Add OR column BETWEEN low AND high clause."""
1046
+ builder = cast("SQLBuilderProtocol", self)
1047
+ column_name = _extract_column_name(column)
1048
+ low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
1049
+ high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
1050
+ _, low_param = builder.add_parameter(low, name=low_param)
1051
+ _, high_param = builder.add_parameter(high, name=high_param)
1052
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
1053
+ condition: exp.Expression = col_expr.between(exp.Placeholder(this=low_param), exp.Placeholder(this=high_param))
1054
+ return self.or_where(condition)
1055
+
1056
+ def or_where_not_like(self, column: Union[str, exp.Column], pattern: str) -> Self:
1057
+ """Add OR column NOT LIKE pattern clause."""
1058
+ condition = self._create_parameterized_condition(
1059
+ column, pattern, lambda col, placeholder: col.like(placeholder).not_()
1060
+ )
1061
+ return self.or_where(condition)
1062
+
1063
+ def or_where_not_in(self, column: Union[str, exp.Column], values: Any) -> Self:
1064
+ """Add OR column NOT IN (values) clause."""
1065
+ builder = cast("SQLBuilderProtocol", self)
1066
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
1067
+ if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
1068
+ subquery_exp: exp.Expression
1069
+ if has_query_builder_parameters(values):
1070
+ subquery_builder_parameters: dict[str, Any] = values.parameters # pyright: ignore
1071
+ param_mapping = {}
1072
+ if subquery_builder_parameters:
1073
+ for p_name, p_value in subquery_builder_parameters.items():
1074
+ unique_name = builder._generate_unique_parameter_name(p_name)
1075
+ param_mapping[p_name] = unique_name
1076
+ builder.add_parameter(p_value, name=unique_name)
1077
+ subquery = values.build() # pyright: ignore
1078
+ subquery_parsed = exp.maybe_parse(subquery.sql, dialect=builder.dialect_name) # pyright: ignore
1079
+ if param_mapping and subquery_parsed:
1080
+ subquery_parsed = cast("Any", builder)._update_placeholders_in_expression(
1081
+ subquery_parsed, param_mapping
1082
+ )
1083
+ subquery_exp = (
1084
+ exp.paren(subquery_parsed)
1085
+ if subquery_parsed
1086
+ else exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
1087
+ ) # pyright: ignore
1088
+ else:
1089
+ subquery_exp = values # type: ignore[assignment]
1090
+ condition = exp.Not(this=col_expr.isin(subquery_exp))
1091
+ return self.or_where(condition)
1092
+ if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
1093
+ msg = "Values for or_where_not_in must be a non-string iterable or subquery."
1094
+ raise SQLBuilderError(msg)
1095
+ column_name = _extract_column_name(column)
1096
+ parameters = []
1097
+ for i, v in enumerate(values):
1098
+ if len(values) == 1:
1099
+ param_name = builder._generate_unique_parameter_name(column_name)
1100
+ else:
1101
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_{i + 1}")
1102
+ _, param_name = builder.add_parameter(v, name=param_name)
1103
+ parameters.append(exp.Placeholder(this=param_name))
1104
+ condition = exp.Not(this=col_expr.isin(*parameters))
1105
+ return self.or_where(condition)
1106
+
1107
+ def or_where_ilike(self, column: Union[str, exp.Column], pattern: str) -> Self:
1108
+ """Add OR column ILIKE pattern clause."""
1109
+ condition = self._create_parameterized_condition(
1110
+ column, pattern, lambda col, placeholder: col.ilike(placeholder)
1111
+ )
1112
+ return self.or_where(condition)
1113
+
1114
+ def or_where_null(self, column: Union[str, exp.Column]) -> Self:
1115
+ """Add OR column IS NULL clause."""
1116
+ return self.or_where_is_null(column)
1117
+
1118
+ def or_where_not_null(self, column: Union[str, exp.Column]) -> Self:
1119
+ """Add OR column IS NOT NULL clause."""
1120
+ return self.or_where_is_not_null(column)
1121
+
1122
+ def or_where_exists(self, subquery: Union[str, Any]) -> Self:
1123
+ """Add OR EXISTS (subquery) clause."""
1124
+ builder = cast("SQLBuilderProtocol", self)
1125
+ sub_expr: exp.Expression
1126
+ if has_query_builder_parameters(subquery):
1127
+ subquery_builder_parameters: dict[str, Any] = subquery.parameters
1128
+ param_mapping = {}
1129
+ if subquery_builder_parameters:
1130
+ for p_name, p_value in subquery_builder_parameters.items():
1131
+ unique_name = builder._generate_unique_parameter_name(p_name)
1132
+ param_mapping[p_name] = unique_name
1133
+ builder.add_parameter(p_value, name=unique_name)
1134
+ sub_sql_obj = subquery.build() # pyright: ignore
1135
+ sub_expr = exp.maybe_parse(sub_sql_obj.sql, dialect=builder.dialect_name) # pyright: ignore
1136
+ # Update placeholders to use unique parameter names
1137
+ if param_mapping and sub_expr:
1138
+ sub_expr = cast("Any", builder)._update_placeholders_in_expression(sub_expr, param_mapping)
1139
+ else:
1140
+ sub_expr = exp.maybe_parse(str(subquery), dialect=builder.dialect_name)
1141
+
1142
+ if sub_expr is None:
1143
+ msg = "Could not parse subquery for OR EXISTS"
1144
+ raise SQLBuilderError(msg)
1145
+
1146
+ exists_expr = exp.Exists(this=sub_expr)
1147
+ return self.or_where(exists_expr)
1148
+
1149
+ def or_where_not_exists(self, subquery: Union[str, Any]) -> Self:
1150
+ """Add OR NOT EXISTS (subquery) clause."""
1151
+ builder = cast("SQLBuilderProtocol", self)
1152
+ sub_expr: exp.Expression
1153
+ if has_query_builder_parameters(subquery):
1154
+ subquery_builder_parameters: dict[str, Any] = subquery.parameters
1155
+ param_mapping = {}
1156
+ if subquery_builder_parameters:
1157
+ for p_name, p_value in subquery_builder_parameters.items():
1158
+ unique_name = builder._generate_unique_parameter_name(p_name)
1159
+ param_mapping[p_name] = unique_name
1160
+ builder.add_parameter(p_value, name=unique_name)
1161
+ sub_sql_obj = subquery.build() # pyright: ignore
1162
+ sub_expr = exp.maybe_parse(sub_sql_obj.sql, dialect=builder.dialect_name) # pyright: ignore
1163
+ # Update placeholders to use unique parameter names
1164
+ if param_mapping and sub_expr:
1165
+ sub_expr = cast("Any", builder)._update_placeholders_in_expression(sub_expr, param_mapping)
1166
+ else:
1167
+ sub_expr = exp.maybe_parse(str(subquery), dialect=builder.dialect_name)
1168
+
1169
+ if sub_expr is None:
1170
+ msg = "Could not parse subquery for OR NOT EXISTS"
1171
+ raise SQLBuilderError(msg)
1172
+
1173
+ not_exists_expr = exp.Not(this=exp.Exists(this=sub_expr))
1174
+ return self.or_where(not_exists_expr)
1175
+
1176
+ def or_where_any(self, column: Union[str, exp.Column], values: Any) -> Self:
1177
+ """Add OR column = ANY(values) clause."""
1178
+ builder = cast("SQLBuilderProtocol", self)
1179
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
1180
+ if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
1181
+ subquery_exp: exp.Expression
1182
+ if has_query_builder_parameters(values):
1183
+ subquery_builder_parameters: dict[str, Any] = values.parameters # pyright: ignore
1184
+ param_mapping = {}
1185
+ if subquery_builder_parameters:
1186
+ for p_name, p_value in subquery_builder_parameters.items():
1187
+ unique_name = builder._generate_unique_parameter_name(p_name)
1188
+ param_mapping[p_name] = unique_name
1189
+ builder.add_parameter(p_value, name=unique_name)
1190
+ subquery = values.build() # pyright: ignore
1191
+ subquery_parsed = exp.maybe_parse(subquery.sql, dialect=builder.dialect_name) # pyright: ignore
1192
+ if param_mapping and subquery_parsed:
1193
+ subquery_parsed = cast("Any", builder)._update_placeholders_in_expression(
1194
+ subquery_parsed, param_mapping
1195
+ )
1196
+ subquery_exp = (
1197
+ exp.paren(subquery_parsed)
1198
+ if subquery_parsed
1199
+ else exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
1200
+ ) # pyright: ignore
1201
+ else:
1202
+ subquery_exp = values # type: ignore[assignment]
1203
+ condition = exp.EQ(this=col_expr, expression=exp.Any(this=subquery_exp))
1204
+ return self.or_where(condition)
1205
+ if isinstance(values, str):
1206
+ try:
1207
+ parsed_expr: Optional[exp.Expression] = exp.maybe_parse(values)
1208
+ if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
1209
+ subquery_exp = exp.paren(parsed_expr)
1210
+ condition = exp.EQ(this=col_expr, expression=exp.Any(this=subquery_exp))
1211
+ return self.or_where(condition)
1212
+ except Exception: # noqa: S110
1213
+ pass
1214
+ msg = "Unsupported type for 'values' in OR WHERE ANY"
1215
+ raise SQLBuilderError(msg)
1216
+ if not is_iterable_parameters(values) or isinstance(values, bytes):
1217
+ msg = "Unsupported type for 'values' in OR WHERE ANY"
1218
+ raise SQLBuilderError(msg)
1219
+ column_name = _extract_column_name(column)
1220
+ parameters = []
1221
+ for i, v in enumerate(values):
1222
+ if len(values) == 1:
1223
+ param_name = builder._generate_unique_parameter_name(column_name)
1224
+ else:
1225
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_any_{i + 1}")
1226
+ _, param_name = builder.add_parameter(v, name=param_name)
1227
+ parameters.append(exp.Placeholder(this=param_name))
1228
+ tuple_expr = exp.Tuple(expressions=parameters)
1229
+ condition = exp.EQ(this=col_expr, expression=exp.Any(this=tuple_expr))
1230
+ return self.or_where(condition)
1231
+
1232
+ def or_where_not_any(self, column: Union[str, exp.Column], values: Any) -> Self:
1233
+ """Add OR column <> ANY(values) clause."""
1234
+ builder = cast("SQLBuilderProtocol", self)
1235
+ col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
1236
+ if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
1237
+ subquery_exp: exp.Expression
1238
+ if has_query_builder_parameters(values):
1239
+ subquery_builder_parameters: dict[str, Any] = values.parameters # pyright: ignore
1240
+ param_mapping = {}
1241
+ if subquery_builder_parameters:
1242
+ for p_name, p_value in subquery_builder_parameters.items():
1243
+ unique_name = builder._generate_unique_parameter_name(p_name)
1244
+ param_mapping[p_name] = unique_name
1245
+ builder.add_parameter(p_value, name=unique_name)
1246
+ subquery = values.build() # pyright: ignore
1247
+ subquery_parsed = exp.maybe_parse(subquery.sql, dialect=builder.dialect_name) # pyright: ignore
1248
+ if param_mapping and subquery_parsed:
1249
+ subquery_parsed = cast("Any", builder)._update_placeholders_in_expression(
1250
+ subquery_parsed, param_mapping
1251
+ )
1252
+ subquery_exp = (
1253
+ exp.paren(subquery_parsed)
1254
+ if subquery_parsed
1255
+ else exp.paren(exp.maybe_parse(subquery.sql, dialect=builder.dialect_name)) # pyright: ignore
1256
+ ) # pyright: ignore
1257
+ else:
1258
+ subquery_exp = values # type: ignore[assignment]
1259
+ condition = exp.NEQ(this=col_expr, expression=exp.Any(this=subquery_exp))
1260
+ return self.or_where(condition)
1261
+ if isinstance(values, str):
1262
+ try:
1263
+ parsed_expr: Optional[exp.Expression] = exp.maybe_parse(values)
1264
+ if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
1265
+ subquery_exp = exp.paren(parsed_expr)
1266
+ condition = exp.NEQ(this=col_expr, expression=exp.Any(this=subquery_exp))
1267
+ return self.or_where(condition)
1268
+ except Exception: # noqa: S110
1269
+ pass
1270
+ msg = "Unsupported type for 'values' in OR WHERE NOT ANY"
1271
+ raise SQLBuilderError(msg)
1272
+ if not is_iterable_parameters(values) or isinstance(values, bytes):
1273
+ msg = "Unsupported type for 'values' in OR WHERE NOT ANY"
1274
+ raise SQLBuilderError(msg)
1275
+ column_name = _extract_column_name(column)
1276
+ parameters = []
1277
+ for i, v in enumerate(values):
1278
+ if len(values) == 1:
1279
+ param_name = builder._generate_unique_parameter_name(column_name)
1280
+ else:
1281
+ param_name = builder._generate_unique_parameter_name(f"{column_name}_not_any_{i + 1}")
1282
+ _, param_name = builder.add_parameter(v, name=param_name)
1283
+ parameters.append(exp.Placeholder(this=param_name))
1284
+ tuple_expr = exp.Tuple(expressions=parameters)
1285
+ condition = exp.NEQ(this=col_expr, expression=exp.Any(this=tuple_expr))
1286
+ return self.or_where(condition)
1287
+
669
1288
 
670
1289
  @trait
671
1290
  class HavingClauseMixin: