Skip to main content

rudb_exec/
build.rs

1//! Turning a bound plan into a tree of operators.
2//!
3//! One match, one arm per logical operator, and nothing else. There is no physical plan and no cost
4//! based choice between two ways of running the same node, which is the honest description of tier
5//! 0: there is one implementation of each operator so there is nothing to choose between. The
6//! physical planner that section 9.6 describes goes here, and the reason this is a separate module
7//! from the operators is so that it can grow into one without any of them moving.
8//!
9//! The tree borrows the plan and the catalog for as long as it exists. A scan reads its rows out of
10//! the catalog's table rather than copying them, and an expression reads its constants, its function
11//! names and its types out of the plan's arena, so a plan that outlives the query it built is the
12//! whole of the lifetime story here.
13//!
14//! # Where the measurement comes from
15//!
16//! Every operator this module makes is wrapped in [`Watched`] before it goes into the tree, and the
17//! counters it reports into are registered with the [`Report`] the caller passed in. That is the
18//! only place the wrapping happens, which is what makes it impossible for an operator to be left
19//! out: an arm that forgets to wrap is an arm that does not compile, because the id it was handed
20//! has to go somewhere.
21//!
22//! Neither the ids nor the pipeline numbers are worked out here. They come from [`Shape`], which is
23//! one walk over the plan in `rudb-plan`, because `EXPLAIN` prints the same numbering and the same
24//! decomposition without building anything, and two versions of that rule would be right on the day
25//! they were written and disagree some time after. What this module does is ask which operator a
26//! node is and wrap it.
27
28use std::sync::Arc;
29
30use rudb_catalog::{Catalog, QualifiedName};
31use rudb_common::{Cancel, Memory, Result};
32use rudb_functions::TableFunction;
33use rudb_metrics::{Counters, Report};
34use rudb_pipeline::{Source, Watched};
35use rudb_plan::{Node, NodeRef, Plan, Shape};
36
37use crate::adapt::{Broken, Fed, Paired, Pulled, Streamed};
38use crate::cancel::Guarded;
39use crate::gather::{Gather, Keep};
40use crate::group::{Aggregate, Distinct};
41use crate::join::{CrossProduct, Gathered, Join};
42use crate::operator::Operator;
43use crate::schema::Schema;
44use crate::setop::SetOp;
45use crate::sort::Sort;
46use crate::source::{Dummy, FileScan, Scan, Series, Values};
47use crate::strategies::Strategies;
48use crate::stream::{Filter, Limit, Project};
49use crate::topn::TopN;
50
51/// Builds the operator tree for a plan's root, for a query nothing will stop.
52///
53/// # Errors
54///
55/// If the plan names a table or a column the catalog does not have, if an expression is malformed
56/// in a way [`Plan::validate`] would have caught, or anything an operator's construction reports.
57pub fn build<'a>(plan: &'a Plan, catalog: &'a Catalog) -> Result<Box<dyn Operator + 'a>> {
58    build_with(plan, catalog, &Cancel::new(), &Memory::unlimited())
59}
60
61/// Builds the operator tree for a plan's root, stoppable through this token and held to this
62/// budget.
63///
64/// Every node in the tree is wrapped in a check, so the query stops at the first chunk boundary
65/// after the token says to. See the `cancel` module for why the check is uniform rather than
66/// placed in the operators that can loop.
67///
68/// The budget is not uniform, and that is the difference between the two. A streaming operator
69/// holds one chunk and gives it away again, so charging every node would count the same megabyte
70/// once per level of the tree. Only the operators that buffer without bound take a reservation, and
71/// [`rudb_common::Memory`] lists which ones those are.
72///
73/// The measurement still happens. It goes into a report nobody reads, because the alternative is
74/// two builders that drift apart, and a pair of clock readings per chunk is not a cost worth
75/// avoiding by having a second one.
76///
77/// # Errors
78///
79/// The same as [`build`].
80pub fn build_with<'a>(
81    plan: &'a Plan,
82    catalog: &'a Catalog,
83    cancel: &Cancel,
84    memory: &Memory,
85) -> Result<Box<dyn Operator + 'a>> {
86    build_measured(plan, catalog, cancel, memory, &Report::new())
87}
88
89/// Builds the operator tree, reporting what every operator in it did into `report`.
90///
91/// The report is what the caller keeps. Once the tree has been drained,
92/// [`Report::fill`] turns it into the operator and pipeline rows of a metrics document, and that
93/// document is the same one `EXPLAIN ANALYZE` prints and `--metrics` writes.
94///
95/// # Errors
96///
97/// The same as [`build`].
98pub fn build_measured<'a>(
99    plan: &'a Plan,
100    catalog: &'a Catalog,
101    cancel: &Cancel,
102    memory: &Memory,
103    report: &Report,
104) -> Result<Box<dyn Operator + 'a>> {
105    let shape = Shape::of(plan);
106    for pipeline in shape.all() {
107        report.pipeline(pipeline);
108        for waits_for in shape.waits_for(pipeline) {
109            report.depends(pipeline, *waits_for);
110        }
111    }
112    let building = Building { plan, catalog, cancel, memory, report, shape };
113    building.node(plan.root())
114}
115
116/// What the walk down the plan carries with it.
117struct Building<'a, 'b> {
118    plan: &'a Plan,
119    catalog: &'a Catalog,
120    cancel: &'b Cancel,
121    memory: &'b Memory,
122    report: &'b Report,
123    shape: Shape,
124}
125
126impl<'a> Building<'a, '_> {
127    /// The counters for one operator, registered with the report.
128    ///
129    /// Everything built here is marked as a reference implementation, because at tier 0 everything
130    /// built here is one. That is not a placeholder: the marker is what stops a number measured
131    /// against the simplest correct version of an operator from being quoted as if it came from the
132    /// fast one, and it comes off an operator on the day that operator gets a second tier.
133    /// The id of the operator holding the side of this node that has to finish first.
134    ///
135    /// # Panics
136    ///
137    /// If the node has one input, which is a node whose arm below should not have called this.
138    fn gathered(&self, node: NodeRef) -> u32 {
139        self.shape.gathered(node).expect("a node with two inputs has a second operator")
140    }
141
142    fn watch(&self, id: u32, pipeline: u32, kind: &str, detail: Option<&str>) -> Arc<Counters> {
143        let counters = Counters::new(id, pipeline, kind).reference();
144        let counters = match detail {
145            Some(detail) => counters.detailed(detail),
146            None => counters,
147        };
148        self.report.watch(counters)
149    }
150
151    fn node(&self, reference: NodeRef) -> Result<Box<dyn Operator + 'a>> {
152        let plan = self.plan;
153        let memory = self.memory;
154        let id = self.shape.operator(reference);
155        let pipeline = self.shape.pipeline(reference);
156        let inner: Box<dyn Operator + 'a> = match *plan.node(reference) {
157            Node::Get { catalog: database, schema, table, index, columns, .. } => {
158                let name = QualifiedName::new(
159                    plan.string(database),
160                    plan.string(schema),
161                    plan.string(table),
162                );
163                let scan = Scan::new(plan, self.catalog.table(&name)?, index, columns)?;
164                let schema = scan.schema().clone();
165                let counters = self.watch(id, pipeline, "Scan", Some(plan.string(table)));
166                pulled(Watched::new(scan, counters), schema)
167            }
168            Node::Dummy => {
169                let dummy = Dummy::new();
170                let schema = dummy.schema().clone();
171                pulled(Watched::new(dummy, self.watch(id, pipeline, "Dummy", None)), schema)
172            }
173            Node::Values { index, columns, rows } => {
174                let values = Values::new(plan, index, columns, rows)?;
175                let schema = values.schema().clone();
176                pulled(Watched::new(values, self.watch(id, pipeline, "Values", None)), schema)
177            }
178            Node::TableFunction { index, function, args, options, settings, columns } => {
179                let name = plan.string(function);
180                match TableFunction::lookup(name) {
181                    Some(function @ (TableFunction::ReadParquet | TableFunction::ReadCsv)) => {
182                        let scan =
183                            FileScan::new(plan, index, function, args, options, settings, columns)?;
184                        let schema = scan.schema().clone();
185                        let counters = self.watch(id, pipeline, "FileScan", Some(name));
186                        pulled(Watched::new(scan, counters), schema)
187                    }
188                    Some(TableFunction::RudbStrategies) => {
189                        let table = Strategies::new(plan, index, columns)?;
190                        let schema = table.schema().clone();
191                        let counters = self.watch(id, pipeline, "Strategies", None);
192                        pulled(Watched::new(table, counters), schema)
193                    }
194                    _ => {
195                        let series = Series::new(plan, index, name, args)?;
196                        let schema = series.schema().clone();
197                        let counters = self.watch(id, pipeline, "Series", Some(name));
198                        pulled(Watched::new(series, counters), schema)
199                    }
200                }
201            }
202            Node::Filter { input, predicate } => {
203                let input = self.node(input)?;
204                let schema = input.schema().clone();
205                let filter = Filter::new(plan, predicate, &schema)?;
206                let counters = self.watch(id, pipeline, "Filter", None);
207                Box::new(Streamed::new(input, Watched::new(filter, counters), schema))
208            }
209            Node::Project { input, index, exprs, names } => {
210                let input = self.node(input)?;
211                let project = Project::new(plan, input.schema(), index, exprs, names)?;
212                let schema = project.schema().clone();
213                let counters = self.watch(id, pipeline, "Project", None);
214                Box::new(Streamed::new(input, Watched::new(project, counters), schema))
215            }
216            Node::Aggregate { input, index, groups, aggregates } => {
217                let input = self.node(input)?;
218                let (aggregate, out) =
219                    Aggregate::new(plan, input.schema(), index, groups, aggregates, memory)?;
220                let schema = aggregate.schema().clone();
221                let counters = self.watch(id, pipeline, "Aggregate", None);
222                let made = Arc::clone(&counters);
223                let driver = self.report.driving(pipeline);
224                Box::new(Broken::new(
225                    input,
226                    Watched::new(aggregate, counters),
227                    driver,
228                    out,
229                    made,
230                    schema,
231                ))
232            }
233            Node::Sort { input, keys } => {
234                let input = self.node(input)?;
235                let schema = input.schema().clone();
236                let (sort, out) = Sort::new(plan, &schema, keys, memory)?;
237                let counters = self.watch(id, pipeline, "Sort", None);
238                let made = Arc::clone(&counters);
239                let driver = self.report.driving(pipeline);
240                Box::new(Broken::new(
241                    input,
242                    Watched::new(sort, counters),
243                    driver,
244                    out,
245                    made,
246                    schema,
247                ))
248            }
249            Node::Limit { input, count, offset } => {
250                let input = self.node(input)?;
251                let schema = input.schema().clone();
252                let limit = Limit::new(count, offset);
253                let counters = self.watch(id, pipeline, "Limit", None);
254                Box::new(Streamed::new(input, Watched::new(limit, counters), schema))
255            }
256            Node::TopN { input, keys, count, offset } => {
257                let input = self.node(input)?;
258                let schema = input.schema().clone();
259                let (top, out) = TopN::new(plan, &schema, keys, count, offset, memory)?;
260                let counters = self.watch(id, pipeline, "TopN", None);
261                let made = Arc::clone(&counters);
262                let driver = self.report.driving(pipeline);
263                Box::new(Broken::new(input, Watched::new(top, counters), driver, out, made, schema))
264            }
265            Node::Distinct { input, on } => {
266                let input = self.node(input)?;
267                let schema = input.schema().clone();
268                let (distinct, out) = Distinct::new(plan, &schema, on, memory)?;
269                let counters = self.watch(id, pipeline, "Distinct", None);
270                let made = Arc::clone(&counters);
271                let driver = self.report.driving(pipeline);
272                Box::new(Broken::new(
273                    input,
274                    Watched::new(distinct, counters),
275                    driver,
276                    out,
277                    made,
278                    schema,
279                ))
280            }
281            Node::Join { left, right, kind, conditions } => {
282                // The right side runs first, because no left row can be answered until every right
283                // row it might match has been seen. That is the dependency edge, and it is the same
284                // one the hash join builds on. The probing side is a pipeline of its own rather than
285                // part of the one above it, because it ends in a sink, and it waits for the build
286                // side.
287                let gather_id = self.gathered(reference);
288                let gathering = self.shape.pipeline(right);
289                let right = self.node(right)?;
290                let left = self.node(left)?;
291                let (gather, gathered) = Gather::new(memory);
292                let side = Gathered { schema: right.schema(), rows: gathered };
293                let (join, out) =
294                    Join::new(plan, left.schema(), side, kind, conditions, self.cancel, memory);
295                let schema = join.schema().clone();
296                let kept = self.watch(gather_id, gathering, "Gather", None);
297                let counters = self.watch(id, pipeline, "Join", None);
298                let made = Arc::clone(&counters);
299                Box::new(Paired::new(
300                    right,
301                    Watched::new(gather, kept),
302                    self.report.driving(gathering),
303                    left,
304                    Watched::new(join, counters),
305                    self.report.driving(pipeline),
306                    out,
307                    made,
308                    schema,
309                ))
310            }
311            Node::CrossProduct { left, right } => {
312                // The right side runs first and is kept as the chunks it arrived in, because it is
313                // replayed once per left row. The left side streams, which is the whole point of
314                // this operator: the product is produced a chunk at a time and never held, so the
315                // product stays in the pipeline the left rows came from rather than starting one.
316                let keep_id = self.gathered(reference);
317                let aside = self.shape.pipeline(right);
318                let right = self.node(right)?;
319                let left = self.node(left)?;
320                let (keep, kept) = Keep::new(memory);
321                let cross = CrossProduct::new(left.schema(), right.schema(), kept);
322                let schema = cross.schema().clone();
323                let held = self.watch(keep_id, aside, "Keep", None);
324                let counters = self.watch(id, pipeline, "CrossProduct", None);
325                Box::new(Fed::new(
326                    right,
327                    Watched::new(keep, held),
328                    self.report.driving(aside),
329                    Streamed::new(left, Watched::new(cross, counters), schema),
330                ))
331            }
332            Node::SetOp { left, right, kind, all, index } => {
333                // The right side runs first, because nothing can be said about a left row until the
334                // whole right side has been counted. That is the dependency edge, spelled out.
335                let gather_id = self.gathered(reference);
336                let counting = self.shape.pipeline(right);
337                let right = self.node(right)?;
338                let left = self.node(left)?;
339                let (gather, gathered) = Gather::new(memory);
340                let (setop, out) = SetOp::new(left.schema(), gathered, kind, all, index, memory);
341                let schema = setop.schema().clone();
342                let kept = self.watch(gather_id, counting, "Gather", None);
343                let counters = self.watch(id, pipeline, "SetOp", None);
344                let made = Arc::clone(&counters);
345                Box::new(Paired::new(
346                    right,
347                    Watched::new(gather, kept),
348                    self.report.driving(counting),
349                    left,
350                    Watched::new(setop, counters),
351                    self.report.driving(pipeline),
352                    out,
353                    made,
354                    schema,
355                ))
356            }
357        };
358        Ok(Box::new(Guarded::new(inner, self.cancel.clone())))
359    }
360}
361
362/// A leaf source with the adapter that pulls chunks out of it.
363///
364/// Every leaf is a [`Source`] and everything above it still pulls, so this is
365/// where the two meet. The schema is passed in rather than asked for through a trait, because a
366/// source says what it produces on its own type and adding a trait method to say it again would be
367/// a second answer to the same question.
368fn pulled<'a, S: Source + 'a>(source: S, schema: Schema) -> Box<dyn Operator + 'a> {
369    Box::new(Pulled::new(source, schema))
370}