diff --git a/.changes/unreleased/Fixes-20240410-181741.yaml b/.changes/unreleased/Fixes-20240410-181741.yaml new file mode 100644 index 00000000000..66ec5e7d373 --- /dev/null +++ b/.changes/unreleased/Fixes-20240410-181741.yaml @@ -0,0 +1,6 @@ +kind: Fixes +body: Add NodeRelation to SavedQuery Export +time: 2024-04-10T18:17:41.42533+01:00 +custom: + Author: aranke + Issue: "9534" diff --git a/core/dbt/artifacts/resources/v1/saved_query.py b/core/dbt/artifacts/resources/v1/saved_query.py index cc24d8fddf4..5f0575d26a7 100644 --- a/core/dbt/artifacts/resources/v1/saved_query.py +++ b/core/dbt/artifacts/resources/v1/saved_query.py @@ -21,6 +21,7 @@ class ExportConfig(dbtClassMixin): export_as: ExportDestinationType schema_name: Optional[str] = None alias: Optional[str] = None + database: Optional[str] = None @dataclass diff --git a/core/dbt/parser/base.py b/core/dbt/parser/base.py index 61e34237e5c..e345a74183c 100644 --- a/core/dbt/parser/base.py +++ b/core/dbt/parser/base.py @@ -109,7 +109,7 @@ def __init__(self, config: RuntimeConfig, manifest: Manifest, component: str) -> self.component = component def __call__(self, parsed_node: Any, override: Optional[str]) -> None: - if parsed_node.package_name in self.package_updaters: + if getattr(parsed_node, "package_name", None) in self.package_updaters: new_value = self.package_updaters[parsed_node.package_name](override, parsed_node) else: new_value = self.default_updater(override, parsed_node) @@ -293,7 +293,7 @@ def update_parsed_node_relation_names( self._update_node_alias(parsed_node, config_dict.get("alias")) # Snapshot nodes use special "target_database" and "target_schema" fields for some reason - if parsed_node.resource_type == NodeType.Snapshot: + if getattr(parsed_node, "resource_type", None) == NodeType.Snapshot: if "target_database" in config_dict and config_dict["target_database"]: parsed_node.database = config_dict["target_database"] if "target_schema" in config_dict and config_dict["target_schema"]: @@ -452,7 +452,7 @@ def _update_node_relation_name(self, node: ManifestNode): # and TestNodes that store_failures. # TestNodes do not get a relation_name without store failures # because no schema is created. - if node.is_relational and not node.is_ephemeral_model: + if getattr(node, "is_relational", None) and not getattr(node, "is_ephemeral_model", None): adapter = get_adapter(self.root_project) relation_cls = adapter.Relation node.relation_name = str(relation_cls.create_from(self.root_project, node)) diff --git a/core/dbt/parser/schema_yaml_readers.py b/core/dbt/parser/schema_yaml_readers.py index 8b1a780a0c6..b7c047d01dd 100644 --- a/core/dbt/parser/schema_yaml_readers.py +++ b/core/dbt/parser/schema_yaml_readers.py @@ -764,6 +764,22 @@ def parse_saved_query(self, unparsed: UnparsedSavedQuery) -> None: group=config.group, ) + for export in parsed.exports: + self.schema_parser.update_parsed_node_relation_names(export, export.config.to_dict()) # type: ignore + + if not export.config.schema_name: + export.config.schema_name = getattr(export, "schema", None) + delattr(export, "schema") + + export.config.database = getattr(export, "database", None) or export.config.database + delattr(export, "database") + + if not export.config.alias: + export.config.alias = getattr(export, "alias", None) + delattr(export, "alias") + + delattr(export, "relation_name") + # Only add thes saved query if it's enabled, otherwise we track it with other diabled nodes if parsed.config.enabled: self.manifest.add_saved_query(self.yaml.file, parsed) diff --git a/tests/functional/saved_queries/fixtures.py b/tests/functional/saved_queries/fixtures.py index b2025ba208a..e938760a12e 100644 --- a/tests/functional/saved_queries/fixtures.py +++ b/tests/functional/saved_queries/fixtures.py @@ -26,6 +26,29 @@ schema: my_export_schema_name """ +saved_queries_with_defaults_yml = """ +version: 2 + +saved_queries: + - name: test_saved_query + description: "{{ doc('saved_query_description') }}" + label: Test Saved Query + query_params: + metrics: + - simple_metric + group_by: + - "Dimension('user__ds')" + where: + - "{{ Dimension('user__ds', 'DAY') }} <= now()" + - "{{ Dimension('user__ds', 'DAY') }} >= '2023-01-01'" + - "{{ Metric('txn_revenue', ['id']) }} > 1" + exports: + - name: my_export + config: + alias: my_export_alias + export_as: table +""" + saved_queries_with_diff_filters_yml = """ version: 2 diff --git a/tests/functional/saved_queries/test_configs.py b/tests/functional/saved_queries/test_configs.py index 4c55c54a9eb..ef63888441a 100644 --- a/tests/functional/saved_queries/test_configs.py +++ b/tests/functional/saved_queries/test_configs.py @@ -12,6 +12,7 @@ saved_query_with_extra_config_attributes_yml, saved_query_with_export_configs_defined_at_saved_query_level_yml, saved_query_without_export_configs_defined_yml, + saved_queries_with_defaults_yml, ) from tests.functional.semantic_models.fixtures import ( fct_revenue_sql, @@ -121,6 +122,33 @@ def test_extra_config_properties_dont_break_parsing(self, project): assert saved_query.exports[0].config.__dict__.get("my_random_config") is None +class TestExportConfigsWithDefaultProperties(BaseConfigProject): + @pytest.fixture(scope="class") + def models(self): + return { + "saved_queries.yml": saved_queries_with_defaults_yml, + "schema.yml": schema_yml, + "fct_revenue.sql": fct_revenue_sql, + "metricflow_time_spine.sql": metricflow_time_spine_sql, + "docs.md": saved_query_description, + } + + def test_default_properties(self, project): + runner = dbtTestRunner() + + # parse with default fixture project config + result = runner.invoke(["parse"]) + assert result.success + assert isinstance(result.result, Manifest) + assert len(result.result.saved_queries) == 1 + saved_query = result.result.saved_queries["saved_query.test.test_saved_query"] + assert len(saved_query.exports) == 1 + export = saved_query.exports[0] + assert export.config.alias == "my_export_alias" + assert export.config.schema_name == project.test_schema + assert export.config.database == project.database + + class TestInheritingExportConfigFromSavedQueryConfig(BaseConfigProject): @pytest.fixture(scope="class") def models(self): @@ -152,6 +180,7 @@ def test_export_config_inherits_from_saved_query(self, project): assert export1.config.export_as != saved_query.config.export_as assert export1.config.schema_name == "my_custom_export_schema" assert export1.config.schema_name != saved_query.config.schema + assert export1.config.database == project.database # assert Export `my_export` has its configs defined from the saved_query because they should take priority export2 = next( @@ -162,6 +191,7 @@ def test_export_config_inherits_from_saved_query(self, project): assert export2.config.export_as == saved_query.config.export_as assert export2.config.schema_name == "my_default_export_schema" assert export2.config.schema_name == saved_query.config.schema + assert export2.config.database == project.database class TestInheritingExportConfigsFromProject(BaseConfigProject):