55from pathlib import Path
66
77from astor import to_source
8- from collections import defaultdict
98from difflib import get_close_matches
109from sqlglot import exp
1110from sqlglot .helper import ensure_list
@@ -67,9 +66,9 @@ def make_python_env(
6766 # id(expr) -> false: expr appears under the AST of a macro function whose metadata status we don't yet know
6867 expr_under_metadata_macro_func : t .Dict [int , bool ] = {}
6968
70- # For an expression like @foo(@v1, @bar(@v1, @v2 ), @v3 ), the following mapping would be:
71- # v1 -> {"foo", "bar"}, v2 -> {"bar"}, v3 -> "foo"
72- macro_funcs_by_used_var : t .DefaultDict [ str , t . Set [str ]] = defaultdict ( set )
69+ # For @m1(@m2(@x ), @y ), we'd get x -> m1 and y -> m1
70+ outermost_macro_func_ancestor_by_var : t . Dict [ str , str ] = {}
71+ visited_macro_funcs : t .Set [int ] = set ( )
7372
7473 def _is_metadata_var (
7574 name : str , expression : exp .Expression , appears_in_metadata_expression : bool
@@ -131,13 +130,13 @@ def _is_metadata_macro(name: str, appears_in_metadata_expression: bool) -> bool:
131130 used_variables [var_name ] = _is_metadata_var (
132131 name , macro_func_or_var , is_metadata
133132 )
134- else :
135- var_refs , _expr_under_metadata_macro_func = (
133+ elif id ( macro_func_or_var ) not in visited_macro_funcs :
134+ var_refs , _expr_under_metadata_macro_func , _visited_macro_funcs = (
136135 _extract_macro_func_variable_references (macro_func_or_var , is_metadata )
137136 )
138137 expr_under_metadata_macro_func .update (_expr_under_metadata_macro_func )
139- for var_ref in var_refs :
140- macro_funcs_by_used_var [ var_ref ]. add ( name )
138+ visited_macro_funcs . update ( _visited_macro_funcs )
139+ outermost_macro_func_ancestor_by_var |= { var_ref : name for var_ref in var_refs }
141140 elif macro_func_or_var .__class__ is d .MacroVar :
142141 name = macro_func_or_var .name .lower ()
143142 if name in macros :
@@ -180,28 +179,22 @@ def _is_metadata_macro(name: str, appears_in_metadata_expression: bool) -> bool:
180179 blueprint_variables = blueprint_variables ,
181180 dialect = dialect ,
182181 strict_resolution = strict_resolution ,
183- macro_funcs_by_used_var = macro_funcs_by_used_var ,
182+ outermost_macro_func_ancestor_by_var = outermost_macro_func_ancestor_by_var ,
184183 )
185184
186185
187186def _extract_macro_func_variable_references (
188187 macro_func : exp .Expression ,
189188 is_metadata : bool ,
190- ) -> t .Tuple [t .Set [str ], t .Dict [int , bool ]]:
189+ ) -> t .Tuple [t .Set [str ], t .Dict [int , bool ], t . Set [ int ] ]:
191190 references = set ()
191+ visited_macro_funcs = set ()
192192 expr_under_metadata_macro_func = {}
193193
194- # Don't descend into nested MacroFunc nodes besides @VAR() and @BLUEPRINT_VAR(), because
195- # they will be handled in a separate call of _extract_macro_func_variable_references.
196- def _prune_nested_macro_func (expression : exp .Expression ) -> bool :
197- return (
198- type (expression ) is d .MacroFunc
199- and expression is not macro_func
200- and expression .this .name .lower () not in (c .VAR , c .BLUEPRINT_VAR )
201- )
202-
203- for n in macro_func .walk (prune = _prune_nested_macro_func ):
194+ for n in macro_func .walk ():
204195 if type (n ) is d .MacroFunc :
196+ visited_macro_funcs .add (id (n ))
197+
205198 this = n .this
206199 args = this .expressions
207200
@@ -218,7 +211,7 @@ def _prune_nested_macro_func(expression: exp.Expression) -> bool:
218211 )
219212 expr_under_metadata_macro_func [id (n )] = is_metadata
220213
221- return (references , expr_under_metadata_macro_func )
214+ return (references , expr_under_metadata_macro_func , visited_macro_funcs )
222215
223216
224217def _add_variables_to_python_env (
@@ -228,7 +221,7 @@ def _add_variables_to_python_env(
228221 strict_resolution : bool = True ,
229222 blueprint_variables : t .Optional [t .Dict [str , t .Any ]] = None ,
230223 dialect : DialectType = None ,
231- macro_funcs_by_used_var : t .Optional [t .DefaultDict [str , t . Set [ str ] ]] = None ,
224+ outermost_macro_func_ancestor_by_var : t .Optional [t .Dict [str , str ]] = None ,
232225) -> t .Dict [str , Executable ]:
233226 _ , python_used_variables = parse_dependencies (
234227 python_env ,
@@ -244,13 +237,13 @@ def _add_variables_to_python_env(
244237 # - They are only referenced in metadata-only contexts, such as `audits (...)`, virtual statements, etc
245238 # - They are only referenced in metadata-only macros, either as their arguments or within their definitions
246239 metadata_used_variables = set ()
247- for used_var , macro_names in (macro_funcs_by_used_var or {}).items ():
240+ for used_var , outermost_macro_func in (outermost_macro_func_ancestor_by_var or {}).items ():
248241 used_var_is_metadata = used_variables .get (used_var )
249242 if used_var_is_metadata is False :
250243 continue
251244
252- if used_var_is_metadata or all (
253- name in python_env and python_env [name ].is_metadata for name in macro_names
245+ if used_var_is_metadata or (
246+ outermost_macro_func in python_env and python_env [outermost_macro_func ].is_metadata
254247 ):
255248 metadata_used_variables .add (used_var )
256249
0 commit comments