Skip to main content

rudb_plan/
reducer.rs

1//! What a [`Node::Consistent`](crate::Node::Consistent) reads, and in which order.
2//!
3//! The node answers a MIN or MAX over an acyclic join without running the join. What makes that
4//! possible is that the join's relations can be laid out as a tree in which every pair of joined
5//! relations sharing a column class is connected by a path that carries it, the running
6//! intersection property, and on such a tree two sweeps of semijoins leave exactly the rows that
7//! take part in at least one joined row. The first sweep goes from the leaves up and the second
8//! from the root down. Everything the executor needs to run them is here: the relations, which
9//! columns of each are join keys and which class each key is in, which relation is each one's
10//! parent and on which class, and which column of which relation each extreme is read from.
11//!
12//! The relations are stored in the order they are scanned, and that order is part of the contract
13//! rather than a detail: every relation comes after all of its children. That is what lets the
14//! first sweep happen while the relations are being scanned rather than after, since by the time a
15//! relation's rows arrive the set of every child's keys is already built, and a row with no partner
16//! in one of them is dropped before it is stored anywhere. A root is last in its tree.
17//!
18//! A plan can hold a forest rather than a tree, when the query joins groups of relations that
19//! share no class and so are only a cross product of each other. Each tree is reduced on its own,
20//! and the product is empty exactly when one of them is.
21
22use rudb_common::{Error, Result};
23
24use crate::NodeRef;
25
26/// The relations of one consistent node and the join tree over them.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct Reducer {
29    /// One per relation, children before parents.
30    pub leaves: Vec<Leaf>,
31    /// How many classes of join columns there are, numbered from zero.
32    pub classes: u32,
33    /// One per produced column, in the order the node produces them.
34    pub extremes: Vec<Extreme>,
35}
36
37/// One relation of the join.
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct Leaf {
40    /// The plan that produces its rows, which is a scan or a filter over a scan.
41    ///
42    /// The scan carries only the columns this node reads, which are the keys, the columns an
43    /// extreme is read from and the columns the filter reads, and the positions below are
44    /// positions in what this produces.
45    pub input: NodeRef,
46    /// Every join column of the relation, one per class it is in.
47    pub keys: Vec<Key>,
48    /// The relation this one hangs under in the join tree and the class they share, or nothing
49    /// for a root.
50    pub parent: Option<Edge>,
51}
52
53/// One join column of a relation.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct Key {
56    /// Which class of equal columns it is in.
57    pub class: u32,
58    /// Its position in what the relation's input produces.
59    pub column: u32,
60}
61
62/// The line from a relation to its parent in the join tree.
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub struct Edge {
65    /// The parent's position in [`Reducer::leaves`].
66    pub leaf: u32,
67    /// The one class the two share.
68    pub class: u32,
69}
70
71/// One MIN or MAX the node produces.
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct Extreme {
74    /// The relation the column is in, as a position in [`Reducer::leaves`].
75    pub leaf: u32,
76    /// The column's position in what that relation's input produces.
77    pub column: u32,
78    /// Whether this is a MAX rather than a MIN.
79    pub max: bool,
80}
81
82impl Reducer {
83    /// The relations directly under `leaf`, each with the class it shares with `leaf`.
84    pub fn children(&self, leaf: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
85        self.leaves.iter().enumerate().filter_map(move |(child, held)| match held.parent {
86            Some(edge) if edge.leaf == leaf => Some((position(child), edge.class)),
87            _ => None,
88        })
89    }
90
91    /// The column of `leaf` that is in `class`, if it has one.
92    #[must_use]
93    pub fn key(&self, leaf: u32, class: u32) -> Option<u32> {
94        self.leaves[leaf as usize].keys.iter().find(|key| key.class == class).map(|key| key.column)
95    }
96
97    /// Whether some extreme is read from `leaf` or from a relation under it.
98    ///
99    /// The second sweep only has to reach the relations an extreme is read from, and a relation
100    /// with no extreme anywhere under it is one the second sweep can leave alone: nothing that is
101    /// read later depends on which of its rows survive it.
102    #[must_use]
103    pub fn wanted(&self, leaf: u32) -> bool {
104        self.extremes.iter().any(|extreme| self.under(extreme.leaf, leaf))
105    }
106
107    /// Whether the rows of `leaf` have to be held until the second sweep reaches it.
108    ///
109    /// A root is reduced completely by the first sweep alone, since everything in its tree is
110    /// under it, so its extremes and the keys it hands down are read off its rows as they are
111    /// scanned. Every other relation the second sweep reaches has to keep its rows until its parent
112    /// has been reduced, and that is the memory this node spends.
113    #[must_use]
114    pub fn held(&self, leaf: u32) -> bool {
115        self.leaves[leaf as usize].parent.is_some() && self.wanted(leaf)
116    }
117
118    /// Whether `leaf` is `ancestor` or somewhere under it.
119    fn under(&self, leaf: u32, ancestor: u32) -> bool {
120        let mut at = Some(leaf);
121        while let Some(here) = at {
122            if here == ancestor {
123                return true;
124            }
125            at = self.leaves[here as usize].parent.map(|edge| edge.leaf);
126        }
127        false
128    }
129
130    /// Checks the promises the executor relies on.
131    ///
132    /// A parent after its child, a class that both ends of an edge hold a column of, a class number
133    /// that is in range and an extreme that names a relation there is. Each of these broken is a
134    /// sweep that reads the wrong column or waits for a set that is built after it is needed, which
135    /// is a wrong answer rather than an error, so it is checked where the plan is.
136    ///
137    /// # Errors
138    ///
139    /// Naming the relation that broke one.
140    pub fn validate(&self) -> Result<()> {
141        let count = self.leaves.len();
142        for (at, leaf) in self.leaves.iter().enumerate() {
143            let fail = |what: &str| Err(Error::internal(format!("relation {at} {what}")));
144            if leaf.keys.iter().any(|key| key.class >= self.classes) {
145                return fail("has a key in a class that is not there");
146            }
147            if let Some(edge) = leaf.parent {
148                if edge.leaf as usize <= at || edge.leaf as usize >= count {
149                    return fail("hangs under a relation that is not after it");
150                }
151                if self.key(position(at), edge.class).is_none()
152                    || self.key(edge.leaf, edge.class).is_none()
153                {
154                    return fail(
155                        "shares a class with its parent that one of the two has no column in",
156                    );
157                }
158            }
159        }
160        if self.extremes.iter().any(|extreme| extreme.leaf as usize >= count) {
161            return Err(Error::internal("an extreme is read from a relation that is not there"));
162        }
163        Ok(())
164    }
165}
166
167/// A position in a list that came from a plan, which has fewer than `u32::MAX` of anything.
168fn position(at: usize) -> u32 {
169    u32::try_from(at).expect("a plan has fewer than u32::MAX relations")
170}
171
172#[cfg(test)]
173mod tests {
174    use super::{Edge, Extreme, Key, Leaf, Reducer};
175
176    /// Three relations in a line, `a` under `b` under `c`, with the extreme read from `a`.
177    fn line() -> Reducer {
178        Reducer {
179            leaves: vec![
180                Leaf {
181                    input: 0,
182                    keys: vec![Key { class: 0, column: 0 }],
183                    parent: Some(Edge { leaf: 1, class: 0 }),
184                },
185                Leaf {
186                    input: 1,
187                    keys: vec![Key { class: 0, column: 0 }, Key { class: 1, column: 1 }],
188                    parent: Some(Edge { leaf: 2, class: 1 }),
189                },
190                Leaf { input: 2, keys: vec![Key { class: 1, column: 0 }], parent: None },
191            ],
192            classes: 2,
193            extremes: vec![Extreme { leaf: 0, column: 1, max: false }],
194        }
195    }
196
197    #[test]
198    fn the_children_are_the_relations_that_name_it_as_their_parent() {
199        let reducer = line();
200        assert_eq!(reducer.children(2).collect::<Vec<_>>(), [(1, 1)]);
201        assert_eq!(reducer.children(1).collect::<Vec<_>>(), [(0, 0)]);
202        assert_eq!(reducer.children(0).count(), 0);
203    }
204
205    #[test]
206    fn only_the_path_down_to_an_extreme_is_held() {
207        let reducer = line();
208        assert!(reducer.held(0), "the extreme is read here");
209        assert!(reducer.held(1), "and the path to it passes through here");
210        assert!(!reducer.held(2), "the root is reduced as it is scanned");
211        let mut rooted = reducer;
212        rooted.extremes = vec![Extreme { leaf: 2, column: 0, max: true }];
213        assert!(!rooted.held(0) && !rooted.held(1), "nothing below the root is read again");
214    }
215
216    #[test]
217    fn a_parent_before_its_child_is_refused() {
218        let mut reducer = line();
219        assert!(reducer.validate().is_ok());
220        reducer.leaves[2].parent = Some(Edge { leaf: 0, class: 0 });
221        assert!(reducer.validate().is_err());
222    }
223
224    #[test]
225    fn an_edge_on_a_class_one_end_has_no_column_in_is_refused() {
226        let mut reducer = line();
227        reducer.leaves[0].parent = Some(Edge { leaf: 1, class: 1 });
228        assert!(reducer.validate().is_err());
229    }
230}