in datafusion/optimizer/src/simplify_expressions/expr_simplifier.rs [755:1902]
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 {
//
// Rules for Eq
//
// true = A --> A
// false = A --> !A
// null = A --> null
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(),
})
}
// A = true --> A
// A = false --> !A
// A = null --> 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(),
})
}
// According to SQL's null semantics, NULL = NULL evaluates to NULL
// Both sides are the same expression (A = A) and A is non-volatile expression
// A = A --> A IS NOT NULL OR NULL
// A = A --> true (if A not nullable)
Expr::BinaryExpr(BinaryExpr {
left,
op: Eq,
right,
}) if (left == right) & !left.is_volatile() => {
Transformed::yes(match !info.nullable(&left)? {
true => lit(true),
false => Expr::BinaryExpr(BinaryExpr {
left: Box::new(Expr::IsNotNull(left)),
op: Or,
right: Box::new(lit_bool_null()),
}),
})
}
// Rules for NotEq
//
// true != A --> !A
// false != A --> A
// null != A --> 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(),
})
}
// A != true --> !A
// A != false --> A
// A != null --> 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(),
})
}
//
// Rules for OR
//
// true OR A --> true (even if A is null)
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right: _,
}) if is_true(&left) => Transformed::yes(*left),
// false OR A --> A
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right,
}) if is_false(&left) => Transformed::yes(*right),
// A OR true --> true (even if A is null)
Expr::BinaryExpr(BinaryExpr {
left: _,
op: Or,
right,
}) if is_true(&right) => Transformed::yes(*right),
// A OR false --> A
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right,
}) if is_false(&right) => Transformed::yes(*left),
// A OR !A ---> true (if A not nullable)
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right,
}) if is_not_of(&right, &left) && !info.nullable(&left)? => {
Transformed::yes(lit(true))
}
// !A OR A ---> true (if A not nullable)
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right,
}) if is_not_of(&left, &right) && !info.nullable(&right)? => {
Transformed::yes(lit(true))
}
// (..A..) OR A --> (..A..)
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right,
}) if expr_contains(&left, &right, Or) => Transformed::yes(*left),
// A OR (..A..) --> (..A..)
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right,
}) if expr_contains(&right, &left, Or) => Transformed::yes(*right),
// A OR (A AND B) --> A
Expr::BinaryExpr(BinaryExpr {
left,
op: Or,
right,
}) if is_op_with(And, &right, &left) => Transformed::yes(*left),
// (A AND B) OR A --> A
Expr::BinaryExpr(BinaryExpr {
left,
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) && !e.is_volatile());
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
//
// true AND A --> A
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if is_true(&left) => Transformed::yes(*right),
// false AND A --> false (even if A is null)
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right: _,
}) if is_false(&left) => Transformed::yes(*left),
// A AND true --> A
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if is_true(&right) => Transformed::yes(*left),
// A AND false --> false (even if A is null)
Expr::BinaryExpr(BinaryExpr {
left: _,
op: And,
right,
}) if is_false(&right) => Transformed::yes(*right),
// A AND !A ---> false (if A not nullable)
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if is_not_of(&right, &left) && !info.nullable(&left)? => {
Transformed::yes(lit(false))
}
// !A AND A ---> false (if A not nullable)
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if is_not_of(&left, &right) && !info.nullable(&right)? => {
Transformed::yes(lit(false))
}
// (..A..) AND A --> (..A..)
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if expr_contains(&left, &right, And) => Transformed::yes(*left),
// A AND (..A..) --> (..A..)
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if expr_contains(&right, &left, And) => Transformed::yes(*right),
// A AND (A OR B) --> A
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if is_op_with(Or, &right, &left) => Transformed::yes(*left),
// (A OR B) AND A --> A
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if is_op_with(Or, &left, &right) => Transformed::yes(*right),
// A >= constant AND constant <= A --> A = constant
Expr::BinaryExpr(BinaryExpr {
left,
op: And,
right,
}) if can_reduce_to_equal_statement(&left, &right) => {
if let Expr::BinaryExpr(BinaryExpr {
left: left_left,
right: left_right,
..
}) = *left
{
Transformed::yes(Expr::BinaryExpr(BinaryExpr {
left: left_left,
op: Eq,
right: left_right,
}))
} else {
return internal_err!("can_reduce_to_equal_statement should only be called with a BinaryExpr");
}
}
//
// Rules for Multiply
//
// A * 1 --> A (with type coercion if needed)
Expr::BinaryExpr(BinaryExpr {
left,
op: Multiply,
right,
}) if is_one(&right) => {
simplify_right_is_one_case(info, left, &Multiply, &right)?
}
// A * null --> null
Expr::BinaryExpr(BinaryExpr {
left,
op: Multiply,
right,
}) if is_null(&right) => {
simplify_right_is_null_case(info, &left, &Multiply, right)?
}
// 1 * A --> A
Expr::BinaryExpr(BinaryExpr {
left,
op: Multiply,
right,
}) if is_one(&left) => {
// 1 * A is equivalent to A * 1
simplify_right_is_one_case(info, right, &Multiply, &left)?
}
// null * A --> null
Expr::BinaryExpr(BinaryExpr {
left,
op: Multiply,
right,
}) if is_null(&left) => {
simplify_right_is_null_case(info, &right, &Multiply, left)?
}
// A * 0 --> 0 (if A is not null and not floating, since NAN * 0 -> NAN)
Expr::BinaryExpr(BinaryExpr {
left,
op: Multiply,
right,
}) if !info.nullable(&left)?
&& !info.get_data_type(&left)?.is_floating()
&& is_zero(&right) =>
{
Transformed::yes(*right)
}
// 0 * A --> 0 (if A is not null and not floating, since 0 * NAN -> NAN)
Expr::BinaryExpr(BinaryExpr {
left,
op: Multiply,
right,
}) if !info.nullable(&right)?
&& !info.get_data_type(&right)?.is_floating()
&& is_zero(&left) =>
{
Transformed::yes(*left)
}
//
// Rules for Divide
//
// A / 1 --> A
Expr::BinaryExpr(BinaryExpr {
left,
op: Divide,
right,
}) if is_one(&right) => {
simplify_right_is_one_case(info, left, &Divide, &right)?
}
// A / null --> null
Expr::BinaryExpr(BinaryExpr {
left,
op: Divide,
right,
}) if is_null(&right) => {
simplify_right_is_null_case(info, &left, &Divide, right)?
}
// null / A --> null
Expr::BinaryExpr(BinaryExpr {
left,
op: Divide,
right,
}) if is_null(&left) => simplify_null_div_other_case(info, left, &right)?,
//
// Rules for Modulo
//
// A % null --> null
Expr::BinaryExpr(BinaryExpr {
left: _,
op: Modulo,
right,
}) if is_null(&right) => Transformed::yes(*right),
// null % A --> null
Expr::BinaryExpr(BinaryExpr {
left,
op: Modulo,
right: _,
}) if is_null(&left) => Transformed::yes(*left),
// A % 1 --> 0 (if A is not nullable and not floating, since NAN % 1 --> NAN)
Expr::BinaryExpr(BinaryExpr {
left,
op: Modulo,
right,
}) if !info.nullable(&left)?
&& !info.get_data_type(&left)?.is_floating()
&& is_one(&right) =>
{
Transformed::yes(Expr::Literal(ScalarValue::new_zero(
&info.get_data_type(&left)?,
)?))
}
//
// Rules for BitwiseAnd
//
// A & null -> null
Expr::BinaryExpr(BinaryExpr {
left: _,
op: BitwiseAnd,
right,
}) if is_null(&right) => Transformed::yes(*right),
// null & A -> null
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right: _,
}) if is_null(&left) => Transformed::yes(*left),
// A & 0 -> 0 (if A not nullable)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right,
}) if !info.nullable(&left)? && is_zero(&right) => Transformed::yes(*right),
// 0 & A -> 0 (if A not nullable)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right,
}) if !info.nullable(&right)? && is_zero(&left) => Transformed::yes(*left),
// !A & A -> 0 (if A not nullable)
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)?,
)?))
}
// A & !A -> 0 (if A not nullable)
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)?,
)?))
}
// (..A..) & A --> (..A..)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right,
}) if expr_contains(&left, &right, BitwiseAnd) => Transformed::yes(*left),
// A & (..A..) --> (..A..)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right,
}) if expr_contains(&right, &left, BitwiseAnd) => Transformed::yes(*right),
// A & (A | B) --> A (if B not null)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right,
}) if !info.nullable(&right)? && is_op_with(BitwiseOr, &right, &left) => {
Transformed::yes(*left)
}
// (A | B) & A --> A (if B not null)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseAnd,
right,
}) if !info.nullable(&left)? && is_op_with(BitwiseOr, &left, &right) => {
Transformed::yes(*right)
}
//
// Rules for BitwiseOr
//
// A | null -> null
Expr::BinaryExpr(BinaryExpr {
left: _,
op: BitwiseOr,
right,
}) if is_null(&right) => Transformed::yes(*right),
// null | A -> null
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right: _,
}) if is_null(&left) => Transformed::yes(*left),
// A | 0 -> A (even if A is null)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right,
}) if is_zero(&right) => Transformed::yes(*left),
// 0 | A -> A (even if A is null)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right,
}) if is_zero(&left) => Transformed::yes(*right),
// !A | A -> -1 (if A not nullable)
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)?,
)?))
}
// A | !A -> -1 (if A not nullable)
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)?,
)?))
}
// (..A..) | A --> (..A..)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right,
}) if expr_contains(&left, &right, BitwiseOr) => Transformed::yes(*left),
// A | (..A..) --> (..A..)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right,
}) if expr_contains(&right, &left, BitwiseOr) => Transformed::yes(*right),
// A | (A & B) --> A (if B not null)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right,
}) if !info.nullable(&right)? && is_op_with(BitwiseAnd, &right, &left) => {
Transformed::yes(*left)
}
// (A & B) | A --> A (if B not null)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseOr,
right,
}) if !info.nullable(&left)? && is_op_with(BitwiseAnd, &left, &right) => {
Transformed::yes(*right)
}
//
// Rules for BitwiseXor
//
// A ^ null -> null
Expr::BinaryExpr(BinaryExpr {
left: _,
op: BitwiseXor,
right,
}) if is_null(&right) => Transformed::yes(*right),
// null ^ A -> null
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseXor,
right: _,
}) if is_null(&left) => Transformed::yes(*left),
// A ^ 0 -> A (if A not nullable)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseXor,
right,
}) if !info.nullable(&left)? && is_zero(&right) => Transformed::yes(*left),
// 0 ^ A -> A (if A not nullable)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseXor,
right,
}) if !info.nullable(&right)? && is_zero(&left) => Transformed::yes(*right),
// !A ^ A -> -1 (if A not nullable)
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)?,
)?))
}
// A ^ !A -> -1 (if A not nullable)
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)?,
)?))
}
// (..A..) ^ A --> (the expression without A, if number of A is odd, otherwise one A)
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
})
}
// A ^ (..A..) --> (the expression without A, if number of A is odd, otherwise one A)
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
})
}
//
// Rules for BitwiseShiftRight
//
// A >> null -> null
Expr::BinaryExpr(BinaryExpr {
left: _,
op: BitwiseShiftRight,
right,
}) if is_null(&right) => Transformed::yes(*right),
// null >> A -> null
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseShiftRight,
right: _,
}) if is_null(&left) => Transformed::yes(*left),
// A >> 0 -> A (even if A is null)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseShiftRight,
right,
}) if is_zero(&right) => Transformed::yes(*left),
//
// Rules for BitwiseShiftRight
//
// A << null -> null
Expr::BinaryExpr(BinaryExpr {
left: _,
op: BitwiseShiftLeft,
right,
}) if is_null(&right) => Transformed::yes(*right),
// null << A -> null
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseShiftLeft,
right: _,
}) if is_null(&left) => Transformed::yes(*left),
// A << 0 -> A (even if A is null)
Expr::BinaryExpr(BinaryExpr {
left,
op: BitwiseShiftLeft,
right,
}) if is_zero(&right) => Transformed::yes(*left),
//
// Rules for Not
//
Expr::Not(inner) => Transformed::yes(negate_clause(*inner)),
//
// Rules for Negative
//
Expr::Negative(inner) => Transformed::yes(distribute_negation(*inner)),
//
// Rules for Case
//
// CASE
// WHEN X THEN A
// WHEN Y THEN B
// ...
// ELSE Q
// END
//
// ---> (X AND A) OR (Y AND B AND NOT X) OR ... (NOT (X OR Y) AND Q)
//
// Note: the rationale for this rewrite is that the expr can then be further
// simplified using the existing rules for AND/OR
Expr::Case(Case {
expr: None,
when_then_expr,
else_expr,
}) if !when_then_expr.is_empty()
&& when_then_expr.len() < 3 // The rewrite is O(n²) so limit to small number
&& info.is_boolean_type(&when_then_expr[0].1)? =>
{
// String disjunction of all the when predicates encountered so far. Not nullable.
let mut filter_expr = lit(false);
// The disjunction of all the cases
let mut out_expr = lit(false);
for (when, then) in when_then_expr {
let when = is_exactly_true(*when, info)?;
let case_expr =
when.clone().and(filter_expr.clone().not()).and(*then);
out_expr = out_expr.or(case_expr);
filter_expr = filter_expr.or(when);
}
let else_expr = else_expr.map(|b| *b).unwrap_or_else(lit_bool_null);
let case_expr = filter_expr.not().and(else_expr);
out_expr = out_expr.or(case_expr);
// Do a first pass at simplification
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),
},
//
// Rules for Between
//
// a between 3 and 5 --> a >= 3 AND a <=5
// a not between 3 and 5 --> a < 3 OR a > 5
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),
)
}),
//
// Rules for regexes
//
Expr::BinaryExpr(BinaryExpr {
left,
op: op @ (RegexMatch | RegexNotMatch | RegexIMatch | RegexNotIMatch),
right,
}) => Transformed::yes(simplify_regex_expr(left, op, right)?),
// Rules for Like
Expr::Like(like) => {
// `\` is implicit escape, see https://github.com/apache/datafusion/issues/13291
let escape_char = like.escape_char.unwrap_or('\\');
match as_string_scalar(&like.pattern) {
Some((data_type, pattern_str)) => {
match pattern_str {
None => return Ok(Transformed::yes(lit_bool_null())),
Some(pattern_str) if pattern_str == "%" => {
// exp LIKE '%' is
// - when exp is not NULL, it's true
// - when exp is NULL, it's NULL
// exp NOT LIKE '%' is
// - when exp is not NULL, it's false
// - when exp is NULL, it's NULL
let result_for_non_null = lit(!like.negated);
Transformed::yes(if !info.nullable(&like.expr)? {
result_for_non_null
} else {
Expr::Case(Case {
expr: Some(Box::new(Expr::IsNotNull(like.expr))),
when_then_expr: vec![(
Box::new(lit(true)),
Box::new(result_for_non_null),
)],
else_expr: None,
})
})
}
Some(pattern_str)
if pattern_str.contains("%%")
&& !pattern_str.contains(escape_char) =>
{
// Repeated occurrences of wildcard are redundant so remove them
// exp LIKE '%%' --> exp LIKE '%'
let simplified_pattern = Regex::new("%%+")
.unwrap()
.replace_all(pattern_str, "%")
.to_string();
Transformed::yes(Expr::Like(Like {
pattern: Box::new(to_string_scalar(
data_type,
Some(simplified_pattern),
)),
..like
}))
}
Some(pattern_str)
if !like.case_insensitive
&& !pattern_str
.contains(['%', '_', escape_char].as_ref()) =>
{
// If the pattern does not contain any wildcards, we can simplify the like expression to an equality expression
// TODO: handle escape characters
Transformed::yes(Expr::BinaryExpr(BinaryExpr {
left: like.expr.clone(),
op: if like.negated { NotEq } else { Eq },
right: like.pattern.clone(),
}))
}
Some(_pattern_str) => Transformed::no(Expr::Like(like)),
}
}
None => Transformed::no(Expr::Like(like)),
}
}
// a is not null/unknown --> true (if a is not nullable)
Expr::IsNotNull(expr) | Expr::IsNotUnknown(expr)
if !info.nullable(&expr)? =>
{
Transformed::yes(lit(true))
}
// a is null/unknown --> false (if a is not nullable)
Expr::IsNull(expr) | Expr::IsUnknown(expr) if !info.nullable(&expr)? => {
Transformed::yes(lit(false))
}
// expr IN () --> false
// expr NOT IN () --> true
Expr::InList(InList {
expr,
list,
negated,
}) if list.is_empty() && *expr != Expr::Literal(ScalarValue::Null) => {
Transformed::yes(lit(negated))
}
// null in (x, y, z) --> null
// null not in (x, y, z) --> null
Expr::InList(InList {
expr,
list: _,
negated: _,
}) if is_null(expr.as_ref()) => Transformed::yes(lit_bool_null()),
// expr IN ((subquery)) -> expr IN (subquery), see ##5529
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,
)))
}
// Combine multiple OR expressions into a single IN list expression if possible
//
// i.e. `a = 1 OR a = 2 OR a = 3` -> `a IN (1, 2, 3)`
Expr::BinaryExpr(BinaryExpr {
left,
op: 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))
}
// Simplify expressions that is guaranteed to be true or false to a literal boolean expression
//
// Rules:
// If both expressions are `IN` or `NOT IN`, then we can apply intersection or union on both lists
// Intersection:
// 1. `a in (1,2,3) AND a in (4,5) -> a in (), which is false`
// 2. `a in (1,2,3) AND a in (2,3,4) -> a in (2,3)`
// 3. `a not in (1,2,3) OR a not in (3,4,5,6) -> a not in (3)`
// Union:
// 4. `a not int (1,2,3) AND a not in (4,5,6) -> a not in (1,2,3,4,5,6)`
// # This rule is handled by `or_in_list_simplifier.rs`
// 5. `a in (1,2,3) OR a in (4,5,6) -> a in (1,2,3,4,5,6)`
// If one of the expressions is `IN` and another one is `NOT IN`, then we apply exception on `In` expression
// 6. `a in (1,2,3,4) AND a not in (1,2,3,4,5) -> a in (), which is false`
// 7. `a not in (1,2,3,4) AND a in (1,2,3,4,5) -> a = 5`
// 8. `a in (1,2,3,4) AND a not in (5,6,7,8) -> a in (1,2,3,4)`
Expr::BinaryExpr(BinaryExpr {
left,
op: 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);
}
// Matched previously once
_ => unreachable!(),
}
}
Expr::BinaryExpr(BinaryExpr {
left,
op: 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);
}
// Matched previously once
_ => unreachable!(),
}
}
Expr::BinaryExpr(BinaryExpr {
left,
op: 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);
}
// Matched previously once
_ => unreachable!(),
}
}
Expr::BinaryExpr(BinaryExpr {
left,
op: 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);
}
// Matched previously once
_ => unreachable!(),
}
}
Expr::BinaryExpr(BinaryExpr {
left,
op: 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);
}
// Matched previously once
_ => unreachable!(),
}
}
// =======================================
// unwrap_cast_in_comparison
// =======================================
//
// For case:
// try_cast/cast(expr as data_type) op literal
Expr::BinaryExpr(BinaryExpr { left, op, right })
if is_cast_expr_and_support_unwrap_cast_in_comparison_for_binary(
info, &left, op, &right,
) && op.supports_propagation() =>
{
unwrap_cast_in_comparison_for_binary(info, left, right, op)?
}
// literal op try_cast/cast(expr as data_type)
// -->
// try_cast/cast(expr as data_type) op_swap literal
Expr::BinaryExpr(BinaryExpr { left, op, right })
if is_cast_expr_and_support_unwrap_cast_in_comparison_for_binary(
info, &right, op, &left,
) && op.supports_propagation()
&& op.swap().is_some() =>
{
unwrap_cast_in_comparison_for_binary(
info,
right,
left,
op.swap().unwrap(),
)?
}
// For case:
// try_cast/cast(expr as left_type) in (expr1,expr2,expr3)
Expr::InList(InList {
expr: mut left,
list,
negated,
}) if is_cast_expr_and_support_unwrap_cast_in_comparison_for_inlist(
info, &left, &list,
) =>
{
let (Expr::TryCast(TryCast {
expr: left_expr, ..
})
| Expr::Cast(Cast {
expr: left_expr, ..
})) = left.as_mut()
else {
return internal_err!("Expect cast expr, but got {:?}", left)?;
};
let expr_type = info.get_data_type(left_expr)?;
let right_exprs = list
.into_iter()
.map(|right| {
match right {
Expr::Literal(right_lit_value) => {
// if the right_lit_value can be casted to the type of internal_left_expr
// we need to unwrap the cast for cast/try_cast expr, and add cast to the literal
let Some(value) = try_cast_literal_to_type(&right_lit_value, &expr_type) else {
internal_err!(
"Can't cast the list expr {:?} to type {:?}",
right_lit_value, &expr_type
)?
};
Ok(lit(value))
}
other_expr => internal_err!(
"Only support literal expr to optimize, but the expr is {:?}",
&other_expr
),
}
})
.collect::<Result<Vec<_>>>()?;
Transformed::yes(Expr::InList(InList {
expr: std::mem::take(left_expr),
list: right_exprs,
negated,
}))
}
// no additional rewrites possible
expr => Transformed::no(expr),
})
}