From 1b0d59ff0a5a2af60d801cbd92d6fd2ec32ab768 Mon Sep 17 00:00:00 2001 From: Jesse Hodges Date: Tue, 17 Feb 2026 09:37:42 -0600 Subject: [PATCH] selecting models by name when the name contains quotes now works --- sqlmesh/core/selector.py | 20 +++++++++++++- tests/core/test_selector_quoted_wildcard.py | 29 +++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/core/test_selector_quoted_wildcard.py diff --git a/sqlmesh/core/selector.py b/sqlmesh/core/selector.py index 3865327acd..3cd97f813b 100644 --- a/sqlmesh/core/selector.py +++ b/sqlmesh/core/selector.py @@ -191,14 +191,32 @@ def expand_model_selections( models_by_tags.setdefault(tag, set()) models_by_tags[tag].add(model.fqn) + def _normalize_for_match(name: str) -> str: + """Strip surrounding identifier quotes on each component for glob-style matching.""" + parts = name.split(".") + + def _unquote(part: str) -> str: + if len(part) >= 2 and part[0] == part[-1] and part[0] in {"'", '"', "`", "["}: + # For [] quoting, ensure trailing ] + if part[0] == "[" and part[-1] != "]": + return part + return part[1:-1] + return part + + return ".".join(_unquote(p) for p in parts) + def evaluate(node: exp.Expression) -> t.Set[str]: if isinstance(node, exp.Var): pattern = node.this if "*" in pattern: + normalized_pattern = _normalize_for_match(pattern) return { fqn for fqn, model in all_models.items() - if fnmatch.fnmatchcase(self._model_name(model), node.this) + if fnmatch.fnmatchcase( + _normalize_for_match(self._model_name(model)), + normalized_pattern, + ) } return self._pattern_to_model_fqns(pattern, all_models) if isinstance(node, exp.And): diff --git a/tests/core/test_selector_quoted_wildcard.py b/tests/core/test_selector_quoted_wildcard.py new file mode 100644 index 0000000000..cfdbf328f7 --- /dev/null +++ b/tests/core/test_selector_quoted_wildcard.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +import fnmatch + +import pytest + +from sqlmesh.core.selector import parse + + +@pytest.mark.parametrize( + ("selector", "expected"), + [ + ("foo.bar", "foo.bar"), + ("foo*", "foo*"), + ("*bar", "*bar"), + ("'biquery-project'.schema.table", "'biquery-project'.schema.table"), + ("'biquery-project'.schema*", "'biquery-project'.schema*"), + ('"biquery-project".schema*', '"biquery-project".schema*'), + ("`biquery-project`.schema*", "`biquery-project`.schema*"), + ], +) +def test_parse_preserves_quotes_in_var(selector: str, expected: str) -> None: + node = parse(selector) + assert node.this == expected + + +def test_fnmatch_works_with_quotes_in_pattern() -> None: + model_name = "'biquery-project'.schema.table" + assert fnmatch.fnmatchcase(model_name, "'biquery-project'.schema*")