This is an automated email from the ASF dual-hosted git repository.
alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git
The following commit(s) were added to refs/heads/main by this push:
new b6281b54bb Improve volatile expression handling in
`CommonSubexprEliminate` (#11265)
b6281b54bb is described below
commit b6281b54bb16cc88446d73248c58244b693354b7
Author: Peter Toth <[email protected]>
AuthorDate: Mon Jul 8 18:42:52 2024 +0200
Improve volatile expression handling in `CommonSubexprEliminate` (#11265)
* Improve volatile expression handling in `CommonSubexprEliminate` rule
* fix volatile handling with short circuits
* fix comments
* add slt tests for CSE
* Avoid adding datafusion function dependency
* revert changes to datafusion-cli.lock
---------
Co-authored-by: Andrew Lamb <[email protected]>
---
datafusion/expr/src/expr.rs | 13 +-
.../optimizer/src/common_subexpr_eliminate.rs | 196 +++++++++++++++++----
datafusion/sqllogictest/test_files/cse.slt | 173 ++++++++++++++++++
3 files changed, 341 insertions(+), 41 deletions(-)
diff --git a/datafusion/expr/src/expr.rs b/datafusion/expr/src/expr.rs
index 579f5fed57..ecece6dbfc 100644
--- a/datafusion/expr/src/expr.rs
+++ b/datafusion/expr/src/expr.rs
@@ -1413,12 +1413,19 @@ impl Expr {
.unwrap()
}
+ /// Returns true if the expression node is volatile, i.e. whether it can
return
+ /// different results when evaluated multiple times with the same input.
+ /// Note: unlike [`Self::is_volatile`], this function does not consider
inputs:
+ /// - `rand()` returns `true`,
+ /// - `a + rand()` returns `false`
+ pub fn is_volatile_node(&self) -> bool {
+ matches!(self, Expr::ScalarFunction(func) if
func.func.signature().volatility == Volatility::Volatile)
+ }
+
/// Returns true if the expression is volatile, i.e. whether it can return
different
/// results when evaluated multiple times with the same input.
pub fn is_volatile(&self) -> Result<bool> {
- self.exists(|expr| {
- Ok(matches!(expr, Expr::ScalarFunction(func) if
func.func.signature().volatility == Volatility::Volatile ))
- })
+ self.exists(|expr| Ok(expr.is_volatile_node()))
}
/// Recursively find all [`Expr::Placeholder`] expressions, and
diff --git a/datafusion/optimizer/src/common_subexpr_eliminate.rs
b/datafusion/optimizer/src/common_subexpr_eliminate.rs
index cebae410f3..4a4933fe9c 100644
--- a/datafusion/optimizer/src/common_subexpr_eliminate.rs
+++ b/datafusion/optimizer/src/common_subexpr_eliminate.rs
@@ -191,24 +191,19 @@ impl CommonSubexprEliminate {
id_array: &mut IdArray<'n>,
expr_mask: ExprMask,
) -> Result<bool> {
- // Don't consider volatile expressions for CSE.
- Ok(if expr.is_volatile()? {
- false
- } else {
- let mut visitor = ExprIdentifierVisitor {
- expr_stats,
- id_array,
- visit_stack: vec![],
- down_index: 0,
- up_index: 0,
- expr_mask,
- random_state: &self.random_state,
- found_common: false,
- };
- expr.visit(&mut visitor)?;
+ let mut visitor = ExprIdentifierVisitor {
+ expr_stats,
+ id_array,
+ visit_stack: vec![],
+ down_index: 0,
+ up_index: 0,
+ expr_mask,
+ random_state: &self.random_state,
+ found_common: false,
+ };
+ expr.visit(&mut visitor)?;
- visitor.found_common
- })
+ Ok(visitor.found_common)
}
/// Rewrites `exprs_list` with common sub-expressions replaced with a new
@@ -917,27 +912,50 @@ struct ExprIdentifierVisitor<'a, 'n> {
/// Record item that used when traversing an expression tree.
enum VisitRecord<'n> {
- /// Contains the post-order index assigned in during the first, visiting
traversal and
- /// a boolean flag to indicate if the record marks an expression subtree
(not just a
- /// single node).
+ /// Marks the beginning of expression. It contains:
+ /// - The post-order index assigned during the first, visiting traversal.
+ /// - A boolean flag if the record marks an expression subtree (not just a
single
+ /// node).
EnterMark(usize, bool),
- /// Accumulated identifier of sub expression.
- ExprItem(Identifier<'n>),
+
+ /// Marks an accumulated subexpression tree. It contains:
+ /// - The accumulated identifier of a subexpression.
+ /// - A boolean flag if the expression is valid for subexpression
elimination.
+ /// The flag is propagated up from children to parent. (E.g. volatile
expressions
+ /// are not valid and can't be extracted, but non-volatile children of
volatile
+ /// expressions can be extracted.)
+ ExprItem(Identifier<'n>, bool),
}
impl<'n> ExprIdentifierVisitor<'_, 'n> {
- /// Find the first `EnterMark` in the stack, and accumulates every
`ExprItem`
- /// before it.
- fn pop_enter_mark(&mut self) -> (usize, bool, Option<Identifier<'n>>) {
+ /// Find the first `EnterMark` in the stack, and accumulates every
`ExprItem` before
+ /// it. Returns a tuple that contains:
+ /// - The pre-order index of the expression we marked.
+ /// - A boolean flag if we marked an expression subtree (not just a single
node).
+ /// If true we didn't recurse into the node's children, so we need to
calculate the
+ /// hash of the marked expression tree (not just the node) and we need
to validate
+ /// the expression tree (not just the node).
+ /// - The accumulated identifier of the children of the marked expression.
+ /// - An accumulated boolean flag from the children of the marked
expression if all
+ /// children are valid for subexpression elimination (i.e. it is safe to
extract the
+ /// expression as a common expression from its children POV).
+ /// (E.g. if any of the children of the marked expression is not valid
(e.g. is
+ /// volatile) then the expression is also not valid, so we can propagate
this
+ /// information up from children to parents via `visit_stack` during the
first,
+ /// visiting traversal and no need to test the expression's validity
beforehand with
+ /// an extra traversal).
+ fn pop_enter_mark(&mut self) -> (usize, bool, Option<Identifier<'n>>,
bool) {
let mut expr_id = None;
+ let mut is_valid = true;
while let Some(item) = self.visit_stack.pop() {
match item {
- VisitRecord::EnterMark(down_index, tree) => {
- return (down_index, tree, expr_id);
+ VisitRecord::EnterMark(down_index, is_tree) => {
+ return (down_index, is_tree, expr_id, is_valid);
}
- VisitRecord::ExprItem(id) => {
- expr_id = Some(id.combine(expr_id));
+ VisitRecord::ExprItem(sub_expr_id, sub_expr_is_valid) => {
+ expr_id = Some(sub_expr_id.combine(expr_id));
+ is_valid &= sub_expr_is_valid;
}
}
}
@@ -949,8 +967,6 @@ impl<'n> TreeNodeVisitor<'n> for ExprIdentifierVisitor<'_,
'n> {
type Node = Expr;
fn f_down(&mut self, expr: &'n Expr) -> Result<TreeNodeRecursion> {
- // TODO: consider non-volatile sub-expressions for CSE
-
// If an expression can short circuit its children then don't consider
its
// children for CSE
(https://github.com/apache/arrow-datafusion/issues/8814).
// This means that we don't recurse into its children, but handle the
expression
@@ -972,13 +988,22 @@ impl<'n> TreeNodeVisitor<'n> for
ExprIdentifierVisitor<'_, 'n> {
}
fn f_up(&mut self, expr: &'n Expr) -> Result<TreeNodeRecursion> {
- let (down_index, is_tree, sub_expr_id) = self.pop_enter_mark();
+ let (down_index, is_tree, sub_expr_id, sub_expr_is_valid) =
self.pop_enter_mark();
- let expr_id =
- Identifier::new(expr, is_tree,
self.random_state).combine(sub_expr_id);
+ let (expr_id, is_valid) = if is_tree {
+ (
+ Identifier::new(expr, true, self.random_state),
+ !expr.is_volatile()?,
+ )
+ } else {
+ (
+ Identifier::new(expr, false,
self.random_state).combine(sub_expr_id),
+ !expr.is_volatile_node() && sub_expr_is_valid,
+ )
+ };
self.id_array[down_index].0 = self.up_index;
- if !self.expr_mask.ignores(expr) {
+ if is_valid && !self.expr_mask.ignores(expr) {
self.id_array[down_index].1 = Some(expr_id);
let count = self.expr_stats.entry(expr_id).or_insert(0);
*count += 1;
@@ -986,7 +1011,8 @@ impl<'n> TreeNodeVisitor<'n> for ExprIdentifierVisitor<'_,
'n> {
self.found_common = true;
}
}
- self.visit_stack.push(VisitRecord::ExprItem(expr_id));
+ self.visit_stack
+ .push(VisitRecord::ExprItem(expr_id, is_valid));
self.up_index += 1;
Ok(TreeNodeRecursion::Continue)
@@ -1101,6 +1127,7 @@ fn replace_common_expr<'n>(
#[cfg(test)]
mod test {
+ use std::any::Any;
use std::collections::HashSet;
use std::iter;
@@ -1108,8 +1135,9 @@ mod test {
use datafusion_expr::expr::AggregateFunction;
use datafusion_expr::logical_plan::{table_scan, JoinType};
use datafusion_expr::{
- grouping_set, AccumulatorFactoryFunction, AggregateUDF, BinaryExpr,
Signature,
- SimpleAggregateUDF, Volatility,
+ grouping_set, AccumulatorFactoryFunction, AggregateUDF, BinaryExpr,
+ ColumnarValue, ScalarUDF, ScalarUDFImpl, Signature, SimpleAggregateUDF,
+ Volatility,
};
use datafusion_expr::{lit, logical_plan::builder::LogicalPlanBuilder};
@@ -1838,4 +1866,96 @@ mod test {
Ok(())
}
+
+ #[test]
+ fn test_volatile() -> Result<()> {
+ let table_scan = test_table_scan()?;
+
+ let extracted_child = col("a") + col("b");
+ let rand = rand_func().call(vec![]);
+ let not_extracted_volatile = extracted_child + rand;
+ let plan = LogicalPlanBuilder::from(table_scan.clone())
+ .project(vec![
+ not_extracted_volatile.clone().alias("c1"),
+ not_extracted_volatile.alias("c2"),
+ ])?
+ .build()?;
+
+ let expected = "Projection: __common_expr_1 + random() AS c1,
__common_expr_1 + random() AS c2\
+ \n Projection: test.a + test.b AS __common_expr_1, test.a, test.b,
test.c\
+ \n TableScan: test";
+
+ assert_optimized_plan_eq(expected, plan, None);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_volatile_short_circuits() -> Result<()> {
+ let table_scan = test_table_scan()?;
+
+ let rand = rand_func().call(vec![]);
+ let not_extracted_volatile_short_circuit_2 =
+ rand.clone().eq(lit(0)).or(col("b").eq(lit(0)));
+ let not_extracted_volatile_short_circuit_1 =
+ col("a").eq(lit(0)).or(rand.eq(lit(0)));
+ let plan = LogicalPlanBuilder::from(table_scan.clone())
+ .project(vec![
+ not_extracted_volatile_short_circuit_1.clone().alias("c1"),
+ not_extracted_volatile_short_circuit_1.alias("c2"),
+ not_extracted_volatile_short_circuit_2.clone().alias("c3"),
+ not_extracted_volatile_short_circuit_2.alias("c4"),
+ ])?
+ .build()?;
+
+ let expected = "Projection: test.a = Int32(0) OR random() = Int32(0)
AS c1, test.a = Int32(0) OR random() = Int32(0) AS c2, random() = Int32(0) OR
test.b = Int32(0) AS c3, random() = Int32(0) OR test.b = Int32(0) AS c4\
+ \n TableScan: test";
+
+ assert_non_optimized_plan_eq(expected, plan, None);
+
+ Ok(())
+ }
+
+ /// returns a "random" function that is marked volatile (aka each
invocation
+ /// returns a different value)
+ ///
+ /// Does not use datafusion_functions::rand to avoid introducing a
+ /// dependency on that crate.
+ fn rand_func() -> ScalarUDF {
+ ScalarUDF::new_from_impl(RandomStub::new())
+ }
+
+ #[derive(Debug)]
+ struct RandomStub {
+ signature: Signature,
+ }
+
+ impl RandomStub {
+ fn new() -> Self {
+ Self {
+ signature: Signature::exact(vec![], Volatility::Volatile),
+ }
+ }
+ }
+ impl ScalarUDFImpl for RandomStub {
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+
+ fn name(&self) -> &str {
+ "random"
+ }
+
+ fn signature(&self) -> &Signature {
+ &self.signature
+ }
+
+ fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
+ Ok(DataType::Float64)
+ }
+
+ fn invoke(&self, _args: &[ColumnarValue]) -> Result<ColumnarValue> {
+ unimplemented!()
+ }
+ }
}
diff --git a/datafusion/sqllogictest/test_files/cse.slt
b/datafusion/sqllogictest/test_files/cse.slt
new file mode 100644
index 0000000000..3579c1c163
--- /dev/null
+++ b/datafusion/sqllogictest/test_files/cse.slt
@@ -0,0 +1,173 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+statement ok
+CREATE TABLE IF NOT EXISTS t1(a DOUBLE, b DOUBLE)
+
+# Trivial common expression
+query TT
+EXPLAIN SELECT
+ a + 1 AS c1,
+ a + 1 AS c2
+FROM t1
+----
+logical_plan
+01)Projection: __common_expr_1 AS c1, __common_expr_1 AS c2
+02)--Projection: t1.a + Float64(1) AS __common_expr_1
+03)----TableScan: t1 projection=[a]
+physical_plan
+01)ProjectionExec: expr=[__common_expr_1@0 as c1, __common_expr_1@0 as c2]
+02)--ProjectionExec: expr=[a@0 + 1 as __common_expr_1]
+03)----MemoryExec: partitions=1, partition_sizes=[0]
+
+# Common volatile expression
+query TT
+EXPLAIN SELECT
+ a + random() AS c1,
+ a + random() AS c2
+FROM t1
+----
+logical_plan
+01)Projection: t1.a + random() AS c1, t1.a + random() AS c2
+02)--TableScan: t1 projection=[a]
+physical_plan
+01)ProjectionExec: expr=[a@0 + random() as c1, a@0 + random() as c2]
+02)--MemoryExec: partitions=1, partition_sizes=[0]
+
+# Volatile expression with non-volatile common child
+query TT
+EXPLAIN SELECT
+ a + 1 + random() AS c1,
+ a + 1 + random() AS c2
+FROM t1
+----
+logical_plan
+01)Projection: __common_expr_1 + random() AS c1, __common_expr_1 + random() AS
c2
+02)--Projection: t1.a + Float64(1) AS __common_expr_1
+03)----TableScan: t1 projection=[a]
+physical_plan
+01)ProjectionExec: expr=[__common_expr_1@0 + random() as c1, __common_expr_1@0
+ random() as c2]
+02)--ProjectionExec: expr=[a@0 + 1 as __common_expr_1]
+03)----MemoryExec: partitions=1, partition_sizes=[0]
+
+# Volatile expression with non-volatile common children
+query TT
+EXPLAIN SELECT
+ a + 1 + random() + (a + 2) AS c1,
+ a + 1 + random() + (a + 2) AS c2
+FROM t1
+----
+logical_plan
+01)Projection: __common_expr_1 + random() + __common_expr_2 AS c1,
__common_expr_1 + random() + __common_expr_2 AS c2
+02)--Projection: t1.a + Float64(1) AS __common_expr_1, t1.a + Float64(2) AS
__common_expr_2
+03)----TableScan: t1 projection=[a]
+physical_plan
+01)ProjectionExec: expr=[__common_expr_1@0 + random() + __common_expr_2@1 as
c1, __common_expr_1@0 + random() + __common_expr_2@1 as c2]
+02)--ProjectionExec: expr=[a@0 + 1 as __common_expr_1, a@0 + 2 as
__common_expr_2]
+03)----MemoryExec: partitions=1, partition_sizes=[0]
+
+# Common short-circuit expression
+query TT
+EXPLAIN SELECT
+ a = 0 AND b = 0 AS c1,
+ a = 0 AND b = 0 AS c2,
+ a = 0 OR b = 0 AS c3,
+ a = 0 OR b = 0 AS c4,
+ CASE WHEN (a = 0) THEN 0 ELSE 1 END AS c5,
+ CASE WHEN (a = 0) THEN 0 ELSE 1 END AS c6
+FROM t1
+----
+logical_plan
+01)Projection: __common_expr_1 AS c1, __common_expr_1 AS c2, __common_expr_2
AS c3, __common_expr_2 AS c4, __common_expr_3 AS c5, __common_expr_3 AS c6
+02)--Projection: t1.a = Float64(0) AND t1.b = Float64(0) AS __common_expr_1,
t1.a = Float64(0) OR t1.b = Float64(0) AS __common_expr_2, CASE WHEN t1.a =
Float64(0) THEN Int64(0) ELSE Int64(1) END AS __common_expr_3
+03)----TableScan: t1 projection=[a, b]
+physical_plan
+01)ProjectionExec: expr=[__common_expr_1@0 as c1, __common_expr_1@0 as c2,
__common_expr_2@1 as c3, __common_expr_2@1 as c4, __common_expr_3@2 as c5,
__common_expr_3@2 as c6]
+02)--ProjectionExec: expr=[a@0 = 0 AND b@1 = 0 as __common_expr_1, a@0 = 0 OR
b@1 = 0 as __common_expr_2, CASE WHEN a@0 = 0 THEN 0 ELSE 1 END as
__common_expr_3]
+03)----MemoryExec: partitions=1, partition_sizes=[0]
+
+# Common children of short-circuit expression
+# TODO: consider surely executed children of "short circuited"s for CSE. i.e.
`a = 0`, `a = 2`, `a = 4` should be extracted
+query TT
+EXPLAIN SELECT
+ a = 0 AND b = 0 AS c1,
+ a = 0 AND b = 1 AS c2,
+ b = 2 AND a = 1 AS c3,
+ b = 3 AND a = 1 AS c4,
+ a = 2 OR b = 4 AS c5,
+ a = 2 OR b = 5 AS c6,
+ b = 6 OR a = 3 AS c7,
+ b = 7 OR a = 3 AS c8,
+ CASE WHEN (a = 4) THEN 0 ELSE 1 END AS c9,
+ CASE WHEN (a = 4) THEN 0 ELSE 2 END AS c10,
+ CASE WHEN (b = 8) THEN a + 1 ELSE 0 END AS c11,
+ CASE WHEN (b = 9) THEN a + 1 ELSE 0 END AS c12,
+ CASE WHEN (b = 10) THEN 0 ELSE a + 2 END AS c13,
+ CASE WHEN (b = 11) THEN 0 ELSE a + 2 END AS c14
+FROM t1
+----
+logical_plan
+01)Projection: t1.a = Float64(0) AND t1.b = Float64(0) AS c1, t1.a =
Float64(0) AND t1.b = Float64(1) AS c2, t1.b = Float64(2) AND t1.a = Float64(1)
AS c3, t1.b = Float64(3) AND t1.a = Float64(1) AS c4, t1.a = Float64(2) OR t1.b
= Float64(4) AS c5, t1.a = Float64(2) OR t1.b = Float64(5) AS c6, t1.b =
Float64(6) OR t1.a = Float64(3) AS c7, t1.b = Float64(7) OR t1.a = Float64(3)
AS c8, CASE WHEN t1.a = Float64(4) THEN Int64(0) ELSE Int64(1) END AS c9, CASE
WHEN t1.a = Float64(4) THEN Int64 [...]
+02)--TableScan: t1 projection=[a, b]
+physical_plan
+01)ProjectionExec: expr=[a@0 = 0 AND b@1 = 0 as c1, a@0 = 0 AND b@1 = 1 as c2,
b@1 = 2 AND a@0 = 1 as c3, b@1 = 3 AND a@0 = 1 as c4, a@0 = 2 OR b@1 = 4 as c5,
a@0 = 2 OR b@1 = 5 as c6, b@1 = 6 OR a@0 = 3 as c7, b@1 = 7 OR a@0 = 3 as c8,
CASE WHEN a@0 = 4 THEN 0 ELSE 1 END as c9, CASE WHEN a@0 = 4 THEN 0 ELSE 2 END
as c10, CASE WHEN b@1 = 8 THEN a@0 + 1 ELSE 0 END as c11, CASE WHEN b@1 = 9
THEN a@0 + 1 ELSE 0 END as c12, CASE WHEN b@1 = 10 THEN 0 ELSE a@0 + 2 END as
c13, CASE WHEN b@1 = 1 [...]
+02)--MemoryExec: partitions=1, partition_sizes=[0]
+
+# Common children of volatile, short-circuit expression
+# TODO: consider surely executed children of "short circuited"s for CSE. i.e.
`a = 0`, `a = 2`, `a = 4` should be extracted
+query TT
+EXPLAIN SELECT
+ a = 0 AND b = random() AS c1,
+ a = 0 AND b = 1 + random() AS c2,
+ b = 2 + random() AND a = 1 AS c3,
+ b = 3 + random() AND a = 1 AS c4,
+ a = 2 OR b = 4 + random() AS c5,
+ a = 2 OR b = 5 + random() AS c6,
+ b = 6 + random() OR a = 3 AS c7,
+ b = 7 + random() OR a = 3 AS c8,
+ CASE WHEN (a = 4) THEN random() ELSE 1 END AS c9,
+ CASE WHEN (a = 4) THEN random() ELSE 2 END AS c10,
+ CASE WHEN (b = 8 + random()) THEN a + 1 ELSE 0 END AS c11,
+ CASE WHEN (b = 9 + random()) THEN a + 1 ELSE 0 END AS c12,
+ CASE WHEN (b = 10 + random()) THEN 0 ELSE a + 2 END AS c13,
+ CASE WHEN (b = 11 + random()) THEN 0 ELSE a + 2 END AS c14
+FROM t1
+----
+logical_plan
+01)Projection: t1.a = Float64(0) AND t1.b = random() AS c1, t1.a = Float64(0)
AND t1.b = Float64(1) + random() AS c2, t1.b = Float64(2) + random() AND t1.a =
Float64(1) AS c3, t1.b = Float64(3) + random() AND t1.a = Float64(1) AS c4,
t1.a = Float64(2) OR t1.b = Float64(4) + random() AS c5, t1.a = Float64(2) OR
t1.b = Float64(5) + random() AS c6, t1.b = Float64(6) + random() OR t1.a =
Float64(3) AS c7, t1.b = Float64(7) + random() OR t1.a = Float64(3) AS c8, CASE
WHEN t1.a = Float64(4) TH [...]
+02)--TableScan: t1 projection=[a, b]
+physical_plan
+01)ProjectionExec: expr=[a@0 = 0 AND b@1 = random() as c1, a@0 = 0 AND b@1 = 1
+ random() as c2, b@1 = 2 + random() AND a@0 = 1 as c3, b@1 = 3 + random() AND
a@0 = 1 as c4, a@0 = 2 OR b@1 = 4 + random() as c5, a@0 = 2 OR b@1 = 5 +
random() as c6, b@1 = 6 + random() OR a@0 = 3 as c7, b@1 = 7 + random() OR a@0
= 3 as c8, CASE WHEN a@0 = 4 THEN random() ELSE 1 END as c9, CASE WHEN a@0 = 4
THEN random() ELSE 2 END as c10, CASE WHEN b@1 = 8 + random() THEN a@0 + 1 ELSE
0 END as c11, CASE WHEN [...]
+02)--MemoryExec: partitions=1, partition_sizes=[0]
+
+# Common volatile children of short-circuit expression
+query TT
+EXPLAIN SELECT
+ a = random() AND b = 0 AS c1,
+ a = random() AND b = 1 AS c2,
+ a = 2 + random() OR b = 4 AS c3,
+ a = 2 + random() OR b = 5 AS c4,
+ CASE WHEN (a = 4 + random()) THEN 0 ELSE 1 END AS c5,
+ CASE WHEN (a = 4 + random()) THEN 0 ELSE 2 END AS c6
+FROM t1
+----
+logical_plan
+01)Projection: t1.a = random() AND t1.b = Float64(0) AS c1, t1.a = random()
AND t1.b = Float64(1) AS c2, t1.a = Float64(2) + random() OR t1.b = Float64(4)
AS c3, t1.a = Float64(2) + random() OR t1.b = Float64(5) AS c4, CASE WHEN t1.a
= Float64(4) + random() THEN Int64(0) ELSE Int64(1) END AS c5, CASE WHEN t1.a =
Float64(4) + random() THEN Int64(0) ELSE Int64(2) END AS c6
+02)--TableScan: t1 projection=[a, b]
+physical_plan
+01)ProjectionExec: expr=[a@0 = random() AND b@1 = 0 as c1, a@0 = random() AND
b@1 = 1 as c2, a@0 = 2 + random() OR b@1 = 4 as c3, a@0 = 2 + random() OR b@1 =
5 as c4, CASE WHEN a@0 = 4 + random() THEN 0 ELSE 1 END as c5, CASE WHEN a@0 =
4 + random() THEN 0 ELSE 2 END as c6]
+02)--MemoryExec: partitions=1, partition_sizes=[0]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]