Skip to main content

rudb_plan/
shape.rs

1//! The shape a plan runs as: which operator each node becomes, which pipeline it runs in, and which
2//! pipeline waits for which.
3//!
4//! A pipeline is a run of operators from a source to a sink, and a plan breaks into several of them
5//! wherever an operator has to see all of its input before it produces anything. A sort is the
6//! plain case: everything under it is one pipeline that ends in the sort, and what reads the sorted
7//! rows back is the next one, which cannot start until the first has finished. A join is two below
8//! the one above it, because the side that is gathered has to be complete before the side that
9//! probes it can run a single row.
10//!
11//! The operators are numbered by the same walk, because the two answers are the same answer. An
12//! operator's id is what a metrics document calls it, what `EXPLAIN` prints beside it and what the
13//! builder tags its counters with, and a number that three pieces of code work out separately is a
14//! number that three pieces of code can disagree about.
15//!
16//! # Why this is here
17//!
18//! Two crates need all of it and neither can see the other. `rudb-exec` builds the tree, and
19//! `rudb-opt` prints what `EXPLAIN` shows without building anything. Written twice it would be
20//! right twice on the day it was written and wrong once some time after that, and the version that
21//! would be wrong is the printed one, which is the version somebody reads when they are trying to
22//! understand why a query is slow.
23//!
24//! It is physical knowledge about a logical tree, which is worth saying out loud. Whether an
25//! operator is a pipeline breaker is a fact about how it is executed rather than about what it
26//! means, and the reason it can live here anyway is that at this milestone the physical plan is the
27//! logical plan with different words on it, which `crates/rudb-exec/src/build.rs` says at the top.
28//! The day there is a physical plan this moves onto it and every caller keeps its call.
29//!
30//! # The rule for the pipelines
31//!
32//! The root of the plan produces into pipeline 0. Walking down from there, a node inherits the
33//! pipeline of its parent, except that
34//!
35//! - an aggregate, a window, a sort, a top n and a distinct are sinks, so the node and everything
36//!   under it are a new pipeline that the parent's waits for,
37//! - a join and a set operation are two, the side that is gathered first and the side that reads
38//!   it, with the second waiting for the first and the parent's waiting for the second,
39//! - a cross product keeps its left side and itself in the parent's pipeline, because the product
40//!   is produced a chunk at a time and never held, and puts its right side in a new one, because
41//!   that side is kept whole to be replayed.
42//!
43//! There is no scheduler reading any of this yet. It is written down because it is known, and an
44//! edge reconstructed later from a tree somebody has already flattened is an edge somebody has to
45//! guess at.
46//!
47//! # The rule for the numbers
48//!
49//! A node takes the next id when the walk reaches it, so the root is operator 0 and a parent is
50//! always numbered before everything under it. A node with two inputs takes a second id straight
51//! after its own, for the operator that holds the side that has to finish first: the gather under a
52//! join or a set operation, and the kept chunks under a cross product. Those are operators in their
53//! own right, they have their own counters and their own row in a metrics document, and they exist
54//! because the plan has two inputs there rather than because somebody chose to add one.
55//!
56//! Then the children, and for a node with two inputs the side that runs first is walked first, so
57//! the ids go in the order the work happens rather than in the order the tree prints.
58
59use crate::node::Node;
60use crate::plan::Plan;
61use crate::{NodeRef, OperatorRef, PipelineRef};
62
63/// What a plan runs as.
64#[derive(Debug, Clone)]
65pub struct Shape {
66    /// Per node in the arena, the operator it becomes and the pipeline that runs it, or none for a
67    /// node the root does not reach.
68    of: Vec<Option<Placed>>,
69    /// What each pipeline waits for, indexed by pipeline.
70    waits: Vec<Vec<PipelineRef>>,
71    /// How many operators there are.
72    operators: OperatorRef,
73}
74
75/// One node's place in the shape.
76#[derive(Debug, Clone, Copy)]
77struct Placed {
78    operator: OperatorRef,
79    /// The operator that holds the side which has to finish first, for a node with two inputs.
80    gathered: Option<OperatorRef>,
81    pipeline: PipelineRef,
82}
83
84impl Shape {
85    /// Works out the shape of a plan.
86    #[must_use]
87    pub fn of(plan: &Plan) -> Self {
88        let mut shape =
89            Self { of: vec![None; plan.node_count()], waits: vec![Vec::new()], operators: 0 };
90        shape.walk(plan, plan.root(), ROOT);
91        shape
92    }
93
94    /// How many pipelines there are, which is at least one.
95    #[must_use]
96    pub fn pipelines(&self) -> usize {
97        self.waits.len()
98    }
99
100    /// How many operators the tree has, which is at least one and is more than the plan has nodes
101    /// whenever the plan has a node with two inputs in it.
102    #[must_use]
103    pub fn operators(&self) -> OperatorRef {
104        self.operators
105    }
106
107    /// The operator this node becomes.
108    ///
109    /// # Panics
110    ///
111    /// If the node is not reachable from the plan's root, which is a node the arena is still
112    /// holding after a rewrite replaced it.
113    #[must_use]
114    pub fn operator(&self, node: NodeRef) -> OperatorRef {
115        self.placed(node).operator
116    }
117
118    /// The operator this node becomes, or none for a node the root does not reach.
119    ///
120    /// The tolerant form of [`Shape::operator`], for a caller walking the whole arena rather than
121    /// the tree, which is what somebody filling one fact in per operator ends up doing.
122    #[must_use]
123    pub fn operator_of(&self, node: NodeRef) -> Option<OperatorRef> {
124        self.of.get(node as usize).copied().flatten().map(|placed| placed.operator)
125    }
126
127    /// The operator holding the side of this node that has to finish first, if it has two inputs.
128    ///
129    /// # Panics
130    ///
131    /// The same as [`Shape::operator`].
132    #[must_use]
133    pub fn gathered(&self, node: NodeRef) -> Option<OperatorRef> {
134        self.placed(node).gathered
135    }
136
137    /// The pipeline this node runs in.
138    ///
139    /// For a sink that is the pipeline it ends rather than the one above it, so a sort is in the
140    /// pipeline that feeds it and the operator that reads the sorted rows is in the one above.
141    ///
142    /// # Panics
143    ///
144    /// The same as [`Shape::operator`].
145    #[must_use]
146    pub fn pipeline(&self, node: NodeRef) -> PipelineRef {
147        self.placed(node).pipeline
148    }
149
150    /// What this pipeline has to wait for, in ascending order.
151    ///
152    /// # Panics
153    ///
154    /// If there is no such pipeline.
155    #[must_use]
156    pub fn waits_for(&self, pipeline: PipelineRef) -> &[PipelineRef] {
157        &self.waits[pipeline as usize]
158    }
159
160    /// Every pipeline, from the root's outwards.
161    pub fn all(&self) -> impl Iterator<Item = PipelineRef> {
162        0..u32::try_from(self.waits.len()).unwrap_or(u32::MAX)
163    }
164
165    /// Where a node ended up.
166    ///
167    /// # Panics
168    ///
169    /// If the node is not reachable from the plan's root.
170    fn placed(&self, node: NodeRef) -> Placed {
171        self.of[node as usize].expect("a node under the root of the plan it was walked from")
172    }
173
174    /// A new pipeline that nothing waits for yet.
175    fn fresh(&mut self) -> PipelineRef {
176        self.waits.push(Vec::new());
177        u32::try_from(self.waits.len() - 1).unwrap_or(u32::MAX)
178    }
179
180    /// Records that `pipeline` cannot start until `on` has finished.
181    fn waits_on(&mut self, pipeline: PipelineRef, on: PipelineRef) {
182        self.waits[pipeline as usize].push(on);
183    }
184
185    /// The next operator id.
186    fn number(&mut self) -> OperatorRef {
187        let id = self.operators;
188        self.operators += 1;
189        id
190    }
191
192    fn walk(&mut self, plan: &Plan, node: NodeRef, pipeline: PipelineRef) {
193        let operator = self.number();
194        match *plan.node(node) {
195            Node::Aggregate { input, .. }
196            | Node::Window { input, .. }
197            | Node::Sort { input, .. }
198            | Node::TopN { input, .. }
199            | Node::Distinct { input, .. } => {
200                let below = self.fresh();
201                self.waits_on(pipeline, below);
202                self.of[node as usize] = Some(Placed { operator, gathered: None, pipeline: below });
203                self.walk(plan, input, below);
204            }
205            Node::Join { left, right, .. }
206            | Node::DependentJoin { left, right, .. }
207            | Node::SetOp { left, right, .. } => {
208                let gathered = self.number();
209                let first = self.fresh();
210                let second = self.fresh();
211                self.waits_on(second, first);
212                self.waits_on(pipeline, second);
213                self.of[node as usize] =
214                    Some(Placed { operator, gathered: Some(gathered), pipeline: second });
215                self.walk(plan, right, first);
216                self.walk(plan, left, second);
217            }
218            Node::CrossProduct { left, right } => {
219                let gathered = self.number();
220                let aside = self.fresh();
221                self.waits_on(pipeline, aside);
222                self.of[node as usize] =
223                    Some(Placed { operator, gathered: Some(gathered), pipeline });
224                self.walk(plan, right, aside);
225                self.walk(plan, left, pipeline);
226            }
227            ref other => {
228                self.of[node as usize] = Some(Placed { operator, gathered: None, pipeline });
229                for child in other.children().into_iter().flatten() {
230                    self.walk(plan, child, pipeline);
231                }
232            }
233        }
234    }
235}
236
237/// The pipeline the root of a plan produces into.
238///
239/// Public because it is the one pipeline nothing drains. Every other pipeline ends in a sink and is
240/// run by the loop that fills that sink, and this one is pulled from by whoever wanted the answer,
241/// so whoever that is has to know which pipeline the loop they are writing belongs to.
242pub const ROOT: PipelineRef = 0;
243
244#[cfg(test)]
245mod tests {
246    use super::Shape;
247    use crate::plan::Plan;
248
249    fn shaped(text: &str) -> (Plan, Shape) {
250        let plan =
251            Plan::parse(text).unwrap_or_else(|error| panic!("{text} did not parse: {error}"));
252        let shape = Shape::of(&plan);
253        (plan, shape)
254    }
255
256    #[test]
257    fn a_plan_with_nothing_that_buffers_is_one_pipeline() {
258        let (plan, shape) = shaped(concat!(
259            "Filter (#0.0::INTEGER > 1::INTEGER)::BOOLEAN\n",
260            "  Get memory.main.t AS t #0 [a::INTEGER]\n",
261        ));
262        assert_eq!(shape.pipelines(), 1);
263        assert_eq!(shape.pipeline(plan.root()), 0);
264        assert!(shape.waits_for(0).is_empty());
265    }
266
267    #[test]
268    fn a_sort_ends_the_pipeline_below_it_and_the_one_above_waits() {
269        let (plan, shape) = shaped(concat!(
270            "Sort [#0.0::INTEGER ASC NULLS LAST]\n",
271            "  Get memory.main.t AS t #0 [a::INTEGER]\n",
272        ));
273        assert_eq!(shape.pipelines(), 2);
274        assert_eq!(shape.pipeline(plan.root()), 1, "the sort is the sink of the one below");
275        assert_eq!(shape.waits_for(0), [1]);
276        assert!(shape.waits_for(1).is_empty());
277    }
278
279    #[test]
280    fn a_join_is_two_pipelines_in_the_order_they_have_to_run() {
281        let (plan, shape) = shaped(concat!(
282            "Join INNER on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]\n",
283            "  Get memory.main.l AS l #0 [a::INTEGER]\n",
284            "  Get memory.main.r AS r #1 [a::INTEGER]\n",
285        ));
286        let [left, right] = plan.node(plan.root()).children();
287        assert_eq!(shape.pipelines(), 3);
288        assert_eq!(shape.pipeline(right.unwrap()), 1, "the gathered side runs first");
289        assert_eq!(shape.pipeline(left.unwrap()), 2, "the probing side is the second");
290        assert_eq!(shape.pipeline(plan.root()), 2, "and the join is its sink");
291        assert_eq!(shape.waits_for(2), [1]);
292        assert_eq!(shape.waits_for(0), [2]);
293    }
294
295    #[test]
296    fn a_cross_product_keeps_its_left_side_where_it_was() {
297        let (plan, shape) = shaped(concat!(
298            "CrossProduct\n",
299            "  Get memory.main.l AS l #0 [a::INTEGER]\n",
300            "  Get memory.main.r AS r #1 [a::INTEGER]\n",
301        ));
302        let [left, right] = plan.node(plan.root()).children();
303        assert_eq!(shape.pipelines(), 2);
304        assert_eq!(shape.pipeline(plan.root()), 0, "the product streams");
305        assert_eq!(shape.pipeline(left.unwrap()), 0, "and so does the side it streams");
306        assert_eq!(shape.pipeline(right.unwrap()), 1, "the side that is kept is its own");
307        assert_eq!(shape.waits_for(0), [1]);
308    }
309
310    #[test]
311    fn two_sorts_under_one_another_are_three_pipelines_in_a_line() {
312        let (plan, shape) = shaped(concat!(
313            "Sort [#0.0::INTEGER ASC NULLS LAST]\n",
314            "  Limit 10 offset 0\n",
315            "    Sort [#0.0::INTEGER DESC NULLS FIRST]\n",
316            "      Get memory.main.t AS t #0 [a::INTEGER]\n",
317        ));
318        assert_eq!(shape.pipelines(), 3);
319        assert_eq!(shape.pipeline(plan.root()), 1);
320        assert_eq!(shape.waits_for(0), [1]);
321        assert_eq!(shape.waits_for(1), [2]);
322        assert!(shape.waits_for(2).is_empty());
323    }
324
325    #[test]
326    fn a_parent_is_numbered_before_everything_under_it() {
327        let (plan, shape) = shaped(concat!(
328            "Sort [#0.0::INTEGER ASC NULLS LAST]\n",
329            "  Filter (#0.0::INTEGER > 1::INTEGER)::BOOLEAN\n",
330            "    Get memory.main.t AS t #0 [a::INTEGER]\n",
331        ));
332        let filter = plan.node(plan.root()).children()[0].unwrap();
333        let get = plan.node(filter).children()[0].unwrap();
334        assert_eq!(shape.operator(plan.root()), 0);
335        assert_eq!(shape.operator(filter), 1);
336        assert_eq!(shape.operator(get), 2);
337        assert_eq!(shape.operators(), 3);
338        assert_eq!(shape.gathered(plan.root()), None, "one input, nothing to hold");
339    }
340
341    #[test]
342    fn a_node_with_two_inputs_is_two_operators_and_the_first_side_is_numbered_first() {
343        let (plan, shape) = shaped(concat!(
344            "Join INNER on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]\n",
345            "  Get memory.main.l AS l #0 [a::INTEGER]\n",
346            "  Get memory.main.r AS r #1 [a::INTEGER]\n",
347        ));
348        let [left, right] = plan.node(plan.root()).children();
349        assert_eq!(shape.operator(plan.root()), 0);
350        assert_eq!(shape.gathered(plan.root()), Some(1), "the gather is an operator of its own");
351        assert_eq!(shape.operator(right.unwrap()), 2, "the side that has to finish first");
352        assert_eq!(shape.operator(left.unwrap()), 3);
353        assert_eq!(shape.operators(), 4);
354    }
355}