Skip to content

Commit 6fe61bc

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

3 files changed

Lines changed: 102 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: 83 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,83 @@ 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_table_exp(
463+
self, table: exp.Table, table_comment: str, table_kind: str = "TABLE"
464+
) -> exp.Comment | str:
465+
template = dedent("""
466+
DECLARE @comment sql_variant = {comment};
467+
DECLARE @property_name VARCHAR(128) = 'MS_Description';
468+
DECLARE @schema_name VARCHAR(128) = {schema_name};
469+
DECLARE @object_name VARCHAR(128) = {object_name};
470+
DECLARE @object_kind VARCHAR(128) = '{object_kind}';
471+
DECLARE @existing sql_variant;
472+
473+
SELECT TOP 1 @existing = CAST(VALUE AS NVARCHAR) FROM fn_listextendedproperty(@property_name, 'schema', @schema_name, @object_kind, @object_name, DEFAULT, DEFAULT);
474+
475+
IF @comment IS NULL
476+
BEGIN
477+
IF @existing IS NOT NULL
478+
EXEC sp_dropextendedproperty @property_name, 'schema', @schema_name, @object_kind, @object_name;
479+
END
480+
ELSE
481+
BEGIN
482+
IF @existing IS NULL
483+
EXEC sp_addextendedproperty @property_name,@comment, 'schema', @schema_name, @object_kind, @object_name;
484+
ELSE IF @existing != @comment
485+
EXEC sp_updateextendedproperty @property_name, @comment, 'schema', @schema_name, @object_kind, @object_name;
486+
END
487+
""")
488+
tsql_text = template.format(
489+
comment=exp.Literal.string(table_comment or "NULL").sql(
490+
dialect=self.dialect, identify=False
491+
),
492+
schema_name=exp.Literal.string(table.db or "dbo").sql(
493+
dialect=self.dialect, identify=False
494+
),
495+
object_name=exp.Literal.string(table.name).sql(dialect=self.dialect, identify=False),
496+
object_kind=table_kind,
497+
)
498+
return tsql_text
499+
500+
def _build_create_comment_column_exp(
501+
self, table: exp.Table, column_name: str, column_comment: str, table_kind: str = "TABLE"
502+
) -> exp.Comment | str:
503+
template = dedent("""
504+
DECLARE @comment sql_variant = {comment};
505+
DECLARE @property_name VARCHAR(128) = 'MS_Description';
506+
DECLARE @schema_name VARCHAR(128) = {schema_name};
507+
DECLARE @object_name VARCHAR(128) = {object_name};
508+
DECLARE @object_kind VARCHAR(128) = '{object_kind}';
509+
DECLARE @column_name VARCHAR(128) = {column_name};
510+
DECLARE @existing sql_variant;
511+
512+
SELECT TOP 1 @existing = CAST(VALUE AS NVARCHAR) FROM fn_listextendedproperty(@property_name, 'schema', @schema_name, @object_kind, @object_name, 'column', @column_name);
513+
514+
IF @comment IS NULL
515+
BEGIN
516+
IF @existing IS NOT NULL
517+
EXEC sp_dropextendedproperty @property_name, 'schema', @schema_name, @object_kind, @object_name, 'column', @column_name;
518+
END
519+
ELSE
520+
BEGIN
521+
IF @existing IS NULL
522+
EXEC sp_addextendedproperty @property_name,@comment, 'schema', @schema_name, @object_kind, @object_name, 'column', @column_name;
523+
ELSE IF @existing != @comment
524+
EXEC sp_updateextendedproperty @property_name, @comment, 'schema', @schema_name, @object_kind, @object_name, 'column', @column_name;
525+
END
526+
""")
527+
528+
tsql_text = template.format(
529+
comment=exp.Literal.string(column_comment or "NULL").sql(
530+
dialect=self.dialect, identify=False
531+
),
532+
schema_name=exp.Literal.string(table.db or "dbo").sql(
533+
dialect=self.dialect, identify=False
534+
),
535+
object_name=exp.Literal.string(table.name).sql(dialect=self.dialect, identify=False),
536+
object_kind=table_kind,
537+
column_name=exp.Literal.string(column_name).sql(dialect=self.dialect, identify=False),
538+
)
539+
540+
return tsql_text

tests/core/engine_adapter/integration/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,14 @@ def get_table_comment(
526526
AND c.relkind = '{"v" if table_kind == "VIEW" else "r"}'
527527
;
528528
"""
529+
elif self.dialect == "tsql":
530+
kind = "table" if table_kind == "BASE TABLE" else "view"
531+
query = f"""
532+
SELECT
533+
ep.name,
534+
CAST(ep.value AS NVARCHAR(MAX)) comment
535+
FROM fn_listextendedproperty('MS_Description', 'schema', '{schema_name}', '{kind}', '{table_name}', DEFAULT, DEFAULT) ep
536+
"""
529537

530538
result = self.engine_adapter.fetchall(query)
531539

@@ -636,6 +644,16 @@ def get_column_comments(
636644
AND c.relkind = '{"v" if table_kind == "VIEW" else "r"}'
637645
;
638646
"""
647+
elif self.dialect == "tsql":
648+
kind = "table" if table_kind == "BASE TABLE" else "view"
649+
query = f"""
650+
SELECT
651+
col.COLUMN_NAME column_name,
652+
CAST(ep.value AS NVARCHAR(MAX)) comment
653+
FROM INFORMATION_SCHEMA.COLUMNS col
654+
CROSS APPLY fn_listextendedproperty('MS_Description', 'schema', col.TABLE_SCHEMA, '{kind}', col.TABLE_NAME, 'column', col.COLUMN_NAME) ep
655+
WHERE col.TABLE_SCHEMA = '{schema_name}' AND col.TABLE_NAME = '{table_name}'
656+
"""
639657

640658
result = self.engine_adapter.fetchall(query)
641659

0 commit comments

Comments
 (0)