Skip to main content

rudb_exec/
build.rs

1//! Turning a bound plan into the pipelines that run it.
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 pipelines borrow the plan and the catalog for as long as they exist. A scan reads its rows
10//! out of the catalog's table rather than copying them, and an expression reads its constants, its
11//! function names and its types out of the plan's arena, so a plan that outlives the query it built
12//! is the whole of the lifetime story here.
13//!
14//! # How a tree becomes a list
15//!
16//! The walk is the same one it always was, down from the root, and what changed is what it carries
17//! back up. A node returns a [`Segment`], which is a source with the streaming operators stacked on
18//! it so far, and a node that is a pipeline breaker closes the segment under it into a finished
19//! [`Pipeline`] and starts a new segment over the buffer that breaker finalises into. So a plan with
20//! two breakers in it comes back as three pipelines, and they are pushed onto the list in the order
21//! they have to run, because a breaker's own pipeline is closed before the walk returns to whatever
22//! is above it.
23//!
24//! A node with two inputs closes the side that has to finish first and then walks the side that uses
25//! it, which is the same order the ids are handed out in and the same order the work happens in.
26//!
27//! # Where the measurement comes from
28//!
29//! Every operator this module makes is wrapped in [`Watched`] before it goes into a pipeline, and
30//! the counters it reports into are registered with the [`Report`] the caller passed in. That is the
31//! only place the wrapping happens, which is what makes it impossible for an operator to be left
32//! out: an arm that forgets to wrap is an arm that does not compile, because the id it was handed
33//! has to go somewhere.
34//!
35//! A breaker's counters go around two objects rather than one. The sink is the operator, and the
36//! buffer the next pipeline sources from is where its rows come back out, so both are wrapped in the
37//! same counters and a sort's row count is the rows it produced rather than zero.
38//!
39//! Neither the ids nor the pipeline numbers are worked out here. They come from [`Shape`], which is
40//! one walk over the plan in `rudb-plan`, because `EXPLAIN` prints the same numbering and the same
41//! decomposition without building anything, and two versions of that rule would be right on the day
42//! they were written and disagree some time after. What this module does is ask which operator a
43//! node is and wrap it.
44
45use std::sync::Arc;
46
47use rudb_catalog::{Catalog, QualifiedName};
48use rudb_common::{Cancel, Memory, Result};
49use rudb_functions::TableFunction;
50use rudb_metrics::{Counters, Driver, Report};
51use rudb_parquet::{Bound, Op};
52use rudb_pipeline::{
53    BufferId, DynSink, DynStream, Pipeline, PipelineId, Source, Watched, root, root_in_order,
54};
55use rudb_plan::{
56    CompareOp, ConjunctionOp, Expr, ExprRef, Node, NodeRef, PipelineRef, Plan, ROOT, Shape, Slice,
57    seams_of,
58};
59use rudb_seam::Settings;
60
61use crate::fetch::Fetch;
62use crate::gather::{Gather, Keep};
63use crate::group::{Aggregate, Distinct};
64use crate::join::{CrossProduct, Gathered, Join};
65use crate::keywords::keywords;
66use crate::query::Query;
67use crate::register::registries;
68use crate::schema::Schema;
69use crate::setop::SetOp;
70use crate::sort::Sort;
71use crate::source::{Dummy, FileScan, Scan, Series, Values};
72use crate::strategies::strategies;
73use crate::stream::{Filter, Limit, Project};
74use crate::topn::TopN;
75
76/// Builds the pipelines for a plan's root, for a query nothing will stop.
77///
78/// Every seam is left at its default, which is what a caller with no session behind it wants and is
79/// what the tests in this crate are written against.
80///
81/// # Errors
82///
83/// If the plan names a table or a column the catalog does not have, if an expression is malformed
84/// in a way [`Plan::validate`] would have caught, or anything an operator's construction reports.
85pub fn build<'a>(plan: &'a Plan, catalog: &'a Catalog) -> Result<Query<'a>> {
86    build_with(plan, catalog, &Cancel::new(), &Memory::unlimited(), &Settings::new())
87}
88
89/// Builds the pipelines for a plan's root, stoppable through this token and held to this budget.
90///
91/// The token is checked once per chunk by the driver, so the query stops at the first chunk boundary
92/// after the token says to. It is one check in one place rather than a decision per operator,
93/// because a decision per operator is a decision somebody gets wrong when they add the twentieth
94/// one. What the driver cannot see is work an operator does inside one call, and the join is the one
95/// that can: its nested loop runs to the end inside a single push, and a hundred thousand left rows
96/// against thirty thousand right ones is a minute with nothing looking at the token, so that loop
97/// holds the token as well and checks it once per left row.
98///
99/// The budget is not uniform, and that is the difference between the two. A streaming operator holds
100/// one chunk and gives it away again, so charging every operator would count the same megabyte once
101/// per level. Only the operators that buffer without bound take a reservation, and
102/// [`rudb_common::Memory`] lists which ones those are.
103///
104/// The measurement still happens. It goes into a report nobody reads, because the alternative is two
105/// builders that drift apart, and a pair of clock readings per chunk is not a cost worth avoiding by
106/// having a second one.
107///
108/// The seam settings are the session's with the statement's hints on top, and they are read here
109/// rather than looked up later, because a choice made while the query is built is a choice `EXPLAIN`
110/// can print before the query runs. An operator that sits on a seam chooses once, in its
111/// constructor, and holds what it chose.
112///
113/// # Errors
114///
115/// The same as [`build`].
116pub fn build_with<'a>(
117    plan: &'a Plan,
118    catalog: &'a Catalog,
119    cancel: &Cancel,
120    memory: &Memory,
121    seams: &Settings,
122) -> Result<Query<'a>> {
123    build_measured(plan, catalog, cancel, memory, seams, &Report::new())
124}
125
126/// Builds the pipelines, reporting what every operator in them did into `report`.
127///
128/// The report is what the caller keeps. Once the query has been run, [`Report::fill`] turns it into
129/// the operator and pipeline rows of a metrics document, and that document is the same one
130/// `EXPLAIN ANALYZE` prints and `--metrics` writes.
131///
132/// # Errors
133///
134/// The same as [`build`].
135pub fn build_measured<'a>(
136    plan: &'a Plan,
137    catalog: &'a Catalog,
138    cancel: &Cancel,
139    memory: &Memory,
140    seams: &Settings,
141    report: &Report,
142) -> Result<Query<'a>> {
143    let shape = Shape::of(plan);
144    for pipeline in shape.all() {
145        report.pipeline(pipeline);
146        for waits_for in shape.waits_for(pipeline) {
147            report.depends(pipeline, *waits_for);
148        }
149    }
150    let mut building = Building {
151        plan,
152        catalog,
153        cancel,
154        memory,
155        seams,
156        report,
157        shape,
158        done: Vec::new(),
159        drivers: Vec::new(),
160        pruning: Vec::new(),
161    };
162    let segment = building.node(plan.root())?;
163    let schema = segment.schema.clone();
164    // A query whose rows come out of a sort or a top n is already in the order somebody asked for,
165    // and holding chunks back to restore the source order would only add latency to an order nobody
166    // is going to look at. Everything else gets the root that puts them back, because the moment
167    // several threads read the same file a plain `SELECT` would otherwise come back in a different
168    // order on every run. It costs nothing to decide here and it means the scheduler never has to.
169    let (sink, reader) = if ordered(plan, plan.root()) {
170        root(BufferId(0), None)
171    } else {
172        root_in_order(BufferId(0), None)
173    };
174    building.close(segment, ROOT, Arc::new(sink));
175    let Building { done, drivers, .. } = building;
176    Query::new(done, drivers, reader, schema)
177}
178
179/// Whether the rows reaching the root are already in an order the plan chose.
180///
181/// A sort and a top n both decide one. Everything between them and the root either keeps the order
182/// it was given or is not a node that can sit there, and the walk stops at the first node that is
183/// neither.
184fn ordered(plan: &Plan, node: NodeRef) -> bool {
185    match *plan.node(node) {
186        Node::Sort { .. } | Node::TopN { .. } => true,
187        Node::Project { input, .. }
188        | Node::Filter { input, .. }
189        | Node::Limit { input, .. }
190        | Node::Fetch { input, .. } => ordered(plan, input),
191        _ => false,
192    }
193}
194
195/// What a filter over a scan can tell that scan before it reads anything.
196///
197/// Both kinds of scan keep the smallest and the largest value of each of their columns: a Parquet
198/// file keeps them per row group in its footer, and a table in memory keeps them per chunk in a zone
199/// map. A conjunct comparing one of those columns against a constant can rule a whole unit out
200/// without touching a row of it. This pulls out the conjuncts of that shape and drops everything
201/// else, which is the conservative direction: a test that is not here costs time, a test that is
202/// here wrongly costs rows.
203///
204/// Only an `AND` is walked into. Under an `OR` a conjunct being false says nothing about the row,
205/// and a `NOT` is already gone by the time the plan is bound. Of the table functions only
206/// `read_parquet` is worth doing this for, because a CSV has no footer to read. Only a comparison
207/// against this scan's own columns counts, since a binding into some other operator's output is not
208/// in this table at all.
209fn bounds(plan: &Plan, input: NodeRef, predicate: ExprRef) -> Vec<(usize, Op, Bound)> {
210    let index = match *plan.node(input) {
211        Node::TableFunction { index, function, .. } => {
212            if TableFunction::lookup(plan.string(function)) != Some(TableFunction::ReadParquet) {
213                return Vec::new();
214            }
215            index
216        }
217        Node::Get { index, .. } => index,
218        _ => return Vec::new(),
219    };
220    let mut tests = Vec::new();
221    conjuncts(plan, predicate, index, &mut tests);
222    tests
223}
224
225/// Every conjunct of `predicate` that reads as a bounds test, appended to `out`.
226fn conjuncts(plan: &Plan, predicate: ExprRef, index: u32, out: &mut Vec<(usize, Op, Bound)>) {
227    match *plan.expr(predicate) {
228        Expr::Conjunction { op: ConjunctionOp::And, children } => {
229            for child in plan.expr_list(children) {
230                conjuncts(plan, *child, index, out);
231            }
232        }
233        Expr::Compare { op, left, right } => {
234            if let Some(test) = comparison(plan, op, left, right, index) {
235                out.push(test);
236            }
237        }
238        _ => {}
239    }
240}
241
242/// One comparison read as a test on a column of the scan numbered `index`, if it is one.
243///
244/// Written either way round, because `5 < a` and `a > 5` say the same thing and the optimizer does
245/// not normalise which side the constant sits on. The comparisons that survive a null are the four
246/// orderings and equality: `<>` rules out a row group only when the group holds one distinct value,
247/// which the footer does not say, and the two distinctness operators are about nulls rather than
248/// about bounds.
249fn comparison(
250    plan: &Plan,
251    op: CompareOp,
252    left: ExprRef,
253    right: ExprRef,
254    index: u32,
255) -> Option<(usize, Op, Bound)> {
256    let op = match op {
257        CompareOp::Equal => Op::Equal,
258        CompareOp::Less => Op::Less,
259        CompareOp::LessOrEqual => Op::LessOrEqual,
260        CompareOp::Greater => Op::Greater,
261        CompareOp::GreaterOrEqual => Op::GreaterOrEqual,
262        CompareOp::NotEqual | CompareOp::DistinctFrom | CompareOp::NotDistinctFrom => return None,
263    };
264    let (op, binding, value) = match (plan.expr(left), plan.expr(right)) {
265        (Expr::Column(binding), Expr::Constant(value)) => (op, *binding, *value),
266        (Expr::Constant(value), Expr::Column(binding)) => (op.flipped(), *binding, *value),
267        _ => return None,
268    };
269    if binding.table != index {
270        return None;
271    }
272    Some((binding.column as usize, op, Bound::of_value(plan.value(value))?))
273}
274
275/// A pipeline being built from the bottom up.
276///
277/// It is not a [`Pipeline`] yet because it has no sink. What ends it is whichever node above it
278/// turns out to be a pipeline breaker, or the root of the plan, and neither is known until the walk
279/// gets back there.
280struct Segment<'a> {
281    source: Arc<dyn Source + 'a>,
282    /// In the order they run, nearest the source first.
283    streams: Vec<Arc<dyn DynStream + 'a>>,
284    /// What the segment produces as it stands, which changes as streams are added.
285    schema: Schema,
286    /// The pipelines this one cannot start before.
287    after: Vec<PipelineRef>,
288}
289
290impl<'a> Segment<'a> {
291    /// A segment that is just its source.
292    fn new(source: Arc<dyn Source + 'a>, schema: Schema) -> Self {
293        Self { source, streams: Vec::new(), schema, after: Vec::new() }
294    }
295
296    /// A segment reading what a pipeline breaker finalised into.
297    fn reading(source: Arc<dyn Source + 'a>, schema: Schema, after: PipelineRef) -> Self {
298        Self { source, streams: Vec::new(), schema, after: vec![after] }
299    }
300
301    /// Puts a streaming operator on the end, which becomes what the segment produces.
302    fn then(mut self, stream: Arc<dyn DynStream + 'a>, schema: Schema) -> Self {
303        self.streams.push(stream);
304        self.schema = schema;
305        self
306    }
307}
308
309/// What the walk down the plan carries with it.
310struct Building<'a, 'b> {
311    plan: &'a Plan,
312    catalog: &'a Catalog,
313    cancel: &'b Cancel,
314    memory: &'b Memory,
315    seams: &'b Settings,
316    report: &'b Report,
317    shape: Shape,
318    /// The pipelines closed so far, in the order they have to run.
319    done: Vec<Pipeline<'a>>,
320    /// One per entry of `done`, in the same order.
321    drivers: Vec<Arc<Driver>>,
322    /// The bounds tests the filter arm worked out for the scan it is about to walk into.
323    ///
324    /// A scan is built before the filter above it, because the filter needs the schema the scan
325    /// produces, so by the time there is a filter to read there is already a scan that cannot be
326    /// told anything. This carries the tests the other way, down the one step from a filter to its
327    /// own input, and the scan arm takes them. It is empty every other time it is read, and empty
328    /// means hand out every row group, which is what every scan did before pruning existed.
329    pruning: Vec<(usize, Op, Bound)>,
330}
331
332impl<'a> Building<'a, '_> {
333    /// The id of the operator holding the side of this node that has to finish first.
334    ///
335    /// # Panics
336    ///
337    /// If the node has one input, which is a node whose arm below should not have called this.
338    fn gathered(&self, node: NodeRef) -> u32 {
339        self.shape.gathered(node).expect("a node with two inputs has a second operator")
340    }
341
342    /// Ends a segment with a sink and puts the finished pipeline on the list.
343    fn close(&mut self, segment: Segment<'a>, id: PipelineRef, sink: Arc<dyn DynSink + 'a>) {
344        let mut pipeline = Pipeline::new(PipelineId(id), segment.source, sink);
345        for stream in segment.streams {
346            pipeline = pipeline.then(stream);
347        }
348        for after in segment.after {
349            pipeline = pipeline.after(PipelineId(after));
350        }
351        self.done.push(pipeline);
352        self.drivers.push(self.report.driving(id));
353    }
354
355    /// The counters for one operator, registered with the report.
356    ///
357    /// The row records what this operator picked at each seam it sits on, which is `seams_of` on
358    /// its plan node crossed with what is registered and what the statement pinned. That is the
359    /// same three things `EXPLAIN` puts its reference marker from, and it is read here rather than
360    /// asserted here for a reason worth writing down: this used to mark every operator as a
361    /// reference implementation unconditionally, so every ClickBench run said 41 of 41 operators
362    /// ran the slow path no matter what had actually run, and the fold that reported it was read as
363    /// if it meant something.
364    ///
365    /// An operator that sits on no registered seam records nothing and stays marked as a reference,
366    /// because there is one implementation of it and that one is the obvious correct one. The
367    /// marker comes off by itself on the day a seam under it has something else registered and
368    /// chosen, with nothing to remember to change here.
369    fn watch(
370        &self,
371        node: NodeRef,
372        id: u32,
373        pipeline: u32,
374        kind: &str,
375        detail: Option<&str>,
376    ) -> Arc<Counters> {
377        let mut counters = Counters::new(id, pipeline, kind);
378        if let Some(detail) = detail {
379            counters = counters.detailed(detail);
380        }
381        for seam in seams_of(self.plan.node(node)) {
382            if let Some(running) = registries().running(*seam, self.seams) {
383                counters = counters.chose(seam.name(), &running.name, running.is_reference);
384            }
385        }
386        self.report.watch(counters)
387    }
388
389    fn aggregate(
390        &mut self,
391        reference: NodeRef,
392        input: NodeRef,
393        index: u32,
394        groups: Slice,
395        aggregates: Slice,
396        max_groups: Option<usize>,
397    ) -> Result<Segment<'a>> {
398        let below = self.node(input)?;
399        let (aggregate, out) =
400            Aggregate::new(self.plan, &below.schema, index, groups, aggregates, self.memory)?;
401        let aggregate = match max_groups {
402            Some(limit) => aggregate.limit_groups(limit),
403            None => aggregate,
404        };
405        let schema = aggregate.schema().clone();
406        let id = self.shape.operator(reference);
407        let pipeline = self.shape.pipeline(reference);
408        let counters = self.watch(reference, id, pipeline, "Aggregate", None);
409        let reading = Arc::clone(&counters);
410        self.close(below, pipeline, Arc::new(Watched::new(aggregate, counters)));
411        Ok(Segment::reading(Arc::new(Watched::new(out, reading)), schema, pipeline))
412    }
413
414    /// The segment a node produces, closing any pipeline that ends underneath it.
415    fn node(&mut self, reference: NodeRef) -> Result<Segment<'a>> {
416        let plan = self.plan;
417        let memory = self.memory;
418        let id = self.shape.operator(reference);
419        let pipeline = self.shape.pipeline(reference);
420        let segment = match *plan.node(reference) {
421            Node::Get { catalog: database, schema, table, index, columns, .. } => {
422                let name = QualifiedName::new(
423                    plan.string(database),
424                    plan.string(schema),
425                    plan.string(table),
426                );
427                let tests = std::mem::take(&mut self.pruning);
428                let scan = Scan::new(plan, self.catalog.table(&name)?, index, columns, tests)?;
429                let schema = scan.schema().clone();
430                let counters =
431                    self.watch(reference, id, pipeline, "Scan", Some(plan.string(table)));
432                Segment::new(Arc::new(Watched::new(scan, counters)), schema)
433            }
434            Node::Dummy => {
435                let dummy = Dummy::new();
436                let schema = dummy.schema().clone();
437                let counters = self.watch(reference, id, pipeline, "Dummy", None);
438                Segment::new(Arc::new(Watched::new(dummy, counters)), schema)
439            }
440            Node::Values { index, columns, rows } => {
441                let values = Values::new(plan, index, columns, rows)?;
442                let schema = values.schema().clone();
443                let counters = self.watch(reference, id, pipeline, "Values", None);
444                Segment::new(Arc::new(Watched::new(values, counters)), schema)
445            }
446            Node::TableFunction { index, function, args, options, settings, columns } => {
447                let name = plan.string(function);
448                match TableFunction::lookup(name) {
449                    Some(function @ (TableFunction::ReadParquet | TableFunction::ReadCsv)) => {
450                        let counters = self.watch(reference, id, pipeline, "FileScan", Some(name));
451                        let tests = std::mem::take(&mut self.pruning);
452                        let scan = FileScan::new(
453                            plan, index, function, args, options, settings, columns, tests,
454                        )?
455                        .watched(counters.clone());
456                        let schema = scan.schema().clone();
457                        Segment::new(Arc::new(Watched::new(scan, counters)), schema)
458                    }
459                    Some(
460                        function @ (TableFunction::RudbStrategies | TableFunction::DuckdbKeywords),
461                    ) => {
462                        let table = match function {
463                            TableFunction::DuckdbKeywords => keywords(plan, index, columns)?,
464                            _ => strategies(plan, index, columns)?,
465                        };
466                        let schema = table.schema().clone();
467                        // `EXPLAIN` names the table rather than the operator, because every one of
468                        // these is the same operator and a plan that said `Metadata` four times
469                        // would not say which four tables it read.
470                        let counters =
471                            self.watch(reference, id, pipeline, "Metadata", Some(function.name()));
472                        Segment::new(Arc::new(Watched::new(table, counters)), schema)
473                    }
474                    _ => {
475                        let series = Series::new(plan, index, name, args)?;
476                        let schema = series.schema().clone();
477                        let counters = self.watch(reference, id, pipeline, "Series", Some(name));
478                        Segment::new(Arc::new(Watched::new(series, counters)), schema)
479                    }
480                }
481            }
482            Node::Fetch { input, index, args, columns, row } => {
483                let below = self.node(input)?;
484                let counters = self.watch(reference, id, pipeline, "Fetch", None);
485                let fetch = Fetch::new(plan, &below.schema, index, args, columns, row)?
486                    .watched(counters.clone());
487                let schema = fetch.schema().clone();
488                below.then(Arc::new(Watched::new(fetch, counters)), schema)
489            }
490            Node::Filter { input, predicate } => {
491                self.pruning = bounds(plan, input, predicate);
492                let below = self.node(input)?;
493                // Cleared whether or not the scan arm took them, because a filter over anything
494                // else leaves them sitting there for whatever scan the walk reaches next.
495                self.pruning = Vec::new();
496                let schema = below.schema.clone();
497                let filter = Filter::new(plan, reference, predicate, &schema, self.seams)?;
498                let counters = self.watch(reference, id, pipeline, "Filter", None);
499                below.then(Arc::new(Watched::new(filter, counters)), schema)
500            }
501            Node::Project { input, index, exprs, names } => {
502                let below = self.node(input)?;
503                let project = Project::new(plan, &below.schema, index, exprs, names)?;
504                let schema = project.schema().clone();
505                let counters = self.watch(reference, id, pipeline, "Project", None);
506                below.then(Arc::new(Watched::new(project, counters)), schema)
507            }
508            Node::Aggregate { input, index, groups, aggregates } => {
509                self.aggregate(reference, input, index, groups, aggregates, None)?
510            }
511            Node::Sort { input, keys } => {
512                let below = self.node(input)?;
513                let schema = below.schema.clone();
514                let (sort, out) = Sort::new(plan, &schema, keys, memory)?;
515                let counters = self.watch(reference, id, pipeline, "Sort", None);
516                let reading = Arc::clone(&counters);
517                self.close(below, pipeline, Arc::new(Watched::new(sort, counters)));
518                Segment::reading(Arc::new(Watched::new(out, reading)), schema, pipeline)
519            }
520            Node::Limit { input, count, offset } => {
521                let max_groups = count
522                    .and_then(|count| count.checked_add(offset))
523                    .and_then(|count| usize::try_from(count).ok());
524                let below = match (plan.node(input).clone(), max_groups) {
525                    (
526                        Node::Aggregate { input: under, index, groups, aggregates },
527                        Some(max_groups),
528                    ) => {
529                        self.aggregate(input, under, index, groups, aggregates, Some(max_groups))?
530                    }
531                    _ => self.node(input)?,
532                };
533                let schema = below.schema.clone();
534                let limit = Limit::new(count, offset);
535                let counters = self.watch(reference, id, pipeline, "Limit", None);
536                below.then(Arc::new(Watched::new(limit, counters)), schema)
537            }
538            Node::TopN { input, keys, count, offset } => {
539                let below = self.node(input)?;
540                let schema = below.schema.clone();
541                let (top, out) = TopN::new(plan, &schema, keys, count, offset, memory)?;
542                let counters = self.watch(reference, id, pipeline, "TopN", None);
543                let reading = Arc::clone(&counters);
544                self.close(below, pipeline, Arc::new(Watched::new(top, counters)));
545                Segment::reading(Arc::new(Watched::new(out, reading)), schema, pipeline)
546            }
547            Node::Distinct { input, on } => {
548                let below = self.node(input)?;
549                let schema = below.schema.clone();
550                let (distinct, out) = Distinct::new(plan, &schema, on, memory)?;
551                let counters = self.watch(reference, id, pipeline, "Distinct", None);
552                let reading = Arc::clone(&counters);
553                self.close(below, pipeline, Arc::new(Watched::new(distinct, counters)));
554                Segment::reading(Arc::new(Watched::new(out, reading)), schema, pipeline)
555            }
556            Node::Join { left, right, kind, conditions } => {
557                // The right side runs first, because no left row can be answered until every right
558                // row it might match has been seen. That is the dependency edge, and it is the same
559                // one the hash join builds on. The probing side is a pipeline of its own rather than
560                // part of the one above it, because it ends in a sink, and it waits for the build
561                // side.
562                let gather_id = self.gathered(reference);
563                let gathering = self.shape.pipeline(right);
564                let right = self.node(right)?;
565                let right_schema = right.schema.clone();
566                let (gather, gathered) = Gather::new(memory);
567                let kept = self.watch(reference, gather_id, gathering, "Gather", None);
568                self.close(right, gathering, Arc::new(Watched::new(gather, kept)));
569                let mut left = self.node(left)?;
570                let side = Gathered { schema: &right_schema, rows: gathered };
571                let (join, out) =
572                    Join::new(plan, &left.schema, side, kind, conditions, self.cancel, memory);
573                let schema = join.schema().clone();
574                let counters = self.watch(reference, id, pipeline, "Join", None);
575                let reading = Arc::clone(&counters);
576                left.after.push(gathering);
577                self.close(left, pipeline, Arc::new(Watched::new(join, counters)));
578                Segment::reading(Arc::new(Watched::new(out, reading)), schema, pipeline)
579            }
580            Node::CrossProduct { left, right } => {
581                // The right side runs first and is kept as the chunks it arrived in, because it is
582                // replayed once per left row. The left side streams, which is the whole point of
583                // this operator: the product is produced a chunk at a time and never held, so the
584                // product stays in the pipeline the left rows came from rather than starting one.
585                let keep_id = self.gathered(reference);
586                let aside = self.shape.pipeline(right);
587                let right = self.node(right)?;
588                let right_schema = right.schema.clone();
589                let (keep, kept) = Keep::new(memory);
590                let held = self.watch(reference, keep_id, aside, "Keep", None);
591                self.close(right, aside, Arc::new(Watched::new(keep, held)));
592                let mut left = self.node(left)?;
593                let cross = CrossProduct::new(&left.schema, &right_schema, kept);
594                let schema = cross.schema().clone();
595                let counters = self.watch(reference, id, pipeline, "CrossProduct", None);
596                left.after.push(aside);
597                left.then(Arc::new(Watched::new(cross, counters)), schema)
598            }
599            Node::SetOp { left, right, kind, all, index } => {
600                // The right side runs first, because nothing can be said about a left row until the
601                // whole right side has been counted. That is the dependency edge, spelled out.
602                let gather_id = self.gathered(reference);
603                let counting = self.shape.pipeline(right);
604                let right = self.node(right)?;
605                let (gather, gathered) = Gather::new(memory);
606                let kept = self.watch(reference, gather_id, counting, "Gather", None);
607                self.close(right, counting, Arc::new(Watched::new(gather, kept)));
608                let mut left = self.node(left)?;
609                let (setop, out) = SetOp::new(&left.schema, gathered, kind, all, index, memory);
610                let schema = setop.schema().clone();
611                let counters = self.watch(reference, id, pipeline, "SetOp", None);
612                let reading = Arc::clone(&counters);
613                left.after.push(counting);
614                self.close(left, pipeline, Arc::new(Watched::new(setop, counters)));
615                Segment::reading(Arc::new(Watched::new(out, reading)), schema, pipeline)
616            }
617        };
618        Ok(segment)
619    }
620}