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§
- Empty
Result Pullup - Replaces a subtree that cannot produce a row with an empty relation.
Functions§
- prune
- Replaces every highest empty subtree in
planwith an empty relation.