Skip to content

Commit

Permalink
Support filtering of LinkableElements by element name (#1444)
Browse files Browse the repository at this point in the history
This PR updates `LinkableElementFilter` to include the element name.
This allows for more efficient filtering before a spec pattern is
applied to a `LinkableElementSet`.
  • Loading branch information
plypaul authored and courtneyholcomb committed Oct 8, 2024
1 parent a050bfd commit bdc9253
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import FrozenSet
from typing import FrozenSet, Optional

from typing_extensions import Self, override

Expand All @@ -13,13 +13,20 @@
class LinkableElementFilter(Mergeable):
"""Describes a way to filter the `LinkableElements` in a `LinkableElementSet`."""

# A `None` value for element names means no filtering on element names.
element_names: Optional[FrozenSet[str]] = None
with_any_of: FrozenSet[LinkableElementProperty] = LinkableElementProperty.all_properties()
without_any_of: FrozenSet[LinkableElementProperty] = frozenset()
without_all_of: FrozenSet[LinkableElementProperty] = frozenset()

@override
def merge(self: Self, other: LinkableElementFilter) -> LinkableElementFilter:
if self.element_names is None and other.element_names is None:
element_names = None
else:
element_names = (self.element_names or frozenset()).union(other.element_names or frozenset())
return LinkableElementFilter(
element_names=element_names,
with_any_of=self.with_any_of.union(other.with_any_of),
without_any_of=self.without_any_of.union(other.without_any_of),
without_all_of=self.without_all_of.union(other.without_all_of),
Expand All @@ -29,3 +36,11 @@ def merge(self: Self, other: LinkableElementFilter) -> LinkableElementFilter:
@override
def empty_instance(cls) -> LinkableElementFilter:
return LinkableElementFilter()

def without_element_names(self) -> LinkableElementFilter:
"""Return this filter without the `element_names` filter set."""
return LinkableElementFilter(
with_any_of=self.with_any_of,
without_any_of=self.without_any_of,
without_all_of=self.without_all_of,
)
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ def filter(
a property in "without_any_of" set are removed. Lastly, any elements with all properties in without_all_of
are removed.
"""
element_names = element_filter.element_names
with_any_of = element_filter.with_any_of
without_any_of = element_filter.without_any_of
without_all_of = element_filter.without_all_of
Expand All @@ -238,6 +239,9 @@ def filter(
key_to_linkable_metrics: Dict[ElementPathKey, Tuple[LinkableMetric, ...]] = {}

for path_key, linkable_dimensions in self.path_key_to_linkable_dimensions.items():
if element_names is not None and path_key.element_name not in element_names:
continue

filtered_linkable_dimensions = tuple(
linkable_dimension
for linkable_dimension in linkable_dimensions
Expand All @@ -252,6 +256,9 @@ def filter(
key_to_linkable_dimensions[path_key] = filtered_linkable_dimensions

for path_key, linkable_entities in self.path_key_to_linkable_entities.items():
if element_names is not None and path_key.element_name not in element_names:
continue

filtered_linkable_entities = tuple(
linkable_entity
for linkable_entity in linkable_entities
Expand All @@ -266,6 +273,9 @@ def filter(
key_to_linkable_entities[path_key] = filtered_linkable_entities

for path_key, linkable_metrics in self.path_key_to_linkable_metrics.items():
if element_names is not None and path_key.element_name not in element_names:
continue

filtered_linkable_metrics = tuple(
linkable_metric
for linkable_metric in linkable_metrics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,17 @@ def linkable_elements_for_measure(
):
return self._linkable_spec_resolver.get_linkable_element_set_for_measure(measure_reference, element_filter)

cache_key = (measure_reference, element_filter)
# Cache the result without element names in the filter for better hit rates.
element_filter_without_element_names = element_filter.without_element_names()
cache_key = (measure_reference, element_filter_without_element_names)

result = self._linkable_element_set_for_measure_cache.get(cache_key)
if result is not None:
return result
return result.filter(element_filter)

result = self._linkable_spec_resolver.get_linkable_element_set_for_measure(measure_reference, element_filter)
result = self._linkable_spec_resolver.get_linkable_element_set_for_measure(
measure_reference, element_filter_without_element_names
)
self._linkable_element_set_for_measure_cache[cache_key] = result

logger.debug(
Expand All @@ -99,7 +104,7 @@ def linkable_elements_for_measure(
runtime=time.time() - start_time,
)
)
return result
return result.filter(element_filter)

@functools.lru_cache
def linkable_elements_for_no_metrics_query(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,6 @@ def __init__(
suggestion_generator: If there are issues with matching patterns to specs, use this to generate suggestions
that will go in the issue.
source_spec_patterns: The patterns to apply to the specs available at the measure nodes.
with_any_property: Only consider group-by-items with these properties from the measure nodes.
without_any_property: Only consider group-by-items without any of these properties (see
LinkableElementProperty).
filter_location: If resolving a where filter item, where this filter was defined.
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
from dataclasses import dataclass
from enum import Enum
from typing import Any, List, Optional, Sequence, Tuple
from typing import Any, FrozenSet, List, Optional, Sequence, Tuple

from dbt_semantic_interfaces.references import EntityReference
from dbt_semantic_interfaces.type_enums.date_part import DatePart
Expand Down Expand Up @@ -154,10 +154,17 @@ def match(self, candidate_specs: Sequence[InstanceSpec]) -> Sequence[LinkableIns
@property
@override
def element_pre_filter(self) -> LinkableElementFilter:
element_names: Optional[FrozenSet[str]] = None
if ParameterSetField.ELEMENT_NAME in self.parameter_set.fields_to_compare:
element_names = frozenset({self.parameter_set.element_name}) if self.parameter_set.element_name else None
if (
self.parameter_set.metric_subquery_entity_links is None
or len(self.parameter_set.metric_subquery_entity_links) == 0
):
return LinkableElementFilter(without_any_of=frozenset({LinkableElementProperty.METRIC}))
return LinkableElementFilter(
element_names=element_names, without_any_of=frozenset({LinkableElementProperty.METRIC})
)

return LinkableElementFilter()
return LinkableElementFilter(
element_names=element_names,
)
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ def from_call_parameter_set( # noqa: D102
@property
@override
def element_pre_filter(self) -> LinkableElementFilter:
return LinkableElementFilter(without_any_of=frozenset({LinkableElementProperty.METRIC}))
return super().element_pre_filter.merge(
LinkableElementFilter(without_any_of=frozenset({LinkableElementProperty.METRIC}))
)


@dataclass(frozen=True)
Expand Down Expand Up @@ -101,7 +103,9 @@ def from_call_parameter_set(
@property
@override
def element_pre_filter(self) -> LinkableElementFilter:
return LinkableElementFilter(without_any_of=frozenset({LinkableElementProperty.METRIC}))
return super().element_pre_filter.merge(
LinkableElementFilter(without_any_of=frozenset({LinkableElementProperty.METRIC}))
)


@dataclass(frozen=True)
Expand Down

0 comments on commit bdc9253

Please sign in to comment.