diff --git a/dbt/include/sqlserver/macros/adapters/catalog.sql b/dbt/include/sqlserver/macros/adapters/catalog.sql index ccb2f8ed5..1011e93c3 100644 --- a/dbt/include/sqlserver/macros/adapters/catalog.sql +++ b/dbt/include/sqlserver/macros/adapters/catalog.sql @@ -1,7 +1,7 @@ {% macro sqlserver__get_catalog(information_schemas, schemas) -%} {% set query_label = apply_label() %} {%- call statement('catalog', fetch_result=True) -%} - + {{ get_use_database_sql(information_schemas.database) }} with principals as ( select @@ -127,143 +127,151 @@ {% macro sqlserver__get_catalog_relations(information_schema, relations) -%} {% set query_label = apply_label() %} - {%- call statement('catalog', fetch_result=True) -%} + {%- set distinct_databases = relations | map(attribute='database') | unique | list -%} - with - principals as ( - select - name as principal_name, - principal_id as principal_id - from - sys.database_principals {{ information_schema_hints() }} - ), + {%- if distinct_databases | length == 1 -%} + {%- call statement('catalog', fetch_result=True) -%} + {{ get_use_database_sql(distinct_databases[0]) }} + with + principals as ( + select + name as principal_name, + principal_id as principal_id + from + sys.database_principals {{ information_schema_hints() }} + ), - schemas as ( - select - name as schema_name, - schema_id as schema_id, - principal_id as principal_id - from - sys.schemas {{ information_schema_hints() }} - ), + schemas as ( + select + name as schema_name, + schema_id as schema_id, + principal_id as principal_id + from + sys.schemas {{ information_schema_hints() }} + ), - tables as ( - select - object_id, - name as table_name, - schema_id as schema_id, - principal_id as principal_id, - 'BASE TABLE' as table_type - from - sys.tables {{ information_schema_hints() }} - ), + tables as ( + select + object_id, + name as table_name, + schema_id as schema_id, + principal_id as principal_id, + 'BASE TABLE' as table_type + from + sys.tables {{ information_schema_hints() }} + ), - tables_with_metadata as ( - select - object_id, - table_name, - schema_name, - coalesce(tables.principal_id, schemas.principal_id) as owner_principal_id, - table_type - from - tables - join schemas on tables.schema_id = schemas.schema_id - ), + tables_with_metadata as ( + select + object_id, + table_name, + schema_name, + coalesce(tables.principal_id, schemas.principal_id) as owner_principal_id, + table_type + from + tables + join schemas on tables.schema_id = schemas.schema_id + ), - views as ( - select - object_id, - name as table_name, - schema_id as schema_id, - principal_id as principal_id, - 'VIEW' as table_type - from - sys.views {{ information_schema_hints() }} - ), + views as ( + select + object_id, + name as table_name, + schema_id as schema_id, + principal_id as principal_id, + 'VIEW' as table_type + from + sys.views {{ information_schema_hints() }} + ), - views_with_metadata as ( - select - object_id, - table_name, - schema_name, - coalesce(views.principal_id, schemas.principal_id) as owner_principal_id, - table_type - from - views - join schemas on views.schema_id = schemas.schema_id - ), + views_with_metadata as ( + select + object_id, + table_name, + schema_name, + coalesce(views.principal_id, schemas.principal_id) as owner_principal_id, + table_type + from + views + join schemas on views.schema_id = schemas.schema_id + ), - tables_and_views as ( - select - object_id, - table_name, - schema_name, - principal_name, - table_type - from - tables_with_metadata - join principals on tables_with_metadata.owner_principal_id = principals.principal_id - union all - select - object_id, - table_name, - schema_name, - principal_name, - table_type - from - views_with_metadata - join principals on views_with_metadata.owner_principal_id = principals.principal_id - ), + tables_and_views as ( + select + object_id, + table_name, + schema_name, + principal_name, + table_type + from + tables_with_metadata + join principals on tables_with_metadata.owner_principal_id = principals.principal_id + union all + select + object_id, + table_name, + schema_name, + principal_name, + table_type + from + views_with_metadata + join principals on views_with_metadata.owner_principal_id = principals.principal_id + ), - cols as ( + cols as ( - select - c.object_id, - c.name as column_name, - c.column_id as column_index, - t.name as column_type - from sys.columns as c {{ information_schema_hints() }} - left join sys.types as t on c.system_type_id = t.system_type_id - ) + select + c.object_id, + c.name as column_name, + c.column_id as column_index, + t.name as column_type + from sys.columns as c {{ information_schema_hints() }} + left join sys.types as t on c.system_type_id = t.system_type_id + ) - select - DB_NAME() as table_database, - tv.schema_name as table_schema, - tv.table_name, - tv.table_type, - null as table_comment, - tv.principal_name as table_owner, - cols.column_name, - cols.column_index, - cols.column_type, - null as column_comment - from tables_and_views tv - join cols on tv.object_id = cols.object_id - where ( - {%- for relation in relations -%} - {% if relation.schema and relation.identifier %} - ( - upper(tv.schema_name) = upper('{{ relation.schema }}') - and upper(tv.table_name) = upper('{{ relation.identifier }}') - ) - {% elif relation.schema %} - ( - upper(tv.schema_name) = upper('{{ relation.schema }}') - ) - {% else %} - {% do exceptions.raise_compiler_error( - '`get_catalog_relations` requires a list of relations, each with a schema' - ) %} - {% endif %} + select + DB_NAME() as table_database, + tv.schema_name as table_schema, + tv.table_name, + tv.table_type, + null as table_comment, + tv.principal_name as table_owner, + cols.column_name, + cols.column_index, + cols.column_type, + null as column_comment + from tables_and_views tv + join cols on tv.object_id = cols.object_id + where ( + {%- for relation in relations -%} + {% if relation.schema and relation.identifier %} + ( + upper(tv.schema_name) = upper('{{ relation.schema }}') + and upper(tv.table_name) = upper('{{ relation.identifier }}') + ) + {% elif relation.schema %} + ( + upper(tv.schema_name) = upper('{{ relation.schema }}') + ) + {% else %} + {% do exceptions.raise_compiler_error( + '`get_catalog_relations` requires a list of relations, each with a schema' + ) %} + {% endif %} - {%- if not loop.last %} or {% endif -%} - {%- endfor -%} - ) + {%- if not loop.last %} or {% endif -%} + {%- endfor -%} + ) - order by column_index - {{ query_label }} - {%- endcall -%} + order by column_index + {{ query_label }} - {{ return(load_result('catalog').table) }} + {%- endcall -%} + {{ return(load_result('catalog').table) }} + {% else %} + {% do exceptions.raise_compiler_error( + '`get_catalog_relations` can catalog one database at a time' + ) %} + {% endif %} {%- endmacro %} diff --git a/tests/functional/adapter/dbt/test_catalog.py b/tests/functional/adapter/dbt/test_catalog.py index d5f42f2ab..da0924334 100644 --- a/tests/functional/adapter/dbt/test_catalog.py +++ b/tests/functional/adapter/dbt/test_catalog.py @@ -1,9 +1,12 @@ +import json +import os + import pytest from dbt.artifacts.schemas.catalog import CatalogArtifact from dbt.tests.adapter.catalog import files from dbt.tests.adapter.catalog.relation_types import CatalogRelationTypes -from dbt.tests.util import run_dbt +from dbt.tests.util import get_connection, run_dbt class TestRelationTypes(CatalogRelationTypes): @@ -51,3 +54,103 @@ def test_relation_types_populate_correctly( assert node_name in docs.nodes node = docs.nodes[node_name] assert node.metadata.type == relation_type + + +class TestCatalogAcrossDatabases: + SECONDARY_DATABASE = "secondary_db" + + @pytest.fixture(scope="class") + def project_config_update(self): + return { + "vars": { + "test_second_database": self.SECONDARY_DATABASE, + }, + } + + @pytest.fixture(scope="class") + def schema_yml(self): + return """ +version: 2 + +models: + - name: default_db_model + description: model in the profile/default database + columns: + - name: id + description: id column + + - name: other_db_model + description: model in a non-default database + columns: + - name: id + description: id column +""" + + @pytest.fixture(scope="class") + def models(self, schema_yml): + return { + "default_db_model.sql": "select 1 as id", + "other_db_model.sql": """ + {{ config(database=var('test_second_database')) }} + select 2 as id + """, + "schema.yml": schema_yml, + } + + def create_secondary_db(self, project): + create_sql = """ + DECLARE @col NVARCHAR(256) + SET @col = (SELECT CONVERT(varchar(256), SERVERPROPERTY('collation'))); + + IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = '{database}') + BEGIN + EXEC('CREATE DATABASE [{database}] COLLATE ' + @col) + END + """ + with get_connection(project.adapter): + project.adapter.execute( + create_sql.format(database=self.SECONDARY_DATABASE), + fetch=True, + ) + + def cleanup_secondary_database(self, project): + drop_sql = """ + USE [master] + + IF EXISTS (SELECT * FROM sys.databases WHERE name = '{database}') + BEGIN + ALTER DATABASE [{database}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE + DROP DATABASE [{database}] + END + """ + with get_connection(project.adapter): + project.adapter.execute( + drop_sql.format(database=self.SECONDARY_DATABASE), + fetch=True, + ) + + def test_docs_generate_includes_non_default_database(self, project): + self.create_secondary_db(project) + try: + run_dbt(["run"]) + run_dbt(["docs", "generate"]) + + catalog_path = os.path.join(project.project_root, "target", "catalog.json") + with open(catalog_path) as fp: + catalog = json.load(fp) + + nodes = catalog["nodes"] + + default_node = nodes["model.test.default_db_model"] + other_node = nodes["model.test.other_db_model"] + + assert default_node["metadata"]["name"] == "default_db_model" + assert other_node["metadata"]["name"] == "other_db_model" + + assert default_node["metadata"]["database"] != other_node["metadata"]["database"] + assert other_node["metadata"]["database"] == self.SECONDARY_DATABASE + + assert "id" in default_node["columns"] + assert "id" in other_node["columns"] + finally: + self.cleanup_secondary_database(project)