diff --git a/sqlmesh/core/engine_adapter/mssql.py b/sqlmesh/core/engine_adapter/mssql.py index 70031de55a..11eee88e08 100644 --- a/sqlmesh/core/engine_adapter/mssql.py +++ b/sqlmesh/core/engine_adapter/mssql.py @@ -81,6 +81,18 @@ class MSSQLEngineAdapter( VARIABLE_LENGTH_DATA_TYPES = {"binary", "varbinary", "char", "varchar", "nchar", "nvarchar"} INSERT_OVERWRITE_STRATEGY = InsertOverwriteStrategy.MERGE + def normalize_value( + self, + expr: exp.Expr, + type: exp.DataType, + decimal_precision: int = 3, + timestamp_precision: int = RowDiffMixin.MAX_TIMESTAMP_PRECISION, + ) -> exp.Expr: + # T-SQL truncates CAST(x AS VARCHAR) to 30 characters when no length is given + normalized = super().normalize_value(expr, type, decimal_precision, timestamp_precision) + normalized.set("to", exp.DataType.build("VARCHAR(MAX)", dialect=self.dialect)) + return normalized + @property def catalog_support(self) -> CatalogSupport: # MSSQL and AzureSQL both use this engine adapter, but they differ in catalog support. diff --git a/tests/core/engine_adapter/test_mssql.py b/tests/core/engine_adapter/test_mssql.py index e54a42ff29..959a3d4390 100644 --- a/tests/core/engine_adapter/test_mssql.py +++ b/tests/core/engine_adapter/test_mssql.py @@ -1055,3 +1055,17 @@ def test_comments(make_mocked_engine_adapter: t.Callable, mocker: MockerFixture) adapter._build_create_comment_table_exp(table, comment), adapter._build_create_comment_column_exp(table, "a", comment), ] + + +def test_concat_columns_uses_varchar_max(adapter: MSSQLEngineAdapter): + # CAST(x AS VARCHAR) without a length truncates to 30 characters in T-SQL, which + # breaks the table_diff join key for long or UNIQUEIDENTIFIER key columns + columns_to_types = { + "id": exp.DataType.build("UNIQUEIDENTIFIER", dialect=adapter.dialect), + "name": exp.DataType.build("NVARCHAR(100)", dialect=adapter.dialect), + } + + assert ( + adapter.concat_columns(columns_to_types).sql(dialect=adapter.dialect) + == "COALESCE(CAST(id AS VARCHAR(MAX)), '') + ',' + COALESCE(CAST(name AS VARCHAR(MAX)), '')" + )