sqlspec 0.14.1__py3-none-any.whl → 0.16.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 (159) hide show
  1. sqlspec/__init__.py +50 -25
  2. sqlspec/__main__.py +1 -1
  3. sqlspec/__metadata__.py +1 -3
  4. sqlspec/_serialization.py +1 -2
  5. sqlspec/_sql.py +480 -121
  6. sqlspec/_typing.py +278 -142
  7. sqlspec/adapters/adbc/__init__.py +4 -3
  8. sqlspec/adapters/adbc/_types.py +12 -0
  9. sqlspec/adapters/adbc/config.py +115 -260
  10. sqlspec/adapters/adbc/driver.py +462 -367
  11. sqlspec/adapters/aiosqlite/__init__.py +18 -3
  12. sqlspec/adapters/aiosqlite/_types.py +13 -0
  13. sqlspec/adapters/aiosqlite/config.py +199 -129
  14. sqlspec/adapters/aiosqlite/driver.py +230 -269
  15. sqlspec/adapters/asyncmy/__init__.py +18 -3
  16. sqlspec/adapters/asyncmy/_types.py +12 -0
  17. sqlspec/adapters/asyncmy/config.py +80 -168
  18. sqlspec/adapters/asyncmy/driver.py +260 -225
  19. sqlspec/adapters/asyncpg/__init__.py +19 -4
  20. sqlspec/adapters/asyncpg/_types.py +17 -0
  21. sqlspec/adapters/asyncpg/config.py +82 -181
  22. sqlspec/adapters/asyncpg/driver.py +285 -383
  23. sqlspec/adapters/bigquery/__init__.py +17 -3
  24. sqlspec/adapters/bigquery/_types.py +12 -0
  25. sqlspec/adapters/bigquery/config.py +191 -258
  26. sqlspec/adapters/bigquery/driver.py +474 -646
  27. sqlspec/adapters/duckdb/__init__.py +14 -3
  28. sqlspec/adapters/duckdb/_types.py +12 -0
  29. sqlspec/adapters/duckdb/config.py +415 -351
  30. sqlspec/adapters/duckdb/driver.py +343 -413
  31. sqlspec/adapters/oracledb/__init__.py +19 -5
  32. sqlspec/adapters/oracledb/_types.py +14 -0
  33. sqlspec/adapters/oracledb/config.py +123 -379
  34. sqlspec/adapters/oracledb/driver.py +507 -560
  35. sqlspec/adapters/psqlpy/__init__.py +13 -3
  36. sqlspec/adapters/psqlpy/_types.py +11 -0
  37. sqlspec/adapters/psqlpy/config.py +93 -254
  38. sqlspec/adapters/psqlpy/driver.py +505 -234
  39. sqlspec/adapters/psycopg/__init__.py +19 -5
  40. sqlspec/adapters/psycopg/_types.py +17 -0
  41. sqlspec/adapters/psycopg/config.py +143 -403
  42. sqlspec/adapters/psycopg/driver.py +706 -872
  43. sqlspec/adapters/sqlite/__init__.py +14 -3
  44. sqlspec/adapters/sqlite/_types.py +11 -0
  45. sqlspec/adapters/sqlite/config.py +202 -118
  46. sqlspec/adapters/sqlite/driver.py +264 -303
  47. sqlspec/base.py +105 -9
  48. sqlspec/{statement/builder → builder}/__init__.py +12 -14
  49. sqlspec/{statement/builder → builder}/_base.py +120 -55
  50. sqlspec/{statement/builder → builder}/_column.py +17 -6
  51. sqlspec/{statement/builder → builder}/_ddl.py +46 -79
  52. sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
  53. sqlspec/{statement/builder → builder}/_delete.py +6 -25
  54. sqlspec/{statement/builder → builder}/_insert.py +18 -65
  55. sqlspec/builder/_merge.py +56 -0
  56. sqlspec/{statement/builder → builder}/_parsing_utils.py +8 -11
  57. sqlspec/{statement/builder → builder}/_select.py +11 -56
  58. sqlspec/{statement/builder → builder}/_update.py +12 -18
  59. sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
  60. sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
  61. sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +34 -18
  62. sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
  63. sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +19 -9
  64. sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
  65. sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
  66. sqlspec/{statement/builder → builder}/mixins/_select_operations.py +25 -38
  67. sqlspec/{statement/builder → builder}/mixins/_update_operations.py +15 -16
  68. sqlspec/{statement/builder → builder}/mixins/_where_clause.py +210 -137
  69. sqlspec/cli.py +4 -5
  70. sqlspec/config.py +180 -133
  71. sqlspec/core/__init__.py +63 -0
  72. sqlspec/core/cache.py +873 -0
  73. sqlspec/core/compiler.py +396 -0
  74. sqlspec/core/filters.py +830 -0
  75. sqlspec/core/hashing.py +310 -0
  76. sqlspec/core/parameters.py +1209 -0
  77. sqlspec/core/result.py +664 -0
  78. sqlspec/{statement → core}/splitter.py +321 -191
  79. sqlspec/core/statement.py +666 -0
  80. sqlspec/driver/__init__.py +7 -10
  81. sqlspec/driver/_async.py +387 -176
  82. sqlspec/driver/_common.py +527 -289
  83. sqlspec/driver/_sync.py +390 -172
  84. sqlspec/driver/mixins/__init__.py +2 -19
  85. sqlspec/driver/mixins/_result_tools.py +164 -0
  86. sqlspec/driver/mixins/_sql_translator.py +6 -3
  87. sqlspec/exceptions.py +5 -252
  88. sqlspec/extensions/aiosql/adapter.py +93 -96
  89. sqlspec/extensions/litestar/cli.py +1 -1
  90. sqlspec/extensions/litestar/config.py +0 -1
  91. sqlspec/extensions/litestar/handlers.py +15 -26
  92. sqlspec/extensions/litestar/plugin.py +18 -16
  93. sqlspec/extensions/litestar/providers.py +17 -52
  94. sqlspec/loader.py +424 -105
  95. sqlspec/migrations/__init__.py +12 -0
  96. sqlspec/migrations/base.py +92 -68
  97. sqlspec/migrations/commands.py +24 -106
  98. sqlspec/migrations/loaders.py +402 -0
  99. sqlspec/migrations/runner.py +49 -51
  100. sqlspec/migrations/tracker.py +31 -44
  101. sqlspec/migrations/utils.py +64 -24
  102. sqlspec/protocols.py +7 -183
  103. sqlspec/storage/__init__.py +1 -1
  104. sqlspec/storage/backends/base.py +37 -40
  105. sqlspec/storage/backends/fsspec.py +136 -112
  106. sqlspec/storage/backends/obstore.py +138 -160
  107. sqlspec/storage/capabilities.py +5 -4
  108. sqlspec/storage/registry.py +57 -106
  109. sqlspec/typing.py +136 -115
  110. sqlspec/utils/__init__.py +2 -3
  111. sqlspec/utils/correlation.py +0 -3
  112. sqlspec/utils/deprecation.py +6 -6
  113. sqlspec/utils/fixtures.py +6 -6
  114. sqlspec/utils/logging.py +0 -2
  115. sqlspec/utils/module_loader.py +7 -12
  116. sqlspec/utils/singleton.py +0 -1
  117. sqlspec/utils/sync_tools.py +17 -38
  118. sqlspec/utils/text.py +12 -51
  119. sqlspec/utils/type_guards.py +443 -232
  120. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/METADATA +7 -2
  121. sqlspec-0.16.0.dist-info/RECORD +134 -0
  122. sqlspec/adapters/adbc/transformers.py +0 -108
  123. sqlspec/driver/connection.py +0 -207
  124. sqlspec/driver/mixins/_cache.py +0 -114
  125. sqlspec/driver/mixins/_csv_writer.py +0 -91
  126. sqlspec/driver/mixins/_pipeline.py +0 -508
  127. sqlspec/driver/mixins/_query_tools.py +0 -796
  128. sqlspec/driver/mixins/_result_utils.py +0 -138
  129. sqlspec/driver/mixins/_storage.py +0 -912
  130. sqlspec/driver/mixins/_type_coercion.py +0 -128
  131. sqlspec/driver/parameters.py +0 -138
  132. sqlspec/statement/__init__.py +0 -21
  133. sqlspec/statement/builder/_merge.py +0 -95
  134. sqlspec/statement/cache.py +0 -50
  135. sqlspec/statement/filters.py +0 -625
  136. sqlspec/statement/parameters.py +0 -956
  137. sqlspec/statement/pipelines/__init__.py +0 -210
  138. sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
  139. sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
  140. sqlspec/statement/pipelines/context.py +0 -109
  141. sqlspec/statement/pipelines/transformers/__init__.py +0 -7
  142. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
  143. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
  144. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
  145. sqlspec/statement/pipelines/validators/__init__.py +0 -23
  146. sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
  147. sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
  148. sqlspec/statement/pipelines/validators/_performance.py +0 -714
  149. sqlspec/statement/pipelines/validators/_security.py +0 -967
  150. sqlspec/statement/result.py +0 -435
  151. sqlspec/statement/sql.py +0 -1774
  152. sqlspec/utils/cached_property.py +0 -25
  153. sqlspec/utils/statement_hashing.py +0 -203
  154. sqlspec-0.14.1.dist-info/RECORD +0 -145
  155. /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
  156. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/WHEEL +0 -0
  157. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/entry_points.txt +0 -0
  158. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/LICENSE +0 -0
  159. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/NOTICE +0 -0
@@ -3,8 +3,8 @@ from typing import TYPE_CHECKING, Any, Optional, Union, cast
3
3
  from sqlglot import exp
4
4
  from typing_extensions import Self
5
5
 
6
+ from sqlspec.builder._parsing_utils import parse_table_expression
6
7
  from sqlspec.exceptions import SQLBuilderError
7
- from sqlspec.statement.builder._parsing_utils import parse_table_expression
8
8
  from sqlspec.utils.type_guards import has_query_builder_parameters
9
9
 
10
10
  if TYPE_CHECKING:
@@ -33,7 +33,6 @@ class JoinClauseMixin:
33
33
  if isinstance(table, str):
34
34
  table_expr = parse_table_expression(table, alias)
35
35
  elif has_query_builder_parameters(table):
36
- # Work directly with AST when possible to avoid string parsing
37
36
  if hasattr(table, "_expression") and getattr(table, "_expression", None) is not None:
38
37
  table_expr_value = getattr(table, "_expression", None)
39
38
  if table_expr_value is not None:
@@ -46,7 +45,6 @@ class JoinClauseMixin:
46
45
  sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
47
46
  subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect", None)))
48
47
  table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
49
- # Parameter merging logic can be added here if needed
50
48
  else:
51
49
  table_expr = table
52
50
  on_expr: Optional[exp.Expression] = None
@@ -70,10 +70,9 @@ class MergeUsingClauseMixin:
70
70
  if isinstance(source, str):
71
71
  source_expr = exp.to_table(source, alias=alias)
72
72
  elif has_query_builder_parameters(source) and hasattr(source, "_expression"):
73
- # Merge parameters from the SELECT builder or other builder
74
- subquery_builder_params = source.parameters
75
- if subquery_builder_params:
76
- for p_name, p_value in subquery_builder_params.items():
73
+ subquery_builder_parameters = source.parameters
74
+ if subquery_builder_parameters:
75
+ for p_name, p_value in subquery_builder_parameters.items():
77
76
  self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
78
77
 
79
78
  subquery_exp = exp.paren(getattr(source, "_expression", exp.select()))
@@ -173,7 +172,11 @@ class MergeMatchedClauseMixin:
173
172
  """
174
173
  update_expressions: list[exp.EQ] = []
175
174
  for col, val in set_values.items():
176
- param_name = self.add_parameter(val)[1] # type: ignore[attr-defined]
175
+ column_name = col if isinstance(col, str) else str(col)
176
+ if "." in column_name:
177
+ column_name = column_name.split(".")[-1]
178
+ param_name = self._generate_unique_parameter_name(column_name) # type: ignore[attr-defined]
179
+ param_name = self.add_parameter(val, name=param_name)[1] # type: ignore[attr-defined]
177
180
  update_expressions.append(exp.EQ(this=exp.column(col), expression=exp.var(param_name)))
178
181
 
179
182
  when_args: dict[str, Any] = {"matched": True, "then": exp.Update(expressions=update_expressions)}
@@ -271,8 +274,12 @@ class MergeNotMatchedClauseMixin:
271
274
  raise SQLBuilderError(msg)
272
275
 
273
276
  parameterized_values: list[exp.Expression] = []
274
- for val in values:
275
- param_name = self.add_parameter(val)[1] # type: ignore[attr-defined]
277
+ for i, val in enumerate(values):
278
+ column_name = columns[i] if isinstance(columns[i], str) else str(columns[i])
279
+ if "." in column_name:
280
+ column_name = column_name.split(".")[-1]
281
+ param_name = self._generate_unique_parameter_name(column_name) # type: ignore[attr-defined]
282
+ param_name = self.add_parameter(val, name=param_name)[1] # type: ignore[attr-defined]
276
283
  parameterized_values.append(exp.var(param_name))
277
284
 
278
285
  insert_args["this"] = exp.Tuple(expressions=[exp.column(c) for c in columns])
@@ -281,7 +288,6 @@ class MergeNotMatchedClauseMixin:
281
288
  msg = "Specifying columns without values for INSERT action is complex and not fully supported yet. Consider providing full expressions."
282
289
  raise SQLBuilderError(msg)
283
290
  elif not columns and not values:
284
- # INSERT DEFAULT VALUES case
285
291
  pass
286
292
  else:
287
293
  msg = "Cannot specify values without columns for INSERT action."
@@ -338,7 +344,11 @@ class MergeNotMatchedBySourceClauseMixin:
338
344
  """
339
345
  update_expressions: list[exp.EQ] = []
340
346
  for col, val in set_values.items():
341
- param_name = self.add_parameter(val)[1] # type: ignore[attr-defined]
347
+ column_name = col if isinstance(col, str) else str(col)
348
+ if "." in column_name:
349
+ column_name = column_name.split(".")[-1]
350
+ param_name = self._generate_unique_parameter_name(column_name) # type: ignore[attr-defined]
351
+ param_name = self.add_parameter(val, name=param_name)[1] # type: ignore[attr-defined]
342
352
  update_expressions.append(exp.EQ(this=exp.column(col), expression=exp.var(param_name)))
343
353
 
344
354
  when_args: dict[str, Any] = {
@@ -5,8 +5,8 @@ from typing import TYPE_CHECKING, Optional, Union, cast
5
5
  from sqlglot import exp
6
6
  from typing_extensions import Self
7
7
 
8
+ from sqlspec.builder._parsing_utils import parse_order_expression
8
9
  from sqlspec.exceptions import SQLBuilderError
9
- from sqlspec.statement.builder._parsing_utils import parse_order_expression
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from sqlspec.protocols import SQLBuilderProtocol
@@ -71,7 +71,7 @@ class LimitOffsetClauseMixin:
71
71
  if not isinstance(builder._expression, exp.Select):
72
72
  msg = "LIMIT is only supported for SELECT statements."
73
73
  raise SQLBuilderError(msg)
74
- builder._expression = builder._expression.limit(exp.Literal.number(value), copy=False)
74
+ builder._expression = builder._expression.limit(exp.convert(value), copy=False)
75
75
  return cast("Self", builder)
76
76
 
77
77
  def offset(self, value: int) -> Self:
@@ -90,7 +90,7 @@ class LimitOffsetClauseMixin:
90
90
  if not isinstance(builder._expression, exp.Select):
91
91
  msg = "OFFSET is only supported for SELECT statements."
92
92
  raise SQLBuilderError(msg)
93
- builder._expression = builder._expression.offset(exp.Literal.number(value), copy=False)
93
+ builder._expression = builder._expression.offset(exp.convert(value), copy=False)
94
94
  return cast("Self", builder)
95
95
 
96
96
 
@@ -7,7 +7,7 @@ from sqlglot import exp
7
7
  if TYPE_CHECKING:
8
8
  from sqlglot.dialects.dialect import DialectType
9
9
 
10
- from sqlspec.statement.builder._select import Select
10
+ from sqlspec.builder._select import Select
11
11
 
12
12
  __all__ = ("PivotClauseMixin", "UnpivotClauseMixin")
13
13
 
@@ -56,12 +56,10 @@ class PivotClauseMixin:
56
56
  for val in pivot_values:
57
57
  if isinstance(val, exp.Expression):
58
58
  pivot_value_exprs.append(val)
59
- elif isinstance(val, str):
60
- pivot_value_exprs.append(exp.Literal.string(val))
61
- elif isinstance(val, (int, float)):
62
- pivot_value_exprs.append(exp.Literal.number(val))
59
+ elif isinstance(val, (str, int, float)):
60
+ pivot_value_exprs.append(exp.convert(val))
63
61
  else:
64
- pivot_value_exprs.append(exp.Literal.string(str(val)))
62
+ pivot_value_exprs.append(exp.convert(str(val)))
65
63
 
66
64
  in_expr = exp.In(this=pivot_col_expr, expressions=pivot_value_exprs)
67
65
 
@@ -113,7 +111,6 @@ class UnpivotClauseMixin:
113
111
  """
114
112
  current_expr = self._expression
115
113
  if not isinstance(current_expr, exp.Select):
116
- # SelectBuilder's __init__ ensures _expression is exp.Select.
117
114
  msg = "Unpivot can only be applied to a Select expression managed by Select."
118
115
  raise TypeError(msg)
119
116
 
@@ -127,7 +124,6 @@ class UnpivotClauseMixin:
127
124
  elif isinstance(col_name_or_expr, str):
128
125
  unpivot_cols_exprs.append(exp.column(col_name_or_expr))
129
126
  else:
130
- # Fallback for other types, should ideally be an error or more specific handling
131
127
  unpivot_cols_exprs.append(exp.column(str(col_name_or_expr)))
132
128
 
133
129
  in_expr = exp.In(this=name_col_ident, expressions=unpivot_cols_exprs)
@@ -6,15 +6,14 @@ from typing import TYPE_CHECKING, Any, Optional, Union, cast
6
6
  from sqlglot import exp
7
7
  from typing_extensions import Self
8
8
 
9
+ from sqlspec.builder._parsing_utils import parse_column_expression, parse_table_expression
9
10
  from sqlspec.exceptions import SQLBuilderError
10
- from sqlspec.statement.builder._parsing_utils import parse_column_expression, parse_table_expression
11
11
  from sqlspec.utils.type_guards import has_query_builder_parameters, is_expression
12
12
 
13
13
  if TYPE_CHECKING:
14
+ from sqlspec.builder._base import QueryBuilder
15
+ from sqlspec.builder._column import Column, FunctionColumn
14
16
  from sqlspec.protocols import SelectBuilderProtocol, SQLBuilderProtocol
15
- from sqlspec.statement.builder._base import QueryBuilder
16
- from sqlspec.statement.builder._column import Column, FunctionColumn
17
- from sqlspec.typing import RowT
18
17
 
19
18
  __all__ = ("CaseBuilder", "SelectClauseMixin")
20
19
 
@@ -24,7 +23,6 @@ class SelectClauseMixin:
24
23
 
25
24
  _expression: Optional[exp.Expression] = None
26
25
 
27
- # SELECT and DISTINCT methods
28
26
  def select(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn"]) -> Self:
29
27
  """Add columns to SELECT clause.
30
28
 
@@ -69,7 +67,6 @@ class SelectClauseMixin:
69
67
  builder._expression.set("distinct", exp.Distinct(expressions=distinct_columns))
70
68
  return cast("Self", builder)
71
69
 
72
- # FROM clause method
73
70
  def from_(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
74
71
  """Add FROM clause.
75
72
 
@@ -93,30 +90,27 @@ class SelectClauseMixin:
93
90
  if isinstance(table, str):
94
91
  from_expr = parse_table_expression(table, alias)
95
92
  elif is_expression(table):
96
- # Direct sqlglot expression - use as is
97
93
  from_expr = exp.alias_(table, alias) if alias else table
98
94
  elif has_query_builder_parameters(table):
99
- # Query builder with build() method
100
95
  subquery = table.build()
101
96
  sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
102
97
  subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect", None)))
103
98
  from_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
104
- current_params = getattr(builder, "_parameters", None)
105
- merged_params = getattr(type(builder), "ParameterConverter", None)
106
- if merged_params and hasattr(subquery, "parameters"):
107
- subquery_params = getattr(subquery, "parameters", {})
108
- merged_params = merged_params.merge_parameters(
109
- parameters=subquery_params,
110
- args=current_params if isinstance(current_params, list) else None,
111
- kwargs=current_params if isinstance(current_params, dict) else {},
99
+ current_parameters = getattr(builder, "_parameters", None)
100
+ merged_parameters = getattr(type(builder), "ParameterConverter", None)
101
+ if merged_parameters and hasattr(subquery, "parameters"):
102
+ subquery_parameters = getattr(subquery, "parameters", {})
103
+ merged_parameters = merged_parameters.merge_parameters(
104
+ parameters=subquery_parameters,
105
+ args=current_parameters if isinstance(current_parameters, list) else None,
106
+ kwargs=current_parameters if isinstance(current_parameters, dict) else {},
112
107
  )
113
- setattr(builder, "_parameters", merged_params)
108
+ setattr(builder, "_parameters", merged_parameters)
114
109
  else:
115
110
  from_expr = table
116
111
  builder._expression = builder._expression.from_(from_expr, copy=False)
117
112
  return cast("Self", builder)
118
113
 
119
- # GROUP BY methods
120
114
  def group_by(self, *columns: Union[str, exp.Expression]) -> Self:
121
115
  """Add GROUP BY clause.
122
116
 
@@ -149,7 +143,6 @@ class SelectClauseMixin:
149
143
 
150
144
  Example:
151
145
  ```python
152
- # GROUP BY ROLLUP(product, region)
153
146
  query = (
154
147
  sql.select("product", "region", sql.sum("sales"))
155
148
  .from_("sales_data")
@@ -174,7 +167,6 @@ class SelectClauseMixin:
174
167
 
175
168
  Example:
176
169
  ```python
177
- # GROUP BY CUBE(product, region)
178
170
  query = (
179
171
  sql.select("product", "region", sql.sum("sales"))
180
172
  .from_("sales_data")
@@ -200,7 +192,6 @@ class SelectClauseMixin:
200
192
 
201
193
  Example:
202
194
  ```python
203
- # GROUP BY GROUPING SETS ((product), (region), ())
204
195
  query = (
205
196
  sql.select("product", "region", sql.sum("sales"))
206
197
  .from_("sales_data")
@@ -217,13 +208,11 @@ class SelectClauseMixin:
217
208
  columns = [exp.column(col) for col in column_set]
218
209
  set_expressions.append(exp.Tuple(expressions=columns))
219
210
  else:
220
- # Single column
221
211
  set_expressions.append(exp.column(column_set))
222
212
 
223
213
  grouping_sets_expr = exp.GroupingSets(expressions=set_expressions)
224
214
  return self.group_by(grouping_sets_expr)
225
215
 
226
- # Aggregate function methods
227
216
  def count_(self, column: "Union[str, exp.Expression]" = "*", alias: Optional[str] = None) -> Self:
228
217
  """Add COUNT function to SELECT clause.
229
218
 
@@ -440,8 +429,7 @@ class SelectClauseMixin:
440
429
  """
441
430
  builder = cast("SelectBuilderProtocol", self)
442
431
  col_expr = exp.column(column) if isinstance(column, str) else column
443
- # Use GroupConcat which SQLGlot can translate to STRING_AGG for Postgres
444
- string_agg_expr = exp.GroupConcat(this=col_expr, separator=exp.Literal.string(separator))
432
+ string_agg_expr = exp.GroupConcat(this=col_expr, separator=exp.convert(separator))
445
433
  select_expr = exp.alias_(string_agg_expr, alias) if alias else string_agg_expr
446
434
  return cast("Self", builder.select(select_expr))
447
435
 
@@ -461,7 +449,6 @@ class SelectClauseMixin:
461
449
  select_expr = exp.alias_(json_agg_expr, alias) if alias else json_agg_expr
462
450
  return cast("Self", builder.select(select_expr))
463
451
 
464
- # Window function method
465
452
  def window(
466
453
  self,
467
454
  function_expr: Union[str, exp.Expression],
@@ -501,20 +488,19 @@ class SelectClauseMixin:
501
488
  else:
502
489
  func_expr_parsed = function_expr
503
490
 
504
- over_args: dict[str, Any] = {} # Stringified dict
491
+ over_args: dict[str, Any] = {}
505
492
  if partition_by:
506
493
  if isinstance(partition_by, str):
507
494
  over_args["partition_by"] = [exp.column(partition_by)]
508
- elif isinstance(partition_by, list): # Check for list
495
+ elif isinstance(partition_by, list):
509
496
  over_args["partition_by"] = [exp.column(col) if isinstance(col, str) else col for col in partition_by]
510
- elif isinstance(partition_by, exp.Expression): # Check for exp.Expression
497
+ elif isinstance(partition_by, exp.Expression):
511
498
  over_args["partition_by"] = [partition_by]
512
499
 
513
500
  if order_by:
514
501
  if isinstance(order_by, str):
515
502
  over_args["order"] = exp.column(order_by).asc()
516
503
  elif isinstance(order_by, list):
517
- # Properly handle multiple ORDER BY columns using Order expression
518
504
  order_expressions: list[Union[exp.Expression, exp.Column]] = []
519
505
  for col in order_by:
520
506
  if isinstance(col, str):
@@ -534,7 +520,6 @@ class SelectClauseMixin:
534
520
  self._expression.select(exp.alias_(window_expr, alias) if alias else window_expr, copy=False)
535
521
  return self
536
522
 
537
- # CASE expression method
538
523
  def case_(self, alias: "Optional[str]" = None) -> "CaseBuilder":
539
524
  """Create a CASE expression for the SELECT clause.
540
525
 
@@ -544,7 +529,7 @@ class SelectClauseMixin:
544
529
  Returns:
545
530
  CaseBuilder: A CaseBuilder instance for building the CASE expression.
546
531
  """
547
- builder = cast("QueryBuilder[Any]", self) # pyright: ignore
532
+ builder = cast("QueryBuilder", self) # pyright: ignore
548
533
  return CaseBuilder(builder, alias)
549
534
 
550
535
 
@@ -552,11 +537,11 @@ class SelectClauseMixin:
552
537
  class CaseBuilder:
553
538
  """Builder for CASE expressions."""
554
539
 
555
- _parent: "QueryBuilder[Any]" # pyright: ignore
540
+ _parent: "QueryBuilder" # pyright: ignore
556
541
  _alias: Optional[str]
557
542
  _case_expr: exp.Case
558
543
 
559
- def __init__(self, parent: "QueryBuilder[Any]", alias: "Optional[str]" = None) -> None:
544
+ def __init__(self, parent: "QueryBuilder", alias: "Optional[str]" = None) -> None:
560
545
  """Initialize CaseBuilder.
561
546
 
562
547
  Args:
@@ -578,7 +563,8 @@ class CaseBuilder:
578
563
  CaseBuilder: The current builder instance for method chaining.
579
564
  """
580
565
  cond_expr = exp.condition(condition) if isinstance(condition, str) else condition
581
- param_name = self._parent.add_parameter(value)[1]
566
+ param_name = self._parent._generate_unique_parameter_name("case_when_value")
567
+ param_name = self._parent.add_parameter(value, name=param_name)[1]
582
568
  value_expr = exp.Placeholder(this=param_name)
583
569
 
584
570
  when_clause = exp.When(this=cond_expr, then=value_expr)
@@ -597,16 +583,17 @@ class CaseBuilder:
597
583
  Returns:
598
584
  CaseBuilder: The current builder instance for method chaining.
599
585
  """
600
- param_name = self._parent.add_parameter(value)[1]
586
+ param_name = self._parent._generate_unique_parameter_name("case_else_value")
587
+ param_name = self._parent.add_parameter(value, name=param_name)[1]
601
588
  value_expr = exp.Placeholder(this=param_name)
602
589
  self._case_expr.set("default", value_expr)
603
590
  return self
604
591
 
605
- def end(self) -> "QueryBuilder[RowT]":
592
+ def end(self) -> "QueryBuilder":
606
593
  """Finalize the CASE expression and add it to the SELECT clause.
607
594
 
608
595
  Returns:
609
596
  The parent builder instance.
610
597
  """
611
598
  select_expr = exp.alias_(self._case_expr, self._alias) if self._alias else self._case_expr
612
- return cast("QueryBuilder[Any]", self._parent.select(select_expr)) # type: ignore[attr-defined]
599
+ return cast("QueryBuilder", self._parent.select(select_expr)) # type: ignore[attr-defined]
@@ -68,52 +68,51 @@ class UpdateSetClauseMixin:
68
68
  msg = "Cannot add SET clause to non-UPDATE expression."
69
69
  raise SQLBuilderError(msg)
70
70
  assignments = []
71
- # (column, value) signature
72
71
  if len(args) == MIN_SET_ARGS and not kwargs:
73
72
  col, val = args
74
73
  col_expr = col if isinstance(col, exp.Column) else exp.column(col)
75
- # If value is an expression, use it directly
76
74
  if isinstance(val, exp.Expression):
77
75
  value_expr = val
78
76
  elif has_query_builder_parameters(val):
79
- # It's a builder (like SelectBuilder), convert to subquery
80
77
  subquery = val.build()
81
- # Parse the SQL and use as expression
82
78
  sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
83
79
  value_expr = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect", None)))
84
- # Merge parameters from subquery
85
80
  if has_query_builder_parameters(val):
86
81
  for p_name, p_value in val.parameters.items():
87
82
  self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
88
83
  else:
89
- param_name = self.add_parameter(val)[1] # type: ignore[attr-defined]
84
+ column_name = col if isinstance(col, str) else str(col)
85
+ # Extract just the column part if table.column format
86
+ if "." in column_name:
87
+ column_name = column_name.split(".")[-1]
88
+ param_name = self._generate_unique_parameter_name(column_name) # type: ignore[attr-defined]
89
+ param_name = self.add_parameter(val, name=param_name)[1] # type: ignore[attr-defined]
90
90
  value_expr = exp.Placeholder(this=param_name)
91
91
  assignments.append(exp.EQ(this=col_expr, expression=value_expr))
92
- # mapping and/or kwargs
93
92
  elif (len(args) == 1 and isinstance(args[0], Mapping)) or kwargs:
94
93
  all_values = dict(args[0] if args else {}, **kwargs)
95
94
  for col, val in all_values.items():
96
- # If value is an expression, use it directly
97
95
  if isinstance(val, exp.Expression):
98
96
  value_expr = val
99
97
  elif has_query_builder_parameters(val):
100
- # It's a builder (like SelectBuilder), convert to subquery
101
98
  subquery = val.build()
102
- # Parse the SQL and use as expression
103
99
  sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
104
100
  value_expr = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect", None)))
105
- # Merge parameters from subquery
106
101
  if has_query_builder_parameters(val):
107
102
  for p_name, p_value in val.parameters.items():
108
103
  self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
109
104
  else:
110
- param_name = self.add_parameter(val)[1] # type: ignore[attr-defined]
105
+ # Extract column name for parameter naming
106
+ column_name = col if isinstance(col, str) else str(col)
107
+ if "." in column_name:
108
+ column_name = column_name.split(".")[-1]
109
+ param_name = self._generate_unique_parameter_name(column_name) # type: ignore[attr-defined]
110
+ param_name = self.add_parameter(val, name=param_name)[1] # type: ignore[attr-defined]
111
111
  value_expr = exp.Placeholder(this=param_name)
112
112
  assignments.append(exp.EQ(this=exp.column(col), expression=value_expr))
113
113
  else:
114
114
  msg = "Invalid arguments for set(): use (column, value), mapping, or kwargs."
115
115
  raise SQLBuilderError(msg)
116
- # Append to existing expressions instead of replacing
117
116
  existing = self._expression.args.get("expressions", [])
118
117
  self._expression.set("expressions", existing + assignments)
119
118
  return self
@@ -142,9 +141,9 @@ class UpdateFromClauseMixin:
142
141
  if isinstance(table, str):
143
142
  table_expr = exp.to_table(table, alias=alias)
144
143
  elif has_query_builder_parameters(table):
145
- subquery_builder_params = getattr(table, "_parameters", None)
146
- if subquery_builder_params:
147
- for p_name, p_value in subquery_builder_params.items():
144
+ subquery_builder_parameters = getattr(table, "_parameters", None)
145
+ if subquery_builder_parameters:
146
+ for p_name, p_value in subquery_builder_parameters.items():
148
147
  self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
149
148
  subquery_exp = exp.paren(getattr(table, "_expression", exp.select()))
150
149
  table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp