Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport 1.6.latest] Dc/8546 semantic models in graph selection #8724

Merged
merged 3 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/unreleased/Fixes-20230925-233306.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Fixes
body: semantic models in graph selection
time: 2023-09-25T23:33:06.754344+01:00
custom:
Author: dave-connors-3 michelleark
Issue: "8589"
1 change: 1 addition & 0 deletions core/dbt/cli/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@
type=ChoiceTuple(
[
"metric",
"semantic_model",
"source",
"analysis",
"model",
Expand Down
4 changes: 2 additions & 2 deletions core/dbt/compilation.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,10 @@ def link_node(self, node: GraphMemberNode, manifest: Manifest):
def link_graph(self, manifest: Manifest):
for source in manifest.sources.values():
self.add_node(source.unique_id)
for semantic_model in manifest.semantic_models.values():
self.add_node(semantic_model.unique_id)
for node in manifest.nodes.values():
self.link_node(node, manifest)
for semantic_model in manifest.semantic_models.values():
self.link_node(semantic_model, manifest)
for exposure in manifest.exposures.values():
self.link_node(exposure, manifest)
for metric in manifest.metrics.values():
Expand Down
50 changes: 50 additions & 0 deletions core/dbt/contracts/graph/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1602,6 +1602,56 @@ def primary_entity_reference(self) -> Optional[EntityReference]:
else None
)

def same_model(self, old: "SemanticModel") -> bool:
return self.model == old.same_model

def same_node_relation(self, old: "SemanticModel") -> bool:
return self.node_relation == old.node_relation

def same_description(self, old: "SemanticModel") -> bool:
return self.description == old.description

def same_defaults(self, old: "SemanticModel") -> bool:
return self.defaults == old.defaults

def same_entities(self, old: "SemanticModel") -> bool:
return self.entities == old.entities

def same_dimensions(self, old: "SemanticModel") -> bool:
return self.dimensions == old.dimensions

def same_measures(self, old: "SemanticModel") -> bool:
return self.measures == old.measures

def same_config(self, old: "SemanticModel") -> bool:
return self.config == old.config

def same_primary_entity(self, old: "SemanticModel") -> bool:
return self.primary_entity == old.primary_entity

def same_group(self, old: "SemanticModel") -> bool:
return self.group == old.group

def same_contents(self, old: Optional["SemanticModel"]) -> bool:
# existing when it didn't before is a change!
# metadata/tags changes are not "changes"
if old is None:
return True

return (
self.same_model(old)
and self.same_node_relation(old)
and self.same_description(old)
and self.same_defaults(old)
and self.same_entities(old)
and self.same_dimensions(old)
and self.same_measures(old)
and self.same_config(old)
and self.same_primary_entity(old)
and self.same_group(old)
and True
)


# ====================================
# Patches
Expand Down
2 changes: 1 addition & 1 deletion core/dbt/graph/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

INTERSECTION_DELIMITER = ","

DEFAULT_INCLUDES: List[str] = ["fqn:*", "source:*", "exposure:*", "metric:*"]
DEFAULT_INCLUDES: List[str] = ["fqn:*", "source:*", "exposure:*", "metric:*", "semantic_model:*"]
DEFAULT_EXCLUDES: List[str] = []


Expand Down
48 changes: 44 additions & 4 deletions core/dbt/graph/selector_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
ResultNode,
ManifestNode,
ModelNode,
SemanticModel,
)
from dbt.contracts.graph.unparsed import UnparsedVersion
from dbt.contracts.state import PreviousState
Expand Down Expand Up @@ -53,6 +54,7 @@ class MethodName(StrEnum):
SourceStatus = "source_status"
Wildcard = "wildcard"
Version = "version"
SemanticModel = "semantic_model"


def is_selected_node(fqn: List[str], node_selector: str, is_versioned: bool) -> bool:
Expand Down Expand Up @@ -144,6 +146,16 @@ def metric_nodes(self, included_nodes: Set[UniqueId]) -> Iterator[Tuple[UniqueId
continue
yield unique_id, metric

def semantic_model_nodes(
self, included_nodes: Set[UniqueId]
) -> Iterator[Tuple[UniqueId, SemanticModel]]:

for key, semantic_model in self.manifest.semantic_models.items():
unique_id = UniqueId(key)
if unique_id not in included_nodes:
continue
yield unique_id, semantic_model

def all_nodes(
self, included_nodes: Set[UniqueId]
) -> Iterator[Tuple[UniqueId, SelectorTarget]]:
Expand All @@ -152,6 +164,7 @@ def all_nodes(
self.source_nodes(included_nodes),
self.exposure_nodes(included_nodes),
self.metric_nodes(included_nodes),
self.semantic_model_nodes(included_nodes),
)

def configurable_nodes(
Expand All @@ -167,6 +180,7 @@ def non_source_nodes(
self.parsed_nodes(included_nodes),
self.exposure_nodes(included_nodes),
self.metric_nodes(included_nodes),
self.semantic_model_nodes(included_nodes),
)

def groupable_nodes(
Expand Down Expand Up @@ -210,8 +224,8 @@ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[Uniqu
:param str selector: The selector or node name
"""
parsed_nodes = list(self.parsed_nodes(included_nodes))
for node, real_node in parsed_nodes:
non_source_nodes = list(self.non_source_nodes(included_nodes))
for node, real_node in non_source_nodes:
if self.node_is_match(selector, real_node.fqn, real_node.is_versioned):
yield node

Expand Down Expand Up @@ -322,6 +336,31 @@ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[Uniqu
yield node


class SemanticModelSelectorMethod(SelectorMethod):
def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
parts = selector.split(".")
target_package = SELECTOR_GLOB
if len(parts) == 1:
target_name = parts[0]
elif len(parts) == 2:
target_package, target_name = parts
else:
msg = (
'Invalid semantic model selector value "{}". Semantic models must be of '
"the form ${{semantic_model_name}} or "
"${{semantic_model_package.semantic_model_name}}"
).format(selector)
raise DbtRuntimeError(msg)

for node, real_node in self.semantic_model_nodes(included_nodes):
if not fnmatch(real_node.package_name, target_package):
continue
if not fnmatch(real_node.name, target_name):
continue

yield node


class PathSelectorMethod(SelectorMethod):
def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
"""Yields nodes from included that match the given path."""
Expand Down Expand Up @@ -431,7 +470,7 @@ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[Uniqu
resource_type = NodeType(selector)
except ValueError as exc:
raise DbtRuntimeError(f'Invalid resource_type selector "{selector}"') from exc
for node, real_node in self.parsed_nodes(included_nodes):
for node, real_node in self.all_nodes(included_nodes):
if real_node.resource_type == resource_type:
yield node

Expand Down Expand Up @@ -539,7 +578,7 @@ def check_macros_modified(self, node):
def check_modified_content(
self, old: Optional[SelectorTarget], new: SelectorTarget, adapter_type: str
) -> bool:
if isinstance(new, (SourceDefinition, Exposure, Metric)):
if isinstance(new, (SourceDefinition, Exposure, Metric, SemanticModel)):
# these all overwrite `same_contents`
different_contents = not new.same_contents(old) # type: ignore
else:
Expand Down Expand Up @@ -761,6 +800,7 @@ class MethodManager:
MethodName.Result: ResultSelectorMethod,
MethodName.SourceStatus: SourceStatusSelectorMethod,
MethodName.Version: VersionSelectorMethod,
MethodName.SemanticModel: SemanticModelSelectorMethod,
}

def __init__(
Expand Down
9 changes: 8 additions & 1 deletion core/dbt/task/list.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json

from dbt.contracts.graph.nodes import Exposure, SourceDefinition, Metric
from dbt.contracts.graph.nodes import Exposure, SourceDefinition, Metric, SemanticModel
from dbt.flags import get_flags
from dbt.graph import ResourceTypeSelector
from dbt.task.runnable import GraphRunnableTask
Expand Down Expand Up @@ -28,6 +28,7 @@ class ListTask(GraphRunnableTask):
NodeType.Source,
NodeType.Exposure,
NodeType.Metric,
NodeType.SemanticModel,
)
)
ALL_RESOURCE_VALUES = DEFAULT_RESOURCE_VALUES | frozenset((NodeType.Analysis,))
Expand Down Expand Up @@ -74,6 +75,8 @@ def _iterate_selected_nodes(self):
yield self.manifest.exposures[node]
elif node in self.manifest.metrics:
yield self.manifest.metrics[node]
elif node in self.manifest.semantic_models:
yield self.manifest.semantic_models[node]
else:
raise DbtRuntimeError(
f'Got an unexpected result from node selection: "{node}"'
Expand All @@ -97,6 +100,10 @@ def generate_selectors(self):
# metrics are searched for by pkg.metric_name
metric_selector = ".".join([node.package_name, node.name])
yield f"metric:{metric_selector}"
elif node.resource_type == NodeType.SemanticModel:
assert isinstance(node, SemanticModel)
semantic_model_selector = ".".join([node.package_name, node.name])
yield f"semantic_model:{semantic_model_selector}"
else:
# everything else is from `fqn`
yield ".".join(node.fqn)
Expand Down
56 changes: 55 additions & 1 deletion tests/functional/list/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,16 @@
{{ config(materialized='ephemeral') }}
select 1 as id
select
1 as id,
{{ dbt.date_trunc('day', dbt.current_timestamp()) }} as created_at
"""

models__metric_flow = """
select
{{ dbt.date_trunc('day', dbt.current_timestamp()) }} as date_day
"""

Expand Down Expand Up @@ -103,6 +112,38 @@
"""

semantic_models__sm_yml = """
semantic_models:
- name: my_sm
model: ref('outer')
defaults:
agg_time_dimension: created_at
entities:
- name: my_entity
type: primary
expr: id
dimensions:
- name: created_at
type: time
type_params:
time_granularity: day
measures:
- name: total_outer_count
agg: count
expr: 1
"""

metrics__m_yml = """
metrics:
- name: total_outer
type: simple
description: The total count of outer
label: Total Outer
type_params:
measure: total_outer_count
"""


@pytest.fixture(scope="class")
def snapshots():
Expand All @@ -122,6 +163,9 @@ def models():
"incremental.sql": models__incremental_sql,
"docs.md": models__docs_md,
"outer.sql": models__outer_sql,
"metricflow_time_spine.sql": models__metric_flow,
"sm.yml": semantic_models__sm_yml,
"m.yml": metrics__m_yml,
"sub": {"inner.sql": models__sub__inner_sql},
}

Expand All @@ -141,6 +185,16 @@ def analyses():
return {"a.sql": analyses__a_sql}


@pytest.fixture(scope="class")
def semantic_models():
return {"sm.yml": semantic_models__sm_yml}


@pytest.fixture(scope="class")
def metrics():
return {"m.yml": metrics__m_yml}


@pytest.fixture(scope="class")
def project_files(
project_root,
Expand Down
Loading
Loading