sqlspec 0.16.1__cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sqlspec might be problematic. Click here for more details.
- 51ff5a9eadfdefd49f98__mypyc.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/__init__.py +92 -0
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +14 -0
- sqlspec/_serialization.py +77 -0
- sqlspec/_sql.py +1780 -0
- sqlspec/_typing.py +680 -0
- sqlspec/adapters/__init__.py +0 -0
- sqlspec/adapters/adbc/__init__.py +5 -0
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +361 -0
- sqlspec/adapters/adbc/driver.py +512 -0
- sqlspec/adapters/aiosqlite/__init__.py +19 -0
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +253 -0
- sqlspec/adapters/aiosqlite/driver.py +248 -0
- sqlspec/adapters/asyncmy/__init__.py +19 -0
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +180 -0
- sqlspec/adapters/asyncmy/driver.py +274 -0
- sqlspec/adapters/asyncpg/__init__.py +21 -0
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +229 -0
- sqlspec/adapters/asyncpg/driver.py +344 -0
- sqlspec/adapters/bigquery/__init__.py +18 -0
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +298 -0
- sqlspec/adapters/bigquery/driver.py +558 -0
- sqlspec/adapters/duckdb/__init__.py +22 -0
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +504 -0
- sqlspec/adapters/duckdb/driver.py +368 -0
- sqlspec/adapters/oracledb/__init__.py +32 -0
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +317 -0
- sqlspec/adapters/oracledb/driver.py +538 -0
- sqlspec/adapters/psqlpy/__init__.py +16 -0
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +214 -0
- sqlspec/adapters/psqlpy/driver.py +530 -0
- sqlspec/adapters/psycopg/__init__.py +32 -0
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +426 -0
- sqlspec/adapters/psycopg/driver.py +796 -0
- sqlspec/adapters/sqlite/__init__.py +15 -0
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +240 -0
- sqlspec/adapters/sqlite/driver.py +294 -0
- sqlspec/base.py +571 -0
- sqlspec/builder/__init__.py +62 -0
- sqlspec/builder/_base.py +473 -0
- sqlspec/builder/_column.py +320 -0
- sqlspec/builder/_ddl.py +1346 -0
- sqlspec/builder/_ddl_utils.py +103 -0
- sqlspec/builder/_delete.py +76 -0
- sqlspec/builder/_insert.py +256 -0
- sqlspec/builder/_merge.py +71 -0
- sqlspec/builder/_parsing_utils.py +140 -0
- sqlspec/builder/_select.py +170 -0
- sqlspec/builder/_update.py +188 -0
- sqlspec/builder/mixins/__init__.py +55 -0
- sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
- sqlspec/builder/mixins/_delete_operations.py +41 -0
- sqlspec/builder/mixins/_insert_operations.py +244 -0
- sqlspec/builder/mixins/_join_operations.py +122 -0
- sqlspec/builder/mixins/_merge_operations.py +476 -0
- sqlspec/builder/mixins/_order_limit_operations.py +135 -0
- sqlspec/builder/mixins/_pivot_operations.py +153 -0
- sqlspec/builder/mixins/_select_operations.py +603 -0
- sqlspec/builder/mixins/_update_operations.py +187 -0
- sqlspec/builder/mixins/_where_clause.py +621 -0
- sqlspec/cli.py +247 -0
- sqlspec/config.py +395 -0
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/cache.py +871 -0
- sqlspec/core/compiler.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/compiler.py +417 -0
- sqlspec/core/filters.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/filters.py +830 -0
- sqlspec/core/hashing.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/parameters.py +1237 -0
- sqlspec/core/result.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/result.py +677 -0
- sqlspec/core/splitter.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/splitter.py +819 -0
- sqlspec/core/statement.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/core/statement.py +676 -0
- sqlspec/driver/__init__.py +19 -0
- sqlspec/driver/_async.py +502 -0
- sqlspec/driver/_common.py +631 -0
- sqlspec/driver/_sync.py +503 -0
- sqlspec/driver/mixins/__init__.py +6 -0
- sqlspec/driver/mixins/_result_tools.py +193 -0
- sqlspec/driver/mixins/_sql_translator.py +86 -0
- sqlspec/exceptions.py +193 -0
- sqlspec/extensions/__init__.py +0 -0
- sqlspec/extensions/aiosql/__init__.py +10 -0
- sqlspec/extensions/aiosql/adapter.py +461 -0
- sqlspec/extensions/litestar/__init__.py +6 -0
- sqlspec/extensions/litestar/_utils.py +52 -0
- sqlspec/extensions/litestar/cli.py +48 -0
- sqlspec/extensions/litestar/config.py +92 -0
- sqlspec/extensions/litestar/handlers.py +260 -0
- sqlspec/extensions/litestar/plugin.py +145 -0
- sqlspec/extensions/litestar/providers.py +454 -0
- sqlspec/loader.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/loader.py +760 -0
- sqlspec/migrations/__init__.py +35 -0
- sqlspec/migrations/base.py +414 -0
- sqlspec/migrations/commands.py +443 -0
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +213 -0
- sqlspec/migrations/tracker.py +140 -0
- sqlspec/migrations/utils.py +129 -0
- sqlspec/protocols.py +407 -0
- sqlspec/py.typed +0 -0
- sqlspec/storage/__init__.py +23 -0
- sqlspec/storage/backends/__init__.py +0 -0
- sqlspec/storage/backends/base.py +163 -0
- sqlspec/storage/backends/fsspec.py +386 -0
- sqlspec/storage/backends/obstore.py +459 -0
- sqlspec/storage/capabilities.py +102 -0
- sqlspec/storage/registry.py +239 -0
- sqlspec/typing.py +299 -0
- sqlspec/utils/__init__.py +3 -0
- sqlspec/utils/correlation.py +150 -0
- sqlspec/utils/deprecation.py +106 -0
- sqlspec/utils/fixtures.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/fixtures.py +58 -0
- sqlspec/utils/logging.py +127 -0
- sqlspec/utils/module_loader.py +89 -0
- sqlspec/utils/serializers.py +4 -0
- sqlspec/utils/singleton.py +32 -0
- sqlspec/utils/sync_tools.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/sync_tools.py +237 -0
- sqlspec/utils/text.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/text.py +96 -0
- sqlspec/utils/type_guards.cpython-312-aarch64-linux-gnu.so +0 -0
- sqlspec/utils/type_guards.py +1139 -0
- sqlspec-0.16.1.dist-info/METADATA +365 -0
- sqlspec-0.16.1.dist-info/RECORD +148 -0
- sqlspec-0.16.1.dist-info/WHEEL +7 -0
- sqlspec-0.16.1.dist-info/entry_points.txt +2 -0
- sqlspec-0.16.1.dist-info/licenses/LICENSE +21 -0
- sqlspec-0.16.1.dist-info/licenses/NOTICE +29 -0
sqlspec/builder/_ddl.py
ADDED
|
@@ -0,0 +1,1346 @@
|
|
|
1
|
+
"""DDL builders for SQLSpec: DROP, CREATE INDEX, TRUNCATE, etc."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
5
|
+
|
|
6
|
+
from sqlglot import exp
|
|
7
|
+
from sqlglot.dialects.dialect import DialectType
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from sqlspec.builder._base import QueryBuilder, SafeQuery
|
|
11
|
+
from sqlspec.builder._ddl_utils import build_column_expression, build_constraint_expression
|
|
12
|
+
from sqlspec.core.result import SQLResult
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from sqlspec.builder._column import ColumnExpression
|
|
16
|
+
from sqlspec.core.statement import SQL, StatementConfig
|
|
17
|
+
|
|
18
|
+
__all__ = (
|
|
19
|
+
"AlterOperation",
|
|
20
|
+
"AlterTable",
|
|
21
|
+
"ColumnDefinition",
|
|
22
|
+
"CommentOn",
|
|
23
|
+
"ConstraintDefinition",
|
|
24
|
+
"CreateIndex",
|
|
25
|
+
"CreateMaterializedView",
|
|
26
|
+
"CreateSchema",
|
|
27
|
+
"CreateTable",
|
|
28
|
+
"CreateTableAsSelect",
|
|
29
|
+
"CreateView",
|
|
30
|
+
"DDLBuilder",
|
|
31
|
+
"DropIndex",
|
|
32
|
+
"DropSchema",
|
|
33
|
+
"DropTable",
|
|
34
|
+
"DropView",
|
|
35
|
+
"RenameTable",
|
|
36
|
+
"Truncate",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class DDLBuilder(QueryBuilder):
|
|
42
|
+
"""Base class for DDL builders (CREATE, DROP, ALTER, etc)."""
|
|
43
|
+
|
|
44
|
+
dialect: DialectType = None
|
|
45
|
+
_expression: Optional[exp.Expression] = field(default=None, init=False, repr=False, compare=False, hash=False)
|
|
46
|
+
|
|
47
|
+
def __post_init__(self) -> None:
|
|
48
|
+
# Initialize parent class attributes since dataclass doesn't call super().__init__()
|
|
49
|
+
super().__init__(dialect=self.dialect)
|
|
50
|
+
|
|
51
|
+
def _create_base_expression(self) -> exp.Expression:
|
|
52
|
+
msg = "Subclasses must implement _create_base_expression."
|
|
53
|
+
raise NotImplementedError(msg)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def _expected_result_type(self) -> "type[SQLResult]":
|
|
57
|
+
return SQLResult
|
|
58
|
+
|
|
59
|
+
def build(self) -> "SafeQuery":
|
|
60
|
+
if self._expression is None:
|
|
61
|
+
self._expression = self._create_base_expression()
|
|
62
|
+
return super().build()
|
|
63
|
+
|
|
64
|
+
def to_statement(self, config: "Optional[StatementConfig]" = None) -> "SQL":
|
|
65
|
+
return super().to_statement(config=config)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class ColumnDefinition:
|
|
70
|
+
"""Column definition for CREATE TABLE."""
|
|
71
|
+
|
|
72
|
+
name: str
|
|
73
|
+
dtype: str
|
|
74
|
+
default: "Optional[Any]" = None
|
|
75
|
+
not_null: bool = False
|
|
76
|
+
primary_key: bool = False
|
|
77
|
+
unique: bool = False
|
|
78
|
+
auto_increment: bool = False
|
|
79
|
+
comment: "Optional[str]" = None
|
|
80
|
+
check: "Optional[str]" = None
|
|
81
|
+
generated: "Optional[str]" = None
|
|
82
|
+
collate: "Optional[str]" = None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class ConstraintDefinition:
|
|
87
|
+
"""Constraint definition for CREATE TABLE."""
|
|
88
|
+
|
|
89
|
+
constraint_type: str
|
|
90
|
+
name: "Optional[str]" = None
|
|
91
|
+
columns: "list[str]" = field(default_factory=list)
|
|
92
|
+
references_table: "Optional[str]" = None
|
|
93
|
+
references_columns: "list[str]" = field(default_factory=list)
|
|
94
|
+
condition: "Optional[str]" = None
|
|
95
|
+
on_delete: "Optional[str]" = None
|
|
96
|
+
on_update: "Optional[str]" = None
|
|
97
|
+
deferrable: bool = False
|
|
98
|
+
initially_deferred: bool = False
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass
|
|
102
|
+
class CreateTable(DDLBuilder):
|
|
103
|
+
"""Builder for CREATE TABLE statements with columns and constraints.
|
|
104
|
+
|
|
105
|
+
Example:
|
|
106
|
+
builder = (
|
|
107
|
+
CreateTable("users")
|
|
108
|
+
.column("id", "SERIAL", primary_key=True)
|
|
109
|
+
.column("email", "VARCHAR(255)", not_null=True, unique=True)
|
|
110
|
+
.column("created_at", "TIMESTAMP", default="CURRENT_TIMESTAMP")
|
|
111
|
+
.foreign_key_constraint("org_id", "organizations", "id")
|
|
112
|
+
)
|
|
113
|
+
sql = builder.build().sql
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
_table_name: str = field(default="", init=False)
|
|
117
|
+
_if_not_exists: bool = False
|
|
118
|
+
_temporary: bool = False
|
|
119
|
+
_columns: "list[ColumnDefinition]" = field(default_factory=list)
|
|
120
|
+
_constraints: "list[ConstraintDefinition]" = field(default_factory=list)
|
|
121
|
+
_table_options: "dict[str, Any]" = field(default_factory=dict)
|
|
122
|
+
_schema: "Optional[str]" = None
|
|
123
|
+
_tablespace: "Optional[str]" = None
|
|
124
|
+
_like_table: "Optional[str]" = None
|
|
125
|
+
_partition_by: "Optional[str]" = None
|
|
126
|
+
|
|
127
|
+
def __init__(self, table_name: str) -> None:
|
|
128
|
+
super().__init__()
|
|
129
|
+
self._table_name = table_name
|
|
130
|
+
|
|
131
|
+
def in_schema(self, schema_name: str) -> "Self":
|
|
132
|
+
"""Set the schema for the table."""
|
|
133
|
+
self._schema = schema_name
|
|
134
|
+
return self
|
|
135
|
+
|
|
136
|
+
def if_not_exists(self) -> "Self":
|
|
137
|
+
"""Add IF NOT EXISTS clause."""
|
|
138
|
+
self._if_not_exists = True
|
|
139
|
+
return self
|
|
140
|
+
|
|
141
|
+
def temporary(self) -> "Self":
|
|
142
|
+
"""Create a temporary table."""
|
|
143
|
+
self._temporary = True
|
|
144
|
+
return self
|
|
145
|
+
|
|
146
|
+
def like(self, source_table: str) -> "Self":
|
|
147
|
+
"""Create table LIKE another table."""
|
|
148
|
+
self._like_table = source_table
|
|
149
|
+
return self
|
|
150
|
+
|
|
151
|
+
def tablespace(self, name: str) -> "Self":
|
|
152
|
+
"""Set tablespace for the table."""
|
|
153
|
+
self._tablespace = name
|
|
154
|
+
return self
|
|
155
|
+
|
|
156
|
+
def partition_by(self, partition_spec: str) -> "Self":
|
|
157
|
+
"""Set partitioning specification."""
|
|
158
|
+
self._partition_by = partition_spec
|
|
159
|
+
return self
|
|
160
|
+
|
|
161
|
+
def column(
|
|
162
|
+
self,
|
|
163
|
+
name: str,
|
|
164
|
+
dtype: str,
|
|
165
|
+
default: "Optional[Any]" = None,
|
|
166
|
+
not_null: bool = False,
|
|
167
|
+
primary_key: bool = False,
|
|
168
|
+
unique: bool = False,
|
|
169
|
+
auto_increment: bool = False,
|
|
170
|
+
comment: "Optional[str]" = None,
|
|
171
|
+
check: "Optional[str]" = None,
|
|
172
|
+
generated: "Optional[str]" = None,
|
|
173
|
+
collate: "Optional[str]" = None,
|
|
174
|
+
) -> "Self":
|
|
175
|
+
"""Add a column definition to the table."""
|
|
176
|
+
if not name:
|
|
177
|
+
self._raise_sql_builder_error("Column name must be a non-empty string")
|
|
178
|
+
|
|
179
|
+
if not dtype:
|
|
180
|
+
self._raise_sql_builder_error("Column type must be a non-empty string")
|
|
181
|
+
|
|
182
|
+
if any(col.name == name for col in self._columns):
|
|
183
|
+
self._raise_sql_builder_error(f"Column '{name}' already defined")
|
|
184
|
+
|
|
185
|
+
column_def = ColumnDefinition(
|
|
186
|
+
name=name,
|
|
187
|
+
dtype=dtype,
|
|
188
|
+
default=default,
|
|
189
|
+
not_null=not_null,
|
|
190
|
+
primary_key=primary_key,
|
|
191
|
+
unique=unique,
|
|
192
|
+
auto_increment=auto_increment,
|
|
193
|
+
comment=comment,
|
|
194
|
+
check=check,
|
|
195
|
+
generated=generated,
|
|
196
|
+
collate=collate,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
self._columns.append(column_def)
|
|
200
|
+
|
|
201
|
+
if primary_key and not any(c.constraint_type == "PRIMARY KEY" for c in self._constraints):
|
|
202
|
+
self.primary_key_constraint([name])
|
|
203
|
+
|
|
204
|
+
return self
|
|
205
|
+
|
|
206
|
+
def primary_key_constraint(self, columns: "Union[str, list[str]]", name: "Optional[str]" = None) -> "Self":
|
|
207
|
+
"""Add a primary key constraint."""
|
|
208
|
+
col_list = [columns] if isinstance(columns, str) else list(columns)
|
|
209
|
+
|
|
210
|
+
if not col_list:
|
|
211
|
+
self._raise_sql_builder_error("Primary key must include at least one column")
|
|
212
|
+
|
|
213
|
+
existing_pk = next((c for c in self._constraints if c.constraint_type == "PRIMARY KEY"), None)
|
|
214
|
+
if existing_pk:
|
|
215
|
+
for col in col_list:
|
|
216
|
+
if col not in existing_pk.columns:
|
|
217
|
+
existing_pk.columns.append(col)
|
|
218
|
+
else:
|
|
219
|
+
constraint = ConstraintDefinition(constraint_type="PRIMARY KEY", name=name, columns=col_list)
|
|
220
|
+
self._constraints.append(constraint)
|
|
221
|
+
|
|
222
|
+
return self
|
|
223
|
+
|
|
224
|
+
def foreign_key_constraint(
|
|
225
|
+
self,
|
|
226
|
+
columns: "Union[str, list[str]]",
|
|
227
|
+
references_table: str,
|
|
228
|
+
references_columns: "Union[str, list[str]]",
|
|
229
|
+
name: "Optional[str]" = None,
|
|
230
|
+
on_delete: "Optional[str]" = None,
|
|
231
|
+
on_update: "Optional[str]" = None,
|
|
232
|
+
deferrable: bool = False,
|
|
233
|
+
initially_deferred: bool = False,
|
|
234
|
+
) -> "Self":
|
|
235
|
+
"""Add a foreign key constraint."""
|
|
236
|
+
col_list = [columns] if isinstance(columns, str) else list(columns)
|
|
237
|
+
|
|
238
|
+
ref_col_list = [references_columns] if isinstance(references_columns, str) else list(references_columns)
|
|
239
|
+
|
|
240
|
+
if len(col_list) != len(ref_col_list):
|
|
241
|
+
self._raise_sql_builder_error("Foreign key columns and referenced columns must have same length")
|
|
242
|
+
|
|
243
|
+
valid_actions = {"CASCADE", "SET NULL", "SET DEFAULT", "RESTRICT", "NO ACTION", None}
|
|
244
|
+
if on_delete and on_delete.upper() not in valid_actions:
|
|
245
|
+
self._raise_sql_builder_error(f"Invalid ON DELETE action: {on_delete}")
|
|
246
|
+
if on_update and on_update.upper() not in valid_actions:
|
|
247
|
+
self._raise_sql_builder_error(f"Invalid ON UPDATE action: {on_update}")
|
|
248
|
+
|
|
249
|
+
constraint = ConstraintDefinition(
|
|
250
|
+
constraint_type="FOREIGN KEY",
|
|
251
|
+
name=name,
|
|
252
|
+
columns=col_list,
|
|
253
|
+
references_table=references_table,
|
|
254
|
+
references_columns=ref_col_list,
|
|
255
|
+
on_delete=on_delete.upper() if on_delete else None,
|
|
256
|
+
on_update=on_update.upper() if on_update else None,
|
|
257
|
+
deferrable=deferrable,
|
|
258
|
+
initially_deferred=initially_deferred,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
self._constraints.append(constraint)
|
|
262
|
+
return self
|
|
263
|
+
|
|
264
|
+
def unique_constraint(self, columns: "Union[str, list[str]]", name: "Optional[str]" = None) -> "Self":
|
|
265
|
+
"""Add a unique constraint."""
|
|
266
|
+
col_list = [columns] if isinstance(columns, str) else list(columns)
|
|
267
|
+
|
|
268
|
+
if not col_list:
|
|
269
|
+
self._raise_sql_builder_error("Unique constraint must include at least one column")
|
|
270
|
+
|
|
271
|
+
constraint = ConstraintDefinition(constraint_type="UNIQUE", name=name, columns=col_list)
|
|
272
|
+
|
|
273
|
+
self._constraints.append(constraint)
|
|
274
|
+
return self
|
|
275
|
+
|
|
276
|
+
def check_constraint(self, condition: Union[str, "ColumnExpression"], name: "Optional[str]" = None) -> "Self":
|
|
277
|
+
"""Add a check constraint."""
|
|
278
|
+
if not condition:
|
|
279
|
+
self._raise_sql_builder_error("Check constraint must have a condition")
|
|
280
|
+
|
|
281
|
+
condition_str: str
|
|
282
|
+
if hasattr(condition, "sqlglot_expression"):
|
|
283
|
+
sqlglot_expr = getattr(condition, "sqlglot_expression", None)
|
|
284
|
+
condition_str = sqlglot_expr.sql(dialect=self.dialect) if sqlglot_expr else str(condition)
|
|
285
|
+
else:
|
|
286
|
+
condition_str = str(condition)
|
|
287
|
+
|
|
288
|
+
constraint = ConstraintDefinition(constraint_type="CHECK", name=name, condition=condition_str)
|
|
289
|
+
|
|
290
|
+
self._constraints.append(constraint)
|
|
291
|
+
return self
|
|
292
|
+
|
|
293
|
+
def engine(self, engine_name: str) -> "Self":
|
|
294
|
+
"""Set storage engine (MySQL/MariaDB)."""
|
|
295
|
+
self._table_options["engine"] = engine_name
|
|
296
|
+
return self
|
|
297
|
+
|
|
298
|
+
def charset(self, charset_name: str) -> "Self":
|
|
299
|
+
"""Set character set."""
|
|
300
|
+
self._table_options["charset"] = charset_name
|
|
301
|
+
return self
|
|
302
|
+
|
|
303
|
+
def collate(self, collation: str) -> "Self":
|
|
304
|
+
"""Set table collation."""
|
|
305
|
+
self._table_options["collate"] = collation
|
|
306
|
+
return self
|
|
307
|
+
|
|
308
|
+
def comment(self, comment_text: str) -> "Self":
|
|
309
|
+
"""Set table comment."""
|
|
310
|
+
self._table_options["comment"] = comment_text
|
|
311
|
+
return self
|
|
312
|
+
|
|
313
|
+
def with_option(self, key: str, value: "Any") -> "Self":
|
|
314
|
+
"""Add custom table option."""
|
|
315
|
+
self._table_options[key] = value
|
|
316
|
+
return self
|
|
317
|
+
|
|
318
|
+
def _create_base_expression(self) -> "exp.Expression":
|
|
319
|
+
"""Create the SQLGlot expression for CREATE TABLE."""
|
|
320
|
+
if not self._columns and not self._like_table:
|
|
321
|
+
self._raise_sql_builder_error("Table must have at least one column or use LIKE clause")
|
|
322
|
+
|
|
323
|
+
if self._schema:
|
|
324
|
+
table = exp.Table(this=exp.to_identifier(self._table_name), db=exp.to_identifier(self._schema))
|
|
325
|
+
else:
|
|
326
|
+
table = exp.to_table(self._table_name)
|
|
327
|
+
|
|
328
|
+
column_defs: list[exp.Expression] = []
|
|
329
|
+
for col in self._columns:
|
|
330
|
+
col_expr = build_column_expression(col)
|
|
331
|
+
column_defs.append(col_expr)
|
|
332
|
+
|
|
333
|
+
for constraint in self._constraints:
|
|
334
|
+
if constraint.constraint_type == "PRIMARY KEY" and len(constraint.columns) == 1:
|
|
335
|
+
col_name = constraint.columns[0]
|
|
336
|
+
if any(c.name == col_name and c.primary_key for c in self._columns):
|
|
337
|
+
continue
|
|
338
|
+
|
|
339
|
+
constraint_expr = build_constraint_expression(constraint)
|
|
340
|
+
if constraint_expr:
|
|
341
|
+
column_defs.append(constraint_expr)
|
|
342
|
+
|
|
343
|
+
props: list[exp.Property] = []
|
|
344
|
+
if self._table_options.get("engine"):
|
|
345
|
+
props.append(
|
|
346
|
+
exp.Property(
|
|
347
|
+
this=exp.to_identifier("ENGINE"), value=exp.to_identifier(self._table_options.get("engine"))
|
|
348
|
+
)
|
|
349
|
+
)
|
|
350
|
+
if self._tablespace:
|
|
351
|
+
props.append(exp.Property(this=exp.to_identifier("TABLESPACE"), value=exp.to_identifier(self._tablespace)))
|
|
352
|
+
if self._partition_by:
|
|
353
|
+
props.append(exp.Property(this=exp.to_identifier("PARTITION BY"), value=exp.convert(self._partition_by)))
|
|
354
|
+
|
|
355
|
+
for key, value in self._table_options.items():
|
|
356
|
+
if key != "engine":
|
|
357
|
+
props.append(exp.Property(this=exp.to_identifier(key.upper()), value=exp.convert(value)))
|
|
358
|
+
|
|
359
|
+
properties_node = exp.Properties(expressions=props) if props else None
|
|
360
|
+
|
|
361
|
+
schema_expr = exp.Schema(expressions=column_defs) if column_defs else None
|
|
362
|
+
|
|
363
|
+
like_expr = None
|
|
364
|
+
if self._like_table:
|
|
365
|
+
like_expr = exp.to_table(self._like_table)
|
|
366
|
+
|
|
367
|
+
return exp.Create(
|
|
368
|
+
kind="TABLE",
|
|
369
|
+
this=table,
|
|
370
|
+
exists=self._if_not_exists,
|
|
371
|
+
temporary=self._temporary,
|
|
372
|
+
expression=schema_expr,
|
|
373
|
+
properties=properties_node,
|
|
374
|
+
like=like_expr,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
@staticmethod
|
|
378
|
+
def _build_column_expression(col: "ColumnDefinition") -> "exp.Expression":
|
|
379
|
+
"""Build SQLGlot expression for a column definition."""
|
|
380
|
+
return build_column_expression(col)
|
|
381
|
+
|
|
382
|
+
@staticmethod
|
|
383
|
+
def _build_constraint_expression(constraint: "ConstraintDefinition") -> "Optional[exp.Expression]":
|
|
384
|
+
"""Build SQLGlot expression for a table constraint."""
|
|
385
|
+
return build_constraint_expression(constraint)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
@dataclass
|
|
389
|
+
class DropTable(DDLBuilder):
|
|
390
|
+
"""Builder for DROP TABLE [IF EXISTS] ... [CASCADE|RESTRICT]."""
|
|
391
|
+
|
|
392
|
+
_table_name: Optional[str] = None
|
|
393
|
+
_if_exists: bool = False
|
|
394
|
+
_cascade: Optional[bool] = None
|
|
395
|
+
|
|
396
|
+
def __init__(self, table_name: str, **kwargs: Any) -> None:
|
|
397
|
+
"""Initialize DROP TABLE with table name.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
table_name: Name of the table to drop
|
|
401
|
+
**kwargs: Additional DDLBuilder arguments
|
|
402
|
+
"""
|
|
403
|
+
super().__init__(**kwargs)
|
|
404
|
+
self._table_name = table_name
|
|
405
|
+
|
|
406
|
+
def table(self, name: str) -> Self:
|
|
407
|
+
self._table_name = name
|
|
408
|
+
return self
|
|
409
|
+
|
|
410
|
+
def if_exists(self) -> Self:
|
|
411
|
+
self._if_exists = True
|
|
412
|
+
return self
|
|
413
|
+
|
|
414
|
+
def cascade(self) -> Self:
|
|
415
|
+
self._cascade = True
|
|
416
|
+
return self
|
|
417
|
+
|
|
418
|
+
def restrict(self) -> Self:
|
|
419
|
+
self._cascade = False
|
|
420
|
+
return self
|
|
421
|
+
|
|
422
|
+
def _create_base_expression(self) -> exp.Expression:
|
|
423
|
+
if not self._table_name:
|
|
424
|
+
self._raise_sql_builder_error("Table name must be set for DROP TABLE.")
|
|
425
|
+
return exp.Drop(
|
|
426
|
+
kind="TABLE", this=exp.to_table(self._table_name), exists=self._if_exists, cascade=self._cascade
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@dataclass
|
|
431
|
+
class DropIndex(DDLBuilder):
|
|
432
|
+
"""Builder for DROP INDEX [IF EXISTS] ... [ON table] [CASCADE|RESTRICT]."""
|
|
433
|
+
|
|
434
|
+
_index_name: Optional[str] = None
|
|
435
|
+
_table_name: Optional[str] = None
|
|
436
|
+
_if_exists: bool = False
|
|
437
|
+
_cascade: Optional[bool] = None
|
|
438
|
+
|
|
439
|
+
def __init__(self, index_name: str, **kwargs: Any) -> None:
|
|
440
|
+
"""Initialize DROP INDEX with index name.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
index_name: Name of the index to drop
|
|
444
|
+
**kwargs: Additional DDLBuilder arguments
|
|
445
|
+
"""
|
|
446
|
+
super().__init__(**kwargs)
|
|
447
|
+
self._index_name = index_name
|
|
448
|
+
|
|
449
|
+
def name(self, index_name: str) -> Self:
|
|
450
|
+
self._index_name = index_name
|
|
451
|
+
return self
|
|
452
|
+
|
|
453
|
+
def on_table(self, table_name: str) -> Self:
|
|
454
|
+
self._table_name = table_name
|
|
455
|
+
return self
|
|
456
|
+
|
|
457
|
+
def if_exists(self) -> Self:
|
|
458
|
+
self._if_exists = True
|
|
459
|
+
return self
|
|
460
|
+
|
|
461
|
+
def cascade(self) -> Self:
|
|
462
|
+
self._cascade = True
|
|
463
|
+
return self
|
|
464
|
+
|
|
465
|
+
def restrict(self) -> Self:
|
|
466
|
+
self._cascade = False
|
|
467
|
+
return self
|
|
468
|
+
|
|
469
|
+
def _create_base_expression(self) -> exp.Expression:
|
|
470
|
+
if not self._index_name:
|
|
471
|
+
self._raise_sql_builder_error("Index name must be set for DROP INDEX.")
|
|
472
|
+
return exp.Drop(
|
|
473
|
+
kind="INDEX",
|
|
474
|
+
this=exp.to_identifier(self._index_name),
|
|
475
|
+
table=exp.to_table(self._table_name) if self._table_name else None,
|
|
476
|
+
exists=self._if_exists,
|
|
477
|
+
cascade=self._cascade,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
@dataclass
|
|
482
|
+
class DropView(DDLBuilder):
|
|
483
|
+
"""Builder for DROP VIEW [IF EXISTS] ... [CASCADE|RESTRICT]."""
|
|
484
|
+
|
|
485
|
+
_view_name: Optional[str] = None
|
|
486
|
+
_if_exists: bool = False
|
|
487
|
+
_cascade: Optional[bool] = None
|
|
488
|
+
|
|
489
|
+
def name(self, view_name: str) -> Self:
|
|
490
|
+
self._view_name = view_name
|
|
491
|
+
return self
|
|
492
|
+
|
|
493
|
+
def if_exists(self) -> Self:
|
|
494
|
+
self._if_exists = True
|
|
495
|
+
return self
|
|
496
|
+
|
|
497
|
+
def cascade(self) -> Self:
|
|
498
|
+
self._cascade = True
|
|
499
|
+
return self
|
|
500
|
+
|
|
501
|
+
def restrict(self) -> Self:
|
|
502
|
+
self._cascade = False
|
|
503
|
+
return self
|
|
504
|
+
|
|
505
|
+
def _create_base_expression(self) -> exp.Expression:
|
|
506
|
+
if not self._view_name:
|
|
507
|
+
self._raise_sql_builder_error("View name must be set for DROP VIEW.")
|
|
508
|
+
return exp.Drop(
|
|
509
|
+
kind="VIEW", this=exp.to_identifier(self._view_name), exists=self._if_exists, cascade=self._cascade
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
@dataclass
|
|
514
|
+
class DropSchema(DDLBuilder):
|
|
515
|
+
"""Builder for DROP SCHEMA [IF EXISTS] ... [CASCADE|RESTRICT]."""
|
|
516
|
+
|
|
517
|
+
_schema_name: Optional[str] = None
|
|
518
|
+
_if_exists: bool = False
|
|
519
|
+
_cascade: Optional[bool] = None
|
|
520
|
+
|
|
521
|
+
def name(self, schema_name: str) -> Self:
|
|
522
|
+
self._schema_name = schema_name
|
|
523
|
+
return self
|
|
524
|
+
|
|
525
|
+
def if_exists(self) -> Self:
|
|
526
|
+
self._if_exists = True
|
|
527
|
+
return self
|
|
528
|
+
|
|
529
|
+
def cascade(self) -> Self:
|
|
530
|
+
self._cascade = True
|
|
531
|
+
return self
|
|
532
|
+
|
|
533
|
+
def restrict(self) -> Self:
|
|
534
|
+
self._cascade = False
|
|
535
|
+
return self
|
|
536
|
+
|
|
537
|
+
def _create_base_expression(self) -> exp.Expression:
|
|
538
|
+
if not self._schema_name:
|
|
539
|
+
self._raise_sql_builder_error("Schema name must be set for DROP SCHEMA.")
|
|
540
|
+
return exp.Drop(
|
|
541
|
+
kind="SCHEMA", this=exp.to_identifier(self._schema_name), exists=self._if_exists, cascade=self._cascade
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
@dataclass
|
|
546
|
+
class CreateIndex(DDLBuilder):
|
|
547
|
+
"""Builder for CREATE [UNIQUE] INDEX [IF NOT EXISTS] ... ON ... (...).
|
|
548
|
+
|
|
549
|
+
Supports columns, expressions, ordering, using, and where.
|
|
550
|
+
"""
|
|
551
|
+
|
|
552
|
+
_index_name: Optional[str] = None
|
|
553
|
+
_table_name: Optional[str] = None
|
|
554
|
+
_columns: list[Union[str, exp.Ordered, exp.Expression]] = field(default_factory=list)
|
|
555
|
+
_unique: bool = False
|
|
556
|
+
_if_not_exists: bool = False
|
|
557
|
+
_using: Optional[str] = None
|
|
558
|
+
_where: Optional[Union[str, exp.Expression]] = None
|
|
559
|
+
|
|
560
|
+
def __init__(self, index_name: str, **kwargs: Any) -> None:
|
|
561
|
+
"""Initialize CREATE INDEX with index name.
|
|
562
|
+
|
|
563
|
+
Args:
|
|
564
|
+
index_name: Name of the index to create
|
|
565
|
+
**kwargs: Additional DDLBuilder arguments
|
|
566
|
+
"""
|
|
567
|
+
super().__init__(**kwargs)
|
|
568
|
+
self._index_name = index_name
|
|
569
|
+
if not hasattr(self, "_columns"):
|
|
570
|
+
self._columns = []
|
|
571
|
+
|
|
572
|
+
def name(self, index_name: str) -> Self:
|
|
573
|
+
self._index_name = index_name
|
|
574
|
+
return self
|
|
575
|
+
|
|
576
|
+
def on_table(self, table_name: str) -> Self:
|
|
577
|
+
self._table_name = table_name
|
|
578
|
+
return self
|
|
579
|
+
|
|
580
|
+
def columns(self, *cols: Union[str, exp.Ordered, exp.Expression]) -> Self:
|
|
581
|
+
self._columns.extend(cols)
|
|
582
|
+
return self
|
|
583
|
+
|
|
584
|
+
def expressions(self, *exprs: Union[str, exp.Expression]) -> Self:
|
|
585
|
+
self._columns.extend(exprs)
|
|
586
|
+
return self
|
|
587
|
+
|
|
588
|
+
def unique(self) -> Self:
|
|
589
|
+
self._unique = True
|
|
590
|
+
return self
|
|
591
|
+
|
|
592
|
+
def if_not_exists(self) -> Self:
|
|
593
|
+
self._if_not_exists = True
|
|
594
|
+
return self
|
|
595
|
+
|
|
596
|
+
def using(self, method: str) -> Self:
|
|
597
|
+
self._using = method
|
|
598
|
+
return self
|
|
599
|
+
|
|
600
|
+
def where(self, condition: Union[str, exp.Expression]) -> Self:
|
|
601
|
+
self._where = condition
|
|
602
|
+
return self
|
|
603
|
+
|
|
604
|
+
def _create_base_expression(self) -> exp.Expression:
|
|
605
|
+
if not self._index_name or not self._table_name:
|
|
606
|
+
self._raise_sql_builder_error("Index name and table name must be set for CREATE INDEX.")
|
|
607
|
+
exprs: list[exp.Expression] = []
|
|
608
|
+
for col in self._columns:
|
|
609
|
+
if isinstance(col, str):
|
|
610
|
+
exprs.append(exp.column(col))
|
|
611
|
+
else:
|
|
612
|
+
exprs.append(col)
|
|
613
|
+
where_expr = None
|
|
614
|
+
if self._where:
|
|
615
|
+
where_expr = exp.condition(self._where) if isinstance(self._where, str) else self._where
|
|
616
|
+
return exp.Create(
|
|
617
|
+
kind="INDEX",
|
|
618
|
+
this=exp.to_identifier(self._index_name),
|
|
619
|
+
table=exp.to_table(self._table_name),
|
|
620
|
+
expressions=exprs,
|
|
621
|
+
unique=self._unique,
|
|
622
|
+
exists=self._if_not_exists,
|
|
623
|
+
using=exp.to_identifier(self._using) if self._using else None,
|
|
624
|
+
where=where_expr,
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
@dataclass
|
|
629
|
+
class Truncate(DDLBuilder):
|
|
630
|
+
"""Builder for TRUNCATE TABLE ... [CASCADE|RESTRICT] [RESTART IDENTITY|CONTINUE IDENTITY]."""
|
|
631
|
+
|
|
632
|
+
_table_name: Optional[str] = None
|
|
633
|
+
_cascade: Optional[bool] = None
|
|
634
|
+
_identity: Optional[str] = None
|
|
635
|
+
|
|
636
|
+
def table(self, name: str) -> Self:
|
|
637
|
+
self._table_name = name
|
|
638
|
+
return self
|
|
639
|
+
|
|
640
|
+
def cascade(self) -> Self:
|
|
641
|
+
self._cascade = True
|
|
642
|
+
return self
|
|
643
|
+
|
|
644
|
+
def restrict(self) -> Self:
|
|
645
|
+
self._cascade = False
|
|
646
|
+
return self
|
|
647
|
+
|
|
648
|
+
def restart_identity(self) -> Self:
|
|
649
|
+
self._identity = "RESTART"
|
|
650
|
+
return self
|
|
651
|
+
|
|
652
|
+
def continue_identity(self) -> Self:
|
|
653
|
+
self._identity = "CONTINUE"
|
|
654
|
+
return self
|
|
655
|
+
|
|
656
|
+
def _create_base_expression(self) -> exp.Expression:
|
|
657
|
+
if not self._table_name:
|
|
658
|
+
self._raise_sql_builder_error("Table name must be set for TRUNCATE TABLE.")
|
|
659
|
+
identity_expr = exp.Var(this=self._identity) if self._identity else None
|
|
660
|
+
return exp.TruncateTable(this=exp.to_table(self._table_name), cascade=self._cascade, identity=identity_expr)
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
@dataclass
|
|
664
|
+
class AlterOperation:
|
|
665
|
+
"""Represents a single ALTER TABLE operation."""
|
|
666
|
+
|
|
667
|
+
operation_type: str
|
|
668
|
+
column_name: "Optional[str]" = None
|
|
669
|
+
column_definition: "Optional[ColumnDefinition]" = None
|
|
670
|
+
constraint_name: "Optional[str]" = None
|
|
671
|
+
constraint_definition: "Optional[ConstraintDefinition]" = None
|
|
672
|
+
new_type: "Optional[str]" = None
|
|
673
|
+
new_name: "Optional[str]" = None
|
|
674
|
+
after_column: "Optional[str]" = None
|
|
675
|
+
first: bool = False
|
|
676
|
+
using_expression: "Optional[str]" = None
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
@dataclass
|
|
680
|
+
class CreateSchema(DDLBuilder):
|
|
681
|
+
"""Builder for CREATE SCHEMA [IF NOT EXISTS] schema_name [AUTHORIZATION user_name]."""
|
|
682
|
+
|
|
683
|
+
_schema_name: Optional[str] = None
|
|
684
|
+
_if_not_exists: bool = False
|
|
685
|
+
_authorization: Optional[str] = None
|
|
686
|
+
|
|
687
|
+
def name(self, schema_name: str) -> Self:
|
|
688
|
+
self._schema_name = schema_name
|
|
689
|
+
return self
|
|
690
|
+
|
|
691
|
+
def if_not_exists(self) -> Self:
|
|
692
|
+
self._if_not_exists = True
|
|
693
|
+
return self
|
|
694
|
+
|
|
695
|
+
def authorization(self, user_name: str) -> Self:
|
|
696
|
+
self._authorization = user_name
|
|
697
|
+
return self
|
|
698
|
+
|
|
699
|
+
def _create_base_expression(self) -> exp.Expression:
|
|
700
|
+
if not self._schema_name:
|
|
701
|
+
self._raise_sql_builder_error("Schema name must be set for CREATE SCHEMA.")
|
|
702
|
+
props: list[exp.Property] = []
|
|
703
|
+
if self._authorization:
|
|
704
|
+
props.append(
|
|
705
|
+
exp.Property(this=exp.to_identifier("AUTHORIZATION"), value=exp.to_identifier(self._authorization))
|
|
706
|
+
)
|
|
707
|
+
properties_node = exp.Properties(expressions=props) if props else None
|
|
708
|
+
return exp.Create(
|
|
709
|
+
kind="SCHEMA",
|
|
710
|
+
this=exp.to_identifier(self._schema_name),
|
|
711
|
+
exists=self._if_not_exists,
|
|
712
|
+
properties=properties_node,
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
@dataclass
|
|
717
|
+
class CreateTableAsSelect(DDLBuilder):
|
|
718
|
+
"""Builder for CREATE TABLE [IF NOT EXISTS] ... AS SELECT ... (CTAS).
|
|
719
|
+
|
|
720
|
+
Supports optional column list and parameterized SELECT sources.
|
|
721
|
+
|
|
722
|
+
Example:
|
|
723
|
+
builder = (
|
|
724
|
+
CreateTableAsSelectBuilder()
|
|
725
|
+
.name("my_table")
|
|
726
|
+
.if_not_exists()
|
|
727
|
+
.columns("id", "name")
|
|
728
|
+
.as_select(select_builder)
|
|
729
|
+
)
|
|
730
|
+
sql = builder.build().sql
|
|
731
|
+
|
|
732
|
+
Methods:
|
|
733
|
+
- name(table_name: str): Set the table name.
|
|
734
|
+
- if_not_exists(): Add IF NOT EXISTS.
|
|
735
|
+
- columns(*cols: str): Set explicit column list (optional).
|
|
736
|
+
- as_select(select_query): Set the SELECT source (SQL, SelectBuilder, or str).
|
|
737
|
+
"""
|
|
738
|
+
|
|
739
|
+
_table_name: Optional[str] = None
|
|
740
|
+
_if_not_exists: bool = False
|
|
741
|
+
_columns: list[str] = field(default_factory=list)
|
|
742
|
+
_select_query: Optional[object] = None
|
|
743
|
+
|
|
744
|
+
def name(self, table_name: str) -> Self:
|
|
745
|
+
self._table_name = table_name
|
|
746
|
+
return self
|
|
747
|
+
|
|
748
|
+
def if_not_exists(self) -> Self:
|
|
749
|
+
self._if_not_exists = True
|
|
750
|
+
return self
|
|
751
|
+
|
|
752
|
+
def columns(self, *cols: str) -> Self:
|
|
753
|
+
self._columns = list(cols)
|
|
754
|
+
return self
|
|
755
|
+
|
|
756
|
+
def as_select(self, select_query: object) -> Self:
|
|
757
|
+
self._select_query = select_query
|
|
758
|
+
return self
|
|
759
|
+
|
|
760
|
+
def _create_base_expression(self) -> exp.Expression:
|
|
761
|
+
if not self._table_name:
|
|
762
|
+
self._raise_sql_builder_error("Table name must be set for CREATE TABLE AS SELECT.")
|
|
763
|
+
if self._select_query is None:
|
|
764
|
+
self._raise_sql_builder_error("SELECT query must be set for CREATE TABLE AS SELECT.")
|
|
765
|
+
|
|
766
|
+
select_expr = None
|
|
767
|
+
select_parameters = None
|
|
768
|
+
from sqlspec.builder._select import Select
|
|
769
|
+
from sqlspec.core.statement import SQL
|
|
770
|
+
|
|
771
|
+
if isinstance(self._select_query, SQL):
|
|
772
|
+
select_expr = self._select_query.expression
|
|
773
|
+
select_parameters = getattr(self._select_query, "parameters", None)
|
|
774
|
+
elif isinstance(self._select_query, Select):
|
|
775
|
+
select_expr = getattr(self._select_query, "_expression", None)
|
|
776
|
+
select_parameters = getattr(self._select_query, "_parameters", None)
|
|
777
|
+
|
|
778
|
+
with_ctes = getattr(self._select_query, "_with_ctes", {})
|
|
779
|
+
if with_ctes and select_expr and isinstance(select_expr, exp.Select):
|
|
780
|
+
for alias, cte in with_ctes.items():
|
|
781
|
+
if hasattr(select_expr, "with_"):
|
|
782
|
+
select_expr = select_expr.with_(cte.this, as_=alias, copy=False)
|
|
783
|
+
elif isinstance(self._select_query, str):
|
|
784
|
+
select_expr = exp.maybe_parse(self._select_query)
|
|
785
|
+
select_parameters = None
|
|
786
|
+
else:
|
|
787
|
+
self._raise_sql_builder_error("Unsupported type for SELECT query in CTAS.")
|
|
788
|
+
if select_expr is None:
|
|
789
|
+
self._raise_sql_builder_error("SELECT query must be a valid SELECT expression.")
|
|
790
|
+
|
|
791
|
+
if select_parameters:
|
|
792
|
+
for p_name, p_value in select_parameters.items():
|
|
793
|
+
self._parameters[p_name] = p_value
|
|
794
|
+
|
|
795
|
+
schema_expr = None
|
|
796
|
+
if self._columns:
|
|
797
|
+
schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
|
|
798
|
+
|
|
799
|
+
return exp.Create(
|
|
800
|
+
kind="TABLE",
|
|
801
|
+
this=exp.to_table(self._table_name),
|
|
802
|
+
exists=self._if_not_exists,
|
|
803
|
+
expression=select_expr,
|
|
804
|
+
schema=schema_expr,
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
@dataclass
|
|
809
|
+
class CreateMaterializedView(DDLBuilder):
|
|
810
|
+
"""Builder for CREATE MATERIALIZED VIEW [IF NOT EXISTS] ... AS SELECT ...
|
|
811
|
+
|
|
812
|
+
Supports optional column list, parameterized SELECT sources, and dialect-specific options.
|
|
813
|
+
"""
|
|
814
|
+
|
|
815
|
+
_view_name: Optional[str] = None
|
|
816
|
+
_if_not_exists: bool = False
|
|
817
|
+
_columns: list[str] = field(default_factory=list)
|
|
818
|
+
_select_query: Optional[object] = None
|
|
819
|
+
_with_data: Optional[bool] = None
|
|
820
|
+
_refresh_mode: Optional[str] = None
|
|
821
|
+
_storage_parameters: dict[str, Any] = field(default_factory=dict)
|
|
822
|
+
_tablespace: Optional[str] = None
|
|
823
|
+
_using_index: Optional[str] = None
|
|
824
|
+
_hints: list[str] = field(default_factory=list)
|
|
825
|
+
|
|
826
|
+
def name(self, view_name: str) -> Self:
|
|
827
|
+
self._view_name = view_name
|
|
828
|
+
return self
|
|
829
|
+
|
|
830
|
+
def if_not_exists(self) -> Self:
|
|
831
|
+
self._if_not_exists = True
|
|
832
|
+
return self
|
|
833
|
+
|
|
834
|
+
def columns(self, *cols: str) -> Self:
|
|
835
|
+
self._columns = list(cols)
|
|
836
|
+
return self
|
|
837
|
+
|
|
838
|
+
def as_select(self, select_query: object) -> Self:
|
|
839
|
+
self._select_query = select_query
|
|
840
|
+
return self
|
|
841
|
+
|
|
842
|
+
def with_data(self) -> Self:
|
|
843
|
+
self._with_data = True
|
|
844
|
+
return self
|
|
845
|
+
|
|
846
|
+
def no_data(self) -> Self:
|
|
847
|
+
self._with_data = False
|
|
848
|
+
return self
|
|
849
|
+
|
|
850
|
+
def refresh_mode(self, mode: str) -> Self:
|
|
851
|
+
self._refresh_mode = mode
|
|
852
|
+
return self
|
|
853
|
+
|
|
854
|
+
def storage_parameter(self, key: str, value: Any) -> Self:
|
|
855
|
+
self._storage_parameters[key] = value
|
|
856
|
+
return self
|
|
857
|
+
|
|
858
|
+
def tablespace(self, name: str) -> Self:
|
|
859
|
+
self._tablespace = name
|
|
860
|
+
return self
|
|
861
|
+
|
|
862
|
+
def using_index(self, index_name: str) -> Self:
|
|
863
|
+
self._using_index = index_name
|
|
864
|
+
return self
|
|
865
|
+
|
|
866
|
+
def with_hint(self, hint: str) -> Self:
|
|
867
|
+
self._hints.append(hint)
|
|
868
|
+
return self
|
|
869
|
+
|
|
870
|
+
def _create_base_expression(self) -> exp.Expression:
|
|
871
|
+
if not self._view_name:
|
|
872
|
+
self._raise_sql_builder_error("View name must be set for CREATE MATERIALIZED VIEW.")
|
|
873
|
+
if self._select_query is None:
|
|
874
|
+
self._raise_sql_builder_error("SELECT query must be set for CREATE MATERIALIZED VIEW.")
|
|
875
|
+
|
|
876
|
+
select_expr = None
|
|
877
|
+
select_parameters = None
|
|
878
|
+
from sqlspec.builder._select import Select
|
|
879
|
+
from sqlspec.core.statement import SQL
|
|
880
|
+
|
|
881
|
+
if isinstance(self._select_query, SQL):
|
|
882
|
+
select_expr = self._select_query.expression
|
|
883
|
+
select_parameters = getattr(self._select_query, "parameters", None)
|
|
884
|
+
elif isinstance(self._select_query, Select):
|
|
885
|
+
select_expr = getattr(self._select_query, "_expression", None)
|
|
886
|
+
select_parameters = getattr(self._select_query, "_parameters", None)
|
|
887
|
+
elif isinstance(self._select_query, str):
|
|
888
|
+
select_expr = exp.maybe_parse(self._select_query)
|
|
889
|
+
select_parameters = None
|
|
890
|
+
else:
|
|
891
|
+
self._raise_sql_builder_error("Unsupported type for SELECT query in materialized view.")
|
|
892
|
+
if select_expr is None or not isinstance(select_expr, exp.Select):
|
|
893
|
+
self._raise_sql_builder_error("SELECT query must be a valid SELECT expression.")
|
|
894
|
+
|
|
895
|
+
if select_parameters:
|
|
896
|
+
for p_name, p_value in select_parameters.items():
|
|
897
|
+
self._parameters[p_name] = p_value
|
|
898
|
+
|
|
899
|
+
schema_expr = None
|
|
900
|
+
if self._columns:
|
|
901
|
+
schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
|
|
902
|
+
|
|
903
|
+
props: list[exp.Property] = []
|
|
904
|
+
if self._refresh_mode:
|
|
905
|
+
props.append(exp.Property(this=exp.to_identifier("REFRESH_MODE"), value=exp.convert(self._refresh_mode)))
|
|
906
|
+
if self._tablespace:
|
|
907
|
+
props.append(exp.Property(this=exp.to_identifier("TABLESPACE"), value=exp.to_identifier(self._tablespace)))
|
|
908
|
+
if self._using_index:
|
|
909
|
+
props.append(
|
|
910
|
+
exp.Property(this=exp.to_identifier("USING_INDEX"), value=exp.to_identifier(self._using_index))
|
|
911
|
+
)
|
|
912
|
+
for k, v in self._storage_parameters.items():
|
|
913
|
+
props.append(exp.Property(this=exp.to_identifier(k), value=exp.convert(str(v))))
|
|
914
|
+
if self._with_data is not None:
|
|
915
|
+
props.append(exp.Property(this=exp.to_identifier("WITH_DATA" if self._with_data else "NO_DATA")))
|
|
916
|
+
props.extend(exp.Property(this=exp.to_identifier("HINT"), value=exp.convert(hint)) for hint in self._hints)
|
|
917
|
+
properties_node = exp.Properties(expressions=props) if props else None
|
|
918
|
+
|
|
919
|
+
return exp.Create(
|
|
920
|
+
kind="MATERIALIZED_VIEW",
|
|
921
|
+
this=exp.to_identifier(self._view_name),
|
|
922
|
+
exists=self._if_not_exists,
|
|
923
|
+
expression=select_expr,
|
|
924
|
+
schema=schema_expr,
|
|
925
|
+
properties=properties_node,
|
|
926
|
+
)
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
@dataclass
|
|
930
|
+
class CreateView(DDLBuilder):
|
|
931
|
+
"""Builder for CREATE VIEW [IF NOT EXISTS] ... AS SELECT ...
|
|
932
|
+
|
|
933
|
+
Supports optional column list, parameterized SELECT sources, and hints.
|
|
934
|
+
"""
|
|
935
|
+
|
|
936
|
+
_view_name: Optional[str] = None
|
|
937
|
+
_if_not_exists: bool = False
|
|
938
|
+
_columns: list[str] = field(default_factory=list)
|
|
939
|
+
_select_query: Optional[object] = None
|
|
940
|
+
_hints: list[str] = field(default_factory=list)
|
|
941
|
+
|
|
942
|
+
def name(self, view_name: str) -> Self:
|
|
943
|
+
self._view_name = view_name
|
|
944
|
+
return self
|
|
945
|
+
|
|
946
|
+
def if_not_exists(self) -> Self:
|
|
947
|
+
self._if_not_exists = True
|
|
948
|
+
return self
|
|
949
|
+
|
|
950
|
+
def columns(self, *cols: str) -> Self:
|
|
951
|
+
self._columns = list(cols)
|
|
952
|
+
return self
|
|
953
|
+
|
|
954
|
+
def as_select(self, select_query: object) -> Self:
|
|
955
|
+
self._select_query = select_query
|
|
956
|
+
return self
|
|
957
|
+
|
|
958
|
+
def with_hint(self, hint: str) -> Self:
|
|
959
|
+
self._hints.append(hint)
|
|
960
|
+
return self
|
|
961
|
+
|
|
962
|
+
def _create_base_expression(self) -> exp.Expression:
|
|
963
|
+
if not self._view_name:
|
|
964
|
+
self._raise_sql_builder_error("View name must be set for CREATE VIEW.")
|
|
965
|
+
if self._select_query is None:
|
|
966
|
+
self._raise_sql_builder_error("SELECT query must be set for CREATE VIEW.")
|
|
967
|
+
|
|
968
|
+
select_expr = None
|
|
969
|
+
select_parameters = None
|
|
970
|
+
from sqlspec.builder._select import Select
|
|
971
|
+
from sqlspec.core.statement import SQL
|
|
972
|
+
|
|
973
|
+
if isinstance(self._select_query, SQL):
|
|
974
|
+
select_expr = self._select_query.expression
|
|
975
|
+
select_parameters = getattr(self._select_query, "parameters", None)
|
|
976
|
+
elif isinstance(self._select_query, Select):
|
|
977
|
+
select_expr = getattr(self._select_query, "_expression", None)
|
|
978
|
+
select_parameters = getattr(self._select_query, "_parameters", None)
|
|
979
|
+
elif isinstance(self._select_query, str):
|
|
980
|
+
select_expr = exp.maybe_parse(self._select_query)
|
|
981
|
+
select_parameters = None
|
|
982
|
+
else:
|
|
983
|
+
self._raise_sql_builder_error("Unsupported type for SELECT query in view.")
|
|
984
|
+
if select_expr is None or not isinstance(select_expr, exp.Select):
|
|
985
|
+
self._raise_sql_builder_error("SELECT query must be a valid SELECT expression.")
|
|
986
|
+
|
|
987
|
+
if select_parameters:
|
|
988
|
+
for p_name, p_value in select_parameters.items():
|
|
989
|
+
self._parameters[p_name] = p_value
|
|
990
|
+
|
|
991
|
+
schema_expr = None
|
|
992
|
+
if self._columns:
|
|
993
|
+
schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
|
|
994
|
+
|
|
995
|
+
props: list[exp.Property] = [
|
|
996
|
+
exp.Property(this=exp.to_identifier("HINT"), value=exp.convert(h)) for h in self._hints
|
|
997
|
+
]
|
|
998
|
+
properties_node = exp.Properties(expressions=props) if props else None
|
|
999
|
+
|
|
1000
|
+
return exp.Create(
|
|
1001
|
+
kind="VIEW",
|
|
1002
|
+
this=exp.to_identifier(self._view_name),
|
|
1003
|
+
exists=self._if_not_exists,
|
|
1004
|
+
expression=select_expr,
|
|
1005
|
+
schema=schema_expr,
|
|
1006
|
+
properties=properties_node,
|
|
1007
|
+
)
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
@dataclass
|
|
1011
|
+
class AlterTable(DDLBuilder):
|
|
1012
|
+
"""Builder for ALTER TABLE with granular operations.
|
|
1013
|
+
|
|
1014
|
+
Supports column operations (add, drop, alter type, rename) and constraint operations.
|
|
1015
|
+
|
|
1016
|
+
Example:
|
|
1017
|
+
builder = (
|
|
1018
|
+
AlterTableBuilder("users")
|
|
1019
|
+
.add_column("email", "VARCHAR(255)", not_null=True)
|
|
1020
|
+
.drop_column("old_field")
|
|
1021
|
+
.add_constraint("check_age", "CHECK (age >= 18)")
|
|
1022
|
+
)
|
|
1023
|
+
"""
|
|
1024
|
+
|
|
1025
|
+
_table_name: str = field(default="", init=False)
|
|
1026
|
+
_operations: "list[AlterOperation]" = field(default_factory=list)
|
|
1027
|
+
_schema: "Optional[str]" = None
|
|
1028
|
+
_if_exists: bool = False
|
|
1029
|
+
|
|
1030
|
+
def __init__(self, table_name: str) -> None:
|
|
1031
|
+
super().__init__()
|
|
1032
|
+
self._table_name = table_name
|
|
1033
|
+
self._operations = []
|
|
1034
|
+
self._schema = None
|
|
1035
|
+
self._if_exists = False
|
|
1036
|
+
|
|
1037
|
+
def if_exists(self) -> "Self":
|
|
1038
|
+
"""Add IF EXISTS clause."""
|
|
1039
|
+
self._if_exists = True
|
|
1040
|
+
return self
|
|
1041
|
+
|
|
1042
|
+
def add_column(
|
|
1043
|
+
self,
|
|
1044
|
+
name: str,
|
|
1045
|
+
dtype: str,
|
|
1046
|
+
default: "Optional[Any]" = None,
|
|
1047
|
+
not_null: bool = False,
|
|
1048
|
+
unique: bool = False,
|
|
1049
|
+
comment: "Optional[str]" = None,
|
|
1050
|
+
after: "Optional[str]" = None,
|
|
1051
|
+
first: bool = False,
|
|
1052
|
+
) -> "Self":
|
|
1053
|
+
"""Add a new column to the table."""
|
|
1054
|
+
if not name:
|
|
1055
|
+
self._raise_sql_builder_error("Column name must be a non-empty string")
|
|
1056
|
+
|
|
1057
|
+
if not dtype:
|
|
1058
|
+
self._raise_sql_builder_error("Column type must be a non-empty string")
|
|
1059
|
+
|
|
1060
|
+
column_def = ColumnDefinition(
|
|
1061
|
+
name=name, dtype=dtype, default=default, not_null=not_null, unique=unique, comment=comment
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
operation = AlterOperation(
|
|
1065
|
+
operation_type="ADD COLUMN", column_definition=column_def, after_column=after, first=first
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
self._operations.append(operation)
|
|
1069
|
+
return self
|
|
1070
|
+
|
|
1071
|
+
def drop_column(self, name: str, cascade: bool = False) -> "Self":
|
|
1072
|
+
"""Drop a column from the table."""
|
|
1073
|
+
if not name:
|
|
1074
|
+
self._raise_sql_builder_error("Column name must be a non-empty string")
|
|
1075
|
+
|
|
1076
|
+
operation = AlterOperation(operation_type="DROP COLUMN CASCADE" if cascade else "DROP COLUMN", column_name=name)
|
|
1077
|
+
|
|
1078
|
+
self._operations.append(operation)
|
|
1079
|
+
return self
|
|
1080
|
+
|
|
1081
|
+
def alter_column_type(self, name: str, new_type: str, using: "Optional[str]" = None) -> "Self":
|
|
1082
|
+
"""Change the type of an existing column."""
|
|
1083
|
+
if not name:
|
|
1084
|
+
self._raise_sql_builder_error("Column name must be a non-empty string")
|
|
1085
|
+
|
|
1086
|
+
if not new_type:
|
|
1087
|
+
self._raise_sql_builder_error("New type must be a non-empty string")
|
|
1088
|
+
|
|
1089
|
+
operation = AlterOperation(
|
|
1090
|
+
operation_type="ALTER COLUMN TYPE", column_name=name, new_type=new_type, using_expression=using
|
|
1091
|
+
)
|
|
1092
|
+
|
|
1093
|
+
self._operations.append(operation)
|
|
1094
|
+
return self
|
|
1095
|
+
|
|
1096
|
+
def rename_column(self, old_name: str, new_name: str) -> "Self":
|
|
1097
|
+
"""Rename a column."""
|
|
1098
|
+
if not old_name:
|
|
1099
|
+
self._raise_sql_builder_error("Old column name must be a non-empty string")
|
|
1100
|
+
|
|
1101
|
+
if not new_name:
|
|
1102
|
+
self._raise_sql_builder_error("New column name must be a non-empty string")
|
|
1103
|
+
|
|
1104
|
+
operation = AlterOperation(operation_type="RENAME COLUMN", column_name=old_name, new_name=new_name)
|
|
1105
|
+
|
|
1106
|
+
self._operations.append(operation)
|
|
1107
|
+
return self
|
|
1108
|
+
|
|
1109
|
+
def add_constraint(
|
|
1110
|
+
self,
|
|
1111
|
+
constraint_type: str,
|
|
1112
|
+
columns: "Optional[Union[str, list[str]]]" = None,
|
|
1113
|
+
name: "Optional[str]" = None,
|
|
1114
|
+
references_table: "Optional[str]" = None,
|
|
1115
|
+
references_columns: "Optional[Union[str, list[str]]]" = None,
|
|
1116
|
+
condition: "Optional[Union[str, ColumnExpression]]" = None,
|
|
1117
|
+
on_delete: "Optional[str]" = None,
|
|
1118
|
+
on_update: "Optional[str]" = None,
|
|
1119
|
+
) -> "Self":
|
|
1120
|
+
"""Add a constraint to the table.
|
|
1121
|
+
|
|
1122
|
+
Args:
|
|
1123
|
+
constraint_type: Type of constraint ('PRIMARY KEY', 'FOREIGN KEY', 'UNIQUE', 'CHECK')
|
|
1124
|
+
columns: Column(s) for the constraint (not needed for CHECK)
|
|
1125
|
+
name: Optional constraint name
|
|
1126
|
+
references_table: Table referenced by foreign key
|
|
1127
|
+
references_columns: Columns referenced by foreign key
|
|
1128
|
+
condition: CHECK constraint condition
|
|
1129
|
+
on_delete: Foreign key ON DELETE action
|
|
1130
|
+
on_update: Foreign key ON UPDATE action
|
|
1131
|
+
"""
|
|
1132
|
+
valid_types = {"PRIMARY KEY", "FOREIGN KEY", "UNIQUE", "CHECK"}
|
|
1133
|
+
if constraint_type.upper() not in valid_types:
|
|
1134
|
+
self._raise_sql_builder_error(f"Invalid constraint type: {constraint_type}")
|
|
1135
|
+
|
|
1136
|
+
col_list = None
|
|
1137
|
+
if columns is not None:
|
|
1138
|
+
col_list = [columns] if isinstance(columns, str) else list(columns)
|
|
1139
|
+
|
|
1140
|
+
ref_col_list = None
|
|
1141
|
+
if references_columns is not None:
|
|
1142
|
+
ref_col_list = [references_columns] if isinstance(references_columns, str) else list(references_columns)
|
|
1143
|
+
|
|
1144
|
+
condition_str: Optional[str] = None
|
|
1145
|
+
if condition is not None:
|
|
1146
|
+
if hasattr(condition, "sqlglot_expression"):
|
|
1147
|
+
sqlglot_expr = getattr(condition, "sqlglot_expression", None)
|
|
1148
|
+
condition_str = sqlglot_expr.sql(dialect=self.dialect) if sqlglot_expr else str(condition)
|
|
1149
|
+
else:
|
|
1150
|
+
condition_str = str(condition)
|
|
1151
|
+
|
|
1152
|
+
constraint_def = ConstraintDefinition(
|
|
1153
|
+
constraint_type=constraint_type.upper(),
|
|
1154
|
+
name=name,
|
|
1155
|
+
columns=col_list or [],
|
|
1156
|
+
references_table=references_table,
|
|
1157
|
+
references_columns=ref_col_list or [],
|
|
1158
|
+
condition=condition_str,
|
|
1159
|
+
on_delete=on_delete,
|
|
1160
|
+
on_update=on_update,
|
|
1161
|
+
)
|
|
1162
|
+
|
|
1163
|
+
operation = AlterOperation(operation_type="ADD CONSTRAINT", constraint_definition=constraint_def)
|
|
1164
|
+
|
|
1165
|
+
self._operations.append(operation)
|
|
1166
|
+
return self
|
|
1167
|
+
|
|
1168
|
+
def drop_constraint(self, name: str, cascade: bool = False) -> "Self":
|
|
1169
|
+
"""Drop a constraint from the table."""
|
|
1170
|
+
if not name:
|
|
1171
|
+
self._raise_sql_builder_error("Constraint name must be a non-empty string")
|
|
1172
|
+
|
|
1173
|
+
operation = AlterOperation(
|
|
1174
|
+
operation_type="DROP CONSTRAINT CASCADE" if cascade else "DROP CONSTRAINT", constraint_name=name
|
|
1175
|
+
)
|
|
1176
|
+
|
|
1177
|
+
self._operations.append(operation)
|
|
1178
|
+
return self
|
|
1179
|
+
|
|
1180
|
+
def set_not_null(self, column: str) -> "Self":
|
|
1181
|
+
"""Set a column to NOT NULL."""
|
|
1182
|
+
operation = AlterOperation(operation_type="ALTER COLUMN SET NOT NULL", column_name=column)
|
|
1183
|
+
|
|
1184
|
+
self._operations.append(operation)
|
|
1185
|
+
return self
|
|
1186
|
+
|
|
1187
|
+
def drop_not_null(self, column: str) -> "Self":
|
|
1188
|
+
"""Remove NOT NULL constraint from a column."""
|
|
1189
|
+
operation = AlterOperation(operation_type="ALTER COLUMN DROP NOT NULL", column_name=column)
|
|
1190
|
+
|
|
1191
|
+
self._operations.append(operation)
|
|
1192
|
+
return self
|
|
1193
|
+
|
|
1194
|
+
def _create_base_expression(self) -> "exp.Expression":
|
|
1195
|
+
"""Create the SQLGlot expression for ALTER TABLE."""
|
|
1196
|
+
if not self._operations:
|
|
1197
|
+
self._raise_sql_builder_error("At least one operation must be specified for ALTER TABLE")
|
|
1198
|
+
|
|
1199
|
+
if self._schema:
|
|
1200
|
+
table = exp.Table(this=exp.to_identifier(self._table_name), db=exp.to_identifier(self._schema))
|
|
1201
|
+
else:
|
|
1202
|
+
table = exp.to_table(self._table_name)
|
|
1203
|
+
|
|
1204
|
+
actions: list[exp.Expression] = [self._build_operation_expression(op) for op in self._operations]
|
|
1205
|
+
|
|
1206
|
+
return exp.Alter(this=table, kind="TABLE", actions=actions, exists=self._if_exists)
|
|
1207
|
+
|
|
1208
|
+
def _build_operation_expression(self, op: "AlterOperation") -> exp.Expression:
|
|
1209
|
+
"""Build a structured SQLGlot expression for a single alter operation."""
|
|
1210
|
+
op_type = op.operation_type.upper()
|
|
1211
|
+
|
|
1212
|
+
if op_type == "ADD COLUMN":
|
|
1213
|
+
if not op.column_definition:
|
|
1214
|
+
self._raise_sql_builder_error("Column definition required for ADD COLUMN")
|
|
1215
|
+
return build_column_expression(op.column_definition)
|
|
1216
|
+
|
|
1217
|
+
if op_type == "DROP COLUMN":
|
|
1218
|
+
return exp.Drop(this=exp.to_identifier(op.column_name), kind="COLUMN", exists=True)
|
|
1219
|
+
|
|
1220
|
+
if op_type == "DROP COLUMN CASCADE":
|
|
1221
|
+
return exp.Drop(this=exp.to_identifier(op.column_name), kind="COLUMN", cascade=True, exists=True)
|
|
1222
|
+
|
|
1223
|
+
if op_type == "ALTER COLUMN TYPE":
|
|
1224
|
+
if not op.new_type:
|
|
1225
|
+
self._raise_sql_builder_error("New type required for ALTER COLUMN TYPE")
|
|
1226
|
+
return exp.AlterColumn(
|
|
1227
|
+
this=exp.to_identifier(op.column_name),
|
|
1228
|
+
dtype=exp.DataType.build(op.new_type),
|
|
1229
|
+
using=exp.maybe_parse(op.using_expression) if op.using_expression else None,
|
|
1230
|
+
)
|
|
1231
|
+
|
|
1232
|
+
if op_type == "RENAME COLUMN":
|
|
1233
|
+
return exp.RenameColumn(this=exp.to_identifier(op.column_name), to=exp.to_identifier(op.new_name))
|
|
1234
|
+
|
|
1235
|
+
if op_type == "ADD CONSTRAINT":
|
|
1236
|
+
if not op.constraint_definition:
|
|
1237
|
+
self._raise_sql_builder_error("Constraint definition required for ADD CONSTRAINT")
|
|
1238
|
+
constraint_expr = build_constraint_expression(op.constraint_definition)
|
|
1239
|
+
return exp.AddConstraint(this=constraint_expr)
|
|
1240
|
+
|
|
1241
|
+
if op_type == "DROP CONSTRAINT":
|
|
1242
|
+
return exp.Drop(this=exp.to_identifier(op.constraint_name), kind="CONSTRAINT", exists=True)
|
|
1243
|
+
|
|
1244
|
+
if op_type == "DROP CONSTRAINT CASCADE":
|
|
1245
|
+
return exp.Drop(this=exp.to_identifier(op.constraint_name), kind="CONSTRAINT", cascade=True, exists=True)
|
|
1246
|
+
|
|
1247
|
+
if op_type == "ALTER COLUMN SET NOT NULL":
|
|
1248
|
+
return exp.AlterColumn(this=exp.to_identifier(op.column_name), allow_null=False)
|
|
1249
|
+
|
|
1250
|
+
if op_type == "ALTER COLUMN DROP NOT NULL":
|
|
1251
|
+
return exp.AlterColumn(this=exp.to_identifier(op.column_name), drop=True, allow_null=True)
|
|
1252
|
+
|
|
1253
|
+
if op_type == "ALTER COLUMN SET DEFAULT":
|
|
1254
|
+
if not op.column_definition or op.column_definition.default is None:
|
|
1255
|
+
self._raise_sql_builder_error("Default value required for SET DEFAULT")
|
|
1256
|
+
default_val = op.column_definition.default
|
|
1257
|
+
default_expr: Optional[exp.Expression]
|
|
1258
|
+
if isinstance(default_val, str):
|
|
1259
|
+
if default_val.upper() in {"CURRENT_TIMESTAMP", "CURRENT_DATE", "CURRENT_TIME"} or "(" in default_val:
|
|
1260
|
+
default_expr = exp.maybe_parse(default_val)
|
|
1261
|
+
else:
|
|
1262
|
+
default_expr = exp.convert(default_val)
|
|
1263
|
+
elif isinstance(default_val, (int, float)):
|
|
1264
|
+
default_expr = exp.convert(default_val)
|
|
1265
|
+
elif default_val is True:
|
|
1266
|
+
default_expr = exp.true()
|
|
1267
|
+
elif default_val is False:
|
|
1268
|
+
default_expr = exp.false()
|
|
1269
|
+
else:
|
|
1270
|
+
default_expr = exp.convert(str(default_val))
|
|
1271
|
+
return exp.AlterColumn(this=exp.to_identifier(op.column_name), default=default_expr)
|
|
1272
|
+
|
|
1273
|
+
if op_type == "ALTER COLUMN DROP DEFAULT":
|
|
1274
|
+
return exp.AlterColumn(this=exp.to_identifier(op.column_name), kind="DROP DEFAULT")
|
|
1275
|
+
|
|
1276
|
+
self._raise_sql_builder_error(f"Unknown operation type: {op.operation_type}")
|
|
1277
|
+
raise AssertionError
|
|
1278
|
+
|
|
1279
|
+
|
|
1280
|
+
@dataclass
|
|
1281
|
+
class CommentOn(DDLBuilder):
|
|
1282
|
+
"""Builder for COMMENT ON ... IS ... statements.
|
|
1283
|
+
|
|
1284
|
+
Supports COMMENT ON TABLE and COMMENT ON COLUMN.
|
|
1285
|
+
"""
|
|
1286
|
+
|
|
1287
|
+
_target_type: Optional[str] = None
|
|
1288
|
+
_table: Optional[str] = None
|
|
1289
|
+
_column: Optional[str] = None
|
|
1290
|
+
_comment: Optional[str] = None
|
|
1291
|
+
|
|
1292
|
+
def on_table(self, table: str) -> Self:
|
|
1293
|
+
self._target_type = "TABLE"
|
|
1294
|
+
self._table = table
|
|
1295
|
+
self._column = None
|
|
1296
|
+
return self
|
|
1297
|
+
|
|
1298
|
+
def on_column(self, table: str, column: str) -> Self:
|
|
1299
|
+
self._target_type = "COLUMN"
|
|
1300
|
+
self._table = table
|
|
1301
|
+
self._column = column
|
|
1302
|
+
return self
|
|
1303
|
+
|
|
1304
|
+
def is_(self, comment: str) -> Self:
|
|
1305
|
+
self._comment = comment
|
|
1306
|
+
return self
|
|
1307
|
+
|
|
1308
|
+
def _create_base_expression(self) -> exp.Expression:
|
|
1309
|
+
if self._target_type == "TABLE" and self._table and self._comment is not None:
|
|
1310
|
+
return exp.Comment(this=exp.to_table(self._table), kind="TABLE", expression=exp.convert(self._comment))
|
|
1311
|
+
if self._target_type == "COLUMN" and self._table and self._column and self._comment is not None:
|
|
1312
|
+
return exp.Comment(
|
|
1313
|
+
this=exp.Column(table=self._table, this=self._column),
|
|
1314
|
+
kind="COLUMN",
|
|
1315
|
+
expression=exp.convert(self._comment),
|
|
1316
|
+
)
|
|
1317
|
+
self._raise_sql_builder_error("Must specify target and comment for COMMENT ON statement.")
|
|
1318
|
+
raise AssertionError
|
|
1319
|
+
|
|
1320
|
+
|
|
1321
|
+
@dataclass
|
|
1322
|
+
class RenameTable(DDLBuilder):
|
|
1323
|
+
"""Builder for ALTER TABLE ... RENAME TO ... statements.
|
|
1324
|
+
|
|
1325
|
+
Supports renaming a table.
|
|
1326
|
+
"""
|
|
1327
|
+
|
|
1328
|
+
_old_name: Optional[str] = None
|
|
1329
|
+
_new_name: Optional[str] = None
|
|
1330
|
+
|
|
1331
|
+
def table(self, old_name: str) -> Self:
|
|
1332
|
+
self._old_name = old_name
|
|
1333
|
+
return self
|
|
1334
|
+
|
|
1335
|
+
def to(self, new_name: str) -> Self:
|
|
1336
|
+
self._new_name = new_name
|
|
1337
|
+
return self
|
|
1338
|
+
|
|
1339
|
+
def _create_base_expression(self) -> exp.Expression:
|
|
1340
|
+
if not self._old_name or not self._new_name:
|
|
1341
|
+
self._raise_sql_builder_error("Both old and new table names must be set for RENAME TABLE.")
|
|
1342
|
+
return exp.Alter(
|
|
1343
|
+
this=exp.to_table(self._old_name),
|
|
1344
|
+
kind="TABLE",
|
|
1345
|
+
actions=[exp.AlterRename(this=exp.to_identifier(self._new_name))],
|
|
1346
|
+
)
|