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]

Reply via email to