1313# limitations under the License.
1414from __future__ import annotations
1515
16+ import dataclasses
1617import typing
1718
1819from bigframes .core import identifiers , nodes
@@ -26,32 +27,68 @@ def remap_variables(
2627 nodes .BigFrameNode ,
2728 dict [identifiers .ColumnId , identifiers .ColumnId ],
2829]:
29- """Remaps `ColumnId`s in the BFET to produce deterministic and sequential UIDs .
30+ """Remaps `ColumnId`s in the expression tree to be deterministic and sequential.
3031
31- Note: this will convert a DAG to a tree.
32+ This function performs a post-order traversal. It recursively remaps children
33+ nodes first, then remaps the current node's references and definitions.
34+
35+ Note: this will convert a DAG to a tree by duplicating shared nodes.
36+
37+ Args:
38+ root: The root node of the expression tree.
39+ id_generator: An iterator that yields new column IDs.
40+
41+ Returns:
42+ A tuple of the new root node and a mapping from old to new column IDs
43+ visible to the parent node.
3244 """
33- child_replacement_map = dict ()
34- ref_mapping = dict ()
35- # Sequential ids are assigned bottom-up left-to-right
45+ # Step 1: Recursively remap children to get their new nodes and ID mappings.
46+ new_child_nodes : list [ nodes . BigFrameNode ] = []
47+ new_child_mappings : list [ dict [ identifiers . ColumnId , identifiers . ColumnId ]] = []
3648 for child in root .child_nodes :
37- new_child , child_var_mapping = remap_variables (child , id_generator = id_generator )
38- child_replacement_map [child ] = new_child
39- ref_mapping .update (child_var_mapping )
40-
41- # This is actually invalid until we've replaced all of children, refs and var defs
42- with_new_children = root .transform_children (
43- lambda node : child_replacement_map [node ]
44- )
45-
46- with_new_refs = with_new_children .remap_refs (ref_mapping )
47-
48- node_var_mapping = {old_id : next (id_generator ) for old_id in root .node_defined_ids }
49- with_new_vars = with_new_refs .remap_vars (node_var_mapping )
50- with_new_vars ._validate ()
51-
52- return (
53- with_new_vars ,
54- node_var_mapping
55- if root .defines_namespace
56- else (ref_mapping | node_var_mapping ),
57- )
49+ new_child , child_mappings = remap_variables (child , id_generator = id_generator )
50+ new_child_nodes .append (new_child )
51+ new_child_mappings .append (child_mappings )
52+
53+ # Step 2: Transform children to use their new nodes.
54+ remapped_children : dict [nodes .BigFrameNode , nodes .BigFrameNode ] = {
55+ child : new_child for child , new_child in zip (root .child_nodes , new_child_nodes )
56+ }
57+ new_root = root .transform_children (lambda node : remapped_children [node ])
58+
59+ # Step 3: Transform the current node using the mappings from its children.
60+ downstream_mappings : dict [identifiers .ColumnId , identifiers .ColumnId ] = {
61+ k : v for mapping in new_child_mappings for k , v in mapping .items ()
62+ }
63+ if isinstance (new_root , nodes .InNode ):
64+ new_root = typing .cast (nodes .InNode , new_root )
65+ new_root = dataclasses .replace (
66+ new_root ,
67+ left_col = new_root .left_col .remap_column_refs (
68+ new_child_mappings [0 ], allow_partial_bindings = True
69+ ),
70+ right_col = new_root .right_col .remap_column_refs (
71+ new_child_mappings [1 ], allow_partial_bindings = True
72+ ),
73+ )
74+ else :
75+ new_root = new_root .remap_refs (downstream_mappings )
76+
77+ # Step 4: Create new IDs for columns defined by the current node.
78+ node_defined_mappings = {
79+ old_id : next (id_generator ) for old_id in root .node_defined_ids
80+ }
81+ new_root = new_root .remap_vars (node_defined_mappings )
82+
83+ new_root ._validate ()
84+
85+ # Step 5: Determine which mappings to propagate up to the parent.
86+ if root .defines_namespace :
87+ # If a node defines a new namespace (e.g., a join), mappings from its
88+ # children are not visible to its parents.
89+ mappings_for_parent = node_defined_mappings
90+ else :
91+ # Otherwise, pass up the combined mappings from children and the current node.
92+ mappings_for_parent = downstream_mappings | node_defined_mappings
93+
94+ return new_root , mappings_for_parent
0 commit comments