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: one implementation of each logical operator
6//! the binder can produce, every one of them written the simplest way that is correct. Tier 0 is
7//! never removed and never optional, because it is the reference every faster tier is differentially
8//! tested against, and a reference that is clever is a reference nobody can read the answer out of
9//! when the clever tier disagrees with it.
10//!
11//! # A query is a list of pipelines
12//!
13//! [`build`] turns a plan into a [`Query`], which is the pipelines that run it in the order they
14//! have to run plus the queue the rows come out of. A pipeline is a run of operators from a source
15//! to a sink, and a plan is cut into several of them wherever an operator cannot produce anything
16//! until it has consumed everything: the aggregate, the sort, the top n, the distinct, the set
17//! operations and the side of a join that is gathered first. [`Query::run`] hands each of them to
18//! [`run_serial`](rudb_pipeline::run_serial), and that is the whole of the execution path.
19//!
20//! It used to be a tree of operators with a `next` on each of them, driven by pulling the root, and
21//! the breakers drained the tree below them on the first pull. The order was the same order, because
22//! a breaker cannot answer until its input is finished either way. What changed is that the order is
23//! now written down as a list rather than being whatever the call stack happened to do, which is the
24//! thing a scheduler can be handed.
25//!
26//! # The three interfaces
27//!
28//! They live in `rudb-pipeline`. The table scan, the dummy scan, the series, the file scan, the
29//! values list, the strategies table and the buffer a breaker finalises into are
30//! [`Source`](rudb_pipeline::Source) implementations, the filter, the projection, the limit, the
31//! fetch and the cross product are [`Stream`](rudb_pipeline::Stream) implementations, and the sort,
32//! the top N, the distinct, the set operations, the aggregate and the join are
33//! [`Sink`](rudb_pipeline::Sink) implementations. All of them take `&self` and are handed the mutable
34//! part separately, so one of them can be instantiated on as many threads as the scheduler wants
35//! without copying its predicate or its key list.
36//!
37//! A source is the one of the three that is shared rather than instanced, so the position it is up
38//! to is an atomic and a morsel goes to whoever asks for it first. What a morsel covers is each
39//! source's own business: one stored chunk for a table scan, a run of sixteen chunks for a series
40//! because those rows are worked out rather than read, one row group for a Parquet scan, and a whole
41//! file for a CSV one, because nothing in a CSV says where a row begins until every byte before it
42//! has been parsed.
43//!
44//! Being in the shape is not the same as being parallel. There is still one instance of each
45//! pipeline, because the driver is the serial one, and the pool and the scheduler that run several
46//! are the next milestone. What the operators already do is merge: a grouped aggregate combines a
47//! second instance's table into the one it is keeping, so the thing standing between here and
48//! several threads is the driver rather than the operators.
49//!
50//! An operator with two inputs is two pipelines with an edge between them, and the set operation
51//! and the join are both built that way. The side that has to finish first ends in a
52//! `gather::Gather`, which holds its rows and does nothing else, and the side that uses it reads
53//! them through a handle. That edge is the one [`Query::run`] takes its order from, and for the join
54//! it is where the hash table goes when #62 replaces the nested loop.
55//!
56//! The cross product sits on that edge too, and it is the operator that made `rudb-pipeline` grow a
57//! [`Progress::Again`](rudb_pipeline::Progress::Again). One of its input chunks becomes as many
58//! output chunks as its right side has, which a stream could not say and a sink could only answer by
59//! holding the whole product. Its right side is kept as chunks rather than rows, by the other sink
60//! in `gather`, because it replays them as they stand. Unlike every other two input operator it does
61//! not start a pipeline of its own, because the product is produced a chunk at a time and never
62//! held, so it stays in the pipeline its left rows came from.
63//!
64//! A sink finalises into a `buffer::Buffered`, which is a separate source that reads the finished
65//! chunks back out, rather than handing them back from `finalize`. That split is what makes the
66//! parallel read possible later and it costs nothing now, and it is what a pipeline downstream of a
67//! breaker sources from.
68//!
69//! # Why a schema per operator
70//!
71//! A bound plan refers to columns by [`ColumnBinding`](rudb_plan::ColumnBinding), which is a table
72//! index and a position, and a chunk is a row of vectors with no names on it. Something has to turn
73//! one into the other, and that something is [`Schema`]: it is what an operator says it produces,
74//! it carries the binding alongside the name and the type, and [`Schema::position_of`] is the whole
75//! of expression column resolution. Building it is where the operators agree with the binder about
76//! what a table index means, and it is checked rather than assumed, because a schema that is one
77//! column out produces a wrong answer instead of an error.
78
79#![forbid(unsafe_code)]
80
81mod buffer;
82mod build;
83mod entrynames;
84mod expr;
85mod fetch;
86mod functionnames;
87mod gather;
88mod group;
89mod join;
90mod key;
91mod keywords;
92mod metadata;
93mod ordering;
94mod prepared;
95mod query;
96mod register;
97mod rows;
98mod schema;
99mod setop;
100mod settingnames;
101mod sort;
102mod source;
103mod spill;
104mod strategies;
105mod stream;
106mod table;
107mod topn;
108mod typenames;
109mod written;
110
111#[cfg(test)]
112mod tests;
113
114pub use build::{build, build_measured, build_with};
115pub use expr::{evaluate, evaluate_all};
116pub use prepared::{Prepared, Scratch};
117pub use query::Query;
118pub use register::registries;
119pub use schema::Schema;
120pub use written::written;