sqlspec 0.13.1__py3-none-any.whl → 0.14.1__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/__main__.py +12 -0
- sqlspec/adapters/adbc/config.py +16 -40
- sqlspec/adapters/adbc/driver.py +43 -16
- sqlspec/adapters/adbc/transformers.py +108 -0
- 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 +143 -54
- sqlspec/statement/pipelines/__init__.py +1 -1
- sqlspec/statement/pipelines/context.py +4 -10
- 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.1.dist-info}/METADATA +1 -1
- sqlspec-0.14.1.dist-info/RECORD +145 -0
- sqlspec-0.14.1.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.1.dist-info}/WHEEL +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.14.1.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.14.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -18,37 +18,37 @@ class ColumnExpression:
|
|
|
18
18
|
"""Base class for column expressions that can be combined with operators."""
|
|
19
19
|
|
|
20
20
|
def __init__(self, expression: exp.Expression) -> None:
|
|
21
|
-
self.
|
|
21
|
+
self._expression = expression
|
|
22
22
|
|
|
23
23
|
def __and__(self, other: "ColumnExpression") -> "ColumnExpression":
|
|
24
24
|
"""Combine with AND operator (&)."""
|
|
25
25
|
if not isinstance(other, ColumnExpression):
|
|
26
26
|
return NotImplemented
|
|
27
|
-
return ColumnExpression(exp.And(this=self.
|
|
27
|
+
return ColumnExpression(exp.And(this=self._expression, expression=other._expression))
|
|
28
28
|
|
|
29
29
|
def __or__(self, other: "ColumnExpression") -> "ColumnExpression":
|
|
30
30
|
"""Combine with OR operator (|)."""
|
|
31
31
|
if not isinstance(other, ColumnExpression):
|
|
32
32
|
return NotImplemented
|
|
33
|
-
return ColumnExpression(exp.Or(this=self.
|
|
33
|
+
return ColumnExpression(exp.Or(this=self._expression, expression=other._expression))
|
|
34
34
|
|
|
35
35
|
def __invert__(self) -> "ColumnExpression":
|
|
36
36
|
"""Apply NOT operator (~)."""
|
|
37
|
-
return ColumnExpression(exp.Not(this=self.
|
|
37
|
+
return ColumnExpression(exp.Not(this=self._expression))
|
|
38
38
|
|
|
39
39
|
def __bool__(self) -> bool:
|
|
40
40
|
"""Prevent accidental use of 'and'/'or' keywords."""
|
|
41
41
|
msg = (
|
|
42
42
|
"Cannot use 'and'/'or' operators on ColumnExpression. "
|
|
43
43
|
"Use '&'/'|' operators instead. "
|
|
44
|
-
f"Expression: {self.
|
|
44
|
+
f"Expression: {self._expression.sql()}"
|
|
45
45
|
)
|
|
46
46
|
raise TypeError(msg)
|
|
47
47
|
|
|
48
48
|
@property
|
|
49
49
|
def sqlglot_expression(self) -> exp.Expression:
|
|
50
50
|
"""Get the underlying SQLGlot expression."""
|
|
51
|
-
return self.
|
|
51
|
+
return self._expression
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
class Column:
|
|
@@ -60,60 +60,60 @@ class Column:
|
|
|
60
60
|
|
|
61
61
|
# Create SQLGlot column expression
|
|
62
62
|
if table:
|
|
63
|
-
self.
|
|
63
|
+
self._expression = exp.Column(this=exp.Identifier(this=name), table=exp.Identifier(this=table))
|
|
64
64
|
else:
|
|
65
|
-
self.
|
|
65
|
+
self._expression = exp.Column(this=exp.Identifier(this=name))
|
|
66
66
|
|
|
67
67
|
# Comparison operators
|
|
68
68
|
def __eq__(self, other: object) -> ColumnExpression: # type: ignore[override]
|
|
69
69
|
"""Equal to (==)."""
|
|
70
70
|
if other is None:
|
|
71
|
-
return ColumnExpression(exp.Is(this=self.
|
|
72
|
-
return ColumnExpression(exp.EQ(this=self.
|
|
71
|
+
return ColumnExpression(exp.Is(this=self._expression, expression=exp.Null()))
|
|
72
|
+
return ColumnExpression(exp.EQ(this=self._expression, expression=exp.convert(other)))
|
|
73
73
|
|
|
74
74
|
def __ne__(self, other: object) -> ColumnExpression: # type: ignore[override]
|
|
75
75
|
"""Not equal to (!=)."""
|
|
76
76
|
if other is None:
|
|
77
|
-
return ColumnExpression(exp.Not(this=exp.Is(this=self.
|
|
78
|
-
return ColumnExpression(exp.NEQ(this=self.
|
|
77
|
+
return ColumnExpression(exp.Not(this=exp.Is(this=self._expression, expression=exp.Null())))
|
|
78
|
+
return ColumnExpression(exp.NEQ(this=self._expression, expression=exp.convert(other)))
|
|
79
79
|
|
|
80
80
|
def __gt__(self, other: Any) -> ColumnExpression:
|
|
81
81
|
"""Greater than (>)."""
|
|
82
|
-
return ColumnExpression(exp.GT(this=self.
|
|
82
|
+
return ColumnExpression(exp.GT(this=self._expression, expression=exp.convert(other)))
|
|
83
83
|
|
|
84
84
|
def __ge__(self, other: Any) -> ColumnExpression:
|
|
85
85
|
"""Greater than or equal (>=)."""
|
|
86
|
-
return ColumnExpression(exp.GTE(this=self.
|
|
86
|
+
return ColumnExpression(exp.GTE(this=self._expression, expression=exp.convert(other)))
|
|
87
87
|
|
|
88
88
|
def __lt__(self, other: Any) -> ColumnExpression:
|
|
89
89
|
"""Less than (<)."""
|
|
90
|
-
return ColumnExpression(exp.LT(this=self.
|
|
90
|
+
return ColumnExpression(exp.LT(this=self._expression, expression=exp.convert(other)))
|
|
91
91
|
|
|
92
92
|
def __le__(self, other: Any) -> ColumnExpression:
|
|
93
93
|
"""Less than or equal (<=)."""
|
|
94
|
-
return ColumnExpression(exp.LTE(this=self.
|
|
94
|
+
return ColumnExpression(exp.LTE(this=self._expression, expression=exp.convert(other)))
|
|
95
95
|
|
|
96
96
|
def __invert__(self) -> ColumnExpression:
|
|
97
97
|
"""Apply NOT operator (~)."""
|
|
98
|
-
return ColumnExpression(exp.Not(this=self.
|
|
98
|
+
return ColumnExpression(exp.Not(this=self._expression))
|
|
99
99
|
|
|
100
100
|
# SQL-specific methods
|
|
101
101
|
def like(self, pattern: str, escape: Optional[str] = None) -> ColumnExpression:
|
|
102
102
|
"""SQL LIKE pattern matching."""
|
|
103
103
|
if escape:
|
|
104
|
-
like_expr = exp.Like(this=self.
|
|
104
|
+
like_expr = exp.Like(this=self._expression, expression=exp.convert(pattern), escape=exp.convert(escape))
|
|
105
105
|
else:
|
|
106
|
-
like_expr = exp.Like(this=self.
|
|
106
|
+
like_expr = exp.Like(this=self._expression, expression=exp.convert(pattern))
|
|
107
107
|
return ColumnExpression(like_expr)
|
|
108
108
|
|
|
109
109
|
def ilike(self, pattern: str) -> ColumnExpression:
|
|
110
110
|
"""Case-insensitive LIKE."""
|
|
111
|
-
return ColumnExpression(exp.ILike(this=self.
|
|
111
|
+
return ColumnExpression(exp.ILike(this=self._expression, expression=exp.convert(pattern)))
|
|
112
112
|
|
|
113
113
|
def in_(self, values: Iterable[Any]) -> ColumnExpression:
|
|
114
114
|
"""SQL IN clause."""
|
|
115
115
|
converted_values = [exp.convert(v) for v in values]
|
|
116
|
-
return ColumnExpression(exp.In(this=self.
|
|
116
|
+
return ColumnExpression(exp.In(this=self._expression, expressions=converted_values))
|
|
117
117
|
|
|
118
118
|
def not_in(self, values: Iterable[Any]) -> ColumnExpression:
|
|
119
119
|
"""SQL NOT IN clause."""
|
|
@@ -121,15 +121,15 @@ class Column:
|
|
|
121
121
|
|
|
122
122
|
def between(self, start: Any, end: Any) -> ColumnExpression:
|
|
123
123
|
"""SQL BETWEEN clause."""
|
|
124
|
-
return ColumnExpression(exp.Between(this=self.
|
|
124
|
+
return ColumnExpression(exp.Between(this=self._expression, low=exp.convert(start), high=exp.convert(end)))
|
|
125
125
|
|
|
126
126
|
def is_null(self) -> ColumnExpression:
|
|
127
127
|
"""SQL IS NULL."""
|
|
128
|
-
return ColumnExpression(exp.Is(this=self.
|
|
128
|
+
return ColumnExpression(exp.Is(this=self._expression, expression=exp.Null()))
|
|
129
129
|
|
|
130
130
|
def is_not_null(self) -> ColumnExpression:
|
|
131
131
|
"""SQL IS NOT NULL."""
|
|
132
|
-
return ColumnExpression(exp.Not(this=exp.Is(this=self.
|
|
132
|
+
return ColumnExpression(exp.Not(this=exp.Is(this=self._expression, expression=exp.Null())))
|
|
133
133
|
|
|
134
134
|
def not_like(self, pattern: str, escape: Optional[str] = None) -> ColumnExpression:
|
|
135
135
|
"""SQL NOT LIKE pattern matching."""
|
|
@@ -142,67 +142,97 @@ class Column:
|
|
|
142
142
|
def any_(self, values: Iterable[Any]) -> ColumnExpression:
|
|
143
143
|
"""SQL = ANY(...) clause."""
|
|
144
144
|
converted_values = [exp.convert(v) for v in values]
|
|
145
|
-
return ColumnExpression(exp.EQ(this=self.
|
|
145
|
+
return ColumnExpression(exp.EQ(this=self._expression, expression=exp.Any(expressions=converted_values)))
|
|
146
146
|
|
|
147
147
|
def not_any_(self, values: Iterable[Any]) -> ColumnExpression:
|
|
148
148
|
"""SQL <> ANY(...) clause."""
|
|
149
149
|
converted_values = [exp.convert(v) for v in values]
|
|
150
|
-
return ColumnExpression(exp.NEQ(this=self.
|
|
150
|
+
return ColumnExpression(exp.NEQ(this=self._expression, expression=exp.Any(expressions=converted_values)))
|
|
151
151
|
|
|
152
152
|
# SQL Functions
|
|
153
153
|
def lower(self) -> "FunctionColumn":
|
|
154
154
|
"""SQL LOWER() function."""
|
|
155
|
-
return FunctionColumn(exp.Lower(this=self.
|
|
155
|
+
return FunctionColumn(exp.Lower(this=self._expression))
|
|
156
156
|
|
|
157
157
|
def upper(self) -> "FunctionColumn":
|
|
158
158
|
"""SQL UPPER() function."""
|
|
159
|
-
return FunctionColumn(exp.Upper(this=self.
|
|
159
|
+
return FunctionColumn(exp.Upper(this=self._expression))
|
|
160
160
|
|
|
161
161
|
def length(self) -> "FunctionColumn":
|
|
162
162
|
"""SQL LENGTH() function."""
|
|
163
|
-
return FunctionColumn(exp.Length(this=self.
|
|
163
|
+
return FunctionColumn(exp.Length(this=self._expression))
|
|
164
164
|
|
|
165
165
|
def trim(self) -> "FunctionColumn":
|
|
166
166
|
"""SQL TRIM() function."""
|
|
167
|
-
return FunctionColumn(exp.Trim(this=self.
|
|
167
|
+
return FunctionColumn(exp.Trim(this=self._expression))
|
|
168
168
|
|
|
169
169
|
def abs(self) -> "FunctionColumn":
|
|
170
170
|
"""SQL ABS() function."""
|
|
171
|
-
return FunctionColumn(exp.Abs(this=self.
|
|
171
|
+
return FunctionColumn(exp.Abs(this=self._expression))
|
|
172
172
|
|
|
173
173
|
def round(self, decimals: int = 0) -> "FunctionColumn":
|
|
174
174
|
"""SQL ROUND() function."""
|
|
175
175
|
if decimals == 0:
|
|
176
|
-
return FunctionColumn(exp.Round(this=self.
|
|
177
|
-
return FunctionColumn(exp.Round(this=self.
|
|
176
|
+
return FunctionColumn(exp.Round(this=self._expression))
|
|
177
|
+
return FunctionColumn(exp.Round(this=self._expression, expression=exp.Literal.number(decimals)))
|
|
178
178
|
|
|
179
179
|
def floor(self) -> "FunctionColumn":
|
|
180
180
|
"""SQL FLOOR() function."""
|
|
181
|
-
return FunctionColumn(exp.Floor(this=self.
|
|
181
|
+
return FunctionColumn(exp.Floor(this=self._expression))
|
|
182
182
|
|
|
183
183
|
def ceil(self) -> "FunctionColumn":
|
|
184
184
|
"""SQL CEIL() function."""
|
|
185
|
-
return FunctionColumn(exp.Ceil(this=self.
|
|
185
|
+
return FunctionColumn(exp.Ceil(this=self._expression))
|
|
186
186
|
|
|
187
187
|
def substring(self, start: int, length: Optional[int] = None) -> "FunctionColumn":
|
|
188
188
|
"""SQL SUBSTRING() function."""
|
|
189
189
|
args = [exp.Literal.number(start)]
|
|
190
190
|
if length is not None:
|
|
191
191
|
args.append(exp.Literal.number(length))
|
|
192
|
-
return FunctionColumn(exp.Substring(this=self.
|
|
192
|
+
return FunctionColumn(exp.Substring(this=self._expression, expressions=args))
|
|
193
193
|
|
|
194
194
|
def coalesce(self, *values: Any) -> "FunctionColumn":
|
|
195
195
|
"""SQL COALESCE() function."""
|
|
196
|
-
expressions = [self.
|
|
196
|
+
expressions = [self._expression] + [exp.convert(v) for v in values]
|
|
197
197
|
return FunctionColumn(exp.Coalesce(expressions=expressions))
|
|
198
198
|
|
|
199
199
|
def cast(self, data_type: str) -> "FunctionColumn":
|
|
200
200
|
"""SQL CAST() function."""
|
|
201
|
-
return FunctionColumn(exp.Cast(this=self.
|
|
201
|
+
return FunctionColumn(exp.Cast(this=self._expression, to=exp.DataType.build(data_type)))
|
|
202
|
+
|
|
203
|
+
# Aggregate functions
|
|
204
|
+
def count(self) -> "FunctionColumn":
|
|
205
|
+
"""SQL COUNT() function."""
|
|
206
|
+
return FunctionColumn(exp.Count(this=self._expression))
|
|
207
|
+
|
|
208
|
+
def sum(self) -> "FunctionColumn":
|
|
209
|
+
"""SQL SUM() function."""
|
|
210
|
+
return FunctionColumn(exp.Sum(this=self._expression))
|
|
211
|
+
|
|
212
|
+
def avg(self) -> "FunctionColumn":
|
|
213
|
+
"""SQL AVG() function."""
|
|
214
|
+
return FunctionColumn(exp.Avg(this=self._expression))
|
|
215
|
+
|
|
216
|
+
def min(self) -> "FunctionColumn":
|
|
217
|
+
"""SQL MIN() function."""
|
|
218
|
+
return FunctionColumn(exp.Min(this=self._expression))
|
|
219
|
+
|
|
220
|
+
def max(self) -> "FunctionColumn":
|
|
221
|
+
"""SQL MAX() function."""
|
|
222
|
+
return FunctionColumn(exp.Max(this=self._expression))
|
|
223
|
+
|
|
224
|
+
def count_distinct(self) -> "FunctionColumn":
|
|
225
|
+
"""SQL COUNT(DISTINCT column) function."""
|
|
226
|
+
return FunctionColumn(exp.Count(this=exp.Distinct(expressions=[self._expression])))
|
|
227
|
+
|
|
228
|
+
@staticmethod
|
|
229
|
+
def count_all() -> "FunctionColumn":
|
|
230
|
+
"""SQL COUNT(*) function."""
|
|
231
|
+
return FunctionColumn(exp.Count(this=exp.Star()))
|
|
202
232
|
|
|
203
233
|
def alias(self, alias_name: str) -> exp.Expression:
|
|
204
234
|
"""Create an aliased column expression."""
|
|
205
|
-
return exp.Alias(this=self.
|
|
235
|
+
return exp.Alias(this=self._expression, alias=alias_name)
|
|
206
236
|
|
|
207
237
|
def __repr__(self) -> str:
|
|
208
238
|
if self.table:
|
|
@@ -218,25 +248,25 @@ class FunctionColumn:
|
|
|
218
248
|
"""Represents the result of a SQL function call on a column."""
|
|
219
249
|
|
|
220
250
|
def __init__(self, expression: exp.Expression) -> None:
|
|
221
|
-
self.
|
|
251
|
+
self._expression = expression
|
|
222
252
|
|
|
223
253
|
def __eq__(self, other: object) -> ColumnExpression: # type: ignore[override]
|
|
224
|
-
return ColumnExpression(exp.EQ(this=self.
|
|
254
|
+
return ColumnExpression(exp.EQ(this=self._expression, expression=exp.convert(other)))
|
|
225
255
|
|
|
226
256
|
def __ne__(self, other: object) -> ColumnExpression: # type: ignore[override]
|
|
227
|
-
return ColumnExpression(exp.NEQ(this=self.
|
|
257
|
+
return ColumnExpression(exp.NEQ(this=self._expression, expression=exp.convert(other)))
|
|
228
258
|
|
|
229
259
|
def like(self, pattern: str) -> ColumnExpression:
|
|
230
|
-
return ColumnExpression(exp.Like(this=self.
|
|
260
|
+
return ColumnExpression(exp.Like(this=self._expression, expression=exp.convert(pattern)))
|
|
231
261
|
|
|
232
262
|
def ilike(self, pattern: str) -> ColumnExpression:
|
|
233
263
|
"""Case-insensitive LIKE."""
|
|
234
|
-
return ColumnExpression(exp.ILike(this=self.
|
|
264
|
+
return ColumnExpression(exp.ILike(this=self._expression, expression=exp.convert(pattern)))
|
|
235
265
|
|
|
236
266
|
def in_(self, values: Iterable[Any]) -> ColumnExpression:
|
|
237
267
|
"""SQL IN clause."""
|
|
238
268
|
converted_values = [exp.convert(v) for v in values]
|
|
239
|
-
return ColumnExpression(exp.In(this=self.
|
|
269
|
+
return ColumnExpression(exp.In(this=self._expression, expressions=converted_values))
|
|
240
270
|
|
|
241
271
|
def not_in_(self, values: Iterable[Any]) -> ColumnExpression:
|
|
242
272
|
"""SQL NOT IN clause."""
|
|
@@ -252,32 +282,32 @@ class FunctionColumn:
|
|
|
252
282
|
|
|
253
283
|
def between(self, start: Any, end: Any) -> ColumnExpression:
|
|
254
284
|
"""SQL BETWEEN clause."""
|
|
255
|
-
return ColumnExpression(exp.Between(this=self.
|
|
285
|
+
return ColumnExpression(exp.Between(this=self._expression, low=exp.convert(start), high=exp.convert(end)))
|
|
256
286
|
|
|
257
287
|
def is_null(self) -> ColumnExpression:
|
|
258
288
|
"""SQL IS NULL."""
|
|
259
|
-
return ColumnExpression(exp.Is(this=self.
|
|
289
|
+
return ColumnExpression(exp.Is(this=self._expression, expression=exp.Null()))
|
|
260
290
|
|
|
261
291
|
def is_not_null(self) -> ColumnExpression:
|
|
262
292
|
"""SQL IS NOT NULL."""
|
|
263
|
-
return ColumnExpression(exp.Not(this=exp.Is(this=self.
|
|
293
|
+
return ColumnExpression(exp.Not(this=exp.Is(this=self._expression, expression=exp.Null())))
|
|
264
294
|
|
|
265
295
|
def any_(self, values: Iterable[Any]) -> ColumnExpression:
|
|
266
296
|
"""SQL = ANY(...) clause."""
|
|
267
297
|
converted_values = [exp.convert(v) for v in values]
|
|
268
|
-
return ColumnExpression(exp.EQ(this=self.
|
|
298
|
+
return ColumnExpression(exp.EQ(this=self._expression, expression=exp.Any(expressions=converted_values)))
|
|
269
299
|
|
|
270
300
|
def not_any_(self, values: Iterable[Any]) -> ColumnExpression:
|
|
271
301
|
"""SQL <> ANY(...) clause."""
|
|
272
302
|
converted_values = [exp.convert(v) for v in values]
|
|
273
|
-
return ColumnExpression(exp.NEQ(this=self.
|
|
303
|
+
return ColumnExpression(exp.NEQ(this=self._expression, expression=exp.Any(expressions=converted_values)))
|
|
274
304
|
|
|
275
305
|
def alias(self, alias_name: str) -> exp.Expression:
|
|
276
306
|
"""Create an aliased function expression."""
|
|
277
|
-
return exp.Alias(this=self.
|
|
307
|
+
return exp.Alias(this=self._expression, alias=alias_name)
|
|
278
308
|
|
|
279
309
|
# Add other operators as needed...
|
|
280
310
|
|
|
281
311
|
def __hash__(self) -> int:
|
|
282
312
|
"""Hash based on the SQL expression."""
|
|
283
|
-
return hash(self.
|
|
313
|
+
return hash(self._expression.sql() if has_sql_method(self._expression) else str(self._expression))
|
|
@@ -7,12 +7,12 @@ from sqlglot import exp
|
|
|
7
7
|
from sqlglot.dialects.dialect import DialectType
|
|
8
8
|
from typing_extensions import Self
|
|
9
9
|
|
|
10
|
+
from sqlspec.statement.builder._base import QueryBuilder, SafeQuery
|
|
10
11
|
from sqlspec.statement.builder._ddl_utils import build_column_expression, build_constraint_expression
|
|
11
|
-
from sqlspec.statement.builder.base import QueryBuilder, SafeQuery
|
|
12
12
|
from sqlspec.statement.result import SQLResult
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
-
from sqlspec.statement.builder.
|
|
15
|
+
from sqlspec.statement.builder._column import ColumnExpression
|
|
16
16
|
from sqlspec.statement.sql import SQL, SQLConfig
|
|
17
17
|
|
|
18
18
|
__all__ = (
|
|
@@ -792,7 +792,7 @@ class CreateTableAsSelect(DDLBuilder):
|
|
|
792
792
|
|
|
793
793
|
select_expr = None
|
|
794
794
|
select_params = None
|
|
795
|
-
from sqlspec.statement.builder.
|
|
795
|
+
from sqlspec.statement.builder._select import Select
|
|
796
796
|
from sqlspec.statement.sql import SQL
|
|
797
797
|
|
|
798
798
|
if isinstance(self._select_query, SQL):
|
|
@@ -909,7 +909,7 @@ class CreateMaterializedView(DDLBuilder):
|
|
|
909
909
|
|
|
910
910
|
select_expr = None
|
|
911
911
|
select_params = None
|
|
912
|
-
from sqlspec.statement.builder.
|
|
912
|
+
from sqlspec.statement.builder._select import Select
|
|
913
913
|
from sqlspec.statement.sql import SQL
|
|
914
914
|
|
|
915
915
|
if isinstance(self._select_query, SQL):
|
|
@@ -1008,7 +1008,7 @@ class CreateView(DDLBuilder):
|
|
|
1008
1008
|
|
|
1009
1009
|
select_expr = None
|
|
1010
1010
|
select_params = None
|
|
1011
|
-
from sqlspec.statement.builder.
|
|
1011
|
+
from sqlspec.statement.builder._select import Select
|
|
1012
1012
|
from sqlspec.statement.sql import SQL
|
|
1013
1013
|
|
|
1014
1014
|
if isinstance(self._select_query, SQL):
|
|
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Optional
|
|
|
5
5
|
from sqlglot import exp
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
|
-
from sqlspec.statement.builder.
|
|
8
|
+
from sqlspec.statement.builder._ddl import ColumnDefinition, ConstraintDefinition
|
|
9
9
|
|
|
10
10
|
__all__ = ("build_column_expression", "build_constraint_expression")
|
|
11
11
|
|
|
@@ -9,7 +9,7 @@ from typing import Any, Optional
|
|
|
9
9
|
|
|
10
10
|
from sqlglot import exp
|
|
11
11
|
|
|
12
|
-
from sqlspec.statement.builder.
|
|
12
|
+
from sqlspec.statement.builder._base import QueryBuilder, SafeQuery
|
|
13
13
|
from sqlspec.statement.builder.mixins import DeleteFromClauseMixin, ReturningClauseMixin, WhereClauseMixin
|
|
14
14
|
from sqlspec.statement.result import SQLResult
|
|
15
15
|
from sqlspec.typing import RowT
|
|
@@ -11,7 +11,7 @@ from sqlglot import exp
|
|
|
11
11
|
from typing_extensions import Self
|
|
12
12
|
|
|
13
13
|
from sqlspec.exceptions import SQLBuilderError
|
|
14
|
-
from sqlspec.statement.builder.
|
|
14
|
+
from sqlspec.statement.builder._base import QueryBuilder
|
|
15
15
|
from sqlspec.statement.builder.mixins import (
|
|
16
16
|
InsertFromSelectMixin,
|
|
17
17
|
InsertIntoClauseMixin,
|
|
@@ -8,7 +8,7 @@ from dataclasses import dataclass
|
|
|
8
8
|
|
|
9
9
|
from sqlglot import exp
|
|
10
10
|
|
|
11
|
-
from sqlspec.statement.builder.
|
|
11
|
+
from sqlspec.statement.builder._base import QueryBuilder
|
|
12
12
|
from sqlspec.statement.builder.mixins import (
|
|
13
13
|
MergeIntoClauseMixin,
|
|
14
14
|
MergeMatchedClauseMixin,
|
|
@@ -9,6 +9,8 @@ from typing import Any, Optional, Union, cast
|
|
|
9
9
|
|
|
10
10
|
from sqlglot import exp, maybe_parse, parse_one
|
|
11
11
|
|
|
12
|
+
from sqlspec.utils.type_guards import has_expression_attr, has_parameter_builder
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
def parse_column_expression(column_input: Union[str, exp.Expression, Any]) -> exp.Expression:
|
|
14
16
|
"""Parse a column input that might be a complex expression.
|
|
@@ -31,8 +33,8 @@ def parse_column_expression(column_input: Union[str, exp.Expression, Any]) -> ex
|
|
|
31
33
|
return column_input
|
|
32
34
|
|
|
33
35
|
# Handle our custom Column objects
|
|
34
|
-
if
|
|
35
|
-
attr_value = getattr(column_input, "
|
|
36
|
+
if has_expression_attr(column_input):
|
|
37
|
+
attr_value = getattr(column_input, "_expression", None)
|
|
36
38
|
if isinstance(attr_value, exp.Expression):
|
|
37
39
|
return attr_value
|
|
38
40
|
|
|
@@ -109,7 +111,7 @@ def parse_condition_expression(
|
|
|
109
111
|
if value is None:
|
|
110
112
|
return exp.Is(this=column_expr, expression=exp.null())
|
|
111
113
|
# Use builder's parameter system if available
|
|
112
|
-
if builder and
|
|
114
|
+
if builder and has_parameter_builder(builder):
|
|
113
115
|
_, param_name = builder.add_parameter(value)
|
|
114
116
|
return exp.EQ(this=column_expr, expression=exp.Placeholder(this=param_name))
|
|
115
117
|
if isinstance(value, str):
|
|
@@ -11,23 +11,18 @@ from typing import Any, Optional, Union, cast
|
|
|
11
11
|
from sqlglot import exp
|
|
12
12
|
from typing_extensions import Self
|
|
13
13
|
|
|
14
|
-
from sqlspec.statement.builder.
|
|
14
|
+
from sqlspec.statement.builder._base import QueryBuilder, SafeQuery
|
|
15
15
|
from sqlspec.statement.builder.mixins import (
|
|
16
|
-
AggregateFunctionsMixin,
|
|
17
|
-
CaseBuilderMixin,
|
|
18
16
|
CommonTableExpressionMixin,
|
|
19
|
-
FromClauseMixin,
|
|
20
|
-
GroupByClauseMixin,
|
|
21
17
|
HavingClauseMixin,
|
|
22
18
|
JoinClauseMixin,
|
|
23
19
|
LimitOffsetClauseMixin,
|
|
24
20
|
OrderByClauseMixin,
|
|
25
21
|
PivotClauseMixin,
|
|
26
|
-
|
|
22
|
+
SelectClauseMixin,
|
|
27
23
|
SetOperationMixin,
|
|
28
24
|
UnpivotClauseMixin,
|
|
29
25
|
WhereClauseMixin,
|
|
30
|
-
WindowFunctionsMixin,
|
|
31
26
|
)
|
|
32
27
|
from sqlspec.statement.result import SQLResult
|
|
33
28
|
from sqlspec.typing import RowT
|
|
@@ -35,22 +30,21 @@ from sqlspec.typing import RowT
|
|
|
35
30
|
__all__ = ("Select",)
|
|
36
31
|
|
|
37
32
|
|
|
33
|
+
# This pattern is formatted with the table name and compiled for each hint.
|
|
34
|
+
TABLE_HINT_PATTERN = r"\b{}\b(\s+AS\s+\w+)?"
|
|
35
|
+
|
|
36
|
+
|
|
38
37
|
@dataclass
|
|
39
38
|
class Select(
|
|
40
39
|
QueryBuilder[RowT],
|
|
41
40
|
WhereClauseMixin,
|
|
42
41
|
OrderByClauseMixin,
|
|
43
42
|
LimitOffsetClauseMixin,
|
|
44
|
-
|
|
43
|
+
SelectClauseMixin,
|
|
45
44
|
JoinClauseMixin,
|
|
46
|
-
FromClauseMixin,
|
|
47
|
-
GroupByClauseMixin,
|
|
48
45
|
HavingClauseMixin,
|
|
49
46
|
SetOperationMixin,
|
|
50
47
|
CommonTableExpressionMixin,
|
|
51
|
-
AggregateFunctionsMixin,
|
|
52
|
-
WindowFunctionsMixin,
|
|
53
|
-
CaseBuilderMixin,
|
|
54
48
|
PivotClauseMixin,
|
|
55
49
|
UnpivotClauseMixin,
|
|
56
50
|
):
|
|
@@ -95,14 +89,14 @@ class Select(
|
|
|
95
89
|
"""
|
|
96
90
|
super().__init__(**kwargs)
|
|
97
91
|
|
|
98
|
-
#
|
|
92
|
+
# Manually initialize dataclass fields here because a custom __init__ is defined.
|
|
93
|
+
# This is necessary to support the `Select("col1", "col2")` shorthand initialization.
|
|
99
94
|
self._with_parts = {}
|
|
100
95
|
self._expression = None
|
|
101
96
|
self._schema = None
|
|
102
97
|
self._hints = []
|
|
103
98
|
|
|
104
|
-
|
|
105
|
-
self._create_base_expression()
|
|
99
|
+
self._create_base_expression()
|
|
106
100
|
|
|
107
101
|
# Add columns if provided - just a shorthand for .select()
|
|
108
102
|
if columns:
|
|
@@ -174,48 +168,52 @@ class Select(
|
|
|
174
168
|
"""
|
|
175
169
|
safe_query = super().build()
|
|
176
170
|
|
|
177
|
-
if
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
171
|
+
if not self._hints:
|
|
172
|
+
return safe_query
|
|
173
|
+
|
|
174
|
+
modified_expr = self._expression.copy() if self._expression else self._create_base_expression()
|
|
175
|
+
|
|
176
|
+
if isinstance(modified_expr, exp.Select):
|
|
177
|
+
statement_hints = [h["hint"] for h in self._hints if h.get("location") == "statement"]
|
|
178
|
+
if statement_hints:
|
|
179
|
+
# Parse each hint and create proper hint expressions
|
|
180
|
+
hint_expressions = []
|
|
181
|
+
|
|
182
|
+
def parse_hint(hint: Any) -> exp.Expression:
|
|
183
|
+
"""Parse a single hint with error handling."""
|
|
184
|
+
try:
|
|
185
|
+
# Try to parse hint as an expression (e.g., "INDEX(users idx_name)")
|
|
186
|
+
hint_str = str(hint) # Ensure hint is a string
|
|
187
|
+
hint_expr: Optional[exp.Expression] = exp.maybe_parse(hint_str, dialect=self.dialect_name)
|
|
188
|
+
if hint_expr:
|
|
189
|
+
return hint_expr
|
|
190
|
+
return exp.Anonymous(this=hint_str)
|
|
191
|
+
except Exception:
|
|
192
|
+
return exp.Anonymous(this=str(hint))
|
|
193
|
+
|
|
194
|
+
hint_expressions = [parse_hint(hint) for hint in statement_hints]
|
|
195
|
+
|
|
196
|
+
if hint_expressions:
|
|
197
|
+
hint_node = exp.Hint(expressions=hint_expressions)
|
|
198
|
+
modified_expr.set("hint", hint_node)
|
|
199
|
+
|
|
200
|
+
# For table-level hints, we'll fall back to comment injection in SQL
|
|
201
|
+
# since SQLGlot doesn't have a standard way to attach hints to individual tables
|
|
202
|
+
modified_sql = modified_expr.sql(dialect=self.dialect_name, pretty=True)
|
|
203
|
+
|
|
204
|
+
table_hints = [h for h in self._hints if h.get("location") == "table" and h.get("table")]
|
|
205
|
+
if table_hints:
|
|
206
|
+
for th in table_hints:
|
|
207
|
+
table = str(th["table"])
|
|
208
|
+
hint = th["hint"]
|
|
209
|
+
# More precise regex that captures the table and optional alias
|
|
210
|
+
pattern = TABLE_HINT_PATTERN.format(re.escape(table))
|
|
211
|
+
compiled_pattern = re.compile(pattern, re.IGNORECASE)
|
|
212
|
+
|
|
213
|
+
def replacement_func(match: re.Match[str]) -> str:
|
|
214
|
+
alias_part = match.group(1) or ""
|
|
215
|
+
return f"/*+ {hint} */ {table}{alias_part}" # noqa: B023
|
|
216
|
+
|
|
217
|
+
modified_sql = compiled_pattern.sub(replacement_func, modified_sql, count=1)
|
|
218
|
+
|
|
219
|
+
return SafeQuery(sql=modified_sql, parameters=safe_query.parameters, dialect=safe_query.dialect)
|
|
@@ -11,7 +11,7 @@ from sqlglot import exp
|
|
|
11
11
|
from typing_extensions import Self
|
|
12
12
|
|
|
13
13
|
from sqlspec.exceptions import SQLBuilderError
|
|
14
|
-
from sqlspec.statement.builder.
|
|
14
|
+
from sqlspec.statement.builder._base import QueryBuilder, SafeQuery
|
|
15
15
|
from sqlspec.statement.builder.mixins import (
|
|
16
16
|
ReturningClauseMixin,
|
|
17
17
|
UpdateFromClauseMixin,
|
|
@@ -23,7 +23,7 @@ from sqlspec.statement.result import SQLResult
|
|
|
23
23
|
from sqlspec.typing import RowT
|
|
24
24
|
|
|
25
25
|
if TYPE_CHECKING:
|
|
26
|
-
from sqlspec.statement.builder.
|
|
26
|
+
from sqlspec.statement.builder._select import Select
|
|
27
27
|
|
|
28
28
|
__all__ = ("Update",)
|
|
29
29
|
|