sqlspec 0.16.2__cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.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 (148) hide show
  1. 51ff5a9eadfdefd49f98__mypyc.cpython-39-aarch64-linux-gnu.so +0 -0
  2. sqlspec/__init__.py +92 -0
  3. sqlspec/__main__.py +12 -0
  4. sqlspec/__metadata__.py +14 -0
  5. sqlspec/_serialization.py +77 -0
  6. sqlspec/_sql.py +1782 -0
  7. sqlspec/_typing.py +680 -0
  8. sqlspec/adapters/__init__.py +0 -0
  9. sqlspec/adapters/adbc/__init__.py +5 -0
  10. sqlspec/adapters/adbc/_types.py +12 -0
  11. sqlspec/adapters/adbc/config.py +361 -0
  12. sqlspec/adapters/adbc/driver.py +512 -0
  13. sqlspec/adapters/aiosqlite/__init__.py +19 -0
  14. sqlspec/adapters/aiosqlite/_types.py +13 -0
  15. sqlspec/adapters/aiosqlite/config.py +253 -0
  16. sqlspec/adapters/aiosqlite/driver.py +248 -0
  17. sqlspec/adapters/asyncmy/__init__.py +19 -0
  18. sqlspec/adapters/asyncmy/_types.py +12 -0
  19. sqlspec/adapters/asyncmy/config.py +180 -0
  20. sqlspec/adapters/asyncmy/driver.py +274 -0
  21. sqlspec/adapters/asyncpg/__init__.py +21 -0
  22. sqlspec/adapters/asyncpg/_types.py +17 -0
  23. sqlspec/adapters/asyncpg/config.py +229 -0
  24. sqlspec/adapters/asyncpg/driver.py +344 -0
  25. sqlspec/adapters/bigquery/__init__.py +18 -0
  26. sqlspec/adapters/bigquery/_types.py +12 -0
  27. sqlspec/adapters/bigquery/config.py +298 -0
  28. sqlspec/adapters/bigquery/driver.py +558 -0
  29. sqlspec/adapters/duckdb/__init__.py +22 -0
  30. sqlspec/adapters/duckdb/_types.py +12 -0
  31. sqlspec/adapters/duckdb/config.py +504 -0
  32. sqlspec/adapters/duckdb/driver.py +368 -0
  33. sqlspec/adapters/oracledb/__init__.py +32 -0
  34. sqlspec/adapters/oracledb/_types.py +14 -0
  35. sqlspec/adapters/oracledb/config.py +317 -0
  36. sqlspec/adapters/oracledb/driver.py +538 -0
  37. sqlspec/adapters/psqlpy/__init__.py +16 -0
  38. sqlspec/adapters/psqlpy/_types.py +11 -0
  39. sqlspec/adapters/psqlpy/config.py +214 -0
  40. sqlspec/adapters/psqlpy/driver.py +530 -0
  41. sqlspec/adapters/psycopg/__init__.py +32 -0
  42. sqlspec/adapters/psycopg/_types.py +17 -0
  43. sqlspec/adapters/psycopg/config.py +426 -0
  44. sqlspec/adapters/psycopg/driver.py +796 -0
  45. sqlspec/adapters/sqlite/__init__.py +15 -0
  46. sqlspec/adapters/sqlite/_types.py +11 -0
  47. sqlspec/adapters/sqlite/config.py +240 -0
  48. sqlspec/adapters/sqlite/driver.py +294 -0
  49. sqlspec/base.py +571 -0
  50. sqlspec/builder/__init__.py +62 -0
  51. sqlspec/builder/_base.py +473 -0
  52. sqlspec/builder/_column.py +320 -0
  53. sqlspec/builder/_ddl.py +1346 -0
  54. sqlspec/builder/_ddl_utils.py +103 -0
  55. sqlspec/builder/_delete.py +76 -0
  56. sqlspec/builder/_insert.py +421 -0
  57. sqlspec/builder/_merge.py +71 -0
  58. sqlspec/builder/_parsing_utils.py +164 -0
  59. sqlspec/builder/_select.py +170 -0
  60. sqlspec/builder/_update.py +188 -0
  61. sqlspec/builder/mixins/__init__.py +55 -0
  62. sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
  63. sqlspec/builder/mixins/_delete_operations.py +41 -0
  64. sqlspec/builder/mixins/_insert_operations.py +244 -0
  65. sqlspec/builder/mixins/_join_operations.py +149 -0
  66. sqlspec/builder/mixins/_merge_operations.py +562 -0
  67. sqlspec/builder/mixins/_order_limit_operations.py +135 -0
  68. sqlspec/builder/mixins/_pivot_operations.py +153 -0
  69. sqlspec/builder/mixins/_select_operations.py +604 -0
  70. sqlspec/builder/mixins/_update_operations.py +202 -0
  71. sqlspec/builder/mixins/_where_clause.py +644 -0
  72. sqlspec/cli.py +247 -0
  73. sqlspec/config.py +395 -0
  74. sqlspec/core/__init__.py +63 -0
  75. sqlspec/core/cache.cpython-39-aarch64-linux-gnu.so +0 -0
  76. sqlspec/core/cache.py +871 -0
  77. sqlspec/core/compiler.cpython-39-aarch64-linux-gnu.so +0 -0
  78. sqlspec/core/compiler.py +417 -0
  79. sqlspec/core/filters.cpython-39-aarch64-linux-gnu.so +0 -0
  80. sqlspec/core/filters.py +830 -0
  81. sqlspec/core/hashing.cpython-39-aarch64-linux-gnu.so +0 -0
  82. sqlspec/core/hashing.py +310 -0
  83. sqlspec/core/parameters.cpython-39-aarch64-linux-gnu.so +0 -0
  84. sqlspec/core/parameters.py +1237 -0
  85. sqlspec/core/result.cpython-39-aarch64-linux-gnu.so +0 -0
  86. sqlspec/core/result.py +677 -0
  87. sqlspec/core/splitter.cpython-39-aarch64-linux-gnu.so +0 -0
  88. sqlspec/core/splitter.py +819 -0
  89. sqlspec/core/statement.cpython-39-aarch64-linux-gnu.so +0 -0
  90. sqlspec/core/statement.py +676 -0
  91. sqlspec/driver/__init__.py +19 -0
  92. sqlspec/driver/_async.py +502 -0
  93. sqlspec/driver/_common.py +631 -0
  94. sqlspec/driver/_sync.py +503 -0
  95. sqlspec/driver/mixins/__init__.py +6 -0
  96. sqlspec/driver/mixins/_result_tools.py +193 -0
  97. sqlspec/driver/mixins/_sql_translator.py +86 -0
  98. sqlspec/exceptions.py +193 -0
  99. sqlspec/extensions/__init__.py +0 -0
  100. sqlspec/extensions/aiosql/__init__.py +10 -0
  101. sqlspec/extensions/aiosql/adapter.py +461 -0
  102. sqlspec/extensions/litestar/__init__.py +6 -0
  103. sqlspec/extensions/litestar/_utils.py +52 -0
  104. sqlspec/extensions/litestar/cli.py +48 -0
  105. sqlspec/extensions/litestar/config.py +92 -0
  106. sqlspec/extensions/litestar/handlers.py +260 -0
  107. sqlspec/extensions/litestar/plugin.py +145 -0
  108. sqlspec/extensions/litestar/providers.py +454 -0
  109. sqlspec/loader.cpython-39-aarch64-linux-gnu.so +0 -0
  110. sqlspec/loader.py +760 -0
  111. sqlspec/migrations/__init__.py +35 -0
  112. sqlspec/migrations/base.py +414 -0
  113. sqlspec/migrations/commands.py +443 -0
  114. sqlspec/migrations/loaders.py +402 -0
  115. sqlspec/migrations/runner.py +213 -0
  116. sqlspec/migrations/tracker.py +140 -0
  117. sqlspec/migrations/utils.py +129 -0
  118. sqlspec/protocols.py +407 -0
  119. sqlspec/py.typed +0 -0
  120. sqlspec/storage/__init__.py +23 -0
  121. sqlspec/storage/backends/__init__.py +0 -0
  122. sqlspec/storage/backends/base.py +163 -0
  123. sqlspec/storage/backends/fsspec.py +386 -0
  124. sqlspec/storage/backends/obstore.py +459 -0
  125. sqlspec/storage/capabilities.py +102 -0
  126. sqlspec/storage/registry.py +239 -0
  127. sqlspec/typing.py +299 -0
  128. sqlspec/utils/__init__.py +3 -0
  129. sqlspec/utils/correlation.py +150 -0
  130. sqlspec/utils/deprecation.py +106 -0
  131. sqlspec/utils/fixtures.cpython-39-aarch64-linux-gnu.so +0 -0
  132. sqlspec/utils/fixtures.py +58 -0
  133. sqlspec/utils/logging.py +127 -0
  134. sqlspec/utils/module_loader.py +89 -0
  135. sqlspec/utils/serializers.py +4 -0
  136. sqlspec/utils/singleton.py +32 -0
  137. sqlspec/utils/sync_tools.cpython-39-aarch64-linux-gnu.so +0 -0
  138. sqlspec/utils/sync_tools.py +237 -0
  139. sqlspec/utils/text.cpython-39-aarch64-linux-gnu.so +0 -0
  140. sqlspec/utils/text.py +96 -0
  141. sqlspec/utils/type_guards.cpython-39-aarch64-linux-gnu.so +0 -0
  142. sqlspec/utils/type_guards.py +1139 -0
  143. sqlspec-0.16.2.dist-info/METADATA +365 -0
  144. sqlspec-0.16.2.dist-info/RECORD +148 -0
  145. sqlspec-0.16.2.dist-info/WHEEL +7 -0
  146. sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
  147. sqlspec-0.16.2.dist-info/licenses/LICENSE +21 -0
  148. sqlspec-0.16.2.dist-info/licenses/NOTICE +29 -0
@@ -0,0 +1,604 @@
1
+ """SELECT clause mixins consolidated into a single module."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Any, Optional, Union, cast
5
+
6
+ from mypy_extensions import trait
7
+ from sqlglot import exp
8
+ from typing_extensions import Self
9
+
10
+ from sqlspec.builder._parsing_utils import parse_column_expression, parse_table_expression
11
+ from sqlspec.exceptions import SQLBuilderError
12
+ from sqlspec.utils.type_guards import has_query_builder_parameters, is_expression
13
+
14
+ if TYPE_CHECKING:
15
+ from sqlspec.builder._column import Column, FunctionColumn
16
+ from sqlspec.core.statement import SQL
17
+ from sqlspec.protocols import SelectBuilderProtocol, SQLBuilderProtocol
18
+
19
+ __all__ = ("CaseBuilder", "SelectClauseMixin")
20
+
21
+
22
+ @trait
23
+ class SelectClauseMixin:
24
+ """Consolidated mixin providing all SELECT-related clauses and functionality."""
25
+
26
+ __slots__ = ()
27
+
28
+ # Type annotation for PyRight - this will be provided by the base class
29
+ _expression: Optional[exp.Expression]
30
+
31
+ def select(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn", "SQL"]) -> Self:
32
+ """Add columns to SELECT clause.
33
+
34
+ Raises:
35
+ SQLBuilderError: If the current expression is not a SELECT statement.
36
+
37
+ Returns:
38
+ The current builder instance for method chaining.
39
+ """
40
+ builder = cast("SQLBuilderProtocol", self)
41
+ if builder._expression is None:
42
+ builder._expression = exp.Select()
43
+ if not isinstance(builder._expression, exp.Select):
44
+ msg = "Cannot add select columns to a non-SELECT expression."
45
+ raise SQLBuilderError(msg)
46
+ for column in columns:
47
+ builder._expression = builder._expression.select(parse_column_expression(column, builder), copy=False)
48
+ return cast("Self", builder)
49
+
50
+ def distinct(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn", "SQL"]) -> Self:
51
+ """Add DISTINCT clause to SELECT.
52
+
53
+ Args:
54
+ *columns: Optional columns to make distinct. If none provided, applies DISTINCT to all selected columns.
55
+
56
+ Raises:
57
+ SQLBuilderError: If the current expression is not a SELECT statement.
58
+
59
+ Returns:
60
+ The current builder instance for method chaining.
61
+ """
62
+ builder = cast("SQLBuilderProtocol", self)
63
+ if builder._expression is None:
64
+ builder._expression = exp.Select()
65
+ if not isinstance(builder._expression, exp.Select):
66
+ msg = "Cannot add DISTINCT to a non-SELECT expression."
67
+ raise SQLBuilderError(msg)
68
+ if not columns:
69
+ builder._expression.set("distinct", exp.Distinct())
70
+ else:
71
+ distinct_columns = [parse_column_expression(column, builder) for column in columns]
72
+ builder._expression.set("distinct", exp.Distinct(expressions=distinct_columns))
73
+ return cast("Self", builder)
74
+
75
+ def from_(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
76
+ """Add FROM clause.
77
+
78
+ Args:
79
+ table: The table name, expression, or subquery to select from.
80
+ alias: Optional alias for the table.
81
+
82
+ Raises:
83
+ SQLBuilderError: If the current expression is not a SELECT statement or if the table type is unsupported.
84
+
85
+ Returns:
86
+ The current builder instance for method chaining.
87
+ """
88
+ builder = cast("SQLBuilderProtocol", self)
89
+ if builder._expression is None:
90
+ builder._expression = exp.Select()
91
+ if not isinstance(builder._expression, exp.Select):
92
+ msg = "FROM clause is only supported for SELECT statements."
93
+ raise SQLBuilderError(msg)
94
+ from_expr: exp.Expression
95
+ if isinstance(table, str):
96
+ from_expr = parse_table_expression(table, alias)
97
+ elif is_expression(table):
98
+ from_expr = exp.alias_(table, alias) if alias else table
99
+ elif has_query_builder_parameters(table):
100
+ subquery = table.build()
101
+ sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
102
+ subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect", None)))
103
+ from_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
104
+ current_parameters = getattr(builder, "_parameters", None)
105
+ merged_parameters = getattr(type(builder), "ParameterConverter", None)
106
+ if merged_parameters and hasattr(subquery, "parameters"):
107
+ subquery_parameters = getattr(subquery, "parameters", {})
108
+ merged_parameters = merged_parameters.merge_parameters(
109
+ parameters=subquery_parameters,
110
+ args=current_parameters if isinstance(current_parameters, list) else None,
111
+ kwargs=current_parameters if isinstance(current_parameters, dict) else {},
112
+ )
113
+ setattr(builder, "_parameters", merged_parameters)
114
+ else:
115
+ from_expr = table
116
+ builder._expression = builder._expression.from_(from_expr, copy=False)
117
+ return cast("Self", builder)
118
+
119
+ def group_by(self, *columns: Union[str, exp.Expression]) -> Self:
120
+ """Add GROUP BY clause.
121
+
122
+ Args:
123
+ *columns: Columns to group by. Can be column names, expressions,
124
+ or special grouping expressions like ROLLUP, CUBE, etc.
125
+
126
+ Returns:
127
+ The current builder instance for method chaining.
128
+ """
129
+ if self._expression is None or not isinstance(self._expression, exp.Select):
130
+ return self
131
+
132
+ for column in columns:
133
+ self._expression = self._expression.group_by(
134
+ exp.column(column) if isinstance(column, str) else column, copy=False
135
+ )
136
+ return self
137
+
138
+ def group_by_rollup(self, *columns: Union[str, exp.Expression]) -> Self:
139
+ """Add GROUP BY ROLLUP clause.
140
+
141
+ ROLLUP generates subtotals and grand totals for a hierarchical set of columns.
142
+
143
+ Args:
144
+ *columns: Columns to include in the rollup hierarchy.
145
+
146
+ Returns:
147
+ The current builder instance for method chaining.
148
+
149
+ Example:
150
+ ```python
151
+ query = (
152
+ sql.select("product", "region", sql.sum("sales"))
153
+ .from_("sales_data")
154
+ .group_by_rollup("product", "region")
155
+ )
156
+ ```
157
+ """
158
+ column_exprs = [exp.column(col) if isinstance(col, str) else col for col in columns]
159
+ rollup_expr = exp.Rollup(expressions=column_exprs)
160
+ return self.group_by(rollup_expr)
161
+
162
+ def group_by_cube(self, *columns: Union[str, exp.Expression]) -> Self:
163
+ """Add GROUP BY CUBE clause.
164
+
165
+ CUBE generates subtotals for all possible combinations of the specified columns.
166
+
167
+ Args:
168
+ *columns: Columns to include in the cube.
169
+
170
+ Returns:
171
+ The current builder instance for method chaining.
172
+
173
+ Example:
174
+ ```python
175
+ query = (
176
+ sql.select("product", "region", sql.sum("sales"))
177
+ .from_("sales_data")
178
+ .group_by_cube("product", "region")
179
+ )
180
+ ```
181
+ """
182
+ column_exprs = [exp.column(col) if isinstance(col, str) else col for col in columns]
183
+ cube_expr = exp.Cube(expressions=column_exprs)
184
+ return self.group_by(cube_expr)
185
+
186
+ def group_by_grouping_sets(self, *column_sets: Union[tuple[str, ...], list[str]]) -> Self:
187
+ """Add GROUP BY GROUPING SETS clause.
188
+
189
+ GROUPING SETS allows you to specify multiple grouping sets in a single query.
190
+
191
+ Args:
192
+ *column_sets: Sets of columns to group by. Each set can be a tuple or list.
193
+ Empty tuple/list creates a grand total grouping.
194
+
195
+ Returns:
196
+ The current builder instance for method chaining.
197
+
198
+ Example:
199
+ ```python
200
+ query = (
201
+ sql.select("product", "region", sql.sum("sales"))
202
+ .from_("sales_data")
203
+ .group_by_grouping_sets(("product",), ("region",), ())
204
+ )
205
+ ```
206
+ """
207
+ set_expressions = []
208
+ for column_set in column_sets:
209
+ if isinstance(column_set, (tuple, list)):
210
+ if len(column_set) == 0:
211
+ set_expressions.append(exp.Tuple(expressions=[]))
212
+ else:
213
+ columns = [exp.column(col) for col in column_set]
214
+ set_expressions.append(exp.Tuple(expressions=columns))
215
+ else:
216
+ set_expressions.append(exp.column(column_set))
217
+
218
+ grouping_sets_expr = exp.GroupingSets(expressions=set_expressions)
219
+ return self.group_by(grouping_sets_expr)
220
+
221
+ def count_(self, column: "Union[str, exp.Expression]" = "*", alias: Optional[str] = None) -> Self:
222
+ """Add COUNT function to SELECT clause.
223
+
224
+ Args:
225
+ column: The column to count (default is "*").
226
+ alias: Optional alias for the count.
227
+
228
+ Returns:
229
+ The current builder instance for method chaining.
230
+ """
231
+ builder = cast("SelectBuilderProtocol", self)
232
+ if column == "*":
233
+ count_expr = exp.Count(this=exp.Star())
234
+ else:
235
+ col_expr = exp.column(column) if isinstance(column, str) else column
236
+ count_expr = exp.Count(this=col_expr)
237
+
238
+ select_expr = exp.alias_(count_expr, alias) if alias else count_expr
239
+ return cast("Self", builder.select(select_expr))
240
+
241
+ def sum_(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
242
+ """Add SUM function to SELECT clause.
243
+
244
+ Args:
245
+ column: The column to sum.
246
+ alias: Optional alias for the sum.
247
+
248
+ Returns:
249
+ The current builder instance for method chaining.
250
+ """
251
+ builder = cast("SelectBuilderProtocol", self)
252
+ col_expr = exp.column(column) if isinstance(column, str) else column
253
+ sum_expr = exp.Sum(this=col_expr)
254
+ select_expr = exp.alias_(sum_expr, alias) if alias else sum_expr
255
+ return cast("Self", builder.select(select_expr))
256
+
257
+ def avg_(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
258
+ """Add AVG function to SELECT clause.
259
+
260
+ Args:
261
+ column: The column to average.
262
+ alias: Optional alias for the average.
263
+
264
+ Returns:
265
+ The current builder instance for method chaining.
266
+ """
267
+ builder = cast("SelectBuilderProtocol", self)
268
+ col_expr = exp.column(column) if isinstance(column, str) else column
269
+ avg_expr = exp.Avg(this=col_expr)
270
+ select_expr = exp.alias_(avg_expr, alias) if alias else avg_expr
271
+ return cast("Self", builder.select(select_expr))
272
+
273
+ def max_(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
274
+ """Add MAX function to SELECT clause.
275
+
276
+ Args:
277
+ column: The column to find the maximum of.
278
+ alias: Optional alias for the maximum.
279
+
280
+ Returns:
281
+ The current builder instance for method chaining.
282
+ """
283
+ builder = cast("SelectBuilderProtocol", self)
284
+ col_expr = exp.column(column) if isinstance(column, str) else column
285
+ max_expr = exp.Max(this=col_expr)
286
+ select_expr = exp.alias_(max_expr, alias) if alias else max_expr
287
+ return cast("Self", builder.select(select_expr))
288
+
289
+ def min_(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
290
+ """Add MIN function to SELECT clause.
291
+
292
+ Args:
293
+ column: The column to find the minimum of.
294
+ alias: Optional alias for the minimum.
295
+
296
+ Returns:
297
+ The current builder instance for method chaining.
298
+ """
299
+ builder = cast("SelectBuilderProtocol", self)
300
+ col_expr = exp.column(column) if isinstance(column, str) else column
301
+ min_expr = exp.Min(this=col_expr)
302
+ select_expr = exp.alias_(min_expr, alias) if alias else min_expr
303
+ return cast("Self", builder.select(select_expr))
304
+
305
+ def array_agg(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
306
+ """Add ARRAY_AGG aggregate function to SELECT clause.
307
+
308
+ Args:
309
+ column: The column to aggregate into an array.
310
+ alias: Optional alias for the result.
311
+
312
+ Returns:
313
+ The current builder instance for method chaining.
314
+ """
315
+ builder = cast("SelectBuilderProtocol", self)
316
+ col_expr = exp.column(column) if isinstance(column, str) else column
317
+ array_agg_expr = exp.ArrayAgg(this=col_expr)
318
+ select_expr = exp.alias_(array_agg_expr, alias) if alias else array_agg_expr
319
+ return cast("Self", builder.select(select_expr))
320
+
321
+ def count_distinct(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
322
+ """Add COUNT(DISTINCT column) to SELECT clause.
323
+
324
+ Args:
325
+ column: The column to count distinct values of.
326
+ alias: Optional alias for the count.
327
+
328
+ Returns:
329
+ The current builder instance for method chaining.
330
+ """
331
+ builder = cast("SelectBuilderProtocol", self)
332
+ col_expr = exp.column(column) if isinstance(column, str) else column
333
+ count_expr = exp.Count(this=exp.Distinct(expressions=[col_expr]))
334
+ select_expr = exp.alias_(count_expr, alias) if alias else count_expr
335
+ return cast("Self", builder.select(select_expr))
336
+
337
+ def stddev(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
338
+ """Add STDDEV aggregate function to SELECT clause.
339
+
340
+ Args:
341
+ column: The column to calculate standard deviation of.
342
+ alias: Optional alias for the result.
343
+
344
+ Returns:
345
+ The current builder instance for method chaining.
346
+ """
347
+ builder = cast("SelectBuilderProtocol", self)
348
+ col_expr = exp.column(column) if isinstance(column, str) else column
349
+ stddev_expr = exp.Stddev(this=col_expr)
350
+ select_expr = exp.alias_(stddev_expr, alias) if alias else stddev_expr
351
+ return cast("Self", builder.select(select_expr))
352
+
353
+ def stddev_pop(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
354
+ """Add STDDEV_POP aggregate function to SELECT clause.
355
+
356
+ Args:
357
+ column: The column to calculate population standard deviation of.
358
+ alias: Optional alias for the result.
359
+
360
+ Returns:
361
+ The current builder instance for method chaining.
362
+ """
363
+ builder = cast("SelectBuilderProtocol", self)
364
+ col_expr = exp.column(column) if isinstance(column, str) else column
365
+ stddev_pop_expr = exp.StddevPop(this=col_expr)
366
+ select_expr = exp.alias_(stddev_pop_expr, alias) if alias else stddev_pop_expr
367
+ return cast("Self", builder.select(select_expr))
368
+
369
+ def stddev_samp(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
370
+ """Add STDDEV_SAMP aggregate function to SELECT clause.
371
+
372
+ Args:
373
+ column: The column to calculate sample standard deviation of.
374
+ alias: Optional alias for the result.
375
+
376
+ Returns:
377
+ The current builder instance for method chaining.
378
+ """
379
+ builder = cast("SelectBuilderProtocol", self)
380
+ col_expr = exp.column(column) if isinstance(column, str) else column
381
+ stddev_samp_expr = exp.StddevSamp(this=col_expr)
382
+ select_expr = exp.alias_(stddev_samp_expr, alias) if alias else stddev_samp_expr
383
+ return cast("Self", builder.select(select_expr))
384
+
385
+ def variance(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
386
+ """Add VARIANCE aggregate function to SELECT clause.
387
+
388
+ Args:
389
+ column: The column to calculate variance of.
390
+ alias: Optional alias for the result.
391
+
392
+ Returns:
393
+ The current builder instance for method chaining.
394
+ """
395
+ builder = cast("SelectBuilderProtocol", self)
396
+ col_expr = exp.column(column) if isinstance(column, str) else column
397
+ variance_expr = exp.Variance(this=col_expr)
398
+ select_expr = exp.alias_(variance_expr, alias) if alias else variance_expr
399
+ return cast("Self", builder.select(select_expr))
400
+
401
+ def var_pop(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
402
+ """Add VAR_POP aggregate function to SELECT clause.
403
+
404
+ Args:
405
+ column: The column to calculate population variance of.
406
+ alias: Optional alias for the result.
407
+
408
+ Returns:
409
+ The current builder instance for method chaining.
410
+ """
411
+ builder = cast("SelectBuilderProtocol", self)
412
+ col_expr = exp.column(column) if isinstance(column, str) else column
413
+ var_pop_expr = exp.VariancePop(this=col_expr)
414
+ select_expr = exp.alias_(var_pop_expr, alias) if alias else var_pop_expr
415
+ return cast("Self", builder.select(select_expr))
416
+
417
+ def string_agg(self, column: Union[str, exp.Expression], separator: str = ",", alias: Optional[str] = None) -> Self:
418
+ """Add STRING_AGG aggregate function to SELECT clause.
419
+
420
+ Args:
421
+ column: The column to aggregate into a string.
422
+ separator: The separator between values (default is comma).
423
+ alias: Optional alias for the result.
424
+
425
+ Returns:
426
+ The current builder instance for method chaining.
427
+
428
+ Note:
429
+ Different databases have different names for this function:
430
+ - PostgreSQL: STRING_AGG
431
+ - MySQL: GROUP_CONCAT
432
+ - SQLite: GROUP_CONCAT
433
+ SQLGlot will handle the translation.
434
+ """
435
+ builder = cast("SelectBuilderProtocol", self)
436
+ col_expr = exp.column(column) if isinstance(column, str) else column
437
+ string_agg_expr = exp.GroupConcat(this=col_expr, separator=exp.convert(separator))
438
+ select_expr = exp.alias_(string_agg_expr, alias) if alias else string_agg_expr
439
+ return cast("Self", builder.select(select_expr))
440
+
441
+ def json_agg(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
442
+ """Add JSON_AGG aggregate function to SELECT clause.
443
+
444
+ Args:
445
+ column: The column to aggregate into a JSON array.
446
+ alias: Optional alias for the result.
447
+
448
+ Returns:
449
+ The current builder instance for method chaining.
450
+ """
451
+ builder = cast("SelectBuilderProtocol", self)
452
+ col_expr = exp.column(column) if isinstance(column, str) else column
453
+ json_agg_expr = exp.JSONArrayAgg(this=col_expr)
454
+ select_expr = exp.alias_(json_agg_expr, alias) if alias else json_agg_expr
455
+ return cast("Self", builder.select(select_expr))
456
+
457
+ def window(
458
+ self,
459
+ function_expr: Union[str, exp.Expression],
460
+ partition_by: Optional[Union[str, list[str], exp.Expression, list[exp.Expression]]] = None,
461
+ order_by: Optional[Union[str, list[str], exp.Expression, list[exp.Expression]]] = None,
462
+ frame: Optional[str] = None,
463
+ alias: Optional[str] = None,
464
+ ) -> Self:
465
+ """Add a window function to the SELECT clause.
466
+
467
+ Args:
468
+ function_expr: The window function expression (e.g., "COUNT(*)", "ROW_NUMBER()").
469
+ partition_by: Column(s) to partition by.
470
+ order_by: Column(s) to order by within the window.
471
+ frame: Window frame specification (e.g., "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW").
472
+ alias: Optional alias for the window function.
473
+
474
+ Raises:
475
+ SQLBuilderError: If the current expression is not a SELECT statement or function parsing fails.
476
+
477
+ Returns:
478
+ The current builder instance for method chaining.
479
+ """
480
+ if self._expression is None:
481
+ self._expression = exp.Select()
482
+ if not isinstance(self._expression, exp.Select):
483
+ msg = "Cannot add window function to a non-SELECT expression."
484
+ raise SQLBuilderError(msg)
485
+
486
+ func_expr_parsed: exp.Expression
487
+ if isinstance(function_expr, str):
488
+ parsed: Optional[exp.Expression] = exp.maybe_parse(function_expr, dialect=getattr(self, "dialect", None))
489
+ if not parsed:
490
+ msg = f"Could not parse function expression: {function_expr}"
491
+ raise SQLBuilderError(msg)
492
+ func_expr_parsed = parsed
493
+ else:
494
+ func_expr_parsed = function_expr
495
+
496
+ over_args: dict[str, Any] = {}
497
+ if partition_by:
498
+ if isinstance(partition_by, str):
499
+ over_args["partition_by"] = [exp.column(partition_by)]
500
+ elif isinstance(partition_by, list):
501
+ over_args["partition_by"] = [exp.column(col) if isinstance(col, str) else col for col in partition_by]
502
+ elif isinstance(partition_by, exp.Expression):
503
+ over_args["partition_by"] = [partition_by]
504
+
505
+ if order_by:
506
+ if isinstance(order_by, str):
507
+ over_args["order"] = exp.column(order_by).asc()
508
+ elif isinstance(order_by, list):
509
+ order_expressions: list[Union[exp.Expression, exp.Column]] = []
510
+ for col in order_by:
511
+ if isinstance(col, str):
512
+ order_expressions.append(exp.column(col).asc())
513
+ else:
514
+ order_expressions.append(col)
515
+ over_args["order"] = exp.Order(expressions=order_expressions)
516
+ elif isinstance(order_by, exp.Expression):
517
+ over_args["order"] = order_by
518
+
519
+ if frame:
520
+ frame_expr: Optional[exp.Expression] = exp.maybe_parse(frame, dialect=getattr(self, "dialect", None))
521
+ if frame_expr:
522
+ over_args["frame"] = frame_expr
523
+
524
+ window_expr = exp.Window(this=func_expr_parsed, **over_args)
525
+ self._expression.select(exp.alias_(window_expr, alias) if alias else window_expr, copy=False)
526
+ return self
527
+
528
+ def case_(self, alias: "Optional[str]" = None) -> "CaseBuilder":
529
+ """Create a CASE expression for the SELECT clause.
530
+
531
+ Args:
532
+ alias: Optional alias for the CASE expression.
533
+
534
+ Returns:
535
+ CaseBuilder: A CaseBuilder instance for building the CASE expression.
536
+ """
537
+ builder = cast("SelectBuilderProtocol", self)
538
+ return CaseBuilder(builder, alias)
539
+
540
+
541
+ @dataclass
542
+ class CaseBuilder:
543
+ """Builder for CASE expressions."""
544
+
545
+ _parent: "SelectBuilderProtocol"
546
+ _alias: Optional[str]
547
+ _case_expr: exp.Case
548
+
549
+ def __init__(self, parent: "SelectBuilderProtocol", alias: "Optional[str]" = None) -> None:
550
+ """Initialize CaseBuilder.
551
+
552
+ Args:
553
+ parent: The parent builder with select capabilities.
554
+ alias: Optional alias for the CASE expression.
555
+ """
556
+ self._parent = parent
557
+ self._alias = alias
558
+ self._case_expr = exp.Case()
559
+
560
+ def when(self, condition: "Union[str, exp.Expression]", value: "Any") -> "CaseBuilder":
561
+ """Add WHEN clause to CASE expression.
562
+
563
+ Args:
564
+ condition: The condition to test.
565
+ value: The value to return if condition is true.
566
+
567
+ Returns:
568
+ CaseBuilder: The current builder instance for method chaining.
569
+ """
570
+ cond_expr = exp.condition(condition) if isinstance(condition, str) else condition
571
+ param_name = self._parent._generate_unique_parameter_name("case_when_value")
572
+ param_name = self._parent.add_parameter(value, name=param_name)[1]
573
+ value_expr = exp.Placeholder(this=param_name)
574
+
575
+ when_clause = exp.When(this=cond_expr, then=value_expr)
576
+
577
+ if not self._case_expr.args.get("ifs"):
578
+ self._case_expr.set("ifs", [])
579
+ self._case_expr.args["ifs"].append(when_clause)
580
+ return self
581
+
582
+ def else_(self, value: "Any") -> "CaseBuilder":
583
+ """Add ELSE clause to CASE expression.
584
+
585
+ Args:
586
+ value: The value to return if no conditions match.
587
+
588
+ Returns:
589
+ CaseBuilder: The current builder instance for method chaining.
590
+ """
591
+ param_name = self._parent._generate_unique_parameter_name("case_else_value")
592
+ param_name = self._parent.add_parameter(value, name=param_name)[1]
593
+ value_expr = exp.Placeholder(this=param_name)
594
+ self._case_expr.set("default", value_expr)
595
+ return self
596
+
597
+ def end(self) -> "SelectBuilderProtocol":
598
+ """Finalize the CASE expression and add it to the SELECT clause.
599
+
600
+ Returns:
601
+ The parent builder instance.
602
+ """
603
+ select_expr = exp.alias_(self._case_expr, self._alias) if self._alias else self._case_expr
604
+ return self._parent.select(select_expr)