Skip to content

Commit e4592a9

Browse files
authored
Fix: Add CASCADE modifier when dropping columns in Postgres / Redshift (#3427)
1 parent 943cc30 commit e4592a9

6 files changed

Lines changed: 77 additions & 2 deletions

File tree

sqlmesh/core/engine_adapter/postgres.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class PostgresEngineAdapter(
6161
exp.DataType.build("BPCHAR", dialect=DIALECT).this
6262
},
6363
},
64+
drop_cascade=True,
6465
)
6566

6667
def _fetch_native_df(

sqlmesh/core/engine_adapter/redshift.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class RedshiftEngineAdapter(
5858
exp.DataType.build("CHAR", dialect=DIALECT).this: 4096,
5959
exp.DataType.build("VARCHAR", dialect=DIALECT).this: 65535,
6060
},
61+
drop_cascade=True,
6162
)
6263
VARIABLE_LENGTH_DATA_TYPES = {
6364
"char",

sqlmesh/core/schema_diff.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ class TableAlterOperation(PydanticModel):
169169
expected_table_struct: exp.DataType
170170
add_position: t.Optional[TableAlterColumnPosition] = None
171171
current_type: t.Optional[exp.DataType] = None
172+
cascade: bool = False
172173

173174
@classmethod
174175
def add(
@@ -192,13 +193,15 @@ def drop(
192193
columns: t.Union[TableAlterColumn, t.List[TableAlterColumn]],
193194
expected_table_struct: t.Union[str, exp.DataType],
194195
column_type: t.Optional[t.Union[str, exp.DataType]] = None,
196+
cascade: bool = False,
195197
) -> TableAlterOperation:
196198
column_type = exp.DataType.build(column_type) if column_type else exp.DataType.build("INT")
197199
return cls(
198200
op=TableAlterOperationType.DROP,
199201
columns=ensure_list(columns),
200202
column_type=column_type,
201203
expected_table_struct=exp.DataType.build(expected_table_struct),
204+
cascade=cascade,
202205
)
203206

204207
@classmethod
@@ -274,7 +277,9 @@ def expression(
274277
return alter_table
275278
elif self.is_drop:
276279
alter_table = exp.Alter(this=exp.to_table(table_name), kind="TABLE")
277-
drop_column = exp.Drop(this=self.column(array_element_selector), kind="COLUMN")
280+
drop_column = exp.Drop(
281+
this=self.column(array_element_selector), kind="COLUMN", cascade=self.cascade
282+
)
278283
alter_table.set("actions", [drop_column])
279284
return alter_table
280285
else:
@@ -313,6 +318,7 @@ class SchemaDiffer(PydanticModel):
313318
into a FLOAT64 column just fine.
314319
support_coercing_compatible_types: Whether or not the engine for which the diff is being computed supports direct
315320
coercion of compatible types.
321+
drop_cascade: Whether to add CASCADE modifier when dropping a column.
316322
parameterized_type_defaults: Default values for parameterized data types. Dict key is a sqlglot exp.DataType.Type,
317323
but in the engine adapter specification we build it from the dialect string instead of specifying it directly.
318324
Example: `exp.DataType.build("STRING", dialect=DIALECT).this` instead of the underlying `exp.DataType.Type.TEXT`
@@ -336,6 +342,7 @@ class SchemaDiffer(PydanticModel):
336342
default_factory=dict, alias="coerceable_types"
337343
)
338344
support_coercing_compatible_types: bool = False
345+
drop_cascade: bool = False
339346
parameterized_type_defaults: t.Dict[
340347
exp.DataType.Type, t.List[t.Tuple[t.Union[int, float], ...]]
341348
] = {}
@@ -458,7 +465,9 @@ def _drop_operation(
458465
assert column_kwarg
459466
struct.expressions.pop(column_pos)
460467
operations.append(
461-
TableAlterOperation.drop(columns, root_struct.copy(), column_kwarg.args["kind"])
468+
TableAlterOperation.drop(
469+
columns, root_struct.copy(), column_kwarg.args["kind"], cascade=self.drop_cascade
470+
)
462471
)
463472
return operations
464473

tests/core/engine_adapter/test_postgres.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,23 @@ def test_merge_version_lt_15(
141141
'INSERT INTO "target" ("ID", "ts", "val") SELECT DISTINCT ON ("ID") "ID", "ts", "val" FROM "__temp_test_abcdefgh"',
142142
'DROP TABLE IF EXISTS "__temp_test_abcdefgh"',
143143
]
144+
145+
146+
def test_alter_table_drop_column_cascade(make_mocked_engine_adapter: t.Callable):
147+
adapter = make_mocked_engine_adapter(PostgresEngineAdapter)
148+
149+
current_table_name = "test_table"
150+
target_table_name = "target_table"
151+
152+
def table_columns(table_name: str) -> t.Dict[str, exp.DataType]:
153+
if table_name == current_table_name:
154+
return {"id": exp.DataType.build("int"), "test_column": exp.DataType.build("int")}
155+
else:
156+
return {"id": exp.DataType.build("int")}
157+
158+
adapter.columns = table_columns
159+
160+
adapter.alter_table(adapter.get_alter_expressions(current_table_name, target_table_name))
161+
assert to_sql_calls(adapter) == [
162+
'ALTER TABLE "test_table" DROP COLUMN "test_column" CASCADE',
163+
]

tests/core/engine_adapter/test_redshift.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,21 @@ def test_create_view(adapter: t.Callable):
304304
'DROP VIEW IF EXISTS "test_view" CASCADE',
305305
'CREATE VIEW "test_view" ("a", "b") AS SELECT "cola" FROM "table" WITH NO SCHEMA BINDING',
306306
]
307+
308+
309+
def test_alter_table_drop_column_cascade(adapter: t.Callable):
310+
current_table_name = "test_table"
311+
target_table_name = "target_table"
312+
313+
def table_columns(table_name: str) -> t.Dict[str, exp.DataType]:
314+
if table_name == current_table_name:
315+
return {"id": exp.DataType.build("int"), "test_column": exp.DataType.build("int")}
316+
else:
317+
return {"id": exp.DataType.build("int")}
318+
319+
adapter.columns = table_columns
320+
321+
adapter.alter_table(adapter.get_alter_expressions(current_table_name, target_table_name))
322+
assert to_sql_calls(adapter) == [
323+
'ALTER TABLE "test_table" DROP COLUMN "test_column" CASCADE',
324+
]

tests/core/test_schema_diff.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,32 @@ def test_schema_diff_calculate():
4545
]
4646

4747

48+
def test_schema_diff_drop_cascade():
49+
alter_expressions = SchemaDiffer(
50+
**{
51+
"support_positional_add": False,
52+
"support_nested_operations": False,
53+
"array_element_selector": "",
54+
"drop_cascade": True,
55+
}
56+
).compare_columns(
57+
"apply_to_table",
58+
{
59+
"id": exp.DataType.build("INT"),
60+
"name": exp.DataType.build("STRING"),
61+
"price": exp.DataType.build("DOUBLE"),
62+
},
63+
{
64+
"id": exp.DataType.build("INT"),
65+
"name": exp.DataType.build("STRING"),
66+
},
67+
)
68+
69+
assert [x.sql() for x in alter_expressions] == [
70+
"""ALTER TABLE apply_to_table DROP COLUMN price CASCADE"""
71+
]
72+
73+
4874
def test_schema_diff_calculate_type_transitions():
4975
alter_expressions = SchemaDiffer(
5076
**{

0 commit comments

Comments
 (0)