Skip to main content

vortex_array/expr/analysis/
labeling.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::hash::Hash;
5
6use vortex_error::VortexExpect;
7use vortex_error::VortexResult;
8use vortex_utils::aliases::hash_map::HashMap;
9
10use crate::expr::BoundExpression;
11use crate::expr::ExactBoundExpr;
12use crate::expr::Expression;
13use crate::expr::traversal::Node;
14use crate::expr::traversal::NodeExt;
15use crate::expr::traversal::NodeVisitor;
16use crate::expr::traversal::TraversalOrder;
17
18/// Boolean labels keyed by each expression node in a tree.
19pub type BooleanLabels<'a, N = Expression> = HashMap<&'a N, bool>;
20
21/// Labels keyed by bound-tree identity.
22pub type BoundLabels<L> = HashMap<ExactBoundExpr, L>;
23
24/// Label each node in an expression tree using a bottom-up traversal.
25///
26/// This function separates tree labeling into two distinct steps:
27/// 1. **Label Self**: Compute a label for each node based only on the node itself
28/// 2. **Merge Child**: Fold/accumulate labels from children into the node's self-label
29///
30/// The labeling process:
31/// - First, `self_label` is called on the node to produce its self-label
32/// - Then, for each child, `merge_child` is called with `(self_label, child_label)`
33///   to fold the child label into the self_label
34/// - This produces the final label for the node
35///
36/// # Parameters
37///
38/// - `expr`: The root expression to label
39/// - `self_label`: Function that computes a label for a single node
40/// - `merge_child`: Mutable function that folds child labels into an accumulator.
41///   Takes `(self_label, child_label)` and returns the updated accumulator.
42///   Called once per child, with the initial accumulator being the node's self-label.
43pub fn label_tree<N, L: Clone>(
44    expr: &N,
45    self_label: impl Fn(&N) -> L,
46    mut merge_child: impl FnMut(L, &L) -> L,
47) -> HashMap<&N, L>
48where
49    N: Node + Eq + Hash,
50{
51    let mut visitor = LabelingVisitor {
52        labels: Default::default(),
53        self_label,
54        merge_child: &mut merge_child,
55    };
56    expr.accept(&mut visitor)
57        .vortex_expect("LabelingVisitor is infallible");
58    visitor.labels
59}
60
61/// Label each node in a bound expression using identity-keyed lookups.
62///
63/// This avoids structurally hashing bound dtypes, which may deserialize a lazy schema.
64pub fn label_bound_tree<L: Clone>(
65    expr: &BoundExpression,
66    self_label: impl Fn(&BoundExpression) -> L,
67    mut merge_child: impl FnMut(L, &L) -> L,
68) -> BoundLabels<L> {
69    let mut visitor = BoundLabelingVisitor {
70        labels: Default::default(),
71        self_label,
72        merge_child: &mut merge_child,
73    };
74    expr.accept(&mut visitor)
75        .vortex_expect("BoundLabelingVisitor is infallible");
76    visitor.labels
77}
78
79struct LabelingVisitor<'a, 'b, N, L, F, G>
80where
81    N: Node + Eq + Hash,
82    F: Fn(&N) -> L,
83    G: FnMut(L, &L) -> L,
84{
85    labels: HashMap<&'a N, L>,
86    self_label: F,
87    merge_child: &'b mut G,
88}
89
90impl<'a, 'b, N, L: Clone, F, G> NodeVisitor<'a> for LabelingVisitor<'a, 'b, N, L, F, G>
91where
92    N: Node + Eq + Hash,
93    F: Fn(&N) -> L,
94    G: FnMut(L, &L) -> L,
95{
96    type NodeTy = N;
97
98    fn visit_down(&mut self, _node: &'a Self::NodeTy) -> VortexResult<TraversalOrder> {
99        Ok(TraversalOrder::Continue)
100    }
101
102    fn visit_up(&mut self, node: &'a N) -> VortexResult<TraversalOrder> {
103        let self_label = (self.self_label)(node);
104
105        let final_label = node.iter_children(|children| {
106            children.fold(self_label, |acc, child| {
107                let child_label = self
108                    .labels
109                    .get(child)
110                    .vortex_expect("child must have label");
111                (self.merge_child)(acc, child_label)
112            })
113        });
114
115        self.labels.insert(node, final_label);
116
117        Ok(TraversalOrder::Continue)
118    }
119}
120
121struct BoundLabelingVisitor<'a, L, F, G>
122where
123    F: Fn(&BoundExpression) -> L,
124    G: FnMut(L, &L) -> L,
125{
126    labels: BoundLabels<L>,
127    self_label: F,
128    merge_child: &'a mut G,
129}
130
131impl<'node, 'visitor, L: Clone, F, G> NodeVisitor<'node> for BoundLabelingVisitor<'visitor, L, F, G>
132where
133    F: Fn(&BoundExpression) -> L,
134    G: FnMut(L, &L) -> L,
135{
136    type NodeTy = BoundExpression;
137
138    fn visit_down(&mut self, _node: &'node Self::NodeTy) -> VortexResult<TraversalOrder> {
139        Ok(TraversalOrder::Continue)
140    }
141
142    fn visit_up(&mut self, node: &'node Self::NodeTy) -> VortexResult<TraversalOrder> {
143        let self_label = (self.self_label)(node);
144        let final_label = node.children().iter().fold(self_label, |acc, child| {
145            let child_label = self
146                .labels
147                .get(&ExactBoundExpr(child.clone()))
148                .vortex_expect("child must have label");
149            (self.merge_child)(acc, child_label)
150        });
151        self.labels
152            .insert(ExactBoundExpr(node.clone()), final_label);
153        Ok(TraversalOrder::Continue)
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160    use crate::expr::col;
161    use crate::expr::eq;
162    use crate::expr::lit;
163
164    #[test]
165    fn test_tree_depth() {
166        // Expression: $.col1 = 5
167        // Tree: eq(get_item(root(), "col1"), lit(5))
168        // Depth: root = 1, get_item = 2, lit = 1, eq = 3
169        let expr = eq(col("col1"), lit(5));
170        let depths = label_tree(
171            &expr,
172            |_node| 1, // Each node has depth 1 by itself
173            |self_depth, child_depth| self_depth.max(*child_depth + 1),
174        );
175
176        // The root (eq) should have depth 3
177        assert_eq!(depths.get(&expr), Some(&3));
178    }
179
180    #[test]
181    fn test_node_count() {
182        // Count total nodes in subtree (including self)
183        // Tree: eq(get_item(root(), "col1"), lit(5))
184        // Nodes: eq, get_item, root, lit = 4
185        let expr = eq(col("col1"), lit(5));
186        let counts = label_tree(
187            &expr,
188            |_node| 1, // Each node counts as 1
189            |self_count, child_count| self_count + *child_count,
190        );
191
192        // Root should have count of 4 (eq, get_item, root, lit)
193        assert_eq!(counts.get(&expr), Some(&4));
194    }
195}