sqlspec 0.13.1__py3-none-any.whl → 0.14.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.
- sqlspec/__init__.py +39 -1
- sqlspec/adapters/adbc/config.py +4 -40
- sqlspec/adapters/adbc/driver.py +29 -16
- sqlspec/adapters/aiosqlite/config.py +2 -20
- sqlspec/adapters/aiosqlite/driver.py +36 -18
- sqlspec/adapters/asyncmy/config.py +2 -33
- sqlspec/adapters/asyncmy/driver.py +23 -16
- sqlspec/adapters/asyncpg/config.py +5 -39
- sqlspec/adapters/asyncpg/driver.py +41 -18
- sqlspec/adapters/bigquery/config.py +2 -43
- sqlspec/adapters/bigquery/driver.py +26 -14
- sqlspec/adapters/duckdb/config.py +2 -49
- sqlspec/adapters/duckdb/driver.py +35 -16
- sqlspec/adapters/oracledb/config.py +4 -83
- sqlspec/adapters/oracledb/driver.py +54 -27
- sqlspec/adapters/psqlpy/config.py +2 -55
- sqlspec/adapters/psqlpy/driver.py +28 -8
- sqlspec/adapters/psycopg/config.py +4 -73
- sqlspec/adapters/psycopg/driver.py +69 -24
- sqlspec/adapters/sqlite/config.py +3 -21
- sqlspec/adapters/sqlite/driver.py +50 -26
- sqlspec/cli.py +248 -0
- sqlspec/config.py +18 -20
- sqlspec/driver/_async.py +28 -10
- sqlspec/driver/_common.py +5 -4
- sqlspec/driver/_sync.py +28 -10
- sqlspec/driver/mixins/__init__.py +6 -0
- sqlspec/driver/mixins/_cache.py +114 -0
- sqlspec/driver/mixins/_pipeline.py +0 -4
- sqlspec/{service/base.py → driver/mixins/_query_tools.py} +86 -421
- sqlspec/driver/mixins/_result_utils.py +0 -2
- sqlspec/driver/mixins/_sql_translator.py +0 -2
- sqlspec/driver/mixins/_storage.py +4 -18
- sqlspec/driver/mixins/_type_coercion.py +0 -2
- sqlspec/driver/parameters.py +4 -4
- sqlspec/extensions/aiosql/adapter.py +4 -4
- sqlspec/extensions/litestar/__init__.py +2 -1
- sqlspec/extensions/litestar/cli.py +48 -0
- sqlspec/extensions/litestar/plugin.py +3 -0
- sqlspec/loader.py +1 -1
- sqlspec/migrations/__init__.py +23 -0
- sqlspec/migrations/base.py +390 -0
- sqlspec/migrations/commands.py +525 -0
- sqlspec/migrations/runner.py +215 -0
- sqlspec/migrations/tracker.py +153 -0
- sqlspec/migrations/utils.py +89 -0
- sqlspec/protocols.py +37 -3
- sqlspec/statement/builder/__init__.py +8 -8
- sqlspec/statement/builder/{column.py → _column.py} +82 -52
- sqlspec/statement/builder/{ddl.py → _ddl.py} +5 -5
- sqlspec/statement/builder/_ddl_utils.py +1 -1
- sqlspec/statement/builder/{delete.py → _delete.py} +1 -1
- sqlspec/statement/builder/{insert.py → _insert.py} +1 -1
- sqlspec/statement/builder/{merge.py → _merge.py} +1 -1
- sqlspec/statement/builder/_parsing_utils.py +5 -3
- sqlspec/statement/builder/{select.py → _select.py} +59 -61
- sqlspec/statement/builder/{update.py → _update.py} +2 -2
- sqlspec/statement/builder/mixins/__init__.py +24 -30
- sqlspec/statement/builder/mixins/{_set_ops.py → _cte_and_set_ops.py} +86 -2
- sqlspec/statement/builder/mixins/{_delete_from.py → _delete_operations.py} +2 -0
- sqlspec/statement/builder/mixins/{_insert_values.py → _insert_operations.py} +70 -1
- sqlspec/statement/builder/mixins/{_merge_clauses.py → _merge_operations.py} +2 -0
- sqlspec/statement/builder/mixins/_order_limit_operations.py +123 -0
- sqlspec/statement/builder/mixins/{_pivot.py → _pivot_operations.py} +71 -2
- sqlspec/statement/builder/mixins/_select_operations.py +612 -0
- sqlspec/statement/builder/mixins/{_update_set.py → _update_operations.py} +73 -2
- sqlspec/statement/builder/mixins/_where_clause.py +536 -0
- sqlspec/statement/cache.py +50 -0
- sqlspec/statement/filters.py +37 -8
- sqlspec/statement/parameters.py +154 -25
- sqlspec/statement/pipelines/__init__.py +1 -1
- sqlspec/statement/pipelines/context.py +4 -4
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +3 -3
- sqlspec/statement/pipelines/validators/_parameter_style.py +22 -22
- sqlspec/statement/pipelines/validators/_performance.py +1 -5
- sqlspec/statement/sql.py +246 -176
- sqlspec/utils/__init__.py +2 -1
- sqlspec/utils/statement_hashing.py +203 -0
- sqlspec/utils/type_guards.py +32 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.14.0.dist-info}/METADATA +1 -1
- sqlspec-0.14.0.dist-info/RECORD +143 -0
- sqlspec-0.14.0.dist-info/entry_points.txt +2 -0
- sqlspec/service/__init__.py +0 -4
- sqlspec/service/_util.py +0 -147
- sqlspec/service/pagination.py +0 -26
- sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
- sqlspec/statement/builder/mixins/_case_builder.py +0 -91
- sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
- sqlspec/statement/builder/mixins/_from.py +0 -63
- sqlspec/statement/builder/mixins/_group_by.py +0 -118
- sqlspec/statement/builder/mixins/_having.py +0 -35
- sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
- sqlspec/statement/builder/mixins/_insert_into.py +0 -36
- sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
- sqlspec/statement/builder/mixins/_order_by.py +0 -46
- sqlspec/statement/builder/mixins/_returning.py +0 -37
- sqlspec/statement/builder/mixins/_select_columns.py +0 -61
- sqlspec/statement/builder/mixins/_unpivot.py +0 -77
- sqlspec/statement/builder/mixins/_update_from.py +0 -55
- sqlspec/statement/builder/mixins/_update_table.py +0 -29
- sqlspec/statement/builder/mixins/_where.py +0 -401
- sqlspec/statement/builder/mixins/_window_functions.py +0 -86
- sqlspec/statement/parameter_manager.py +0 -220
- sqlspec/statement/sql_compiler.py +0 -140
- sqlspec-0.13.1.dist-info/RECORD +0 -150
- /sqlspec/statement/builder/{base.py → _base.py} +0 -0
- /sqlspec/statement/builder/mixins/{_join.py → _join_operations.py} +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.14.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.14.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.14.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING, Optional, Union, cast
|
|
2
|
-
|
|
3
|
-
from sqlglot import exp
|
|
4
|
-
from typing_extensions import Self
|
|
5
|
-
|
|
6
|
-
if TYPE_CHECKING:
|
|
7
|
-
from sqlspec.protocols import SelectBuilderProtocol
|
|
8
|
-
|
|
9
|
-
__all__ = ("AggregateFunctionsMixin",)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class AggregateFunctionsMixin:
|
|
13
|
-
"""Mixin providing aggregate function methods for SQL builders."""
|
|
14
|
-
|
|
15
|
-
def count_(self, column: "Union[str, exp.Expression]" = "*", alias: Optional[str] = None) -> Self:
|
|
16
|
-
"""Add COUNT function to SELECT clause.
|
|
17
|
-
|
|
18
|
-
Args:
|
|
19
|
-
column: The column to count (default is "*").
|
|
20
|
-
alias: Optional alias for the count.
|
|
21
|
-
|
|
22
|
-
Returns:
|
|
23
|
-
The current builder instance for method chaining.
|
|
24
|
-
"""
|
|
25
|
-
builder = cast("SelectBuilderProtocol", self)
|
|
26
|
-
if column == "*":
|
|
27
|
-
count_expr = exp.Count(this=exp.Star())
|
|
28
|
-
else:
|
|
29
|
-
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
30
|
-
count_expr = exp.Count(this=col_expr)
|
|
31
|
-
|
|
32
|
-
select_expr = exp.alias_(count_expr, alias) if alias else count_expr
|
|
33
|
-
return cast("Self", builder.select(select_expr))
|
|
34
|
-
|
|
35
|
-
def sum_(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
|
|
36
|
-
"""Add SUM function to SELECT clause.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
column: The column to sum.
|
|
40
|
-
alias: Optional alias for the sum.
|
|
41
|
-
|
|
42
|
-
Returns:
|
|
43
|
-
The current builder instance for method chaining.
|
|
44
|
-
"""
|
|
45
|
-
builder = cast("SelectBuilderProtocol", self)
|
|
46
|
-
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
47
|
-
sum_expr = exp.Sum(this=col_expr)
|
|
48
|
-
select_expr = exp.alias_(sum_expr, alias) if alias else sum_expr
|
|
49
|
-
return cast("Self", builder.select(select_expr))
|
|
50
|
-
|
|
51
|
-
def avg_(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
|
|
52
|
-
"""Add AVG function to SELECT clause.
|
|
53
|
-
|
|
54
|
-
Args:
|
|
55
|
-
column: The column to average.
|
|
56
|
-
alias: Optional alias for the average.
|
|
57
|
-
|
|
58
|
-
Returns:
|
|
59
|
-
The current builder instance for method chaining.
|
|
60
|
-
"""
|
|
61
|
-
builder = cast("SelectBuilderProtocol", self)
|
|
62
|
-
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
63
|
-
avg_expr = exp.Avg(this=col_expr)
|
|
64
|
-
select_expr = exp.alias_(avg_expr, alias) if alias else avg_expr
|
|
65
|
-
return cast("Self", builder.select(select_expr))
|
|
66
|
-
|
|
67
|
-
def max_(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
|
|
68
|
-
"""Add MAX function to SELECT clause.
|
|
69
|
-
|
|
70
|
-
Args:
|
|
71
|
-
column: The column to find the maximum of.
|
|
72
|
-
alias: Optional alias for the maximum.
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
The current builder instance for method chaining.
|
|
76
|
-
"""
|
|
77
|
-
builder = cast("SelectBuilderProtocol", self)
|
|
78
|
-
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
79
|
-
max_expr = exp.Max(this=col_expr)
|
|
80
|
-
select_expr = exp.alias_(max_expr, alias) if alias else max_expr
|
|
81
|
-
return cast("Self", builder.select(select_expr))
|
|
82
|
-
|
|
83
|
-
def min_(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
|
|
84
|
-
"""Add MIN function to SELECT clause.
|
|
85
|
-
|
|
86
|
-
Args:
|
|
87
|
-
column: The column to find the minimum of.
|
|
88
|
-
alias: Optional alias for the minimum.
|
|
89
|
-
|
|
90
|
-
Returns:
|
|
91
|
-
The current builder instance for method chaining.
|
|
92
|
-
"""
|
|
93
|
-
builder = cast("SelectBuilderProtocol", self)
|
|
94
|
-
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
95
|
-
min_expr = exp.Min(this=col_expr)
|
|
96
|
-
select_expr = exp.alias_(min_expr, alias) if alias else min_expr
|
|
97
|
-
return cast("Self", builder.select(select_expr))
|
|
98
|
-
|
|
99
|
-
def array_agg(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
|
|
100
|
-
"""Add ARRAY_AGG aggregate function to SELECT clause.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
column: The column to aggregate into an array.
|
|
104
|
-
alias: Optional alias for the result.
|
|
105
|
-
|
|
106
|
-
Returns:
|
|
107
|
-
The current builder instance for method chaining.
|
|
108
|
-
"""
|
|
109
|
-
builder = cast("SelectBuilderProtocol", self)
|
|
110
|
-
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
111
|
-
array_agg_expr = exp.ArrayAgg(this=col_expr)
|
|
112
|
-
select_expr = exp.alias_(array_agg_expr, alias) if alias else array_agg_expr
|
|
113
|
-
return cast("Self", builder.select(select_expr))
|
|
114
|
-
|
|
115
|
-
def count_distinct(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
|
|
116
|
-
"""Add COUNT(DISTINCT column) to SELECT clause.
|
|
117
|
-
|
|
118
|
-
Args:
|
|
119
|
-
column: The column to count distinct values of.
|
|
120
|
-
alias: Optional alias for the count.
|
|
121
|
-
|
|
122
|
-
Returns:
|
|
123
|
-
The current builder instance for method chaining.
|
|
124
|
-
"""
|
|
125
|
-
builder = cast("SelectBuilderProtocol", self)
|
|
126
|
-
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
127
|
-
count_expr = exp.Count(this=exp.Distinct(expressions=[col_expr]))
|
|
128
|
-
select_expr = exp.alias_(count_expr, alias) if alias else count_expr
|
|
129
|
-
return cast("Self", builder.select(select_expr))
|
|
130
|
-
|
|
131
|
-
def stddev(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
|
|
132
|
-
"""Add STDDEV aggregate function to SELECT clause.
|
|
133
|
-
|
|
134
|
-
Args:
|
|
135
|
-
column: The column to calculate standard deviation of.
|
|
136
|
-
alias: Optional alias for the result.
|
|
137
|
-
|
|
138
|
-
Returns:
|
|
139
|
-
The current builder instance for method chaining.
|
|
140
|
-
"""
|
|
141
|
-
builder = cast("SelectBuilderProtocol", self)
|
|
142
|
-
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
143
|
-
stddev_expr = exp.Stddev(this=col_expr)
|
|
144
|
-
select_expr = exp.alias_(stddev_expr, alias) if alias else stddev_expr
|
|
145
|
-
return cast("Self", builder.select(select_expr))
|
|
146
|
-
|
|
147
|
-
def stddev_pop(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
|
|
148
|
-
"""Add STDDEV_POP aggregate function to SELECT clause.
|
|
149
|
-
|
|
150
|
-
Args:
|
|
151
|
-
column: The column to calculate population standard deviation of.
|
|
152
|
-
alias: Optional alias for the result.
|
|
153
|
-
|
|
154
|
-
Returns:
|
|
155
|
-
The current builder instance for method chaining.
|
|
156
|
-
"""
|
|
157
|
-
builder = cast("SelectBuilderProtocol", self)
|
|
158
|
-
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
159
|
-
stddev_pop_expr = exp.StddevPop(this=col_expr)
|
|
160
|
-
select_expr = exp.alias_(stddev_pop_expr, alias) if alias else stddev_pop_expr
|
|
161
|
-
return cast("Self", builder.select(select_expr))
|
|
162
|
-
|
|
163
|
-
def stddev_samp(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
|
|
164
|
-
"""Add STDDEV_SAMP aggregate function to SELECT clause.
|
|
165
|
-
|
|
166
|
-
Args:
|
|
167
|
-
column: The column to calculate sample standard deviation of.
|
|
168
|
-
alias: Optional alias for the result.
|
|
169
|
-
|
|
170
|
-
Returns:
|
|
171
|
-
The current builder instance for method chaining.
|
|
172
|
-
"""
|
|
173
|
-
builder = cast("SelectBuilderProtocol", self)
|
|
174
|
-
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
175
|
-
stddev_samp_expr = exp.StddevSamp(this=col_expr)
|
|
176
|
-
select_expr = exp.alias_(stddev_samp_expr, alias) if alias else stddev_samp_expr
|
|
177
|
-
return cast("Self", builder.select(select_expr))
|
|
178
|
-
|
|
179
|
-
def variance(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
|
|
180
|
-
"""Add VARIANCE aggregate function to SELECT clause.
|
|
181
|
-
|
|
182
|
-
Args:
|
|
183
|
-
column: The column to calculate variance of.
|
|
184
|
-
alias: Optional alias for the result.
|
|
185
|
-
|
|
186
|
-
Returns:
|
|
187
|
-
The current builder instance for method chaining.
|
|
188
|
-
"""
|
|
189
|
-
builder = cast("SelectBuilderProtocol", self)
|
|
190
|
-
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
191
|
-
variance_expr = exp.Variance(this=col_expr)
|
|
192
|
-
select_expr = exp.alias_(variance_expr, alias) if alias else variance_expr
|
|
193
|
-
return cast("Self", builder.select(select_expr))
|
|
194
|
-
|
|
195
|
-
def var_pop(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
|
|
196
|
-
"""Add VAR_POP aggregate function to SELECT clause.
|
|
197
|
-
|
|
198
|
-
Args:
|
|
199
|
-
column: The column to calculate population variance of.
|
|
200
|
-
alias: Optional alias for the result.
|
|
201
|
-
|
|
202
|
-
Returns:
|
|
203
|
-
The current builder instance for method chaining.
|
|
204
|
-
"""
|
|
205
|
-
builder = cast("SelectBuilderProtocol", self)
|
|
206
|
-
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
207
|
-
var_pop_expr = exp.VariancePop(this=col_expr)
|
|
208
|
-
select_expr = exp.alias_(var_pop_expr, alias) if alias else var_pop_expr
|
|
209
|
-
return cast("Self", builder.select(select_expr))
|
|
210
|
-
|
|
211
|
-
def string_agg(self, column: Union[str, exp.Expression], separator: str = ",", alias: Optional[str] = None) -> Self:
|
|
212
|
-
"""Add STRING_AGG aggregate function to SELECT clause.
|
|
213
|
-
|
|
214
|
-
Args:
|
|
215
|
-
column: The column to aggregate into a string.
|
|
216
|
-
separator: The separator between values (default is comma).
|
|
217
|
-
alias: Optional alias for the result.
|
|
218
|
-
|
|
219
|
-
Returns:
|
|
220
|
-
The current builder instance for method chaining.
|
|
221
|
-
|
|
222
|
-
Note:
|
|
223
|
-
Different databases have different names for this function:
|
|
224
|
-
- PostgreSQL: STRING_AGG
|
|
225
|
-
- MySQL: GROUP_CONCAT
|
|
226
|
-
- SQLite: GROUP_CONCAT
|
|
227
|
-
SQLGlot will handle the translation.
|
|
228
|
-
"""
|
|
229
|
-
builder = cast("SelectBuilderProtocol", self)
|
|
230
|
-
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
231
|
-
# Use GroupConcat which SQLGlot can translate to STRING_AGG for Postgres
|
|
232
|
-
string_agg_expr = exp.GroupConcat(this=col_expr, separator=exp.Literal.string(separator))
|
|
233
|
-
select_expr = exp.alias_(string_agg_expr, alias) if alias else string_agg_expr
|
|
234
|
-
return cast("Self", builder.select(select_expr))
|
|
235
|
-
|
|
236
|
-
def json_agg(self, column: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
|
|
237
|
-
"""Add JSON_AGG aggregate function to SELECT clause.
|
|
238
|
-
|
|
239
|
-
Args:
|
|
240
|
-
column: The column to aggregate into a JSON array.
|
|
241
|
-
alias: Optional alias for the result.
|
|
242
|
-
|
|
243
|
-
Returns:
|
|
244
|
-
The current builder instance for method chaining.
|
|
245
|
-
"""
|
|
246
|
-
builder = cast("SelectBuilderProtocol", self)
|
|
247
|
-
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
248
|
-
json_agg_expr = exp.JSONArrayAgg(this=col_expr)
|
|
249
|
-
select_expr = exp.alias_(json_agg_expr, alias) if alias else json_agg_expr
|
|
250
|
-
return cast("Self", builder.select(select_expr))
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# mypy: disable-error-code="valid-type,type-var"
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
4
|
-
|
|
5
|
-
from sqlglot import exp
|
|
6
|
-
|
|
7
|
-
if TYPE_CHECKING:
|
|
8
|
-
from sqlspec.statement.builder.base import QueryBuilder
|
|
9
|
-
from sqlspec.typing import RowT
|
|
10
|
-
|
|
11
|
-
__all__ = ("CaseBuilder", "CaseBuilderMixin")
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class CaseBuilderMixin:
|
|
15
|
-
"""Mixin providing CASE expression functionality for SQL builders."""
|
|
16
|
-
|
|
17
|
-
def case_(self, alias: "Optional[str]" = None) -> "CaseBuilder":
|
|
18
|
-
"""Create a CASE expression for the SELECT clause.
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
alias: Optional alias for the CASE expression.
|
|
22
|
-
|
|
23
|
-
Returns:
|
|
24
|
-
CaseBuilder: A CaseBuilder instance for building the CASE expression.
|
|
25
|
-
"""
|
|
26
|
-
builder = cast("QueryBuilder[RowT]", self) # pyright: ignore
|
|
27
|
-
return CaseBuilder(builder, alias)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@dataclass
|
|
31
|
-
class CaseBuilder:
|
|
32
|
-
"""Builder for CASE expressions."""
|
|
33
|
-
|
|
34
|
-
_parent: "QueryBuilder[RowT]" # pyright: ignore
|
|
35
|
-
_alias: Optional[str]
|
|
36
|
-
_case_expr: exp.Case
|
|
37
|
-
|
|
38
|
-
def __init__(self, parent: "QueryBuilder[RowT]", alias: "Optional[str]" = None) -> None:
|
|
39
|
-
"""Initialize CaseBuilder.
|
|
40
|
-
|
|
41
|
-
Args:
|
|
42
|
-
parent: The parent builder.
|
|
43
|
-
alias: Optional alias for the CASE expression.
|
|
44
|
-
"""
|
|
45
|
-
self._parent = parent
|
|
46
|
-
self._alias = alias
|
|
47
|
-
self._case_expr = exp.Case()
|
|
48
|
-
|
|
49
|
-
def when(self, condition: "Union[str, exp.Expression]", value: "Any") -> "CaseBuilder":
|
|
50
|
-
"""Add WHEN clause to CASE expression.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
condition: The condition to test.
|
|
54
|
-
value: The value to return if condition is true.
|
|
55
|
-
|
|
56
|
-
Returns:
|
|
57
|
-
CaseBuilder: The current builder instance for method chaining.
|
|
58
|
-
"""
|
|
59
|
-
cond_expr = exp.condition(condition) if isinstance(condition, str) else condition
|
|
60
|
-
param_name = self._parent.add_parameter(value)[1]
|
|
61
|
-
value_expr = exp.Placeholder(this=param_name)
|
|
62
|
-
|
|
63
|
-
when_clause = exp.When(this=cond_expr, then=value_expr)
|
|
64
|
-
|
|
65
|
-
if not self._case_expr.args.get("ifs"):
|
|
66
|
-
self._case_expr.set("ifs", [])
|
|
67
|
-
self._case_expr.args["ifs"].append(when_clause)
|
|
68
|
-
return self
|
|
69
|
-
|
|
70
|
-
def else_(self, value: "Any") -> "CaseBuilder":
|
|
71
|
-
"""Add ELSE clause to CASE expression.
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
value: The value to return if no conditions match.
|
|
75
|
-
|
|
76
|
-
Returns:
|
|
77
|
-
CaseBuilder: The current builder instance for method chaining.
|
|
78
|
-
"""
|
|
79
|
-
param_name = self._parent.add_parameter(value)[1]
|
|
80
|
-
value_expr = exp.Placeholder(this=param_name)
|
|
81
|
-
self._case_expr.set("default", value_expr)
|
|
82
|
-
return self
|
|
83
|
-
|
|
84
|
-
def end(self) -> "QueryBuilder[RowT]":
|
|
85
|
-
"""Finalize the CASE expression and add it to the SELECT clause.
|
|
86
|
-
|
|
87
|
-
Returns:
|
|
88
|
-
The parent builder instance.
|
|
89
|
-
"""
|
|
90
|
-
select_expr = exp.alias_(self._case_expr, self._alias) if self._alias else self._case_expr
|
|
91
|
-
return cast("QueryBuilder[RowT]", self._parent.select(select_expr)) # type: ignore[attr-defined]
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
from typing import Any, Optional, Union
|
|
2
|
-
|
|
3
|
-
from sqlglot import exp
|
|
4
|
-
from typing_extensions import Self
|
|
5
|
-
|
|
6
|
-
from sqlspec.exceptions import SQLBuilderError
|
|
7
|
-
|
|
8
|
-
__all__ = ("CommonTableExpressionMixin",)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class CommonTableExpressionMixin:
|
|
12
|
-
"""Mixin providing WITH clause (Common Table Expressions) support for SQL builders."""
|
|
13
|
-
|
|
14
|
-
_expression: Optional[exp.Expression] = None
|
|
15
|
-
|
|
16
|
-
def with_(
|
|
17
|
-
self, name: str, query: Union[Any, str], recursive: bool = False, columns: Optional[list[str]] = None
|
|
18
|
-
) -> Self:
|
|
19
|
-
"""Add WITH clause (Common Table Expression).
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
name: The name of the CTE.
|
|
23
|
-
query: The query for the CTE (builder instance or SQL string).
|
|
24
|
-
recursive: Whether this is a recursive CTE.
|
|
25
|
-
columns: Optional column names for the CTE.
|
|
26
|
-
|
|
27
|
-
Raises:
|
|
28
|
-
SQLBuilderError: If the query type is unsupported.
|
|
29
|
-
|
|
30
|
-
Returns:
|
|
31
|
-
The current builder instance for method chaining.
|
|
32
|
-
"""
|
|
33
|
-
if self._expression is None:
|
|
34
|
-
msg = "Cannot add WITH clause: expression not initialized."
|
|
35
|
-
raise SQLBuilderError(msg)
|
|
36
|
-
|
|
37
|
-
if not hasattr(self._expression, "with_") and not isinstance(
|
|
38
|
-
self._expression, (exp.Select, exp.Insert, exp.Update, exp.Delete)
|
|
39
|
-
):
|
|
40
|
-
msg = f"Cannot add WITH clause to {type(self._expression).__name__} expression."
|
|
41
|
-
raise SQLBuilderError(msg)
|
|
42
|
-
|
|
43
|
-
cte_expr: Optional[exp.Expression] = None
|
|
44
|
-
if hasattr(query, "to_statement"):
|
|
45
|
-
# Query is a builder instance
|
|
46
|
-
built_query = query.to_statement() # pyright: ignore
|
|
47
|
-
cte_sql = built_query.to_sql()
|
|
48
|
-
cte_expr = exp.maybe_parse(cte_sql, dialect=getattr(self, "dialect", None))
|
|
49
|
-
|
|
50
|
-
# Merge parameters
|
|
51
|
-
if hasattr(self, "add_parameter"):
|
|
52
|
-
parameters = getattr(built_query, "parameters", None) or {}
|
|
53
|
-
for param_name, param_value in parameters.items():
|
|
54
|
-
self.add_parameter(param_value, name=param_name) # pyright: ignore
|
|
55
|
-
elif isinstance(query, str):
|
|
56
|
-
cte_expr = exp.maybe_parse(query, dialect=getattr(self, "dialect", None))
|
|
57
|
-
elif isinstance(query, exp.Expression):
|
|
58
|
-
cte_expr = query
|
|
59
|
-
|
|
60
|
-
if not cte_expr:
|
|
61
|
-
msg = f"Could not parse CTE query: {query}"
|
|
62
|
-
raise SQLBuilderError(msg)
|
|
63
|
-
|
|
64
|
-
if columns:
|
|
65
|
-
# CTE with explicit column list: name(col1, col2, ...)
|
|
66
|
-
cte_alias_expr = exp.alias_(cte_expr, name, table=[exp.to_identifier(col) for col in columns])
|
|
67
|
-
else:
|
|
68
|
-
# Simple CTE alias: name
|
|
69
|
-
cte_alias_expr = exp.alias_(cte_expr, name)
|
|
70
|
-
|
|
71
|
-
# Different handling for different expression types
|
|
72
|
-
if hasattr(self._expression, "with_"):
|
|
73
|
-
existing_with = self._expression.args.get("with") # pyright: ignore
|
|
74
|
-
if existing_with:
|
|
75
|
-
existing_with.expressions.append(cte_alias_expr)
|
|
76
|
-
if recursive:
|
|
77
|
-
existing_with.set("recursive", recursive)
|
|
78
|
-
else:
|
|
79
|
-
self._expression = self._expression.with_(cte_alias_expr, as_=name, copy=False) # pyright: ignore
|
|
80
|
-
if recursive:
|
|
81
|
-
with_clause = self._expression.find(exp.With)
|
|
82
|
-
if with_clause:
|
|
83
|
-
with_clause.set("recursive", recursive)
|
|
84
|
-
else:
|
|
85
|
-
# Store CTEs for later application during build
|
|
86
|
-
if not hasattr(self, "_with_ctes"):
|
|
87
|
-
setattr(self, "_with_ctes", {})
|
|
88
|
-
self._with_ctes[name] = exp.CTE(this=cte_expr, alias=exp.to_table(name)) # type: ignore[attr-defined]
|
|
89
|
-
|
|
90
|
-
return self
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
2
|
-
|
|
3
|
-
from sqlglot import exp
|
|
4
|
-
from typing_extensions import Self
|
|
5
|
-
|
|
6
|
-
from sqlspec.exceptions import SQLBuilderError
|
|
7
|
-
from sqlspec.statement.builder._parsing_utils import parse_table_expression
|
|
8
|
-
from sqlspec.utils.type_guards import has_query_builder_parameters, is_expression
|
|
9
|
-
|
|
10
|
-
if TYPE_CHECKING:
|
|
11
|
-
from sqlspec.protocols import SQLBuilderProtocol
|
|
12
|
-
|
|
13
|
-
__all__ = ("FromClauseMixin",)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class FromClauseMixin:
|
|
17
|
-
"""Mixin providing FROM clause for SELECT builders."""
|
|
18
|
-
|
|
19
|
-
def from_(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
|
|
20
|
-
"""Add FROM clause.
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
table: The table name, expression, or subquery to select from.
|
|
24
|
-
alias: Optional alias for the table.
|
|
25
|
-
|
|
26
|
-
Raises:
|
|
27
|
-
SQLBuilderError: If the current expression is not a SELECT statement or if the table type is unsupported.
|
|
28
|
-
|
|
29
|
-
Returns:
|
|
30
|
-
The current builder instance for method chaining.
|
|
31
|
-
"""
|
|
32
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
33
|
-
if builder._expression is None:
|
|
34
|
-
builder._expression = exp.Select()
|
|
35
|
-
if not isinstance(builder._expression, exp.Select):
|
|
36
|
-
msg = "FROM clause is only supported for SELECT statements."
|
|
37
|
-
raise SQLBuilderError(msg)
|
|
38
|
-
from_expr: exp.Expression
|
|
39
|
-
if isinstance(table, str):
|
|
40
|
-
from_expr = parse_table_expression(table, alias)
|
|
41
|
-
elif is_expression(table):
|
|
42
|
-
# Direct sqlglot expression - use as is
|
|
43
|
-
from_expr = exp.alias_(table, alias) if alias else table
|
|
44
|
-
elif has_query_builder_parameters(table):
|
|
45
|
-
# Query builder with build() method
|
|
46
|
-
subquery = table.build()
|
|
47
|
-
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
48
|
-
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect", None)))
|
|
49
|
-
from_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
50
|
-
current_params = getattr(builder, "_parameters", None)
|
|
51
|
-
merged_params = getattr(type(builder), "ParameterConverter", None)
|
|
52
|
-
if merged_params and hasattr(subquery, "parameters"):
|
|
53
|
-
subquery_params = getattr(subquery, "parameters", {})
|
|
54
|
-
merged_params = merged_params.merge_parameters(
|
|
55
|
-
parameters=subquery_params,
|
|
56
|
-
args=current_params if isinstance(current_params, list) else None,
|
|
57
|
-
kwargs=current_params if isinstance(current_params, dict) else {},
|
|
58
|
-
)
|
|
59
|
-
setattr(builder, "_parameters", merged_params)
|
|
60
|
-
else:
|
|
61
|
-
from_expr = table
|
|
62
|
-
builder._expression = builder._expression.from_(from_expr, copy=False)
|
|
63
|
-
return cast("Self", builder)
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
from typing import Optional, Union
|
|
2
|
-
|
|
3
|
-
from sqlglot import exp
|
|
4
|
-
from typing_extensions import Self
|
|
5
|
-
|
|
6
|
-
__all__ = ("GroupByClauseMixin",)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class GroupByClauseMixin:
|
|
10
|
-
"""Mixin providing GROUP BY clause functionality for SQL builders."""
|
|
11
|
-
|
|
12
|
-
_expression: Optional[exp.Expression] = None
|
|
13
|
-
|
|
14
|
-
def group_by(self, *columns: Union[str, exp.Expression]) -> Self:
|
|
15
|
-
"""Add GROUP BY clause.
|
|
16
|
-
|
|
17
|
-
Args:
|
|
18
|
-
*columns: Columns to group by. Can be column names, expressions,
|
|
19
|
-
or special grouping expressions like ROLLUP, CUBE, etc.
|
|
20
|
-
|
|
21
|
-
Returns:
|
|
22
|
-
The current builder instance for method chaining.
|
|
23
|
-
"""
|
|
24
|
-
if self._expression is None or not isinstance(self._expression, exp.Select):
|
|
25
|
-
return self
|
|
26
|
-
|
|
27
|
-
for column in columns:
|
|
28
|
-
self._expression = self._expression.group_by(
|
|
29
|
-
exp.column(column) if isinstance(column, str) else column, copy=False
|
|
30
|
-
)
|
|
31
|
-
return self
|
|
32
|
-
|
|
33
|
-
def group_by_rollup(self, *columns: Union[str, exp.Expression]) -> Self:
|
|
34
|
-
"""Add GROUP BY ROLLUP clause.
|
|
35
|
-
|
|
36
|
-
ROLLUP generates subtotals and grand totals for a hierarchical set of columns.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
*columns: Columns to include in the rollup hierarchy.
|
|
40
|
-
|
|
41
|
-
Returns:
|
|
42
|
-
The current builder instance for method chaining.
|
|
43
|
-
|
|
44
|
-
Example:
|
|
45
|
-
```python
|
|
46
|
-
# GROUP BY ROLLUP(product, region)
|
|
47
|
-
query = (
|
|
48
|
-
sql.select("product", "region", sql.sum("sales"))
|
|
49
|
-
.from_("sales_data")
|
|
50
|
-
.group_by_rollup("product", "region")
|
|
51
|
-
)
|
|
52
|
-
```
|
|
53
|
-
"""
|
|
54
|
-
column_exprs = [exp.column(col) if isinstance(col, str) else col for col in columns]
|
|
55
|
-
rollup_expr = exp.Rollup(expressions=column_exprs)
|
|
56
|
-
return self.group_by(rollup_expr)
|
|
57
|
-
|
|
58
|
-
def group_by_cube(self, *columns: Union[str, exp.Expression]) -> Self:
|
|
59
|
-
"""Add GROUP BY CUBE clause.
|
|
60
|
-
|
|
61
|
-
CUBE generates subtotals for all possible combinations of the specified columns.
|
|
62
|
-
|
|
63
|
-
Args:
|
|
64
|
-
*columns: Columns to include in the cube.
|
|
65
|
-
|
|
66
|
-
Returns:
|
|
67
|
-
The current builder instance for method chaining.
|
|
68
|
-
|
|
69
|
-
Example:
|
|
70
|
-
```python
|
|
71
|
-
# GROUP BY CUBE(product, region)
|
|
72
|
-
query = (
|
|
73
|
-
sql.select("product", "region", sql.sum("sales"))
|
|
74
|
-
.from_("sales_data")
|
|
75
|
-
.group_by_cube("product", "region")
|
|
76
|
-
)
|
|
77
|
-
```
|
|
78
|
-
"""
|
|
79
|
-
column_exprs = [exp.column(col) if isinstance(col, str) else col for col in columns]
|
|
80
|
-
cube_expr = exp.Cube(expressions=column_exprs)
|
|
81
|
-
return self.group_by(cube_expr)
|
|
82
|
-
|
|
83
|
-
def group_by_grouping_sets(self, *column_sets: Union[tuple[str, ...], list[str]]) -> Self:
|
|
84
|
-
"""Add GROUP BY GROUPING SETS clause.
|
|
85
|
-
|
|
86
|
-
GROUPING SETS allows you to specify multiple grouping sets in a single query.
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
*column_sets: Sets of columns to group by. Each set can be a tuple or list.
|
|
90
|
-
Empty tuple/list creates a grand total grouping.
|
|
91
|
-
|
|
92
|
-
Returns:
|
|
93
|
-
The current builder instance for method chaining.
|
|
94
|
-
|
|
95
|
-
Example:
|
|
96
|
-
```python
|
|
97
|
-
# GROUP BY GROUPING SETS ((product), (region), ())
|
|
98
|
-
query = (
|
|
99
|
-
sql.select("product", "region", sql.sum("sales"))
|
|
100
|
-
.from_("sales_data")
|
|
101
|
-
.group_by_grouping_sets(("product",), ("region",), ())
|
|
102
|
-
)
|
|
103
|
-
```
|
|
104
|
-
"""
|
|
105
|
-
set_expressions = []
|
|
106
|
-
for column_set in column_sets:
|
|
107
|
-
if isinstance(column_set, (tuple, list)):
|
|
108
|
-
if len(column_set) == 0:
|
|
109
|
-
set_expressions.append(exp.Tuple(expressions=[]))
|
|
110
|
-
else:
|
|
111
|
-
columns = [exp.column(col) for col in column_set]
|
|
112
|
-
set_expressions.append(exp.Tuple(expressions=columns))
|
|
113
|
-
else:
|
|
114
|
-
# Single column
|
|
115
|
-
set_expressions.append(exp.column(column_set))
|
|
116
|
-
|
|
117
|
-
grouping_sets_expr = exp.GroupingSets(expressions=set_expressions)
|
|
118
|
-
return self.group_by(grouping_sets_expr)
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
from typing import Optional, Union
|
|
2
|
-
|
|
3
|
-
from sqlglot import exp
|
|
4
|
-
from typing_extensions import Self
|
|
5
|
-
|
|
6
|
-
from sqlspec.exceptions import SQLBuilderError
|
|
7
|
-
|
|
8
|
-
__all__ = ("HavingClauseMixin",)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class HavingClauseMixin:
|
|
12
|
-
"""Mixin providing HAVING clause for SELECT builders."""
|
|
13
|
-
|
|
14
|
-
_expression: Optional[exp.Expression] = None
|
|
15
|
-
|
|
16
|
-
def having(self, condition: Union[str, exp.Expression]) -> Self:
|
|
17
|
-
"""Add HAVING clause.
|
|
18
|
-
|
|
19
|
-
Args:
|
|
20
|
-
condition: The condition for the HAVING clause.
|
|
21
|
-
|
|
22
|
-
Raises:
|
|
23
|
-
SQLBuilderError: If the current expression is not a SELECT statement.
|
|
24
|
-
|
|
25
|
-
Returns:
|
|
26
|
-
The current builder instance for method chaining.
|
|
27
|
-
"""
|
|
28
|
-
if self._expression is None:
|
|
29
|
-
self._expression = exp.Select()
|
|
30
|
-
if not isinstance(self._expression, exp.Select):
|
|
31
|
-
msg = "Cannot add HAVING to a non-SELECT expression."
|
|
32
|
-
raise SQLBuilderError(msg)
|
|
33
|
-
having_expr = exp.condition(condition) if isinstance(condition, str) else condition
|
|
34
|
-
self._expression = self._expression.having(having_expr, copy=False)
|
|
35
|
-
return self
|