Skip to content

Commit 7c2f060

Browse files
committed
Comment registration support for MSSQL engine
Signed-off-by: Ujfalusi Sándor <ujfalusi.sandor@gmail.com>
1 parent da2e5fc commit 7c2f060

3 files changed

Lines changed: 88 additions & 3 deletions

File tree

docs/concepts/models/overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ This table lists each engine's support for `TABLE` and `VIEW` object comments:
184184
| DuckDB <=0.9 | N | N |
185185
| DuckDB >=0.10 | Y | Y |
186186
| MySQL | Y | Y |
187-
| MSSQL | N | N |
187+
| MSSQL | Y | Y |
188188
| Postgres | Y | Y |
189189
| GCP Postgres | Y | Y |
190190
| Redshift | Y | N |

sqlmesh/core/engine_adapter/mssql.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
from textwrap import dedent
56
import typing as t
67
import logging
78

@@ -53,8 +54,8 @@ class MSSQLEngineAdapter(
5354
SUPPORTS_TUPLE_IN = False
5455
SUPPORTS_MATERIALIZED_VIEWS = False
5556
CURRENT_CATALOG_EXPRESSION = exp.func("db_name")
56-
COMMENT_CREATION_TABLE = CommentCreationTable.UNSUPPORTED
57-
COMMENT_CREATION_VIEW = CommentCreationView.UNSUPPORTED
57+
COMMENT_CREATION_TABLE = CommentCreationTable.COMMENT_COMMAND_ONLY
58+
COMMENT_CREATION_VIEW = CommentCreationView.COMMENT_COMMAND_ONLY
5859
SUPPORTS_REPLACE_TABLE = False
5960
MAX_IDENTIFIER_LENGTH = 128
6061
SUPPORTS_QUERY_EXECUTION_TRACKING = True
@@ -457,3 +458,64 @@ def delete_from(self, table_name: TableName, where: t.Union[str, exp.Expr]) -> N
457458
)
458459

459460
return super().delete_from(table_name, where)
461+
462+
def _build_create_comment_column_exp(
463+
self, table: exp.Table, column_name: str, column_comment: str, table_kind: str = "TABLE"
464+
) -> exp.Comment | str:
465+
tsql_text = dedent(f"""
466+
SET NOCOUNT ON;
467+
468+
DECLARE @comment sql_variant = {exp.Literal.string(column_comment).sql(dialect=self.dialect) if column_comment is not None else "NULL"};
469+
DECLARE @property_name VARCHAR(128) = 'MS_Description';
470+
DECLARE @schema_name VARCHAR(128) = '{table.db if table.db else "dbo"}';
471+
DECLARE @object_name VARCHAR(128) = '{table.name}';
472+
DECLARE @object_kind VARCHAR(128) = '{table_kind}';
473+
DECLARE @column_name VARCHAR(128) = '{column_name}';
474+
DECLARE @existing sql_variant;
475+
476+
SELECT TOP 1 @existing = CAST(VALUE AS NVARCHAR) FROM fn_listextendedproperty(@property_name, 'schema', @schema_name, @object_kind, @object_name, 'column', @column_name);
477+
478+
IF @comment IS NULL
479+
BEGIN
480+
IF @existing IS NOT NULL
481+
EXEC sp_dropextendedproperty @property_name, 'schema', @schema_name, @object_kind, @object_name, 'column', @column_name;
482+
END
483+
ELSE
484+
BEGIN
485+
IF @existing IS NULL
486+
EXEC sp_addextendedproperty @property_name,@comment, 'schema', @schema_name, @object_kind, @object_name, 'column', @column_name;
487+
ELSE IF @existing != @comment
488+
EXEC sp_updateextendedproperty @property_name, @comment, 'schema', @schema_name, @object_kind, @object_name, 'column', @column_name;
489+
END
490+
""")
491+
return tsql_text
492+
493+
def _build_create_comment_table_exp(
494+
self, table: exp.Table, table_comment: str, table_kind: str
495+
) -> exp.Comment | str:
496+
tsql_text = dedent(f"""
497+
SET NOCOUNT ON;
498+
499+
DECLARE @comment sql_variant = {exp.Literal.string(table_comment).sql(dialect=self.dialect) if table_comment is not None else "NULL"};
500+
DECLARE @property_name VARCHAR(128) = 'MS_Description';
501+
DECLARE @schema_name VARCHAR(128) = '{table.db if table.db else "dbo"}';
502+
DECLARE @object_name VARCHAR(128) = '{table.name}';
503+
DECLARE @object_kind VARCHAR(128) = '{table_kind}';
504+
DECLARE @existing sql_variant;
505+
506+
SELECT TOP 1 @existing = CAST(VALUE AS NVARCHAR) FROM fn_listextendedproperty(@property_name, 'schema', @schema_name, @object_kind, @object_name, DEFAULT, DEFAULT);
507+
508+
IF @comment IS NULL
509+
BEGIN
510+
IF @existing IS NOT NULL
511+
EXEC sp_dropextendedproperty @property_name, 'schema', @schema_name, @object_kind, @object_name;
512+
END
513+
ELSE
514+
BEGIN
515+
IF @existing IS NULL
516+
EXEC sp_addextendedproperty @property_name,@comment, 'schema', @schema_name, @object_kind, @object_name;
517+
ELSE IF @existing != @comment
518+
EXEC sp_updateextendedproperty @property_name, @comment, 'schema', @schema_name, @object_kind, @object_name;
519+
END
520+
""")
521+
return tsql_text

tests/core/engine_adapter/test_mssql.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,3 +1002,26 @@ def python_scd2_model(context, **kwargs):
10021002
snapshot: Snapshot = make_snapshot(m)
10031003
assert snapshot.node.physical_properties == m.physical_properties
10041004
assert snapshot.node.physical_properties.get("mssql_merge_exists")
1005+
1006+
1007+
def test_comments(make_mocked_engine_adapter: t.Callable, mocker: MockerFixture):
1008+
adapter = make_mocked_engine_adapter(MSSQLEngineAdapter)
1009+
1010+
columns_to_types = {
1011+
"cola": exp.DataType.build("INT"),
1012+
"colb": exp.DataType.build("TEXT"),
1013+
}
1014+
adapter.create_table(
1015+
"test_table", columns_to_types, table_description="\\", column_descriptions={"cola": "\\"}
1016+
)
1017+
1018+
sql_calls = to_sql_calls(adapter)
1019+
assert sql_calls == [
1020+
"""IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'test_table') EXEC('CREATE TABLE [test_table] ([cola] INTEGER, [colb] VARCHAR(MAX))');""",
1021+
adapter._build_create_comment_table_exp(
1022+
exp.table_("test_table", quoted=True), "\\", "TABLE"
1023+
),
1024+
adapter._build_create_comment_column_exp(
1025+
exp.table_("test_table", quoted=True), "cola", "\\", "TABLE"
1026+
),
1027+
]

0 commit comments

Comments
 (0)