diff --git a/sqlmesh/core/engine_adapter/mssql.py b/sqlmesh/core/engine_adapter/mssql.py index fca6b4cc9f..8f24139d95 100644 --- a/sqlmesh/core/engine_adapter/mssql.py +++ b/sqlmesh/core/engine_adapter/mssql.py @@ -180,7 +180,10 @@ def drop_schema( **drop_args: t.Dict[str, exp.Expr], ) -> None: """ - MsSql doesn't support CASCADE clause and drops schemas unconditionally. + MsSql doesn't support CASCADE clause so objects are dropped individually. + + SQL Server also forbids dropping the built-in ``dbo`` schema (error 15150). + Objects inside it are still dropped when cascade=True, but the schema itself is left in place. """ if cascade: objects = self._get_data_objects(schema_name) @@ -199,6 +202,11 @@ def drop_schema( object_table, exists=ignore_if_not_exists, ) + + schema = schema_name.db if isinstance(schema_name, exp.Table) else schema_name + if schema.lower() == "dbo": + return + super().drop_schema(schema_name, ignore_if_not_exists=ignore_if_not_exists, cascade=False) def merge( diff --git a/tests/core/engine_adapter/test_mssql.py b/tests/core/engine_adapter/test_mssql.py index 007a59b365..86ec2b92b0 100644 --- a/tests/core/engine_adapter/test_mssql.py +++ b/tests/core/engine_adapter/test_mssql.py @@ -714,6 +714,26 @@ def test_drop_schema(make_mocked_engine_adapter: t.Callable): ] +def test_drop_schema_skips_dbo(make_mocked_engine_adapter: t.Callable): + adapter = make_mocked_engine_adapter(MSSQLEngineAdapter) + + adapter._get_data_objects = mock.Mock() + adapter._get_data_objects.return_value = [ + DataObject( + catalog="test_catalog", + schema="dbo", + name="test_view", + type=DataObjectType.from_str("VIEW"), + ) + ] + + adapter.drop_schema("dbo", cascade=True) + + sql_calls = to_sql_calls(adapter) + assert """DROP VIEW IF EXISTS [dbo].[test_view];""" in sql_calls + assert """DROP SCHEMA IF EXISTS [dbo];""" not in sql_calls + + def test_drop_schema_with_special_identifiers(make_mocked_engine_adapter: t.Callable): adapter = make_mocked_engine_adapter(MSSQLEngineAdapter)