Skip to main content

Node

Enum Node 

Source
pub enum Node {
Show 22 variants Get { catalog: StrRef, schema: StrRef, table: StrRef, alias: StrRef, index: u32, columns: Slice, }, Dummy, Values { index: u32, columns: Slice, rows: Slice, }, TableFunction { index: u32, function: StrRef, args: Slice, options: Slice, settings: Slice, columns: Slice, }, LateralFunction { input: NodeRef, index: u32, function: StrRef, args: Slice, options: Slice, settings: Slice, columns: Slice, }, Filter { input: NodeRef, predicate: ExprRef, }, Project { input: NodeRef, index: u32, exprs: Slice, names: Slice, }, Aggregate { input: NodeRef, index: u32, groups: Slice, aggregates: Slice, }, Window { input: NodeRef, index: u32, partition: Slice, order: Slice, frame: WindowFrame, expressions: Slice, }, Sort { input: NodeRef, keys: Slice, }, Limit { input: NodeRef, count: Option<u64>, offset: u64, }, LimitPercent { input: NodeRef, percent: f64, offset: u64, }, TopN { input: NodeRef, keys: Slice, count: u64, offset: u64, }, Fetch { input: NodeRef, index: u32, args: Slice, columns: Slice, row: ExprRef, }, TableFetch { input: NodeRef, index: u32, catalog: StrRef, schema: StrRef, table: StrRef, columns: Slice, row: ExprRef, }, Distinct { input: NodeRef, on: Slice, }, Join { left: NodeRef, right: NodeRef, kind: JoinKind, conditions: Slice, build: BuildSide, }, DependentJoin { left: NodeRef, right: NodeRef, kind: JoinKind, conditions: Slice, }, CrossProduct { left: NodeRef, right: NodeRef, }, MaterializedCte { definition: NodeRef, body: NodeRef, name: StrRef, cte: u32, columns: Slice, }, CteScan { index: u32, cte: u32, name: StrRef, columns: Slice, }, SetOp { left: NodeRef, right: NodeRef, kind: SetOpKind, all: bool, index: u32, },
}
Expand description

One logical operator.

Children are the inputs, in the order Node::children returns them, which is the order they print in and the order the reader expects.

PartialEq and not Eq, because Node::LimitPercent holds a percentage as a f64. Value is the same shape for the same reason.

Variants§

§

Get

A base table scan.

The projection is in columns, so a scan of two columns of a 105-column table is a two column scan in the plan and not a filter over a wide one. spec/09-optimizer.md section 9.2 calls projection pushdown the difference between 20 GB and 200 MB on ClickBench, and this is the field it pushes into.

Fields

§catalog: StrRef

The catalog name.

§schema: StrRef

The schema name.

§table: StrRef

The table name.

§alias: StrRef

The alias the query used, which is what an error message should say.

§index: u32

The table index that this scan’s columns bind against.

§columns: Slice

The projected columns with their types, into the field pool.

§

Dummy

One row and no columns.

What SELECT 1 sits on top of. Not an empty result: an empty result produces no rows and SELECT 1 produces one, and conflating them is how a scalar subquery starts returning nothing instead of null.

§

Values

Literal rows.

Every row has the same length as columns, which Plan::validate checks, because a ragged VALUES is a wrong answer rather than a crash.

Fields

§index: u32

The table index that these columns bind against.

§columns: Slice

The output columns with their types, into the field pool.

§rows: Slice

The rows, into the row pool, each row a slice of the expression list pool.

§

TableFunction

A function call where a table goes, such as range(10).

The arguments are expressions rather than numbers, because range(2 + 3) is a legal call and folding it here would mean the plan could not be printed back as what was written. They cannot refer to a column: a table function that sees the row on its left is LATERAL, and that is Node::LateralFunction.

A separate node from Node::Values even though range(3) and VALUES (0), (1), (2) produce the same rows, because the one that produces three million rows should be three numbers in the plan rather than three million expressions in it.

Fields

§index: u32

The table index that this call’s columns bind against.

§function: StrRef

Which function, as its own canonical name.

§args: Slice

The arguments, into the expression list pool.

§options: Slice

The names of the named parameters the call was written with, into the name pool.

read_csv('f.csv', delim=';') keeps the delim here rather than only in whatever the binder made of it, because the executor opens the file a second time and has to open it the same way. A parameter the binder answers on its own, such as binary_as_string, is here too, so that a plan prints back as the call that was written.

§settings: Slice

What each of those names was given, into the expression list pool and the same length.

Constants, every one of them. The binder refuses anything else, because a parameter can decide what the columns are and the columns are settled there.

§columns: Slice

The produced columns with their types, into the field pool.

§

LateralFunction

A table function evaluated once per row of its input, which is what LATERAL means.

FROM t, range(t.n) is this. A table function’s arguments are what produce its rows rather than something read over rows that already exist, so there is nothing underneath one for a domain to be pushed into and nothing the rules in the unnesting pass can rewrite it into. This is the operator those rules stop at: the domain goes in on the left, the arguments read it, and the call is made once per row of it.

The output is the input’s columns followed by the function’s, which is a cross product whose right side is allowed to change per left row. That is what lets the join putting the rows back beside their outer row sit above this and read the domain columns where it reads them everywhere else.

Only the series family reaches here. A reader takes a file name, the binder settles the columns by opening the file, and a name that is not a constant is refused there, so a correlated read_csv never gets this far.

Fields

§input: NodeRef

The rows the call is made against, one call per row.

§index: u32

The table index that this call’s columns bind against.

§function: StrRef

Which function, as its own canonical name.

§args: Slice

The arguments, into the expression list pool, read against a row of input.

§options: Slice

The names of the named parameters the call was written with, into the name pool.

§settings: Slice

What each of those names was given, into the expression list pool and the same length.

§columns: Slice

The produced columns with their types, into the field pool, not counting the input’s.

§

Filter

A predicate over the input, keeping the rows where it is true.

True, not “not false”. A null predicate drops the row, which is SQL’s rule and is the difference between WHERE and CHECK.

Fields

§input: NodeRef

The input.

§predicate: ExprRef

The predicate, which has to be BOOLEAN.

§

Project

A projection, producing a new set of columns from the input’s.

Fields

§input: NodeRef

The input.

§index: u32

The table index the produced columns bind against.

§exprs: Slice

The expressions, into the expression list pool.

§names: Slice

One output name per expression, into the name list pool.

Names are carried through the whole plan rather than attached at the root, because the thing a person reads a plan dump to answer is usually which column this is, and a dump with the names stripped out answers that with a number.

§

Aggregate

A grouped or ungrouped aggregation.

The output is the group expressions followed by the aggregates, in that order, and that is what a binding into index means. An ungrouped aggregate has an empty groups and still produces exactly one row, including over an empty input.

Fields

§input: NodeRef

The input.

§index: u32

The table index the produced columns bind against.

§groups: Slice

The group expressions, into the expression list pool.

§aggregates: Slice

The aggregate expressions, into the expression list pool. Every element is an Expr::Aggregate and this is the only place one may appear.

§

Window

Window expressions that share one partition, ordering, and frame.

Fields

§input: NodeRef

Rows over which the windows are evaluated.

§index: u32

The table index of the appended window result columns.

§partition: Slice

Expressions that divide the input into independent partitions.

§order: Slice

The ordering within each partition.

§frame: WindowFrame

The complete frame shared by this compatible expression run.

§expressions: Slice

Direct Expr::Window expressions appended to the input columns.

§

Sort

An ordering.

Fields

§input: NodeRef

The input.

§keys: Slice

The keys in priority order, into the sort key pool.

§

Limit

A row count limit and an offset.

Both are constants. LIMIT over an expression is legal SQL and DuckDB evaluates it before the plan runs, so by the time it is here it is a number or the query did not bind.

Fields

§input: NodeRef

The input.

§count: Option<u64>

How many rows to emit, or all of them.

§offset: u64

How many rows to skip first.

§

LimitPercent

A limit written as a share of the input rather than as a row count.

LIMIT 30 PERCENT over ten rows is three rows, and it is a node of its own rather than a Node::Limit with another field for three reasons. The share is of the whole input, so this cannot emit anything until it has counted every row, where a plain limit hands each chunk on as it arrives and stops the scan early. The rewrites that fire on a plain limit are wrong here: a filter pushed under this one changes how many rows there are to take a share of, and the sort underneath it cannot become a top n because the count is not known until the sort has finished. And the pin builds a separate Limit Percent operator for it, which is the same split one layer down.

The percentage is between nought and a hundred inclusive, checked while the query is bound, because that is where the pin refuses LIMIT 101 PERCENT too. The offset is applied after the share has been worked out, so LIMIT 30 PERCENT OFFSET 2 over ten rows is three rows starting at the third.

Fields

§input: NodeRef

The input.

§percent: f64

The share of the input to emit, from nought to a hundred.

§offset: u64

How many rows to skip first.

§

TopN

A sort with a limit over it, which never holds more rows than the limit can emit.

The same answer as a Node::Limit over a Node::Sort and a different amount of work. A sort has to see every row before it can emit the first one, so it holds the whole input; this holds the rows that could still come out and throws the rest away as it goes, which on ORDER BY x LIMIT 10 over a hundred million rows is ten rows rather than a hundred million.

count is not optional, because LIMIT ALL over a sort is a sort and there would be nothing to bound. The offset is part of the node rather than left above it, since the rows that are skipped still have to be found to be skipped, so what this has to keep is count + offset.

Fields

§input: NodeRef

The input.

§keys: Slice

The keys in priority order, into the sort key pool.

§count: u64

How many rows to emit.

§offset: u64

How many rows to skip first.

§

Fetch

The columns of rows something below already picked out, read back from the file by ordinal.

This is the top half of late materialisation. A SELECT * FROM hits ORDER BY EventTime LIMIT 10 over a hundred and five columns needs one column to decide which ten rows win and all hundred and five of those ten rows afterwards, and a plan that carries the wide rows through the top N reads the whole file to throw almost all of it away. The rewrite in rudb-opt’s late module narrows the scan under the top N to the ordering columns plus the row’s ordinal inside its file, and puts this above it to read the rest for the rows that survived.

The ordinals come out of the input rather than being counted here, because the operator that counted them is the scan and everything between the scan and here may have dropped rows. The column that holds them is Self::Fetch::row, and the scan produced it because the rewrite turned file_row_number on.

The produced columns are the whole row and not only the deferred part, so the answer is one read of the file at the ordinals rather than a stitch of what was carried with what was fetched. That costs the ordering column a second read of a few pages and saves the plan above this from having any idea the rewrite happened.

Fields

§input: NodeRef

The input, which carries each row’s ordinal inside the file.

§index: u32

The table index the produced columns bind against, which is the one the node this replaced produced, so that nothing above has to be rebound.

§args: Slice

The file, into the expression list pool. One constant path, because a row ordinal only says which row when there is one file it could be in.

§columns: Slice

The produced columns with their types, into the field pool.

§row: ExprRef

The input column holding the ordinal, which has to be BIGINT.

§

TableFetch

Rows of a catalog table read back by their table-wide ordinal.

Fields

§input: NodeRef
§index: u32
§catalog: StrRef
§schema: StrRef
§table: StrRef
§columns: Slice
§

Distinct

Duplicate elimination, over the whole row or over named expressions.

Fields

§input: NodeRef

The input.

§on: Slice

The DISTINCT ON expressions, into the expression list pool. Empty means the whole row, which is plain DISTINCT.

§

Join

A join with a condition.

Fields

§left: NodeRef

The left input.

§right: NodeRef

The right input.

§kind: JoinKind

Which join.

§conditions: Slice

The conditions, into the expression list pool, combined with AND. Empty is a join with no condition, which for an inner join is a cross product and for an outer join is not.

§build: BuildSide

Which input is gathered whole before the other one starts.

The binder emits BuildSide::Right for everything, because at binding time there is nothing to choose with. rudb_opt’s sides pass overwrites it from an estimate, and the executor honours whatever it finds here.

§

DependentJoin

A join whose right input can refer to columns produced by its left input.

Binding emits this for a correlated subquery. The unnesting pass has to replace every one before execution, so the executor never evaluates the right input once per left row.

Fields

§left: NodeRef

The outer input whose columns the right side may reference.

§right: NodeRef

The correlated input.

§kind: JoinKind

Which result shape the subquery needs.

§conditions: Slice

Conditions introduced while binding the subquery.

§

CrossProduct

An unconditional cross product.

Separate from a Node::Join with no conditions because join ordering treats them differently: a cross product has no edge in the join graph and section 9.4’s dynamic program enumerates connected subgraphs.

Fields

§left: NodeRef

The left input.

§right: NodeRef

The right input.

§

MaterializedCte

A WITH name AS MATERIALIZED (...), which is run once and read wherever it is named.

The left input is the definition and the right input is the query that reads it. They are in that order because that is the order they run in: the definition is a pipeline breaker whichever operators are in it, since nothing above may start until the rows are all there.

A plain WITH is not this. The reference binary inlines one at every use whatever its shape and however many times it is named, and the only decision left is whether the rows are needed at all, which is why an unused one is dropped rather than run for nothing.

Fields

§definition: NodeRef

The query whose rows are held.

§body: NodeRef

The query that reads them, which is where every Node::CteScan for this one is.

§name: StrRef

The name it was written with, which is what the printer and an error message say.

§cte: u32

Which materialisation this is, matching the cte of the scans that read it.

A number of its own rather than the table index, because a scan binds against its own index and two scans of one materialisation have two of those.

§columns: Slice

The held columns with their types, into the field pool.

§

CteScan

A read of a Node::MaterializedCte that has already run.

A leaf, the same way a table scan is. What it reads was computed by a node above it rather than by a node under it, which is the one place in the plan where that is true, and it is why the materialisation holds its body as an input rather than sitting beside it.

Fields

§index: u32

The table index that this read’s columns bind against.

§cte: u32

Which materialisation it reads.

§name: StrRef

The name it was written with.

§columns: Slice

The produced columns with their types, into the field pool.

§

SetOp

UNION, EXCEPT or INTERSECT.

Fields

§left: NodeRef

The left input.

§right: NodeRef

The right input.

§kind: SetOpKind

Which operation.

§all: bool

Whether duplicates are kept.

§index: u32

The table index the produced columns bind against, since the output is neither side’s columns.

Implementations§

Source§

impl Node

Source

pub fn keyword(&self) -> &'static str

The keyword this operator prints as, which is also what the reader dispatches on.

Source

pub fn children(&self) -> [Option<NodeRef>; 2]

The inputs, in printing order.

Two slots rather than a Vec, because no logical operator in this set has three inputs and the printer walks this on every node of every dump. A caller wants node.children().into_iter().flatten().

Source

pub fn arity(&self) -> usize

How many inputs this operator takes.

Source

pub fn table_index(&self) -> Option<u32>

The table index this operator introduces, if it introduces one.

Trait Implementations§

Source§

impl Clone for Node

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Node

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for Node

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Node

Auto Trait Implementations§

§

impl Freeze for Node

§

impl RefUnwindSafe for Node

§

impl Send for Node

§

impl Sync for Node

§

impl Unpin for Node

§

impl UnsafeUnpin for Node

§

impl UnwindSafe for Node

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.