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