From 8e027a804dec236a35f18f0196c51bfcf5c9ac92 Mon Sep 17 00:00:00 2001 From: Alexey Semchenko Date: Sat, 17 Sep 2022 16:44:04 +0300 Subject: [PATCH 1/3] Add implementation of persist_docs using extending properties --- .../macros/adapters/persist_docs.sql | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 dbt/include/sqlserver/macros/adapters/persist_docs.sql diff --git a/dbt/include/sqlserver/macros/adapters/persist_docs.sql b/dbt/include/sqlserver/macros/adapters/persist_docs.sql new file mode 100644 index 000000000..ac7fc31af --- /dev/null +++ b/dbt/include/sqlserver/macros/adapters/persist_docs.sql @@ -0,0 +1,27 @@ +{% macro sqlserver__alter_column_comment(relation, column_dict) -%} + {%- set existing_columns = adapter.get_columns_in_relation(relation)|map(attribute="name")|list %} + {%- for column_name in column_dict if (column_name in existing_columns) %} + {{ log('Alter extended property "MS_Description" to "' ~ column_dict[column_name]['description'] ~ '" for ' ~ relation ~ ' column "' ~ column_name ~ '"') }} + if not exists ( + select 1 + from + sys.extended_properties as ep + inner join sys.all_columns as cols + on cols.object_id = ep.major_id + and cols.column_id = ep.minor_id + where + ep.major_id = object_id('{{ relation }}') + and ep.name = N'MS_Description' + and cols.name = N'{{ column_name }}' + ) + execute sp_addextendedproperty @name = N'MS_Description', @value = N'{{ column_dict[column_name]['description'] }}' + , @level0type = N'SCHEMA', @level0name = N'{{ relation.schema }}' + , @level1type = N'{{ relation.type }}', @level1name = N'{{ relation.identifier }}' + , @level2type = N'COLUMN', @level2name = N'{{ column_name }}'; + else + execute sp_updateextendedproperty @name = N'MS_Description', @value = N'{{ column_dict[column_name]['description'] }}' + , @level0type = N'SCHEMA', @level0name = N'{{ relation.schema }}' + , @level1type = N'{{ relation.type }}', @level1name = N'{{ relation.identifier }}' + , @level2type = N'COLUMN', @level2name = N'{{ column_name }}'; + {%- endfor %} +{%- endmacro %} From 81c8e221ec9c2d8675f3cab6d4c65277b3968c1d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 17 Sep 2022 13:47:27 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- dbt/include/sqlserver/macros/adapters/persist_docs.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbt/include/sqlserver/macros/adapters/persist_docs.sql b/dbt/include/sqlserver/macros/adapters/persist_docs.sql index ac7fc31af..38cd9b546 100644 --- a/dbt/include/sqlserver/macros/adapters/persist_docs.sql +++ b/dbt/include/sqlserver/macros/adapters/persist_docs.sql @@ -10,7 +10,7 @@ on cols.object_id = ep.major_id and cols.column_id = ep.minor_id where - ep.major_id = object_id('{{ relation }}') + ep.major_id = object_id('{{ relation }}') and ep.name = N'MS_Description' and cols.name = N'{{ column_name }}' ) From ef7b445926abf7c7c89bf40e5d8448a8e0d4e1b7 Mon Sep 17 00:00:00 2001 From: Axell Padilla <68310020+axellpadilla@users.noreply.github.com> Date: Sun, 31 May 2026 01:27:40 +0000 Subject: [PATCH 3/3] feat: implement relation and column comment persistence in SQL Server and fixed user type duplication --- .../sqlserver/macros/adapters/catalog.sql | 129 +++++++++----- .../macros/adapters/persist_docs.sql | 160 +++++++++++++++--- .../adapter/dbt/test_persist_docs.py | 6 - 3 files changed, 222 insertions(+), 73 deletions(-) diff --git a/dbt/include/sqlserver/macros/adapters/catalog.sql b/dbt/include/sqlserver/macros/adapters/catalog.sql index 1011e93c3..e5c6dbcc2 100644 --- a/dbt/include/sqlserver/macros/adapters/catalog.sql +++ b/dbt/include/sqlserver/macros/adapters/catalog.sql @@ -22,13 +22,19 @@ tables as ( select - object_id, - name as table_name, - schema_id as schema_id, - principal_id as principal_id, - 'BASE TABLE' as table_type + t.object_id, + t.name as table_name, + t.schema_id as schema_id, + t.principal_id as principal_id, + 'BASE TABLE' as table_type, + cast(ep.value as nvarchar(max)) as table_comment from - sys.tables {{ information_schema_hints() }} + sys.tables as t {{ information_schema_hints() }} + left join sys.extended_properties as ep {{ information_schema_hints() }} + on ep.class = 1 + and ep.major_id = t.object_id + and ep.minor_id = 0 + and ep.name = N'MS_Description' ), tables_with_metadata as ( @@ -37,7 +43,8 @@ table_name, schema_name, coalesce(tables.principal_id, schemas.principal_id) as owner_principal_id, - table_type + table_type, + table_comment from tables join schemas on tables.schema_id = schemas.schema_id @@ -45,13 +52,19 @@ views as ( select - object_id, - name as table_name, - schema_id as schema_id, - principal_id as principal_id, - 'VIEW' as table_type + v.object_id, + v.name as table_name, + v.schema_id as schema_id, + v.principal_id as principal_id, + 'VIEW' as table_type, + cast(ep.value as nvarchar(max)) as table_comment from - sys.views {{ information_schema_hints() }} + sys.views as v {{ information_schema_hints() }} + left join sys.extended_properties as ep {{ information_schema_hints() }} + on ep.class = 1 + and ep.major_id = v.object_id + and ep.minor_id = 0 + and ep.name = N'MS_Description' ), views_with_metadata as ( @@ -60,7 +73,8 @@ table_name, schema_name, coalesce(views.principal_id, schemas.principal_id) as owner_principal_id, - table_type + table_type, + table_comment from views join schemas on views.schema_id = schemas.schema_id @@ -72,7 +86,8 @@ table_name, schema_name, principal_name, - table_type + table_type, + table_comment from tables_with_metadata join principals on tables_with_metadata.owner_principal_id = principals.principal_id @@ -82,21 +97,29 @@ table_name, schema_name, principal_name, - table_type + table_type, + table_comment from views_with_metadata join principals on views_with_metadata.owner_principal_id = principals.principal_id ), cols as ( - select c.object_id, c.name as column_name, c.column_id as column_index, - t.name as column_type + t.name as column_type, + cast(ep.value as nvarchar(max)) as column_comment from sys.columns as c {{ information_schema_hints() }} - left join sys.types as t {{ information_schema_hints() }} on c.system_type_id = t.system_type_id + left join sys.types as t {{ information_schema_hints() }} + on c.system_type_id = t.system_type_id + and c.user_type_id = t.user_type_id + left join sys.extended_properties as ep {{ information_schema_hints() }} + on ep.class = 1 + and ep.major_id = c.object_id + and ep.minor_id = c.column_id + and ep.name = N'MS_Description' ) select @@ -104,12 +127,12 @@ tv.schema_name as table_schema, tv.table_name, tv.table_type, - null as table_comment, + tv.table_comment, tv.principal_name as table_owner, cols.column_name, cols.column_index, cols.column_type, - null as column_comment + cols.column_comment from tables_and_views tv join cols on tv.object_id = cols.object_id where ({%- for schema in schemas -%} @@ -152,13 +175,19 @@ tables as ( select - object_id, - name as table_name, - schema_id as schema_id, - principal_id as principal_id, - 'BASE TABLE' as table_type + t.object_id, + t.name as table_name, + t.schema_id as schema_id, + t.principal_id as principal_id, + 'BASE TABLE' as table_type, + cast(ep.value as nvarchar(max)) as table_comment from - sys.tables {{ information_schema_hints() }} + sys.tables as t {{ information_schema_hints() }} + left join sys.extended_properties as ep {{ information_schema_hints() }} + on ep.class = 1 + and ep.major_id = t.object_id + and ep.minor_id = 0 + and ep.name = N'MS_Description' ), tables_with_metadata as ( @@ -167,7 +196,8 @@ table_name, schema_name, coalesce(tables.principal_id, schemas.principal_id) as owner_principal_id, - table_type + table_type, + table_comment from tables join schemas on tables.schema_id = schemas.schema_id @@ -175,13 +205,19 @@ views as ( select - object_id, - name as table_name, - schema_id as schema_id, - principal_id as principal_id, - 'VIEW' as table_type + v.object_id, + v.name as table_name, + v.schema_id as schema_id, + v.principal_id as principal_id, + 'VIEW' as table_type, + cast(ep.value as nvarchar(max)) as table_comment from - sys.views {{ information_schema_hints() }} + sys.views as v {{ information_schema_hints() }} + left join sys.extended_properties as ep {{ information_schema_hints() }} + on ep.class = 1 + and ep.major_id = v.object_id + and ep.minor_id = 0 + and ep.name = N'MS_Description' ), views_with_metadata as ( @@ -190,7 +226,8 @@ table_name, schema_name, coalesce(views.principal_id, schemas.principal_id) as owner_principal_id, - table_type + table_type, + table_comment from views join schemas on views.schema_id = schemas.schema_id @@ -202,7 +239,8 @@ table_name, schema_name, principal_name, - table_type + table_type, + table_comment from tables_with_metadata join principals on tables_with_metadata.owner_principal_id = principals.principal_id @@ -212,21 +250,28 @@ table_name, schema_name, principal_name, - table_type + table_type, + table_comment from views_with_metadata join principals on views_with_metadata.owner_principal_id = principals.principal_id ), cols as ( - select c.object_id, c.name as column_name, c.column_id as column_index, - t.name as column_type + t.name as column_type, + cast(ep.value as nvarchar(max)) as column_comment from sys.columns as c {{ information_schema_hints() }} - left join sys.types as t on c.system_type_id = t.system_type_id + left join sys.types as t {{ information_schema_hints() }} + on c.user_type_id = t.user_type_id + left join sys.extended_properties as ep {{ information_schema_hints() }} + on ep.class = 1 + and ep.major_id = c.object_id + and ep.minor_id = c.column_id + and ep.name = N'MS_Description' ) select @@ -234,12 +279,12 @@ tv.schema_name as table_schema, tv.table_name, tv.table_type, - null as table_comment, + tv.table_comment, tv.principal_name as table_owner, cols.column_name, cols.column_index, cols.column_type, - null as column_comment + cols.column_comment from tables_and_views tv join cols on tv.object_id = cols.object_id where ( diff --git a/dbt/include/sqlserver/macros/adapters/persist_docs.sql b/dbt/include/sqlserver/macros/adapters/persist_docs.sql index 38cd9b546..5095eddf1 100644 --- a/dbt/include/sqlserver/macros/adapters/persist_docs.sql +++ b/dbt/include/sqlserver/macros/adapters/persist_docs.sql @@ -1,27 +1,137 @@ +{% macro sqlserver__alter_relation_comment(relation, relation_comment) -%} + {%- set escaped_comment = (relation_comment or '') | replace("'", "''") -%} + {%- set relation_name = relation.identifier | replace("'", "''") -%} + {%- set schema_name = relation.schema | replace("'", "''") -%} + + declare @relation_schema sysname = N'{{ schema_name }}'; + declare @relation_name sysname = N'{{ relation_name }}'; + declare @relation_comment nvarchar(3750) = N'{{ escaped_comment }}'; + declare @relation_type nvarchar(128); + + select + @relation_type = + case + when obj.[type] = 'V' then N'VIEW' + when obj.[type] = 'U' then N'TABLE' + end + from sys.objects as obj {{ information_schema_hints() }} + inner join sys.schemas as sch {{ information_schema_hints() }} + on sch.schema_id = obj.schema_id + where sch.name = @relation_schema + and obj.name = @relation_name + and obj.[type] in ('U', 'V'); + + if @relation_type is not null + begin + if exists ( + select 1 + from sys.extended_properties as ep {{ information_schema_hints() }} + inner join sys.objects as obj {{ information_schema_hints() }} + on obj.object_id = ep.major_id + inner join sys.schemas as sch {{ information_schema_hints() }} + on sch.schema_id = obj.schema_id + where ep.class = 1 + and ep.minor_id = 0 + and ep.name = N'MS_Description' + and sch.name = @relation_schema + and obj.name = @relation_name + and obj.[type] in ('U', 'V') + ) + begin + exec sys.sp_updateextendedproperty + @name = N'MS_Description', + @value = @relation_comment, + @level0type = N'SCHEMA', + @level0name = @relation_schema, + @level1type = @relation_type, + @level1name = @relation_name; + end + else + begin + exec sys.sp_addextendedproperty + @name = N'MS_Description', + @value = @relation_comment, + @level0type = N'SCHEMA', + @level0name = @relation_schema, + @level1type = @relation_type, + @level1name = @relation_name; + end + end; +{%- endmacro %} + + {% macro sqlserver__alter_column_comment(relation, column_dict) -%} - {%- set existing_columns = adapter.get_columns_in_relation(relation)|map(attribute="name")|list %} - {%- for column_name in column_dict if (column_name in existing_columns) %} - {{ log('Alter extended property "MS_Description" to "' ~ column_dict[column_name]['description'] ~ '" for ' ~ relation ~ ' column "' ~ column_name ~ '"') }} - if not exists ( - select 1 - from - sys.extended_properties as ep - inner join sys.all_columns as cols - on cols.object_id = ep.major_id - and cols.column_id = ep.minor_id - where - ep.major_id = object_id('{{ relation }}') - and ep.name = N'MS_Description' - and cols.name = N'{{ column_name }}' - ) - execute sp_addextendedproperty @name = N'MS_Description', @value = N'{{ column_dict[column_name]['description'] }}' - , @level0type = N'SCHEMA', @level0name = N'{{ relation.schema }}' - , @level1type = N'{{ relation.type }}', @level1name = N'{{ relation.identifier }}' - , @level2type = N'COLUMN', @level2name = N'{{ column_name }}'; - else - execute sp_updateextendedproperty @name = N'MS_Description', @value = N'{{ column_dict[column_name]['description'] }}' - , @level0type = N'SCHEMA', @level0name = N'{{ relation.schema }}' - , @level1type = N'{{ relation.type }}', @level1name = N'{{ relation.identifier }}' - , @level2type = N'COLUMN', @level2name = N'{{ column_name }}'; - {%- endfor %} + {%- set relation_name = relation.identifier | replace("'", "''") -%} + {%- set schema_name = relation.schema | replace("'", "''") -%} + + {%- for column_name, column_config in column_dict.items() %} + {%- set escaped_column_name = column_name | replace("'", "''") -%} + {%- set escaped_comment = (column_config.get('description') or '') | replace("'", "''") -%} + + declare @schema_{{ loop.index }} sysname = N'{{ schema_name }}'; + declare @relation_{{ loop.index }} sysname = N'{{ relation_name }}'; + declare @column_{{ loop.index }} sysname = N'{{ escaped_column_name }}'; + declare @comment_{{ loop.index }} nvarchar(3750) = N'{{ escaped_comment }}'; + declare @relation_type_{{ loop.index }} nvarchar(128); + + select + @relation_type_{{ loop.index }} = + case + when obj.[type] = 'V' then N'VIEW' + when obj.[type] = 'U' then N'TABLE' + end + from sys.objects as obj {{ information_schema_hints() }} + inner join sys.schemas as sch {{ information_schema_hints() }} + on sch.schema_id = obj.schema_id + inner join sys.columns as col {{ information_schema_hints() }} + on col.object_id = obj.object_id + where sch.name = @schema_{{ loop.index }} + and obj.name = @relation_{{ loop.index }} + and col.name = @column_{{ loop.index }} + and obj.[type] in ('U', 'V'); + + if @relation_type_{{ loop.index }} is not null + begin + if exists ( + select 1 + from sys.extended_properties as ep {{ information_schema_hints() }} + inner join sys.objects as obj {{ information_schema_hints() }} + on obj.object_id = ep.major_id + inner join sys.schemas as sch {{ information_schema_hints() }} + on sch.schema_id = obj.schema_id + inner join sys.columns as col {{ information_schema_hints() }} + on col.object_id = ep.major_id + and col.column_id = ep.minor_id + where ep.class = 1 + and ep.name = N'MS_Description' + and sch.name = @schema_{{ loop.index }} + and obj.name = @relation_{{ loop.index }} + and col.name = @column_{{ loop.index }} + and obj.[type] in ('U', 'V') + ) + begin + exec sys.sp_updateextendedproperty + @name = N'MS_Description', + @value = @comment_{{ loop.index }}, + @level0type = N'SCHEMA', + @level0name = @schema_{{ loop.index }}, + @level1type = @relation_type_{{ loop.index }}, + @level1name = @relation_{{ loop.index }}, + @level2type = N'COLUMN', + @level2name = @column_{{ loop.index }}; + end + else + begin + exec sys.sp_addextendedproperty + @name = N'MS_Description', + @value = @comment_{{ loop.index }}, + @level0type = N'SCHEMA', + @level0name = @schema_{{ loop.index }}, + @level1type = @relation_type_{{ loop.index }}, + @level1name = @relation_{{ loop.index }}, + @level2type = N'COLUMN', + @level2name = @column_{{ loop.index }}; + end + end; + {%- endfor %} {%- endmacro %} diff --git a/tests/functional/adapter/dbt/test_persist_docs.py b/tests/functional/adapter/dbt/test_persist_docs.py index 078010d6c..e9449cb9a 100644 --- a/tests/functional/adapter/dbt/test_persist_docs.py +++ b/tests/functional/adapter/dbt/test_persist_docs.py @@ -1,11 +1,5 @@ -import pytest - from dbt.tests.adapter.persist_docs.test_persist_docs import BasePersistDocs -@pytest.mark.skip(reason=""" - Persisted docs are not implemented in SQLServer. - Could be implemented with sp_addextendedproperty - """) class TestPersistDocs(BasePersistDocs): pass