Skip to main content

rudb_exec/
lib.rs

1//! Operators, morsels, the scheduler, hash tables, sorting and spilling.
2//!
3//! Rank 12 in the layer rule. See `xtask/layers.toml` and `spec/18-package-layout.md`.
4//!
5//! This is tier 0 of `spec/08-codegen.md` section 8.1: a pull based tree of operators, one variant
6//! per logical operator the binder can produce, every one of them written the simplest way that is
7//! correct. Tier 0 is never removed and never optional, because it is the reference every faster
8//! tier is differentially tested against, and a reference that is clever is a reference nobody can
9//! read the answer out of when the clever tier disagrees with it.
10//!
11//! # What pull based means here and what it does not mean
12//!
13//! [`Operator::next`] returns the next [`Chunk`](rudb_vector::Chunk) or `None` when there are no
14//! more. A pipeline of a scan, a filter and a projection is three of those calls deep and nothing
15//! is materialized between them. An operator that cannot answer without seeing all of its input,
16//! which is the aggregate, the sort, the join build side, the distinct and the set operations, does
17//! all of its work on the first call to `next` and then hands out what it built one chunk at a
18//! time. That is what `spec/07-execution.md` calls a pipeline breaker and it is the boundary the
19//! morsel driven scheduler will later cut pipelines at.
20//!
21//! What this is not is the scheduler. There is one thread, the morsels a source hands out are all
22//! read by it, and the hash join is a nested loop. Every one of those is M1 or later work and every
23//! one of them replaces an operator here without changing the tree that builds it, because the
24//! thing that builds the tree is [`build`] and the thing it builds against is a trait with two
25//! methods.
26//!
27//! # The move to push
28//!
29//! The interface every operator ends up behind is in `rudb-pipeline`, and they moved to it one at a
30//! time rather than in one commit. Every one of them is there now. The table scan, the dummy scan,
31//! the series, the file scan, the values list and the strategies table are
32//! [`Source`](rudb_pipeline::Source) implementations, the filter, the projection, the limit and the
33//! cross product are [`Stream`](rudb_pipeline::Stream) implementations, and the sort, the top N, the
34//! distinct, the set operations, the aggregate and the join are [`Sink`](rudb_pipeline::Sink)
35//! implementations. All of them take `&self` and are handed the mutable part separately, so one of
36//! them can be instantiated on as many threads as F4 wants without copying its predicate or its key
37//! list.
38//!
39//! A source is the one of the three that is shared rather than instanced, so the position it is up
40//! to is an atomic and a morsel goes to whoever asks for it first. What a morsel covers is each
41//! source's own business: one stored chunk for a table scan, a run of sixteen chunks for a series
42//! because those rows are worked out rather than read, and the whole file list for a file scan,
43//! since both file readers are a position in a file and cannot be asked for the tenth chunk without
44//! having read the nine before it.
45//!
46//! What is left of the pull side is the shape of the tree and the adapters that drive it, which is
47//! `adapt` and nothing else.
48//!
49//! Being in the shape is not the same as being parallel. The aggregate holds its hash table in the
50//! instance, which is where it has to be, and merging two of those tables needs a serialize and a
51//! combine per aggregate that nothing implements yet, so a second instance is refused rather than
52//! answered wrongly. That is the one place where F4 has work left in an operator rather than in the
53//! scheduler.
54//!
55//! An operator with two inputs is two pipelines with an edge between them, and the set operation
56//! and the join are both built that way. The side that has to finish first ends in a
57//! `gather::Gather`, which holds its rows and does nothing else, and the side that uses it reads
58//! them through a handle. That edge is the one the scheduler will read off the plan, and for the
59//! join it is where the hash table goes when #62 replaces the nested loop.
60//!
61//! The cross product sits on that edge too, and it is the operator that made `rudb-pipeline` grow a
62//! [`Progress::Again`](rudb_pipeline::Progress::Again). One of its input chunks becomes as many
63//! output chunks as its right side has, which a stream could not say and a sink could only answer by
64//! holding the whole product. Its right side is kept as chunks rather than rows, by the other sink
65//! in `gather`, because it replays them as they stand.
66//!
67//! A sink finalises into a `buffer::Buffered`, which is a separate source that reads the finished
68//! chunks back out, rather than handing them back from `finalize`. That split is what makes the
69//! parallel read possible later and it costs nothing now.
70//!
71//! `adapt` is the one thing that knows how to put a pushing operator in a pulling tree, and now
72//! that every operator has moved it is the whole of the pull side. What it does not do yet is cut
73//! the tree into pipelines and hand them to [`run_serial`](rudb_pipeline::run_serial), which is the
74//! next step and the one that deletes this file rather than changing it.
75//!
76//! # Why a schema per operator
77//!
78//! A bound plan refers to columns by [`ColumnBinding`](rudb_plan::ColumnBinding), which is a table
79//! index and a position, and a chunk is a row of vectors with no names on it. Something has to turn
80//! one into the other, and that something is [`Schema`]: it is what an operator says it produces,
81//! it carries the binding alongside the name and the type, and [`Schema::position_of`] is the whole
82//! of expression column resolution. Building it is where the operators agree with the binder about
83//! what a table index means, and it is checked rather than assumed, because a schema that is one
84//! column out produces a wrong answer instead of an error.
85
86#![forbid(unsafe_code)]
87
88mod adapt;
89mod buffer;
90mod build;
91mod cancel;
92mod expr;
93mod gather;
94mod group;
95mod join;
96mod key;
97mod operator;
98mod prepared;
99mod register;
100mod rows;
101mod schema;
102mod setop;
103mod sort;
104mod source;
105mod spill;
106mod strategies;
107mod stream;
108mod table;
109mod topn;
110mod written;
111
112#[cfg(test)]
113mod tests;
114
115pub use build::{build, build_measured, build_with};
116pub use expr::{evaluate, evaluate_all};
117pub use operator::Operator;
118pub use prepared::{Prepared, Scratch};
119pub use register::registries;
120pub use schema::Schema;
121pub use written::written;