sqlspec 0.24.0__py3-none-any.whl → 0.25.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 (42) hide show
  1. sqlspec/_sql.py +21 -23
  2. sqlspec/_typing.py +2 -0
  3. sqlspec/adapters/adbc/driver.py +2 -2
  4. sqlspec/adapters/oracledb/driver.py +5 -0
  5. sqlspec/adapters/psycopg/config.py +2 -4
  6. sqlspec/base.py +3 -4
  7. sqlspec/builder/_base.py +55 -13
  8. sqlspec/builder/_column.py +9 -0
  9. sqlspec/builder/_ddl.py +7 -7
  10. sqlspec/builder/_insert.py +10 -6
  11. sqlspec/builder/_parsing_utils.py +23 -4
  12. sqlspec/builder/_update.py +1 -1
  13. sqlspec/builder/mixins/_cte_and_set_ops.py +31 -22
  14. sqlspec/builder/mixins/_delete_operations.py +12 -7
  15. sqlspec/builder/mixins/_insert_operations.py +50 -36
  16. sqlspec/builder/mixins/_join_operations.py +1 -0
  17. sqlspec/builder/mixins/_merge_operations.py +54 -28
  18. sqlspec/builder/mixins/_order_limit_operations.py +1 -0
  19. sqlspec/builder/mixins/_pivot_operations.py +1 -0
  20. sqlspec/builder/mixins/_select_operations.py +42 -14
  21. sqlspec/builder/mixins/_update_operations.py +30 -18
  22. sqlspec/builder/mixins/_where_clause.py +48 -60
  23. sqlspec/core/__init__.py +3 -2
  24. sqlspec/core/cache.py +297 -351
  25. sqlspec/core/compiler.py +5 -3
  26. sqlspec/core/filters.py +246 -213
  27. sqlspec/core/hashing.py +9 -11
  28. sqlspec/core/parameters.py +20 -7
  29. sqlspec/core/statement.py +67 -12
  30. sqlspec/driver/_async.py +2 -2
  31. sqlspec/driver/_common.py +31 -14
  32. sqlspec/driver/_sync.py +2 -2
  33. sqlspec/driver/mixins/_result_tools.py +60 -7
  34. sqlspec/loader.py +8 -9
  35. sqlspec/storage/backends/fsspec.py +1 -0
  36. sqlspec/typing.py +2 -0
  37. {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/METADATA +1 -1
  38. {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/RECORD +42 -42
  39. {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/WHEEL +0 -0
  40. {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/entry_points.txt +0 -0
  41. {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/licenses/LICENSE +0 -0
  42. {sqlspec-0.24.0.dist-info → sqlspec-0.25.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,3 +1,4 @@
1
+ # pyright: reportPrivateUsage=false
1
2
  """DELETE operation mixins.
2
3
 
3
4
  Provides mixins for DELETE statement functionality including
@@ -21,8 +22,9 @@ class DeleteFromClauseMixin:
21
22
 
22
23
  __slots__ = ()
23
24
 
24
- # Type annotation for PyRight - this will be provided by the base class
25
- _expression: Optional[exp.Expression]
25
+ # Type annotations for PyRight - these will be provided by the base class
26
+ def get_expression(self) -> Optional[exp.Expression]: ...
27
+ def set_expression(self, expression: exp.Expression) -> None: ...
26
28
 
27
29
  def from_(self, table: str) -> Self:
28
30
  """Set the target table for the DELETE statement.
@@ -33,13 +35,16 @@ class DeleteFromClauseMixin:
33
35
  Returns:
34
36
  The current builder instance for method chaining.
35
37
  """
36
- if self._expression is None:
37
- self._expression = exp.Delete()
38
- if not isinstance(self._expression, exp.Delete):
39
- current_expr_type = type(self._expression).__name__
38
+ current_expr = self.get_expression()
39
+ if current_expr is None:
40
+ self.set_expression(exp.Delete())
41
+ current_expr = self.get_expression()
42
+
43
+ if not isinstance(current_expr, exp.Delete):
44
+ current_expr_type = type(current_expr).__name__
40
45
  msg = f"Base expression for Delete is {current_expr_type}, expected Delete."
41
46
  raise SQLBuilderError(msg)
42
47
 
43
48
  setattr(self, "_table", table)
44
- self._expression.set("this", exp.to_table(table))
49
+ current_expr.set("this", exp.to_table(table))
45
50
  return self
@@ -1,3 +1,4 @@
1
+ # pyright: reportPrivateUsage=false
1
2
  """INSERT operation mixins.
2
3
 
3
4
  Provides mixins for INSERT statement functionality including
@@ -25,8 +26,9 @@ class InsertIntoClauseMixin:
25
26
 
26
27
  __slots__ = ()
27
28
 
28
- # Type annotation for PyRight - this will be provided by the base class
29
- _expression: Optional[exp.Expression]
29
+ # Type annotations for PyRight - these will be provided by the base class
30
+ def get_expression(self) -> Optional[exp.Expression]: ...
31
+ def set_expression(self, expression: exp.Expression) -> None: ...
30
32
 
31
33
  def into(self, table: str) -> Self:
32
34
  """Set the target table for the INSERT statement.
@@ -40,14 +42,17 @@ class InsertIntoClauseMixin:
40
42
  Returns:
41
43
  The current builder instance for method chaining.
42
44
  """
43
- if self._expression is None:
44
- self._expression = exp.Insert()
45
- if not isinstance(self._expression, exp.Insert):
45
+ current_expr = self.get_expression()
46
+ if current_expr is None:
47
+ self.set_expression(exp.Insert())
48
+ current_expr = self.get_expression()
49
+
50
+ if not isinstance(current_expr, exp.Insert):
46
51
  msg = "Cannot set target table on a non-INSERT expression."
47
52
  raise SQLBuilderError(msg)
48
53
 
49
54
  setattr(self, "_table", table)
50
- self._expression.set("this", exp.to_table(table))
55
+ current_expr.set("this", exp.to_table(table))
51
56
  return self
52
57
 
53
58
 
@@ -57,8 +62,9 @@ class InsertValuesMixin:
57
62
 
58
63
  __slots__ = ()
59
64
 
60
- # Type annotation for PyRight - this will be provided by the base class
61
- _expression: Optional[exp.Expression]
65
+ # Type annotations for PyRight - these will be provided by the base class
66
+ def get_expression(self) -> Optional[exp.Expression]: ...
67
+ def set_expression(self, expression: exp.Expression) -> None: ...
62
68
 
63
69
  _columns: Any # Provided by QueryBuilder
64
70
 
@@ -74,14 +80,17 @@ class InsertValuesMixin:
74
80
 
75
81
  def columns(self, *columns: Union[str, exp.Expression]) -> Self:
76
82
  """Set the columns for the INSERT statement and synchronize the _columns attribute on the builder."""
77
- if self._expression is None:
78
- self._expression = exp.Insert()
79
- if not isinstance(self._expression, exp.Insert):
83
+ current_expr = self.get_expression()
84
+ if current_expr is None:
85
+ self.set_expression(exp.Insert())
86
+ current_expr = self.get_expression()
87
+
88
+ if not isinstance(current_expr, exp.Insert):
80
89
  msg = "Cannot set columns on a non-INSERT expression."
81
90
  raise SQLBuilderError(msg)
82
91
 
83
92
  # Get the current table from the expression
84
- current_this = self._expression.args.get("this")
93
+ current_this = current_expr.args.get("this")
85
94
  if current_this is None:
86
95
  msg = "Table must be set using .into() before setting columns."
87
96
  raise SQLBuilderError(msg)
@@ -95,11 +104,11 @@ class InsertValuesMixin:
95
104
 
96
105
  # Create Schema object with table and columns
97
106
  schema = exp.Schema(this=table_name, expressions=column_identifiers)
98
- self._expression.set("this", schema)
107
+ current_expr.set("this", schema)
99
108
  # No columns specified - ensure we have just a Table object
100
109
  elif isinstance(current_this, exp.Schema):
101
110
  table_name = current_this.this
102
- self._expression.set("this", exp.Table(this=table_name))
111
+ current_expr.set("this", exp.Table(this=table_name))
103
112
 
104
113
  try:
105
114
  cols = self._columns
@@ -126,9 +135,12 @@ class InsertValuesMixin:
126
135
  Returns:
127
136
  The current builder instance for method chaining.
128
137
  """
129
- if self._expression is None:
130
- self._expression = exp.Insert()
131
- if not isinstance(self._expression, exp.Insert):
138
+ current_expr = self.get_expression()
139
+ if current_expr is None:
140
+ self.set_expression(exp.Insert())
141
+ current_expr = self.get_expression()
142
+
143
+ if not isinstance(current_expr, exp.Insert):
132
144
  msg = "Cannot add values to a non-INSERT expression."
133
145
  raise SQLBuilderError(msg)
134
146
 
@@ -137,8 +149,8 @@ class InsertValuesMixin:
137
149
  msg = "Cannot mix positional values with keyword values."
138
150
  raise SQLBuilderError(msg)
139
151
  try:
140
- _columns = self._columns
141
- if not _columns:
152
+ cols = self._columns
153
+ if not cols:
142
154
  self.columns(*kwargs.keys())
143
155
  except AttributeError:
144
156
  pass
@@ -156,8 +168,8 @@ class InsertValuesMixin:
156
168
  elif len(values) == 1 and hasattr(values[0], "items"):
157
169
  mapping = values[0]
158
170
  try:
159
- _columns = self._columns
160
- if not _columns:
171
+ cols = self._columns
172
+ if not cols:
161
173
  self.columns(*mapping.keys())
162
174
  except AttributeError:
163
175
  pass
@@ -174,9 +186,9 @@ class InsertValuesMixin:
174
186
  row_exprs.append(exp.Placeholder(this=param_name))
175
187
  else:
176
188
  try:
177
- _columns = self._columns
178
- if _columns and len(values) != len(_columns):
179
- msg = f"Number of values ({len(values)}) does not match the number of specified columns ({len(_columns)})."
189
+ cols = self._columns
190
+ if cols and len(values) != len(cols):
191
+ msg = f"Number of values ({len(values)}) does not match the number of specified columns ({len(cols)})."
180
192
  raise SQLBuilderError(msg)
181
193
  except AttributeError:
182
194
  pass
@@ -186,11 +198,9 @@ class InsertValuesMixin:
186
198
  row_exprs.append(v)
187
199
  else:
188
200
  try:
189
- _columns = self._columns
190
- if _columns and i < len(_columns):
191
- column_name = (
192
- str(_columns[i]).split(".")[-1] if "." in str(_columns[i]) else str(_columns[i])
193
- )
201
+ cols = self._columns
202
+ if cols and i < len(cols):
203
+ column_name = str(cols[i]).split(".")[-1] if "." in str(cols[i]) else str(cols[i])
194
204
  param_name = self._generate_unique_parameter_name(column_name)
195
205
  else:
196
206
  param_name = self._generate_unique_parameter_name(f"value_{i + 1}")
@@ -200,7 +210,7 @@ class InsertValuesMixin:
200
210
  row_exprs.append(exp.Placeholder(this=param_name))
201
211
 
202
212
  values_expr = exp.Values(expressions=[row_exprs])
203
- self._expression.set("expression", values_expr)
213
+ current_expr.set("expression", values_expr)
204
214
  return self
205
215
 
206
216
  def add_values(self, values: Sequence[Any]) -> Self:
@@ -221,8 +231,9 @@ class InsertFromSelectMixin:
221
231
 
222
232
  __slots__ = ()
223
233
 
224
- # Type annotation for PyRight - this will be provided by the base class
225
- _expression: Optional[exp.Expression]
234
+ # Type annotations for PyRight - these will be provided by the base class
235
+ def get_expression(self) -> Optional[exp.Expression]: ...
236
+ def set_expression(self, expression: exp.Expression) -> None: ...
226
237
 
227
238
  _table: Any # Provided by QueryBuilder
228
239
 
@@ -250,9 +261,12 @@ class InsertFromSelectMixin:
250
261
  except AttributeError:
251
262
  msg = "The target table must be set using .into() before adding values."
252
263
  raise SQLBuilderError(msg)
253
- if self._expression is None:
254
- self._expression = exp.Insert()
255
- if not isinstance(self._expression, exp.Insert):
264
+ current_expr = self.get_expression()
265
+ if current_expr is None:
266
+ self.set_expression(exp.Insert())
267
+ current_expr = self.get_expression()
268
+
269
+ if not isinstance(current_expr, exp.Insert):
256
270
  msg = "Cannot set INSERT source on a non-INSERT expression."
257
271
  raise SQLBuilderError(msg)
258
272
  subquery_parameters = select_builder._parameters
@@ -261,7 +275,7 @@ class InsertFromSelectMixin:
261
275
  self.add_parameter(p_value, name=p_name)
262
276
  select_expr = select_builder._expression
263
277
  if select_expr and isinstance(select_expr, exp.Select):
264
- self._expression.set("expression", select_expr.copy())
278
+ current_expr.set("expression", select_expr.copy())
265
279
  else:
266
280
  msg = "SelectBuilder must have a valid SELECT expression."
267
281
  raise SQLBuilderError(msg)
@@ -1,3 +1,4 @@
1
+ # pyright: reportPrivateUsage=false
1
2
  """JOIN operation mixins.
2
3
 
3
4
  Provides mixins for JOIN operations in SELECT statements.
@@ -1,3 +1,4 @@
1
+ # pyright: reportPrivateUsage=false
1
2
  """MERGE operation mixins.
2
3
 
3
4
  Provides mixins for MERGE statement functionality including INTO,
@@ -28,7 +29,10 @@ class MergeIntoClauseMixin:
28
29
  """Mixin providing INTO clause for MERGE builders."""
29
30
 
30
31
  __slots__ = ()
31
- _expression: Optional[exp.Expression]
32
+
33
+ # Type annotations for PyRight - these will be provided by the base class
34
+ def get_expression(self) -> Optional[exp.Expression]: ...
35
+ def set_expression(self, expression: exp.Expression) -> None: ...
32
36
 
33
37
  def into(self, table: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
34
38
  """Set the target table for the MERGE operation (INTO clause).
@@ -41,11 +45,14 @@ class MergeIntoClauseMixin:
41
45
  Returns:
42
46
  The current builder instance for method chaining.
43
47
  """
44
- if self._expression is None:
45
- self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
46
- if not isinstance(self._expression, exp.Merge):
47
- self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
48
- self._expression.set("this", exp.to_table(table, alias=alias) if isinstance(table, str) else table)
48
+ current_expr = self.get_expression()
49
+ if current_expr is None or not isinstance(current_expr, exp.Merge):
50
+ self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
51
+ current_expr = self.get_expression()
52
+
53
+ # Type guard: current_expr is now guaranteed to be an Expression
54
+ assert current_expr is not None
55
+ current_expr.set("this", exp.to_table(table, alias=alias) if isinstance(table, str) else table)
49
56
  return self
50
57
 
51
58
 
@@ -54,7 +61,10 @@ class MergeUsingClauseMixin:
54
61
  """Mixin providing USING clause for MERGE builders."""
55
62
 
56
63
  __slots__ = ()
57
- _expression: Optional[exp.Expression]
64
+
65
+ # Type annotations for PyRight - these will be provided by the base class
66
+ def get_expression(self) -> Optional[exp.Expression]: ...
67
+ def set_expression(self, expression: exp.Expression) -> None: ...
58
68
 
59
69
  def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
60
70
  """Add parameter - provided by QueryBuilder."""
@@ -75,11 +85,13 @@ class MergeUsingClauseMixin:
75
85
  Raises:
76
86
  SQLBuilderError: If the current expression is not a MERGE statement or if the source type is unsupported.
77
87
  """
78
- if self._expression is None:
79
- self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
80
- if not isinstance(self._expression, exp.Merge):
81
- self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
88
+ current_expr = self.get_expression()
89
+ if current_expr is None or not isinstance(current_expr, exp.Merge):
90
+ self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
91
+ current_expr = self.get_expression()
82
92
 
93
+ # Type guard: current_expr is now guaranteed to be an Expression
94
+ assert current_expr is not None
83
95
  source_expr: exp.Expression
84
96
  if isinstance(source, str):
85
97
  source_expr = exp.to_table(source, alias=alias)
@@ -99,7 +111,7 @@ class MergeUsingClauseMixin:
99
111
  msg = f"Unsupported source type for USING clause: {type(source)}"
100
112
  raise SQLBuilderError(msg)
101
113
 
102
- self._expression.set("using", source_expr)
114
+ current_expr.set("using", source_expr)
103
115
  return self
104
116
 
105
117
 
@@ -108,7 +120,10 @@ class MergeOnClauseMixin:
108
120
  """Mixin providing ON clause for MERGE builders."""
109
121
 
110
122
  __slots__ = ()
111
- _expression: Optional[exp.Expression]
123
+
124
+ # Type annotations for PyRight - these will be provided by the base class
125
+ def get_expression(self) -> Optional[exp.Expression]: ...
126
+ def set_expression(self, expression: exp.Expression) -> None: ...
112
127
 
113
128
  def on(self, condition: Union[str, exp.Expression]) -> Self:
114
129
  """Set the join condition for the MERGE operation (ON clause).
@@ -123,11 +138,13 @@ class MergeOnClauseMixin:
123
138
  Raises:
124
139
  SQLBuilderError: If the current expression is not a MERGE statement or if the condition type is unsupported.
125
140
  """
126
- if self._expression is None:
127
- self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
128
- if not isinstance(self._expression, exp.Merge):
129
- self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
141
+ current_expr = self.get_expression()
142
+ if current_expr is None or not isinstance(current_expr, exp.Merge):
143
+ self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
144
+ current_expr = self.get_expression()
130
145
 
146
+ # Type guard: current_expr is now guaranteed to be an Expression
147
+ assert current_expr is not None
131
148
  condition_expr: exp.Expression
132
149
  if isinstance(condition, str):
133
150
  parsed_condition: Optional[exp.Expression] = exp.maybe_parse(
@@ -143,7 +160,7 @@ class MergeOnClauseMixin:
143
160
  msg = f"Unsupported condition type for ON clause: {type(condition)}"
144
161
  raise SQLBuilderError(msg)
145
162
 
146
- self._expression.set("on", condition_expr)
163
+ current_expr.set("on", condition_expr)
147
164
  return self
148
165
 
149
166
 
@@ -152,7 +169,10 @@ class MergeMatchedClauseMixin:
152
169
  """Mixin providing WHEN MATCHED THEN ... clauses for MERGE builders."""
153
170
 
154
171
  __slots__ = ()
155
- _expression: Optional[exp.Expression]
172
+
173
+ # Type annotations for PyRight - these will be provided by the base class
174
+ def get_expression(self) -> Optional[exp.Expression]: ...
175
+ def set_expression(self, expression: exp.Expression) -> None: ...
156
176
 
157
177
  def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
158
178
  """Add parameter - provided by QueryBuilder."""
@@ -170,15 +190,17 @@ class MergeMatchedClauseMixin:
170
190
  Args:
171
191
  when_clause: The WHEN clause to add.
172
192
  """
173
- if self._expression is None:
174
- self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])) # type: ignore[misc]
175
- if not isinstance(self._expression, exp.Merge):
176
- self._expression = exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])) # type: ignore[misc]
177
-
178
- whens = self._expression.args.get("whens")
193
+ current_expr = self.get_expression()
194
+ if current_expr is None or not isinstance(current_expr, exp.Merge):
195
+ self.set_expression(exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[])))
196
+ current_expr = self.get_expression()
197
+
198
+ # Type guard: current_expr is now guaranteed to be an Expression
199
+ assert current_expr is not None
200
+ whens = current_expr.args.get("whens")
179
201
  if not whens:
180
202
  whens = exp.Whens(expressions=[])
181
- self._expression.set("whens", whens)
203
+ current_expr.set("whens", whens)
182
204
 
183
205
  whens.append("expressions", when_clause)
184
206
 
@@ -315,7 +337,9 @@ class MergeNotMatchedClauseMixin:
315
337
 
316
338
  __slots__ = ()
317
339
 
318
- _expression: Optional[exp.Expression]
340
+ # Type annotations for PyRight - these will be provided by the base class
341
+ def get_expression(self) -> Optional[exp.Expression]: ...
342
+ def set_expression(self, expression: exp.Expression) -> None: ...
319
343
 
320
344
  def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
321
345
  """Add parameter - provided by QueryBuilder."""
@@ -415,7 +439,9 @@ class MergeNotMatchedBySourceClauseMixin:
415
439
 
416
440
  __slots__ = ()
417
441
 
418
- _expression: Optional[exp.Expression]
442
+ # Type annotations for PyRight - these will be provided by the base class
443
+ def get_expression(self) -> Optional[exp.Expression]: ...
444
+ def set_expression(self, expression: exp.Expression) -> None: ...
419
445
 
420
446
  def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
421
447
  """Add parameter - provided by QueryBuilder."""
@@ -1,3 +1,4 @@
1
+ # pyright: reportPrivateUsage=false
1
2
  """ORDER BY, LIMIT, OFFSET, and RETURNING clause mixins.
2
3
 
3
4
  Provides mixins for query result ordering, limiting, and result
@@ -1,3 +1,4 @@
1
+ # pyright: reportPrivateUsage=false
1
2
  """PIVOT and UNPIVOT operation mixins.
2
3
 
3
4
  Provides mixins for PIVOT and UNPIVOT operations in SELECT statements.
@@ -1,3 +1,4 @@
1
+ # pyright: reportPrivateUsage=false
1
2
  """SELECT clause mixins.
2
3
 
3
4
  Provides mixins for SELECT statement functionality including column selection,
@@ -28,8 +29,9 @@ class SelectClauseMixin:
28
29
 
29
30
  __slots__ = ()
30
31
 
31
- # Type annotation for PyRight - this will be provided by the base class
32
- _expression: Optional[exp.Expression]
32
+ # Type annotations for PyRight - these will be provided by the base class
33
+ def get_expression(self) -> Optional[exp.Expression]: ...
34
+ def set_expression(self, expression: exp.Expression) -> None: ...
33
35
 
34
36
  def select(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn", "SQL", "Case"]) -> Self:
35
37
  """Add columns to SELECT clause.
@@ -41,13 +43,17 @@ class SelectClauseMixin:
41
43
  The current builder instance for method chaining.
42
44
  """
43
45
  builder = cast("SQLBuilderProtocol", self)
44
- if builder._expression is None:
45
- builder._expression = exp.Select()
46
- if not isinstance(builder._expression, exp.Select):
46
+ current_expr = self.get_expression()
47
+ if current_expr is None:
48
+ self.set_expression(exp.Select())
49
+ current_expr = self.get_expression()
50
+
51
+ if not isinstance(current_expr, exp.Select):
47
52
  msg = "Cannot add select columns to a non-SELECT expression."
48
53
  raise SQLBuilderError(msg)
49
54
  for column in columns:
50
- builder._expression = builder._expression.select(parse_column_expression(column, builder), copy=False)
55
+ current_expr = current_expr.select(parse_column_expression(column, builder), copy=False)
56
+ self.set_expression(current_expr)
51
57
  return cast("Self", builder)
52
58
 
53
59
  def distinct(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn", "SQL"]) -> Self:
@@ -129,13 +135,13 @@ class SelectClauseMixin:
129
135
  Returns:
130
136
  The current builder instance for method chaining.
131
137
  """
132
- if self._expression is None or not isinstance(self._expression, exp.Select):
138
+ current_expr = self.get_expression()
139
+ if current_expr is None or not isinstance(current_expr, exp.Select):
133
140
  return self
134
141
 
135
142
  for column in columns:
136
- self._expression = self._expression.group_by(
137
- exp.column(column) if isinstance(column, str) else column, copy=False
138
- )
143
+ current_expr = current_expr.group_by(exp.column(column) if isinstance(column, str) else column, copy=False)
144
+ self.set_expression(current_expr)
139
145
  return self
140
146
 
141
147
  def group_by_rollup(self, *columns: Union[str, exp.Expression]) -> Self:
@@ -480,9 +486,12 @@ class SelectClauseMixin:
480
486
  Returns:
481
487
  The current builder instance for method chaining.
482
488
  """
483
- if self._expression is None:
484
- self._expression = exp.Select()
485
- if not isinstance(self._expression, exp.Select):
489
+ current_expr = self.get_expression()
490
+ if current_expr is None:
491
+ self.set_expression(exp.Select())
492
+ current_expr = self.get_expression()
493
+
494
+ if not isinstance(current_expr, exp.Select):
486
495
  msg = "Cannot add window function to a non-SELECT expression."
487
496
  raise SQLBuilderError(msg)
488
497
 
@@ -525,7 +534,8 @@ class SelectClauseMixin:
525
534
  over_args["frame"] = frame_expr
526
535
 
527
536
  window_expr = exp.Window(this=func_expr_parsed, **over_args)
528
- self._expression.select(exp.alias_(window_expr, alias) if alias else window_expr, copy=False)
537
+ current_expr = current_expr.select(exp.alias_(window_expr, alias) if alias else window_expr, copy=False)
538
+ self.set_expression(current_expr)
529
539
  return self
530
540
 
531
541
  def case_(self, alias: "Optional[str]" = None) -> "CaseBuilder":
@@ -906,3 +916,21 @@ class Case:
906
916
  """
907
917
  case_expr = exp.Case(ifs=self._conditions, default=self._default)
908
918
  return cast("exp.Alias", exp.alias_(case_expr, alias))
919
+
920
+ @property
921
+ def conditions(self) -> "list[exp.If]":
922
+ """Get CASE conditions (public API).
923
+
924
+ Returns:
925
+ List of If expressions representing WHEN clauses
926
+ """
927
+ return self._conditions
928
+
929
+ @property
930
+ def default(self) -> Optional[exp.Expression]:
931
+ """Get CASE default value (public API).
932
+
933
+ Returns:
934
+ Default expression for the ELSE clause, or None
935
+ """
936
+ return self._default
@@ -1,3 +1,4 @@
1
+ # pyright: reportPrivateUsage=false
1
2
  """UPDATE operation mixins.
2
3
 
3
4
  Provides mixins for UPDATE statement functionality including
@@ -25,8 +26,9 @@ class UpdateTableClauseMixin:
25
26
 
26
27
  __slots__ = ()
27
28
 
28
- # Type annotation for PyRight - this will be provided by the base class
29
- _expression: Optional[exp.Expression]
29
+ # Type annotations for PyRight - these will be provided by the base class
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,9 @@ 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
+ # Type annotations for PyRight - these will be provided by the base class
62
+ def get_expression(self) -> Optional[exp.Expression]: ...
63
+ def set_expression(self, expression: exp.Expression) -> None: ...
57
64
 
58
65
  def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
59
66
  """Add parameter - provided by QueryBuilder."""
@@ -130,9 +137,12 @@ class UpdateSetClauseMixin:
130
137
  Returns:
131
138
  The current builder instance for method chaining.
132
139
  """
133
- if self._expression is None:
134
- self._expression = exp.Update()
135
- if not isinstance(self._expression, exp.Update):
140
+ current_expr = self.get_expression()
141
+ if current_expr is None:
142
+ self.set_expression(exp.Update())
143
+ current_expr = self.get_expression()
144
+
145
+ if not isinstance(current_expr, exp.Update):
136
146
  msg = "Cannot add SET clause to non-UPDATE expression."
137
147
  raise SQLBuilderError(msg)
138
148
  assignments = []
@@ -149,8 +159,8 @@ class UpdateSetClauseMixin:
149
159
  else:
150
160
  msg = "Invalid arguments for set(): use (column, value), mapping, or kwargs."
151
161
  raise SQLBuilderError(msg)
152
- existing = self._expression.args.get("expressions", [])
153
- self._expression.set("expressions", existing + assignments)
162
+ existing = current_expr.args.get("expressions", [])
163
+ current_expr.set("expressions", existing + assignments)
154
164
  return self
155
165
 
156
166
 
@@ -160,8 +170,9 @@ class UpdateFromClauseMixin:
160
170
 
161
171
  __slots__ = ()
162
172
 
163
- # Type annotation for PyRight - this will be provided by the base class
164
- _expression: Optional[exp.Expression]
173
+ # Type annotations for PyRight - these will be provided by the base class
174
+ def get_expression(self) -> Optional[exp.Expression]: ...
175
+ def set_expression(self, expression: exp.Expression) -> None: ...
165
176
 
166
177
  def from_(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
167
178
  """Add a FROM clause to the UPDATE statement.
@@ -176,7 +187,8 @@ class UpdateFromClauseMixin:
176
187
  Raises:
177
188
  SQLBuilderError: If the current expression is not an UPDATE statement.
178
189
  """
179
- if self._expression is None or not isinstance(self._expression, exp.Update):
190
+ current_expr = self.get_expression()
191
+ if current_expr is None or not isinstance(current_expr, exp.Update):
180
192
  msg = "Cannot add FROM clause to non-UPDATE expression. Set the main table first."
181
193
  raise SQLBuilderError(msg)
182
194
  table_expr: exp.Expression
@@ -194,9 +206,9 @@ class UpdateFromClauseMixin:
194
206
  else:
195
207
  msg = f"Unsupported table type for FROM clause: {type(table)}"
196
208
  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"]
209
+ if current_expr.args.get("from") is None:
210
+ current_expr.set("from", exp.From(expressions=[]))
211
+ from_clause = current_expr.args["from"]
200
212
  if hasattr(from_clause, "append"):
201
213
  from_clause.append("expressions", table_expr)
202
214
  else: