Skip to main content

rudb_opt/
tables.rs

1//! Which tables an expression reads, and which ones an operator produces.
2//!
3//! The first of the four expression analyses `spec/engine/11-optimizer.md` asks for. Every question
4//! the optimizer has about a join is really this question: a predicate can be pushed into a side
5//! when everything it reads is produced by that side, two relations have an edge between them when a
6//! condition reads both, and a join is a cross product when no condition reads both. All three are
7//! set containment, so the analysis is a set and the rules that use it are one line each.
8//!
9//! A bitset rather than a list of indices, because the operation these rules do is containment and
10//! not iteration. Table indices are handed out by the binder in order from zero, so a query's
11//! indices are dense and a bitset over them is as wide as the query is large rather than as wide as
12//! the largest index. The words grow rather than being one `u64`, because the binder gives an index
13//! to every operator that introduces columns and not only to every table, so a query with seventy
14//! projections in it has a table index past sixty four and is not a query anybody would call large.
15
16use std::collections::HashMap;
17
18use rudb_plan::{Expr, ExprRef, Node, NodeRef, Plan};
19
20/// A set of table indices.
21#[derive(Debug, Clone, Default, PartialEq, Eq)]
22pub struct TableSet {
23    words: Vec<u64>,
24}
25
26impl TableSet {
27    /// The empty set, which is what a constant reads.
28    #[must_use]
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    /// The set holding one index.
34    #[must_use]
35    pub fn of(index: u32) -> Self {
36        let mut set = Self::new();
37        set.insert(index);
38        set
39    }
40
41    /// Adds an index.
42    pub fn insert(&mut self, index: u32) {
43        let (word, bit) = place(index);
44        if self.words.len() <= word {
45            self.words.resize(word + 1, 0);
46        }
47        self.words[word] |= bit;
48    }
49
50    /// Whether the index is in the set.
51    #[must_use]
52    pub fn contains(&self, index: u32) -> bool {
53        let (word, bit) = place(index);
54        self.words.get(word).is_some_and(|held| held & bit != 0)
55    }
56
57    /// Whether the set holds nothing, which is what a predicate over no column reads.
58    #[must_use]
59    pub fn is_empty(&self) -> bool {
60        self.words.iter().all(|word| *word == 0)
61    }
62
63    /// Adds everything in `other`.
64    pub fn extend(&mut self, other: &Self) {
65        if self.words.len() < other.words.len() {
66            self.words.resize(other.words.len(), 0);
67        }
68        for (held, word) in self.words.iter_mut().zip(&other.words) {
69            *held |= word;
70        }
71    }
72
73    /// Whether everything in this set is also in `other`.
74    ///
75    /// The question filter pushdown asks of every predicate at every join. An empty set is a subset
76    /// of everything, which is the right answer for a predicate that reads no column: it gives the
77    /// same answer wherever it is evaluated.
78    #[must_use]
79    pub fn is_subset_of(&self, other: &Self) -> bool {
80        self.words
81            .iter()
82            .enumerate()
83            .all(|(at, word)| word & !other.words.get(at).copied().unwrap_or(0) == 0)
84    }
85}
86
87/// Which word holds an index and which bit of it.
88fn place(index: u32) -> (usize, u64) {
89    let index = index as usize;
90    (index / 64, 1 << (index % 64))
91}
92
93/// The table indices an expression reads, worked out once per expression.
94///
95/// Cached because the passes after this one ask the same question of the same expression many
96/// times: join ordering asks it of every condition once per subset it enumerates, which is the one
97/// place in the optimizer where a repeated walk of an expression tree would show up in a profile.
98/// Filter pushdown asks once per predicate per join and would be fine without it.
99#[derive(Debug, Default)]
100pub struct Tables {
101    known: HashMap<ExprRef, TableSet>,
102}
103
104impl Tables {
105    /// A cache with nothing in it.
106    #[must_use]
107    pub fn new() -> Self {
108        Self::default()
109    }
110
111    /// The table indices `expr` reads.
112    ///
113    /// Handed back by value rather than by reference, since the recursion needs the cache back
114    /// before it can union what the operands answered, and a set is one word for every sixty four
115    /// tables in the query.
116    pub fn of(&mut self, plan: &Plan, expr: ExprRef) -> TableSet {
117        if let Some(known) = self.known.get(&expr) {
118            return known.clone();
119        }
120        let mut set = TableSet::new();
121        match *plan.expr(expr) {
122            Expr::Column(binding) => set.insert(binding.table),
123            Expr::Constant(_) => {}
124            Expr::Cast { input, .. } => set.extend(&self.of(plan, input)),
125            Expr::Compare { left, right, .. } => {
126                set.extend(&self.of(plan, left));
127                set.extend(&self.of(plan, right));
128            }
129            Expr::Conjunction { children, .. } | Expr::Function { args: children, .. } => {
130                for child in plan.expr_list(children).to_vec() {
131                    set.extend(&self.of(plan, child));
132                }
133            }
134            Expr::Aggregate { args, filter, .. } => {
135                for arg in plan.expr_list(args).to_vec() {
136                    set.extend(&self.of(plan, arg));
137                }
138                if let Some(inner) = filter {
139                    set.extend(&self.of(plan, inner));
140                }
141            }
142            Expr::Case { arms, otherwise } => {
143                for arm in plan.arm_list(arms).to_vec() {
144                    set.extend(&self.of(plan, arm.when));
145                    set.extend(&self.of(plan, arm.then));
146                }
147                if let Some(inner) = otherwise {
148                    set.extend(&self.of(plan, inner));
149                }
150            }
151        }
152        self.known.insert(expr, set.clone());
153        set
154    }
155}
156
157/// The table indices the subtree under `at` produces.
158///
159/// A projection, a grouping and a set operation stop the walk, because each of them introduces its
160/// own index and nothing above it can name what is underneath. That is what makes the answer a set
161/// of what is visible rather than a set of everything down there.
162#[must_use]
163pub fn produced(plan: &Plan, at: NodeRef) -> TableSet {
164    let mut set = TableSet::new();
165    collect(plan, at, &mut set);
166    set
167}
168
169/// Adds what the subtree under `at` produces to `set`.
170fn collect(plan: &Plan, at: NodeRef, set: &mut TableSet) {
171    match *plan.node(at) {
172        Node::Get { index, .. }
173        | Node::Values { index, .. }
174        | Node::TableFunction { index, .. }
175        | Node::Project { index, .. }
176        | Node::Aggregate { index, .. }
177        | Node::SetOp { index, .. } => set.insert(index),
178        Node::Dummy => {}
179        Node::Filter { input, .. }
180        | Node::Sort { input, .. }
181        | Node::Limit { input, .. }
182        | Node::TopN { input, .. }
183        | Node::Distinct { input, .. } => collect(plan, input, set),
184        Node::Join { left, right, .. } | Node::CrossProduct { left, right } => {
185            collect(plan, left, set);
186            collect(plan, right, set);
187        }
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::{TableSet, Tables, produced};
194    use rudb_plan::Plan;
195
196    #[test]
197    fn a_set_holds_what_was_put_in_it_and_grows_past_one_word() {
198        let mut set = TableSet::new();
199        assert!(set.is_empty());
200        set.insert(0);
201        set.insert(200);
202        assert!(set.contains(0));
203        assert!(set.contains(200));
204        assert!(!set.contains(1));
205        assert!(!set.contains(199));
206        assert!(!set.is_empty());
207    }
208
209    #[test]
210    fn the_empty_set_is_a_subset_of_everything_including_itself() {
211        let empty = TableSet::new();
212        assert!(empty.is_subset_of(&empty));
213        assert!(empty.is_subset_of(&TableSet::of(3)));
214        assert!(!TableSet::of(3).is_subset_of(&empty));
215    }
216
217    #[test]
218    fn containment_holds_across_the_word_boundary() {
219        let mut wide = TableSet::of(1);
220        wide.insert(100);
221        assert!(TableSet::of(100).is_subset_of(&wide));
222        assert!(!TableSet::of(101).is_subset_of(&wide));
223        assert!(wide.is_subset_of(&wide));
224
225        let mut narrow = TableSet::of(1);
226        assert!(narrow.is_subset_of(&wide));
227        narrow.extend(&TableSet::of(100));
228        assert_eq!(narrow, wide);
229    }
230
231    const JOIN: &str = "\
232Join INNER on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]
233  Get memory.main.t AS a #0 [a::INTEGER]
234  Get memory.main.t AS b #1 [a::INTEGER]
235";
236
237    #[test]
238    fn a_condition_that_reads_both_sides_reads_both_table_indices() {
239        let plan = Plan::parse(JOIN).expect("a join");
240        let condition = match *plan.node(plan.root()) {
241            rudb_plan::Node::Join { conditions, .. } => plan.expr_list(conditions)[0],
242            _ => unreachable!("the root is the join"),
243        };
244        let mut tables = Tables::new();
245        let read = tables.of(&plan, condition);
246        assert!(read.contains(0));
247        assert!(read.contains(1));
248        // And a second ask is the cached answer rather than a second walk.
249        assert_eq!(tables.of(&plan, condition), read);
250    }
251
252    #[test]
253    fn a_join_produces_both_sides_and_a_projection_produces_only_itself() {
254        let plan = Plan::parse(JOIN).expect("a join");
255        let both = produced(&plan, plan.root());
256        assert!(both.contains(0));
257        assert!(both.contains(1));
258
259        let text = "\
260Project #2 [#0.0::INTEGER AS x]
261  Get memory.main.t AS t #0 [a::INTEGER]
262";
263        let plan = Plan::parse(text).expect("a projection");
264        let visible = produced(&plan, plan.root());
265        assert!(visible.contains(2));
266        assert!(!visible.contains(0), "nothing above a projection can name what is under it");
267    }
268}