diff --git a/.changes/unreleased/Features-20231023-102225.yaml b/.changes/unreleased/Features-20231023-102225.yaml new file mode 100644 index 00000000..7bc05710 --- /dev/null +++ b/.changes/unreleased/Features-20231023-102225.yaml @@ -0,0 +1,6 @@ +kind: Features +body: Begin validating `SavedQuery` names +time: 2023-10-23T10:22:25.686081-07:00 +custom: + Author: QMalcolm + Issue: "180" diff --git a/dbt_semantic_interfaces/validations/unique_valid_name.py b/dbt_semantic_interfaces/validations/unique_valid_name.py index a74bf6f2..ab741caa 100644 --- a/dbt_semantic_interfaces/validations/unique_valid_name.py +++ b/dbt_semantic_interfaces/validations/unique_valid_name.py @@ -7,6 +7,7 @@ from dbt_semantic_interfaces.enum_extension import assert_values_exhausted from dbt_semantic_interfaces.protocols import ( Metric, + SavedQuery, SemanticManifest, SemanticManifestT, SemanticModel, @@ -28,6 +29,7 @@ ValidationContext, ValidationError, ValidationIssue, + ValidationIssueContext, validate_safely, ) @@ -172,7 +174,9 @@ def _validate_semantic_model_elements(semantic_model: SemanticModel) -> List[Val @validate_safely(whats_being_done="checking top level elements of a specific type have unique and valid names") def _validate_top_level_objects_of_type( object_context_tuples: Union[ - List[Tuple[SemanticModel, SemanticModelContext]], List[Tuple[Metric, MetricContext]] + List[Tuple[SemanticModel, SemanticModelContext]], + List[Tuple[Metric, MetricContext]], + List[Tuple[SavedQuery, ValidationIssueContext]], ], object_type: str, ) -> List[ValidationIssue]: @@ -232,6 +236,23 @@ def _validate_top_level_objects(semantic_manifest: SemanticManifest) -> List[Val ] issues.extend(UniqueAndValidNameRule._validate_top_level_objects_of_type(metric_context_tuples, "metric")) + if semantic_manifest.saved_queries: + # TODO: We should clean up this pattern of precompiling object contexts + saved_query_context_tuples = [ + ( + saved_query, + ValidationIssueContext( + file_context=FileContext.from_metadata(metadata=saved_query.metadata), + object_type="saved query", + object_name=saved_query.name, + ), + ) + for saved_query in semantic_manifest.saved_queries + ] + issues.extend( + UniqueAndValidNameRule._validate_top_level_objects_of_type(saved_query_context_tuples, "saved query") + ) + return issues @staticmethod diff --git a/dbt_semantic_interfaces/validations/validator_helpers.py b/dbt_semantic_interfaces/validations/validator_helpers.py index c5f1c39a..6ea60f48 100644 --- a/dbt_semantic_interfaces/validations/validator_helpers.py +++ b/dbt_semantic_interfaces/validations/validator_helpers.py @@ -176,6 +176,7 @@ def context_str(self) -> str: SemanticModelContext, SemanticModelElementContext, SavedQueryContext, + ValidationIssueContext, ] diff --git a/tests/validations/test_unique_valid_name.py b/tests/validations/test_unique_valid_name.py index 20c90be2..20dfa566 100644 --- a/tests/validations/test_unique_valid_name.py +++ b/tests/validations/test_unique_valid_name.py @@ -24,6 +24,7 @@ Top level elements include - Semantic Models - Metrics + - Saved Queries For each top level element type we test for - Name validity checking @@ -116,6 +117,43 @@ def test_top_level_metric_can_have_same_name_as_any_other_top_level_item( # noq ) +def test_saved_query_name_validity( # noqa: D + simple_semantic_manifest__with_primary_transforms: PydanticSemanticManifest, +): + validator = SemanticManifestValidator[PydanticSemanticManifest]( + [UniqueAndValidNameRule[PydanticSemanticManifest]()] + ) + + # Shouldn't raise an exception + validator.checked_validations(simple_semantic_manifest__with_primary_transforms) + + # Should raise an exception + copied_manifest = deepcopy(simple_semantic_manifest__with_primary_transforms) + saved_query = copied_manifest.saved_queries[0] + saved_query.name = f"@{saved_query.name}" + with pytest.raises( + SemanticManifestValidationException, + match=rf"Invalid name `{saved_query.name}", + ): + validator.checked_validations(copied_manifest) + + +def test_duplicate_saved_query_name( # noqa: D + simple_semantic_manifest__with_primary_transforms: PydanticSemanticManifest, +) -> None: + manifest = deepcopy(simple_semantic_manifest__with_primary_transforms) + duplicated_saved_query = manifest.saved_queries[0] + manifest.saved_queries.append(duplicated_saved_query) + with pytest.raises( + SemanticManifestValidationException, + match=rf"Can't use name `{duplicated_saved_query.name}` for a saved query when it was already used for " + "another saved query", + ): + SemanticManifestValidator[PydanticSemanticManifest]( + [UniqueAndValidNameRule[PydanticSemanticManifest]()] + ).checked_validations(manifest) + + """ Semantic Model Element Tests There are three types of semantic model elements