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 818ce3f01e refactor: Incorporate RewriteDisjunctivePredicate rule into
SimplifyExpressions (#13032)
818ce3f01e is described below
commit 818ce3f01efe1213a9a1eda5dff1542bb9d457f7
Author: Emil Ejbyfeldt <[email protected]>
AuthorDate: Tue Oct 22 17:27:55 2024 +0200
refactor: Incorporate RewriteDisjunctivePredicate rule into
SimplifyExpressions (#13032)
* Elliminate common factors in disjunctions
This adds a rewrite rule that elliminates common factors in OR. This is
already implmented in RewriteDisjunctivePredicate but this
implementation is simpler and will apply in more cases.
* Remove RewriteDisjunctivePredicate rule
* Fix cse test
* Update datafusion/optimizer/src/simplify_expressions/expr_simplifier.rs
Co-authored-by: Andrew Lamb <[email protected]>
---------
Co-authored-by: Andrew Lamb <[email protected]>
---
datafusion/expr/src/utils.rs | 48 +++
datafusion/optimizer/src/lib.rs | 1 -
datafusion/optimizer/src/optimizer.rs | 2 -
datafusion/optimizer/src/push_down_filter.rs | 4 +-
.../optimizer/src/rewrite_disjunctive_predicate.rs | 430 ---------------------
.../src/simplify_expressions/expr_simplifier.rs | 73 +++-
datafusion/sqllogictest/test_files/cse.slt | 8 +-
datafusion/sqllogictest/test_files/explain.slt | 2 -
8 files changed, 126 insertions(+), 442 deletions(-)
diff --git a/datafusion/expr/src/utils.rs b/datafusion/expr/src/utils.rs
index 9ee13f1e06..86562daf69 100644
--- a/datafusion/expr/src/utils.rs
+++ b/datafusion/expr/src/utils.rs
@@ -1101,6 +1101,54 @@ fn split_conjunction_impl<'a>(expr: &'a Expr, mut exprs:
Vec<&'a Expr>) -> Vec<&
}
}
+/// Iteratate parts in a conjunctive [`Expr`] such as `A AND B AND C` => `[A,
B, C]`
+///
+/// See [`split_conjunction_owned`] for more details and an example.
+pub fn iter_conjunction(expr: &Expr) -> impl Iterator<Item = &Expr> {
+ let mut stack = vec![expr];
+ std::iter::from_fn(move || {
+ while let Some(expr) = stack.pop() {
+ match expr {
+ Expr::BinaryExpr(BinaryExpr {
+ right,
+ op: Operator::And,
+ left,
+ }) => {
+ stack.push(right);
+ stack.push(left);
+ }
+ Expr::Alias(Alias { expr, .. }) => stack.push(expr),
+ other => return Some(other),
+ }
+ }
+ None
+ })
+}
+
+/// Iteratate parts in a conjunctive [`Expr`] such as `A AND B AND C` => `[A,
B, C]`
+///
+/// See [`split_conjunction_owned`] for more details and an example.
+pub fn iter_conjunction_owned(expr: Expr) -> impl Iterator<Item = Expr> {
+ let mut stack = vec![expr];
+ std::iter::from_fn(move || {
+ while let Some(expr) = stack.pop() {
+ match expr {
+ Expr::BinaryExpr(BinaryExpr {
+ right,
+ op: Operator::And,
+ left,
+ }) => {
+ stack.push(*right);
+ stack.push(*left);
+ }
+ Expr::Alias(Alias { expr, .. }) => stack.push(*expr),
+ other => return Some(other),
+ }
+ }
+ None
+ })
+}
+
/// Splits an owned conjunctive [`Expr`] such as `A AND B AND C` => `[A, B, C]`
///
/// This is often used to "split" filter expressions such as `col1 = 5
diff --git a/datafusion/optimizer/src/lib.rs b/datafusion/optimizer/src/lib.rs
index 3b1df3510d..f310838311 100644
--- a/datafusion/optimizer/src/lib.rs
+++ b/datafusion/optimizer/src/lib.rs
@@ -51,7 +51,6 @@ pub mod propagate_empty_relation;
pub mod push_down_filter;
pub mod push_down_limit;
pub mod replace_distinct_aggregate;
-pub mod rewrite_disjunctive_predicate;
pub mod scalar_subquery_to_join;
pub mod simplify_expressions;
pub mod single_distinct_to_groupby;
diff --git a/datafusion/optimizer/src/optimizer.rs
b/datafusion/optimizer/src/optimizer.rs
index 08dcefa22f..373c877187 100644
--- a/datafusion/optimizer/src/optimizer.rs
+++ b/datafusion/optimizer/src/optimizer.rs
@@ -51,7 +51,6 @@ use crate::propagate_empty_relation::PropagateEmptyRelation;
use crate::push_down_filter::PushDownFilter;
use crate::push_down_limit::PushDownLimit;
use crate::replace_distinct_aggregate::ReplaceDistinctWithAggregate;
-use crate::rewrite_disjunctive_predicate::RewriteDisjunctivePredicate;
use crate::scalar_subquery_to_join::ScalarSubqueryToJoin;
use crate::simplify_expressions::SimplifyExpressions;
use crate::single_distinct_to_groupby::SingleDistinctToGroupBy;
@@ -255,7 +254,6 @@ impl Optimizer {
// run it again after running the optimizations that potentially
converted
// subqueries to joins
Arc::new(SimplifyExpressions::new()),
- Arc::new(RewriteDisjunctivePredicate::new()),
Arc::new(EliminateDuplicatedExpr::new()),
Arc::new(EliminateFilter::new()),
Arc::new(EliminateCrossJoin::new()),
diff --git a/datafusion/optimizer/src/push_down_filter.rs
b/datafusion/optimizer/src/push_down_filter.rs
index 2e3bca5b0b..ac81f3efaa 100644
--- a/datafusion/optimizer/src/push_down_filter.rs
+++ b/datafusion/optimizer/src/push_down_filter.rs
@@ -1213,7 +1213,7 @@ mod tests {
};
use crate::optimizer::Optimizer;
- use crate::rewrite_disjunctive_predicate::RewriteDisjunctivePredicate;
+ use crate::simplify_expressions::SimplifyExpressions;
use crate::test::*;
use crate::OptimizerContext;
use datafusion_expr::test::function_stub::sum;
@@ -1235,7 +1235,7 @@ mod tests {
expected: &str,
) -> Result<()> {
let optimizer = Optimizer::with_rules(vec![
- Arc::new(RewriteDisjunctivePredicate::new()),
+ Arc::new(SimplifyExpressions::new()),
Arc::new(PushDownFilter::new()),
]);
let optimized_plan =
diff --git a/datafusion/optimizer/src/rewrite_disjunctive_predicate.rs
b/datafusion/optimizer/src/rewrite_disjunctive_predicate.rs
deleted file mode 100644
index a6b633fdb8..0000000000
--- a/datafusion/optimizer/src/rewrite_disjunctive_predicate.rs
+++ /dev/null
@@ -1,430 +0,0 @@
-// 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.
-
-//! [`RewriteDisjunctivePredicate`] rewrites predicates to reduce redundancy
-
-use crate::optimizer::ApplyOrder;
-use crate::{OptimizerConfig, OptimizerRule};
-use datafusion_common::tree_node::Transformed;
-use datafusion_common::Result;
-use datafusion_expr::expr::BinaryExpr;
-use datafusion_expr::logical_plan::Filter;
-use datafusion_expr::{Expr, LogicalPlan, Operator};
-
-/// Optimizer pass that rewrites predicates of the form
-///
-/// ```text
-/// (A = B AND <expr1>) OR (A = B AND <expr2>) OR ... (A = B AND <exprN>)
-/// ```
-///
-/// Into
-/// ```text
-/// (A = B) AND (<expr1> OR <expr2> OR ... <exprN> )
-/// ```
-///
-/// Predicates connected by `OR` typically not able to be broken down
-/// and distributed as well as those connected by `AND`.
-///
-/// The idea is to rewrite predicates into `good_predicate1 AND
-/// good_predicate2 AND ...` where `good_predicate` means the
-/// predicate has special support in the execution engine.
-///
-/// Equality join predicates (e.g. `col1 = col2`), or single column
-/// expressions (e.g. `col = 5`) are examples of predicates with
-/// special support.
-///
-/// # TPCH Q19
-///
-/// This optimization is admittedly somewhat of a niche usecase. It's
-/// main use is that it appears in TPCH Q19 and is required to avoid a
-/// CROSS JOIN.
-///
-/// Specifically, Q19 has a WHERE clause that looks like
-///
-/// ```sql
-/// where
-/// (
-/// p_partkey = l_partkey
-/// and p_brand = ‘[BRAND1]’
-/// and p_container in ( ‘SM CASE’, ‘SM BOX’, ‘SM PACK’, ‘SM PKG’)
-/// and l_quantity >= [QUANTITY1] and l_quantity <= [QUANTITY1] + 10
-/// and p_size between 1 and 5
-/// and l_shipmode in (‘AIR’, ‘AIR REG’)
-/// and l_shipinstruct = ‘DELIVER IN PERSON’
-/// )
-/// or
-/// (
-/// p_partkey = l_partkey
-/// and p_brand = ‘[BRAND2]’
-/// and p_container in (‘MED BAG’, ‘MED BOX’, ‘MED PKG’, ‘MED PACK’)
-/// and l_quantity >= [QUANTITY2] and l_quantity <= [QUANTITY2] + 10
-/// and p_size between 1 and 10
-/// and l_shipmode in (‘AIR’, ‘AIR REG’)
-/// and l_shipinstruct = ‘DELIVER IN PERSON’
-/// )
-/// or
-/// (
-/// p_partkey = l_partkey
-/// and p_brand = ‘[BRAND3]’
-/// and p_container in ( ‘LG CASE’, ‘LG BOX’, ‘LG PACK’, ‘LG PKG’)
-/// and l_quantity >= [QUANTITY3] and l_quantity <= [QUANTITY3] + 10
-/// and p_size between 1 and 15
-/// and l_shipmode in (‘AIR’, ‘AIR REG’)
-/// and l_shipinstruct = ‘DELIVER IN PERSON’
-/// )
-/// )
-/// ```
-///
-/// Naively planning this query will result in a CROSS join with that
-/// single large OR filter. However, rewriting it using the rewrite in
-/// this pass results in a proper join predicate, `p_partkey = l_partkey`:
-///
-/// ```sql
-/// where
-/// p_partkey = l_partkey
-/// and l_shipmode in (‘AIR’, ‘AIR REG’)
-/// and l_shipinstruct = ‘DELIVER IN PERSON’
-/// and (
-/// (
-/// and p_brand = ‘[BRAND1]’
-/// and p_container in ( ‘SM CASE’, ‘SM BOX’, ‘SM PACK’, ‘SM PKG’)
-/// and l_quantity >= [QUANTITY1] and l_quantity <= [QUANTITY1] + 10
-/// and p_size between 1 and 5
-/// )
-/// or
-/// (
-/// and p_brand = ‘[BRAND2]’
-/// and p_container in (‘MED BAG’, ‘MED BOX’, ‘MED PKG’, ‘MED PACK’)
-/// and l_quantity >= [QUANTITY2] and l_quantity <= [QUANTITY2] + 10
-/// and p_size between 1 and 10
-/// )
-/// or
-/// (
-/// and p_brand = ‘[BRAND3]’
-/// and p_container in ( ‘LG CASE’, ‘LG BOX’, ‘LG PACK’, ‘LG PKG’)
-/// and l_quantity >= [QUANTITY3] and l_quantity <= [QUANTITY3] + 10
-/// and p_size between 1 and 15
-/// )
-/// )
-/// ```
-///
-#[derive(Default, Debug)]
-pub struct RewriteDisjunctivePredicate;
-
-impl RewriteDisjunctivePredicate {
- pub fn new() -> Self {
- Self
- }
-}
-
-impl OptimizerRule for RewriteDisjunctivePredicate {
- fn name(&self) -> &str {
- "rewrite_disjunctive_predicate"
- }
-
- fn apply_order(&self) -> Option<ApplyOrder> {
- Some(ApplyOrder::TopDown)
- }
-
- fn supports_rewrite(&self) -> bool {
- true
- }
-
- fn rewrite(
- &self,
- plan: LogicalPlan,
- _config: &dyn OptimizerConfig,
- ) -> Result<Transformed<LogicalPlan>> {
- match plan {
- LogicalPlan::Filter(filter) => {
- let predicate = predicate(filter.predicate)?;
- let rewritten_predicate = rewrite_predicate(predicate);
- let rewritten_expr = normalize_predicate(rewritten_predicate);
- Ok(Transformed::yes(LogicalPlan::Filter(Filter::try_new(
- rewritten_expr,
- filter.input,
- )?)))
- }
- _ => Ok(Transformed::no(plan)),
- }
- }
-}
-
-#[derive(Clone, PartialEq, Debug)]
-enum Predicate {
- And { args: Vec<Predicate> },
- Or { args: Vec<Predicate> },
- Other { expr: Box<Expr> },
-}
-
-fn predicate(expr: Expr) -> Result<Predicate> {
- match expr {
- Expr::BinaryExpr(BinaryExpr { left, op, right }) => match op {
- Operator::And => {
- let args = vec![predicate(*left)?, predicate(*right)?];
- Ok(Predicate::And { args })
- }
- Operator::Or => {
- let args = vec![predicate(*left)?, predicate(*right)?];
- Ok(Predicate::Or { args })
- }
- _ => Ok(Predicate::Other {
- expr: Box::new(Expr::BinaryExpr(BinaryExpr::new(left, op,
right))),
- }),
- },
- _ => Ok(Predicate::Other {
- expr: Box::new(expr),
- }),
- }
-}
-
-fn normalize_predicate(predicate: Predicate) -> Expr {
- match predicate {
- Predicate::And { args } => {
- assert!(args.len() >= 2);
- args.into_iter()
- .map(normalize_predicate)
- .reduce(Expr::and)
- .expect("had more than one arg")
- }
- Predicate::Or { args } => {
- assert!(args.len() >= 2);
- args.into_iter()
- .map(normalize_predicate)
- .reduce(Expr::or)
- .expect("had more than one arg")
- }
- Predicate::Other { expr } => *expr,
- }
-}
-
-fn rewrite_predicate(predicate: Predicate) -> Predicate {
- match predicate {
- Predicate::And { args } => {
- let mut rewritten_args = Vec::with_capacity(args.len());
- for arg in args.into_iter() {
- rewritten_args.push(rewrite_predicate(arg));
- }
- rewritten_args = flatten_and_predicates(rewritten_args);
- Predicate::And {
- args: rewritten_args,
- }
- }
- Predicate::Or { args } => {
- let mut rewritten_args = vec![];
- for arg in args.into_iter() {
- rewritten_args.push(rewrite_predicate(arg));
- }
- rewritten_args = flatten_or_predicates(rewritten_args);
- delete_duplicate_predicates(rewritten_args)
- }
- Predicate::Other { expr } => Predicate::Other { expr },
- }
-}
-
-fn flatten_and_predicates(
- and_predicates: impl IntoIterator<Item = Predicate>,
-) -> Vec<Predicate> {
- let mut flattened_predicates = vec![];
- for predicate in and_predicates {
- match predicate {
- Predicate::And { args } => {
- flattened_predicates.append(&mut flatten_and_predicates(args));
- }
- _ => {
- flattened_predicates.push(predicate);
- }
- }
- }
- flattened_predicates
-}
-
-fn flatten_or_predicates(
- or_predicates: impl IntoIterator<Item = Predicate>,
-) -> Vec<Predicate> {
- let mut flattened_predicates = vec![];
- for predicate in or_predicates {
- match predicate {
- Predicate::Or { args } => {
- flattened_predicates.append(&mut flatten_or_predicates(args));
- }
- _ => {
- flattened_predicates.push(predicate);
- }
- }
- }
- flattened_predicates
-}
-
-fn delete_duplicate_predicates(or_predicates: Vec<Predicate>) -> Predicate {
- let mut shortest_exprs: Vec<Predicate> = vec![];
- let mut shortest_exprs_len = 0;
- // choose the shortest AND predicate
- for or_predicate in or_predicates.iter() {
- match or_predicate {
- Predicate::And { args } => {
- let args_num = args.len();
- if shortest_exprs.is_empty() || args_num < shortest_exprs_len {
- shortest_exprs.clone_from(args);
- shortest_exprs_len = args_num;
- }
- }
- _ => {
- // if there is no AND predicate, it must be the shortest
expression.
- shortest_exprs = vec![or_predicate.clone()];
- break;
- }
- }
- }
-
- // dedup shortest_exprs
- shortest_exprs.dedup();
-
- // Check each element in shortest_exprs to see if it's in all the OR
arguments.
- let mut exist_exprs: Vec<Predicate> = vec![];
- for expr in shortest_exprs.iter() {
- let found = or_predicates.iter().all(|or_predicate| match or_predicate
{
- Predicate::And { args } => args.contains(expr),
- _ => or_predicate == expr,
- });
- if found {
- exist_exprs.push((*expr).clone());
- }
- }
- if exist_exprs.is_empty() {
- return Predicate::Or {
- args: or_predicates,
- };
- }
-
- // Rebuild the OR predicate.
- // (A AND B) OR A will be optimized to A.
- let mut new_or_predicates = vec![];
- for or_predicate in or_predicates.into_iter() {
- match or_predicate {
- Predicate::And { mut args } => {
- args.retain(|expr| !exist_exprs.contains(expr));
- if !args.is_empty() {
- if args.len() == 1 {
- new_or_predicates.push(args.remove(0));
- } else {
- new_or_predicates.push(Predicate::And { args });
- }
- } else {
- new_or_predicates.clear();
- break;
- }
- }
- _ => {
- if exist_exprs.contains(&or_predicate) {
- new_or_predicates.clear();
- break;
- }
- }
- }
- }
- if !new_or_predicates.is_empty() {
- if new_or_predicates.len() == 1 {
- exist_exprs.push(new_or_predicates.remove(0));
- } else {
- exist_exprs.push(Predicate::Or {
- args: flatten_or_predicates(new_or_predicates),
- });
- }
- }
-
- if exist_exprs.len() == 1 {
- exist_exprs.remove(0)
- } else {
- Predicate::And {
- args: flatten_and_predicates(exist_exprs),
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::rewrite_disjunctive_predicate::{
- normalize_predicate, predicate, rewrite_predicate, Predicate,
- };
-
- use datafusion_common::{Result, ScalarValue};
- use datafusion_expr::{and, col, lit, or};
-
- #[test]
- fn test_rewrite_predicate() -> Result<()> {
- let equi_expr = col("t1.a").eq(col("t2.b"));
- let gt_expr = col("t1.c").gt(lit(ScalarValue::Int8(Some(1))));
- let lt_expr = col("t1.d").lt(lit(ScalarValue::Int8(Some(2))));
- let expr = or(
- and(equi_expr.clone(), gt_expr.clone()),
- and(equi_expr.clone(), lt_expr.clone()),
- );
- let predicate = predicate(expr)?;
- assert_eq!(
- predicate,
- Predicate::Or {
- args: vec![
- Predicate::And {
- args: vec![
- Predicate::Other {
- expr: Box::new(equi_expr.clone())
- },
- Predicate::Other {
- expr: Box::new(gt_expr.clone())
- },
- ]
- },
- Predicate::And {
- args: vec![
- Predicate::Other {
- expr: Box::new(equi_expr.clone())
- },
- Predicate::Other {
- expr: Box::new(lt_expr.clone())
- },
- ]
- },
- ]
- }
- );
- let rewritten_predicate = rewrite_predicate(predicate);
- assert_eq!(
- rewritten_predicate,
- Predicate::And {
- args: vec![
- Predicate::Other {
- expr: Box::new(equi_expr.clone())
- },
- Predicate::Or {
- args: vec![
- Predicate::Other {
- expr: Box::new(gt_expr.clone())
- },
- Predicate::Other {
- expr: Box::new(lt_expr.clone())
- },
- ]
- },
- ]
- }
- );
- let rewritten_expr = normalize_predicate(rewritten_predicate);
- assert_eq!(rewritten_expr, and(equi_expr, or(gt_expr, lt_expr)));
- Ok(())
- }
-}
diff --git a/datafusion/optimizer/src/simplify_expressions/expr_simplifier.rs
b/datafusion/optimizer/src/simplify_expressions/expr_simplifier.rs
index 2bac71a6ae..f9dfadc708 100644
--- a/datafusion/optimizer/src/simplify_expressions/expr_simplifier.rs
+++ b/datafusion/optimizer/src/simplify_expressions/expr_simplifier.rs
@@ -32,14 +32,18 @@ use datafusion_common::{
tree_node::{Transformed, TransformedResult, TreeNode, TreeNodeRewriter},
};
use datafusion_common::{internal_err, DFSchema, DataFusionError, Result,
ScalarValue};
-use datafusion_expr::expr::{InList, InSubquery, WindowFunction};
use datafusion_expr::simplify::ExprSimplifyResult;
use datafusion_expr::{
and, lit, or, BinaryExpr, Case, ColumnarValue, Expr, Like, Operator,
Volatility,
WindowFunctionDefinition,
};
use datafusion_expr::{expr::ScalarFunction,
interval_arithmetic::NullableInterval};
+use datafusion_expr::{
+ expr::{InList, InSubquery, WindowFunction},
+ utils::{iter_conjunction, iter_conjunction_owned},
+};
use datafusion_physical_expr::{create_physical_expr,
execution_props::ExecutionProps};
+use indexmap::IndexSet;
use crate::analyzer::type_coercion::TypeCoercionRewriter;
use crate::simplify_expressions::guarantees::GuaranteeRewriter;
@@ -850,6 +854,27 @@ impl<'a, S: SimplifyInfo> TreeNodeRewriter for
Simplifier<'a, S> {
op: Or,
right,
}) if is_op_with(And, &left, &right) => Transformed::yes(*right),
+ // Eliminate common factors in conjunctions e.g
+ // (A AND B) OR (A AND C) -> A AND (B OR C)
+ Expr::BinaryExpr(BinaryExpr {
+ left,
+ op: Or,
+ right,
+ }) if has_common_conjunction(&left, &right) => {
+ let lhs: IndexSet<Expr> =
iter_conjunction_owned(*left).collect();
+ let (common, rhs): (Vec<_>, Vec<_>) =
+ iter_conjunction_owned(*right).partition(|e|
lhs.contains(e));
+
+ let new_rhs = rhs.into_iter().reduce(and);
+ let new_lhs = lhs.into_iter().filter(|e|
!common.contains(e)).reduce(and);
+ let common_conjunction =
common.into_iter().reduce(and).unwrap();
+
+ let new_expr = match (new_lhs, new_rhs) {
+ (Some(lhs), Some(rhs)) => and(common_conjunction, or(lhs,
rhs)),
+ (_, _) => common_conjunction,
+ };
+ Transformed::yes(new_expr)
+ }
//
// Rules for AND
@@ -1656,6 +1681,11 @@ impl<'a, S: SimplifyInfo> TreeNodeRewriter for
Simplifier<'a, S> {
}
}
+fn has_common_conjunction(lhs: &Expr, rhs: &Expr) -> bool {
+ let lhs: HashSet<&Expr> = iter_conjunction(lhs).collect();
+ iter_conjunction(rhs).any(|e| lhs.contains(&e))
+}
+
// TODO: We might not need this after defer pattern for Box is stabilized.
https://github.com/rust-lang/rust/issues/87121
fn are_inlist_and_eq_and_match_neg(
left: &Expr,
@@ -3743,6 +3773,47 @@ mod tests {
assert_eq!(expr, expected);
assert_eq!(num_iter, 2);
}
+
+ fn boolean_test_schema() -> DFSchemaRef {
+ Schema::new(vec![
+ Field::new("A", DataType::Boolean, false),
+ Field::new("B", DataType::Boolean, false),
+ Field::new("C", DataType::Boolean, false),
+ Field::new("D", DataType::Boolean, false),
+ ])
+ .to_dfschema_ref()
+ .unwrap()
+ }
+
+ #[test]
+ fn simplify_common_factor_conjuction_in_disjunction() {
+ let props = ExecutionProps::new();
+ let schema = boolean_test_schema();
+ let simplifier =
+
ExprSimplifier::new(SimplifyContext::new(&props).with_schema(schema));
+
+ let a = || col("A");
+ let b = || col("B");
+ let c = || col("C");
+ let d = || col("D");
+
+ // (A AND B) OR (A AND C) -> A AND (B OR C)
+ let expr = a().and(b()).or(a().and(c()));
+ let expected = a().and(b().or(c()));
+
+ assert_eq!(expected, simplifier.simplify(expr).unwrap());
+
+ // (A AND B) OR (A AND C) OR (A AND D) -> A AND (B OR C OR D)
+ let expr = a().and(b()).or(a().and(c())).or(a().and(d()));
+ let expected = a().and(b().or(c()).or(d()));
+ assert_eq!(expected, simplifier.simplify(expr).unwrap());
+
+ // A OR (B AND C AND A) -> A
+ let expr = a().or(b().and(c().and(a())));
+ let expected = a();
+ assert_eq!(expected, simplifier.simplify(expr).unwrap());
+ }
+
#[test]
fn test_simplify_udaf() {
let udaf =
AggregateUDF::new_from_impl(SimplifyMockUdaf::new_with_simplify());
diff --git a/datafusion/sqllogictest/test_files/cse.slt
b/datafusion/sqllogictest/test_files/cse.slt
index 9f0f654179..c95e9a1309 100644
--- a/datafusion/sqllogictest/test_files/cse.slt
+++ b/datafusion/sqllogictest/test_files/cse.slt
@@ -199,18 +199,18 @@ physical_plan
# Surely only once but also conditionally evaluated subexpressions
query TT
EXPLAIN SELECT
- (a = 1 OR random() = 0) AND (a = 1 OR random() = 1) AS c1,
- (a = 2 AND random() = 0) OR (a = 2 AND random() = 1) AS c2,
+ (a = 1 OR random() = 0) AND (a = 2 OR random() = 1) AS c1,
+ (a = 2 AND random() = 0) OR (a = 1 AND random() = 1) AS c2,
CASE WHEN a + 3 = 0 THEN a + 3 + random() ELSE 0 END AS c3,
CASE WHEN a + 4 = 0 THEN 0 ELSE a + 4 + random() END AS c4
FROM t1
----
logical_plan
-01)Projection: (__common_expr_1 OR random() = Float64(0)) AND (__common_expr_1
OR random() = Float64(1)) AS c1, __common_expr_2 AND random() = Float64(0) OR
__common_expr_2 AND random() = Float64(1) AS c2, CASE WHEN __common_expr_3 =
Float64(0) THEN __common_expr_3 + random() ELSE Float64(0) END AS c3, CASE WHEN
__common_expr_4 = Float64(0) THEN Float64(0) ELSE __common_expr_4 + random()
END AS c4
+01)Projection: (__common_expr_1 OR random() = Float64(0)) AND (__common_expr_2
OR random() = Float64(1)) AS c1, __common_expr_2 AND random() = Float64(0) OR
__common_expr_1 AND random() = Float64(1) AS c2, CASE WHEN __common_expr_3 =
Float64(0) THEN __common_expr_3 + random() ELSE Float64(0) END AS c3, CASE WHEN
__common_expr_4 = Float64(0) THEN Float64(0) ELSE __common_expr_4 + random()
END AS c4
02)--Projection: t1.a = Float64(1) AS __common_expr_1, t1.a = Float64(2) AS
__common_expr_2, t1.a + Float64(3) AS __common_expr_3, t1.a + Float64(4) AS
__common_expr_4
03)----TableScan: t1 projection=[a]
physical_plan
-01)ProjectionExec: expr=[(__common_expr_1@0 OR random() = 0) AND
(__common_expr_1@0 OR random() = 1) as c1, __common_expr_2@1 AND random() = 0
OR __common_expr_2@1 AND random() = 1 as c2, CASE WHEN __common_expr_3@2 = 0
THEN __common_expr_3@2 + random() ELSE 0 END as c3, CASE WHEN __common_expr_4@3
= 0 THEN 0 ELSE __common_expr_4@3 + random() END as c4]
+01)ProjectionExec: expr=[(__common_expr_1@0 OR random() = 0) AND
(__common_expr_2@1 OR random() = 1) as c1, __common_expr_2@1 AND random() = 0
OR __common_expr_1@0 AND random() = 1 as c2, CASE WHEN __common_expr_3@2 = 0
THEN __common_expr_3@2 + random() ELSE 0 END as c3, CASE WHEN __common_expr_4@3
= 0 THEN 0 ELSE __common_expr_4@3 + random() END as c4]
02)--ProjectionExec: expr=[a@0 = 1 as __common_expr_1, a@0 = 2 as
__common_expr_2, a@0 + 3 as __common_expr_3, a@0 + 4 as __common_expr_4]
03)----MemoryExec: partitions=1, partition_sizes=[0]
diff --git a/datafusion/sqllogictest/test_files/explain.slt
b/datafusion/sqllogictest/test_files/explain.slt
index b1962ffcc1..54340604ad 100644
--- a/datafusion/sqllogictest/test_files/explain.slt
+++ b/datafusion/sqllogictest/test_files/explain.slt
@@ -189,7 +189,6 @@ logical_plan after decorrelate_predicate_subquery SAME TEXT
AS ABOVE
logical_plan after scalar_subquery_to_join SAME TEXT AS ABOVE
logical_plan after extract_equijoin_predicate SAME TEXT AS ABOVE
logical_plan after simplify_expressions SAME TEXT AS ABOVE
-logical_plan after rewrite_disjunctive_predicate SAME TEXT AS ABOVE
logical_plan after eliminate_duplicated_expr SAME TEXT AS ABOVE
logical_plan after eliminate_filter SAME TEXT AS ABOVE
logical_plan after eliminate_cross_join SAME TEXT AS ABOVE
@@ -216,7 +215,6 @@ logical_plan after decorrelate_predicate_subquery SAME TEXT
AS ABOVE
logical_plan after scalar_subquery_to_join SAME TEXT AS ABOVE
logical_plan after extract_equijoin_predicate SAME TEXT AS ABOVE
logical_plan after simplify_expressions SAME TEXT AS ABOVE
-logical_plan after rewrite_disjunctive_predicate SAME TEXT AS ABOVE
logical_plan after eliminate_duplicated_expr SAME TEXT AS ABOVE
logical_plan after eliminate_filter SAME TEXT AS ABOVE
logical_plan after eliminate_cross_join SAME TEXT AS ABOVE
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]