Skip to content

Commit

Permalink
Fix state:modified check for exports (#10565)
Browse files Browse the repository at this point in the history
  • Loading branch information
aliceliu authored Aug 23, 2024
1 parent bba020f commit 3c55806
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 7 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/Fixes-20240813-154235.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Fixes
body: Fix state:modified check for exports
time: 2024-08-13T15:42:35.471685-07:00
custom:
Author: aliceliu
Issue: "10138"
1 change: 1 addition & 0 deletions core/dbt/artifacts/resources/v1/saved_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class Export(dbtClassMixin):

name: str
config: ExportConfig
unrendered_config: Dict[str, str] = field(default_factory=dict)


@dataclass
Expand Down
11 changes: 5 additions & 6 deletions core/dbt/contracts/graph/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1579,13 +1579,12 @@ def same_exports(self, old: "SavedQuery") -> bool:

# exports should be in the same order, so we zip them for easy iteration
for old_export, new_export in zip(old.exports, self.exports):
if not (
old_export.name == new_export.name
and old_export.config.export_as == new_export.config.export_as
and old_export.config.schema_name == new_export.config.schema_name
and old_export.config.alias == new_export.config.alias
):
if not (old_export.name == new_export.name):
return False
keys = ["export_as", "schema", "alias"]
for key in keys:
if old_export.unrendered_config.get(key) != new_export.unrendered_config.get(key):
return False

return True

Expand Down
4 changes: 3 additions & 1 deletion core/dbt/parser/schema_yaml_readers.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,9 @@ def _get_export(
self, unparsed: UnparsedExport, saved_query_config: SavedQueryConfig
) -> Export:
return Export(
name=unparsed.name, config=self._get_export_config(unparsed.config, saved_query_config)
name=unparsed.name,
config=self._get_export_config(unparsed.config, saved_query_config),
unrendered_config=unparsed.config,
)

def _get_query_params(self, unparsed: UnparsedQueryParams) -> QueryParams:
Expand Down
18 changes: 18 additions & 0 deletions schemas/dbt/manifest/v12.json
Original file line number Diff line number Diff line change
Expand Up @@ -19164,6 +19164,15 @@
"required": [
"export_as"
]
},
"unrendered_config": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"propertyNames": {
"type": "string"
}
}
},
"additionalProperties": false,
Expand Down Expand Up @@ -20689,6 +20698,15 @@
"required": [
"export_as"
]
},
"unrendered_config": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"propertyNames": {
"type": "string"
}
}
},
"additionalProperties": false,
Expand Down
97 changes: 97 additions & 0 deletions tests/functional/saved_queries/test_saved_query_parsing.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import shutil
from copy import deepcopy
from typing import List

import pytest
Expand Down Expand Up @@ -36,6 +37,17 @@ def models(self):
"docs.md": saved_query_description,
}

@pytest.fixture(scope="class")
def other_schema(self, unique_schema):
return unique_schema + "_other"

@pytest.fixture(scope="class")
def profiles_config_update(self, dbt_profile_target, unique_schema, other_schema):
outputs = {"default": dbt_profile_target, "prod": deepcopy(dbt_profile_target)}
outputs["default"]["schema"] = unique_schema
outputs["prod"]["schema"] = other_schema
return {"test": {"outputs": outputs, "target": "default"}}

def copy_state(self):
if not os.path.exists("state"):
os.makedirs("state")
Expand All @@ -60,6 +72,11 @@ def test_semantic_model_parsing(self, project):
assert saved_query.exports[0].config.alias == "my_export_alias"
assert saved_query.exports[0].config.export_as == ExportDestinationType.TABLE
assert saved_query.exports[0].config.schema_name == "my_export_schema_name"
assert saved_query.exports[0].unrendered_config == {
"alias": "my_export_alias",
"export_as": "table",
"schema": "my_export_schema_name",
}

# Save state
self.copy_state()
Expand All @@ -86,6 +103,86 @@ def test_semantic_model_parsing(self, project):
results = run_dbt(["ls", "--select", "state:modified", "--state", "./state"])
assert len(results) == 1

def test_semantic_model_parsing_change_export(self, project, other_schema):
runner = dbtTestRunner()
result = runner.invoke(["parse", "--no-partial-parse"])
assert result.success
assert isinstance(result.result, Manifest)
manifest = result.result
assert len(manifest.saved_queries) == 1
saved_query = manifest.saved_queries["saved_query.test.test_saved_query"]
assert saved_query.name == "test_saved_query"
assert saved_query.exports[0].name == "my_export"

# Save state
self.copy_state()
# Nothing has changed, so no state:modified results
results = run_dbt(["ls", "--select", "state:modified", "--state", "./state"])
assert len(results) == 0

# Change export name
write_file(
saved_queries_yml.replace("name: my_export", "name: my_expor2"),
project.project_root,
"models",
"saved_queries.yml",
)
# State modified finds changed saved_query
results = run_dbt(["ls", "--select", "state:modified", "--state", "./state"])
assert len(results) == 1

# Change export schema
write_file(
saved_queries_yml.replace(
"schema: my_export_schema_name", "schema: my_export_schema_name2"
),
project.project_root,
"models",
"saved_queries.yml",
)
# State modified finds changed saved_query
results = run_dbt(["ls", "--select", "state:modified", "--state", "./state"])
assert len(results) == 1

def test_semantic_model_parsing_with_default_schema(self, project, other_schema):
write_file(
saved_queries_with_defaults_yml, project.project_root, "models", "saved_queries.yml"
)
runner = dbtTestRunner()
result = runner.invoke(["parse", "--no-partial-parse", "--target", "prod"])
assert result.success
assert isinstance(result.result, Manifest)
manifest = result.result
assert len(manifest.saved_queries) == 1
saved_query = manifest.saved_queries["saved_query.test.test_saved_query"]
assert saved_query.name == "test_saved_query"
assert len(saved_query.query_params.metrics) == 1
assert len(saved_query.query_params.group_by) == 1
assert len(saved_query.query_params.where.where_filters) == 3
assert len(saved_query.depends_on.nodes) == 1
assert saved_query.description == "My SavedQuery Description"
assert len(saved_query.exports) == 1
assert saved_query.exports[0].name == "my_export"
assert saved_query.exports[0].config.alias == "my_export_alias"
assert saved_query.exports[0].config.export_as == ExportDestinationType.TABLE
assert saved_query.exports[0].config.schema_name == other_schema
assert saved_query.exports[0].unrendered_config == {
"alias": "my_export_alias",
"export_as": "table",
}

# Save state
self.copy_state()
# Nothing has changed, so no state:modified results
results = run_dbt(
["ls", "--select", "state:modified", "--state", "./state", "--target", "prod"]
)
assert len(results) == 0

# There should also be no state:modified results when using the default schema
results = run_dbt(["ls", "--select", "state:modified", "--state", "./state"])
assert len(results) == 0

def test_saved_query_error(self, project):
error_schema_yml = saved_queries_yml.replace("simple_metric", "metric_not_found")
write_file(error_schema_yml, project.project_root, "models", "saved_queries.yml")
Expand Down

0 comments on commit 3c55806

Please sign in to comment.