sqlspec 0.24.1__py3-none-any.whl → 0.26.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.

Files changed (95) hide show
  1. sqlspec/_serialization.py +223 -21
  2. sqlspec/_sql.py +20 -62
  3. sqlspec/_typing.py +11 -0
  4. sqlspec/adapters/adbc/config.py +8 -1
  5. sqlspec/adapters/adbc/data_dictionary.py +290 -0
  6. sqlspec/adapters/adbc/driver.py +129 -20
  7. sqlspec/adapters/adbc/type_converter.py +159 -0
  8. sqlspec/adapters/aiosqlite/config.py +3 -0
  9. sqlspec/adapters/aiosqlite/data_dictionary.py +117 -0
  10. sqlspec/adapters/aiosqlite/driver.py +17 -3
  11. sqlspec/adapters/asyncmy/_types.py +1 -1
  12. sqlspec/adapters/asyncmy/config.py +11 -8
  13. sqlspec/adapters/asyncmy/data_dictionary.py +122 -0
  14. sqlspec/adapters/asyncmy/driver.py +31 -7
  15. sqlspec/adapters/asyncpg/config.py +3 -0
  16. sqlspec/adapters/asyncpg/data_dictionary.py +134 -0
  17. sqlspec/adapters/asyncpg/driver.py +19 -4
  18. sqlspec/adapters/bigquery/config.py +3 -0
  19. sqlspec/adapters/bigquery/data_dictionary.py +109 -0
  20. sqlspec/adapters/bigquery/driver.py +21 -3
  21. sqlspec/adapters/bigquery/type_converter.py +93 -0
  22. sqlspec/adapters/duckdb/_types.py +1 -1
  23. sqlspec/adapters/duckdb/config.py +2 -0
  24. sqlspec/adapters/duckdb/data_dictionary.py +124 -0
  25. sqlspec/adapters/duckdb/driver.py +32 -5
  26. sqlspec/adapters/duckdb/pool.py +1 -1
  27. sqlspec/adapters/duckdb/type_converter.py +103 -0
  28. sqlspec/adapters/oracledb/config.py +6 -0
  29. sqlspec/adapters/oracledb/data_dictionary.py +442 -0
  30. sqlspec/adapters/oracledb/driver.py +68 -9
  31. sqlspec/adapters/oracledb/migrations.py +51 -67
  32. sqlspec/adapters/oracledb/type_converter.py +132 -0
  33. sqlspec/adapters/psqlpy/config.py +3 -0
  34. sqlspec/adapters/psqlpy/data_dictionary.py +133 -0
  35. sqlspec/adapters/psqlpy/driver.py +23 -179
  36. sqlspec/adapters/psqlpy/type_converter.py +73 -0
  37. sqlspec/adapters/psycopg/config.py +8 -4
  38. sqlspec/adapters/psycopg/data_dictionary.py +257 -0
  39. sqlspec/adapters/psycopg/driver.py +40 -5
  40. sqlspec/adapters/sqlite/config.py +3 -0
  41. sqlspec/adapters/sqlite/data_dictionary.py +117 -0
  42. sqlspec/adapters/sqlite/driver.py +18 -3
  43. sqlspec/adapters/sqlite/pool.py +13 -4
  44. sqlspec/base.py +3 -4
  45. sqlspec/builder/_base.py +130 -48
  46. sqlspec/builder/_column.py +66 -24
  47. sqlspec/builder/_ddl.py +91 -41
  48. sqlspec/builder/_insert.py +40 -58
  49. sqlspec/builder/_parsing_utils.py +127 -12
  50. sqlspec/builder/_select.py +147 -2
  51. sqlspec/builder/_update.py +1 -1
  52. sqlspec/builder/mixins/_cte_and_set_ops.py +31 -23
  53. sqlspec/builder/mixins/_delete_operations.py +12 -7
  54. sqlspec/builder/mixins/_insert_operations.py +50 -36
  55. sqlspec/builder/mixins/_join_operations.py +15 -30
  56. sqlspec/builder/mixins/_merge_operations.py +210 -78
  57. sqlspec/builder/mixins/_order_limit_operations.py +4 -10
  58. sqlspec/builder/mixins/_pivot_operations.py +1 -0
  59. sqlspec/builder/mixins/_select_operations.py +44 -22
  60. sqlspec/builder/mixins/_update_operations.py +30 -37
  61. sqlspec/builder/mixins/_where_clause.py +52 -70
  62. sqlspec/cli.py +246 -140
  63. sqlspec/config.py +33 -19
  64. sqlspec/core/__init__.py +3 -2
  65. sqlspec/core/cache.py +298 -352
  66. sqlspec/core/compiler.py +61 -4
  67. sqlspec/core/filters.py +246 -213
  68. sqlspec/core/hashing.py +9 -11
  69. sqlspec/core/parameters.py +27 -10
  70. sqlspec/core/statement.py +72 -12
  71. sqlspec/core/type_conversion.py +234 -0
  72. sqlspec/driver/__init__.py +6 -3
  73. sqlspec/driver/_async.py +108 -5
  74. sqlspec/driver/_common.py +186 -17
  75. sqlspec/driver/_sync.py +108 -5
  76. sqlspec/driver/mixins/_result_tools.py +60 -7
  77. sqlspec/exceptions.py +5 -0
  78. sqlspec/loader.py +8 -9
  79. sqlspec/migrations/__init__.py +4 -3
  80. sqlspec/migrations/base.py +153 -14
  81. sqlspec/migrations/commands.py +34 -96
  82. sqlspec/migrations/context.py +145 -0
  83. sqlspec/migrations/loaders.py +25 -8
  84. sqlspec/migrations/runner.py +352 -82
  85. sqlspec/storage/backends/fsspec.py +1 -0
  86. sqlspec/typing.py +4 -0
  87. sqlspec/utils/config_resolver.py +153 -0
  88. sqlspec/utils/serializers.py +50 -2
  89. {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/METADATA +1 -1
  90. sqlspec-0.26.0.dist-info/RECORD +157 -0
  91. sqlspec-0.24.1.dist-info/RECORD +0 -139
  92. {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/WHEEL +0 -0
  93. {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/entry_points.txt +0 -0
  94. {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/licenses/LICENSE +0 -0
  95. {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,3 +1,4 @@
1
+ # pyright: reportPrivateUsage=false
1
2
  """UPDATE operation mixins.
2
3
 
3
4
  Provides mixins for UPDATE statement functionality including
@@ -5,12 +6,13 @@ table specification, SET clauses, and FROM clauses.
5
6
  """
6
7
 
7
8
  from collections.abc import Mapping
8
- from typing import Any, Optional, Union, cast
9
+ from typing import Any, Optional, Union
9
10
 
10
11
  from mypy_extensions import trait
11
12
  from sqlglot import exp
12
13
  from typing_extensions import Self
13
14
 
15
+ from sqlspec.builder._parsing_utils import extract_sql_object_expression
14
16
  from sqlspec.exceptions import SQLBuilderError
15
17
  from sqlspec.utils.type_guards import has_query_builder_parameters
16
18
 
@@ -25,8 +27,8 @@ class UpdateTableClauseMixin:
25
27
 
26
28
  __slots__ = ()
27
29
 
28
- # Type annotation for PyRight - this will be provided by the base class
29
- _expression: Optional[exp.Expression]
30
+ def get_expression(self) -> Optional[exp.Expression]: ...
31
+ def set_expression(self, expression: exp.Expression) -> None: ...
30
32
 
31
33
  def table(self, table_name: str, alias: Optional[str] = None) -> Self:
32
34
  """Set the table to update.
@@ -38,10 +40,14 @@ class UpdateTableClauseMixin:
38
40
  Returns:
39
41
  The current builder instance for method chaining.
40
42
  """
41
- if self._expression is None or not isinstance(self._expression, exp.Update):
42
- self._expression = exp.Update(this=None, expressions=[], joins=[])
43
+ current_expr = self.get_expression()
44
+ if current_expr is None or not isinstance(current_expr, exp.Update):
45
+ self.set_expression(exp.Update(this=None, expressions=[], joins=[]))
46
+ current_expr = self.get_expression()
47
+
48
+ assert current_expr is not None
43
49
  table_expr: exp.Expression = exp.to_table(table_name, alias=alias)
44
- self._expression.set("this", table_expr)
50
+ current_expr.set("this", table_expr)
45
51
  setattr(self, "_table", table_name)
46
52
  return self
47
53
 
@@ -52,8 +58,8 @@ class UpdateSetClauseMixin:
52
58
 
53
59
  __slots__ = ()
54
60
 
55
- # Type annotation for PyRight - this will be provided by the base class
56
- _expression: Optional[exp.Expression]
61
+ def get_expression(self) -> Optional[exp.Expression]: ...
62
+ def set_expression(self, expression: exp.Expression) -> None: ...
57
63
 
58
64
  def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
59
65
  """Add parameter - provided by QueryBuilder."""
@@ -86,24 +92,7 @@ class UpdateSetClauseMixin:
86
92
  self.add_parameter(p_value, name=p_name)
87
93
  return value_expr
88
94
  if hasattr(val, "expression") and hasattr(val, "sql"):
89
- # Handle SQL objects (from sql.raw with parameters)
90
- expression = getattr(val, "expression", None)
91
- if expression is not None and isinstance(expression, exp.Expression):
92
- # Merge parameters from SQL object into builder
93
- if hasattr(val, "parameters"):
94
- sql_parameters = getattr(val, "parameters", {})
95
- for param_name, param_value in sql_parameters.items():
96
- self.add_parameter(param_value, name=param_name)
97
- return cast("exp.Expression", expression)
98
- # If expression is None, fall back to parsing the raw SQL
99
- sql_text = getattr(val, "sql", "")
100
- # Merge parameters even when parsing raw SQL
101
- if hasattr(val, "parameters"):
102
- sql_parameters = getattr(val, "parameters", {})
103
- for param_name, param_value in sql_parameters.items():
104
- self.add_parameter(param_value, name=param_name)
105
- parsed_expr = exp.maybe_parse(sql_text)
106
- return parsed_expr if parsed_expr is not None else exp.convert(str(sql_text))
95
+ return extract_sql_object_expression(val, builder=self)
107
96
  column_name = col if isinstance(col, str) else str(col)
108
97
  if "." in column_name:
109
98
  column_name = column_name.split(".")[-1]
@@ -130,9 +119,12 @@ class UpdateSetClauseMixin:
130
119
  Returns:
131
120
  The current builder instance for method chaining.
132
121
  """
133
- if self._expression is None:
134
- self._expression = exp.Update()
135
- if not isinstance(self._expression, exp.Update):
122
+ current_expr = self.get_expression()
123
+ if current_expr is None:
124
+ self.set_expression(exp.Update())
125
+ current_expr = self.get_expression()
126
+
127
+ if not isinstance(current_expr, exp.Update):
136
128
  msg = "Cannot add SET clause to non-UPDATE expression."
137
129
  raise SQLBuilderError(msg)
138
130
  assignments = []
@@ -149,8 +141,8 @@ class UpdateSetClauseMixin:
149
141
  else:
150
142
  msg = "Invalid arguments for set(): use (column, value), mapping, or kwargs."
151
143
  raise SQLBuilderError(msg)
152
- existing = self._expression.args.get("expressions", [])
153
- self._expression.set("expressions", existing + assignments)
144
+ existing = current_expr.args.get("expressions", [])
145
+ current_expr.set("expressions", existing + assignments)
154
146
  return self
155
147
 
156
148
 
@@ -160,8 +152,8 @@ class UpdateFromClauseMixin:
160
152
 
161
153
  __slots__ = ()
162
154
 
163
- # Type annotation for PyRight - this will be provided by the base class
164
- _expression: Optional[exp.Expression]
155
+ def get_expression(self) -> Optional[exp.Expression]: ...
156
+ def set_expression(self, expression: exp.Expression) -> None: ...
165
157
 
166
158
  def from_(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
167
159
  """Add a FROM clause to the UPDATE statement.
@@ -176,7 +168,8 @@ class UpdateFromClauseMixin:
176
168
  Raises:
177
169
  SQLBuilderError: If the current expression is not an UPDATE statement.
178
170
  """
179
- if self._expression is None or not isinstance(self._expression, exp.Update):
171
+ current_expr = self.get_expression()
172
+ if current_expr is None or not isinstance(current_expr, exp.Update):
180
173
  msg = "Cannot add FROM clause to non-UPDATE expression. Set the main table first."
181
174
  raise SQLBuilderError(msg)
182
175
  table_expr: exp.Expression
@@ -194,9 +187,9 @@ class UpdateFromClauseMixin:
194
187
  else:
195
188
  msg = f"Unsupported table type for FROM clause: {type(table)}"
196
189
  raise SQLBuilderError(msg)
197
- if self._expression.args.get("from") is None:
198
- self._expression.set("from", exp.From(expressions=[]))
199
- from_clause = self._expression.args["from"]
190
+ if current_expr.args.get("from") is None:
191
+ current_expr.set("from", exp.From(expressions=[]))
192
+ from_clause = current_expr.args["from"]
200
193
  if hasattr(from_clause, "append"):
201
194
  from_clause.append("expressions", table_expr)
202
195
  else:
@@ -1,4 +1,5 @@
1
1
  # ruff: noqa: PLR2004
2
+ # pyright: reportPrivateUsage=false, reportPrivateImportUsage=false
2
3
  """WHERE and HAVING clause mixins.
3
4
 
4
5
  Provides mixins for WHERE and HAVING clause functionality with
@@ -14,7 +15,9 @@ from mypy_extensions import trait
14
15
  from sqlglot import exp
15
16
  from typing_extensions import Self
16
17
 
17
- from sqlspec.builder._parsing_utils import parse_column_expression, parse_condition_expression
18
+ from sqlspec.builder._parsing_utils import extract_column_name, parse_column_expression, parse_condition_expression
19
+ from sqlspec.core.parameters import ParameterStyle, ParameterValidator
20
+ from sqlspec.core.statement import SQL
18
21
  from sqlspec.exceptions import SQLBuilderError
19
22
  from sqlspec.utils.type_guards import (
20
23
  has_expression_and_parameters,
@@ -24,31 +27,6 @@ from sqlspec.utils.type_guards import (
24
27
  is_iterable_parameters,
25
28
  )
26
29
 
27
-
28
- def _extract_column_name(column: Union[str, exp.Column]) -> str:
29
- """Extract column name from column expression for parameter naming.
30
-
31
- Args:
32
- column: Column expression (string or SQLGlot Column)
33
-
34
- Returns:
35
- Column name as string for use as parameter name
36
- """
37
- if isinstance(column, str):
38
- # Handle simple column names and table.column references
39
- if "." in column:
40
- return column.split(".")[-1] # Return just the column part
41
- return column
42
- if isinstance(column, exp.Column):
43
- # Extract the column name from SQLGlot Column expression
44
- try:
45
- return str(column.this.this)
46
- except AttributeError:
47
- return str(column.this) if column.this else "column"
48
- # Fallback for any unexpected types (defensive programming)
49
- return "column"
50
-
51
-
52
30
  if TYPE_CHECKING:
53
31
  from sqlspec.builder._column import ColumnExpression
54
32
  from sqlspec.protocols import SQLBuilderProtocol
@@ -62,8 +40,9 @@ class WhereClauseMixin:
62
40
 
63
41
  __slots__ = ()
64
42
 
65
- # Type annotation for PyRight - this will be provided by the base class
66
- _expression: Optional[exp.Expression]
43
+ # Type annotations for PyRight - these will be provided by the base class
44
+ def get_expression(self) -> Optional[exp.Expression]: ...
45
+ def set_expression(self, expression: exp.Expression) -> None: ...
67
46
 
68
47
  def _create_parameterized_condition(
69
48
  self,
@@ -82,7 +61,7 @@ class WhereClauseMixin:
82
61
  The created condition expression
83
62
  """
84
63
  builder = cast("SQLBuilderProtocol", self)
85
- column_name = _extract_column_name(column)
64
+ column_name = extract_column_name(column)
86
65
  param_name = builder._generate_unique_parameter_name(column_name)
87
66
  _, param_name = builder.add_parameter(value, name=param_name)
88
67
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
@@ -118,18 +97,20 @@ class WhereClauseMixin:
118
97
  Self with OR condition applied
119
98
  """
120
99
  # Create a temporary clone to capture the condition
121
- original_expr = self._expression
100
+ original_expr = self.get_expression()
122
101
 
123
102
  # Apply the where method to get the condition
124
103
  where_method(*args, **kwargs)
125
104
 
126
105
  # 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)
106
+ current_expr = self.get_expression()
107
+ if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)) and original_expr != current_expr:
108
+ last_where = current_expr.find(exp.Where)
129
109
  if last_where and last_where.this:
130
110
  condition = last_where.this
131
111
  # Restore original expression
132
- self._expression = original_expr
112
+ if original_expr is not None:
113
+ self.set_expression(original_expr)
133
114
  # Apply as OR
134
115
  return self.or_where(condition)
135
116
 
@@ -236,7 +217,7 @@ class WhereClauseMixin:
236
217
  column_name_raw, operator, value = condition
237
218
  operator = str(operator).upper()
238
219
  column_exp = parse_column_expression(column_name_raw)
239
- column_name = _extract_column_name(column_name_raw)
220
+ column_name = extract_column_name(column_name_raw)
240
221
 
241
222
  # Simple operators that use direct parameterization
242
223
  simple_operators = {
@@ -299,16 +280,17 @@ class WhereClauseMixin:
299
280
  Returns:
300
281
  The current builder instance for method chaining.
301
282
  """
302
- if self.__class__.__name__ == "Update" and not isinstance(self._expression, exp.Update):
283
+ current_expr = self.get_expression()
284
+ if self.__class__.__name__ == "Update" and not isinstance(current_expr, exp.Update):
303
285
  msg = "Cannot add WHERE clause to non-UPDATE expression"
304
286
  raise SQLBuilderError(msg)
305
287
 
306
288
  builder = cast("SQLBuilderProtocol", self)
307
- if builder._expression is None:
289
+ if current_expr is None:
308
290
  msg = "Cannot add WHERE clause: expression is not initialized."
309
291
  raise SQLBuilderError(msg)
310
292
 
311
- if isinstance(builder._expression, exp.Delete) and not builder._expression.args.get("this"):
293
+ if isinstance(current_expr, exp.Delete) and not current_expr.args.get("this"):
312
294
  msg = "WHERE clause requires a table to be set. Use from() to set the table first."
313
295
  raise SQLBuilderError(msg)
314
296
 
@@ -319,15 +301,11 @@ class WhereClauseMixin:
319
301
  raise SQLBuilderError(msg)
320
302
 
321
303
  # Check if condition contains parameter placeholders
322
- from sqlspec.core.parameters import ParameterStyle, ParameterValidator
323
-
324
304
  validator = ParameterValidator()
325
305
  param_info = validator.extract_parameters(condition)
326
306
 
327
307
  if param_info:
328
308
  # String condition with placeholders - create SQL object with parameters
329
- from sqlspec import sql as sql_factory
330
-
331
309
  # Create parameter mapping based on the detected parameter info
332
310
  param_dict = dict(kwargs) # Start with named parameters
333
311
 
@@ -347,7 +325,7 @@ class WhereClauseMixin:
347
325
  param_dict[f"param_{i}"] = value
348
326
 
349
327
  # Create SQL object with parameters that will be processed correctly
350
- condition = sql_factory.raw(condition, **param_dict)
328
+ condition = SQL(condition, param_dict)
351
329
  # Fall through to existing SQL object handling logic
352
330
 
353
331
  elif len(values) == 1 and not kwargs:
@@ -357,10 +335,11 @@ class WhereClauseMixin:
357
335
  else:
358
336
  where_expr = self._process_tuple_condition((condition, values[0]))
359
337
  # Process this condition and skip the rest
360
- if isinstance(builder._expression, (exp.Select, exp.Update, exp.Delete)):
361
- builder._expression = builder._expression.where(where_expr, copy=False)
338
+ if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)):
339
+ updated_expr = current_expr.where(where_expr, copy=False)
340
+ self.set_expression(updated_expr)
362
341
  else:
363
- msg = f"WHERE clause not supported for {type(builder._expression).__name__}"
342
+ msg = f"WHERE clause not supported for {type(current_expr).__name__}"
364
343
  raise SQLBuilderError(msg)
365
344
  return self
366
345
  else:
@@ -400,10 +379,11 @@ class WhereClauseMixin:
400
379
  msg = f"Unsupported condition type: {type(condition).__name__}"
401
380
  raise SQLBuilderError(msg)
402
381
 
403
- if isinstance(builder._expression, (exp.Select, exp.Update, exp.Delete)):
404
- builder._expression = builder._expression.where(where_expr, copy=False)
382
+ if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)):
383
+ updated_expr = current_expr.where(where_expr, copy=False)
384
+ self.set_expression(updated_expr)
405
385
  else:
406
- msg = f"WHERE clause not supported for {type(builder._expression).__name__}"
386
+ msg = f"WHERE clause not supported for {type(current_expr).__name__}"
407
387
  raise SQLBuilderError(msg)
408
388
  return self
409
389
 
@@ -448,7 +428,7 @@ class WhereClauseMixin:
448
428
  def where_between(self, column: Union[str, exp.Column], low: Any, high: Any) -> Self:
449
429
  """Add WHERE column BETWEEN low AND high clause."""
450
430
  builder = cast("SQLBuilderProtocol", self)
451
- column_name = _extract_column_name(column)
431
+ column_name = extract_column_name(column)
452
432
  low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
453
433
  high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
454
434
  _, low_param = builder.add_parameter(low, name=low_param)
@@ -460,7 +440,7 @@ class WhereClauseMixin:
460
440
  def where_like(self, column: Union[str, exp.Column], pattern: str, escape: Optional[str] = None) -> Self:
461
441
  """Add WHERE column LIKE pattern clause."""
462
442
  builder = cast("SQLBuilderProtocol", self)
463
- column_name = _extract_column_name(column)
443
+ column_name = extract_column_name(column)
464
444
  param_name = builder._generate_unique_parameter_name(column_name)
465
445
  _, param_name = builder.add_parameter(pattern, name=param_name)
466
446
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
@@ -519,7 +499,7 @@ class WhereClauseMixin:
519
499
  if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
520
500
  msg = "Unsupported type for 'values' in WHERE IN"
521
501
  raise SQLBuilderError(msg)
522
- column_name = _extract_column_name(column)
502
+ column_name = extract_column_name(column)
523
503
  parameters = []
524
504
  for i, v in enumerate(values):
525
505
  if len(values) == 1:
@@ -548,7 +528,7 @@ class WhereClauseMixin:
548
528
  if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
549
529
  msg = "Values for where_not_in must be a non-string iterable or subquery."
550
530
  raise SQLBuilderError(msg)
551
- column_name = _extract_column_name(column)
531
+ column_name = extract_column_name(column)
552
532
  parameters = []
553
533
  for i, v in enumerate(values):
554
534
  if len(values) == 1:
@@ -638,7 +618,7 @@ class WhereClauseMixin:
638
618
  if not is_iterable_parameters(values) or isinstance(values, bytes):
639
619
  msg = "Unsupported type for 'values' in WHERE ANY"
640
620
  raise SQLBuilderError(msg)
641
- column_name = _extract_column_name(column)
621
+ column_name = extract_column_name(column)
642
622
  parameters = []
643
623
  for i, v in enumerate(values):
644
624
  if len(values) == 1:
@@ -678,7 +658,7 @@ class WhereClauseMixin:
678
658
  if not is_iterable_parameters(values) or isinstance(values, bytes):
679
659
  msg = "Unsupported type for 'values' in WHERE NOT ANY"
680
660
  raise SQLBuilderError(msg)
681
- column_name = _extract_column_name(column)
661
+ column_name = extract_column_name(column)
682
662
  parameters = []
683
663
  for i, v in enumerate(values):
684
664
  if len(values) == 1:
@@ -837,15 +817,11 @@ class WhereClauseMixin:
837
817
  raise SQLBuilderError(msg)
838
818
 
839
819
  # Check if condition contains parameter placeholders
840
- from sqlspec.core.parameters import ParameterStyle, ParameterValidator
841
-
842
820
  validator = ParameterValidator()
843
821
  param_info = validator.extract_parameters(condition)
844
822
 
845
823
  if param_info:
846
824
  # String condition with placeholders - create SQL object with parameters
847
- from sqlspec import sql as sql_factory
848
-
849
825
  # Create parameter mapping based on the detected parameter info
850
826
  param_dict = dict(kwargs) # Start with named parameters
851
827
 
@@ -865,7 +841,7 @@ class WhereClauseMixin:
865
841
  param_dict[f"param_{i}"] = value
866
842
 
867
843
  # Create SQL object with parameters that will be processed correctly
868
- condition = sql_factory.raw(condition, **param_dict)
844
+ condition = SQL(condition, param_dict)
869
845
  # Fall through to existing SQL object handling logic
870
846
 
871
847
  elif len(values) == 1 and not kwargs:
@@ -975,7 +951,7 @@ class WhereClauseMixin:
975
951
  if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
976
952
  msg = "Unsupported type for 'values' in OR WHERE IN"
977
953
  raise SQLBuilderError(msg)
978
- column_name = _extract_column_name(column)
954
+ column_name = extract_column_name(column)
979
955
  parameters = []
980
956
  for i, v in enumerate(values):
981
957
  if len(values) == 1:
@@ -990,7 +966,7 @@ class WhereClauseMixin:
990
966
  def or_where_like(self, column: Union[str, exp.Column], pattern: str, escape: Optional[str] = None) -> Self:
991
967
  """Add OR column LIKE pattern clause."""
992
968
  builder = cast("SQLBuilderProtocol", self)
993
- column_name = _extract_column_name(column)
969
+ column_name = extract_column_name(column)
994
970
  param_name = builder._generate_unique_parameter_name(column_name)
995
971
  _, param_name = builder.add_parameter(pattern, name=param_name)
996
972
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
@@ -1044,7 +1020,7 @@ class WhereClauseMixin:
1044
1020
  def or_where_between(self, column: Union[str, exp.Column], low: Any, high: Any) -> Self:
1045
1021
  """Add OR column BETWEEN low AND high clause."""
1046
1022
  builder = cast("SQLBuilderProtocol", self)
1047
- column_name = _extract_column_name(column)
1023
+ column_name = extract_column_name(column)
1048
1024
  low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
1049
1025
  high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
1050
1026
  _, low_param = builder.add_parameter(low, name=low_param)
@@ -1092,7 +1068,7 @@ class WhereClauseMixin:
1092
1068
  if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
1093
1069
  msg = "Values for or_where_not_in must be a non-string iterable or subquery."
1094
1070
  raise SQLBuilderError(msg)
1095
- column_name = _extract_column_name(column)
1071
+ column_name = extract_column_name(column)
1096
1072
  parameters = []
1097
1073
  for i, v in enumerate(values):
1098
1074
  if len(values) == 1:
@@ -1216,7 +1192,7 @@ class WhereClauseMixin:
1216
1192
  if not is_iterable_parameters(values) or isinstance(values, bytes):
1217
1193
  msg = "Unsupported type for 'values' in OR WHERE ANY"
1218
1194
  raise SQLBuilderError(msg)
1219
- column_name = _extract_column_name(column)
1195
+ column_name = extract_column_name(column)
1220
1196
  parameters = []
1221
1197
  for i, v in enumerate(values):
1222
1198
  if len(values) == 1:
@@ -1272,7 +1248,7 @@ class WhereClauseMixin:
1272
1248
  if not is_iterable_parameters(values) or isinstance(values, bytes):
1273
1249
  msg = "Unsupported type for 'values' in OR WHERE NOT ANY"
1274
1250
  raise SQLBuilderError(msg)
1275
- column_name = _extract_column_name(column)
1251
+ column_name = extract_column_name(column)
1276
1252
  parameters = []
1277
1253
  for i, v in enumerate(values):
1278
1254
  if len(values) == 1:
@@ -1292,7 +1268,9 @@ class HavingClauseMixin:
1292
1268
 
1293
1269
  __slots__ = ()
1294
1270
 
1295
- _expression: Optional[exp.Expression]
1271
+ # Type annotations for PyRight - these will be provided by the base class
1272
+ def get_expression(self) -> Optional[exp.Expression]: ...
1273
+ def set_expression(self, expression: exp.Expression) -> None: ...
1296
1274
 
1297
1275
  def having(self, condition: Union[str, exp.Expression]) -> Self:
1298
1276
  """Add HAVING clause.
@@ -1306,11 +1284,15 @@ class HavingClauseMixin:
1306
1284
  Returns:
1307
1285
  The current builder instance for method chaining.
1308
1286
  """
1309
- if self._expression is None:
1310
- self._expression = exp.Select()
1311
- if not isinstance(self._expression, exp.Select):
1287
+ current_expr = self.get_expression()
1288
+ if current_expr is None:
1289
+ self.set_expression(exp.Select())
1290
+ current_expr = self.get_expression()
1291
+
1292
+ if not isinstance(current_expr, exp.Select):
1312
1293
  msg = "Cannot add HAVING to a non-SELECT expression."
1313
1294
  raise SQLBuilderError(msg)
1314
1295
  having_expr = exp.condition(condition) if isinstance(condition, str) else condition
1315
- self._expression = self._expression.having(having_expr, copy=False)
1296
+ updated_expr = current_expr.having(having_expr, copy=False)
1297
+ self.set_expression(updated_expr)
1316
1298
  return self