@@ -75,17 +75,22 @@ def _is_metadata_var(
7575 ) -> t .Optional [bool ]:
7676 is_metadata_so_far = used_variables .get (name , True )
7777 if is_metadata_so_far is False :
78+ # We've concluded this variable is definitely not metadata-only
7879 return False
7980
8081 appears_under_metadata_macro_func = expr_under_metadata_macro_func .get (id (expression ))
8182 if is_metadata_so_far and (
8283 appears_in_metadata_expression or appears_under_metadata_macro_func
8384 ):
85+ # The variable appears in a metadata expression, e.g., audits (...),
86+ # or in the AST of metadata-only macro call, e.g., @FOO(@x)
8487 return True
8588
89+ # The variable appears in the AST of a macro call, but we don't know if it's metadata-only
8690 if appears_under_metadata_macro_func is False :
8791 return None
8892
93+ # The variable appears elsewhere, e.g., in the model's query: SELECT @x
8994 return False
9095
9196 def _is_metadata_macro (name : str , appears_in_metadata_expression : bool ) -> bool :
@@ -131,6 +136,12 @@ def _is_metadata_macro(name: str, appears_in_metadata_expression: bool) -> bool:
131136 var_name , macro_func_or_var , is_metadata
132137 )
133138 elif id (macro_func_or_var ) not in visited_macro_funcs :
139+ # We only care about the top-level macro func calls to determine the metadata
140+ # status of the variables referenced in their ASTs. If the top-level call is
141+ # known to be metadata-only or appear in a metadata expression, then we don't
142+ # need to traverse the sub-macro func calls in its AST, hence why we track
143+ # visited macro funcs and the outermost macro func call ancestors by var name.
144+
134145 var_refs , _expr_under_metadata_macro_func , _visited_macro_funcs = (
135146 _extract_macro_func_variable_references (macro_func_or_var , is_metadata )
136147 )
@@ -192,7 +203,7 @@ def _extract_macro_func_variable_references(
192203 macro_func : exp .Expression ,
193204 is_metadata : bool ,
194205) -> t .Tuple [t .Set [str ], t .Dict [int , bool ], t .Set [int ]]:
195- references = set ()
206+ var_references = set ()
196207 visited_macro_funcs = set ()
197208 expr_under_metadata_macro_func = {}
198209
@@ -204,19 +215,19 @@ def _extract_macro_func_variable_references(
204215 args = this .expressions
205216
206217 if this .name .lower () in (c .VAR , c .BLUEPRINT_VAR ) and args and args [0 ].is_string :
207- references .add (args [0 ].this .lower ())
218+ var_references .add (args [0 ].this .lower ())
208219 expr_under_metadata_macro_func [id (n )] = is_metadata
209220 elif isinstance (n , d .MacroVar ):
210- references .add (n .name .lower ())
221+ var_references .add (n .name .lower ())
211222 expr_under_metadata_macro_func [id (n )] = is_metadata
212223 elif isinstance (n , (exp .Identifier , d .MacroStrReplace , d .MacroSQL )) and "@" in n .name :
213- references .update (
224+ var_references .update (
214225 (braced_identifier or identifier ).lower ()
215226 for _ , identifier , braced_identifier , _ in MacroStrTemplate .pattern .findall (n .name )
216227 )
217228 expr_under_metadata_macro_func [id (n )] = is_metadata
218229
219- return (references , expr_under_metadata_macro_func , visited_macro_funcs )
230+ return (var_references , expr_under_metadata_macro_func , visited_macro_funcs )
220231
221232
222233def _add_variables_to_python_env (
@@ -238,16 +249,22 @@ def _add_variables_to_python_env(
238249 for var_name , is_metadata in python_used_variables .items ():
239250 used_variables [var_name ] = is_metadata and used_variables .get (var_name , True )
240251
241- # Variables are treated as metadata when:
242- # - They are only referenced in metadata-only contexts, such as `audits (...)`, virtual statements, etc
243- # - They are only referenced in metadata-only macros, either as their arguments or within their definitions
252+ # Variables are treated as metadata-only when all of their references either:
253+ # - appear in metadata-only expressions, such as `audits (...)`, virtual statements, etc
254+ # - appear in the ASTs or definitions of metadata-only macros
255+ #
256+ # See also: https://github.com/TobikoData/sqlmesh/pull/4936#issuecomment-3136339936,
257+ # specifically the "Terminology" and "Observations" section.
244258 metadata_used_variables = {
245259 var_name for var_name , is_metadata in used_variables .items () if is_metadata
246260 }
247261 for used_var , outermost_macro_func in (outermost_macro_func_ancestor_by_var or {}).items ():
248262 used_var_is_metadata = used_variables .get (used_var )
249263 if used_var_is_metadata is False :
250264 continue
265+
266+ # At this point we can decide whether a variable reference in a macro call's AST is
267+ # metadata-only, because we've annotated the corresponding macro call in the python env.
251268 if outermost_macro_func in python_env and python_env [outermost_macro_func ].is_metadata :
252269 metadata_used_variables .add (used_var )
253270
0 commit comments