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}