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

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

Potentially problematic release.


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

Files changed (43) hide show
  1. sqlspec/_sql.py +702 -44
  2. sqlspec/builder/_base.py +77 -44
  3. sqlspec/builder/_column.py +0 -4
  4. sqlspec/builder/_ddl.py +15 -52
  5. sqlspec/builder/_ddl_utils.py +0 -1
  6. sqlspec/builder/_delete.py +4 -5
  7. sqlspec/builder/_insert.py +235 -44
  8. sqlspec/builder/_merge.py +17 -2
  9. sqlspec/builder/_parsing_utils.py +42 -14
  10. sqlspec/builder/_select.py +29 -33
  11. sqlspec/builder/_update.py +4 -2
  12. sqlspec/builder/mixins/_cte_and_set_ops.py +47 -20
  13. sqlspec/builder/mixins/_delete_operations.py +6 -1
  14. sqlspec/builder/mixins/_insert_operations.py +126 -24
  15. sqlspec/builder/mixins/_join_operations.py +44 -10
  16. sqlspec/builder/mixins/_merge_operations.py +183 -25
  17. sqlspec/builder/mixins/_order_limit_operations.py +15 -3
  18. sqlspec/builder/mixins/_pivot_operations.py +11 -2
  19. sqlspec/builder/mixins/_select_operations.py +21 -14
  20. sqlspec/builder/mixins/_update_operations.py +80 -32
  21. sqlspec/builder/mixins/_where_clause.py +201 -66
  22. sqlspec/core/cache.py +26 -28
  23. sqlspec/core/compiler.py +58 -37
  24. sqlspec/core/filters.py +12 -10
  25. sqlspec/core/parameters.py +80 -52
  26. sqlspec/core/result.py +30 -17
  27. sqlspec/core/statement.py +47 -22
  28. sqlspec/driver/_async.py +76 -46
  29. sqlspec/driver/_common.py +25 -6
  30. sqlspec/driver/_sync.py +73 -43
  31. sqlspec/driver/mixins/_result_tools.py +62 -37
  32. sqlspec/driver/mixins/_sql_translator.py +61 -11
  33. sqlspec/extensions/litestar/cli.py +1 -1
  34. sqlspec/extensions/litestar/plugin.py +2 -2
  35. sqlspec/protocols.py +7 -0
  36. sqlspec/utils/sync_tools.py +1 -1
  37. sqlspec/utils/type_guards.py +7 -3
  38. {sqlspec-0.15.0.dist-info → sqlspec-0.16.2.dist-info}/METADATA +1 -1
  39. {sqlspec-0.15.0.dist-info → sqlspec-0.16.2.dist-info}/RECORD +43 -43
  40. {sqlspec-0.15.0.dist-info → sqlspec-0.16.2.dist-info}/WHEEL +0 -0
  41. {sqlspec-0.15.0.dist-info → sqlspec-0.16.2.dist-info}/entry_points.txt +0 -0
  42. {sqlspec-0.15.0.dist-info → sqlspec-0.16.2.dist-info}/licenses/LICENSE +0 -0
  43. {sqlspec-0.15.0.dist-info → sqlspec-0.16.2.dist-info}/licenses/NOTICE +0 -0
@@ -1,8 +1,9 @@
1
1
  """Update operation mixins for SQL builders."""
2
2
 
3
3
  from collections.abc import Mapping
4
- from typing import Any, Optional, Union
4
+ from typing import Any, Optional, Union, cast
5
5
 
6
+ from mypy_extensions import trait
6
7
  from sqlglot import exp
7
8
  from typing_extensions import Self
8
9
 
@@ -14,10 +15,14 @@ __all__ = ("UpdateFromClauseMixin", "UpdateSetClauseMixin", "UpdateTableClauseMi
14
15
  MIN_SET_ARGS = 2
15
16
 
16
17
 
18
+ @trait
17
19
  class UpdateTableClauseMixin:
18
20
  """Mixin providing TABLE clause for UPDATE builders."""
19
21
 
20
- _expression: Optional[exp.Expression] = None
22
+ __slots__ = ()
23
+
24
+ # Type annotation for PyRight - this will be provided by the base class
25
+ _expression: Optional[exp.Expression]
21
26
 
22
27
  def table(self, table_name: str, alias: Optional[str] = None) -> Self:
23
28
  """Set the table to update.
@@ -37,10 +42,70 @@ class UpdateTableClauseMixin:
37
42
  return self
38
43
 
39
44
 
45
+ @trait
40
46
  class UpdateSetClauseMixin:
41
47
  """Mixin providing SET clause for UPDATE builders."""
42
48
 
43
- _expression: Optional[exp.Expression] = None
49
+ __slots__ = ()
50
+
51
+ # Type annotation for PyRight - this will be provided by the base class
52
+ _expression: Optional[exp.Expression]
53
+
54
+ def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
55
+ """Add parameter - provided by QueryBuilder."""
56
+ msg = "Method must be provided by QueryBuilder subclass"
57
+ raise NotImplementedError(msg)
58
+
59
+ def _generate_unique_parameter_name(self, base_name: str) -> str:
60
+ """Generate unique parameter name - provided by QueryBuilder."""
61
+ msg = "Method must be provided by QueryBuilder subclass"
62
+ raise NotImplementedError(msg)
63
+
64
+ def _process_update_value(self, val: Any, col: Any) -> exp.Expression:
65
+ """Process a value for UPDATE assignment, handling SQL objects and parameters.
66
+
67
+ Args:
68
+ val: The value to process
69
+ col: The column name for parameter naming
70
+
71
+ Returns:
72
+ The processed expression for the value
73
+ """
74
+ if isinstance(val, exp.Expression):
75
+ return val
76
+ if has_query_builder_parameters(val):
77
+ subquery = val.build()
78
+ sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
79
+ value_expr = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect", None)))
80
+ if has_query_builder_parameters(val):
81
+ for p_name, p_value in val.parameters.items():
82
+ self.add_parameter(p_value, name=p_name)
83
+ return value_expr
84
+ if hasattr(val, "expression") and hasattr(val, "sql"):
85
+ # Handle SQL objects (from sql.raw with parameters)
86
+ expression = getattr(val, "expression", None)
87
+ if expression is not None and isinstance(expression, exp.Expression):
88
+ # Merge parameters from SQL object into builder
89
+ if hasattr(val, "parameters"):
90
+ sql_parameters = getattr(val, "parameters", {})
91
+ for param_name, param_value in sql_parameters.items():
92
+ self.add_parameter(param_value, name=param_name)
93
+ return cast("exp.Expression", expression)
94
+ # If expression is None, fall back to parsing the raw SQL
95
+ sql_text = getattr(val, "sql", "")
96
+ # Merge parameters even when parsing raw SQL
97
+ if hasattr(val, "parameters"):
98
+ sql_parameters = getattr(val, "parameters", {})
99
+ for param_name, param_value in sql_parameters.items():
100
+ self.add_parameter(param_value, name=param_name)
101
+ parsed_expr = exp.maybe_parse(sql_text)
102
+ return parsed_expr if parsed_expr is not None else exp.convert(str(sql_text))
103
+ column_name = col if isinstance(col, str) else str(col)
104
+ if "." in column_name:
105
+ column_name = column_name.split(".")[-1]
106
+ param_name = self._generate_unique_parameter_name(column_name)
107
+ param_name = self.add_parameter(val, name=param_name)[1]
108
+ return exp.Placeholder(this=param_name)
44
109
 
45
110
  def set(self, *args: Any, **kwargs: Any) -> Self:
46
111
  """Set columns and values for the UPDATE statement.
@@ -61,7 +126,6 @@ class UpdateSetClauseMixin:
61
126
  Returns:
62
127
  The current builder instance for method chaining.
63
128
  """
64
-
65
129
  if self._expression is None:
66
130
  self._expression = exp.Update()
67
131
  if not isinstance(self._expression, exp.Update):
@@ -71,34 +135,12 @@ class UpdateSetClauseMixin:
71
135
  if len(args) == MIN_SET_ARGS and not kwargs:
72
136
  col, val = args
73
137
  col_expr = col if isinstance(col, exp.Column) else exp.column(col)
74
- if isinstance(val, exp.Expression):
75
- value_expr = val
76
- elif has_query_builder_parameters(val):
77
- subquery = val.build()
78
- sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
79
- value_expr = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect", None)))
80
- if has_query_builder_parameters(val):
81
- for p_name, p_value in val.parameters.items():
82
- self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
83
- else:
84
- param_name = self.add_parameter(val)[1] # type: ignore[attr-defined]
85
- value_expr = exp.Placeholder(this=param_name)
138
+ value_expr = self._process_update_value(val, col)
86
139
  assignments.append(exp.EQ(this=col_expr, expression=value_expr))
87
140
  elif (len(args) == 1 and isinstance(args[0], Mapping)) or kwargs:
88
141
  all_values = dict(args[0] if args else {}, **kwargs)
89
142
  for col, val in all_values.items():
90
- if isinstance(val, exp.Expression):
91
- value_expr = val
92
- elif has_query_builder_parameters(val):
93
- subquery = val.build()
94
- sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
95
- value_expr = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect", None)))
96
- if has_query_builder_parameters(val):
97
- for p_name, p_value in val.parameters.items():
98
- self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
99
- else:
100
- param_name = self.add_parameter(val)[1] # type: ignore[attr-defined]
101
- value_expr = exp.Placeholder(this=param_name)
143
+ value_expr = self._process_update_value(val, col)
102
144
  assignments.append(exp.EQ(this=exp.column(col), expression=value_expr))
103
145
  else:
104
146
  msg = "Invalid arguments for set(): use (column, value), mapping, or kwargs."
@@ -108,9 +150,15 @@ class UpdateSetClauseMixin:
108
150
  return self
109
151
 
110
152
 
153
+ @trait
111
154
  class UpdateFromClauseMixin:
112
155
  """Mixin providing FROM clause for UPDATE builders (e.g., PostgreSQL style)."""
113
156
 
157
+ __slots__ = ()
158
+
159
+ # Type annotation for PyRight - this will be provided by the base class
160
+ _expression: Optional[exp.Expression]
161
+
114
162
  def from_(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
115
163
  """Add a FROM clause to the UPDATE statement.
116
164
 
@@ -124,7 +172,7 @@ class UpdateFromClauseMixin:
124
172
  Raises:
125
173
  SQLBuilderError: If the current expression is not an UPDATE statement.
126
174
  """
127
- if self._expression is None or not isinstance(self._expression, exp.Update): # type: ignore[attr-defined]
175
+ if self._expression is None or not isinstance(self._expression, exp.Update):
128
176
  msg = "Cannot add FROM clause to non-UPDATE expression. Set the main table first."
129
177
  raise SQLBuilderError(msg)
130
178
  table_expr: exp.Expression
@@ -142,9 +190,9 @@ class UpdateFromClauseMixin:
142
190
  else:
143
191
  msg = f"Unsupported table type for FROM clause: {type(table)}"
144
192
  raise SQLBuilderError(msg)
145
- if self._expression.args.get("from") is None: # type: ignore[attr-defined]
146
- self._expression.set("from", exp.From(expressions=[])) # type: ignore[attr-defined]
147
- from_clause = self._expression.args["from"] # type: ignore[attr-defined]
193
+ if self._expression.args.get("from") is None:
194
+ self._expression.set("from", exp.From(expressions=[]))
195
+ from_clause = self._expression.args["from"]
148
196
  if hasattr(from_clause, "append"):
149
197
  from_clause.append("expressions", table_expr)
150
198
  else: