use std::borrow::Cow;
use std::collections::HashSet;
use std::ops::Not;
use arrow::{
array::{new_null_array, AsArray},
datatypes::{DataType, Field, Schema},
record_batch::RecordBatch,
};
use datafusion_common::{
cast::{as_large_list_array, as_list_array},
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_physical_expr::{create_physical_expr, execution_props::ExecutionProps};
use crate::analyzer::type_coercion::TypeCoercionRewriter;
use crate::simplify_expressions::guarantees::GuaranteeRewriter;
use crate::simplify_expressions::regex::simplify_regex_expr;
use crate::simplify_expressions::SimplifyInfo;
use super::inlist_simplifier::ShortenInListSimplifier;
use super::utils::*;
pub struct ExprSimplifier<S> {
info: S,
guarantees: Vec<(Expr, NullableInterval)>,
canonicalize: bool,
max_simplifier_cycles: u32,
}
pub const THRESHOLD_INLINE_INLIST: usize = 3;
pub const DEFAULT_MAX_SIMPLIFIER_CYCLES: u32 = 3;
impl<S: SimplifyInfo> ExprSimplifier<S> {
pub fn new(info: S) -> Self {
Self {
info,
guarantees: vec![],
canonicalize: true,
max_simplifier_cycles: DEFAULT_MAX_SIMPLIFIER_CYCLES,
}
}
pub fn simplify(&self, expr: Expr) -> Result<Expr> {
Ok(self.simplify_with_cycle_count(expr)?.0)
}
pub fn simplify_with_cycle_count(&self, mut expr: Expr) -> Result<(Expr, u32)> {
let mut simplifier = Simplifier::new(&self.info);
let mut const_evaluator = ConstEvaluator::try_new(self.info.execution_props())?;
let mut shorten_in_list_simplifier = ShortenInListSimplifier::new();
let mut guarantee_rewriter = GuaranteeRewriter::new(&self.guarantees);
if self.canonicalize {
expr = expr.rewrite(&mut Canonicalizer::new()).data()?
}
let mut num_cycles = 0;
loop {
let Transformed {
data, transformed, ..
} = expr
.rewrite(&mut const_evaluator)?
.transform_data(|expr| expr.rewrite(&mut simplifier))?
.transform_data(|expr| expr.rewrite(&mut guarantee_rewriter))?;
expr = data;
num_cycles += 1;
if !transformed || num_cycles >= self.max_simplifier_cycles {
break;
}
}
expr = expr.rewrite(&mut shorten_in_list_simplifier).data()?;
Ok((expr, num_cycles))
}
pub fn coerce(&self, expr: Expr, schema: &DFSchema) -> Result<Expr> {
let mut expr_rewrite = TypeCoercionRewriter { schema };
expr.rewrite(&mut expr_rewrite).data()
}
pub fn with_guarantees(mut self, guarantees: Vec<(Expr, NullableInterval)>) -> Self {
self.guarantees = guarantees;
self
}
pub fn with_canonicalize(mut self, canonicalize: bool) -> Self {
self.canonicalize = canonicalize;
self
}
pub fn with_max_cycles(mut self, max_simplifier_cycles: u32) -> Self {
self.max_simplifier_cycles = max_simplifier_cycles;
self
}
}
struct Canonicalizer {}
impl Canonicalizer {
fn new() -> Self {
Self {}
}
}
impl TreeNodeRewriter for Canonicalizer {
type Node = Expr;
fn f_up(&mut self, expr: Expr) -> Result<Transformed<Expr>> {
let Expr::BinaryExpr(BinaryExpr { left, op, right }) = expr else {
return Ok(Transformed::no(expr));
};
match (left.as_ref(), right.as_ref(), op.swap()) {
(Expr::Column(left_col), Expr::Column(right_col), Some(swapped_op))
if right_col > left_col =>
{
Ok(Transformed::yes(Expr::BinaryExpr(BinaryExpr {
left: right,
op: swapped_op,
right: left,
})))
}
(Expr::Literal(_a), Expr::Column(_b), Some(swapped_op)) => {
Ok(Transformed::yes(Expr::BinaryExpr(BinaryExpr {
left: right,
op: swapped_op,
right: left,
})))
}
_ => Ok(Transformed::no(Expr::BinaryExpr(BinaryExpr {
left,
op,
right,
}))),
}
}
}
#[allow(rustdoc::private_intra_doc_links)]
struct ConstEvaluator<'a> {
can_evaluate: Vec<bool>,
execution_props: &'a ExecutionProps,
input_schema: DFSchema,
input_batch: RecordBatch,
}
#[allow(dead_code)]
enum ConstSimplifyResult {
Simplified(ScalarValue),
NotSimplified(ScalarValue),
SimplifyRuntimeError(DataFusionError, Expr),
}
impl<'a> TreeNodeRewriter for ConstEvaluator<'a> {
type Node = Expr;
fn f_down(&mut self, expr: Expr) -> Result<Transformed<Expr>> {
self.can_evaluate.push(true);
if !Self::can_evaluate(&expr) {
let parent_iter = self.can_evaluate.iter_mut().rev();
for p in parent_iter {
if !*p {
break;
}
*p = false;
}
}
Ok(Transformed::no(expr))
}
fn f_up(&mut self, expr: Expr) -> Result<Transformed<Expr>> {
match self.can_evaluate.pop() {
Some(true) => {
let result = self.evaluate_to_scalar(expr);
match result {
ConstSimplifyResult::Simplified(s) => {
Ok(Transformed::yes(Expr::Literal(s)))
}
ConstSimplifyResult::NotSimplified(s) => {
Ok(Transformed::no(Expr::Literal(s)))
}
ConstSimplifyResult::SimplifyRuntimeError(_, expr) => {
Ok(Transformed::yes(expr))
}
}
}
Some(false) => Ok(Transformed::no(expr)),
_ => internal_err!("Failed to pop can_evaluate"),
}
}
}
impl<'a> ConstEvaluator<'a> {
pub fn try_new(execution_props: &'a ExecutionProps) -> Result<Self> {
static DUMMY_COL_NAME: &str = ".";
let schema = Schema::new(vec![Field::new(DUMMY_COL_NAME, DataType::Null, true)]);
let input_schema = DFSchema::try_from(schema.clone())?;
let col = new_null_array(&DataType::Null, 1);
let input_batch = RecordBatch::try_new(std::sync::Arc::new(schema), vec![col])?;
Ok(Self {
can_evaluate: vec![],
execution_props,
input_schema,
input_batch,
})
}
fn volatility_ok(volatility: Volatility) -> bool {
match volatility {
Volatility::Immutable => true,
Volatility::Stable => true,
Volatility::Volatile => false,
}
}
fn can_evaluate(expr: &Expr) -> bool {
match expr {
Expr::Alias(..)
| Expr::AggregateFunction { .. }
| Expr::ScalarVariable(_, _)
| Expr::Column(_)
| Expr::OuterReferenceColumn(_, _)
| Expr::Exists { .. }
| Expr::InSubquery(_)
| Expr::ScalarSubquery(_)
| Expr::WindowFunction { .. }
| Expr::Sort { .. }
| Expr::GroupingSet(_)
| Expr::Wildcard { .. }
| Expr::Placeholder(_) => false,
Expr::ScalarFunction(ScalarFunction { func, .. }) => {
Self::volatility_ok(func.signature().volatility)
}
Expr::Literal(_)
| Expr::Unnest(_)
| Expr::BinaryExpr { .. }
| Expr::Not(_)
| Expr::IsNotNull(_)
| Expr::IsNull(_)
| Expr::IsTrue(_)
| Expr::IsFalse(_)
| Expr::IsUnknown(_)
| Expr::IsNotTrue(_)
| Expr::IsNotFalse(_)
| Expr::IsNotUnknown(_)
| Expr::Negative(_)
| Expr::Between { .. }
| Expr::Like { .. }
| Expr::SimilarTo { .. }
| Expr::Case(_)
| Expr::Cast { .. }
| Expr::TryCast { .. }
| Expr::InList { .. } => true,
}
}
pub(crate) fn evaluate_to_scalar(&mut self, expr: Expr) -> ConstSimplifyResult {
if let Expr::Literal(s) = expr {
return ConstSimplifyResult::NotSimplified(s);
}
let phys_expr =
match create_physical_expr(&expr, &self.input_schema, self.execution_props) {
Ok(e) => e,
Err(err) => return ConstSimplifyResult::SimplifyRuntimeError(err, expr),
};
let col_val = match phys_expr.evaluate(&self.input_batch) {
Ok(v) => v,
Err(err) => return ConstSimplifyResult::SimplifyRuntimeError(err, expr),
};
match col_val {
ColumnarValue::Array(a) => {
if a.len() != 1 {
ConstSimplifyResult::SimplifyRuntimeError(
DataFusionError::Execution(format!("Could not evaluate the expression, found a result of length {}", a.len())),
expr,
)
} else if as_list_array(&a).is_ok() {
ConstSimplifyResult::Simplified(ScalarValue::List(
a.as_list::<i32>().to_owned().into(),
))
} else if as_large_list_array(&a).is_ok() {
ConstSimplifyResult::Simplified(ScalarValue::LargeList(
a.as_list::<i64>().to_owned().into(),
))
} else {
match ScalarValue::try_from_array(&a, 0) {
Ok(s) => {
if matches!(&s, ScalarValue::Map(_)) {
ConstSimplifyResult::SimplifyRuntimeError(
DataFusionError::NotImplemented("Const evaluate for Map type is still not supported".to_string()),
expr,
)
} else {
ConstSimplifyResult::Simplified(s)
}
}
Err(err) => ConstSimplifyResult::SimplifyRuntimeError(err, expr),
}
}
}
ColumnarValue::Scalar(s) => {
if matches!(&s, ScalarValue::Map(_)) {
ConstSimplifyResult::SimplifyRuntimeError(
DataFusionError::NotImplemented(
"Const evaluate for Map type is still not supported"
.to_string(),
),
expr,
)
} else {
ConstSimplifyResult::Simplified(s)
}
}
}
}
}
struct Simplifier<'a, S> {
info: &'a S,
}
impl<'a, S> Simplifier<'a, S> {
pub fn new(info: &'a S) -> Self {
Self { info }
}
}
impl<'a, S: SimplifyInfo> TreeNodeRewriter for Simplifier<'a, S> {
type Node = Expr;
fn f_up(&mut self, expr: Expr) -> Result<Transformed<Expr>> {
use datafusion_expr::Operator::{
And, BitwiseAnd, BitwiseOr, BitwiseShiftLeft, BitwiseShiftRight, BitwiseXor,
Divide, Eq, Modulo, Multiply, NotEq, Or, RegexIMatch, RegexMatch,
RegexNotIMatch, RegexNotMatch,
};
let info = self.info;
Ok(match expr {
Expr::BinaryExpr(BinaryExpr {
left,
op: Eq,
right,
}) if is_bool_lit(&left) && info.is_boolean_type(&right)? => {
Transformed::yes(match as_bool_lit(*left)? {
Some(true) => *right,
Some(false) => Expr::Not(right),
None => lit_bool_null(),
})
}
Expr::BinaryExpr(BinaryExpr {
left,
op: Eq,
right,
}) if is_bool_lit(&right) && info.is_boolean_type(&left)? => {
Transformed::yes(match as_bool_lit(*right)? {
Some(true) => *left,
Some(false) => Expr::Not(left),
None => lit_bool_null(),
})
}
Expr::BinaryExpr(BinaryExpr {
left,
op: NotEq,
right,
}) if is_bool_lit(&left) && info.is_boolean_type(&right)? => {
Transformed::yes(match as_bool_lit(*left)? {
Some(true) => Expr::Not(right),
Some(false) => *right,
None => lit_bool_null(),
})
}
Expr::BinaryExpr(BinaryExpr {
left,
op: NotEq,
right,
}) if is_bool_lit(&right) && info.is_boolean_type(&left)? => {
Transformed::yes(match as_bool_lit(*right)? {
Some(true) => Expr::Not(left),
Some(false) => *left,
None => lit_bool_null(),
})
}
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right: _,
}) if is_true(&left) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right,
}) if is_false(&left) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left: _,
op: Or,
right,
}) if is_true(&right) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right,
}) if is_false(&right) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right,
}) if is_not_of(&right, &left) && !info.nullable(&left)? => {
Transformed::yes(Expr::Literal(ScalarValue::Boolean(Some(true))))
}
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right,
}) if is_not_of(&left, &right) && !info.nullable(&right)? => {
Transformed::yes(Expr::Literal(ScalarValue::Boolean(Some(true))))
}
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right,
}) if expr_contains(&left, &right, Or) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right,
}) if expr_contains(&right, &left, Or) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right,
}) if !info.nullable(&right)? && is_op_with(And, &right, &left) => {
Transformed::yes(*left)
}
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right,
}) if !info.nullable(&left)? && is_op_with(And, &left, &right) => {
Transformed::yes(*right)
}
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if is_true(&left) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right: _,
}) if is_false(&left) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if is_true(&right) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left: _,
op: And,
right,
}) if is_false(&right) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if is_not_of(&right, &left) && !info.nullable(&left)? => {
Transformed::yes(Expr::Literal(ScalarValue::Boolean(Some(false))))
}
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if is_not_of(&left, &right) && !info.nullable(&right)? => {
Transformed::yes(Expr::Literal(ScalarValue::Boolean(Some(false))))
}
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if expr_contains(&left, &right, And) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if expr_contains(&right, &left, And) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if !info.nullable(&right)? && is_op_with(Or, &right, &left) => {
Transformed::yes(*left)
}
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if !info.nullable(&left)? && is_op_with(Or, &left, &right) => {
Transformed::yes(*right)
}
Expr::BinaryExpr(BinaryExpr {
left,
op: Multiply,
right,
}) if is_one(&right) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: Multiply,
right,
}) if is_one(&left) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left: _,
op: Multiply,
right,
}) if is_null(&right) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: Multiply,
right: _,
}) if is_null(&left) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: Multiply,
right,
}) if !info.nullable(&left)?
&& !info.get_data_type(&left)?.is_floating()
&& is_zero(&right) =>
{
Transformed::yes(*right)
}
Expr::BinaryExpr(BinaryExpr {
left,
op: Multiply,
right,
}) if !info.nullable(&right)?
&& !info.get_data_type(&right)?.is_floating()
&& is_zero(&left) =>
{
Transformed::yes(*left)
}
Expr::BinaryExpr(BinaryExpr {
left,
op: Divide,
right,
}) if is_one(&right) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: Divide,
right: _,
}) if is_null(&left) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left: _,
op: Divide,
right,
}) if is_null(&right) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left: _,
op: Modulo,
right,
}) if is_null(&right) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: Modulo,
right: _,
}) if is_null(&left) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: Modulo,
right,
}) if !info.nullable(&left)?
&& !info.get_data_type(&left)?.is_floating()
&& is_one(&right) =>
{
Transformed::yes(lit(0))
}
Expr::BinaryExpr(BinaryExpr {
left: _,
op: BitwiseAnd,
right,
}) if is_null(&right) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right: _,
}) if is_null(&left) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right,
}) if !info.nullable(&left)? && is_zero(&right) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right,
}) if !info.nullable(&right)? && is_zero(&left) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right,
}) if is_negative_of(&left, &right) && !info.nullable(&right)? => {
Transformed::yes(Expr::Literal(ScalarValue::new_zero(
&info.get_data_type(&left)?,
)?))
}
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right,
}) if is_negative_of(&right, &left) && !info.nullable(&left)? => {
Transformed::yes(Expr::Literal(ScalarValue::new_zero(
&info.get_data_type(&left)?,
)?))
}
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right,
}) if expr_contains(&left, &right, BitwiseAnd) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right,
}) if expr_contains(&right, &left, BitwiseAnd) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right,
}) if !info.nullable(&right)? && is_op_with(BitwiseOr, &right, &left) => {
Transformed::yes(*left)
}
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right,
}) if !info.nullable(&left)? && is_op_with(BitwiseOr, &left, &right) => {
Transformed::yes(*right)
}
Expr::BinaryExpr(BinaryExpr {
left: _,
op: BitwiseOr,
right,
}) if is_null(&right) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right: _,
}) if is_null(&left) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right,
}) if is_zero(&right) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right,
}) if is_zero(&left) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right,
}) if is_negative_of(&left, &right) && !info.nullable(&right)? => {
Transformed::yes(Expr::Literal(ScalarValue::new_negative_one(
&info.get_data_type(&left)?,
)?))
}
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right,
}) if is_negative_of(&right, &left) && !info.nullable(&left)? => {
Transformed::yes(Expr::Literal(ScalarValue::new_negative_one(
&info.get_data_type(&left)?,
)?))
}
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right,
}) if expr_contains(&left, &right, BitwiseOr) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right,
}) if expr_contains(&right, &left, BitwiseOr) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right,
}) if !info.nullable(&right)? && is_op_with(BitwiseAnd, &right, &left) => {
Transformed::yes(*left)
}
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right,
}) if !info.nullable(&left)? && is_op_with(BitwiseAnd, &left, &right) => {
Transformed::yes(*right)
}
Expr::BinaryExpr(BinaryExpr {
left: _,
op: BitwiseXor,
right,
}) if is_null(&right) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseXor,
right: _,
}) if is_null(&left) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseXor,
right,
}) if !info.nullable(&left)? && is_zero(&right) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseXor,
right,
}) if !info.nullable(&right)? && is_zero(&left) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseXor,
right,
}) if is_negative_of(&left, &right) && !info.nullable(&right)? => {
Transformed::yes(Expr::Literal(ScalarValue::new_negative_one(
&info.get_data_type(&left)?,
)?))
}
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseXor,
right,
}) if is_negative_of(&right, &left) && !info.nullable(&left)? => {
Transformed::yes(Expr::Literal(ScalarValue::new_negative_one(
&info.get_data_type(&left)?,
)?))
}
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseXor,
right,
}) if expr_contains(&left, &right, BitwiseXor) => {
let expr = delete_xor_in_complex_expr(&left, &right, false);
Transformed::yes(if expr == *right {
Expr::Literal(ScalarValue::new_zero(&info.get_data_type(&right)?)?)
} else {
expr
})
}
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseXor,
right,
}) if expr_contains(&right, &left, BitwiseXor) => {
let expr = delete_xor_in_complex_expr(&right, &left, true);
Transformed::yes(if expr == *left {
Expr::Literal(ScalarValue::new_zero(&info.get_data_type(&left)?)?)
} else {
expr
})
}
Expr::BinaryExpr(BinaryExpr {
left: _,
op: BitwiseShiftRight,
right,
}) if is_null(&right) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseShiftRight,
right: _,
}) if is_null(&left) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseShiftRight,
right,
}) if is_zero(&right) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left: _,
op: BitwiseShiftLeft,
right,
}) if is_null(&right) => Transformed::yes(*right),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseShiftLeft,
right: _,
}) if is_null(&left) => Transformed::yes(*left),
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseShiftLeft,
right,
}) if is_zero(&right) => Transformed::yes(*left),
Expr::Not(inner) => Transformed::yes(negate_clause(*inner)),
Expr::Negative(inner) => Transformed::yes(distribute_negation(*inner)),
Expr::Case(Case {
expr: None,
when_then_expr,
else_expr,
}) if !when_then_expr.is_empty()
&& when_then_expr.len() < 3 && info.is_boolean_type(&when_then_expr[0].1)? =>
{
let mut filter_expr = lit(false);
let mut out_expr = lit(false);
for (when, then) in when_then_expr {
let case_expr = when
.as_ref()
.clone()
.and(filter_expr.clone().not())
.and(*then);
out_expr = out_expr.or(case_expr);
filter_expr = filter_expr.or(*when);
}
if let Some(else_expr) = else_expr {
let case_expr = filter_expr.not().and(*else_expr);
out_expr = out_expr.or(case_expr);
}
out_expr.rewrite(self)?
}
Expr::ScalarFunction(ScalarFunction { func: udf, args }) => {
match udf.simplify(args, info)? {
ExprSimplifyResult::Original(args) => {
Transformed::no(Expr::ScalarFunction(ScalarFunction {
func: udf,
args,
}))
}
ExprSimplifyResult::Simplified(expr) => Transformed::yes(expr),
}
}
Expr::AggregateFunction(datafusion_expr::expr::AggregateFunction {
ref func,
..
}) => match (func.simplify(), expr) {
(Some(simplify_function), Expr::AggregateFunction(af)) => {
Transformed::yes(simplify_function(af, info)?)
}
(_, expr) => Transformed::no(expr),
},
Expr::WindowFunction(WindowFunction {
fun: WindowFunctionDefinition::WindowUDF(ref udwf),
..
}) => match (udwf.simplify(), expr) {
(Some(simplify_function), Expr::WindowFunction(wf)) => {
Transformed::yes(simplify_function(wf, info)?)
}
(_, expr) => Transformed::no(expr),
},
Expr::Between(between) => Transformed::yes(if between.negated {
let l = *between.expr.clone();
let r = *between.expr;
or(l.lt(*between.low), r.gt(*between.high))
} else {
and(
between.expr.clone().gt_eq(*between.low),
between.expr.lt_eq(*between.high),
)
}),
Expr::BinaryExpr(BinaryExpr {
left,
op: op @ (RegexMatch | RegexNotMatch | RegexIMatch | RegexNotIMatch),
right,
}) => Transformed::yes(simplify_regex_expr(left, op, right)?),
Expr::Like(Like {
expr,
pattern,
negated,
escape_char: _,
case_insensitive: _,
}) if !is_null(&expr)
&& matches!(
pattern.as_ref(),
Expr::Literal(ScalarValue::Utf8(Some(pattern_str))) if pattern_str == "%"
) =>
{
Transformed::yes(lit(!negated))
}
Expr::IsNotNull(expr) | Expr::IsNotUnknown(expr)
if !info.nullable(&expr)? =>
{
Transformed::yes(lit(true))
}
Expr::IsNull(expr) | Expr::IsUnknown(expr) if !info.nullable(&expr)? => {
Transformed::yes(lit(false))
}
Expr::InList(InList {
expr,
list,
negated,
}) if list.is_empty() && *expr != Expr::Literal(ScalarValue::Null) => {
Transformed::yes(lit(negated))
}
Expr::InList(InList {
expr,
list: _,
negated: _,
}) if is_null(expr.as_ref()) => Transformed::yes(lit_bool_null()),
Expr::InList(InList {
expr,
mut list,
negated,
}) if list.len() == 1
&& matches!(list.first(), Some(Expr::ScalarSubquery { .. })) =>
{
let Expr::ScalarSubquery(subquery) = list.remove(0) else {
unreachable!()
};
Transformed::yes(Expr::InSubquery(InSubquery::new(
expr, subquery, negated,
)))
}
Expr::BinaryExpr(BinaryExpr {
left,
op: Operator::Or,
right,
}) if are_inlist_and_eq(left.as_ref(), right.as_ref()) => {
let lhs = to_inlist(*left).unwrap();
let rhs = to_inlist(*right).unwrap();
let mut seen: HashSet<Expr> = HashSet::new();
let list = lhs
.list
.into_iter()
.chain(rhs.list)
.filter(|e| seen.insert(e.to_owned()))
.collect::<Vec<_>>();
let merged_inlist = InList {
expr: lhs.expr,
list,
negated: false,
};
Transformed::yes(Expr::InList(merged_inlist))
}
Expr::BinaryExpr(BinaryExpr {
left,
op: Operator::And,
right,
}) if are_inlist_and_eq_and_match_neg(
left.as_ref(),
right.as_ref(),
false,
false,
) =>
{
match (*left, *right) {
(Expr::InList(l1), Expr::InList(l2)) => {
return inlist_intersection(l1, l2, false).map(Transformed::yes);
}
_ => unreachable!(),
}
}
Expr::BinaryExpr(BinaryExpr {
left,
op: Operator::And,
right,
}) if are_inlist_and_eq_and_match_neg(
left.as_ref(),
right.as_ref(),
true,
true,
) =>
{
match (*left, *right) {
(Expr::InList(l1), Expr::InList(l2)) => {
return inlist_union(l1, l2, true).map(Transformed::yes);
}
_ => unreachable!(),
}
}
Expr::BinaryExpr(BinaryExpr {
left,
op: Operator::And,
right,
}) if are_inlist_and_eq_and_match_neg(
left.as_ref(),
right.as_ref(),
false,
true,
) =>
{
match (*left, *right) {
(Expr::InList(l1), Expr::InList(l2)) => {
return inlist_except(l1, l2).map(Transformed::yes);
}
_ => unreachable!(),
}
}
Expr::BinaryExpr(BinaryExpr {
left,
op: Operator::And,
right,
}) if are_inlist_and_eq_and_match_neg(
left.as_ref(),
right.as_ref(),
true,
false,
) =>
{
match (*left, *right) {
(Expr::InList(l1), Expr::InList(l2)) => {
return inlist_except(l2, l1).map(Transformed::yes);
}
_ => unreachable!(),
}
}
Expr::BinaryExpr(BinaryExpr {
left,
op: Operator::Or,
right,
}) if are_inlist_and_eq_and_match_neg(
left.as_ref(),
right.as_ref(),
true,
true,
) =>
{
match (*left, *right) {
(Expr::InList(l1), Expr::InList(l2)) => {
return inlist_intersection(l1, l2, true).map(Transformed::yes);
}
_ => unreachable!(),
}
}
expr => Transformed::no(expr),
})
}
}
fn are_inlist_and_eq_and_match_neg(
left: &Expr,
right: &Expr,
is_left_neg: bool,
is_right_neg: bool,
) -> bool {
match (left, right) {
(Expr::InList(l), Expr::InList(r)) => {
l.expr == r.expr && l.negated == is_left_neg && r.negated == is_right_neg
}
_ => false,
}
}
fn are_inlist_and_eq(left: &Expr, right: &Expr) -> bool {
let left = as_inlist(left);
let right = as_inlist(right);
if let (Some(lhs), Some(rhs)) = (left, right) {
matches!(lhs.expr.as_ref(), Expr::Column(_))
&& matches!(rhs.expr.as_ref(), Expr::Column(_))
&& lhs.expr == rhs.expr
&& !lhs.negated
&& !rhs.negated
} else {
false
}
}
fn as_inlist(expr: &Expr) -> Option<Cow<InList>> {
match expr {
Expr::InList(inlist) => Some(Cow::Borrowed(inlist)),
Expr::BinaryExpr(BinaryExpr { left, op, right }) if *op == Operator::Eq => {
match (left.as_ref(), right.as_ref()) {
(Expr::Column(_), Expr::Literal(_)) => Some(Cow::Owned(InList {
expr: left.clone(),
list: vec![*right.clone()],
negated: false,
})),
(Expr::Literal(_), Expr::Column(_)) => Some(Cow::Owned(InList {
expr: right.clone(),
list: vec![*left.clone()],
negated: false,
})),
_ => None,
}
}
_ => None,
}
}
fn to_inlist(expr: Expr) -> Option<InList> {
match expr {
Expr::InList(inlist) => Some(inlist),
Expr::BinaryExpr(BinaryExpr {
left,
op: Operator::Eq,
right,
}) => match (left.as_ref(), right.as_ref()) {
(Expr::Column(_), Expr::Literal(_)) => Some(InList {
expr: left,
list: vec![*right],
negated: false,
}),
(Expr::Literal(_), Expr::Column(_)) => Some(InList {
expr: right,
list: vec![*left],
negated: false,
}),
_ => None,
},
_ => None,
}
}
fn inlist_union(mut l1: InList, l2: InList, negated: bool) -> Result<Expr> {
let l1_items: HashSet<_> = l1.list.iter().collect();
let keep_l2: Vec<_> = l2
.list
.into_iter()
.filter_map(|e| if l1_items.contains(&e) { None } else { Some(e) })
.collect();
l1.list.extend(keep_l2);
l1.negated = negated;
Ok(Expr::InList(l1))
}
fn inlist_intersection(mut l1: InList, l2: InList, negated: bool) -> Result<Expr> {
let l2_items = l2.list.iter().collect::<HashSet<_>>();
l1.list.retain(|e| l2_items.contains(e));
if l1.list.is_empty() {
return Ok(lit(negated));
}
Ok(Expr::InList(l1))
}
fn inlist_except(mut l1: InList, l2: InList) -> Result<Expr> {
let l2_items = l2.list.iter().collect::<HashSet<_>>();
l1.list.retain(|e| !l2_items.contains(e));
if l1.list.is_empty() {
return Ok(lit(false));
}
Ok(Expr::InList(l1))
}
#[cfg(test)]
mod tests {
use datafusion_common::{assert_contains, DFSchemaRef, ToDFSchema};
use datafusion_expr::{
function::{
AccumulatorArgs, AggregateFunctionSimplification,
WindowFunctionSimplification,
},
interval_arithmetic::Interval,
*,
};
use std::{
collections::HashMap,
ops::{BitAnd, BitOr, BitXor},
sync::Arc,
};
use crate::simplify_expressions::SimplifyContext;
use crate::test::test_table_scan_with_name;
use super::*;
#[test]
fn api_basic() {
let props = ExecutionProps::new();
let simplifier =
ExprSimplifier::new(SimplifyContext::new(&props).with_schema(test_schema()));
let expr = lit(1) + lit(2);
let expected = lit(3);
assert_eq!(expected, simplifier.simplify(expr).unwrap());
}
#[test]
fn basic_coercion() {
let schema = test_schema();
let props = ExecutionProps::new();
let simplifier = ExprSimplifier::new(
SimplifyContext::new(&props).with_schema(Arc::clone(&schema)),
);
let expr = (lit(1i64) + lit(2i32)).lt(col("i"));
let expected = lit(3i64).lt(col("i"));
let expr = simplifier.coerce(expr, &schema).unwrap();
assert_eq!(expected, simplifier.simplify(expr).unwrap());
}
fn test_schema() -> DFSchemaRef {
Schema::new(vec![
Field::new("i", DataType::Int64, false),
Field::new("b", DataType::Boolean, true),
])
.to_dfschema_ref()
.unwrap()
}
#[test]
fn simplify_and_constant_prop() {
let props = ExecutionProps::new();
let simplifier =
ExprSimplifier::new(SimplifyContext::new(&props).with_schema(test_schema()));
let expr = (col("i") * (lit(1) - lit(1))).gt(lit(0));
let expected = lit(false);
assert_eq!(expected, simplifier.simplify(expr).unwrap());
}
#[test]
fn simplify_and_constant_prop_with_case() {
let props = ExecutionProps::new();
let simplifier =
ExprSimplifier::new(SimplifyContext::new(&props).with_schema(test_schema()));
let expr = when(col("i").gt(lit(5)).and(lit(false)), col("i").gt(lit(5)))
.when(col("i").lt(lit(5)).and(lit(true)), col("i").lt(lit(5)))
.otherwise(lit(false))
.unwrap();
let expected = col("i").lt(lit(5));
assert_eq!(expected, simplifier.simplify(expr).unwrap());
}
#[test]
fn test_simplify_canonicalize() {
{
let expr = lit(1).lt(col("c2")).and(col("c2").gt(lit(1)));
let expected = col("c2").gt(lit(1));
assert_eq!(simplify(expr), expected);
}
{
let expr = col("c1").lt(col("c2")).and(col("c2").gt(col("c1")));
let expected = col("c2").gt(col("c1"));
assert_eq!(simplify(expr), expected);
}
{
let expr = col("c1")
.eq(lit(1))
.and(lit(1).eq(col("c1")))
.and(col("c1").eq(lit(3)));
let expected = col("c1").eq(lit(1)).and(col("c1").eq(lit(3)));
assert_eq!(simplify(expr), expected);
}
{
let expr = col("c1")
.eq(col("c2"))
.and(col("c1").gt(lit(5)))
.and(col("c2").eq(col("c1")));
let expected = col("c2").eq(col("c1")).and(col("c1").gt(lit(5)));
assert_eq!(simplify(expr), expected);
}
{
let expr = col("c1")
.eq(lit(1))
.and(col("c2").gt(lit(3)).or(lit(3).lt(col("c2"))));
let expected = col("c1").eq(lit(1)).and(col("c2").gt(lit(3)));
assert_eq!(simplify(expr), expected);
}
{
let expr = col("c1").lt(lit(5)).and(col("c1").gt_eq(lit(5)));
let expected = col("c1").lt(lit(5)).and(col("c1").gt_eq(lit(5)));
assert_eq!(simplify(expr), expected);
}
{
let expr = col("c1").lt(lit(5)).and(col("c1").gt_eq(lit(5)));
let expected = col("c1").lt(lit(5)).and(col("c1").gt_eq(lit(5)));
assert_eq!(simplify(expr), expected);
}
{
let expr = col("c1").gt(col("c2")).and(col("c1").gt(col("c2")));
let expected = col("c2").lt(col("c1"));
assert_eq!(simplify(expr), expected);
}
}
#[test]
fn test_simplify_or_true() {
let expr_a = col("c2").or(lit(true));
let expr_b = lit(true).or(col("c2"));
let expected = lit(true);
assert_eq!(simplify(expr_a), expected);
assert_eq!(simplify(expr_b), expected);
}
#[test]
fn test_simplify_or_false() {
let expr_a = lit(false).or(col("c2"));
let expr_b = col("c2").or(lit(false));
let expected = col("c2");
assert_eq!(simplify(expr_a), expected);
assert_eq!(simplify(expr_b), expected);
}
#[test]
fn test_simplify_or_same() {
let expr = col("c2").or(col("c2"));
let expected = col("c2");
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_or_not_self() {
let expr_a = col("c2_non_null").or(col("c2_non_null").not());
let expr_b = col("c2_non_null").not().or(col("c2_non_null"));
let expected = lit(true);
assert_eq!(simplify(expr_a), expected);
assert_eq!(simplify(expr_b), expected);
}
#[test]
fn test_simplify_and_false() {
let expr_a = lit(false).and(col("c2"));
let expr_b = col("c2").and(lit(false));
let expected = lit(false);
assert_eq!(simplify(expr_a), expected);
assert_eq!(simplify(expr_b), expected);
}
#[test]
fn test_simplify_and_same() {
let expr = col("c2").and(col("c2"));
let expected = col("c2");
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_and_true() {
let expr_a = lit(true).and(col("c2"));
let expr_b = col("c2").and(lit(true));
let expected = col("c2");
assert_eq!(simplify(expr_a), expected);
assert_eq!(simplify(expr_b), expected);
}
#[test]
fn test_simplify_and_not_self() {
let expr_a = col("c2_non_null").and(col("c2_non_null").not());
let expr_b = col("c2_non_null").not().and(col("c2_non_null"));
let expected = lit(false);
assert_eq!(simplify(expr_a), expected);
assert_eq!(simplify(expr_b), expected);
}
#[test]
fn test_simplify_multiply_by_one() {
let expr_a = col("c2") * lit(1);
let expr_b = lit(1) * col("c2");
let expected = col("c2");
assert_eq!(simplify(expr_a), expected);
assert_eq!(simplify(expr_b), expected);
let expr = col("c2") * lit(ScalarValue::Decimal128(Some(10000000000), 38, 10));
assert_eq!(simplify(expr), expected);
let expr = lit(ScalarValue::Decimal128(Some(10000000000), 31, 10)) * col("c2");
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_multiply_by_null() {
let null = Expr::Literal(ScalarValue::Null);
{
let expr = col("c2") * null.clone();
assert_eq!(simplify(expr), null);
}
{
let expr = null.clone() * col("c2");
assert_eq!(simplify(expr), null);
}
}
#[test]
fn test_simplify_multiply_by_zero() {
{
let expr_a = col("c2") * lit(0);
let expr_b = lit(0) * col("c2");
assert_eq!(simplify(expr_a.clone()), expr_a);
assert_eq!(simplify(expr_b.clone()), expr_b);
}
{
let expr = lit(0) * col("c2_non_null");
assert_eq!(simplify(expr), lit(0));
}
{
let expr = col("c2_non_null") * lit(0);
assert_eq!(simplify(expr), lit(0));
}
{
let expr = col("c2_non_null") * lit(ScalarValue::Decimal128(Some(0), 31, 10));
assert_eq!(
simplify(expr),
lit(ScalarValue::Decimal128(Some(0), 31, 10))
);
let expr = binary_expr(
lit(ScalarValue::Decimal128(Some(0), 31, 10)),
Operator::Multiply,
col("c2_non_null"),
);
assert_eq!(
simplify(expr),
lit(ScalarValue::Decimal128(Some(0), 31, 10))
);
}
}
#[test]
fn test_simplify_divide_by_one() {
let expr = binary_expr(col("c2"), Operator::Divide, lit(1));
let expected = col("c2");
assert_eq!(simplify(expr), expected);
let expr = col("c2") / lit(ScalarValue::Decimal128(Some(10000000000), 31, 10));
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_divide_null() {
let null = lit(ScalarValue::Null);
{
let expr = col("c") / null.clone();
assert_eq!(simplify(expr), null);
}
{
let expr = null.clone() / col("c");
assert_eq!(simplify(expr), null);
}
}
#[test]
fn test_simplify_divide_by_same() {
let expr = col("c2") / col("c2");
let expected = expr.clone();
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_modulo_by_null() {
let null = lit(ScalarValue::Null);
{
let expr = col("c2") % null.clone();
assert_eq!(simplify(expr), null);
}
{
let expr = null.clone() % col("c2");
assert_eq!(simplify(expr), null);
}
}
#[test]
fn test_simplify_modulo_by_one() {
let expr = col("c2") % lit(1);
let expected = expr.clone();
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_divide_zero_by_zero() {
let expr = lit(0) / lit(0);
let expected = expr.clone();
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_divide_by_zero() {
let expr = col("c2_non_null") / lit(0);
let expected = expr.clone();
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_modulo_by_one_non_null() {
let expr = col("c2_non_null") % lit(1);
let expected = lit(0);
assert_eq!(simplify(expr), expected);
let expr =
col("c2_non_null") % lit(ScalarValue::Decimal128(Some(10000000000), 31, 10));
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_bitwise_xor_by_null() {
let null = lit(ScalarValue::Null);
{
let expr = col("c2") ^ null.clone();
assert_eq!(simplify(expr), null);
}
{
let expr = null.clone() ^ col("c2");
assert_eq!(simplify(expr), null);
}
}
#[test]
fn test_simplify_bitwise_shift_right_by_null() {
let null = lit(ScalarValue::Null);
{
let expr = col("c2") >> null.clone();
assert_eq!(simplify(expr), null);
}
{
let expr = null.clone() >> col("c2");
assert_eq!(simplify(expr), null);
}
}
#[test]
fn test_simplify_bitwise_shift_left_by_null() {
let null = lit(ScalarValue::Null);
{
let expr = col("c2") << null.clone();
assert_eq!(simplify(expr), null);
}
{
let expr = null.clone() << col("c2");
assert_eq!(simplify(expr), null);
}
}
#[test]
fn test_simplify_bitwise_and_by_zero() {
{
let expr = col("c2_non_null") & lit(0);
assert_eq!(simplify(expr), lit(0));
}
{
let expr = lit(0) & col("c2_non_null");
assert_eq!(simplify(expr), lit(0));
}
}
#[test]
fn test_simplify_bitwise_or_by_zero() {
{
let expr = col("c2_non_null") | lit(0);
assert_eq!(simplify(expr), col("c2_non_null"));
}
{
let expr = lit(0) | col("c2_non_null");
assert_eq!(simplify(expr), col("c2_non_null"));
}
}
#[test]
fn test_simplify_bitwise_xor_by_zero() {
{
let expr = col("c2_non_null") ^ lit(0);
assert_eq!(simplify(expr), col("c2_non_null"));
}
{
let expr = lit(0) ^ col("c2_non_null");
assert_eq!(simplify(expr), col("c2_non_null"));
}
}
#[test]
fn test_simplify_bitwise_bitwise_shift_right_by_zero() {
{
let expr = col("c2_non_null") >> lit(0);
assert_eq!(simplify(expr), col("c2_non_null"));
}
}
#[test]
fn test_simplify_bitwise_bitwise_shift_left_by_zero() {
{
let expr = col("c2_non_null") << lit(0);
assert_eq!(simplify(expr), col("c2_non_null"));
}
}
#[test]
fn test_simplify_bitwise_and_by_null() {
let null = lit(ScalarValue::Null);
{
let expr = col("c2") & null.clone();
assert_eq!(simplify(expr), null);
}
{
let expr = null.clone() & col("c2");
assert_eq!(simplify(expr), null);
}
}
#[test]
fn test_simplify_composed_bitwise_and() {
let expr = bitwise_and(
bitwise_and(col("c2").gt(lit(5)), col("c1").lt(lit(6))),
col("c2").gt(lit(5)),
);
let expected = bitwise_and(col("c2").gt(lit(5)), col("c1").lt(lit(6)));
assert_eq!(simplify(expr), expected);
let expr = bitwise_and(
col("c2").gt(lit(5)),
bitwise_and(col("c2").gt(lit(5)), col("c1").lt(lit(6))),
);
let expected = bitwise_and(col("c2").gt(lit(5)), col("c1").lt(lit(6)));
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_composed_bitwise_or() {
let expr = bitwise_or(
bitwise_or(col("c2").gt(lit(5)), col("c1").lt(lit(6))),
col("c2").gt(lit(5)),
);
let expected = bitwise_or(col("c2").gt(lit(5)), col("c1").lt(lit(6)));
assert_eq!(simplify(expr), expected);
let expr = bitwise_or(
col("c2").gt(lit(5)),
bitwise_or(col("c2").gt(lit(5)), col("c1").lt(lit(6))),
);
let expected = bitwise_or(col("c2").gt(lit(5)), col("c1").lt(lit(6)));
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_composed_bitwise_xor() {
let expr = bitwise_xor(
col("c2"),
bitwise_xor(
bitwise_xor(col("c2"), bitwise_or(col("c2"), col("c1"))),
bitwise_and(col("c1"), col("c2")),
),
);
let expected = bitwise_xor(
bitwise_or(col("c2"), col("c1")),
bitwise_and(col("c1"), col("c2")),
);
assert_eq!(simplify(expr), expected);
let expr = bitwise_xor(
col("c2"),
bitwise_xor(
bitwise_xor(col("c2"), bitwise_or(col("c2"), col("c1"))),
bitwise_xor(bitwise_and(col("c1"), col("c2")), col("c2")),
),
);
let expected = bitwise_xor(
col("c2"),
bitwise_xor(
bitwise_or(col("c2"), col("c1")),
bitwise_and(col("c1"), col("c2")),
),
);
assert_eq!(simplify(expr), expected);
let expr = bitwise_xor(
bitwise_xor(
bitwise_xor(col("c2"), bitwise_or(col("c2"), col("c1"))),
bitwise_and(col("c1"), col("c2")),
),
col("c2"),
);
let expected = bitwise_xor(
bitwise_or(col("c2"), col("c1")),
bitwise_and(col("c1"), col("c2")),
);
assert_eq!(simplify(expr), expected);
let expr = bitwise_xor(
bitwise_xor(
bitwise_xor(col("c2"), bitwise_or(col("c2"), col("c1"))),
bitwise_xor(bitwise_and(col("c1"), col("c2")), col("c2")),
),
col("c2"),
);
let expected = bitwise_xor(
bitwise_xor(
bitwise_or(col("c2"), col("c1")),
bitwise_and(col("c1"), col("c2")),
),
col("c2"),
);
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_negated_bitwise_and() {
let expr = (-col("c4_non_null")) & col("c4_non_null");
let expected = lit(0u32);
assert_eq!(simplify(expr), expected);
let expr = col("c4_non_null") & (-col("c4_non_null"));
let expected = lit(0u32);
assert_eq!(simplify(expr), expected);
let expr = (-col("c3_non_null")) & col("c3_non_null");
let expected = lit(0i64);
assert_eq!(simplify(expr), expected);
let expr = col("c3_non_null") & (-col("c3_non_null"));
let expected = lit(0i64);
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_negated_bitwise_or() {
let expr = (-col("c4_non_null")) | col("c4_non_null");
let expected = lit(-1i32);
assert_eq!(simplify(expr), expected);
let expr = col("c4_non_null") | (-col("c4_non_null"));
let expected = lit(-1i32);
assert_eq!(simplify(expr), expected);
let expr = (-col("c3_non_null")) | col("c3_non_null");
let expected = lit(-1i64);
assert_eq!(simplify(expr), expected);
let expr = col("c3_non_null") | (-col("c3_non_null"));
let expected = lit(-1i64);
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_negated_bitwise_xor() {
let expr = (-col("c4_non_null")) ^ col("c4_non_null");
let expected = lit(-1i32);
assert_eq!(simplify(expr), expected);
let expr = col("c4_non_null") ^ (-col("c4_non_null"));
let expected = lit(-1i32);
assert_eq!(simplify(expr), expected);
let expr = (-col("c3_non_null")) ^ col("c3_non_null");
let expected = lit(-1i64);
assert_eq!(simplify(expr), expected);
let expr = col("c3_non_null") ^ (-col("c3_non_null"));
let expected = lit(-1i64);
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_bitwise_and_or() {
let expr = bitwise_and(
col("c2_non_null").lt(lit(3)),
bitwise_or(col("c2_non_null").lt(lit(3)), col("c1_non_null")),
);
let expected = col("c2_non_null").lt(lit(3));
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_bitwise_or_and() {
let expr = bitwise_or(
col("c2_non_null").lt(lit(3)),
bitwise_and(col("c2_non_null").lt(lit(3)), col("c1_non_null")),
);
let expected = col("c2_non_null").lt(lit(3));
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_simple_bitwise_and() {
let expr = (col("c2").gt(lit(5))).bitand(col("c2").gt(lit(5)));
let expected = col("c2").gt(lit(5));
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_simple_bitwise_or() {
let expr = (col("c2").gt(lit(5))).bitor(col("c2").gt(lit(5)));
let expected = col("c2").gt(lit(5));
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_simple_bitwise_xor() {
let expr = (col("c4")).bitxor(col("c4"));
let expected = lit(0u32);
assert_eq!(simplify(expr), expected);
let expr = col("c3").bitxor(col("c3"));
let expected = lit(0i64);
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_modulo_by_zero_non_null() {
let expr = col("c2_non_null") % lit(0);
let expected = expr.clone();
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_simple_and() {
let expr = (col("c2").gt(lit(5))).and(col("c2").gt(lit(5)));
let expected = col("c2").gt(lit(5));
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_composed_and() {
let expr = and(
and(col("c2").gt(lit(5)), col("c1").lt(lit(6))),
col("c2").gt(lit(5)),
);
let expected = and(col("c2").gt(lit(5)), col("c1").lt(lit(6)));
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_negated_and() {
let expr = and(col("c2").gt(lit(5)), Expr::not(col("c2").gt(lit(5))));
let expected = col("c2").gt(lit(5)).and(col("c2").lt_eq(lit(5)));
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_or_and() {
let l = col("c2").gt(lit(5));
let r = and(col("c1").lt(lit(6)), col("c2").gt(lit(5)));
let expr = or(l.clone(), r.clone());
let expected = expr.clone();
assert_eq!(simplify(expr), expected);
let expr = or(l, r);
let expected = expr.clone();
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_or_and_non_null() {
let l = col("c2_non_null").gt(lit(5));
let r = and(col("c1_non_null").lt(lit(6)), col("c2_non_null").gt(lit(5)));
let expr = or(l.clone(), r.clone());
let expected = col("c2_non_null").gt(lit(5));
assert_eq!(simplify(expr), expected);
let expr = or(l, r);
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_and_or() {
let l = col("c2").gt(lit(5));
let r = or(col("c1").lt(lit(6)), col("c2").gt(lit(5)));
let expr = and(l.clone(), r.clone());
let expected = expr.clone();
assert_eq!(simplify(expr), expected);
let expr = and(l, r);
let expected = expr.clone();
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_and_or_non_null() {
let l = col("c2_non_null").gt(lit(5));
let r = or(col("c1_non_null").lt(lit(6)), col("c2_non_null").gt(lit(5)));
let expr = and(l.clone(), r.clone());
let expected = col("c2_non_null").gt(lit(5));
assert_eq!(simplify(expr), expected);
let expr = and(l, r);
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_by_de_morgan_laws() {
let expr = and(col("c3"), col("c4")).not();
let expected = or(col("c3").not(), col("c4").not());
assert_eq!(simplify(expr), expected);
let expr = or(col("c3"), col("c4")).not();
let expected = and(col("c3").not(), col("c4").not());
assert_eq!(simplify(expr), expected);
let expr = col("c3").not().not();
let expected = col("c3");
assert_eq!(simplify(expr), expected);
let expr = -bitwise_and(col("c3"), col("c4"));
let expected = bitwise_or(-col("c3"), -col("c4"));
assert_eq!(simplify(expr), expected);
let expr = -bitwise_or(col("c3"), col("c4"));
let expected = bitwise_and(-col("c3"), -col("c4"));
assert_eq!(simplify(expr), expected);
let expr = -(-col("c3"));
let expected = col("c3");
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_null_and_false() {
let expr = and(lit_bool_null(), lit(false));
let expr_eq = lit(false);
assert_eq!(simplify(expr), expr_eq);
}
#[test]
fn test_simplify_divide_null_by_null() {
let null = lit(ScalarValue::Int32(None));
let expr_plus = null.clone() / null.clone();
let expr_eq = null;
assert_eq!(simplify(expr_plus), expr_eq);
}
#[test]
fn test_simplify_simplify_arithmetic_expr() {
let expr_plus = lit(1) + lit(1);
assert_eq!(simplify(expr_plus), lit(2));
}
#[test]
fn test_simplify_simplify_eq_expr() {
let expr_eq = binary_expr(lit(1), Operator::Eq, lit(1));
assert_eq!(simplify(expr_eq), lit(true));
}
#[test]
fn test_simplify_regex() {
assert_contains!(
try_simplify(regex_match(col("c1"), lit("foo{")))
.unwrap_err()
.to_string(),
"regex parse error"
);
assert_no_change(regex_match(col("c1"), lit("foo.*")));
assert_no_change(regex_match(col("c1"), lit("(foo)")));
assert_no_change(regex_match(col("c1"), lit("%")));
assert_no_change(regex_match(col("c1"), lit("_")));
assert_no_change(regex_match(col("c1"), lit("f%o")));
assert_no_change(regex_match(col("c1"), lit("^f%o")));
assert_no_change(regex_match(col("c1"), lit("f_o")));
assert_change(regex_match(col("c1"), lit("")), lit(true));
assert_change(regex_not_match(col("c1"), lit("")), lit(false));
assert_change(regex_imatch(col("c1"), lit("")), lit(true));
assert_change(regex_not_imatch(col("c1"), lit("")), lit(false));
assert_change(regex_match(col("c1"), lit("x")), like(col("c1"), "%x%"));
assert_change(regex_match(col("c1"), lit("foo")), like(col("c1"), "%foo%"));
assert_change(regex_match(col("c1"), lit("^$")), col("c1").eq(lit("")));
assert_change(
regex_not_match(col("c1"), lit("^$")),
col("c1").not_eq(lit("")),
);
assert_change(
regex_match(col("c1"), lit("^foo$")),
col("c1").eq(lit("foo")),
);
assert_change(
regex_not_match(col("c1"), lit("^foo$")),
col("c1").not_eq(lit("foo")),
);
assert_change(
regex_match(col("c1"), lit("^(foo|bar)$")),
col("c1").eq(lit("foo")).or(col("c1").eq(lit("bar"))),
);
assert_change(
regex_not_match(col("c1"), lit("^(foo|bar)$")),
col("c1")
.not_eq(lit("foo"))
.and(col("c1").not_eq(lit("bar"))),
);
assert_change(
regex_match(col("c1"), lit("^(foo)$")),
col("c1").eq(lit("foo")),
);
assert_change(
regex_match(col("c1"), lit("^(foo|bar|baz)$")),
((col("c1").eq(lit("foo"))).or(col("c1").eq(lit("bar"))))
.or(col("c1").eq(lit("baz"))),
);
assert_change(
regex_match(col("c1"), lit("^(foo|bar|baz|qux)$")),
col("c1")
.in_list(vec![lit("foo"), lit("bar"), lit("baz"), lit("qux")], false),
);
assert_change(
regex_match(col("c1"), lit("^(fo_o)$")),
col("c1").eq(lit("fo_o")),
);
assert_change(
regex_match(col("c1"), lit("^(fo_o)$")),
col("c1").eq(lit("fo_o")),
);
assert_change(
regex_match(col("c1"), lit("^(fo_o|ba_r)$")),
col("c1").eq(lit("fo_o")).or(col("c1").eq(lit("ba_r"))),
);
assert_change(
regex_not_match(col("c1"), lit("^(fo_o|ba_r)$")),
col("c1")
.not_eq(lit("fo_o"))
.and(col("c1").not_eq(lit("ba_r"))),
);
assert_change(
regex_match(col("c1"), lit("^(fo_o|ba_r|ba_z)$")),
((col("c1").eq(lit("fo_o"))).or(col("c1").eq(lit("ba_r"))))
.or(col("c1").eq(lit("ba_z"))),
);
assert_change(
regex_match(col("c1"), lit("^(fo_o|ba_r|baz|qu_x)$")),
col("c1").in_list(
vec![lit("fo_o"), lit("ba_r"), lit("baz"), lit("qu_x")],
false,
),
);
assert_no_change(regex_match(col("c1"), lit("(foo|bar)")));
assert_no_change(regex_match(col("c1"), lit("(foo|bar)*")));
assert_no_change(regex_match(col("c1"), lit("(fo_o|b_ar)")));
assert_no_change(regex_match(col("c1"), lit("(foo|ba_r)*")));
assert_no_change(regex_match(col("c1"), lit("(fo_o|ba_r)*")));
assert_no_change(regex_match(col("c1"), lit("^(foo|bar)*")));
assert_no_change(regex_match(col("c1"), lit("^(foo)(bar)$")));
assert_no_change(regex_match(col("c1"), lit("^")));
assert_no_change(regex_match(col("c1"), lit("$")));
assert_no_change(regex_match(col("c1"), lit("$^")));
assert_no_change(regex_match(col("c1"), lit("$foo^")));
assert_change(regex_match(col("c1"), lit("^foo")), like(col("c1"), "foo%"));
assert_change(regex_match(col("c1"), lit("foo$")), like(col("c1"), "%foo"));
assert_change(
regex_match(col("c1"), lit("^foo|bar$")),
like(col("c1"), "foo%").or(like(col("c1"), "%bar")),
);
assert_change(
regex_match(col("c1"), lit("foo|bar|baz")),
like(col("c1"), "%foo%")
.or(like(col("c1"), "%bar%"))
.or(like(col("c1"), "%baz%")),
);
assert_change(
regex_match(col("c1"), lit("foo|x|baz")),
like(col("c1"), "%foo%")
.or(like(col("c1"), "%x%"))
.or(like(col("c1"), "%baz%")),
);
assert_change(
regex_not_match(col("c1"), lit("foo|bar|baz")),
not_like(col("c1"), "%foo%")
.and(not_like(col("c1"), "%bar%"))
.and(not_like(col("c1"), "%baz%")),
);
assert_change(
regex_match(col("c1"), lit("foo|^x$|baz")),
like(col("c1"), "%foo%")
.or(col("c1").eq(lit("x")))
.or(like(col("c1"), "%baz%")),
);
assert_change(
regex_not_match(col("c1"), lit("foo|^bar$|baz")),
not_like(col("c1"), "%foo%")
.and(col("c1").not_eq(lit("bar")))
.and(not_like(col("c1"), "%baz%")),
);
assert_no_change(regex_match(col("c1"), lit("foo|bar|baz|blarg|bozo|etc")));
}
#[track_caller]
fn assert_no_change(expr: Expr) {
let optimized = simplify(expr.clone());
assert_eq!(expr, optimized);
}
#[track_caller]
fn assert_change(expr: Expr, expected: Expr) {
let optimized = simplify(expr);
assert_eq!(optimized, expected);
}
fn regex_match(left: Expr, right: Expr) -> Expr {
Expr::BinaryExpr(BinaryExpr {
left: Box::new(left),
op: Operator::RegexMatch,
right: Box::new(right),
})
}
fn regex_not_match(left: Expr, right: Expr) -> Expr {
Expr::BinaryExpr(BinaryExpr {
left: Box::new(left),
op: Operator::RegexNotMatch,
right: Box::new(right),
})
}
fn regex_imatch(left: Expr, right: Expr) -> Expr {
Expr::BinaryExpr(BinaryExpr {
left: Box::new(left),
op: Operator::RegexIMatch,
right: Box::new(right),
})
}
fn regex_not_imatch(left: Expr, right: Expr) -> Expr {
Expr::BinaryExpr(BinaryExpr {
left: Box::new(left),
op: Operator::RegexNotIMatch,
right: Box::new(right),
})
}
fn like(expr: Expr, pattern: &str) -> Expr {
Expr::Like(Like {
negated: false,
expr: Box::new(expr),
pattern: Box::new(lit(pattern)),
escape_char: None,
case_insensitive: false,
})
}
fn not_like(expr: Expr, pattern: &str) -> Expr {
Expr::Like(Like {
negated: true,
expr: Box::new(expr),
pattern: Box::new(lit(pattern)),
escape_char: None,
case_insensitive: false,
})
}
fn ilike(expr: Expr, pattern: &str) -> Expr {
Expr::Like(Like {
negated: false,
expr: Box::new(expr),
pattern: Box::new(lit(pattern)),
escape_char: None,
case_insensitive: true,
})
}
fn not_ilike(expr: Expr, pattern: &str) -> Expr {
Expr::Like(Like {
negated: true,
expr: Box::new(expr),
pattern: Box::new(lit(pattern)),
escape_char: None,
case_insensitive: true,
})
}
fn try_simplify(expr: Expr) -> Result<Expr> {
let schema = expr_test_schema();
let execution_props = ExecutionProps::new();
let simplifier = ExprSimplifier::new(
SimplifyContext::new(&execution_props).with_schema(schema),
);
simplifier.simplify(expr)
}
fn simplify(expr: Expr) -> Expr {
try_simplify(expr).unwrap()
}
fn try_simplify_with_cycle_count(expr: Expr) -> Result<(Expr, u32)> {
let schema = expr_test_schema();
let execution_props = ExecutionProps::new();
let simplifier = ExprSimplifier::new(
SimplifyContext::new(&execution_props).with_schema(schema),
);
simplifier.simplify_with_cycle_count(expr)
}
fn simplify_with_cycle_count(expr: Expr) -> (Expr, u32) {
try_simplify_with_cycle_count(expr).unwrap()
}
fn simplify_with_guarantee(
expr: Expr,
guarantees: Vec<(Expr, NullableInterval)>,
) -> Expr {
let schema = expr_test_schema();
let execution_props = ExecutionProps::new();
let simplifier = ExprSimplifier::new(
SimplifyContext::new(&execution_props).with_schema(schema),
)
.with_guarantees(guarantees);
simplifier.simplify(expr).unwrap()
}
fn expr_test_schema() -> DFSchemaRef {
Arc::new(
DFSchema::from_unqualified_fields(
vec![
Field::new("c1", DataType::Utf8, true),
Field::new("c2", DataType::Boolean, true),
Field::new("c3", DataType::Int64, true),
Field::new("c4", DataType::UInt32, true),
Field::new("c1_non_null", DataType::Utf8, false),
Field::new("c2_non_null", DataType::Boolean, false),
Field::new("c3_non_null", DataType::Int64, false),
Field::new("c4_non_null", DataType::UInt32, false),
]
.into(),
HashMap::new(),
)
.unwrap(),
)
}
#[test]
fn simplify_expr_null_comparison() {
assert_eq!(
simplify(lit(true).eq(lit(ScalarValue::Boolean(None)))),
lit(ScalarValue::Boolean(None)),
);
assert_eq!(
simplify(
lit(ScalarValue::Boolean(None)).not_eq(lit(ScalarValue::Boolean(None)))
),
lit(ScalarValue::Boolean(None)),
);
assert_eq!(
simplify(col("c2").not_eq(lit(ScalarValue::Boolean(None)))),
lit(ScalarValue::Boolean(None)),
);
assert_eq!(
simplify(lit(ScalarValue::Boolean(None)).eq(col("c2"))),
lit(ScalarValue::Boolean(None)),
);
}
#[test]
fn simplify_expr_is_not_null() {
assert_eq!(
simplify(Expr::IsNotNull(Box::new(col("c1")))),
Expr::IsNotNull(Box::new(col("c1")))
);
assert_eq!(
simplify(Expr::IsNotNull(Box::new(col("c1_non_null")))),
lit(true)
);
}
#[test]
fn simplify_expr_is_null() {
assert_eq!(
simplify(Expr::IsNull(Box::new(col("c1")))),
Expr::IsNull(Box::new(col("c1")))
);
assert_eq!(
simplify(Expr::IsNull(Box::new(col("c1_non_null")))),
lit(false)
);
}
#[test]
fn simplify_expr_is_unknown() {
assert_eq!(simplify(col("c2").is_unknown()), col("c2").is_unknown(),);
assert_eq!(simplify(col("c2_non_null").is_unknown()), lit(false));
}
#[test]
fn simplify_expr_is_not_known() {
assert_eq!(
simplify(col("c2").is_not_unknown()),
col("c2").is_not_unknown()
);
assert_eq!(simplify(col("c2_non_null").is_not_unknown()), lit(true));
}
#[test]
fn simplify_expr_eq() {
let schema = expr_test_schema();
assert_eq!(col("c2").get_type(&schema).unwrap(), DataType::Boolean);
assert_eq!(simplify(lit(true).eq(lit(true))), lit(true));
assert_eq!(simplify(lit(true).eq(lit(false))), lit(false),);
assert_eq!(simplify(col("c2").eq(lit(true))), col("c2"));
assert_eq!(simplify(col("c2").eq(lit(false))), col("c2").not(),);
}
#[test]
fn simplify_expr_eq_skip_nonboolean_type() {
let schema = expr_test_schema();
assert_eq!(col("c1").get_type(&schema).unwrap(), DataType::Utf8);
assert_eq!(simplify(col("c1").eq(lit("foo"))), col("c1").eq(lit("foo")),);
}
#[test]
fn simplify_expr_not_eq() {
let schema = expr_test_schema();
assert_eq!(col("c2").get_type(&schema).unwrap(), DataType::Boolean);
assert_eq!(simplify(col("c2").not_eq(lit(true))), col("c2").not(),);
assert_eq!(simplify(col("c2").not_eq(lit(false))), col("c2"),);
assert_eq!(simplify(lit(true).not_eq(lit(true))), lit(false),);
assert_eq!(simplify(lit(true).not_eq(lit(false))), lit(true),);
}
#[test]
fn simplify_expr_not_eq_skip_nonboolean_type() {
let schema = expr_test_schema();
assert_eq!(col("c1").get_type(&schema).unwrap(), DataType::Utf8);
assert_eq!(
simplify(col("c1").not_eq(lit("foo"))),
col("c1").not_eq(lit("foo")),
);
}
#[test]
fn simplify_expr_case_when_then_else() {
assert_eq!(
simplify(Expr::Case(Case::new(
None,
vec![(
Box::new(col("c2").not_eq(lit(false))),
Box::new(lit("ok").eq(lit("not_ok"))),
)],
Some(Box::new(col("c2").eq(lit(true)))),
))),
col("c2").not().and(col("c2")) );
assert_eq!(
simplify(simplify(Expr::Case(Case::new(
None,
vec![(
Box::new(col("c2").not_eq(lit(false))),
Box::new(lit("ok").eq(lit("ok"))),
)],
Some(Box::new(col("c2").eq(lit(true)))),
)))),
col("c2").or(col("c2").not().and(col("c2"))) );
assert_eq!(
simplify(simplify(Expr::Case(Case::new(
None,
vec![(Box::new(col("c2").is_null()), Box::new(lit(true)),)],
Some(Box::new(col("c2"))),
)))),
col("c2")
.is_null()
.or(col("c2").is_not_null().and(col("c2")))
);
assert_eq!(
simplify(simplify(Expr::Case(Case::new(
None,
vec![
(Box::new(col("c1")), Box::new(lit(true)),),
(Box::new(col("c2")), Box::new(lit(false)),),
],
Some(Box::new(lit(true))),
)))),
col("c1").or(col("c1").not().and(col("c2").not()))
);
assert_eq!(
simplify(simplify(Expr::Case(Case::new(
None,
vec![
(Box::new(col("c1")), Box::new(lit(true)),),
(Box::new(col("c2")), Box::new(lit(false)),),
],
Some(Box::new(lit(true))),
)))),
col("c1").or(col("c1").not().and(col("c2").not()))
);
}
#[test]
fn simplify_expr_bool_or() {
assert_eq!(simplify(col("c2").or(lit(true))), lit(true),);
assert_eq!(simplify(col("c2").or(lit(false))), col("c2"),);
assert_eq!(simplify(lit(true).or(lit_bool_null())), lit(true),);
assert_eq!(simplify(lit_bool_null().or(lit(true))), lit(true),);
assert_eq!(simplify(lit(false).or(lit_bool_null())), lit_bool_null(),);
assert_eq!(simplify(lit_bool_null().or(lit(false))), lit_bool_null(),);
let expr = col("c1").between(lit(0), lit(10));
let expr = expr.or(lit_bool_null());
let result = simplify(expr);
let expected_expr = or(
and(col("c1").gt_eq(lit(0)), col("c1").lt_eq(lit(10))),
lit_bool_null(),
);
assert_eq!(expected_expr, result);
}
#[test]
fn simplify_inlist() {
assert_eq!(simplify(in_list(col("c1"), vec![], false)), lit(false));
assert_eq!(simplify(in_list(col("c1"), vec![], true)), lit(true));
assert_eq!(
simplify(in_list(lit_bool_null(), vec![col("c1"), lit(1)], false)),
lit_bool_null()
);
assert_eq!(
simplify(in_list(lit_bool_null(), vec![col("c1"), lit(1)], true)),
lit_bool_null()
);
assert_eq!(
simplify(in_list(col("c1"), vec![lit(1)], false)),
col("c1").eq(lit(1))
);
assert_eq!(
simplify(in_list(col("c1"), vec![lit(1)], true)),
col("c1").not_eq(lit(1))
);
assert_eq!(
simplify(in_list(col("c1") * lit(10), vec![lit(2)], false)),
(col("c1") * lit(10)).eq(lit(2))
);
assert_eq!(
simplify(in_list(col("c1"), vec![lit(1), lit(2)], false)),
col("c1").eq(lit(1)).or(col("c1").eq(lit(2)))
);
assert_eq!(
simplify(in_list(col("c1"), vec![lit(1), lit(2)], true)),
col("c1").not_eq(lit(1)).and(col("c1").not_eq(lit(2)))
);
let subquery = Arc::new(test_table_scan_with_name("test").unwrap());
assert_eq!(
simplify(in_list(
col("c1"),
vec![scalar_subquery(Arc::clone(&subquery))],
false
)),
in_subquery(col("c1"), Arc::clone(&subquery))
);
assert_eq!(
simplify(in_list(
col("c1"),
vec![scalar_subquery(Arc::clone(&subquery))],
true
)),
not_in_subquery(col("c1"), subquery)
);
let subquery1 =
scalar_subquery(Arc::new(test_table_scan_with_name("test1").unwrap()));
let subquery2 =
scalar_subquery(Arc::new(test_table_scan_with_name("test2").unwrap()));
assert_eq!(
simplify(in_list(
col("c1"),
vec![subquery1.clone(), subquery2.clone()],
true
)),
col("c1")
.not_eq(subquery1.clone())
.and(col("c1").not_eq(subquery2.clone()))
);
assert_eq!(
simplify(in_list(
col("c1"),
vec![subquery1.clone(), subquery2.clone()],
false
)),
col("c1").eq(subquery1).or(col("c1").eq(subquery2))
);
let expr = in_list(col("c1"), vec![lit(1), lit(2), lit(3), lit(4)], false).and(
in_list(col("c1"), vec![lit(5), lit(6), lit(7), lit(8)], false),
);
assert_eq!(simplify(expr.clone()), lit(false));
let expr = in_list(col("c1"), vec![lit(1), lit(2), lit(3), lit(4)], false).and(
in_list(col("c1"), vec![lit(4), lit(5), lit(6), lit(7)], false),
);
assert_eq!(simplify(expr.clone()), col("c1").eq(lit(4)));
let expr = in_list(col("c1"), vec![lit(1), lit(2), lit(3), lit(4)], true).or(
in_list(col("c1"), vec![lit(5), lit(6), lit(7), lit(8)], true),
);
assert_eq!(simplify(expr.clone()), lit(true));
let expr = in_list(col("c1"), vec![lit(1), lit(2), lit(3), lit(4)], true).or(
in_list(col("c1"), vec![lit(4), lit(5), lit(6), lit(7)], true),
);
assert_eq!(simplify(expr.clone()), col("c1").not_eq(lit(4)));
let expr = in_list(col("c1"), vec![lit(1), lit(2), lit(3), lit(4)], true).and(
in_list(col("c1"), vec![lit(4), lit(5), lit(6), lit(7)], true),
);
assert_eq!(
simplify(expr.clone()),
in_list(
col("c1"),
vec![lit(1), lit(2), lit(3), lit(4), lit(5), lit(6), lit(7)],
true
)
);
let expr = in_list(col("c1"), vec![lit(1), lit(2), lit(3), lit(4)], false).or(
in_list(col("c1"), vec![lit(2), lit(3), lit(4), lit(5)], false),
);
assert_eq!(
simplify(expr.clone()),
in_list(
col("c1"),
vec![lit(1), lit(2), lit(3), lit(4), lit(5)],
false
)
);
let expr = in_list(col("c1"), vec![lit(1), lit(2), lit(3)], false).and(in_list(
col("c1"),
vec![lit(1), lit(2), lit(3), lit(4), lit(5)],
true,
));
assert_eq!(simplify(expr.clone()), lit(false));
let expr =
in_list(col("c1"), vec![lit(1), lit(2), lit(3), lit(4)], true).and(in_list(
col("c1"),
vec![lit(1), lit(2), lit(3), lit(4), lit(5)],
false,
));
assert_eq!(simplify(expr.clone()), col("c1").eq(lit(5)));
let expr = in_list(col("c1"), vec![lit(1), lit(2), lit(3), lit(4)], false).and(
in_list(col("c1"), vec![lit(5), lit(6), lit(7), lit(8)], true),
);
assert_eq!(
simplify(expr.clone()),
in_list(col("c1"), vec![lit(1), lit(2), lit(3), lit(4)], false)
);
let expr = in_list(
col("c1"),
vec![lit(1), lit(2), lit(3), lit(4), lit(5), lit(6)],
false,
)
.and(in_list(
col("c1"),
vec![lit(1), lit(3), lit(5), lit(6)],
false,
))
.and(in_list(col("c1"), vec![lit(3), lit(6)], false));
assert_eq!(
simplify(expr.clone()),
col("c1").eq(lit(3)).or(col("c1").eq(lit(6)))
);
let expr = in_list(col("c1"), vec![lit(1), lit(2), lit(3), lit(4)], true).and(
in_list(col("c1"), vec![lit(5), lit(6), lit(7), lit(8)], false)
.and(in_list(
col("c1"),
vec![lit(3), lit(4), lit(5), lit(6)],
true,
))
.and(in_list(col("c1"), vec![lit(8), lit(9), lit(10)], false)),
);
assert_eq!(simplify(expr.clone()), col("c1").eq(lit(8)));
let expr =
in_list(col("c1"), vec![lit(1), lit(2), lit(3), lit(4)], true).or(col("c1")
.not_eq(lit(5))
.or(in_list(
col("c1"),
vec![lit(6), lit(7), lit(8), lit(9)],
true,
)));
assert_eq!(simplify(expr.clone()), expr);
}
#[test]
fn simplify_large_or() {
let expr = (0..5)
.map(|i| col("c1").eq(lit(i)))
.fold(lit(false), |acc, e| acc.or(e));
assert_eq!(
simplify(expr),
in_list(col("c1"), (0..5).map(lit).collect(), false),
);
}
#[test]
fn simplify_expr_bool_and() {
assert_eq!(simplify(col("c2").and(lit(true))), col("c2"),);
assert_eq!(simplify(col("c2").and(lit(false))), lit(false),);
assert_eq!(simplify(lit(true).and(lit_bool_null())), lit_bool_null(),);
assert_eq!(simplify(lit_bool_null().and(lit(true))), lit_bool_null(),);
assert_eq!(simplify(lit(false).and(lit_bool_null())), lit(false),);
assert_eq!(simplify(lit_bool_null().and(lit(false))), lit(false),);
let expr = col("c1").between(lit(0), lit(10));
let expr = expr.and(lit_bool_null());
let result = simplify(expr);
let expected_expr = and(
and(col("c1").gt_eq(lit(0)), col("c1").lt_eq(lit(10))),
lit_bool_null(),
);
assert_eq!(expected_expr, result);
}
#[test]
fn simplify_expr_between() {
let expr = col("c2").between(lit(3), lit(4));
assert_eq!(
simplify(expr),
and(col("c2").gt_eq(lit(3)), col("c2").lt_eq(lit(4)))
);
let expr = col("c2").not_between(lit(3), lit(4));
assert_eq!(
simplify(expr),
or(col("c2").lt(lit(3)), col("c2").gt(lit(4)))
);
}
#[test]
fn test_like_and_ilke() {
let expr = like(col("c1"), "%");
assert_eq!(simplify(expr), lit(true));
let expr = not_like(col("c1"), "%");
assert_eq!(simplify(expr), lit(false));
let expr = ilike(col("c1"), "%");
assert_eq!(simplify(expr), lit(true));
let expr = not_ilike(col("c1"), "%");
assert_eq!(simplify(expr), lit(false));
let null = lit(ScalarValue::Utf8(None));
let expr = like(null.clone(), "%");
assert_eq!(simplify(expr), lit_bool_null());
let expr = not_like(null.clone(), "%");
assert_eq!(simplify(expr), lit_bool_null());
let expr = ilike(null.clone(), "%");
assert_eq!(simplify(expr), lit_bool_null());
let expr = not_ilike(null, "%");
assert_eq!(simplify(expr), lit_bool_null());
}
#[test]
fn test_simplify_with_guarantee() {
let expr_x = col("c3").gt(lit(3_i64));
let expr_y = (col("c4") + lit(2_u32)).lt(lit(10_u32));
let expr_z = col("c1").in_list(vec![lit("a"), lit("b")], true);
let expr = expr_x.clone().and(expr_y.clone().or(expr_z));
let guarantees = vec![
(col("c3"), NullableInterval::from(ScalarValue::Int64(None))),
(col("c4"), NullableInterval::from(ScalarValue::UInt32(None))),
(col("c1"), NullableInterval::from(ScalarValue::Utf8(None))),
];
let output = simplify_with_guarantee(expr.clone(), guarantees);
assert_eq!(output, lit_bool_null());
let guarantees = vec![
(
col("c3"),
NullableInterval::NotNull {
values: Interval::make(Some(0_i64), Some(2_i64)).unwrap(),
},
),
(
col("c4"),
NullableInterval::from(ScalarValue::UInt32(Some(9))),
),
(col("c1"), NullableInterval::from(ScalarValue::from("a"))),
];
let output = simplify_with_guarantee(expr.clone(), guarantees);
assert_eq!(output, lit(false));
let guarantees = vec![
(
col("c3"),
NullableInterval::MaybeNull {
values: Interval::make(Some(0_i64), Some(2_i64)).unwrap(),
},
),
(
col("c4"),
NullableInterval::MaybeNull {
values: Interval::make(Some(9_u32), Some(9_u32)).unwrap(),
},
),
(
col("c1"),
NullableInterval::NotNull {
values: Interval::try_new(
ScalarValue::from("d"),
ScalarValue::from("f"),
)
.unwrap(),
},
),
];
let output = simplify_with_guarantee(expr.clone(), guarantees);
assert_eq!(&output, &expr_x);
let guarantees = vec![
(
col("c3"),
NullableInterval::from(ScalarValue::Int64(Some(9))),
),
(
col("c4"),
NullableInterval::from(ScalarValue::UInt32(Some(3))),
),
];
let output = simplify_with_guarantee(expr.clone(), guarantees);
assert_eq!(output, lit(true));
let guarantees = vec![(
col("c4"),
NullableInterval::from(ScalarValue::UInt32(Some(3))),
)];
let output = simplify_with_guarantee(expr.clone(), guarantees);
assert_eq!(&output, &expr_x);
}
#[test]
fn test_expression_partial_simplify_1() {
let expr = (lit(1) + lit(2)) + (lit(4) / lit(0));
let expected = (lit(3)) + (lit(4) / lit(0));
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_expression_partial_simplify_2() {
let expr = (lit(1).gt(lit(2))).and(lit(4) / lit(0));
let expected = lit(false);
assert_eq!(simplify(expr), expected);
}
#[test]
fn test_simplify_cycles() {
let expr = lit(true);
let expected = lit(true);
let (expr, num_iter) = simplify_with_cycle_count(expr);
assert_eq!(expr, expected);
assert_eq!(num_iter, 1);
let expr = lit(true).not_eq(lit_bool_null()).or(lit(5).gt(lit(10)));
let expected = lit_bool_null();
let (expr, num_iter) = simplify_with_cycle_count(expr);
assert_eq!(expr, expected);
assert_eq!(num_iter, 2);
let expr = (((col("c4") - lit(10)) + lit(10)) * lit(100)) / lit(100);
let expected = expr.clone();
let (expr, num_iter) = simplify_with_cycle_count(expr);
assert_eq!(expr, expected);
assert_eq!(num_iter, 1);
let expr = col("c4")
.lt(lit(1))
.or(col("c3").lt(lit(2)))
.and(col("c3_non_null").lt(lit(3)))
.and(lit(false));
let expected = lit(false);
let (expr, num_iter) = simplify_with_cycle_count(expr);
assert_eq!(expr, expected);
assert_eq!(num_iter, 2);
}
#[test]
fn test_simplify_udaf() {
let udaf = AggregateUDF::new_from_impl(SimplifyMockUdaf::new_with_simplify());
let aggregate_function_expr =
Expr::AggregateFunction(datafusion_expr::expr::AggregateFunction::new_udf(
udaf.into(),
vec![],
false,
None,
None,
None,
));
let expected = col("result_column");
assert_eq!(simplify(aggregate_function_expr), expected);
let udaf = AggregateUDF::new_from_impl(SimplifyMockUdaf::new_without_simplify());
let aggregate_function_expr =
Expr::AggregateFunction(datafusion_expr::expr::AggregateFunction::new_udf(
udaf.into(),
vec![],
false,
None,
None,
None,
));
let expected = aggregate_function_expr.clone();
assert_eq!(simplify(aggregate_function_expr), expected);
}
#[derive(Debug, Clone)]
struct SimplifyMockUdaf {
simplify: bool,
}
impl SimplifyMockUdaf {
fn new_with_simplify() -> Self {
Self { simplify: true }
}
fn new_without_simplify() -> Self {
Self { simplify: false }
}
}
impl AggregateUDFImpl for SimplifyMockUdaf {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn name(&self) -> &str {
"mock_simplify"
}
fn signature(&self) -> &Signature {
unimplemented!()
}
fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
unimplemented!("not needed for tests")
}
fn accumulator(
&self,
_acc_args: function::AccumulatorArgs,
) -> Result<Box<dyn Accumulator>> {
unimplemented!("not needed for tests")
}
fn groups_accumulator_supported(&self, _args: AccumulatorArgs) -> bool {
unimplemented!("not needed for testing")
}
fn create_groups_accumulator(
&self,
_args: AccumulatorArgs,
) -> Result<Box<dyn GroupsAccumulator>> {
unimplemented!("not needed for testing")
}
fn simplify(&self) -> Option<AggregateFunctionSimplification> {
if self.simplify {
Some(Box::new(|_, _| Ok(col("result_column"))))
} else {
None
}
}
}
#[test]
fn test_simplify_udwf() {
let udwf = WindowFunctionDefinition::WindowUDF(
WindowUDF::new_from_impl(SimplifyMockUdwf::new_with_simplify()).into(),
);
let window_function_expr = Expr::WindowFunction(
datafusion_expr::expr::WindowFunction::new(udwf, vec![]),
);
let expected = col("result_column");
assert_eq!(simplify(window_function_expr), expected);
let udwf = WindowFunctionDefinition::WindowUDF(
WindowUDF::new_from_impl(SimplifyMockUdwf::new_without_simplify()).into(),
);
let window_function_expr = Expr::WindowFunction(
datafusion_expr::expr::WindowFunction::new(udwf, vec![]),
);
let expected = window_function_expr.clone();
assert_eq!(simplify(window_function_expr), expected);
}
#[derive(Debug, Clone)]
struct SimplifyMockUdwf {
simplify: bool,
}
impl SimplifyMockUdwf {
fn new_with_simplify() -> Self {
Self { simplify: true }
}
fn new_without_simplify() -> Self {
Self { simplify: false }
}
}
impl WindowUDFImpl for SimplifyMockUdwf {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn name(&self) -> &str {
"mock_simplify"
}
fn signature(&self) -> &Signature {
unimplemented!()
}
fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
unimplemented!("not needed for tests")
}
fn simplify(&self) -> Option<WindowFunctionSimplification> {
if self.simplify {
Some(Box::new(|_, _| Ok(col("result_column"))))
} else {
None
}
}
fn partition_evaluator(&self) -> Result<Box<dyn PartitionEvaluator>> {
unimplemented!("not needed for tests")
}
}
}