From ac43069da069b66ea584d6b3ab0f83f4fa93bde0 Mon Sep 17 00:00:00 2001 From: Courtney Holcomb Date: Wed, 10 Jul 2024 18:25:22 -0700 Subject: [PATCH 1/7] Add MetricTimeDefaultGranularityPattern --- .../metric_time_default_granularity.py | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 metricflow-semantics/metricflow_semantics/specs/patterns/metric_time_default_granularity.py diff --git a/metricflow-semantics/metricflow_semantics/specs/patterns/metric_time_default_granularity.py b/metricflow-semantics/metricflow_semantics/specs/patterns/metric_time_default_granularity.py new file mode 100644 index 0000000000..b0dbef5485 --- /dev/null +++ b/metricflow-semantics/metricflow_semantics/specs/patterns/metric_time_default_granularity.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +from collections import defaultdict +from typing import Dict, List, Sequence, Set + +from dbt_semantic_interfaces.references import MetricReference +from dbt_semantic_interfaces.type_enums import TimeGranularity +from typing_extensions import override + +from metricflow_semantics.model.semantics.metric_lookup import MetricLookup +from metricflow_semantics.specs.patterns.spec_pattern import SpecPattern +from metricflow_semantics.specs.spec_classes import ( + InstanceSpec, + LinkableInstanceSpec, + TimeDimensionSpec, + TimeDimensionSpecComparisonKey, + TimeDimensionSpecField, +) +from metricflow_semantics.specs.spec_set import group_specs_by_type + + +class MetricTimeDefaultGranularityPattern(SpecPattern): + """A pattern that matches metric_time specs if they have the default granularity for the requested metrics. + + This is used to determine the granularity that should be used for metric_time if no granularity is specified. + Spec passes through if granularity is already selected or if no metrics were queried, since no default is needed. + All non-metric_time specs are passed through. + + e.g., if a metric with default_granularity MONTH is queried + + inputs: + [ + TimeDimensionSpec('metric_time', 'day'), + TimeDimensionSpec('metric_time', 'week'), + TimeDimensionSpec('metric_time', 'month'), + DimensionSpec('listing__country'), + ] + + matches: + [ + TimeDimensionSpec('metric_time', 'month'), + DimensionSpec('listing__country'), + ] + """ + + def __init__(self, metric_lookup: MetricLookup, queried_metrics: Sequence[MetricReference] = ()) -> None: + """Match only time dimensions with the default granularity for a given query. + + Only affects time dimensions. All other items pass through. + """ + self._metric_lookup = metric_lookup + self._queried_metrics = queried_metrics + + @override + def match(self, candidate_specs: Sequence[InstanceSpec]) -> Sequence[InstanceSpec]: + default_granularity_for_metrics = self._metric_lookup.get_default_granularity_for_metrics(self._queried_metrics) + spec_set = group_specs_by_type(candidate_specs) + + # If there are no metrics or metric_time specs in the query, skip this filter. + if not (default_granularity_for_metrics and spec_set.metric_time_specs): + return candidate_specs + + spec_key_to_grains: Dict[TimeDimensionSpecComparisonKey, Set[TimeGranularity]] = defaultdict(set) + spec_key_to_specs: Dict[TimeDimensionSpecComparisonKey, List[TimeDimensionSpec]] = defaultdict(list) + for metric_time_spec in spec_set.metric_time_specs: + spec_key = metric_time_spec.comparison_key(exclude_fields=(TimeDimensionSpecField.TIME_GRANULARITY,)) + spec_key_to_grains[spec_key].add(metric_time_spec.time_granularity) + spec_key_to_specs[spec_key].append(metric_time_spec) + + matched_metric_time_specs: List[TimeDimensionSpec] = [] + for spec_key, time_grains in spec_key_to_grains.items(): + if default_granularity_for_metrics in time_grains: + matched_metric_time_specs.append( + spec_key_to_specs[spec_key][0].with_grain(default_granularity_for_metrics) + ) + else: + # If default_granularity is not in the available options, then time granularity was specified in the request + # and a default is not needed here. Pass all options through for this spec key. + matched_metric_time_specs.extend(spec_key_to_specs[spec_key]) + + matching_specs: Sequence[LinkableInstanceSpec] = ( + spec_set.dimension_specs + + tuple(matched_metric_time_specs) + + tuple(spec for spec in spec_set.time_dimension_specs if not spec.is_metric_time) + + spec_set.entity_specs + + spec_set.group_by_metric_specs + ) + + return matching_specs From ac2b646046b8e2c79c9277379a7325c908f69e42 Mon Sep 17 00:00:00 2001 From: Courtney Holcomb Date: Wed, 10 Jul 2024 18:28:43 -0700 Subject: [PATCH 2/7] Rename BaseTimeGrainPattern -> MinimumTimeGrainPattern This change is intended to make it more clear what the difference is between MinimumTimeGrainPattern and MetricTimeDefaultTimeGranularityPattern, since 'base' might easily be confused with 'default'. --- .../model/semantics/linkable_element_set.py | 2 +- .../query/group_by_item/group_by_item_resolver.py | 6 +++--- .../metricflow_semantics/query/query_parser.py | 6 +++--- .../metricflow_semantics/query/suggestion_generator.py | 6 +++--- .../patterns/{base_time_grain.py => minimum_time_grain.py} | 6 ++++-- 5 files changed, 14 insertions(+), 12 deletions(-) rename metricflow-semantics/metricflow_semantics/specs/patterns/{base_time_grain.py => minimum_time_grain.py} (94%) diff --git a/metricflow-semantics/metricflow_semantics/model/semantics/linkable_element_set.py b/metricflow-semantics/metricflow_semantics/model/semantics/linkable_element_set.py index effa5daf92..378719f45f 100644 --- a/metricflow-semantics/metricflow_semantics/model/semantics/linkable_element_set.py +++ b/metricflow-semantics/metricflow_semantics/model/semantics/linkable_element_set.py @@ -386,7 +386,7 @@ def filter_by_spec_patterns(self, spec_patterns: Sequence[SpecPattern]) -> Linka """ start_time = time.time() - # Spec patterns need all specs to match properly e.g. `BaseTimeGrainPattern`. + # Spec patterns need all specs to match properly e.g. `MinimumTimeGrainPattern`. matching_specs: Sequence[InstanceSpec] = self.specs for spec_pattern in spec_patterns: diff --git a/metricflow-semantics/metricflow_semantics/query/group_by_item/group_by_item_resolver.py b/metricflow-semantics/metricflow_semantics/query/group_by_item/group_by_item_resolver.py index dbc861467e..5bf9b98d16 100644 --- a/metricflow-semantics/metricflow_semantics/query/group_by_item/group_by_item_resolver.py +++ b/metricflow-semantics/metricflow_semantics/query/group_by_item/group_by_item_resolver.py @@ -27,7 +27,7 @@ MetricFlowQueryResolutionIssueSet, ) from metricflow_semantics.query.suggestion_generator import QueryItemSuggestionGenerator -from metricflow_semantics.specs.patterns.base_time_grain import BaseTimeGrainPattern +from metricflow_semantics.specs.patterns.minimum_time_grain import MinimumTimeGrainPattern from metricflow_semantics.specs.patterns.no_group_by_metric import NoGroupByMetricPattern from metricflow_semantics.specs.patterns.spec_pattern import SpecPattern from metricflow_semantics.specs.patterns.typed_patterns import TimeDimensionPattern @@ -102,7 +102,7 @@ def resolve_matching_item_for_querying( ) push_down_result = push_down_result.filter_candidates_by_pattern( - BaseTimeGrainPattern(), + MinimumTimeGrainPattern(), ) logger.info( f"Spec pattern:\n" @@ -152,7 +152,7 @@ def resolve_matching_item_for_filters( push_down_visitor = _PushDownGroupByItemCandidatesVisitor( manifest_lookup=self._manifest_lookup, - source_spec_patterns=(spec_pattern, BaseTimeGrainPattern()), + source_spec_patterns=(spec_pattern, MinimumTimeGrainPattern()), suggestion_generator=suggestion_generator, ) diff --git a/metricflow-semantics/metricflow_semantics/query/query_parser.py b/metricflow-semantics/metricflow_semantics/query/query_parser.py index f6177ca01d..fbcb896a04 100644 --- a/metricflow-semantics/metricflow_semantics/query/query_parser.py +++ b/metricflow-semantics/metricflow_semantics/query/query_parser.py @@ -52,8 +52,8 @@ ResolverInputForQuery, ResolverInputForQueryLevelWhereFilterIntersection, ) -from metricflow_semantics.specs.patterns.base_time_grain import BaseTimeGrainPattern from metricflow_semantics.specs.patterns.metric_time_pattern import MetricTimePattern +from metricflow_semantics.specs.patterns.minimum_time_grain import MinimumTimeGrainPattern from metricflow_semantics.specs.patterns.none_date_part import NoneDatePartPattern from metricflow_semantics.specs.query_param_implementations import DimensionOrEntityParameter, MetricParameter from metricflow_semantics.specs.query_spec import MetricFlowQuerySpec @@ -153,7 +153,7 @@ def _metric_time_granularity(time_dimension_specs: Sequence[TimeDimensionSpec]) for pattern_to_apply in ( MetricTimePattern(), - BaseTimeGrainPattern(), + MinimumTimeGrainPattern(), NoneDatePartPattern(), ): matching_specs = pattern_to_apply.match(matching_specs) @@ -164,7 +164,7 @@ def _metric_time_granularity(time_dimension_specs: Sequence[TimeDimensionSpec]) assert ( len(time_dimension_specs) == 1 - ), f"Bug with BaseTimeGrainPattern - should have returned exactly 1 spec but got {time_dimension_specs}" + ), f"Bug with MinimumTimeGrainPattern - should have returned exactly 1 spec but got {time_dimension_specs}" return time_dimension_specs[0].time_granularity diff --git a/metricflow-semantics/metricflow_semantics/query/suggestion_generator.py b/metricflow-semantics/metricflow_semantics/query/suggestion_generator.py index ce53c50b74..e9744e33f2 100644 --- a/metricflow-semantics/metricflow_semantics/query/suggestion_generator.py +++ b/metricflow-semantics/metricflow_semantics/query/suggestion_generator.py @@ -5,7 +5,7 @@ from metricflow_semantics.naming.naming_scheme import QueryItemNamingScheme from metricflow_semantics.query.similarity import top_fuzzy_matches -from metricflow_semantics.specs.patterns.base_time_grain import BaseTimeGrainPattern +from metricflow_semantics.specs.patterns.minimum_time_grain import MinimumTimeGrainPattern from metricflow_semantics.specs.patterns.no_group_by_metric import NoGroupByMetricPattern from metricflow_semantics.specs.patterns.none_date_part import NoneDatePartPattern from metricflow_semantics.specs.patterns.spec_pattern import SpecPattern @@ -24,9 +24,9 @@ class QueryItemSuggestionGenerator: # Adding these filters so that we don't get multiple suggestions that are similar, but with different # grains. Some additional thought is needed to tweak this as the base grain may not be the best suggestion. - FILTER_ITEM_CANDIDATE_FILTERS: Tuple[SpecPattern, ...] = (BaseTimeGrainPattern(), NoneDatePartPattern()) + FILTER_ITEM_CANDIDATE_FILTERS: Tuple[SpecPattern, ...] = (MinimumTimeGrainPattern(), NoneDatePartPattern()) GROUP_BY_ITEM_CANDIDATE_FILTERS: Tuple[SpecPattern, ...] = ( - BaseTimeGrainPattern(), + MinimumTimeGrainPattern(), NoneDatePartPattern(), NoGroupByMetricPattern(), ) diff --git a/metricflow-semantics/metricflow_semantics/specs/patterns/base_time_grain.py b/metricflow-semantics/metricflow_semantics/specs/patterns/minimum_time_grain.py similarity index 94% rename from metricflow-semantics/metricflow_semantics/specs/patterns/base_time_grain.py rename to metricflow-semantics/metricflow_semantics/specs/patterns/minimum_time_grain.py index 34b44c1267..dcd1a930ce 100644 --- a/metricflow-semantics/metricflow_semantics/specs/patterns/base_time_grain.py +++ b/metricflow-semantics/metricflow_semantics/specs/patterns/minimum_time_grain.py @@ -18,7 +18,7 @@ from metricflow_semantics.specs.spec_set import group_specs_by_type -class BaseTimeGrainPattern(SpecPattern): +class MinimumTimeGrainPattern(SpecPattern): """A pattern that matches linkable specs, but for time dimension specs, only the one with the finest grain. e.g. @@ -63,7 +63,9 @@ def match(self, candidate_specs: Sequence[InstanceSpec]) -> Sequence[InstanceSpe metric_time_specs = MetricTimePattern().match(candidate_specs) other_specs = tuple(spec for spec in candidate_specs if spec not in metric_time_specs) - return other_specs + tuple(BaseTimeGrainPattern(only_apply_for_metric_time=False).match(metric_time_specs)) + return other_specs + tuple( + MinimumTimeGrainPattern(only_apply_for_metric_time=False).match(metric_time_specs) + ) spec_set = group_specs_by_type(candidate_specs) From 377846a9ac94efee8cef0844012dd34ed7a14b57 Mon Sep 17 00:00:00 2001 From: Courtney Holcomb Date: Wed, 10 Jul 2024 19:22:59 -0700 Subject: [PATCH 3/7] Remove unused only_apply_for_metric_time param from MinimumTimeGrainPattern --- .../specs/patterns/min_time_grain.py | 70 +++++++++++++++++++ .../specs/patterns/minimum_time_grain.py | 23 ------ 2 files changed, 70 insertions(+), 23 deletions(-) create mode 100644 metricflow-semantics/metricflow_semantics/specs/patterns/min_time_grain.py diff --git a/metricflow-semantics/metricflow_semantics/specs/patterns/min_time_grain.py b/metricflow-semantics/metricflow_semantics/specs/patterns/min_time_grain.py new file mode 100644 index 0000000000..f64a1e14f7 --- /dev/null +++ b/metricflow-semantics/metricflow_semantics/specs/patterns/min_time_grain.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +from collections import defaultdict +from typing import Dict, List, Sequence, Set + +from dbt_semantic_interfaces.type_enums import TimeGranularity +from typing_extensions import override + +from metricflow_semantics.specs.patterns.spec_pattern import SpecPattern +from metricflow_semantics.specs.spec_classes import ( + InstanceSpec, + LinkableInstanceSpec, + TimeDimensionSpec, + TimeDimensionSpecComparisonKey, + TimeDimensionSpecField, +) +from metricflow_semantics.specs.spec_set import group_specs_by_type + + +class MinimumTimeGrainPattern(SpecPattern): + """A pattern that matches linkable specs, but for time dimension specs, only the one with the finest grain. + + e.g. + + inputs: + [ + TimeDimensionSpec('metric_time', 'day'), + TimeDimensionSpec('metric_time', 'month.'), + DimensionSpec('listing__country'), + ] + + matches: + [ + TimeDimensionSpec('metric_time', 'day'), + DimensionSpec('listing__country'), + ] + + The finest grain represents the defined grain of the time dimension in the semantic model when evaluating specs + of the source. + + This pattern helps to implement matching of group-by-items for where filters - in those cases, an ambiguously + specified group-by-item can only match to time dimension spec with the base grain. + + Also, this is currently used to help implement restrictions on cumulative metrics where they can only be queried + by the base grain of metric_time. + """ + + @override + def match(self, candidate_specs: Sequence[InstanceSpec]) -> Sequence[InstanceSpec]: + spec_set = group_specs_by_type(candidate_specs) + + spec_key_to_grains: Dict[TimeDimensionSpecComparisonKey, Set[TimeGranularity]] = defaultdict(set) + spec_key_to_specs: Dict[TimeDimensionSpecComparisonKey, List[TimeDimensionSpec]] = defaultdict(list) + for time_dimension_spec in spec_set.time_dimension_specs: + spec_key = time_dimension_spec.comparison_key(exclude_fields=(TimeDimensionSpecField.TIME_GRANULARITY,)) + spec_key_to_grains[spec_key].add(time_dimension_spec.time_granularity) + spec_key_to_specs[spec_key].append(time_dimension_spec) + + matched_time_dimension_specs: List[TimeDimensionSpec] = [] + for spec_key, time_grains in spec_key_to_grains.items(): + matched_time_dimension_specs.append(spec_key_to_specs[spec_key][0].with_grain(min(time_grains))) + + matching_specs: Sequence[LinkableInstanceSpec] = ( + spec_set.dimension_specs + + tuple(matched_time_dimension_specs) + + spec_set.entity_specs + + spec_set.group_by_metric_specs + ) + + return matching_specs diff --git a/metricflow-semantics/metricflow_semantics/specs/patterns/minimum_time_grain.py b/metricflow-semantics/metricflow_semantics/specs/patterns/minimum_time_grain.py index dcd1a930ce..64d5615ae5 100644 --- a/metricflow-semantics/metricflow_semantics/specs/patterns/minimum_time_grain.py +++ b/metricflow-semantics/metricflow_semantics/specs/patterns/minimum_time_grain.py @@ -6,7 +6,6 @@ from dbt_semantic_interfaces.type_enums import TimeGranularity from typing_extensions import override -from metricflow_semantics.specs.patterns.metric_time_pattern import MetricTimePattern from metricflow_semantics.specs.patterns.spec_pattern import SpecPattern from metricflow_semantics.specs.spec_classes import ( InstanceSpec, @@ -41,32 +40,10 @@ class MinimumTimeGrainPattern(SpecPattern): This pattern helps to implement matching of group-by-items for where filters - in those cases, an ambiguously specified group-by-item can only match to time dimension spec with the base grain. - - Also, this is currently used to help implement restrictions on cumulative metrics where they can only be queried - by the base grain of metric_time. """ - def __init__(self, only_apply_for_metric_time: bool = False) -> None: - """Initializer. - - Args: - only_apply_for_metric_time: If set, only remove time dimension specs with a non-base grain if it's for - metric_time. This parameter is useful for implementing restrictions on cumulative metrics as they can only - be queried by the base grain of metric_time. - TODO: This is a little odd. This can be replaced once composite patterns are supported. - """ - self._only_apply_for_metric_time = only_apply_for_metric_time - @override def match(self, candidate_specs: Sequence[InstanceSpec]) -> Sequence[InstanceSpec]: - if self._only_apply_for_metric_time: - metric_time_specs = MetricTimePattern().match(candidate_specs) - other_specs = tuple(spec for spec in candidate_specs if spec not in metric_time_specs) - - return other_specs + tuple( - MinimumTimeGrainPattern(only_apply_for_metric_time=False).match(metric_time_specs) - ) - spec_set = group_specs_by_type(candidate_specs) spec_key_to_grains: Dict[TimeDimensionSpecComparisonKey, Set[TimeGranularity]] = defaultdict(set) From db76f4ad7b70783876904697661363748c4f79fe Mon Sep 17 00:00:00 2001 From: Courtney Holcomb Date: Fri, 12 Jul 2024 07:42:36 -0700 Subject: [PATCH 4/7] Cleanup --- .../metric_time_default_granularity.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/metricflow-semantics/metricflow_semantics/specs/patterns/metric_time_default_granularity.py b/metricflow-semantics/metricflow_semantics/specs/patterns/metric_time_default_granularity.py index b0dbef5485..f011fc9538 100644 --- a/metricflow-semantics/metricflow_semantics/specs/patterns/metric_time_default_granularity.py +++ b/metricflow-semantics/metricflow_semantics/specs/patterns/metric_time_default_granularity.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections import defaultdict -from typing import Dict, List, Sequence, Set +from typing import Dict, Sequence, Set, Tuple from dbt_semantic_interfaces.references import MetricReference from dbt_semantic_interfaces.type_enums import TimeGranularity @@ -53,34 +53,36 @@ def __init__(self, metric_lookup: MetricLookup, queried_metrics: Sequence[Metric @override def match(self, candidate_specs: Sequence[InstanceSpec]) -> Sequence[InstanceSpec]: - default_granularity_for_metrics = self._metric_lookup.get_default_granularity_for_metrics(self._queried_metrics) + # TODO: using placeholder grain option for now + max_metric_default_time_granularity = max((TimeGranularity.DAY,)) spec_set = group_specs_by_type(candidate_specs) + # TODO: is this needed? # If there are no metrics or metric_time specs in the query, skip this filter. - if not (default_granularity_for_metrics and spec_set.metric_time_specs): + if not (max_metric_default_time_granularity and spec_set.metric_time_specs): return candidate_specs spec_key_to_grains: Dict[TimeDimensionSpecComparisonKey, Set[TimeGranularity]] = defaultdict(set) - spec_key_to_specs: Dict[TimeDimensionSpecComparisonKey, List[TimeDimensionSpec]] = defaultdict(list) + spec_key_to_specs: Dict[TimeDimensionSpecComparisonKey, Tuple[TimeDimensionSpec, ...]] = defaultdict(tuple) for metric_time_spec in spec_set.metric_time_specs: spec_key = metric_time_spec.comparison_key(exclude_fields=(TimeDimensionSpecField.TIME_GRANULARITY,)) spec_key_to_grains[spec_key].add(metric_time_spec.time_granularity) - spec_key_to_specs[spec_key].append(metric_time_spec) + spec_key_to_specs[spec_key] += (metric_time_spec,) - matched_metric_time_specs: List[TimeDimensionSpec] = [] + matched_metric_time_specs: Tuple[TimeDimensionSpec, ...] = () for spec_key, time_grains in spec_key_to_grains.items(): - if default_granularity_for_metrics in time_grains: - matched_metric_time_specs.append( - spec_key_to_specs[spec_key][0].with_grain(default_granularity_for_metrics) + if max_metric_default_time_granularity in time_grains: + matched_metric_time_specs += ( + spec_key_to_specs[spec_key][0].with_grain(max_metric_default_time_granularity), ) else: # If default_granularity is not in the available options, then time granularity was specified in the request # and a default is not needed here. Pass all options through for this spec key. - matched_metric_time_specs.extend(spec_key_to_specs[spec_key]) + matched_metric_time_specs += spec_key_to_specs[spec_key] matching_specs: Sequence[LinkableInstanceSpec] = ( spec_set.dimension_specs - + tuple(matched_metric_time_specs) + + matched_metric_time_specs + tuple(spec for spec in spec_set.time_dimension_specs if not spec.is_metric_time) + spec_set.entity_specs + spec_set.group_by_metric_specs From 4533fb8d20a60b10178dbd42515e0446967f0d5e Mon Sep 17 00:00:00 2001 From: Courtney Holcomb Date: Thu, 11 Jul 2024 09:01:14 -0700 Subject: [PATCH 5/7] Use MetricTimeDefaultGranularityPattern to resolve query items where appropriate --- .../group_by_item/group_by_item_resolver.py | 42 +++++++++++++++---- .../query/query_parser.py | 35 +++++++++------- .../query/query_resolver.py | 2 + .../test_matching_item_for_querying.py | 10 +++-- 4 files changed, 62 insertions(+), 27 deletions(-) diff --git a/metricflow-semantics/metricflow_semantics/query/group_by_item/group_by_item_resolver.py b/metricflow-semantics/metricflow_semantics/query/group_by_item/group_by_item_resolver.py index 5bf9b98d16..7116ffd9c1 100644 --- a/metricflow-semantics/metricflow_semantics/query/group_by_item/group_by_item_resolver.py +++ b/metricflow-semantics/metricflow_semantics/query/group_by_item/group_by_item_resolver.py @@ -6,7 +6,7 @@ from dbt_semantic_interfaces.call_parameter_sets import TimeDimensionCallParameterSet from dbt_semantic_interfaces.naming.keywords import METRIC_TIME_ELEMENT_NAME -from dbt_semantic_interfaces.references import SemanticModelReference, TimeDimensionReference +from dbt_semantic_interfaces.references import MetricReference, SemanticModelReference, TimeDimensionReference from dbt_semantic_interfaces.type_enums import TimeGranularity from typing_extensions import override @@ -27,6 +27,7 @@ MetricFlowQueryResolutionIssueSet, ) from metricflow_semantics.query.suggestion_generator import QueryItemSuggestionGenerator +from metricflow_semantics.specs.patterns.metric_time_default_granularity import MetricTimeDefaultGranularityPattern from metricflow_semantics.specs.patterns.minimum_time_grain import MinimumTimeGrainPattern from metricflow_semantics.specs.patterns.no_group_by_metric import NoGroupByMetricPattern from metricflow_semantics.specs.patterns.spec_pattern import SpecPattern @@ -80,11 +81,15 @@ def resolve_matching_item_for_querying( self, spec_pattern: SpecPattern, suggestion_generator: Optional[QueryItemSuggestionGenerator], + queried_metrics: Sequence[MetricReference], + only_use_minimum_grain: bool = False, ) -> GroupByItemResolution: - """Returns the spec that corresponds the one described by spec_pattern and is valid for the query. + """Returns the spec that corresponds to the one described by spec_pattern and is valid for the query. For queries, if the pattern matches to a spec for the same element at different grains, the spec with the finest - common grain is returned. + common grain is returned, unless the spec is metric_time, in which case the default grain is returned. + + If only_use_minimum_grain is True, will use minimum grain instead of default for metric_time, too. """ push_down_visitor = _PushDownGroupByItemCandidatesVisitor( manifest_lookup=self._manifest_lookup, @@ -101,9 +106,18 @@ def resolve_matching_item_for_querying( issue_set=push_down_result.issue_set, ) - push_down_result = push_down_result.filter_candidates_by_pattern( - MinimumTimeGrainPattern(), - ) + filters_to_use: Tuple[SpecPattern, ...] = (MinimumTimeGrainPattern(),) + if not only_use_minimum_grain: + # Default pattern must come first to avoid removing default grain options prematurely. + filters_to_use = ( + MetricTimeDefaultGranularityPattern( + metric_lookup=self._manifest_lookup.metric_lookup, queried_metrics=queried_metrics + ), + ) + filters_to_use + + for filter_to_use in filters_to_use: + push_down_result = push_down_result.filter_candidates_by_pattern(filter_to_use) + logger.info( f"Spec pattern:\n" f"{indent(mf_pformat(spec_pattern))}\n" @@ -152,7 +166,15 @@ def resolve_matching_item_for_filters( push_down_visitor = _PushDownGroupByItemCandidatesVisitor( manifest_lookup=self._manifest_lookup, - source_spec_patterns=(spec_pattern, MinimumTimeGrainPattern()), + source_spec_patterns=( + spec_pattern, + # MetricTimeDefaultGranularityPattern must come before MinimumTimeGrainPattern to ensure we don't remove the + # default grain from candiate set prematurely. + MetricTimeDefaultGranularityPattern( + metric_lookup=self._manifest_lookup.metric_lookup, queried_metrics=filter_location.metric_references + ), + MinimumTimeGrainPattern(), + ), suggestion_generator=suggestion_generator, ) @@ -210,16 +232,18 @@ def resolve_available_items( issue_set=push_down_result.issue_set, ) - def resolve_min_metric_time_grain(self) -> TimeGranularity: + def resolve_min_metric_time_grain(self, metrics_in_query: Sequence[MetricReference]) -> TimeGranularity: """Returns the finest time grain of metric_time for querying.""" metric_time_grain_resolution = self.resolve_matching_item_for_querying( spec_pattern=TimeDimensionPattern.from_call_parameter_set( TimeDimensionCallParameterSet( entity_path=(), time_dimension_reference=TimeDimensionReference(element_name=METRIC_TIME_ELEMENT_NAME), - ) + ), ), suggestion_generator=None, + queried_metrics=metrics_in_query, + only_use_minimum_grain=True, ) metric_time_spec_set = ( group_specs_by_type((metric_time_grain_resolution.spec,)) diff --git a/metricflow-semantics/metricflow_semantics/query/query_parser.py b/metricflow-semantics/metricflow_semantics/query/query_parser.py index fbcb896a04..0b52bdd734 100644 --- a/metricflow-semantics/metricflow_semantics/query/query_parser.py +++ b/metricflow-semantics/metricflow_semantics/query/query_parser.py @@ -11,7 +11,7 @@ ) from dbt_semantic_interfaces.protocols import SavedQuery from dbt_semantic_interfaces.protocols.where_filter import WhereFilter -from dbt_semantic_interfaces.references import SemanticModelReference +from dbt_semantic_interfaces.references import MetricReference, SemanticModelReference from dbt_semantic_interfaces.type_enums import TimeGranularity from metricflow_semantics.assert_one_arg import assert_at_most_one_arg_set @@ -53,7 +53,7 @@ ResolverInputForQueryLevelWhereFilterIntersection, ) from metricflow_semantics.specs.patterns.metric_time_pattern import MetricTimePattern -from metricflow_semantics.specs.patterns.minimum_time_grain import MinimumTimeGrainPattern +from metricflow_semantics.specs.patterns.min_time_grain import MinimumTimeGrainPattern from metricflow_semantics.specs.patterns.none_date_part import NoneDatePartPattern from metricflow_semantics.specs.query_param_implementations import DimensionOrEntityParameter, MetricParameter from metricflow_semantics.specs.query_spec import MetricFlowQuerySpec @@ -147,8 +147,9 @@ def _get_saved_query(self, saved_query_parameter: SavedQueryParameter) -> SavedQ return matching_saved_queries[0] - @staticmethod - def _metric_time_granularity(time_dimension_specs: Sequence[TimeDimensionSpec]) -> Optional[TimeGranularity]: + def _get_smallest_requested_metric_time_granularity( + self, time_dimension_specs: Sequence[TimeDimensionSpec] + ) -> Optional[TimeGranularity]: matching_specs: Sequence[InstanceSpec] = time_dimension_specs for pattern_to_apply in ( @@ -173,19 +174,23 @@ def _adjust_time_constraint( resolution_dag: GroupByItemResolutionDag, time_dimension_specs_in_query: Sequence[TimeDimensionSpec], time_constraint: TimeRangeConstraint, + metrics_in_query: Sequence[MetricReference], ) -> TimeRangeConstraint: - metric_time_granularity = MetricFlowQueryParser._metric_time_granularity(time_dimension_specs_in_query) + """Change the time range so that the ends are at the ends of the requested time granularity windows. + + e.g. [2020-01-15, 2020-2-15] with MONTH granularity -> [2020-01-01, 2020-02-29] + """ + metric_time_granularity = self._get_smallest_requested_metric_time_granularity(time_dimension_specs_in_query) if metric_time_granularity is None: + # This indicates there were no metric time specs in the query, so use smallest available granularity for metric_time. group_by_item_resolver = GroupByItemResolver( manifest_lookup=self._manifest_lookup, resolution_dag=resolution_dag, ) - metric_time_granularity = group_by_item_resolver.resolve_min_metric_time_grain() - - """Change the time range so that the ends are at the ends of the appropriate time granularity windows. + metric_time_granularity = group_by_item_resolver.resolve_min_metric_time_grain( + metrics_in_query=metrics_in_query + ) - e.g. [2020-01-15, 2020-2-15] with MONTH granularity -> [2020-01-01, 2020-02-29] - """ return self._time_period_adjuster.expand_time_constraint_to_fill_granularity( time_constraint=time_constraint, granularity=metric_time_granularity, @@ -495,13 +500,13 @@ def _parse_and_validate_query( resolution_dag=query_resolution.resolution_dag, time_dimension_specs_in_query=query_spec.time_dimension_specs, time_constraint=time_constraint, + metrics_in_query=tuple( + metric_resolver_input.spec_pattern.metric_reference + for metric_resolver_input in resolver_inputs_for_metrics + ), ) logger.info(f"Time constraint after adjustment is: {time_constraint}") - - return ParseQueryResult( - query_spec=query_spec.with_time_range_constraint(time_constraint), - queried_semantic_models=query_resolution.queried_semantic_models, - ) + query_spec = query_spec.with_time_range_constraint(time_constraint) return ParseQueryResult( query_spec=query_spec, diff --git a/metricflow-semantics/metricflow_semantics/query/query_resolver.py b/metricflow-semantics/metricflow_semantics/query/query_resolver.py index d91ed9c17b..1f329113cd 100644 --- a/metricflow-semantics/metricflow_semantics/query/query_resolver.py +++ b/metricflow-semantics/metricflow_semantics/query/query_resolver.py @@ -165,9 +165,11 @@ def _resolve_group_by_item_input( ), ), ) + return group_by_item_resolver.resolve_matching_item_for_querying( spec_pattern=group_by_item_input.spec_pattern, suggestion_generator=suggestion_generator, + queried_metrics=queried_metrics, ) def _resolve_metric_inputs( diff --git a/metricflow-semantics/tests_metricflow_semantics/query/group_by_item/test_matching_item_for_querying.py b/metricflow-semantics/tests_metricflow_semantics/query/group_by_item/test_matching_item_for_querying.py index 7b9aa5a429..b1af41ef8f 100644 --- a/metricflow-semantics/tests_metricflow_semantics/query/group_by_item/test_matching_item_for_querying.py +++ b/metricflow-semantics/tests_metricflow_semantics/query/group_by_item/test_matching_item_for_querying.py @@ -16,6 +16,9 @@ from metricflow_semantics.query.group_by_item.resolution_dag.resolution_nodes.metric_resolution_node import ( MetricGroupByItemResolutionNode, ) +from metricflow_semantics.query.group_by_item.resolution_dag.resolution_nodes.query_resolution_node import ( + QueryGroupByItemResolutionNode, +) from metricflow_semantics.test_helpers.config_helpers import MetricFlowTestConfiguration from metricflow_semantics.test_helpers.metric_time_dimension import MTD_SPEC_DAY, MTD_SPEC_MONTH, MTD_SPEC_YEAR from metricflow_semantics.test_helpers.snapshot_helpers import assert_object_snapshot_equal @@ -39,10 +42,9 @@ def test_ambiguous_metric_time_in_query( # noqa: D103 ) spec_pattern = ObjectBuilderNamingScheme().spec_pattern(f"TimeDimension('{METRIC_TIME_ELEMENT_NAME}')") - + assert isinstance(resolution_dag.sink_node, QueryGroupByItemResolutionNode) result = group_by_item_resolver.resolve_matching_item_for_querying( - spec_pattern=spec_pattern, - suggestion_generator=None, + spec_pattern=spec_pattern, suggestion_generator=None, queried_metrics=resolution_dag.sink_node.metrics_in_query ) if case_id is AmbiguousResolutionQueryId.NO_METRICS: @@ -81,6 +83,7 @@ def test_unavailable_group_by_item_in_derived_metric_parent( result = group_by_item_resolver.resolve_matching_item_for_querying( spec_pattern=spec_pattern, suggestion_generator=None, + queried_metrics=(MetricReference("derived_metric_with_different_parent_time_grains"),), ) assert_object_snapshot_equal( @@ -108,6 +111,7 @@ def test_invalid_group_by_item( # noqa: D103 result = group_by_item_resolver.resolve_matching_item_for_querying( spec_pattern=naming_scheme.spec_pattern(input_str), suggestion_generator=None, + queried_metrics=(MetricReference("monthly_metric_0"), MetricReference("yearly_metric_0")), ) assert_object_snapshot_equal( From f311377131a2f2f1712d5c7e21cbaf27e576b599 Mon Sep 17 00:00:00 2001 From: Courtney Holcomb Date: Thu, 11 Jul 2024 09:04:47 -0700 Subject: [PATCH 6/7] Update snapshots These reflect a behavior change in which we don't error if metric_time is queried for metrics with two different default granularities. Instead, we choose the larger of the two, which is guaranteed to work for both metrics. --- ...tion_for_invalid_metric_filter__result.txt | 143 +++++------------ ...or_invalid_metric_input_filter__result.txt | 148 +++++------------- ...h_different_parent_time_grains__result.txt | 143 +++++------------ ...ics_with_different_time_grains__result.txt | 138 +++++----------- 4 files changed, 160 insertions(+), 412 deletions(-) diff --git a/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_resolution_for_invalid_metric_filter__result.txt b/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_resolution_for_invalid_metric_filter__result.txt index 119b0392e5..80b32c7364 100644 --- a/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_resolution_for_invalid_metric_filter__result.txt +++ b/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_resolution_for_invalid_metric_filter__result.txt @@ -22,116 +22,53 @@ FilterSpecResolutionLookUp( ), ], ), - resolved_linkable_element_set=LinkableElementSet(), - spec_pattern=TimeDimensionPattern( - parameter_set=EntityLinkPatternParameterSet( - fields_to_compare=(DATE_PART, ELEMENT_NAME, ENTITY_LINKS), - element_name='metric_time', - ), - ), - issue_set=MetricFlowQueryResolutionIssueSet( - issues=( - NoCommonItemsInParents( - issue_type=ERROR, - query_resolution_path=MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_0), - MetricGroupByItemResolutionNode(node_id=mtr_2), + resolved_linkable_element_set=LinkableElementSet( + path_key_to_linkable_dimensions={ + ElementPathKey( + element_name='metric_time', + element_type=TIME_DIMENSION, + time_granularity=YEAR, + ): ( + LinkableDimension( + defined_in_semantic_model=SemanticModelReference( + semantic_model_name='monthly_measures_source', ), - ), - parent_candidate_sets=( - GroupByItemCandidateSet( - linkable_element_set=LinkableElementSet( - path_key_to_linkable_dimensions={ - ElementPathKey( - element_name='metric_time', - element_type=TIME_DIMENSION, - time_granularity=MONTH, - ): ( - LinkableDimension( - defined_in_semantic_model=SemanticModelReference( - semantic_model_name='monthly_measures_source', - ), - element_name='metric_time', - dimension_type=TIME, - join_path=SemanticModelJoinPath( - left_semantic_model_reference=SemanticModelReference( - semantic_model_name='monthly_measures_source', - ), - ), - properties=frozenset( - 'METRIC_TIME', - ), - time_granularity=MONTH, - ), - ), - }, - ), - measure_paths=( - MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_0), - MetricGroupByItemResolutionNode(node_id=mtr_2), - MetricGroupByItemResolutionNode(node_id=mtr_0), - MeasureGroupByItemSourceNode(node_id=msr_0), - ), - ), - ), - path_from_leaf_node=MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_0), - MetricGroupByItemResolutionNode(node_id=mtr_2), - MetricGroupByItemResolutionNode(node_id=mtr_0), - ), + element_name='metric_time', + dimension_type=TIME, + join_path=SemanticModelJoinPath( + left_semantic_model_reference=SemanticModelReference( + semantic_model_name='monthly_measures_source', ), ), - GroupByItemCandidateSet( - linkable_element_set=LinkableElementSet( - path_key_to_linkable_dimensions={ - ElementPathKey( - element_name='metric_time', - element_type=TIME_DIMENSION, - time_granularity=YEAR, - ): ( - LinkableDimension( - defined_in_semantic_model=SemanticModelReference( - semantic_model_name='yearly_measure_source', - ), - element_name='metric_time', - dimension_type=TIME, - join_path=SemanticModelJoinPath( - left_semantic_model_reference=SemanticModelReference( - semantic_model_name='yearly_measure_source', - ), - ), - properties=frozenset( - 'METRIC_TIME', - ), - time_granularity=YEAR, - ), - ), - }, - ), - measure_paths=( - MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_0), - MetricGroupByItemResolutionNode(node_id=mtr_2), - MetricGroupByItemResolutionNode(node_id=mtr_1), - MeasureGroupByItemSourceNode(node_id=msr_1), - ), - ), - ), - path_from_leaf_node=MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_0), - MetricGroupByItemResolutionNode(node_id=mtr_2), - MetricGroupByItemResolutionNode(node_id=mtr_1), - ), + properties=frozenset( + 'DERIVED_TIME_GRANULARITY', + 'METRIC_TIME', + ), + time_granularity=YEAR, + ), + LinkableDimension( + defined_in_semantic_model=SemanticModelReference( + semantic_model_name='yearly_measure_source', + ), + element_name='metric_time', + dimension_type=TIME, + join_path=SemanticModelJoinPath( + left_semantic_model_reference=SemanticModelReference( + semantic_model_name='yearly_measure_source', ), ), + properties=frozenset( + 'METRIC_TIME', + ), + time_granularity=YEAR, ), ), + }, + ), + spec_pattern=TimeDimensionPattern( + parameter_set=EntityLinkPatternParameterSet( + fields_to_compare=(DATE_PART, ELEMENT_NAME, ENTITY_LINKS), + element_name='metric_time', ), ), filter_location_path=MetricFlowQueryResolutionPath( diff --git a/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_resolution_for_invalid_metric_input_filter__result.txt b/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_resolution_for_invalid_metric_input_filter__result.txt index 7d9f61426a..139eba5580 100644 --- a/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_resolution_for_invalid_metric_input_filter__result.txt +++ b/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_resolution_for_invalid_metric_input_filter__result.txt @@ -22,121 +22,53 @@ FilterSpecResolutionLookUp( ), ], ), - resolved_linkable_element_set=LinkableElementSet(), - spec_pattern=TimeDimensionPattern( - parameter_set=EntityLinkPatternParameterSet( - fields_to_compare=(DATE_PART, ELEMENT_NAME, ENTITY_LINKS), - element_name='metric_time', - ), - ), - issue_set=MetricFlowQueryResolutionIssueSet( - issues=( - NoCommonItemsInParents( - issue_type=ERROR, - query_resolution_path=MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_0), - MetricGroupByItemResolutionNode(node_id=mtr_3), - MetricGroupByItemResolutionNode(node_id=mtr_2), + resolved_linkable_element_set=LinkableElementSet( + path_key_to_linkable_dimensions={ + ElementPathKey( + element_name='metric_time', + element_type=TIME_DIMENSION, + time_granularity=YEAR, + ): ( + LinkableDimension( + defined_in_semantic_model=SemanticModelReference( + semantic_model_name='monthly_measures_source', ), - ), - parent_candidate_sets=( - GroupByItemCandidateSet( - linkable_element_set=LinkableElementSet( - path_key_to_linkable_dimensions={ - ElementPathKey( - element_name='metric_time', - element_type=TIME_DIMENSION, - time_granularity=MONTH, - ): ( - LinkableDimension( - defined_in_semantic_model=SemanticModelReference( - semantic_model_name='monthly_measures_source', - ), - element_name='metric_time', - dimension_type=TIME, - join_path=SemanticModelJoinPath( - left_semantic_model_reference=SemanticModelReference( - semantic_model_name='monthly_measures_source', - ), - ), - properties=frozenset( - 'METRIC_TIME', - ), - time_granularity=MONTH, - ), - ), - }, - ), - measure_paths=( - MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_0), - MetricGroupByItemResolutionNode(node_id=mtr_3), - MetricGroupByItemResolutionNode(node_id=mtr_2), - MetricGroupByItemResolutionNode(node_id=mtr_0), - MeasureGroupByItemSourceNode(node_id=msr_0), - ), - ), - ), - path_from_leaf_node=MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_0), - MetricGroupByItemResolutionNode(node_id=mtr_3), - MetricGroupByItemResolutionNode(node_id=mtr_2), - MetricGroupByItemResolutionNode(node_id=mtr_0), - ), + element_name='metric_time', + dimension_type=TIME, + join_path=SemanticModelJoinPath( + left_semantic_model_reference=SemanticModelReference( + semantic_model_name='monthly_measures_source', ), ), - GroupByItemCandidateSet( - linkable_element_set=LinkableElementSet( - path_key_to_linkable_dimensions={ - ElementPathKey( - element_name='metric_time', - element_type=TIME_DIMENSION, - time_granularity=YEAR, - ): ( - LinkableDimension( - defined_in_semantic_model=SemanticModelReference( - semantic_model_name='yearly_measure_source', - ), - element_name='metric_time', - dimension_type=TIME, - join_path=SemanticModelJoinPath( - left_semantic_model_reference=SemanticModelReference( - semantic_model_name='yearly_measure_source', - ), - ), - properties=frozenset( - 'METRIC_TIME', - ), - time_granularity=YEAR, - ), - ), - }, - ), - measure_paths=( - MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_0), - MetricGroupByItemResolutionNode(node_id=mtr_3), - MetricGroupByItemResolutionNode(node_id=mtr_2), - MetricGroupByItemResolutionNode(node_id=mtr_1), - MeasureGroupByItemSourceNode(node_id=msr_1), - ), - ), - ), - path_from_leaf_node=MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_0), - MetricGroupByItemResolutionNode(node_id=mtr_3), - MetricGroupByItemResolutionNode(node_id=mtr_2), - MetricGroupByItemResolutionNode(node_id=mtr_1), - ), + properties=frozenset( + 'DERIVED_TIME_GRANULARITY', + 'METRIC_TIME', + ), + time_granularity=YEAR, + ), + LinkableDimension( + defined_in_semantic_model=SemanticModelReference( + semantic_model_name='yearly_measure_source', + ), + element_name='metric_time', + dimension_type=TIME, + join_path=SemanticModelJoinPath( + left_semantic_model_reference=SemanticModelReference( + semantic_model_name='yearly_measure_source', ), ), + properties=frozenset( + 'METRIC_TIME', + ), + time_granularity=YEAR, ), ), + }, + ), + spec_pattern=TimeDimensionPattern( + parameter_set=EntityLinkPatternParameterSet( + fields_to_compare=(DATE_PART, ELEMENT_NAME, ENTITY_LINKS), + element_name='metric_time', ), ), filter_location_path=MetricFlowQueryResolutionPath( diff --git a/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_spec_resolution__derived_metric_with_different_parent_time_grains__result.txt b/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_spec_resolution__derived_metric_with_different_parent_time_grains__result.txt index 2fb667f41c..affa1a709f 100644 --- a/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_spec_resolution__derived_metric_with_different_parent_time_grains__result.txt +++ b/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_spec_resolution__derived_metric_with_different_parent_time_grains__result.txt @@ -22,116 +22,53 @@ FilterSpecResolutionLookUp( ), ], ), - resolved_linkable_element_set=LinkableElementSet(), - spec_pattern=TimeDimensionPattern( - parameter_set=EntityLinkPatternParameterSet( - fields_to_compare=(DATE_PART, ELEMENT_NAME, ENTITY_LINKS), - element_name='metric_time', - ), - ), - issue_set=MetricFlowQueryResolutionIssueSet( - issues=( - NoCommonItemsInParents( - issue_type=ERROR, - query_resolution_path=MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_5), - MetricGroupByItemResolutionNode(node_id=mtr_10), + resolved_linkable_element_set=LinkableElementSet( + path_key_to_linkable_dimensions={ + ElementPathKey( + element_name='metric_time', + element_type=TIME_DIMENSION, + time_granularity=YEAR, + ): ( + LinkableDimension( + defined_in_semantic_model=SemanticModelReference( + semantic_model_name='monthly_measures_source', ), - ), - parent_candidate_sets=( - GroupByItemCandidateSet( - linkable_element_set=LinkableElementSet( - path_key_to_linkable_dimensions={ - ElementPathKey( - element_name='metric_time', - element_type=TIME_DIMENSION, - time_granularity=MONTH, - ): ( - LinkableDimension( - defined_in_semantic_model=SemanticModelReference( - semantic_model_name='monthly_measures_source', - ), - element_name='metric_time', - dimension_type=TIME, - join_path=SemanticModelJoinPath( - left_semantic_model_reference=SemanticModelReference( - semantic_model_name='monthly_measures_source', - ), - ), - properties=frozenset( - 'METRIC_TIME', - ), - time_granularity=MONTH, - ), - ), - }, - ), - measure_paths=( - MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_5), - MetricGroupByItemResolutionNode(node_id=mtr_10), - MetricGroupByItemResolutionNode(node_id=mtr_8), - MeasureGroupByItemSourceNode(node_id=msr_7), - ), - ), - ), - path_from_leaf_node=MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_5), - MetricGroupByItemResolutionNode(node_id=mtr_10), - MetricGroupByItemResolutionNode(node_id=mtr_8), - ), + element_name='metric_time', + dimension_type=TIME, + join_path=SemanticModelJoinPath( + left_semantic_model_reference=SemanticModelReference( + semantic_model_name='monthly_measures_source', ), ), - GroupByItemCandidateSet( - linkable_element_set=LinkableElementSet( - path_key_to_linkable_dimensions={ - ElementPathKey( - element_name='metric_time', - element_type=TIME_DIMENSION, - time_granularity=YEAR, - ): ( - LinkableDimension( - defined_in_semantic_model=SemanticModelReference( - semantic_model_name='yearly_measure_source', - ), - element_name='metric_time', - dimension_type=TIME, - join_path=SemanticModelJoinPath( - left_semantic_model_reference=SemanticModelReference( - semantic_model_name='yearly_measure_source', - ), - ), - properties=frozenset( - 'METRIC_TIME', - ), - time_granularity=YEAR, - ), - ), - }, - ), - measure_paths=( - MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_5), - MetricGroupByItemResolutionNode(node_id=mtr_10), - MetricGroupByItemResolutionNode(node_id=mtr_9), - MeasureGroupByItemSourceNode(node_id=msr_8), - ), - ), - ), - path_from_leaf_node=MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_5), - MetricGroupByItemResolutionNode(node_id=mtr_10), - MetricGroupByItemResolutionNode(node_id=mtr_9), - ), + properties=frozenset( + 'DERIVED_TIME_GRANULARITY', + 'METRIC_TIME', + ), + time_granularity=YEAR, + ), + LinkableDimension( + defined_in_semantic_model=SemanticModelReference( + semantic_model_name='yearly_measure_source', + ), + element_name='metric_time', + dimension_type=TIME, + join_path=SemanticModelJoinPath( + left_semantic_model_reference=SemanticModelReference( + semantic_model_name='yearly_measure_source', ), ), + properties=frozenset( + 'METRIC_TIME', + ), + time_granularity=YEAR, ), ), + }, + ), + spec_pattern=TimeDimensionPattern( + parameter_set=EntityLinkPatternParameterSet( + fields_to_compare=(DATE_PART, ELEMENT_NAME, ENTITY_LINKS), + element_name='metric_time', ), ), filter_location_path=MetricFlowQueryResolutionPath( diff --git a/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_spec_resolution__metrics_with_different_time_grains__result.txt b/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_spec_resolution__metrics_with_different_time_grains__result.txt index 870dd193e7..2dd9caf21d 100644 --- a/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_spec_resolution__metrics_with_different_time_grains__result.txt +++ b/metricflow-semantics/tests_metricflow_semantics/snapshots/test_spec_lookup.py/str/test_filter_spec_resolution__metrics_with_different_time_grains__result.txt @@ -25,111 +25,53 @@ FilterSpecResolutionLookUp( ), ], ), - resolved_linkable_element_set=LinkableElementSet(), - spec_pattern=TimeDimensionPattern( - parameter_set=EntityLinkPatternParameterSet( - fields_to_compare=(DATE_PART, ELEMENT_NAME, ENTITY_LINKS), - element_name='metric_time', - ), - ), - issue_set=MetricFlowQueryResolutionIssueSet( - issues=( - NoCommonItemsInParents( - issue_type=ERROR, - query_resolution_path=MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_3), + resolved_linkable_element_set=LinkableElementSet( + path_key_to_linkable_dimensions={ + ElementPathKey( + element_name='metric_time', + element_type=TIME_DIMENSION, + time_granularity=YEAR, + ): ( + LinkableDimension( + defined_in_semantic_model=SemanticModelReference( + semantic_model_name='monthly_measures_source', ), - ), - parent_candidate_sets=( - GroupByItemCandidateSet( - linkable_element_set=LinkableElementSet( - path_key_to_linkable_dimensions={ - ElementPathKey( - element_name='metric_time', - element_type=TIME_DIMENSION, - time_granularity=MONTH, - ): ( - LinkableDimension( - defined_in_semantic_model=SemanticModelReference( - semantic_model_name='monthly_measures_source', - ), - element_name='metric_time', - dimension_type=TIME, - join_path=SemanticModelJoinPath( - left_semantic_model_reference=SemanticModelReference( - semantic_model_name='monthly_measures_source', - ), - ), - properties=frozenset( - 'METRIC_TIME', - ), - time_granularity=MONTH, - ), - ), - }, - ), - measure_paths=( - MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_3), - MetricGroupByItemResolutionNode(node_id=mtr_3), - MeasureGroupByItemSourceNode(node_id=msr_3), - ), - ), - ), - path_from_leaf_node=MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_3), - MetricGroupByItemResolutionNode(node_id=mtr_3), - ), + element_name='metric_time', + dimension_type=TIME, + join_path=SemanticModelJoinPath( + left_semantic_model_reference=SemanticModelReference( + semantic_model_name='monthly_measures_source', ), ), - GroupByItemCandidateSet( - linkable_element_set=LinkableElementSet( - path_key_to_linkable_dimensions={ - ElementPathKey( - element_name='metric_time', - element_type=TIME_DIMENSION, - time_granularity=YEAR, - ): ( - LinkableDimension( - defined_in_semantic_model=SemanticModelReference( - semantic_model_name='yearly_measure_source', - ), - element_name='metric_time', - dimension_type=TIME, - join_path=SemanticModelJoinPath( - left_semantic_model_reference=SemanticModelReference( - semantic_model_name='yearly_measure_source', - ), - ), - properties=frozenset( - 'METRIC_TIME', - ), - time_granularity=YEAR, - ), - ), - }, - ), - measure_paths=( - MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_3), - MetricGroupByItemResolutionNode(node_id=mtr_4), - MeasureGroupByItemSourceNode(node_id=msr_4), - ), - ), - ), - path_from_leaf_node=MetricFlowQueryResolutionPath( - resolution_path_nodes=( - QueryGroupByItemResolutionNode(node_id=qr_3), - MetricGroupByItemResolutionNode(node_id=mtr_4), - ), + properties=frozenset( + 'DERIVED_TIME_GRANULARITY', + 'METRIC_TIME', + ), + time_granularity=YEAR, + ), + LinkableDimension( + defined_in_semantic_model=SemanticModelReference( + semantic_model_name='yearly_measure_source', + ), + element_name='metric_time', + dimension_type=TIME, + join_path=SemanticModelJoinPath( + left_semantic_model_reference=SemanticModelReference( + semantic_model_name='yearly_measure_source', ), ), + properties=frozenset( + 'METRIC_TIME', + ), + time_granularity=YEAR, ), ), + }, + ), + spec_pattern=TimeDimensionPattern( + parameter_set=EntityLinkPatternParameterSet( + fields_to_compare=(DATE_PART, ELEMENT_NAME, ENTITY_LINKS), + element_name='metric_time', ), ), filter_location_path=MetricFlowQueryResolutionPath( From 5091b267c4ee47fba2719711f1e11d990577ec9f Mon Sep 17 00:00:00 2001 From: Courtney Holcomb Date: Thu, 11 Jul 2024 09:06:07 -0700 Subject: [PATCH 7/7] Changelog --- .changes/unreleased/Features-20240628-074617.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/unreleased/Features-20240628-074617.yaml diff --git a/.changes/unreleased/Features-20240628-074617.yaml b/.changes/unreleased/Features-20240628-074617.yaml new file mode 100644 index 0000000000..daecb746f9 --- /dev/null +++ b/.changes/unreleased/Features-20240628-074617.yaml @@ -0,0 +1,6 @@ +kind: Features +body: Use default_granularity to resolve metric_time. +time: 2024-06-28T07:46:17.768805-07:00 +custom: + Author: courtneyholcomb + Issue: "1310"