diff --git a/dbt_semantic_interfaces/validations/saved_query.py b/dbt_semantic_interfaces/validations/saved_query.py index f66bba43..88161595 100644 --- a/dbt_semantic_interfaces/validations/saved_query.py +++ b/dbt_semantic_interfaces/validations/saved_query.py @@ -2,7 +2,10 @@ import traceback from typing import Generic, List, Sequence, Set -from dbt_semantic_interfaces.naming.dundered import DunderedNameFormatter +from dbt_semantic_interfaces.call_parameter_sets import FilterCallParameterSets +from dbt_semantic_interfaces.implementations.filters.where_filter import ( + PydanticWhereFilter, +) from dbt_semantic_interfaces.naming.keywords import METRIC_TIME_ELEMENT_NAME from dbt_semantic_interfaces.protocols import SemanticManifestT from dbt_semantic_interfaces.protocols.saved_query import SavedQuery @@ -35,16 +38,43 @@ class SavedQueryRule(SemanticManifestValidationRule[SemanticManifestT], Generic[ def _check_group_bys(valid_group_by_element_names: Set[str], saved_query: SavedQuery) -> Sequence[ValidationIssue]: issues: List[ValidationIssue] = [] - for group_by_item_name in saved_query.group_bys: - structured_name = DunderedNameFormatter.parse_name(group_by_item_name) - if structured_name.element_name not in valid_group_by_element_names: + for group_by_item in saved_query.group_bys: + # TODO: Replace with more appropriate abstractions once available. + where_filter = PydanticWhereFilter(where_sql_template="{{" + group_by_item + "}}") + parameter_sets: FilterCallParameterSets + try: + parameter_sets = where_filter.call_parameter_sets + except Exception as e: + issues.append( + generate_exception_issue( + what_was_being_done=f"trying to parse a group-by in saved query `{saved_query.name}`", + e=e, + context=SavedQueryContext( + file_context=FileContext.from_metadata(metadata=saved_query.metadata), + element_type=SavedQueryElementType.WHERE, + element_value=where_filter.where_sql_template, + ), + extras={ + "traceback": "".join(traceback.format_tb(e.__traceback__)), + }, + ) + ) + continue + + element_names_in_group_by = ( + [x.entity_reference.element_name for x in parameter_sets.entity_call_parameter_sets] + + [x.dimension_reference.element_name for x in parameter_sets.dimension_call_parameter_sets] + + [x.time_dimension_reference.element_name for x in parameter_sets.time_dimension_call_parameter_sets] + ) + + if len(element_names_in_group_by) != 1 or element_names_in_group_by[0] not in valid_group_by_element_names: issues.append( ValidationError( - message=f"`{group_by_item_name}` is not a valid group-by name.", + message=f"`{group_by_item}` is not a valid group-by name.", context=SavedQueryContext( file_context=FileContext.from_metadata(metadata=saved_query.metadata), element_type=SavedQueryElementType.GROUP_BY, - element_value=group_by_item_name, + element_value=group_by_item, ), ) ) diff --git a/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/saved_queries.yaml b/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/saved_queries.yaml index 77b4ebf8..e822550f 100644 --- a/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/saved_queries.yaml +++ b/tests/fixtures/semantic_manifest_yamls/simple_semantic_manifest/saved_queries.yaml @@ -6,7 +6,7 @@ saved_query: - bookings - instant_bookings group_bys: - - metric_time__day - - listing__capacity_latest + - TimeDimension('metric_time', 'DAY') + - Dimension('listing__capacity_latest') where: - "{{ Dimension('listing__capacity_latest') }} > 3" diff --git a/tests/validations/test_saved_query.py b/tests/validations/test_saved_query.py index 0f704ede..c6ae46f4 100644 --- a/tests/validations/test_saved_query.py +++ b/tests/validations/test_saved_query.py @@ -43,7 +43,7 @@ def test_invalid_metric_in_saved_query( # noqa: D name="Example Saved Query", description="Example description.", metrics=["invalid_metric"], - group_bys=["booking__is_instant"], + group_bys=["Dimension('booking__is_instant')"], where=[PydanticWhereFilter(where_sql_template="{{ Dimension('booking__is_instant') }}")], ), ] @@ -63,7 +63,7 @@ def test_invalid_where_in_saved_query( # noqa: D name="Example Saved Query", description="Example description.", metrics=["bookings"], - group_bys=["booking__is_instant"], + group_bys=["Dimension('booking__is_instant')"], where=[PydanticWhereFilter(where_sql_template="{{ invalid_jinja }}")], ), ] @@ -75,7 +75,7 @@ def test_invalid_where_in_saved_query( # noqa: D ) -def test_invalid_group_by_in_saved_query( # noqa: D +def test_invalid_group_by_element_in_saved_query( # noqa: D simple_semantic_manifest__with_primary_transforms: PydanticSemanticManifest, ) -> None: manifest = copy.deepcopy(simple_semantic_manifest__with_primary_transforms) @@ -84,7 +84,7 @@ def test_invalid_group_by_in_saved_query( # noqa: D name="Example Saved Query", description="Example description.", metrics=["bookings"], - group_bys=["invalid_dimension"], + group_bys=["Dimension('booking__invalid_dimension')"], where=[PydanticWhereFilter(where_sql_template="{{ Dimension('booking__is_instant') }}")], ), ] @@ -94,3 +94,24 @@ def test_invalid_group_by_in_saved_query( # noqa: D manifest_validator.validate_semantic_manifest(manifest), "is not a valid group-by name.", ) + + +def test_invalid_group_by_format_in_saved_query( # noqa: D + simple_semantic_manifest__with_primary_transforms: PydanticSemanticManifest, +) -> None: + manifest = copy.deepcopy(simple_semantic_manifest__with_primary_transforms) + manifest.saved_queries = [ + PydanticSavedQuery( + name="Example Saved Query", + description="Example description.", + metrics=["bookings"], + group_bys=["invalid_format"], + where=[PydanticWhereFilter(where_sql_template="{{ Dimension('booking__is_instant') }}")], + ), + ] + + manifest_validator = SemanticManifestValidator[PydanticSemanticManifest]([SavedQueryRule()]) + check_only_one_error_with_message( + manifest_validator.validate_semantic_manifest(manifest), + "An error occurred while trying to parse a group-by in saved query", + )