From 2243127a8fcc5862e45cf044c63e982f6b38f863 Mon Sep 17 00:00:00 2001 From: Mike Alfare Date: Tue, 7 Mar 2023 19:58:35 -0500 Subject: [PATCH 1/8] added tests for `SnowflakeAdapter.get_relation` and `dbt_utils.is_relation` --- .../adapter/get_relation_tests/macros.py | 106 +++++++++++++ .../get_relation_tests/test_get_relation.py | 149 ++++++++++++++++++ 2 files changed, 255 insertions(+) create mode 100644 tests/functional/adapter/get_relation_tests/macros.py create mode 100644 tests/functional/adapter/get_relation_tests/test_get_relation.py diff --git a/tests/functional/adapter/get_relation_tests/macros.py b/tests/functional/adapter/get_relation_tests/macros.py new file mode 100644 index 000000000..c41b53d42 --- /dev/null +++ b/tests/functional/adapter/get_relation_tests/macros.py @@ -0,0 +1,106 @@ +# wrap `adapter.get_relation()` in a macro +MACRO_GET_RELATION = """ +{% macro get_relation(database, schema, identifier) -%} + {% set relation = adapter.get_relation( + database=database, + schema=schema, + identifier=identifier + ) -%} + + {{ return(relation) }} +{%- endmacro %} +""" + + +# mirrors dbt_utils._is_relation: https://github.com/dbt-labs/dbt-utils/blob/main/macros/jinja_helpers/_is_relation.sql +# instead of throwing a compiler error, return the value of the check in the if statement +MACRO_IS_RELATION = """ +{% macro is_relation(obj, macro) %} + {%- set if_condition = (obj is mapping and obj.get('metadata', {}).get('type', '').endswith('Relation')) -%} + {{ return(if_condition) }} +{% endmacro %} +""" + + +# test whether the relation is found using `get_relation` +# either way return all the pieces to the log by selecting them in the if block +TEST_GET_RELATION_FACT = """ +{% set relation = get_relation( + database=target.database, + schema=target.schema, + identifier='FACT' +) -%} + +{%- if relation is none -%} + select '{{ relation.path.database }}' as actual_database, + '{{ relation.path.schema }}' as actual_schema, + '{{ relation.path.identifier }}' as actual_table, + '{{ target.database }}' as expected_database, + '{{ target.schema }}' as expected_schema, + 'fact' as expected_table, + {{ is_relation(relation) }} as is_relation_results +{% else %} + select 1 where 1 = 0 +{% endif %} +""" +TEST_GET_RELATION_AD_HOC = """ +{% set relation = get_relation( + database=target.database, + schema=target.schema, + identifier='AD_HOC' +) -%} + +{%- if relation is none -%} + select '{{ relation.path.database }}' as actual_database, + '{{ relation.path.schema }}' as actual_schema, + '{{ relation.path.identifier }}' as actual_table, + '{{ target.database }}' as expected_database, + '{{ target.schema }}' as expected_schema, + 'fact' as expected_table, + {{ is_relation(relation) }} as is_relation_results +{% else %} + select 1 where 1 = 0 +{% endif %} +""" + + +# test whether the relation is found using `is_relation` +# either way return all the pieces to the log by selecting them in the if block +TEST_IS_RELATION_FACT = """ +{% set relation = adapter.Relation.create( + database=target.database, + schema=target.schema, + identifier='FACT' +) -%} + +{%- if not is_relation(relation) -%} + select '{{ relation.path.database }}' as actual_database, + '{{ relation.path.schema }}' as actual_schema, + '{{ relation.path.identifier }}' as actual_table, + '{{ target.database }}' as expected_database, + '{{ target.schema }}' as expected_schema, + 'fact' as expected_table, + {{ is_relation(relation) }} as is_relation_results +{% else %} + select 1 where 1 = 0 +{% endif %} +""" +TEST_IS_RELATION_AD_HOC = """ +{% set relation = adapter.Relation.create( + database=target.database, + schema=target.schema, + identifier='AD_HOC' +) -%} + +{%- if not is_relation(relation) -%} + select '{{ relation.path.database }}' as actual_database, + '{{ relation.path.schema }}' as actual_schema, + '{{ relation.path.identifier }}' as actual_table, + '{{ target.database }}' as expected_database, + '{{ target.schema }}' as expected_schema, + 'fact' as expected_table, + {{ is_relation(relation) }} as is_relation_results +{% else %} + select 1 where 1 = 0 +{% endif %} +""" diff --git a/tests/functional/adapter/get_relation_tests/test_get_relation.py b/tests/functional/adapter/get_relation_tests/test_get_relation.py new file mode 100644 index 000000000..c3283bb01 --- /dev/null +++ b/tests/functional/adapter/get_relation_tests/test_get_relation.py @@ -0,0 +1,149 @@ +from typing import Mapping + +import pytest + +from dbt.tests.util import run_dbt + +from dbt.adapters.snowflake.relation import SnowflakeRelation + +from tests.functional.adapter.get_relation_tests import macros + + +class GetRelationBase: + + @pytest.fixture(scope="class") + def project_config_update(self): + return { + "quoting": { + "database": True, + "schema": True, + "identifier": True, + } + } + + @pytest.fixture(scope="class") + def unique_schema(self, request, prefix) -> str: + test_file = request.module.__name__ + # We only want the last part of the name + test_file = test_file.split(".")[-1] + unique_schema = f"{prefix}_{test_file}" + # SnowflakeAdapter._match_kwargs() defaults to upper() whereas BaseAdapter defaults to lower() + return unique_schema.upper() + + @pytest.fixture(scope="class") + def models(self): + """ + dummy model to be checked for existence by `is_relation` and to be returned by `get_relation` + """ + return {"FACT.sql": "select 1 as my_column"} + + @pytest.fixture(scope="class", autouse=True) + def setup_class(self, project): + run_dbt(["run"]) + + @pytest.fixture(scope="function") + def ad_hoc_table(self, project): + """ + Manually create a table in the test schema, not using a model + + This behaves differently from a table created via a model for `get_relation` + """ + source_table = "FACT" + test_table = "AD_HOC" + + create_sql = f""" + create or replace table {project.test_schema}.{test_table} as + select * from {project.test_schema}.{source_table} + """ + verify_sql = f"select * from {project.test_schema}.{test_table}" + drop_sql = f"drop table if exists {project.test_schema}.{test_table}" + + project.run_sql(create_sql) + project.run_sql(verify_sql) + yield + project.run_sql(drop_sql) + + +class TestGetRelationPython(GetRelationBase): + + @pytest.mark.parametrize("table_name,expected_result", [ + ("FACT", True), + ("AD_HOC", True), + ]) + def test_table_exists_with_standard_sql(self, project, ad_hoc_table, table_name, expected_result): + """ + assert the table exists by trying to query it + + This basically tests the setup and fixtures + """ + verify_sql = f"select * from {project.test_schema}.{table_name}" + records = project.run_sql(verify_sql, fetch='one') + table_exists = (len(records) > 0) + assert table_exists == expected_result + + @pytest.mark.parametrize("table_name,expected_result", [ + ("FACT", True), # this exists and should be returned + ("AD_HOC", False), # this exists but should not be returned + ]) + def test_table_exists_with_get_relation(self, project, ad_hoc_table, table_name, expected_result): + """ + assert the table exists using `get_relation` + + `get_relation` will return a relation if the relation exists as a model + `get_relation` will return None if the relation does not exist as a model, even if it exists as a table/view + """ + get_relation = project.adapter.get_relation( + database=project.database, + schema=project.test_schema, + identifier=table_name + ) + get_relation_returned_something = get_relation is not None + assert get_relation_returned_something is expected_result + + @pytest.mark.parametrize("table_name,expected_result", [ + ("FACT", True), + ("AD_HOC", True), + ]) + def test_table_exists_with_is_relation(self, project, ad_hoc_table, table_name, expected_result): + """ + assert the table exists using `is_relation` + + `is_relation` will return True as long as `relation` is an instance of `BaseRelation` + """ + relation = SnowflakeRelation.create( + database=project.database, + schema=project.test_schema, + identifier=table_name + ) + + is_relation = ( + isinstance(relation, Mapping) and + relation.get("metadata", "").get("type", "").endswith("Relation") + ) + assert is_relation == expected_result + + +class TestGetRelationDBT(GetRelationBase): + + @pytest.fixture(scope="class") + def macros(self): + return { + "get_relation.sql": macros.MACRO_GET_RELATION, + "is_relation.sql": macros.MACRO_IS_RELATION, + } + + @pytest.fixture(scope="class") + def tests(self): + """ + These tests mirror `test_table_exists_with_get_relation` and `test_table_exists_with_is_relation` + on `TestGetRelationPython` for `FACT` and `AD_HOC` + """ + return { + "test_get_relation_fact.sql": macros.TEST_GET_RELATION_FACT, + "test_get_relation_ad_hoc.sql": macros.TEST_GET_RELATION_AD_HOC, + "test_is_relation_fact.sql": macros.TEST_IS_RELATION_FACT, + "test_is_relation_ad_hoc.sql": macros.TEST_IS_RELATION_AD_HOC, + } + + def test_get_relation_dbt(self, project, ad_hoc_table): + run_dbt(["test"]) From 9ca9e6d915f6351054ae80b2a3759b6b76ca233b Mon Sep 17 00:00:00 2001 From: Mike Alfare Date: Tue, 7 Mar 2023 20:00:24 -0500 Subject: [PATCH 2/8] added changelog --- .changes/unreleased/Under the Hood-20230307-200000.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/unreleased/Under the Hood-20230307-200000.yaml diff --git a/.changes/unreleased/Under the Hood-20230307-200000.yaml b/.changes/unreleased/Under the Hood-20230307-200000.yaml new file mode 100644 index 000000000..0b094dfca --- /dev/null +++ b/.changes/unreleased/Under the Hood-20230307-200000.yaml @@ -0,0 +1,6 @@ +kind: Under the Hood +body: Added integration tests for `SnowflakeAdapter.get_relation` and `dbt_utils._is_relation` +time: 2023-03-07T20:00:00.913647-05:00 +custom: + Author: mikealfare + Issue: dbt-core/7024 From f3d738197c0cfc1f5c471bad800b37dbc63707c3 Mon Sep 17 00:00:00 2001 From: Mike Alfare Date: Thu, 9 Mar 2023 18:32:12 -0500 Subject: [PATCH 3/8] added an isolated version of @patkearns10's issue --- .../adapter/get_relation_tests/macros.py | 36 ++++++++++++---- .../get_relation_tests/test_get_relation.py | 43 +++++++++++++++++-- 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/tests/functional/adapter/get_relation_tests/macros.py b/tests/functional/adapter/get_relation_tests/macros.py index c41b53d42..ba70e7031 100644 --- a/tests/functional/adapter/get_relation_tests/macros.py +++ b/tests/functional/adapter/get_relation_tests/macros.py @@ -1,14 +1,14 @@ # wrap `adapter.get_relation()` in a macro MACRO_GET_RELATION = """ -{% macro get_relation(database, schema, identifier) -%} +{% macro get_relation(database, schema, identifier) %} {% set relation = adapter.get_relation( database=database, schema=schema, identifier=identifier - ) -%} + ) %} {{ return(relation) }} -{%- endmacro %} +{% endmacro %} """ @@ -16,12 +16,32 @@ # instead of throwing a compiler error, return the value of the check in the if statement MACRO_IS_RELATION = """ {% macro is_relation(obj, macro) %} - {%- set if_condition = (obj is mapping and obj.get('metadata', {}).get('type', '').endswith('Relation')) -%} + {% set if_condition = (obj is mapping and obj.get('metadata', {}).get('type', '').endswith('Relation')) %} {{ return(if_condition) }} {% endmacro %} """ +# combines the above two macros, but keeps the exception +MACRO_CHECK_GET_RELATION_IS_RELATION = """ +{% macro check_get_relation_is_relation() %} + + {% set my_relation = adapter.get_relation( + database=target.database, + schema=target.schema, + identifier="FACT" + ) %} + + {% if not (my_relation is mapping and my_relation.get('metadata', {}).get('type', '').endswith('Relation')) %} + {% do exceptions.raise_compiler_error("Log: " ~ my_relation) %} + {% endif %} + + {{ return(my_relation) }} + +{% endmacro %} +""" + + # test whether the relation is found using `get_relation` # either way return all the pieces to the log by selecting them in the if block TEST_GET_RELATION_FACT = """ @@ -31,7 +51,7 @@ identifier='FACT' ) -%} -{%- if relation is none -%} +{% if relation is none %} select '{{ relation.path.database }}' as actual_database, '{{ relation.path.schema }}' as actual_schema, '{{ relation.path.identifier }}' as actual_table, @@ -48,7 +68,7 @@ database=target.database, schema=target.schema, identifier='AD_HOC' -) -%} +) %} {%- if relation is none -%} select '{{ relation.path.database }}' as actual_database, @@ -71,7 +91,7 @@ database=target.database, schema=target.schema, identifier='FACT' -) -%} +) %} {%- if not is_relation(relation) -%} select '{{ relation.path.database }}' as actual_database, @@ -90,7 +110,7 @@ database=target.database, schema=target.schema, identifier='AD_HOC' -) -%} +) %} {%- if not is_relation(relation) -%} select '{{ relation.path.database }}' as actual_database, diff --git a/tests/functional/adapter/get_relation_tests/test_get_relation.py b/tests/functional/adapter/get_relation_tests/test_get_relation.py index c3283bb01..454f8aae3 100644 --- a/tests/functional/adapter/get_relation_tests/test_get_relation.py +++ b/tests/functional/adapter/get_relation_tests/test_get_relation.py @@ -9,15 +9,23 @@ from tests.functional.adapter.get_relation_tests import macros +_MODEL_FACT = "select 1 as my_column" + + +_MODEL_INVOKE_MACRO = """ +select '{{ check_get_relation_is_relation() }}' as this_should_return +""" + + class GetRelationBase: @pytest.fixture(scope="class") def project_config_update(self): return { "quoting": { - "database": True, - "schema": True, - "identifier": True, + "database": False, + "schema": False, + "identifier": False, } } @@ -35,7 +43,7 @@ def models(self): """ dummy model to be checked for existence by `is_relation` and to be returned by `get_relation` """ - return {"FACT.sql": "select 1 as my_column"} + return {"FACT.sql": _MODEL_FACT} @pytest.fixture(scope="class", autouse=True) def setup_class(self, project): @@ -147,3 +155,30 @@ def tests(self): def test_get_relation_dbt(self, project, ad_hoc_table): run_dbt(["test"]) + + +class TestGetRelationIsRelation(GetRelationBase): + + @pytest.fixture(scope="class") + def models(self): + """ + dummy model to be checked for existence by `is_relation` and to be returned by `get_relation` + model used to call the combination of `get_relation` and `is_relation` + """ + return { + "FACT.sql": _MODEL_FACT, + "INVOKE_MACRO.sql": _MODEL_INVOKE_MACRO, + } + + @pytest.fixture(scope="class") + def macros(self): + """ + a macro that combines `get_relation` and `is_relation` + """ + return {"check_get_relation_is_relation.sql": macros.MACRO_CHECK_GET_RELATION_IS_RELATION} + + def test_it_runs(self, project): + """ + The setup is the test + """ + assert True From 2e0376762d428d5942f7583d90a7ff4576a36235 Mon Sep 17 00:00:00 2001 From: Mike Alfare Date: Fri, 10 Mar 2023 19:59:19 -0500 Subject: [PATCH 4/8] simplified test case, can't reproduce --- .../adapter/get_relation_tests/macros.py | 100 +---------- .../get_relation_tests/test_get_relation.py | 159 ++---------------- 2 files changed, 23 insertions(+), 236 deletions(-) diff --git a/tests/functional/adapter/get_relation_tests/macros.py b/tests/functional/adapter/get_relation_tests/macros.py index ba70e7031..9e8f8e8d0 100644 --- a/tests/functional/adapter/get_relation_tests/macros.py +++ b/tests/functional/adapter/get_relation_tests/macros.py @@ -2,9 +2,9 @@ MACRO_GET_RELATION = """ {% macro get_relation(database, schema, identifier) %} {% set relation = adapter.get_relation( - database=database, - schema=schema, - identifier=identifier + database=target.database, + schema=target.schema, + identifier="FACT" ) %} {{ return(relation) }} @@ -15,7 +15,7 @@ # mirrors dbt_utils._is_relation: https://github.com/dbt-labs/dbt-utils/blob/main/macros/jinja_helpers/_is_relation.sql # instead of throwing a compiler error, return the value of the check in the if statement MACRO_IS_RELATION = """ -{% macro is_relation(obj, macro) %} +{% macro is_relation(obj) %} {% set if_condition = (obj is mapping and obj.get('metadata', {}).get('type', '').endswith('Relation')) %} {{ return(if_condition) }} {% endmacro %} @@ -26,101 +26,17 @@ MACRO_CHECK_GET_RELATION_IS_RELATION = """ {% macro check_get_relation_is_relation() %} - {% set my_relation = adapter.get_relation( + {% set relation = adapter.get_relation( database=target.database, schema=target.schema, identifier="FACT" ) %} - {% if not (my_relation is mapping and my_relation.get('metadata', {}).get('type', '').endswith('Relation')) %} - {% do exceptions.raise_compiler_error("Log: " ~ my_relation) %} + {% if not (relation is mapping and relation.get('metadata', {}).get('type', '').endswith('Relation')) %} + {% do exceptions.raise_compiler_error("Log: " ~ relation) %} {% endif %} - {{ return(my_relation) }} + {{ return(relation) }} {% endmacro %} """ - - -# test whether the relation is found using `get_relation` -# either way return all the pieces to the log by selecting them in the if block -TEST_GET_RELATION_FACT = """ -{% set relation = get_relation( - database=target.database, - schema=target.schema, - identifier='FACT' -) -%} - -{% if relation is none %} - select '{{ relation.path.database }}' as actual_database, - '{{ relation.path.schema }}' as actual_schema, - '{{ relation.path.identifier }}' as actual_table, - '{{ target.database }}' as expected_database, - '{{ target.schema }}' as expected_schema, - 'fact' as expected_table, - {{ is_relation(relation) }} as is_relation_results -{% else %} - select 1 where 1 = 0 -{% endif %} -""" -TEST_GET_RELATION_AD_HOC = """ -{% set relation = get_relation( - database=target.database, - schema=target.schema, - identifier='AD_HOC' -) %} - -{%- if relation is none -%} - select '{{ relation.path.database }}' as actual_database, - '{{ relation.path.schema }}' as actual_schema, - '{{ relation.path.identifier }}' as actual_table, - '{{ target.database }}' as expected_database, - '{{ target.schema }}' as expected_schema, - 'fact' as expected_table, - {{ is_relation(relation) }} as is_relation_results -{% else %} - select 1 where 1 = 0 -{% endif %} -""" - - -# test whether the relation is found using `is_relation` -# either way return all the pieces to the log by selecting them in the if block -TEST_IS_RELATION_FACT = """ -{% set relation = adapter.Relation.create( - database=target.database, - schema=target.schema, - identifier='FACT' -) %} - -{%- if not is_relation(relation) -%} - select '{{ relation.path.database }}' as actual_database, - '{{ relation.path.schema }}' as actual_schema, - '{{ relation.path.identifier }}' as actual_table, - '{{ target.database }}' as expected_database, - '{{ target.schema }}' as expected_schema, - 'fact' as expected_table, - {{ is_relation(relation) }} as is_relation_results -{% else %} - select 1 where 1 = 0 -{% endif %} -""" -TEST_IS_RELATION_AD_HOC = """ -{% set relation = adapter.Relation.create( - database=target.database, - schema=target.schema, - identifier='AD_HOC' -) %} - -{%- if not is_relation(relation) -%} - select '{{ relation.path.database }}' as actual_database, - '{{ relation.path.schema }}' as actual_schema, - '{{ relation.path.identifier }}' as actual_table, - '{{ target.database }}' as expected_database, - '{{ target.schema }}' as expected_schema, - 'fact' as expected_table, - {{ is_relation(relation) }} as is_relation_results -{% else %} - select 1 where 1 = 0 -{% endif %} -""" diff --git a/tests/functional/adapter/get_relation_tests/test_get_relation.py b/tests/functional/adapter/get_relation_tests/test_get_relation.py index 454f8aae3..ccac6e9a4 100644 --- a/tests/functional/adapter/get_relation_tests/test_get_relation.py +++ b/tests/functional/adapter/get_relation_tests/test_get_relation.py @@ -1,23 +1,11 @@ -from typing import Mapping - import pytest from dbt.tests.util import run_dbt -from dbt.adapters.snowflake.relation import SnowflakeRelation - from tests.functional.adapter.get_relation_tests import macros -_MODEL_FACT = "select 1 as my_column" - - -_MODEL_INVOKE_MACRO = """ -select '{{ check_get_relation_is_relation() }}' as this_should_return -""" - - -class GetRelationBase: +class TestGetRelation: @pytest.fixture(scope="class") def project_config_update(self): @@ -40,145 +28,28 @@ def unique_schema(self, request, prefix) -> str: @pytest.fixture(scope="class") def models(self): - """ - dummy model to be checked for existence by `is_relation` and to be returned by `get_relation` - """ - return {"FACT.sql": _MODEL_FACT} - - @pytest.fixture(scope="class", autouse=True) - def setup_class(self, project): - run_dbt(["run"]) - - @pytest.fixture(scope="function") - def ad_hoc_table(self, project): - """ - Manually create a table in the test schema, not using a model - - This behaves differently from a table created via a model for `get_relation` - """ - source_table = "FACT" - test_table = "AD_HOC" - - create_sql = f""" - create or replace table {project.test_schema}.{test_table} as - select * from {project.test_schema}.{source_table} - """ - verify_sql = f"select * from {project.test_schema}.{test_table}" - drop_sql = f"drop table if exists {project.test_schema}.{test_table}" - - project.run_sql(create_sql) - project.run_sql(verify_sql) - yield - project.run_sql(drop_sql) - - -class TestGetRelationPython(GetRelationBase): - - @pytest.mark.parametrize("table_name,expected_result", [ - ("FACT", True), - ("AD_HOC", True), - ]) - def test_table_exists_with_standard_sql(self, project, ad_hoc_table, table_name, expected_result): - """ - assert the table exists by trying to query it - - This basically tests the setup and fixtures - """ - verify_sql = f"select * from {project.test_schema}.{table_name}" - records = project.run_sql(verify_sql, fetch='one') - table_exists = (len(records) > 0) - assert table_exists == expected_result - - @pytest.mark.parametrize("table_name,expected_result", [ - ("FACT", True), # this exists and should be returned - ("AD_HOC", False), # this exists but should not be returned - ]) - def test_table_exists_with_get_relation(self, project, ad_hoc_table, table_name, expected_result): - """ - assert the table exists using `get_relation` - - `get_relation` will return a relation if the relation exists as a model - `get_relation` will return None if the relation does not exist as a model, even if it exists as a table/view - """ - get_relation = project.adapter.get_relation( - database=project.database, - schema=project.test_schema, - identifier=table_name - ) - get_relation_returned_something = get_relation is not None - assert get_relation_returned_something is expected_result - - @pytest.mark.parametrize("table_name,expected_result", [ - ("FACT", True), - ("AD_HOC", True), - ]) - def test_table_exists_with_is_relation(self, project, ad_hoc_table, table_name, expected_result): - """ - assert the table exists using `is_relation` - - `is_relation` will return True as long as `relation` is an instance of `BaseRelation` - """ - relation = SnowflakeRelation.create( - database=project.database, - schema=project.test_schema, - identifier=table_name - ) - - is_relation = ( - isinstance(relation, Mapping) and - relation.get("metadata", "").get("type", "").endswith("Relation") - ) - assert is_relation == expected_result - - -class TestGetRelationDBT(GetRelationBase): + return {"FACT.sql": "select 1 as my_column"} @pytest.fixture(scope="class") def macros(self): return { "get_relation.sql": macros.MACRO_GET_RELATION, "is_relation.sql": macros.MACRO_IS_RELATION, + "check_get_relation_is_relation.sql": macros.MACRO_CHECK_GET_RELATION_IS_RELATION, } - @pytest.fixture(scope="class") - def tests(self): - """ - These tests mirror `test_table_exists_with_get_relation` and `test_table_exists_with_is_relation` - on `TestGetRelationPython` for `FACT` and `AD_HOC` - """ - return { - "test_get_relation_fact.sql": macros.TEST_GET_RELATION_FACT, - "test_get_relation_ad_hoc.sql": macros.TEST_GET_RELATION_AD_HOC, - "test_is_relation_fact.sql": macros.TEST_IS_RELATION_FACT, - "test_is_relation_ad_hoc.sql": macros.TEST_IS_RELATION_AD_HOC, - } - - def test_get_relation_dbt(self, project, ad_hoc_table): - run_dbt(["test"]) - - -class TestGetRelationIsRelation(GetRelationBase): + @pytest.fixture(scope="class", autouse=True) + def setup_class(self, project): + run_dbt(["run"]) - @pytest.fixture(scope="class") - def models(self): - """ - dummy model to be checked for existence by `is_relation` and to be returned by `get_relation` - model used to call the combination of `get_relation` and `is_relation` - """ - return { - "FACT.sql": _MODEL_FACT, - "INVOKE_MACRO.sql": _MODEL_INVOKE_MACRO, - } + def test_get_relation(self, project): + relation = project.adapter.execute_macro("get_relation") + assert relation.get("metadata", {}).get("type") == "SnowflakeRelation" - @pytest.fixture(scope="class") - def macros(self): - """ - a macro that combines `get_relation` and `is_relation` - """ - return {"check_get_relation_is_relation.sql": macros.MACRO_CHECK_GET_RELATION_IS_RELATION} + def test_is_relation(self, project): + relation = project.adapter.execute_macro("get_relation") + assert project.adapter.execute_macro("is_relation", kwargs={"obj": relation}) is True - def test_it_runs(self, project): - """ - The setup is the test - """ - assert True + def test_check_get_relation_is_relation(self, project): + relation = project.adapter.execute_macro("check_get_relation_is_relation") + assert relation.get("metadata", {}).get("type") == "SnowflakeRelation" From b3ff01d29156473694549ecdfcd6de2c90831c97 Mon Sep 17 00:00:00 2001 From: Mike Alfare Date: Mon, 13 Mar 2023 17:02:26 -0400 Subject: [PATCH 5/8] simplified test case, can't reproduce --- .../adapter/get_relation_tests/macros.py | 22 ++-- .../get_relation_tests/test_get_relation.py | 113 +++++++++++++++--- 2 files changed, 107 insertions(+), 28 deletions(-) diff --git a/tests/functional/adapter/get_relation_tests/macros.py b/tests/functional/adapter/get_relation_tests/macros.py index 9e8f8e8d0..d9da9c8b0 100644 --- a/tests/functional/adapter/get_relation_tests/macros.py +++ b/tests/functional/adapter/get_relation_tests/macros.py @@ -2,9 +2,9 @@ MACRO_GET_RELATION = """ {% macro get_relation(database, schema, identifier) %} {% set relation = adapter.get_relation( - database=target.database, - schema=target.schema, - identifier="FACT" + database=database, + schema=schema, + identifier=identifier ) %} {{ return(relation) }} @@ -24,19 +24,15 @@ # combines the above two macros, but keeps the exception MACRO_CHECK_GET_RELATION_IS_RELATION = """ -{% macro check_get_relation_is_relation() %} +{% macro check_get_relation_is_relation(database, schema, identifier) %} - {% set relation = adapter.get_relation( - database=target.database, - schema=target.schema, - identifier="FACT" + {% set relation = get_relation( + database=database, + schema=schema, + identifier=identifier ) %} - {% if not (relation is mapping and relation.get('metadata', {}).get('type', '').endswith('Relation')) %} - {% do exceptions.raise_compiler_error("Log: " ~ relation) %} - {% endif %} - - {{ return(relation) }} + {{ return(is_relation(relation)) }} {% endmacro %} """ diff --git a/tests/functional/adapter/get_relation_tests/test_get_relation.py b/tests/functional/adapter/get_relation_tests/test_get_relation.py index ccac6e9a4..b91743d1c 100644 --- a/tests/functional/adapter/get_relation_tests/test_get_relation.py +++ b/tests/functional/adapter/get_relation_tests/test_get_relation.py @@ -5,7 +5,41 @@ from tests.functional.adapter.get_relation_tests import macros -class TestGetRelation: +_MODEL_FACT = "select 1 as my_column" + + +_MODEL_INVOKE_REF = """ +select + '{{ get_relation( + target.database, + target.schema, + "FACT" + ) }}' as get_relation, + {{ check_get_relation_is_relation( + target.database, + target.schema, + "FACT" + ) }} as is_relation +from {{ ref('FACT') }} +""" + + +_MODEL_INVOKE_NO_REF = """ +select + '{{ get_relation( + target.database, + target.schema, + "FACT" + ) }}' as get_relation, + {{ check_get_relation_is_relation( + target.database, + target.schema, + "FACT" + ) }} as is_relation +""" + + +class GetRelationBase: @pytest.fixture(scope="class") def project_config_update(self): @@ -26,10 +60,6 @@ def unique_schema(self, request, prefix) -> str: # SnowflakeAdapter._match_kwargs() defaults to upper() whereas BaseAdapter defaults to lower() return unique_schema.upper() - @pytest.fixture(scope="class") - def models(self): - return {"FACT.sql": "select 1 as my_column"} - @pytest.fixture(scope="class") def macros(self): return { @@ -39,17 +69,70 @@ def macros(self): } @pytest.fixture(scope="class", autouse=True) - def setup_class(self, project): - run_dbt(["run"]) + def setup(self, project): + run_dbt() - def test_get_relation(self, project): - relation = project.adapter.execute_macro("get_relation") - assert relation.get("metadata", {}).get("type") == "SnowflakeRelation" - def test_is_relation(self, project): - relation = project.adapter.execute_macro("get_relation") - assert project.adapter.execute_macro("is_relation", kwargs={"obj": relation}) is True +class TestGetRelationDirectCall(GetRelationBase): + + @pytest.fixture(scope="class") + def models(self): + return {"FACT.sql": _MODEL_FACT} + + @pytest.fixture(scope="function") + def dummy_model(self, project): + return { + "database": project.database, + "schema": project.test_schema, + "identifier": "FACT", + } + + @pytest.fixture(scope="function") + def dummy_relation(self, project, dummy_model): + return project.adapter.execute_macro("get_relation", kwargs=dummy_model) - def test_check_get_relation_is_relation(self, project): - relation = project.adapter.execute_macro("check_get_relation_is_relation") + def test_get_relation(self, project, dummy_model): + relation = project.adapter.execute_macro("get_relation", kwargs=dummy_model) assert relation.get("metadata", {}).get("type") == "SnowflakeRelation" + + def test_is_relation(self, project, dummy_relation): + assert project.adapter.execute_macro("is_relation", kwargs={"obj": dummy_relation}) + + def test_check_get_relation_is_relation(self, project, dummy_model): + assert project.adapter.execute_macro("check_get_relation_is_relation", kwargs=dummy_model) + + +class TestGetRelationModelCall(GetRelationBase): + + @pytest.fixture(scope="class") + def models(self): + return { + "FACT.sql": _MODEL_FACT, + "INVOKE_REF.sql": _MODEL_INVOKE_REF, + "INVOKE_NO_REF.sql": _MODEL_INVOKE_NO_REF, + } + + def test_get_relation_with_ref(self, project): + """ + When we include the ref statement in the model (even though we don't use anything from that relation), + the macro executes *after* the model is created, hence INVOKE_REF picks up the existence of FACT via + the `get_relation` macro. + """ + invoke_table = f"{project.database}.{project.test_schema}.INVOKE_REF" + fact_table = f"{project.database}.{project.test_schema}.FACT" + + results = project.run_sql(f"""select * from {invoke_table}""", fetch="all") + assert len(results) == 1 + assert results[0] == (fact_table, True) + + def test_get_relation_with_no_ref(self, project): + """ + When we don't include the ref statement in the model, the macro executes *before* the model is created, + hence INVOKE_NO_REF *doesn't* pick up the existence of FACT via the `get_relation` macro. + """ + invoke_table = f"{project.database}.{project.test_schema}.INVOKE_NO_REF" + fact_table = "None" + + results = project.run_sql(f"""select * from {invoke_table}""", fetch="all") + assert len(results) == 1 + assert results[0] == (fact_table, False) From ca4f1cd69a305ad755d3019c7f42c6a9ed2c0a13 Mon Sep 17 00:00:00 2001 From: Mike Alfare Date: Mon, 13 Mar 2023 17:32:26 -0400 Subject: [PATCH 6/8] added rerun of invoke model, found that original model stored with quotes --- .../adapter/get_relation_tests/test_get_relation.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/functional/adapter/get_relation_tests/test_get_relation.py b/tests/functional/adapter/get_relation_tests/test_get_relation.py index b91743d1c..c82c5d9ca 100644 --- a/tests/functional/adapter/get_relation_tests/test_get_relation.py +++ b/tests/functional/adapter/get_relation_tests/test_get_relation.py @@ -136,3 +136,11 @@ def test_get_relation_with_no_ref(self, project): results = project.run_sql(f"""select * from {invoke_table}""", fetch="all") assert len(results) == 1 assert results[0] == (fact_table, False) + + run_dbt(["run", "-s", "INVOKE_NO_REF"]) + # note the extra quotes, different from above + fact_table = f'"{project.database}"."{project.test_schema}"."FACT"' + + results = project.run_sql(f"""select * from {invoke_table}""", fetch="all") + assert len(results) == 1 + assert results[0] == (fact_table, True) From 6015bc08e601d7df1365405f407a2f864991dcd7 Mon Sep 17 00:00:00 2001 From: Mike Alfare Date: Mon, 13 Mar 2023 22:49:19 -0400 Subject: [PATCH 7/8] added back the model calling macros piece to isolate the parallel and idempotency issues --- .../adapter/get_relation_tests/macros.py | 34 +-- .../adapter/get_relation_tests/models.py | 12 + .../get_relation_tests/test_get_relation.py | 217 +++++++++++------- 3 files changed, 161 insertions(+), 102 deletions(-) create mode 100644 tests/functional/adapter/get_relation_tests/models.py diff --git a/tests/functional/adapter/get_relation_tests/macros.py b/tests/functional/adapter/get_relation_tests/macros.py index d9da9c8b0..eda93d556 100644 --- a/tests/functional/adapter/get_relation_tests/macros.py +++ b/tests/functional/adapter/get_relation_tests/macros.py @@ -1,38 +1,40 @@ # wrap `adapter.get_relation()` in a macro -MACRO_GET_RELATION = """ -{% macro get_relation(database, schema, identifier) %} +GET_RELATION = """ +{% macro get_relation() %} {% set relation = adapter.get_relation( - database=database, - schema=schema, - identifier=identifier + database=target.database, + schema="DBT_CORE_ISSUE_7024", + identifier="FACT" ) %} {{ return(relation) }} {% endmacro %} """ - # mirrors dbt_utils._is_relation: https://github.com/dbt-labs/dbt-utils/blob/main/macros/jinja_helpers/_is_relation.sql # instead of throwing a compiler error, return the value of the check in the if statement -MACRO_IS_RELATION = """ +IS_RELATION = """ {% macro is_relation(obj) %} {% set if_condition = (obj is mapping and obj.get('metadata', {}).get('type', '').endswith('Relation')) %} {{ return(if_condition) }} {% endmacro %} """ +# same as above, but throws exception +ASSERT_RELATION = """ +{% macro is_relation(obj) %} + {% set a_relation = (obj is mapping and obj.get('metadata', {}).get('type', '').endswith('Relation')) %} + {% if not a_relation %} + {% do exceptions.raise_compiler_error("Macro expected a Relation but received the value: " ~ obj) %} + {% endif %} + {{ return(a_relation) }} +{% endmacro %} +""" # combines the above two macros, but keeps the exception -MACRO_CHECK_GET_RELATION_IS_RELATION = """ +CHECK_GET_RELATION_IS_RELATION = """ {% macro check_get_relation_is_relation(database, schema, identifier) %} - - {% set relation = get_relation( - database=database, - schema=schema, - identifier=identifier - ) %} - + {% set relation = get_relation() %} {{ return(is_relation(relation)) }} - {% endmacro %} """ diff --git a/tests/functional/adapter/get_relation_tests/models.py b/tests/functional/adapter/get_relation_tests/models.py new file mode 100644 index 000000000..111f16b74 --- /dev/null +++ b/tests/functional/adapter/get_relation_tests/models.py @@ -0,0 +1,12 @@ +FACT = "select 1 as my_column" + + +INVOKE_IS_RELATION = """ +select + '{{ get_relation() }}' as get_relation, + {{ check_get_relation_is_relation() }} as is_relation +""" + + +# Purposely pointing out that the models are the same, except for the call to `ref()` +INVOKE_IS_RELATION_WITH_REF = INVOKE_IS_RELATION + "\nfrom {{ ref('FACT') }}" diff --git a/tests/functional/adapter/get_relation_tests/test_get_relation.py b/tests/functional/adapter/get_relation_tests/test_get_relation.py index c82c5d9ca..8a6d4e67b 100644 --- a/tests/functional/adapter/get_relation_tests/test_get_relation.py +++ b/tests/functional/adapter/get_relation_tests/test_get_relation.py @@ -1,48 +1,21 @@ +""" +This test suite is the result of GitHub issue #dbt-core/7024: https://github.com/dbt-labs/dbt-core/issues/7024 +""" import pytest from dbt.tests.util import run_dbt +from dbt.exceptions import CompilationError -from tests.functional.adapter.get_relation_tests import macros - - -_MODEL_FACT = "select 1 as my_column" - - -_MODEL_INVOKE_REF = """ -select - '{{ get_relation( - target.database, - target.schema, - "FACT" - ) }}' as get_relation, - {{ check_get_relation_is_relation( - target.database, - target.schema, - "FACT" - ) }} as is_relation -from {{ ref('FACT') }} -""" - - -_MODEL_INVOKE_NO_REF = """ -select - '{{ get_relation( - target.database, - target.schema, - "FACT" - ) }}' as get_relation, - {{ check_get_relation_is_relation( - target.database, - target.schema, - "FACT" - ) }} as is_relation -""" +from tests.functional.adapter.get_relation_tests import macros, models class GetRelationBase: @pytest.fixture(scope="class") def project_config_update(self): + """ + There was initial concern that the quote policy was to blame, though that is unlikely after investigation + """ return { "quoting": { "database": False, @@ -53,94 +26,166 @@ def project_config_update(self): @pytest.fixture(scope="class") def unique_schema(self, request, prefix) -> str: - test_file = request.module.__name__ - # We only want the last part of the name - test_file = test_file.split(".")[-1] - unique_schema = f"{prefix}_{test_file}" - # SnowflakeAdapter._match_kwargs() defaults to upper() whereas BaseAdapter defaults to lower() - return unique_schema.upper() + """ + The user specified the schema in their post, hence we hard code it here to use in fixtures. + This must match the macro `macros.GET_RELATION`. + """ + return "DBT_CORE_ISSUE_7024" + + +class TestGetRelationDirectCall(GetRelationBase): + """ + Loads only a dummy model and all macros. Everything is independent so it can all be checked before chaining. + """ + + @pytest.fixture(scope="class") + def models(self): + return {"FACT.sql": models.FACT} @pytest.fixture(scope="class") def macros(self): + + def is_to_assert(macro): + # alias the "assert_relation" macros so that they can be loaded alongside the "is_relation" macros + return macro.replace("is_relation", "assert_relation") + return { - "get_relation.sql": macros.MACRO_GET_RELATION, - "is_relation.sql": macros.MACRO_IS_RELATION, - "check_get_relation_is_relation.sql": macros.MACRO_CHECK_GET_RELATION_IS_RELATION, + "get_relation.sql": macros.GET_RELATION, + "is_relation.sql": macros.IS_RELATION, + "assert_relation.sql": is_to_assert(macros.IS_RELATION), + "check_get_relation_is_relation.sql": macros.CHECK_GET_RELATION_IS_RELATION, + "check_get_relation_assert_relation.sql": is_to_assert(macros.CHECK_GET_RELATION_IS_RELATION), } @pytest.fixture(scope="class", autouse=True) def setup(self, project): run_dbt() + @pytest.fixture(scope="class") + def dummy_relation(self, project): + return project.adapter.execute_macro("get_relation") -class TestGetRelationDirectCall(GetRelationBase): + def test_get_relation(self, project, dummy_relation): + assert dummy_relation.get("metadata", {}).get("type") == "SnowflakeRelation" + + def test_is_relation(self, project, dummy_relation): + assert project.adapter.execute_macro("is_relation", kwargs={"obj": dummy_relation}) + + def test_assert_relation(self, project, dummy_relation): + assert project.adapter.execute_macro("assert_relation", kwargs={"obj": dummy_relation}) + + def test_check_get_relation_is_relation(self, project): + assert project.adapter.execute_macro("check_get_relation_is_relation") + + def test_check_get_relation_assert_relation(self, project): + """ + This test case is the origin for this test module; however, it passes, hence the troubleshooting in the + remainder of the module. + """ + assert project.adapter.execute_macro("check_get_relation_assert_relation") + + +class GetRelationBaseWithModels(GetRelationBase): + """ + Loads all three models. The models are the same for both test classes, only `is_relation`/`assert_relation` + macro changes. Both are aliased as `is_relation`. + """ @pytest.fixture(scope="class") def models(self): - return {"FACT.sql": _MODEL_FACT} - - @pytest.fixture(scope="function") - def dummy_model(self, project): return { - "database": project.database, - "schema": project.test_schema, - "identifier": "FACT", + "FACT.sql": models.FACT, + "INVOKE_IS_RELATION.sql": models.INVOKE_IS_RELATION, + "INVOKE_IS_RELATION_WITH_REF.sql": models.INVOKE_IS_RELATION_WITH_REF, } - @pytest.fixture(scope="function") - def dummy_relation(self, project, dummy_model): - return project.adapter.execute_macro("get_relation", kwargs=dummy_model) + @staticmethod + def results_from_invoke_table(project, with_ref: bool): + invoke_table = f"{project.database}.DBT_CORE_ISSUE_7024.INVOKE_IS_RELATION" + if with_ref: + invoke_table += "_WITH_REF" + return project.run_sql(f"""select * from {invoke_table}""", fetch="all") - def test_get_relation(self, project, dummy_model): - relation = project.adapter.execute_macro("get_relation", kwargs=dummy_model) - assert relation.get("metadata", {}).get("type") == "SnowflakeRelation" - - def test_is_relation(self, project, dummy_relation): - assert project.adapter.execute_macro("is_relation", kwargs={"obj": dummy_relation}) + @pytest.fixture(scope="class") + def fact_table(self, project): + return f"{project.database}.DBT_CORE_ISSUE_7024.FACT" - def test_check_get_relation_is_relation(self, project, dummy_model): - assert project.adapter.execute_macro("check_get_relation_is_relation", kwargs=dummy_model) + @pytest.fixture(scope="class") + def quoted_fact_table(self, project): + return f'"{project.database}"."DBT_CORE_ISSUE_7024"."FACT"' -class TestGetRelationModelCall(GetRelationBase): +class TestGetRelationIsRelationModelCall(GetRelationBaseWithModels): + """ + Loads a version of `is_relation()` that doesn't throw an error when the check fails + """ @pytest.fixture(scope="class") - def models(self): + def macros(self): return { - "FACT.sql": _MODEL_FACT, - "INVOKE_REF.sql": _MODEL_INVOKE_REF, - "INVOKE_NO_REF.sql": _MODEL_INVOKE_NO_REF, + "get_relation.sql": macros.GET_RELATION, + "is_relation.sql": macros.IS_RELATION, # is_relation doesn't throw error + "check_get_relation_is_relation.sql": macros.CHECK_GET_RELATION_IS_RELATION, } - def test_get_relation_with_ref(self, project): + @pytest.fixture(scope="class", autouse=True) + def setup(self, project): + run_dbt() + + def test_get_relation_with_ref(self, project, fact_table): """ When we include the ref statement in the model (even though we don't use anything from that relation), the macro executes *after* the model is created, hence INVOKE_REF picks up the existence of FACT via the `get_relation` macro. """ - invoke_table = f"{project.database}.{project.test_schema}.INVOKE_REF" - fact_table = f"{project.database}.{project.test_schema}.FACT" + results = self.results_from_invoke_table(project, True) + assert results == [(fact_table, True)] - results = project.run_sql(f"""select * from {invoke_table}""", fetch="all") - assert len(results) == 1 - assert results[0] == (fact_table, True) + def test_get_relation_without_ref(self, project): + """ + When we don't include the ref statement in the model, the macro executes *before* the model is created, + hence INVOKE_NO_REF *doesn't* pick up the existence of FACT via the `get_relation` macro. + + Note: If this happens to run after `test_get_relation_without_ref_called_twice`, it will fail because + `FACT` will exist at that point. It's the same scenario as `test_get_relation_without_ref_called_twice` + because the `project` fixture is class-scoped. - def test_get_relation_with_no_ref(self, project): + It's worth calling out the above as it essentially means inserting macros in models that depend on other + models is effectively violating idempotency. + """ + results = self.results_from_invoke_table(project, False) + assert results == [("None", False)] + + def test_get_relation_without_ref_called_twice(self, project, quoted_fact_table): """ When we don't include the ref statement in the model, the macro executes *before* the model is created, hence INVOKE_NO_REF *doesn't* pick up the existence of FACT via the `get_relation` macro. + + However, running dbt a second time will then pick it up because now it exists. Though surprisingly, + it now returns a quoted relation name, unlike `test_get_relation_with_ref`. """ - invoke_table = f"{project.database}.{project.test_schema}.INVOKE_NO_REF" - fact_table = "None" + run_dbt() + results = self.results_from_invoke_table(project, False) + assert results == [(quoted_fact_table, True)] - results = project.run_sql(f"""select * from {invoke_table}""", fetch="all") - assert len(results) == 1 - assert results[0] == (fact_table, False) - run_dbt(["run", "-s", "INVOKE_NO_REF"]) - # note the extra quotes, different from above - fact_table = f'"{project.database}"."{project.test_schema}"."FACT"' +class TestGetRelationAssertRelationModelCall(GetRelationBaseWithModels): + """ + Loads a version of `is_relation()` that throws an error when the check fails + """ - results = project.run_sql(f"""select * from {invoke_table}""", fetch="all") - assert len(results) == 1 - assert results[0] == (fact_table, True) + @pytest.fixture(scope="class") + def macros(self): + return { + "get_relation.sql": macros.GET_RELATION, + "is_relation.sql": macros.ASSERT_RELATION, # assert_relation throws error + "check_get_relation_is_relation.sql": macros.CHECK_GET_RELATION_IS_RELATION, + } + + def test_cannot_run_dbt(self, project): + """ + If we include the version that throws an exception, we simply can't run dbt + """ + with pytest.raises(CompilationError) as exception_results: + run_dbt(expect_pass=False) + assert "Macro expected a Relation but received the value: None" in str(exception_results.value) From a7e6daa67cd3536cd758ba3900ba9193be497ee1 Mon Sep 17 00:00:00 2001 From: Mike Alfare Date: Mon, 13 Mar 2023 23:16:02 -0400 Subject: [PATCH 8/8] separated the single run and double run test cases to call out the different behaviors --- .../get_relation_tests/test_get_relation.py | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/tests/functional/adapter/get_relation_tests/test_get_relation.py b/tests/functional/adapter/get_relation_tests/test_get_relation.py index 8a6d4e67b..6514ffaf1 100644 --- a/tests/functional/adapter/get_relation_tests/test_get_relation.py +++ b/tests/functional/adapter/get_relation_tests/test_get_relation.py @@ -115,9 +115,11 @@ def quoted_fact_table(self, project): return f'"{project.database}"."DBT_CORE_ISSUE_7024"."FACT"' -class TestGetRelationIsRelationModelCall(GetRelationBaseWithModels): +class TestGetRelationIsRelationModelCallRunOnce(GetRelationBaseWithModels): """ Loads a version of `is_relation()` that doesn't throw an error when the check fails + + Only runs dbt once """ @pytest.fixture(scope="class") @@ -145,18 +147,41 @@ def test_get_relation_without_ref(self, project): """ When we don't include the ref statement in the model, the macro executes *before* the model is created, hence INVOKE_NO_REF *doesn't* pick up the existence of FACT via the `get_relation` macro. - - Note: If this happens to run after `test_get_relation_without_ref_called_twice`, it will fail because - `FACT` will exist at that point. It's the same scenario as `test_get_relation_without_ref_called_twice` - because the `project` fixture is class-scoped. - - It's worth calling out the above as it essentially means inserting macros in models that depend on other - models is effectively violating idempotency. """ results = self.results_from_invoke_table(project, False) assert results == [("None", False)] - def test_get_relation_without_ref_called_twice(self, project, quoted_fact_table): + +class TestGetRelationIsRelationModelCallRunTwice(GetRelationBaseWithModels): + """ + Loads a version of `is_relation()` that doesn't throw an error when the check fails + + Runs dbt twice (generates different behavior despite the same starting scenario + """ + + @pytest.fixture(scope="class") + def macros(self): + return { + "get_relation.sql": macros.GET_RELATION, + "is_relation.sql": macros.IS_RELATION, # is_relation doesn't throw error + "check_get_relation_is_relation.sql": macros.CHECK_GET_RELATION_IS_RELATION, + } + + @pytest.fixture(scope="class", autouse=True) + def setup(self, project): + run_dbt() + run_dbt() + + def test_get_relation_with_ref(self, project, quoted_fact_table): + """ + When we include the ref statement in the model (even though we don't use anything from that relation), + the macro executes *after* the model is created, hence INVOKE_REF picks up the existence of FACT via + the `get_relation` macro. + """ + results = self.results_from_invoke_table(project, True) + assert results == [(quoted_fact_table, True)] + + def test_get_relation_without_ref(self, project, quoted_fact_table): """ When we don't include the ref statement in the model, the macro executes *before* the model is created, hence INVOKE_NO_REF *doesn't* pick up the existence of FACT via the `get_relation` macro. @@ -164,7 +189,6 @@ def test_get_relation_without_ref_called_twice(self, project, quoted_fact_table) However, running dbt a second time will then pick it up because now it exists. Though surprisingly, it now returns a quoted relation name, unlike `test_get_relation_with_ref`. """ - run_dbt() results = self.results_from_invoke_table(project, False) assert results == [(quoted_fact_table, True)]