rudb_plan/node.rs
1//! Logical operators.
2//!
3//! One variant per operator, covering what the M0 binder can produce out of what the transformer
4//! in `rudb-parse` can produce. That is a smaller set than DuckDB's and it is smaller on purpose:
5//! an operator here that nothing constructs is an operator whose textual form, whose validation
6//! and whose rewrite rules have never been run, and the first thing that happens when the binder
7//! finally emits one is that all three turn out to be wrong.
8//!
9//! Every operator that introduces new columns carries a table index, which is the left half of a
10//! [`ColumnBinding`](crate::ColumnBinding). [`Node::Filter`], [`Node::Sort`], [`Node::Limit`],
11//! [`Node::TopN`], [`Node::Distinct`] and [`Node::Join`] do not have one, because they pass their
12//! input's columns through unchanged and a binding that survives a filter should not have to be
13//! rewritten by it.
14
15use crate::{ExprRef, NodeRef, Slice, StrRef};
16
17/// How a window frame measures its bounds.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum WindowUnit {
20 Rows,
21 Range,
22 Groups,
23}
24
25/// One end of a window frame.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum WindowBound {
28 UnboundedPreceding,
29 Preceding(ExprRef),
30 CurrentRow,
31 Following(ExprRef),
32 UnboundedFollowing,
33}
34
35/// Which peers a window frame removes after its bounds are applied.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum WindowExclude {
38 NoOthers,
39 CurrentRow,
40 Group,
41 Ties,
42}
43
44/// The complete frame shared by a compatible run of window expressions.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct WindowFrame {
47 pub unit: WindowUnit,
48 pub start: WindowBound,
49 pub end: WindowBound,
50 pub exclude: WindowExclude,
51}
52
53/// One end of a [`Node::Limit`], which is a row count or an offset.
54///
55/// Nearly every limit written is a number, and a number is what the binder writes down when it can
56/// work one out. What it cannot work out is a subquery, which has to run before there is a value,
57/// and a call that answers differently every time it is made, such as `RANDOM()` or `nextval`. The
58/// pin takes both of those and so does this, by evaluating the expression while the query runs
59/// rather than while it is planned.
60///
61/// [`Bound::Read`] is how. The binder joins the query or the call in underneath as a single row,
62/// which puts its one value in a column of every row the limit sees, and the limit reads that
63/// column off the first chunk that reaches it and uses the number for the rest of the query. The
64/// column is a column the query did not ask for, so the binder puts a projection over the limit
65/// that drops it again.
66///
67/// A [`Bound::Read`] count is why the rewrites that need a number have to check: a limit over a
68/// sort only becomes a [`Node::TopN`] when the count is known while the plan is built, and a limit
69/// cannot move below the projection that produces the column it reads.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum Bound {
72 /// Every row, which is what leaving a `LIMIT` off means. Never an offset.
73 All,
74 /// A number the binder worked out.
75 Rows(u64),
76 /// A column of the input holding the number, the same in every row.
77 Read(ExprRef),
78}
79
80impl Bound {
81 /// The number, when it is one already.
82 #[must_use]
83 pub fn rows(self) -> Option<u64> {
84 match self {
85 Self::Rows(rows) => Some(rows),
86 Self::All | Self::Read(_) => None,
87 }
88 }
89
90 /// The column this reads, when it reads one.
91 #[must_use]
92 pub fn read(self) -> Option<ExprRef> {
93 match self {
94 Self::Read(expr) => Some(expr),
95 Self::All | Self::Rows(_) => None,
96 }
97 }
98}
99
100/// The share a `LIMIT` written as a percentage names.
101///
102/// The two arms are the two things somebody can write. `LIMIT 30 PERCENT` and `LIMIT 30%` are a
103/// number the binder works out, and the check that it is between nought and a hundred happens while
104/// the query is bound. `LIMIT (SELECT 30)%` is a number nobody has before the query runs, so it
105/// arrives as a column of the input exactly the way a [`Bound::Read`] count does, and the range
106/// check moves to where the value turns up.
107///
108/// A share the query wrote as a subquery is always written with the sign rather than the word,
109/// because the grammar refuses `PERCENT` after a closing bracket, in both engines. That is a rule
110/// about spelling and not about what the node can hold.
111#[derive(Debug, Clone, Copy, PartialEq)]
112pub enum Share {
113 /// A percentage the binder worked out, from nought to a hundred.
114 Percent(f64),
115 /// A column of the input holding the percentage, the same in every row.
116 Read(ExprRef),
117}
118
119impl Share {
120 /// The percentage, when it is one already.
121 #[must_use]
122 pub fn percent(self) -> Option<f64> {
123 match self {
124 Self::Percent(percent) => Some(percent),
125 Self::Read(_) => None,
126 }
127 }
128
129 /// The column this reads, when it reads one.
130 #[must_use]
131 pub fn read(self) -> Option<ExprRef> {
132 match self {
133 Self::Read(expr) => Some(expr),
134 Self::Percent(_) => None,
135 }
136 }
137}
138
139/// One logical operator.
140///
141/// Children are the inputs, in the order [`Node::children`] returns them, which is the order they
142/// print in and the order the reader expects.
143///
144/// `PartialEq` and not `Eq`, because [`Node::LimitPercent`] holds a percentage as a `f64`.
145/// [`Value`](rudb_common::Value) is the same shape for the same reason.
146#[derive(Debug, Clone, PartialEq)]
147pub enum Node {
148 /// A base table scan.
149 ///
150 /// The projection is in `columns`, so a scan of two columns of a 105-column table is a two
151 /// column scan in the plan and not a filter over a wide one. `spec/09-optimizer.md` section
152 /// 9.2 calls projection pushdown the difference between 20 GB and 200 MB on ClickBench, and
153 /// this is the field it pushes into.
154 Get {
155 /// The catalog name.
156 catalog: StrRef,
157 /// The schema name.
158 schema: StrRef,
159 /// The table name.
160 table: StrRef,
161 /// The alias the query used, which is what an error message should say.
162 alias: StrRef,
163 /// The table index that this scan's columns bind against.
164 index: u32,
165 /// The projected columns with their types, into the field pool.
166 columns: Slice,
167 },
168 /// One row and no columns.
169 ///
170 /// What `SELECT 1` sits on top of. Not an empty result: an empty result produces no rows and
171 /// `SELECT 1` produces one, and conflating them is how a scalar subquery starts returning
172 /// nothing instead of null.
173 Dummy,
174 /// Literal rows.
175 ///
176 /// Every row has the same length as `columns`, which [`Plan::validate`](crate::Plan::validate)
177 /// checks, because a ragged `VALUES` is a wrong answer rather than a crash.
178 Values {
179 /// The table index that these columns bind against.
180 index: u32,
181 /// The output columns with their types, into the field pool.
182 columns: Slice,
183 /// The rows, into the row pool, each row a slice of the expression list pool.
184 rows: Slice,
185 },
186 /// A function call where a table goes, such as `range(10)`.
187 ///
188 /// The arguments are expressions rather than numbers, because `range(2 + 3)` is a legal call
189 /// and folding it here would mean the plan could not be printed back as what was written. They
190 /// cannot refer to a column: a table function that sees the row on its left is `LATERAL`, and
191 /// that is [`Node::LateralFunction`].
192 ///
193 /// A separate node from [`Node::Values`] even though `range(3)` and `VALUES (0), (1), (2)`
194 /// produce the same rows, because the one that produces three million rows should be three
195 /// numbers in the plan rather than three million expressions in it.
196 TableFunction {
197 /// The table index that this call's columns bind against.
198 index: u32,
199 /// Which function, as its own canonical name.
200 function: StrRef,
201 /// The arguments, into the expression list pool.
202 args: Slice,
203 /// The names of the named parameters the call was written with, into the name pool.
204 ///
205 /// `read_csv('f.csv', delim=';')` keeps the `delim` here rather than only in whatever the
206 /// binder made of it, because the executor opens the file a second time and has to open it
207 /// the same way. A parameter the binder answers on its own, such as `binary_as_string`,
208 /// is here too, so that a plan prints back as the call that was written.
209 options: Slice,
210 /// What each of those names was given, into the expression list pool and the same length.
211 ///
212 /// Constants, every one of them. The binder refuses anything else, because a parameter can
213 /// decide what the columns are and the columns are settled there.
214 settings: Slice,
215 /// The produced columns with their types, into the field pool.
216 columns: Slice,
217 },
218 /// A table function evaluated once per row of its input, which is what `LATERAL` means.
219 ///
220 /// `FROM t, range(t.n)` is this. A table function's arguments are what produce its rows rather
221 /// than something read over rows that already exist, so there is nothing underneath one for a
222 /// domain to be pushed into and nothing the rules in the unnesting pass can rewrite it into.
223 /// This is the operator those rules stop at: the domain goes in on the left, the arguments read
224 /// it, and the call is made once per row of it.
225 ///
226 /// The output is the input's columns followed by the function's, which is a cross product whose
227 /// right side is allowed to change per left row. That is what lets the join putting the rows
228 /// back beside their outer row sit above this and read the domain columns where it reads them
229 /// everywhere else.
230 ///
231 /// Only the series family reaches here. A reader takes a file name, the binder settles the
232 /// columns by opening the file, and a name that is not a constant is refused there, so a
233 /// correlated `read_csv` never gets this far.
234 LateralFunction {
235 /// The rows the call is made against, one call per row.
236 input: NodeRef,
237 /// The table index that this call's columns bind against.
238 index: u32,
239 /// Which function, as its own canonical name.
240 function: StrRef,
241 /// The arguments, into the expression list pool, read against a row of `input`.
242 args: Slice,
243 /// The names of the named parameters the call was written with, into the name pool.
244 options: Slice,
245 /// What each of those names was given, into the expression list pool and the same length.
246 settings: Slice,
247 /// The produced columns with their types, into the field pool, not counting the input's.
248 columns: Slice,
249 },
250 /// A predicate over the input, keeping the rows where it is true.
251 ///
252 /// True, not "not false". A null predicate drops the row, which is SQL's rule and is the
253 /// difference between `WHERE` and `CHECK`.
254 Filter {
255 /// The input.
256 input: NodeRef,
257 /// The predicate, which has to be `BOOLEAN`.
258 predicate: ExprRef,
259 },
260 /// A projection, producing a new set of columns from the input's.
261 Project {
262 /// The input.
263 input: NodeRef,
264 /// The table index the produced columns bind against.
265 index: u32,
266 /// The expressions, into the expression list pool.
267 exprs: Slice,
268 /// One output name per expression, into the name list pool.
269 ///
270 /// Names are carried through the whole plan rather than attached at the root, because the
271 /// thing a person reads a plan dump to answer is usually which column this is, and a dump
272 /// with the names stripped out answers that with a number.
273 names: Slice,
274 },
275 /// A grouped or ungrouped aggregation.
276 ///
277 /// The output is the group expressions followed by the aggregates, in that order, and that is
278 /// what a binding into `index` means. An ungrouped aggregate has an empty `groups` and still
279 /// produces exactly one row, including over an empty input.
280 Aggregate {
281 /// The input.
282 input: NodeRef,
283 /// The table index the produced columns bind against.
284 index: u32,
285 /// The group expressions, into the expression list pool.
286 groups: Slice,
287 /// The aggregate expressions, into the expression list pool. Every element is an
288 /// [`Expr::Aggregate`](crate::Expr::Aggregate) and this is the only place one may appear.
289 aggregates: Slice,
290 },
291 /// Window expressions that share one partition, ordering, and frame.
292 Window {
293 /// Rows over which the windows are evaluated.
294 input: NodeRef,
295 /// The table index of the appended window result columns.
296 index: u32,
297 /// Expressions that divide the input into independent partitions.
298 partition: Slice,
299 /// The ordering within each partition.
300 order: Slice,
301 /// The complete frame shared by this compatible expression run.
302 frame: WindowFrame,
303 /// Direct [`Expr::Window`](crate::Expr::Window) expressions appended to the input columns.
304 expressions: Slice,
305 },
306 /// An ordering.
307 Sort {
308 /// The input.
309 input: NodeRef,
310 /// The keys in priority order, into the sort key pool.
311 keys: Slice,
312 },
313 /// A row count limit and an offset.
314 ///
315 /// Both are a [`Bound`], which is a number when the query said one and a column of the input
316 /// when it wrote something the binder could not settle. See [`Bound`] for what puts the value
317 /// in that column and who reads it.
318 Limit {
319 /// The input.
320 input: NodeRef,
321 /// How many rows to emit, or all of them.
322 count: Bound,
323 /// How many rows to skip first.
324 offset: Bound,
325 },
326 /// A limit written as a share of the input rather than as a row count.
327 ///
328 /// `LIMIT 30 PERCENT` over ten rows is three rows, and it is a node of its own rather than a
329 /// [`Node::Limit`] with another field for three reasons. The share is of the whole input, so
330 /// this cannot emit anything until it has counted every row, where a plain limit hands each
331 /// chunk on as it arrives and stops the scan early. The rewrites that fire on a plain limit are
332 /// wrong here: a filter pushed under this one changes how many rows there are to take a share
333 /// of, and the sort underneath it cannot become a top n because the count is not known until
334 /// the sort has finished. And the pin builds a separate `Limit Percent` operator for it, which
335 /// is the same split one layer down.
336 ///
337 /// A share the binder worked out is between nought and a hundred inclusive, checked while the
338 /// query is bound, because that is where the pin refuses `LIMIT 101 PERCENT` too. The offset is
339 /// applied after the share has been worked out, so `LIMIT 30 PERCENT OFFSET 2` over ten rows is
340 /// three rows starting at the third.
341 ///
342 /// Both fields can be read off the rows instead of being a number written down here, for the
343 /// same reason a plain limit's count can. `LIMIT (SELECT 30)% OFFSET (SELECT 2)` holds two
344 /// numbers nobody has before the query runs, so each arrives as a column of the input and is
345 /// read off the first chunk. See [`Share`] and [`Bound`].
346 LimitPercent {
347 /// The input.
348 input: NodeRef,
349 /// The share of the input to emit, from nought to a hundred.
350 percent: Share,
351 /// How many rows to skip first. Never [`Bound::All`], which is not an offset.
352 offset: Bound,
353 },
354 /// A sort with a limit over it, which never holds more rows than the limit can emit.
355 ///
356 /// The same answer as a [`Node::Limit`] over a [`Node::Sort`] and a different amount of work.
357 /// A sort has to see every row before it can emit the first one, so it holds the whole input;
358 /// this holds the rows that could still come out and throws the rest away as it goes, which on
359 /// `ORDER BY x LIMIT 10` over a hundred million rows is ten rows rather than a hundred million.
360 ///
361 /// `count` is not optional, because `LIMIT ALL` over a sort is a sort and there would be nothing
362 /// to bound. The offset is part of the node rather than left above it, since the rows that are
363 /// skipped still have to be found to be skipped, so what this has to keep is `count + offset`.
364 TopN {
365 /// The input.
366 input: NodeRef,
367 /// The keys in priority order, into the sort key pool.
368 keys: Slice,
369 /// How many rows to emit.
370 count: u64,
371 /// How many rows to skip first.
372 offset: u64,
373 },
374 /// The columns of rows something below already picked out, read back from the file by ordinal.
375 ///
376 /// This is the top half of late materialisation. A `SELECT * FROM hits ORDER BY EventTime LIMIT
377 /// 10` over a hundred and five columns needs one column to decide which ten rows win and all
378 /// hundred and five of those ten rows afterwards, and a plan that carries the wide rows through
379 /// the top N reads the whole file to throw almost all of it away. The rewrite in
380 /// `rudb-opt`'s `late` module narrows the scan under the top N to the ordering columns plus the
381 /// row's ordinal inside its file, and puts this above it to read the rest for the rows that
382 /// survived.
383 ///
384 /// The ordinals come out of the input rather than being counted here, because the operator that
385 /// counted them is the scan and everything between the scan and here may have dropped rows. The
386 /// column that holds them is [`Self::Fetch::row`], and the scan produced it because the rewrite
387 /// turned `file_row_number` on.
388 ///
389 /// The produced columns are the whole row and not only the deferred part, so the answer is one
390 /// read of the file at the ordinals rather than a stitch of what was carried with what was
391 /// fetched. That costs the ordering column a second read of a few pages and saves the plan above
392 /// this from having any idea the rewrite happened.
393 Fetch {
394 /// The input, which carries each row's ordinal inside the file.
395 input: NodeRef,
396 /// The table index the produced columns bind against, which is the one the node this
397 /// replaced produced, so that nothing above has to be rebound.
398 index: u32,
399 /// The file, into the expression list pool. One constant path, because a row ordinal only
400 /// says which row when there is one file it could be in.
401 args: Slice,
402 /// The produced columns with their types, into the field pool.
403 columns: Slice,
404 /// The input column holding the ordinal, which has to be `BIGINT`.
405 row: ExprRef,
406 },
407 /// Rows of a catalog table read back by their table-wide ordinal.
408 TableFetch {
409 input: NodeRef,
410 index: u32,
411 catalog: StrRef,
412 schema: StrRef,
413 table: StrRef,
414 columns: Slice,
415 row: ExprRef,
416 },
417 /// Duplicate elimination, over the whole row or over named expressions.
418 Distinct {
419 /// The input.
420 input: NodeRef,
421 /// The `DISTINCT ON` expressions, into the expression list pool. Empty means the whole
422 /// row, which is plain `DISTINCT`.
423 on: Slice,
424 },
425 /// A join with a condition.
426 Join {
427 /// The left input.
428 left: NodeRef,
429 /// The right input.
430 right: NodeRef,
431 /// Which join.
432 kind: JoinKind,
433 /// The conditions, into the expression list pool, combined with `AND`. Empty is a join
434 /// with no condition, which for an inner join is a cross product and for an outer join
435 /// is not.
436 conditions: Slice,
437 /// Which input is gathered whole before the other one starts.
438 ///
439 /// The binder emits [`BuildSide::Right`] for everything, because at binding time there is
440 /// nothing to choose with. `rudb_opt`'s `sides` pass overwrites it from an estimate, and
441 /// the executor honours whatever it finds here.
442 build: BuildSide,
443 },
444 /// A join answered by reading a stored forward link rather than by building a hash table.
445 ///
446 /// spec/graph/05-execution.md section 5.2. The condition is `child.fk = parent.pk` for a
447 /// declared relationship whose forward link is stored, and the child side reaches the join
448 /// with its row id intact. Where a [`Self::Join`] builds one side and probes it with the
449 /// other, this reads one link value beside the child's own columns and emits the parent's
450 /// projected columns as gathers into the parent's column vectors. There is no build side,
451 /// no hash table, no probe and no materialization of the parent per child row.
452 ///
453 /// It is a node of its own rather than a flag on [`Self::Join`] because the two are different
454 /// operators with different shapes: this one has a driving side and no gathered side, so the
455 /// pipeline under it is one pipeline rather than two, and a reader of an explain output that
456 /// saw `Join` with a flag would have to know the flag to know what ran.
457 ///
458 /// Nothing produces this unless the graph sections are on. Section 3.1 says deleting every
459 /// graph section from a file must change no answer, only the time, so every plan holding one
460 /// of these is a plan the optimizer could have written as a [`Self::Join`] over the same two
461 /// inputs, and the rule that rewrites it says so by construction.
462 LinkJoin {
463 /// The child input, whose rows carry the row id the link is indexed by.
464 child: NodeRef,
465 /// The parent input.
466 ///
467 /// Never scanned as a pipeline. It is here so that the column bindings above this node
468 /// keep naming the scan they named, so that the projection pushdown pass can still see
469 /// which of the parent's columns are wanted, and so that dropping back to a hash join is
470 /// a change of node rather than a re-plan.
471 parent: NodeRef,
472 /// Inner, left, semi or anti. Section 5.2 handles no others: right and full need the
473 /// parent rows nothing pointed at, which is the backward direction.
474 kind: JoinKind,
475 /// The join condition, into the expression list pool. One equality per key column, which
476 /// is one or two, and is what makes this shape recognizable at all.
477 conditions: Slice,
478 /// The child column holding the row id the link is indexed by, which has to be `BIGINT`.
479 ///
480 /// Named here rather than looked for by the builder, the same way [`Self::Fetch`] names
481 /// its ordinal. The rule that writes this node is the one thing that has proved the row id
482 /// survives to here, by way of [`crate::rids_of`], and a builder that went looking for the
483 /// column by name afterwards would be trusting a name where the rule trusted an analysis.
484 rid: ExprRef,
485 },
486 /// A join whose right input can refer to columns produced by its left input.
487 ///
488 /// Binding emits this for a correlated subquery. The unnesting pass has to replace every one
489 /// before execution, so the executor never evaluates the right input once per left row.
490 DependentJoin {
491 /// The outer input whose columns the right side may reference.
492 left: NodeRef,
493 /// The correlated input.
494 right: NodeRef,
495 /// Which result shape the subquery needs.
496 kind: JoinKind,
497 /// Conditions introduced while binding the subquery.
498 conditions: Slice,
499 },
500 /// An unconditional cross product.
501 ///
502 /// Separate from a [`Node::Join`] with no conditions because join ordering treats them
503 /// differently: a cross product has no edge in the join graph and section 9.4's dynamic
504 /// program enumerates connected subgraphs.
505 CrossProduct {
506 /// The left input.
507 left: NodeRef,
508 /// The right input.
509 right: NodeRef,
510 },
511 /// A `WITH name AS MATERIALIZED (...)`, which is run once and read wherever it is named.
512 ///
513 /// The left input is the definition and the right input is the query that reads it. They are
514 /// in that order because that is the order they run in: the definition is a pipeline breaker
515 /// whichever operators are in it, since nothing above may start until the rows are all there.
516 ///
517 /// A plain `WITH` is not this. The reference binary inlines one at every use whatever its
518 /// shape and however many times it is named, and the only decision left is whether the rows
519 /// are needed at all, which is why an unused one is dropped rather than run for nothing.
520 MaterializedCte {
521 /// The query whose rows are held.
522 definition: NodeRef,
523 /// The query that reads them, which is where every [`Node::CteScan`] for this one is.
524 body: NodeRef,
525 /// The name it was written with, which is what the printer and an error message say.
526 name: StrRef,
527 /// Which materialisation this is, matching the `cte` of the scans that read it.
528 ///
529 /// A number of its own rather than the table index, because a scan binds against its own
530 /// index and two scans of one materialisation have two of those.
531 cte: u32,
532 /// The held columns with their types, into the field pool.
533 columns: Slice,
534 },
535 /// A read of a [`Node::MaterializedCte`] that has already run.
536 ///
537 /// A leaf, the same way a table scan is. What it reads was computed by a node above it rather
538 /// than by a node under it, which is the one place in the plan where that is true, and it is
539 /// why the materialisation holds its body as an input rather than sitting beside it.
540 CteScan {
541 /// The table index that this read's columns bind against.
542 index: u32,
543 /// Which materialisation it reads.
544 cte: u32,
545 /// The name it was written with.
546 name: StrRef,
547 /// The produced columns with their types, into the field pool.
548 columns: Slice,
549 },
550 /// A MIN or MAX over an acyclic chain of inner equi-joins, answered without running the join.
551 ///
552 /// What it replaces is an ungrouped aggregate whose every call is a MIN or a MAX of one
553 /// relation's column. Neither answer changes when a row is repeated, so the extreme over the
554 /// joined rows is the extreme over the rows of that one relation that take part in at least one
555 /// joined row, and on an acyclic join those rows are found with two sweeps of semijoins over a
556 /// join tree and no join at all. That is Yannakakis's full reducer, and the relations, the
557 /// classes of columns the equalities made equal and the tree are all in the
558 /// [`Reducer`](crate::Reducer) this points at.
559 ///
560 /// A leaf here, and the relations it reads are not its children. A node has two input slots and
561 /// a join of seventeen relations has seventeen inputs, and the passes that run after this one
562 /// have nothing to do inside it anyway: each relation is a scan with its own filter, already as
563 /// narrow as it is going to get. The two walks that do have to reach them, the pipeline shape
564 /// and the printer, read the reducer.
565 Consistent {
566 /// The table index the produced columns bind against, which is the index of the aggregate
567 /// this replaced, so nothing above it had to be rebound.
568 index: u32,
569 /// The produced columns with their types, into the field pool, one per extreme.
570 columns: Slice,
571 /// Which reducer in the plan's pool describes the relations and the tree.
572 reducer: u32,
573 },
574 /// `UNION`, `EXCEPT` or `INTERSECT`.
575 SetOp {
576 /// The left input.
577 left: NodeRef,
578 /// The right input.
579 right: NodeRef,
580 /// Which operation.
581 kind: SetOpKind,
582 /// Whether duplicates are kept.
583 all: bool,
584 /// The table index the produced columns bind against, since the output is neither side's
585 /// columns.
586 index: u32,
587 },
588}
589
590impl Node {
591 /// The keyword this operator prints as, which is also what the reader dispatches on.
592 #[must_use]
593 pub fn keyword(&self) -> &'static str {
594 match self {
595 Self::Get { .. } => "Get",
596 Self::Dummy => "Dummy",
597 Self::Values { .. } => "Values",
598 Self::TableFunction { .. } => "TableFunction",
599 Self::LateralFunction { .. } => "LateralFunction",
600 Self::Filter { .. } => "Filter",
601 Self::Project { .. } => "Project",
602 Self::Aggregate { .. } => "Aggregate",
603 Self::Window { .. } => "Window",
604 Self::Sort { .. } => "Sort",
605 Self::Limit { .. } => "Limit",
606 Self::LimitPercent { .. } => "LimitPercent",
607 Self::TopN { .. } => "TopN",
608 Self::Fetch { .. } => "Fetch",
609 Self::TableFetch { .. } => "TableFetch",
610 Self::Distinct { .. } => "Distinct",
611 Self::Join { .. } => "Join",
612 Self::LinkJoin { .. } => "LinkJoin",
613 Self::DependentJoin { .. } => "DependentJoin",
614 Self::CrossProduct { .. } => "CrossProduct",
615 Self::MaterializedCte { .. } => "MaterializedCte",
616 Self::CteScan { .. } => "CteScan",
617 Self::Consistent { .. } => "Consistent",
618 Self::SetOp { .. } => "SetOp",
619 }
620 }
621
622 /// The inputs, in printing order.
623 ///
624 /// Two slots rather than a `Vec`, because no logical operator in this set has three inputs and
625 /// the printer walks this on every node of every dump. A caller wants
626 /// `node.children().into_iter().flatten()`.
627 #[must_use]
628 pub fn children(&self) -> [Option<NodeRef>; 2] {
629 match *self {
630 Self::Get { .. }
631 | Self::Dummy
632 | Self::Values { .. }
633 | Self::TableFunction { .. }
634 | Self::CteScan { .. }
635 | Self::Consistent { .. } => [None, None],
636 Self::Filter { input, .. }
637 | Self::Project { input, .. }
638 | Self::Aggregate { input, .. }
639 | Self::Window { input, .. }
640 | Self::Sort { input, .. }
641 | Self::Limit { input, .. }
642 | Self::LimitPercent { input, .. }
643 | Self::TopN { input, .. }
644 | Self::Fetch { input, .. }
645 | Self::TableFetch { input, .. }
646 | Self::Distinct { input, .. }
647 | Self::LateralFunction { input, .. } => [Some(input), None],
648 Self::LinkJoin { child: left, parent: right, .. }
649 | Self::Join { left, right, .. }
650 | Self::DependentJoin { left, right, .. }
651 | Self::CrossProduct { left, right }
652 | Self::SetOp { left, right, .. } => [Some(left), Some(right)],
653 Self::MaterializedCte { definition, body, .. } => [Some(definition), Some(body)],
654 }
655 }
656
657 /// How many inputs this operator takes.
658 #[must_use]
659 pub fn arity(&self) -> usize {
660 self.children().into_iter().flatten().count()
661 }
662
663 /// The table index this operator introduces, if it introduces one.
664 #[must_use]
665 pub fn table_index(&self) -> Option<u32> {
666 match *self {
667 Self::Get { index, .. }
668 | Self::Values { index, .. }
669 | Self::TableFunction { index, .. }
670 | Self::LateralFunction { index, .. }
671 | Self::Project { index, .. }
672 | Self::Fetch { index, .. }
673 | Self::TableFetch { index, .. }
674 | Self::Aggregate { index, .. }
675 | Self::Window { index, .. }
676 | Self::CteScan { index, .. }
677 | Self::Consistent { index, .. }
678 | Self::SetOp { index, .. } => Some(index),
679 _ => None,
680 }
681 }
682}
683
684/// Which join.
685///
686/// `Semi` and `Anti` are here because subquery unnesting produces them directly, per section 9.2,
687/// and a semi join expressed as a join plus a distinct is a semi join the executor cannot
688/// recognise.
689#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
690pub enum JoinKind {
691 /// Rows that match on both sides.
692 Inner,
693 /// Every left row, padded with nulls where the right does not match.
694 Left,
695 /// Every right row, padded with nulls where the left does not match.
696 Right,
697 /// Both of the above at once.
698 Full,
699 /// Left rows that have at least one match, each emitted once.
700 Semi,
701 /// Left rows that have no match.
702 Anti,
703 /// Left rows paired with their match, or with nulls, at most one right row each. What a
704 /// correlated scalar subquery unnests to.
705 Single,
706 /// Every left row plus a nullable boolean saying whether its condition matched the right side.
707 /// A null means no row matched and at least one comparison was unknown.
708 Mark,
709 /// The nth left row with the nth right row, which is DuckDB's `POSITIONAL JOIN`.
710 Positional,
711}
712
713impl JoinKind {
714 /// The spelling used in the textual form.
715 #[must_use]
716 pub fn keyword(self) -> &'static str {
717 match self {
718 Self::Inner => "INNER",
719 Self::Left => "LEFT",
720 Self::Right => "RIGHT",
721 Self::Full => "FULL",
722 Self::Semi => "SEMI",
723 Self::Anti => "ANTI",
724 Self::Single => "SINGLE",
725 Self::Mark => "MARK",
726 Self::Positional => "POSITIONAL",
727 }
728 }
729
730 /// Every join kind, which is what the reader searches.
731 pub(crate) const ALL: [Self; 9] = [
732 Self::Inner,
733 Self::Left,
734 Self::Right,
735 Self::Full,
736 Self::Semi,
737 Self::Anti,
738 Self::Single,
739 Self::Mark,
740 Self::Positional,
741 ];
742
743 /// The same join with its two inputs the other way round, for the kinds where there is one.
744 ///
745 /// Swapping the inputs of a `LEFT` join makes a `RIGHT` join and the other way round, because
746 /// the kind names a side. `INNER` and `FULL` name neither and are their own mirror. The rest
747 /// return `None`: `SEMI`, `ANTI`, `SINGLE` and `MARK` produce the left side's rows, or a
748 /// column about them, so their left input is not a side but the subject, and `POSITIONAL`
749 /// pairs the nth with the nth, which no reordering of one input preserves.
750 #[must_use]
751 pub fn mirrored(self) -> Option<Self> {
752 match self {
753 Self::Inner => Some(Self::Inner),
754 Self::Left => Some(Self::Right),
755 Self::Right => Some(Self::Left),
756 Self::Full => Some(Self::Full),
757 Self::Semi | Self::Anti | Self::Single | Self::Mark | Self::Positional => None,
758 }
759 }
760}
761
762/// Which input of a join is gathered whole before the other one starts.
763///
764/// A join is two inputs and a dependency edge between them: one side is finished and held, and then
765/// the other side's rows are matched against what was held. This says which side that is. It is
766/// where the hash table goes when the hash join in #62 lands, and it is the side today's nested
767/// loop turns into chunks and rescans once per row of the other one.
768///
769/// Which side that should be is not a property of the join and is not decided here. It is decided
770/// by [`sides`](../../rudb_opt/sides/index.html) from a cardinality estimate, and the rule it uses
771/// belongs to whichever operator is reading this, not to the flag.
772#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
773pub enum BuildSide {
774 /// The right input, which is what the binder emits and what every join did before this existed.
775 #[default]
776 Right,
777 /// The left input, which means the executor swaps the two and puts the answer back in order.
778 Left,
779}
780
781impl BuildSide {
782 /// The spelling used in the textual form.
783 #[must_use]
784 pub fn keyword(self) -> &'static str {
785 match self {
786 Self::Right => "right",
787 Self::Left => "left",
788 }
789 }
790
791 /// Both sides, which is what the reader searches.
792 pub(crate) const ALL: [Self; 2] = [Self::Right, Self::Left];
793}
794
795/// Which set operation.
796#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
797pub enum SetOpKind {
798 /// Rows from either side.
799 Union,
800 /// Rows from the left that are not on the right.
801 Except,
802 /// Rows on both sides.
803 Intersect,
804}
805
806impl SetOpKind {
807 /// The spelling used in the textual form.
808 #[must_use]
809 pub fn keyword(self) -> &'static str {
810 match self {
811 Self::Union => "UNION",
812 Self::Except => "EXCEPT",
813 Self::Intersect => "INTERSECT",
814 }
815 }
816
817 /// Every set operation, which is what the reader searches.
818 pub(crate) const ALL: [Self; 3] = [Self::Union, Self::Except, Self::Intersect];
819}
820
821#[cfg(test)]
822mod tests {
823 use super::*;
824 use crate::Slice;
825
826 /// Every node in one list, so that a variant added without a keyword, without a child slot or
827 /// without an entry in the reader's dispatch table fails here rather than at the first dump
828 /// that happens to contain one.
829 fn one_of_each() -> Vec<Node> {
830 vec![
831 Node::Get {
832 catalog: 0,
833 schema: 0,
834 table: 0,
835 alias: 0,
836 index: 0,
837 columns: Slice::EMPTY,
838 },
839 Node::Dummy,
840 Node::Values { index: 0, columns: Slice::EMPTY, rows: Slice::EMPTY },
841 Node::TableFunction {
842 index: 0,
843 function: 0,
844 args: Slice::EMPTY,
845 options: Slice::EMPTY,
846 settings: Slice::EMPTY,
847 columns: Slice::EMPTY,
848 },
849 Node::LateralFunction {
850 input: 0,
851 index: 0,
852 function: 0,
853 args: Slice::EMPTY,
854 options: Slice::EMPTY,
855 settings: Slice::EMPTY,
856 columns: Slice::EMPTY,
857 },
858 Node::Filter { input: 0, predicate: 0 },
859 Node::Project { input: 0, index: 0, exprs: Slice::EMPTY, names: Slice::EMPTY },
860 Node::Aggregate { input: 0, index: 0, groups: Slice::EMPTY, aggregates: Slice::EMPTY },
861 Node::Sort { input: 0, keys: Slice::EMPTY },
862 Node::Limit { input: 0, count: Bound::All, offset: Bound::Rows(0) },
863 Node::LimitPercent { input: 0, percent: Share::Percent(50.0), offset: Bound::Rows(0) },
864 Node::Distinct { input: 0, on: Slice::EMPTY },
865 Node::Join {
866 left: 0,
867 right: 1,
868 kind: JoinKind::Inner,
869 conditions: Slice::EMPTY,
870 build: BuildSide::default(),
871 },
872 Node::DependentJoin {
873 left: 0,
874 right: 1,
875 kind: JoinKind::Single,
876 conditions: Slice::EMPTY,
877 },
878 Node::CrossProduct { left: 0, right: 1 },
879 Node::SetOp { left: 0, right: 1, kind: SetOpKind::Union, all: true, index: 0 },
880 Node::Consistent { index: 0, columns: Slice::EMPTY, reducer: 0 },
881 ]
882 }
883
884 #[test]
885 fn every_operator_has_its_own_keyword() {
886 let mut keywords: Vec<&str> = one_of_each().iter().map(Node::keyword).collect();
887 let count = keywords.len();
888 keywords.sort_unstable();
889 keywords.dedup();
890 assert_eq!(keywords.len(), count, "two operators print the same keyword");
891 }
892
893 #[test]
894 fn arity_agrees_with_the_child_slots() {
895 for node in one_of_each() {
896 let counted = node.children().into_iter().flatten().count();
897 assert_eq!(node.arity(), counted, "{} disagrees with itself", node.keyword());
898 }
899 }
900
901 /// A child slot that is `None` before a slot that is `Some` would make the printer emit the
902 /// right input as the left one, and the reader would accept it.
903 #[test]
904 fn the_child_slots_are_filled_from_the_front() {
905 for node in one_of_each() {
906 let slots = node.children();
907 assert!(
908 !(slots[0].is_none() && slots[1].is_some()),
909 "{} has a right input and no left one",
910 node.keyword()
911 );
912 }
913 }
914
915 #[test]
916 fn only_the_operators_that_introduce_columns_have_a_table_index() {
917 for node in one_of_each() {
918 let expected = matches!(
919 node,
920 Node::Get { .. }
921 | Node::Values { .. }
922 | Node::TableFunction { .. }
923 | Node::LateralFunction { .. }
924 | Node::Project { .. }
925 | Node::Aggregate { .. }
926 | Node::SetOp { .. }
927 | Node::Consistent { .. }
928 );
929 assert_eq!(
930 node.table_index().is_some(),
931 expected,
932 "{} is on the wrong side of the table index rule",
933 node.keyword()
934 );
935 }
936 }
937
938 #[test]
939 fn every_join_kind_and_set_operation_is_in_the_list_the_reader_searches() {
940 assert_eq!(JoinKind::ALL.len(), 9);
941 assert_eq!(SetOpKind::ALL.len(), 3);
942 let mut names: Vec<&str> = JoinKind::ALL.iter().map(|k| k.keyword()).collect();
943 names.sort_unstable();
944 names.dedup();
945 assert_eq!(names.len(), JoinKind::ALL.len(), "two join kinds print the same keyword");
946 }
947}