5
5
from pathlib import Path
6
6
7
7
from astor import to_source
8
- from collections import defaultdict
9
8
from difflib import get_close_matches
10
9
from sqlglot import exp
11
10
from sqlglot .helper import ensure_list
@@ -67,9 +66,9 @@ def make_python_env(
67
66
# id(expr) -> false: expr appears under the AST of a macro function whose metadata status we don't yet know
68
67
expr_under_metadata_macro_func : t .Dict [int , bool ] = {}
69
68
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 ( )
73
72
74
73
def _is_metadata_var (
75
74
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:
131
130
used_variables [var_name ] = _is_metadata_var (
132
131
name , macro_func_or_var , is_metadata
133
132
)
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 = (
136
135
_extract_macro_func_variable_references (macro_func_or_var , is_metadata )
137
136
)
138
137
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 }
141
140
elif macro_func_or_var .__class__ is d .MacroVar :
142
141
name = macro_func_or_var .name .lower ()
143
142
if name in macros :
@@ -180,28 +179,22 @@ def _is_metadata_macro(name: str, appears_in_metadata_expression: bool) -> bool:
180
179
blueprint_variables = blueprint_variables ,
181
180
dialect = dialect ,
182
181
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 ,
184
183
)
185
184
186
185
187
186
def _extract_macro_func_variable_references (
188
187
macro_func : exp .Expression ,
189
188
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 ] ]:
191
190
references = set ()
191
+ visited_macro_funcs = set ()
192
192
expr_under_metadata_macro_func = {}
193
193
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 ():
204
195
if type (n ) is d .MacroFunc :
196
+ visited_macro_funcs .add (id (n ))
197
+
205
198
this = n .this
206
199
args = this .expressions
207
200
@@ -218,7 +211,7 @@ def _prune_nested_macro_func(expression: exp.Expression) -> bool:
218
211
)
219
212
expr_under_metadata_macro_func [id (n )] = is_metadata
220
213
221
- return (references , expr_under_metadata_macro_func )
214
+ return (references , expr_under_metadata_macro_func , visited_macro_funcs )
222
215
223
216
224
217
def _add_variables_to_python_env (
@@ -228,7 +221,7 @@ def _add_variables_to_python_env(
228
221
strict_resolution : bool = True ,
229
222
blueprint_variables : t .Optional [t .Dict [str , t .Any ]] = None ,
230
223
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 ,
232
225
) -> t .Dict [str , Executable ]:
233
226
_ , python_used_variables = parse_dependencies (
234
227
python_env ,
@@ -244,13 +237,13 @@ def _add_variables_to_python_env(
244
237
# - They are only referenced in metadata-only contexts, such as `audits (...)`, virtual statements, etc
245
238
# - They are only referenced in metadata-only macros, either as their arguments or within their definitions
246
239
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 ():
248
241
used_var_is_metadata = used_variables .get (used_var )
249
242
if used_var_is_metadata is False :
250
243
continue
251
244
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
254
247
):
255
248
metadata_used_variables .add (used_var )
256
249
0 commit comments