1616from sqlmesh .core .dialect import normalize_model_name
1717from sqlmesh .core .environment import Environment
1818from sqlmesh .core .model import update_model_schemas
19+ from sqlmesh .core .audit import StandaloneAudit
1920from sqlmesh .utils import UniqueKeyDict
2021from sqlmesh .utils .dag import DAG
2122from sqlmesh .utils .git import GitClient
2526if t .TYPE_CHECKING :
2627 from typing_extensions import Literal as Lit # noqa
2728 from sqlmesh .core .model import Model
29+ from sqlmesh .core .node import Node
2830 from sqlmesh .core .state_sync import StateReader
2931
3032
@@ -167,7 +169,7 @@ def get_model(fqn: str) -> t.Optional[Model]:
167169 return models
168170
169171 def expand_model_selections (
170- self , model_selections : t .Iterable [str ], models : t .Optional [t .Dict [str , Model ]] = None
172+ self , model_selections : t .Iterable [str ], models : t .Optional [t .Dict [str , Node ]] = None
171173 ) -> t .Set [str ]:
172174 """Expands a set of model selections into a set of model fqns that can be looked up in the Context.
173175
@@ -180,7 +182,7 @@ def expand_model_selections(
180182
181183 node = parse (" | " .join (f"({ s } )" for s in model_selections ))
182184
183- all_models = models or self ._models
185+ all_models : t . Dict [ str , Node ] = models or dict ( self ._models )
184186 models_by_tags : t .Dict [str , t .Set [str ]] = {}
185187
186188 for fqn , model in all_models .items ():
@@ -226,6 +228,13 @@ def evaluate(node: exp.Expression) -> t.Set[str]:
226228 if fnmatch .fnmatchcase (tag , pattern )
227229 }
228230 return models_by_tags .get (pattern , set ())
231+ if isinstance (node , ResourceType ):
232+ resource_type = node .name .lower ()
233+ return {
234+ fqn
235+ for fqn , model in all_models .items ()
236+ if self ._matches_resource_type (resource_type , model )
237+ }
229238 if isinstance (node , Direction ):
230239 selected = set ()
231240
@@ -243,36 +252,49 @@ def evaluate(node: exp.Expression) -> t.Set[str]:
243252 return evaluate (node )
244253
245254 @abc .abstractmethod
246- def _model_name (self , model : Model ) -> str :
255+ def _model_name (self , model : Node ) -> str :
247256 """Given a model, return the name that a selector pattern contining wildcards should be fnmatch'd on"""
248257 pass
249258
250259 @abc .abstractmethod
251- def _pattern_to_model_fqns (self , pattern : str , all_models : t .Dict [str , Model ]) -> t .Set [str ]:
260+ def _pattern_to_model_fqns (self , pattern : str , all_models : t .Dict [str , Node ]) -> t .Set [str ]:
252261 """Given a pattern, return the keys of the matching models from :all_models"""
253262 pass
254263
264+ @abc .abstractmethod
265+ def _matches_resource_type (self , resource_type : str , model : Node ) -> bool :
266+ """Indicate whether or not the supplied model matches the supplied resource type"""
267+ pass
268+
255269
256270class NativeSelector (Selector ):
257271 """Implementation of selectors that matches objects based on SQLMesh native names"""
258272
259- def _model_name (self , model : Model ) -> str :
273+ def _model_name (self , model : Node ) -> str :
260274 return model .name
261275
262- def _pattern_to_model_fqns (self , pattern : str , all_models : t .Dict [str , Model ]) -> t .Set [str ]:
276+ def _pattern_to_model_fqns (self , pattern : str , all_models : t .Dict [str , Node ]) -> t .Set [str ]:
263277 fqn = normalize_model_name (pattern , self ._default_catalog , self ._dialect )
264278 return {fqn } if fqn in all_models else set ()
265279
280+ def _matches_resource_type (self , resource_type : str , model : Node ) -> bool :
281+ if resource_type == "model" :
282+ return model .is_model
283+ if resource_type == "audit" :
284+ return isinstance (model , StandaloneAudit )
285+
286+ raise SQLMeshError (f"Unsupported resource type: { resource_type } " )
287+
266288
267289class DbtSelector (Selector ):
268290 """Implementation of selectors that matches objects based on the DBT names instead of the SQLMesh native names"""
269291
270- def _model_name (self , model : Model ) -> str :
292+ def _model_name (self , model : Node ) -> str :
271293 if dbt_fqn := model .dbt_fqn :
272294 return dbt_fqn
273295 raise SQLMeshError ("dbt node information must be populated to use dbt selectors" )
274296
275- def _pattern_to_model_fqns (self , pattern : str , all_models : t .Dict [str , Model ]) -> t .Set [str ]:
297+ def _pattern_to_model_fqns (self , pattern : str , all_models : t .Dict [str , Node ]) -> t .Set [str ]:
276298 # a pattern like "staging.customers" should match a model called "jaffle_shop.staging.customers"
277299 # but not a model called "jaffle_shop.customers.staging"
278300 # also a pattern like "aging" should not match "staging" so we need to consider components; not substrings
@@ -306,6 +328,40 @@ def _pattern_to_model_fqns(self, pattern: str, all_models: t.Dict[str, Model]) -
306328 matches .add (fqn )
307329 return matches
308330
331+ def _matches_resource_type (self , resource_type : str , model : Node ) -> bool :
332+ """
333+ ref: https://docs.getdbt.com/reference/node-selection/methods#resource_type
334+
335+ # supported by SQLMesh
336+ "model"
337+ "seed"
338+ "source" # external model
339+ "test" # standalone audit
340+
341+ # not supported by SQLMesh yet, commented out to throw an error if someone tries to use them
342+ "analysis"
343+ "exposure"
344+ "metric"
345+ "saved_query"
346+ "semantic_model"
347+ "snapshot"
348+ "unit_test"
349+ """
350+ if resource_type not in ("model" , "seed" , "source" , "test" ):
351+ raise SQLMeshError (f"Unsupported resource type: { resource_type } " )
352+
353+ if isinstance (model , StandaloneAudit ):
354+ return resource_type == "test"
355+
356+ if resource_type == "model" :
357+ return model .is_model and not model .kind .is_external and not model .kind .is_seed
358+ if resource_type == "source" :
359+ return model .kind .is_external
360+ if resource_type == "seed" :
361+ return model .kind .is_seed
362+
363+ return False
364+
309365
310366class SelectorDialect (Dialect ):
311367 IDENTIFIERS_CAN_START_WITH_DIGIT = True
@@ -336,6 +392,10 @@ class Tag(exp.Expression):
336392 pass
337393
338394
395+ class ResourceType (exp .Expression ):
396+ pass
397+
398+
339399class Direction (exp .Expression ):
340400 pass
341401
@@ -388,7 +448,8 @@ def _parse_var() -> exp.Expression:
388448 upstream = _match (TokenType .PLUS )
389449 downstream = None
390450 tag = _parse_kind ("tag" )
391- git = False if tag else _parse_kind ("git" )
451+ resource_type = False if tag else _parse_kind ("resource_type" )
452+ git = False if resource_type else _parse_kind ("git" )
392453 lstar = "*" if _match (TokenType .STAR ) else ""
393454 directions = {}
394455
@@ -414,6 +475,8 @@ def _parse_var() -> exp.Expression:
414475
415476 if tag :
416477 this = Tag (this = this )
478+ if resource_type :
479+ this = ResourceType (this = this )
417480 if git :
418481 this = Git (this = this )
419482 if directions :
0 commit comments