33import fnmatch
44import typing as t
55from pathlib import Path
6+ from itertools import zip_longest
67
78from sqlglot import exp
89from sqlglot .errors import ParseError
@@ -36,6 +37,7 @@ def __init__(
3637 default_catalog : t .Optional [str ] = None ,
3738 dialect : t .Optional [str ] = None ,
3839 cache_dir : t .Optional [Path ] = None ,
40+ dbt_mode : bool = False ,
3941 ):
4042 self ._state_reader = state_reader
4143 self ._models = models
@@ -44,6 +46,7 @@ def __init__(
4446 self ._default_catalog = default_catalog
4547 self ._dialect = dialect
4648 self ._git_client = GitClient (context_path )
49+ self ._dbt_mode = dbt_mode
4750
4851 if dag is None :
4952 self ._dag : DAG [str ] = DAG ()
@@ -167,13 +170,13 @@ def get_model(fqn: str) -> t.Optional[Model]:
167170 def expand_model_selections (
168171 self , model_selections : t .Iterable [str ], models : t .Optional [t .Dict [str , Model ]] = None
169172 ) -> t .Set [str ]:
170- """Expands a set of model selections into a set of model names .
173+ """Expands a set of model selections into a set of model fqns that can be looked up in the Context .
171174
172175 Args:
173176 model_selections: A set of model selections.
174177
175178 Returns:
176- A set of model names .
179+ A set of model fqns .
177180 """
178181
179182 node = parse (" | " .join (f"({ s } )" for s in model_selections ))
@@ -194,10 +197,9 @@ def evaluate(node: exp.Expression) -> t.Set[str]:
194197 return {
195198 fqn
196199 for fqn , model in all_models .items ()
197- if fnmatch .fnmatchcase (model . name , node .this )
200+ if fnmatch .fnmatchcase (self . _model_name ( model ) , node .this )
198201 }
199- fqn = normalize_model_name (pattern , self ._default_catalog , self ._dialect )
200- return {fqn } if fqn in all_models else set ()
202+ return self ._pattern_to_model_fqns (pattern , all_models )
201203 if isinstance (node , exp .And ):
202204 return evaluate (node .left ) & evaluate (node .right )
203205 if isinstance (node , exp .Or ):
@@ -241,6 +243,59 @@ def evaluate(node: exp.Expression) -> t.Set[str]:
241243
242244 return evaluate (node )
243245
246+ def _model_fqn (self , model : Model ) -> str :
247+ if self ._dbt_mode :
248+ dbt_fqn = model .dbt_fqn
249+ if dbt_fqn is None :
250+ raise SQLMeshError ("Expecting dbt node information to be populated; it wasnt" )
251+ return dbt_fqn
252+ return model .fqn
253+
254+ def _model_name (self , model : Model ) -> str :
255+ if self ._dbt_mode :
256+ # dbt always matches on the fqn, not the name
257+ return self ._model_fqn (model )
258+ return model .name
259+
260+ def _pattern_to_model_fqns (self , pattern : str , all_models : t .Dict [str , Model ]) -> t .Set [str ]:
261+ # note: all_models should be keyed by sqlmesh fqn, not dbt fqn
262+ if not self ._dbt_mode :
263+ fqn = normalize_model_name (pattern , self ._default_catalog , self ._dialect )
264+ return {fqn } if fqn in all_models else set ()
265+
266+ # a pattern like "staging.customers" should match a model called "jaffle_shop.staging.customers"
267+ # but not a model called "jaffle_shop.customers.staging"
268+ # also a pattern like "aging" should not match "staging" so we need to consider components; not substrings
269+ pattern_components = pattern .split ("." )
270+ first_pattern_component = pattern_components [0 ]
271+ matches = set ()
272+ for fqn , model in all_models .items ():
273+ if not model .dbt_fqn :
274+ continue
275+
276+ dbt_fqn_components = model .dbt_fqn .split ("." )
277+ try :
278+ starting_idx = dbt_fqn_components .index (first_pattern_component )
279+ except ValueError :
280+ continue
281+ for pattern_component , fqn_component in zip_longest (
282+ pattern_components , dbt_fqn_components [starting_idx :]
283+ ):
284+ if pattern_component and not fqn_component :
285+ # the pattern still goes but we have run out of fqn components to match; no match
286+ break
287+ if fqn_component and not pattern_component :
288+ # all elements of the pattern have matched elements of the fqn; match
289+ matches .add (fqn )
290+ break
291+ if pattern_component != fqn_component :
292+ # the pattern explicitly doesnt match a component; no match
293+ break
294+ else :
295+ # called if no explicit break, indicating all components of the pattern matched all components of the fqn
296+ matches .add (fqn )
297+ return matches
298+
244299
245300class SelectorDialect (Dialect ):
246301 IDENTIFIERS_CAN_START_WITH_DIGIT = True
0 commit comments