sqlspec 0.24.1__py3-none-any.whl → 0.26.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/_serialization.py +223 -21
- sqlspec/_sql.py +20 -62
- sqlspec/_typing.py +11 -0
- sqlspec/adapters/adbc/config.py +8 -1
- sqlspec/adapters/adbc/data_dictionary.py +290 -0
- sqlspec/adapters/adbc/driver.py +129 -20
- sqlspec/adapters/adbc/type_converter.py +159 -0
- sqlspec/adapters/aiosqlite/config.py +3 -0
- sqlspec/adapters/aiosqlite/data_dictionary.py +117 -0
- sqlspec/adapters/aiosqlite/driver.py +17 -3
- sqlspec/adapters/asyncmy/_types.py +1 -1
- sqlspec/adapters/asyncmy/config.py +11 -8
- sqlspec/adapters/asyncmy/data_dictionary.py +122 -0
- sqlspec/adapters/asyncmy/driver.py +31 -7
- sqlspec/adapters/asyncpg/config.py +3 -0
- sqlspec/adapters/asyncpg/data_dictionary.py +134 -0
- sqlspec/adapters/asyncpg/driver.py +19 -4
- sqlspec/adapters/bigquery/config.py +3 -0
- sqlspec/adapters/bigquery/data_dictionary.py +109 -0
- sqlspec/adapters/bigquery/driver.py +21 -3
- sqlspec/adapters/bigquery/type_converter.py +93 -0
- sqlspec/adapters/duckdb/_types.py +1 -1
- sqlspec/adapters/duckdb/config.py +2 -0
- sqlspec/adapters/duckdb/data_dictionary.py +124 -0
- sqlspec/adapters/duckdb/driver.py +32 -5
- sqlspec/adapters/duckdb/pool.py +1 -1
- sqlspec/adapters/duckdb/type_converter.py +103 -0
- sqlspec/adapters/oracledb/config.py +6 -0
- sqlspec/adapters/oracledb/data_dictionary.py +442 -0
- sqlspec/adapters/oracledb/driver.py +68 -9
- sqlspec/adapters/oracledb/migrations.py +51 -67
- sqlspec/adapters/oracledb/type_converter.py +132 -0
- sqlspec/adapters/psqlpy/config.py +3 -0
- sqlspec/adapters/psqlpy/data_dictionary.py +133 -0
- sqlspec/adapters/psqlpy/driver.py +23 -179
- sqlspec/adapters/psqlpy/type_converter.py +73 -0
- sqlspec/adapters/psycopg/config.py +8 -4
- sqlspec/adapters/psycopg/data_dictionary.py +257 -0
- sqlspec/adapters/psycopg/driver.py +40 -5
- sqlspec/adapters/sqlite/config.py +3 -0
- sqlspec/adapters/sqlite/data_dictionary.py +117 -0
- sqlspec/adapters/sqlite/driver.py +18 -3
- sqlspec/adapters/sqlite/pool.py +13 -4
- sqlspec/base.py +3 -4
- sqlspec/builder/_base.py +130 -48
- sqlspec/builder/_column.py +66 -24
- sqlspec/builder/_ddl.py +91 -41
- sqlspec/builder/_insert.py +40 -58
- sqlspec/builder/_parsing_utils.py +127 -12
- sqlspec/builder/_select.py +147 -2
- sqlspec/builder/_update.py +1 -1
- sqlspec/builder/mixins/_cte_and_set_ops.py +31 -23
- sqlspec/builder/mixins/_delete_operations.py +12 -7
- sqlspec/builder/mixins/_insert_operations.py +50 -36
- sqlspec/builder/mixins/_join_operations.py +15 -30
- sqlspec/builder/mixins/_merge_operations.py +210 -78
- sqlspec/builder/mixins/_order_limit_operations.py +4 -10
- sqlspec/builder/mixins/_pivot_operations.py +1 -0
- sqlspec/builder/mixins/_select_operations.py +44 -22
- sqlspec/builder/mixins/_update_operations.py +30 -37
- sqlspec/builder/mixins/_where_clause.py +52 -70
- sqlspec/cli.py +246 -140
- sqlspec/config.py +33 -19
- sqlspec/core/__init__.py +3 -2
- sqlspec/core/cache.py +298 -352
- sqlspec/core/compiler.py +61 -4
- sqlspec/core/filters.py +246 -213
- sqlspec/core/hashing.py +9 -11
- sqlspec/core/parameters.py +27 -10
- sqlspec/core/statement.py +72 -12
- sqlspec/core/type_conversion.py +234 -0
- sqlspec/driver/__init__.py +6 -3
- sqlspec/driver/_async.py +108 -5
- sqlspec/driver/_common.py +186 -17
- sqlspec/driver/_sync.py +108 -5
- sqlspec/driver/mixins/_result_tools.py +60 -7
- sqlspec/exceptions.py +5 -0
- sqlspec/loader.py +8 -9
- sqlspec/migrations/__init__.py +4 -3
- sqlspec/migrations/base.py +153 -14
- sqlspec/migrations/commands.py +34 -96
- sqlspec/migrations/context.py +145 -0
- sqlspec/migrations/loaders.py +25 -8
- sqlspec/migrations/runner.py +352 -82
- sqlspec/storage/backends/fsspec.py +1 -0
- sqlspec/typing.py +4 -0
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/serializers.py +50 -2
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/METADATA +1 -1
- sqlspec-0.26.0.dist-info/RECORD +157 -0
- sqlspec-0.24.1.dist-info/RECORD +0 -139
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/builder/_column.py
CHANGED
|
@@ -5,6 +5,7 @@ SQL conditions with parameter binding.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from collections.abc import Iterable
|
|
8
|
+
from datetime import date, datetime
|
|
8
9
|
from typing import Any, Optional, cast
|
|
9
10
|
|
|
10
11
|
from sqlglot import exp
|
|
@@ -14,6 +15,24 @@ from sqlspec.utils.type_guards import has_sql_method
|
|
|
14
15
|
__all__ = ("Column", "ColumnExpression", "FunctionColumn")
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
def _convert_value(value: Any) -> exp.Expression:
|
|
19
|
+
"""Convert a Python value to a SQLGlot expression.
|
|
20
|
+
|
|
21
|
+
Special handling for datetime objects to prevent SQLGlot from
|
|
22
|
+
converting them to TIME_STR_TO_TIME function calls. Datetime
|
|
23
|
+
objects should be passed as parameters, not converted to SQL functions.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
value: The value to convert
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
A SQLGlot expression representing the value
|
|
30
|
+
"""
|
|
31
|
+
if isinstance(value, (datetime, date)):
|
|
32
|
+
return exp.Literal(this=value, is_string=False)
|
|
33
|
+
return exp.convert(value)
|
|
34
|
+
|
|
35
|
+
|
|
17
36
|
class ColumnExpression:
|
|
18
37
|
"""Base class for column expressions that can be combined with operators."""
|
|
19
38
|
|
|
@@ -67,33 +86,37 @@ class Column:
|
|
|
67
86
|
else:
|
|
68
87
|
self._expression = exp.Column(this=exp.Identifier(this=name))
|
|
69
88
|
|
|
89
|
+
def _convert_value(self, value: Any) -> exp.Expression:
|
|
90
|
+
"""Convert a Python value to a SQLGlot expression."""
|
|
91
|
+
return _convert_value(value)
|
|
92
|
+
|
|
70
93
|
def __eq__(self, other: object) -> ColumnExpression: # type: ignore[override]
|
|
71
94
|
"""Equal to (==)."""
|
|
72
95
|
if other is None:
|
|
73
96
|
return ColumnExpression(exp.Is(this=self._expression, expression=exp.Null()))
|
|
74
|
-
return ColumnExpression(exp.EQ(this=self._expression, expression=
|
|
97
|
+
return ColumnExpression(exp.EQ(this=self._expression, expression=self._convert_value(other)))
|
|
75
98
|
|
|
76
99
|
def __ne__(self, other: object) -> ColumnExpression: # type: ignore[override]
|
|
77
100
|
"""Not equal to (!=)."""
|
|
78
101
|
if other is None:
|
|
79
102
|
return ColumnExpression(exp.Not(this=exp.Is(this=self._expression, expression=exp.Null())))
|
|
80
|
-
return ColumnExpression(exp.NEQ(this=self._expression, expression=
|
|
103
|
+
return ColumnExpression(exp.NEQ(this=self._expression, expression=self._convert_value(other)))
|
|
81
104
|
|
|
82
105
|
def __gt__(self, other: Any) -> ColumnExpression:
|
|
83
106
|
"""Greater than (>)."""
|
|
84
|
-
return ColumnExpression(exp.GT(this=self._expression, expression=
|
|
107
|
+
return ColumnExpression(exp.GT(this=self._expression, expression=self._convert_value(other)))
|
|
85
108
|
|
|
86
109
|
def __ge__(self, other: Any) -> ColumnExpression:
|
|
87
110
|
"""Greater than or equal (>=)."""
|
|
88
|
-
return ColumnExpression(exp.GTE(this=self._expression, expression=
|
|
111
|
+
return ColumnExpression(exp.GTE(this=self._expression, expression=self._convert_value(other)))
|
|
89
112
|
|
|
90
113
|
def __lt__(self, other: Any) -> ColumnExpression:
|
|
91
114
|
"""Less than (<)."""
|
|
92
|
-
return ColumnExpression(exp.LT(this=self._expression, expression=
|
|
115
|
+
return ColumnExpression(exp.LT(this=self._expression, expression=self._convert_value(other)))
|
|
93
116
|
|
|
94
117
|
def __le__(self, other: Any) -> ColumnExpression:
|
|
95
118
|
"""Less than or equal (<=)."""
|
|
96
|
-
return ColumnExpression(exp.LTE(this=self._expression, expression=
|
|
119
|
+
return ColumnExpression(exp.LTE(this=self._expression, expression=self._convert_value(other)))
|
|
97
120
|
|
|
98
121
|
def __invert__(self) -> ColumnExpression:
|
|
99
122
|
"""Apply NOT operator (~)."""
|
|
@@ -102,18 +125,20 @@ class Column:
|
|
|
102
125
|
def like(self, pattern: str, escape: Optional[str] = None) -> ColumnExpression:
|
|
103
126
|
"""SQL LIKE pattern matching."""
|
|
104
127
|
if escape:
|
|
105
|
-
like_expr = exp.Like(
|
|
128
|
+
like_expr = exp.Like(
|
|
129
|
+
this=self._expression, expression=self._convert_value(pattern), escape=self._convert_value(escape)
|
|
130
|
+
)
|
|
106
131
|
else:
|
|
107
|
-
like_expr = exp.Like(this=self._expression, expression=
|
|
132
|
+
like_expr = exp.Like(this=self._expression, expression=self._convert_value(pattern))
|
|
108
133
|
return ColumnExpression(like_expr)
|
|
109
134
|
|
|
110
135
|
def ilike(self, pattern: str) -> ColumnExpression:
|
|
111
136
|
"""Case-insensitive LIKE."""
|
|
112
|
-
return ColumnExpression(exp.ILike(this=self._expression, expression=
|
|
137
|
+
return ColumnExpression(exp.ILike(this=self._expression, expression=self._convert_value(pattern)))
|
|
113
138
|
|
|
114
139
|
def in_(self, values: Iterable[Any]) -> ColumnExpression:
|
|
115
140
|
"""SQL IN clause."""
|
|
116
|
-
converted_values = [
|
|
141
|
+
converted_values = [self._convert_value(v) for v in values]
|
|
117
142
|
return ColumnExpression(exp.In(this=self._expression, expressions=converted_values))
|
|
118
143
|
|
|
119
144
|
def not_in(self, values: Iterable[Any]) -> ColumnExpression:
|
|
@@ -122,7 +147,9 @@ class Column:
|
|
|
122
147
|
|
|
123
148
|
def between(self, start: Any, end: Any) -> ColumnExpression:
|
|
124
149
|
"""SQL BETWEEN clause."""
|
|
125
|
-
return ColumnExpression(
|
|
150
|
+
return ColumnExpression(
|
|
151
|
+
exp.Between(this=self._expression, low=self._convert_value(start), high=self._convert_value(end))
|
|
152
|
+
)
|
|
126
153
|
|
|
127
154
|
def is_null(self) -> ColumnExpression:
|
|
128
155
|
"""SQL IS NULL."""
|
|
@@ -142,12 +169,12 @@ class Column:
|
|
|
142
169
|
|
|
143
170
|
def any_(self, values: Iterable[Any]) -> ColumnExpression:
|
|
144
171
|
"""SQL = ANY(...) clause."""
|
|
145
|
-
converted_values = [
|
|
172
|
+
converted_values = [self._convert_value(v) for v in values]
|
|
146
173
|
return ColumnExpression(exp.EQ(this=self._expression, expression=exp.Any(expressions=converted_values)))
|
|
147
174
|
|
|
148
175
|
def not_any_(self, values: Iterable[Any]) -> ColumnExpression:
|
|
149
176
|
"""SQL <> ANY(...) clause."""
|
|
150
|
-
converted_values = [
|
|
177
|
+
converted_values = [self._convert_value(v) for v in values]
|
|
151
178
|
return ColumnExpression(exp.NEQ(this=self._expression, expression=exp.Any(expressions=converted_values)))
|
|
152
179
|
|
|
153
180
|
def lower(self) -> "FunctionColumn":
|
|
@@ -186,14 +213,14 @@ class Column:
|
|
|
186
213
|
|
|
187
214
|
def substring(self, start: int, length: Optional[int] = None) -> "FunctionColumn":
|
|
188
215
|
"""SQL SUBSTRING() function."""
|
|
189
|
-
args = [
|
|
216
|
+
args = [self._convert_value(start)]
|
|
190
217
|
if length is not None:
|
|
191
|
-
args.append(
|
|
218
|
+
args.append(self._convert_value(length))
|
|
192
219
|
return FunctionColumn(exp.Substring(this=self._expression, expressions=args))
|
|
193
220
|
|
|
194
221
|
def coalesce(self, *values: Any) -> "FunctionColumn":
|
|
195
222
|
"""SQL COALESCE() function."""
|
|
196
|
-
expressions = [self._expression] + [
|
|
223
|
+
expressions = [self._expression] + [self._convert_value(v) for v in values]
|
|
197
224
|
return FunctionColumn(exp.Coalesce(expressions=expressions))
|
|
198
225
|
|
|
199
226
|
def cast(self, data_type: str) -> "FunctionColumn":
|
|
@@ -254,6 +281,15 @@ class Column:
|
|
|
254
281
|
"""Hash based on table and column name."""
|
|
255
282
|
return hash((self.table, self.name))
|
|
256
283
|
|
|
284
|
+
@property
|
|
285
|
+
def sqlglot_expression(self) -> exp.Expression:
|
|
286
|
+
"""Get the underlying SQLGlot expression (public API).
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
The SQLGlot expression for this column
|
|
290
|
+
"""
|
|
291
|
+
return self._expression
|
|
292
|
+
|
|
257
293
|
|
|
258
294
|
class FunctionColumn:
|
|
259
295
|
"""Represents the result of a SQL function call on a column."""
|
|
@@ -263,22 +299,26 @@ class FunctionColumn:
|
|
|
263
299
|
def __init__(self, expression: exp.Expression) -> None:
|
|
264
300
|
self._expression = expression
|
|
265
301
|
|
|
302
|
+
def _convert_value(self, value: Any) -> exp.Expression:
|
|
303
|
+
"""Convert a Python value to a SQLGlot expression."""
|
|
304
|
+
return _convert_value(value)
|
|
305
|
+
|
|
266
306
|
def __eq__(self, other: object) -> ColumnExpression: # type: ignore[override]
|
|
267
|
-
return ColumnExpression(exp.EQ(this=self._expression, expression=
|
|
307
|
+
return ColumnExpression(exp.EQ(this=self._expression, expression=self._convert_value(other)))
|
|
268
308
|
|
|
269
309
|
def __ne__(self, other: object) -> ColumnExpression: # type: ignore[override]
|
|
270
|
-
return ColumnExpression(exp.NEQ(this=self._expression, expression=
|
|
310
|
+
return ColumnExpression(exp.NEQ(this=self._expression, expression=self._convert_value(other)))
|
|
271
311
|
|
|
272
312
|
def like(self, pattern: str) -> ColumnExpression:
|
|
273
|
-
return ColumnExpression(exp.Like(this=self._expression, expression=
|
|
313
|
+
return ColumnExpression(exp.Like(this=self._expression, expression=self._convert_value(pattern)))
|
|
274
314
|
|
|
275
315
|
def ilike(self, pattern: str) -> ColumnExpression:
|
|
276
316
|
"""Case-insensitive LIKE."""
|
|
277
|
-
return ColumnExpression(exp.ILike(this=self._expression, expression=
|
|
317
|
+
return ColumnExpression(exp.ILike(this=self._expression, expression=self._convert_value(pattern)))
|
|
278
318
|
|
|
279
319
|
def in_(self, values: Iterable[Any]) -> ColumnExpression:
|
|
280
320
|
"""SQL IN clause."""
|
|
281
|
-
converted_values = [
|
|
321
|
+
converted_values = [self._convert_value(v) for v in values]
|
|
282
322
|
return ColumnExpression(exp.In(this=self._expression, expressions=converted_values))
|
|
283
323
|
|
|
284
324
|
def not_in_(self, values: Iterable[Any]) -> ColumnExpression:
|
|
@@ -295,7 +335,9 @@ class FunctionColumn:
|
|
|
295
335
|
|
|
296
336
|
def between(self, start: Any, end: Any) -> ColumnExpression:
|
|
297
337
|
"""SQL BETWEEN clause."""
|
|
298
|
-
return ColumnExpression(
|
|
338
|
+
return ColumnExpression(
|
|
339
|
+
exp.Between(this=self._expression, low=self._convert_value(start), high=self._convert_value(end))
|
|
340
|
+
)
|
|
299
341
|
|
|
300
342
|
def is_null(self) -> ColumnExpression:
|
|
301
343
|
"""SQL IS NULL."""
|
|
@@ -307,12 +349,12 @@ class FunctionColumn:
|
|
|
307
349
|
|
|
308
350
|
def any_(self, values: Iterable[Any]) -> ColumnExpression:
|
|
309
351
|
"""SQL = ANY(...) clause."""
|
|
310
|
-
converted_values = [
|
|
352
|
+
converted_values = [self._convert_value(v) for v in values]
|
|
311
353
|
return ColumnExpression(exp.EQ(this=self._expression, expression=exp.Any(expressions=converted_values)))
|
|
312
354
|
|
|
313
355
|
def not_any_(self, values: Iterable[Any]) -> ColumnExpression:
|
|
314
356
|
"""SQL <> ANY(...) clause."""
|
|
315
|
-
converted_values = [
|
|
357
|
+
converted_values = [self._convert_value(v) for v in values]
|
|
316
358
|
return ColumnExpression(exp.NEQ(this=self._expression, expression=exp.Any(expressions=converted_values)))
|
|
317
359
|
|
|
318
360
|
def alias(self, alias_name: str) -> exp.Expression:
|
sqlspec/builder/_ddl.py
CHANGED
|
@@ -11,12 +11,14 @@ from sqlglot.dialects.dialect import DialectType
|
|
|
11
11
|
from typing_extensions import Self
|
|
12
12
|
|
|
13
13
|
from sqlspec.builder._base import QueryBuilder, SafeQuery
|
|
14
|
+
from sqlspec.builder._select import Select
|
|
14
15
|
from sqlspec.core.result import SQLResult
|
|
16
|
+
from sqlspec.core.statement import SQL
|
|
15
17
|
from sqlspec.utils.type_guards import has_sqlglot_expression, has_with_method
|
|
16
18
|
|
|
17
19
|
if TYPE_CHECKING:
|
|
18
20
|
from sqlspec.builder._column import ColumnExpression
|
|
19
|
-
from sqlspec.core.statement import
|
|
21
|
+
from sqlspec.core.statement import StatementConfig
|
|
20
22
|
|
|
21
23
|
__all__ = (
|
|
22
24
|
"AlterOperation",
|
|
@@ -39,6 +41,37 @@ __all__ = (
|
|
|
39
41
|
"Truncate",
|
|
40
42
|
)
|
|
41
43
|
|
|
44
|
+
CONSTRAINT_TYPE_PRIMARY_KEY = "PRIMARY KEY"
|
|
45
|
+
CONSTRAINT_TYPE_FOREIGN_KEY = "FOREIGN KEY"
|
|
46
|
+
CONSTRAINT_TYPE_UNIQUE = "UNIQUE"
|
|
47
|
+
CONSTRAINT_TYPE_CHECK = "CHECK"
|
|
48
|
+
|
|
49
|
+
FOREIGN_KEY_ACTION_CASCADE = "CASCADE"
|
|
50
|
+
FOREIGN_KEY_ACTION_SET_NULL = "SET NULL"
|
|
51
|
+
FOREIGN_KEY_ACTION_SET_DEFAULT = "SET DEFAULT"
|
|
52
|
+
FOREIGN_KEY_ACTION_RESTRICT = "RESTRICT"
|
|
53
|
+
FOREIGN_KEY_ACTION_NO_ACTION = "NO ACTION"
|
|
54
|
+
|
|
55
|
+
VALID_FOREIGN_KEY_ACTIONS = {
|
|
56
|
+
FOREIGN_KEY_ACTION_CASCADE,
|
|
57
|
+
FOREIGN_KEY_ACTION_SET_NULL,
|
|
58
|
+
FOREIGN_KEY_ACTION_SET_DEFAULT,
|
|
59
|
+
FOREIGN_KEY_ACTION_RESTRICT,
|
|
60
|
+
FOREIGN_KEY_ACTION_NO_ACTION,
|
|
61
|
+
None,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
VALID_CONSTRAINT_TYPES = {
|
|
65
|
+
CONSTRAINT_TYPE_PRIMARY_KEY,
|
|
66
|
+
CONSTRAINT_TYPE_FOREIGN_KEY,
|
|
67
|
+
CONSTRAINT_TYPE_UNIQUE,
|
|
68
|
+
CONSTRAINT_TYPE_CHECK,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
CURRENT_TIMESTAMP_KEYWORD = "CURRENT_TIMESTAMP"
|
|
72
|
+
CURRENT_DATE_KEYWORD = "CURRENT_DATE"
|
|
73
|
+
CURRENT_TIME_KEYWORD = "CURRENT_TIME"
|
|
74
|
+
|
|
42
75
|
|
|
43
76
|
def build_column_expression(col: "ColumnDefinition") -> "exp.Expression":
|
|
44
77
|
"""Build SQLGlot expression for a column definition."""
|
|
@@ -59,11 +92,11 @@ def build_column_expression(col: "ColumnDefinition") -> "exp.Expression":
|
|
|
59
92
|
default_expr: Optional[exp.Expression] = None
|
|
60
93
|
if isinstance(col.default, str):
|
|
61
94
|
default_upper = col.default.upper()
|
|
62
|
-
if default_upper ==
|
|
95
|
+
if default_upper == CURRENT_TIMESTAMP_KEYWORD:
|
|
63
96
|
default_expr = exp.CurrentTimestamp()
|
|
64
|
-
elif default_upper ==
|
|
97
|
+
elif default_upper == CURRENT_DATE_KEYWORD:
|
|
65
98
|
default_expr = exp.CurrentDate()
|
|
66
|
-
elif default_upper ==
|
|
99
|
+
elif default_upper == CURRENT_TIME_KEYWORD:
|
|
67
100
|
default_expr = exp.CurrentTime()
|
|
68
101
|
elif "(" in col.default:
|
|
69
102
|
default_expr = exp.maybe_parse(col.default)
|
|
@@ -96,14 +129,14 @@ def build_column_expression(col: "ColumnDefinition") -> "exp.Expression":
|
|
|
96
129
|
|
|
97
130
|
def build_constraint_expression(constraint: "ConstraintDefinition") -> "Optional[exp.Expression]":
|
|
98
131
|
"""Build SQLGlot expression for a table constraint."""
|
|
99
|
-
if constraint.constraint_type ==
|
|
132
|
+
if constraint.constraint_type == CONSTRAINT_TYPE_PRIMARY_KEY:
|
|
100
133
|
pk_constraint = exp.PrimaryKey(expressions=[exp.to_identifier(col) for col in constraint.columns])
|
|
101
134
|
|
|
102
135
|
if constraint.name:
|
|
103
136
|
return exp.Constraint(this=exp.to_identifier(constraint.name), expression=pk_constraint)
|
|
104
137
|
return pk_constraint
|
|
105
138
|
|
|
106
|
-
if constraint.constraint_type ==
|
|
139
|
+
if constraint.constraint_type == CONSTRAINT_TYPE_FOREIGN_KEY:
|
|
107
140
|
fk_constraint = exp.ForeignKey(
|
|
108
141
|
expressions=[exp.to_identifier(col) for col in constraint.columns],
|
|
109
142
|
reference=exp.Reference(
|
|
@@ -118,14 +151,14 @@ def build_constraint_expression(constraint: "ConstraintDefinition") -> "Optional
|
|
|
118
151
|
return exp.Constraint(this=exp.to_identifier(constraint.name), expression=fk_constraint)
|
|
119
152
|
return fk_constraint
|
|
120
153
|
|
|
121
|
-
if constraint.constraint_type ==
|
|
154
|
+
if constraint.constraint_type == CONSTRAINT_TYPE_UNIQUE:
|
|
122
155
|
unique_constraint = exp.UniqueKeyProperty(expressions=[exp.to_identifier(col) for col in constraint.columns])
|
|
123
156
|
|
|
124
157
|
if constraint.name:
|
|
125
158
|
return exp.Constraint(this=exp.to_identifier(constraint.name), expression=unique_constraint)
|
|
126
159
|
return unique_constraint
|
|
127
160
|
|
|
128
|
-
if constraint.constraint_type ==
|
|
161
|
+
if constraint.constraint_type == CONSTRAINT_TYPE_CHECK:
|
|
129
162
|
check_expr = exp.Check(this=exp.maybe_parse(constraint.condition) if constraint.condition else None)
|
|
130
163
|
|
|
131
164
|
if constraint.name:
|
|
@@ -356,7 +389,7 @@ class CreateTable(DDLBuilder):
|
|
|
356
389
|
|
|
357
390
|
self._columns.append(column_def)
|
|
358
391
|
|
|
359
|
-
if primary_key and not
|
|
392
|
+
if primary_key and not self._has_primary_key_constraint():
|
|
360
393
|
self.primary_key_constraint([name])
|
|
361
394
|
|
|
362
395
|
return self
|
|
@@ -368,13 +401,13 @@ class CreateTable(DDLBuilder):
|
|
|
368
401
|
if not col_list:
|
|
369
402
|
self._raise_sql_builder_error("Primary key must include at least one column")
|
|
370
403
|
|
|
371
|
-
existing_pk =
|
|
404
|
+
existing_pk = self._find_primary_key_constraint()
|
|
372
405
|
if existing_pk:
|
|
373
406
|
for col in col_list:
|
|
374
407
|
if col not in existing_pk.columns:
|
|
375
408
|
existing_pk.columns.append(col)
|
|
376
409
|
else:
|
|
377
|
-
constraint = ConstraintDefinition(constraint_type=
|
|
410
|
+
constraint = ConstraintDefinition(constraint_type=CONSTRAINT_TYPE_PRIMARY_KEY, name=name, columns=col_list)
|
|
378
411
|
self._constraints.append(constraint)
|
|
379
412
|
|
|
380
413
|
return self
|
|
@@ -398,14 +431,11 @@ class CreateTable(DDLBuilder):
|
|
|
398
431
|
if len(col_list) != len(ref_col_list):
|
|
399
432
|
self._raise_sql_builder_error("Foreign key columns and referenced columns must have same length")
|
|
400
433
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
self._raise_sql_builder_error(f"Invalid ON DELETE action: {on_delete}")
|
|
404
|
-
if on_update and on_update.upper() not in valid_actions:
|
|
405
|
-
self._raise_sql_builder_error(f"Invalid ON UPDATE action: {on_update}")
|
|
434
|
+
self._validate_foreign_key_action(on_delete, "ON DELETE")
|
|
435
|
+
self._validate_foreign_key_action(on_update, "ON UPDATE")
|
|
406
436
|
|
|
407
437
|
constraint = ConstraintDefinition(
|
|
408
|
-
constraint_type=
|
|
438
|
+
constraint_type=CONSTRAINT_TYPE_FOREIGN_KEY,
|
|
409
439
|
name=name,
|
|
410
440
|
columns=col_list,
|
|
411
441
|
references_table=references_table,
|
|
@@ -426,7 +456,7 @@ class CreateTable(DDLBuilder):
|
|
|
426
456
|
if not col_list:
|
|
427
457
|
self._raise_sql_builder_error("Unique constraint must include at least one column")
|
|
428
458
|
|
|
429
|
-
constraint = ConstraintDefinition(constraint_type=
|
|
459
|
+
constraint = ConstraintDefinition(constraint_type=CONSTRAINT_TYPE_UNIQUE, name=name, columns=col_list)
|
|
430
460
|
|
|
431
461
|
self._constraints.append(constraint)
|
|
432
462
|
return self
|
|
@@ -443,7 +473,7 @@ class CreateTable(DDLBuilder):
|
|
|
443
473
|
else:
|
|
444
474
|
condition_str = str(condition)
|
|
445
475
|
|
|
446
|
-
constraint = ConstraintDefinition(constraint_type=
|
|
476
|
+
constraint = ConstraintDefinition(constraint_type=CONSTRAINT_TYPE_CHECK, name=name, condition=condition_str)
|
|
447
477
|
|
|
448
478
|
self._constraints.append(constraint)
|
|
449
479
|
return self
|
|
@@ -484,10 +514,8 @@ class CreateTable(DDLBuilder):
|
|
|
484
514
|
column_defs.append(col_expr)
|
|
485
515
|
|
|
486
516
|
for constraint in self._constraints:
|
|
487
|
-
if
|
|
488
|
-
|
|
489
|
-
if any(c.name == col_name and c.primary_key for c in self._columns):
|
|
490
|
-
continue
|
|
517
|
+
if self._is_redundant_single_column_primary_key(constraint):
|
|
518
|
+
continue
|
|
491
519
|
|
|
492
520
|
constraint_expr = build_constraint_expression(constraint)
|
|
493
521
|
if constraint_expr:
|
|
@@ -531,6 +559,27 @@ class CreateTable(DDLBuilder):
|
|
|
531
559
|
like=like_expr,
|
|
532
560
|
)
|
|
533
561
|
|
|
562
|
+
def _has_primary_key_constraint(self) -> bool:
|
|
563
|
+
"""Check if table already has a primary key constraint."""
|
|
564
|
+
return any(c.constraint_type == CONSTRAINT_TYPE_PRIMARY_KEY for c in self._constraints)
|
|
565
|
+
|
|
566
|
+
def _find_primary_key_constraint(self) -> "Optional[ConstraintDefinition]":
|
|
567
|
+
"""Find existing primary key constraint."""
|
|
568
|
+
return next((c for c in self._constraints if c.constraint_type == CONSTRAINT_TYPE_PRIMARY_KEY), None)
|
|
569
|
+
|
|
570
|
+
def _validate_foreign_key_action(self, action: "Optional[str]", action_type: str) -> None:
|
|
571
|
+
"""Validate foreign key action (ON DELETE or ON UPDATE)."""
|
|
572
|
+
if action and action.upper() not in VALID_FOREIGN_KEY_ACTIONS:
|
|
573
|
+
self._raise_sql_builder_error(f"Invalid {action_type} action: {action}")
|
|
574
|
+
|
|
575
|
+
def _is_redundant_single_column_primary_key(self, constraint: "ConstraintDefinition") -> bool:
|
|
576
|
+
"""Check if constraint is a redundant single-column primary key."""
|
|
577
|
+
if constraint.constraint_type != CONSTRAINT_TYPE_PRIMARY_KEY or len(constraint.columns) != 1:
|
|
578
|
+
return False
|
|
579
|
+
|
|
580
|
+
col_name = constraint.columns[0]
|
|
581
|
+
return any(c.name == col_name and c.primary_key for c in self._columns)
|
|
582
|
+
|
|
534
583
|
|
|
535
584
|
class DropTable(DDLBuilder):
|
|
536
585
|
"""Builder for DROP TABLE [IF EXISTS] ... [CASCADE|RESTRICT]."""
|
|
@@ -966,17 +1015,15 @@ class CreateTableAsSelect(DDLBuilder):
|
|
|
966
1015
|
|
|
967
1016
|
select_expr = None
|
|
968
1017
|
select_parameters = None
|
|
969
|
-
from sqlspec.builder._select import Select
|
|
970
|
-
from sqlspec.core.statement import SQL
|
|
971
1018
|
|
|
972
1019
|
if isinstance(self._select_query, SQL):
|
|
973
1020
|
select_expr = self._select_query.expression
|
|
974
1021
|
select_parameters = self._select_query.parameters
|
|
975
1022
|
elif isinstance(self._select_query, Select):
|
|
976
|
-
select_expr = self._select_query.
|
|
977
|
-
select_parameters = self._select_query.
|
|
1023
|
+
select_expr = self._select_query.get_expression()
|
|
1024
|
+
select_parameters = self._select_query.parameters
|
|
978
1025
|
|
|
979
|
-
with_ctes = self._select_query.
|
|
1026
|
+
with_ctes = self._select_query.with_ctes
|
|
980
1027
|
if with_ctes and select_expr and isinstance(select_expr, exp.Select):
|
|
981
1028
|
for alias, cte in with_ctes.items():
|
|
982
1029
|
if has_with_method(select_expr):
|
|
@@ -1093,15 +1140,13 @@ class CreateMaterializedView(DDLBuilder):
|
|
|
1093
1140
|
|
|
1094
1141
|
select_expr = None
|
|
1095
1142
|
select_parameters = None
|
|
1096
|
-
from sqlspec.builder._select import Select
|
|
1097
|
-
from sqlspec.core.statement import SQL
|
|
1098
1143
|
|
|
1099
1144
|
if isinstance(self._select_query, SQL):
|
|
1100
1145
|
select_expr = self._select_query.expression
|
|
1101
1146
|
select_parameters = self._select_query.parameters
|
|
1102
1147
|
elif isinstance(self._select_query, Select):
|
|
1103
|
-
select_expr = self._select_query.
|
|
1104
|
-
select_parameters = self._select_query.
|
|
1148
|
+
select_expr = self._select_query.get_expression()
|
|
1149
|
+
select_parameters = self._select_query.parameters
|
|
1105
1150
|
elif isinstance(self._select_query, str):
|
|
1106
1151
|
select_expr = exp.maybe_parse(self._select_query)
|
|
1107
1152
|
select_parameters = None
|
|
@@ -1191,15 +1236,13 @@ class CreateView(DDLBuilder):
|
|
|
1191
1236
|
|
|
1192
1237
|
select_expr = None
|
|
1193
1238
|
select_parameters = None
|
|
1194
|
-
from sqlspec.builder._select import Select
|
|
1195
|
-
from sqlspec.core.statement import SQL
|
|
1196
1239
|
|
|
1197
1240
|
if isinstance(self._select_query, SQL):
|
|
1198
1241
|
select_expr = self._select_query.expression
|
|
1199
1242
|
select_parameters = self._select_query.parameters
|
|
1200
1243
|
elif isinstance(self._select_query, Select):
|
|
1201
|
-
select_expr = self._select_query.
|
|
1202
|
-
select_parameters = self._select_query.
|
|
1244
|
+
select_expr = self._select_query.get_expression()
|
|
1245
|
+
select_parameters = self._select_query.parameters
|
|
1203
1246
|
elif isinstance(self._select_query, str):
|
|
1204
1247
|
select_expr = exp.maybe_parse(self._select_query)
|
|
1205
1248
|
select_parameters = None
|
|
@@ -1347,8 +1390,7 @@ class AlterTable(DDLBuilder):
|
|
|
1347
1390
|
on_delete: Foreign key ON DELETE action
|
|
1348
1391
|
on_update: Foreign key ON UPDATE action
|
|
1349
1392
|
"""
|
|
1350
|
-
|
|
1351
|
-
if constraint_type.upper() not in valid_types:
|
|
1393
|
+
if constraint_type.upper() not in VALID_CONSTRAINT_TYPES:
|
|
1352
1394
|
self._raise_sql_builder_error(f"Invalid constraint type: {constraint_type}")
|
|
1353
1395
|
|
|
1354
1396
|
col_list = None
|
|
@@ -1361,8 +1403,8 @@ class AlterTable(DDLBuilder):
|
|
|
1361
1403
|
|
|
1362
1404
|
condition_str: Optional[str] = None
|
|
1363
1405
|
if condition is not None:
|
|
1364
|
-
if
|
|
1365
|
-
sqlglot_expr =
|
|
1406
|
+
if has_sqlglot_expression(condition):
|
|
1407
|
+
sqlglot_expr = condition.sqlglot_expression
|
|
1366
1408
|
condition_str = sqlglot_expr.sql(dialect=self.dialect) if sqlglot_expr else str(condition)
|
|
1367
1409
|
else:
|
|
1368
1410
|
condition_str = str(condition)
|
|
@@ -1474,7 +1516,7 @@ class AlterTable(DDLBuilder):
|
|
|
1474
1516
|
default_val = op.column_definition.default
|
|
1475
1517
|
default_expr: Optional[exp.Expression]
|
|
1476
1518
|
if isinstance(default_val, str):
|
|
1477
|
-
if
|
|
1519
|
+
if self._is_sql_function_default(default_val):
|
|
1478
1520
|
default_expr = exp.maybe_parse(default_val)
|
|
1479
1521
|
else:
|
|
1480
1522
|
default_expr = exp.convert(default_val)
|
|
@@ -1494,6 +1536,14 @@ class AlterTable(DDLBuilder):
|
|
|
1494
1536
|
self._raise_sql_builder_error(f"Unknown operation type: {op.operation_type}")
|
|
1495
1537
|
raise AssertionError
|
|
1496
1538
|
|
|
1539
|
+
def _is_sql_function_default(self, default_val: str) -> bool:
|
|
1540
|
+
"""Check if default value is a SQL function or expression."""
|
|
1541
|
+
default_upper = default_val.upper()
|
|
1542
|
+
return (
|
|
1543
|
+
default_upper in {CURRENT_TIMESTAMP_KEYWORD, CURRENT_DATE_KEYWORD, CURRENT_TIME_KEYWORD}
|
|
1544
|
+
or "(" in default_val
|
|
1545
|
+
)
|
|
1546
|
+
|
|
1497
1547
|
|
|
1498
1548
|
class CommentOn(DDLBuilder):
|
|
1499
1549
|
"""Builder for COMMENT ON ... IS ... statements."""
|