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}