From efbf31e56f8d60c89f3c9c1f1196b071326594e1 Mon Sep 17 00:00:00 2001 From: Naman Goyal <43905880+itsnamangoyal@users.noreply.github.com> Date: Thu, 17 Sep 2026 22:52:05 +0530 Subject: [PATCH 1/2] fix: match bare wildcard FQN selectors against nested leaf names --- .changes/unreleased/Fixes-20260917-224607.yaml | 6 ++++++ core/dbt/graph/selector_methods.py | 10 ++++++++-- tests/unit/graph/test_selector_methods.py | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 .changes/unreleased/Fixes-20260917-224607.yaml diff --git a/.changes/unreleased/Fixes-20260917-224607.yaml b/.changes/unreleased/Fixes-20260917-224607.yaml new file mode 100644 index 00000000000..af8222f160e --- /dev/null +++ b/.changes/unreleased/Fixes-20260917-224607.yaml @@ -0,0 +1,6 @@ +kind: Fixes +body: Bare wildcard FQN selectors (e.g. `-s "f_*"`) now match nested models regardless of subfolder depth +time: 2026-09-17T22:46:07.566507+05:30 +custom: + Author: itsnamangoyal + Issue: "" diff --git a/core/dbt/graph/selector_methods.py b/core/dbt/graph/selector_methods.py index 9a722cb613c..5df8efbaac6 100644 --- a/core/dbt/graph/selector_methods.py +++ b/core/dbt/graph/selector_methods.py @@ -108,10 +108,16 @@ def is_selected_node(fqn: List[str], node_selector: str, is_versioned: bool) -> # rest of the fqn, this is 100% backwards compatible with the old behavior of # encountering a wildcard but more expressive in naturally allowing you to # match the rest of the fqn with more advanced patterns - return fnmatch( + if fnmatch( ".".join(flat_fqn[slurp_from_ix:]), ".".join(node_selector.split(".")[slurp_from_ix:]), - ) + ): + return True + # A bare wildcard selector (no dot) should also match against just the + # fqn's leaf segment, regardless of how deeply the node is nested. + if not is_versioned and "." not in node_selector: + return fnmatch(fqn[-1], node_selector) + return False # if we get all the way down here, then the node is a match return True diff --git a/tests/unit/graph/test_selector_methods.py b/tests/unit/graph/test_selector_methods.py index ef3ad91d248..9c27f951b4b 100644 --- a/tests/unit/graph/test_selector_methods.py +++ b/tests/unit/graph/test_selector_methods.py @@ -158,6 +158,21 @@ def test_select_fqn(manifest): } +def test_qualified_name_bare_wildcard_matches_root_leaf(manifest): + # Regression guard: bare wildcard selectors (no dot) already work for + # root-level nodes. Documents the asymmetry alongside the nested-leaf case below. + method = QualifiedNameSelectorMethod(manifest, None, []) + assert method.node_is_match("d_*", ["my_pkg", "d_model"], is_versioned=False) + + +def test_qualified_name_bare_wildcard_matches_nested_leaf(manifest): + # CORE-937: a bare wildcard selector (e.g. "f_*", no dot) should match the + # leaf model name regardless of how deep the model is nested in subfolders. + method = QualifiedNameSelectorMethod(manifest, None, []) + fqn = ["my_pkg", "marts", "subdir", "f_model"] + assert method.node_is_match("f_*", fqn, is_versioned=False) + + def test_select_tag(manifest): methods = MethodManager(manifest, None) method = methods.get_method("tag", []) From 6b3d48361bc5232c37cfa6b6889bcb67be3d8e8d Mon Sep 17 00:00:00 2001 From: Naman Goyal <43905880+itsnamangoyal@users.noreply.github.com> Date: Thu, 17 Sep 2026 23:37:48 +0530 Subject: [PATCH 2/2] fix: match bare wildcard selectors against versioned model names too Addresses review feedback on #16348: nested versioned models were excluded because the fqn's last segment is the version tag (e.g. "v4"), not the model name. --- core/dbt/graph/selector_methods.py | 9 ++++++--- tests/unit/graph/test_selector_methods.py | 9 +++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/core/dbt/graph/selector_methods.py b/core/dbt/graph/selector_methods.py index 5df8efbaac6..1af852e6a43 100644 --- a/core/dbt/graph/selector_methods.py +++ b/core/dbt/graph/selector_methods.py @@ -114,9 +114,12 @@ def is_selected_node(fqn: List[str], node_selector: str, is_versioned: bool) -> ): return True # A bare wildcard selector (no dot) should also match against just the - # fqn's leaf segment, regardless of how deeply the node is nested. - if not is_versioned and "." not in node_selector: - return fnmatch(fqn[-1], node_selector) + # fqn's leaf segment, regardless of how deeply the node is nested. For + # versioned models, the leaf segment is the version tag (e.g. "v4"), + # so match against the model name segment (fqn[-2]) instead. + if "." not in node_selector: + leaf = fqn[-2] if is_versioned else fqn[-1] + return fnmatch(leaf, node_selector) return False # if we get all the way down here, then the node is a match diff --git a/tests/unit/graph/test_selector_methods.py b/tests/unit/graph/test_selector_methods.py index 9c27f951b4b..369edead822 100644 --- a/tests/unit/graph/test_selector_methods.py +++ b/tests/unit/graph/test_selector_methods.py @@ -173,6 +173,15 @@ def test_qualified_name_bare_wildcard_matches_nested_leaf(manifest): assert method.node_is_match("f_*", fqn, is_versioned=False) +def test_qualified_name_bare_wildcard_matches_nested_versioned_leaf(manifest): + # CORE-937 follow-up: for a versioned model, the fqn's last segment is the + # version tag (e.g. "v4"), not the model name, so the bare wildcard match + # must fall back to the name segment (fqn[-2]) instead of fqn[-1]. + method = QualifiedNameSelectorMethod(manifest, None, []) + fqn = ["my_pkg", "nested_dir", "versioned_model", "v4"] + assert method.node_is_match("versioned_model*", fqn, is_versioned=True) + + def test_select_tag(manifest): methods = MethodManager(manifest, None) method = methods.get_method("tag", [])