From f629387203a3f2864b2bdfc1978431cd6b7656c2 Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 16 Jul 2024 22:22:48 +0200 Subject: [PATCH] sql: retain original columns in `Nothing` nodes --- src/sql/execution/execute.rs | 2 +- src/sql/planner/optimizer.rs | 46 ++++++++++++--------- src/sql/planner/plan.rs | 20 +++++---- src/sql/testscripts/optimizer/short_circuit | 31 +++++++++----- src/sql/testscripts/queries/select | 6 +++ 5 files changed, 66 insertions(+), 39 deletions(-) diff --git a/src/sql/execution/execute.rs b/src/sql/execution/execute.rs index d1a428e27..493ea29e0 100644 --- a/src/sql/execution/execute.rs +++ b/src/sql/execution/execute.rs @@ -96,7 +96,7 @@ pub fn execute(node: Node, txn: &impl Transaction) -> Result { join::nested_loop(left, right, right_size, predicate, outer) } - Node::Nothing => Ok(source::nothing()), + Node::Nothing { .. } => Ok(source::nothing()), Node::Offset { source, offset } => { let source = execute(*source, txn)?; diff --git a/src/sql/planner/optimizer.rs b/src/sql/planner/optimizer.rs index aa9c93c96..6b6f37cdb 100644 --- a/src/sql/planner/optimizer.rs +++ b/src/sql/planner/optimizer.rs @@ -287,6 +287,12 @@ pub(super) fn short_circuit(node: Node) -> Result { use Expression::Constant; use Value::{Boolean, Null}; + /// Creates a Nothing node with the columns of the original node. + fn nothing(node: &Node) -> Node { + let columns = (0..node.size()).map(|i| node.column_label(i)).collect(); + Node::Nothing { columns } + } + let transform = |node| match node { // Filter nodes that always yield true are unnecessary: remove them. Node::Filter { source, predicate: Constant(Boolean(true)) } => *source, @@ -300,32 +306,34 @@ pub(super) fn short_circuit(node: Node) -> Result { } // Short-circuit nodes that can't produce anything by replacing them - // with a Nothing node. - Node::Filter { predicate: Constant(Boolean(false) | Null), .. } => Node::Nothing, - Node::IndexLookup { values, .. } if values.is_empty() => Node::Nothing, - Node::KeyLookup { keys, .. } if keys.is_empty() => Node::Nothing, - Node::Limit { limit: 0, .. } => Node::Nothing, - Node::NestedLoopJoin { predicate: Some(Constant(Boolean(false) | Null)), .. } => { - Node::Nothing + // with a Nothing node, retaining the columns. + ref node @ Node::Filter { predicate: Constant(Boolean(false) | Null), .. } => nothing(node), + ref node @ Node::IndexLookup { ref values, .. } if values.is_empty() => nothing(node), + ref node @ Node::KeyLookup { ref keys, .. } if keys.is_empty() => nothing(node), + ref node @ Node::Limit { limit: 0, .. } => nothing(node), + ref node @ Node::NestedLoopJoin { + predicate: Some(Constant(Boolean(false) | Null)), .. + } => nothing(node), + ref node @ Node::Scan { filter: Some(Constant(Boolean(false) | Null)), .. } => { + nothing(node) } - Node::Scan { filter: Some(Constant(Boolean(false) | Null)), .. } => Node::Nothing, - Node::Values { rows, .. } if rows.is_empty() => Node::Nothing, + Node::Values { rows } if rows.is_empty() => Node::Nothing { columns: vec![] }, // Short-circuit nodes that pull from a Nothing node. // // NB: does not short-circuit aggregation, since an aggregation over 0 // rows should produce a result. - Node::Filter { source, .. } - | Node::HashJoin { left: source, .. } - | Node::HashJoin { right: source, .. } - | Node::NestedLoopJoin { left: source, .. } - | Node::NestedLoopJoin { right: source, .. } - | Node::Offset { source, .. } - | Node::Order { source, .. } - | Node::Projection { source, .. } - if *source == Node::Nothing => + ref node @ (Node::Filter { ref source, .. } + | Node::HashJoin { left: ref source, .. } + | Node::HashJoin { right: ref source, .. } + | Node::NestedLoopJoin { left: ref source, .. } + | Node::NestedLoopJoin { right: ref source, .. } + | Node::Offset { ref source, .. } + | Node::Order { ref source, .. } + | Node::Projection { ref source, .. }) + if matches!(**source, Node::Nothing { .. }) => { - Node::Nothing + nothing(node) } // Remove noop projections that simply pass through the source columns. diff --git a/src/sql/planner/plan.rs b/src/sql/planner/plan.rs index 7dbce779c..9849994ba 100644 --- a/src/sql/planner/plan.rs +++ b/src/sql/planner/plan.rs @@ -109,8 +109,9 @@ pub enum Node { /// When outer is true (e.g. LEFT JOIN), a left row without a right match is /// emitted anyway, with NULLs for the right row. NestedLoopJoin { left: Box, right: Box, predicate: Option, outer: bool }, - /// Nothing does not emit anything. - Nothing, + /// Nothing does not emit anything, but retains the column names of any + /// replaced nodes for results and plan expression display. + Nothing { columns: Vec