Skip to main content

Module empty

Module empty 

Source
Expand description

Replacing a subtree that cannot produce a row with a relation that produces none.

WHERE false is the query somebody writes to ask for nothing, and a plan that still has a scan under it answers nothing after reading the whole table. On ClickBench that is a hundred million rows off disk to produce an empty result, so this is a speed matter, but it is a correctness one first: spec/09-optimizer.md asks that a query with an unsatisfiable predicate not touch the storage it was written against, and a scan that runs is a scan that can report an error about a file the query was never going to read from.

§What counts as empty

A filter whose predicate is a false or a null constant, since WHERE keeps the rows where the predicate is true and neither of those ever is. A limit of zero rows, and the top N it fuses into. A VALUES with no rows. Anything above one of those that passes its input through, which is a filter, a sort, a limit, a top N, a DISTINCT and a projection.

The pullup stops at a group by, which is the operator this pass exists to be careful about. An ungrouped aggregate over no rows produces one row and not none, so SELECT count(*) FROM t WHERE false is 0 rather than an empty answer, and a pass that treated the aggregate as empty because its input was would return the wrong number of rows. The empty relation is put under the aggregate and the aggregate stays.

It also stops at a join, a cross product and a set operation, for a reason that is about spelling rather than about semantics. An empty relation here is a Node::Values, which binds its columns to one table index, and those three produce columns bound to two of them or to an index of their own that is not either side’s. Replacing one would mean rewriting every binding above it, so an empty side of a join is left as an empty side of a join, which the executor already handles by finding no rows to pair with.

§Why it is a pullup rather than a pushdown

The walk is from the root, and the highest node that cannot produce a row is the one replaced, so everything beneath it goes away in one step rather than a level per run. That is also what makes the pass settle: a plan it has run over has an empty VALUES where the empty subtree was, and an empty VALUES is the answer this pass would give for it again.

§Where the always true predicate went

The other half of constant pruning, WHERE true, is in crate::filter. A conjunct that is a true constant is dropped as the pass puts the filter back together, and a filter with nothing left in it is not rebuilt, which is where the binary does it too.

Structs§

EmptyResultPullup
Replaces a subtree that cannot produce a row with an empty relation.

Functions§

prune
Replaces every highest empty subtree in plan with an empty relation.