use super::Simplify;
use toasty_core::stmt::{self, Expr, ResolvedRef, VisitMut};
impl Simplify<'_> {
pub(super) fn simplify_expr_binary_op(
&mut self,
op: stmt::BinaryOp,
lhs: &mut stmt::Expr,
rhs: &mut stmt::Expr,
) -> Option<stmt::Expr> {
let result = match (&mut *lhs, &mut *rhs) {
(Expr::Reference(lhs), Expr::Reference(rhs))
if lhs == rhs && (op.is_eq() || op.is_ne()) =>
{
if lhs.is_field() {
let field = self.cx.resolve_expr_reference(lhs).as_field_unwrap();
if !field.nullable() {
return Some(op.is_eq().into());
}
}
None
}
(Expr::Record(lhs_rec), Expr::Record(rhs_rec))
if (op.is_eq() || op.is_ne()) && lhs_rec.len() == rhs_rec.len() =>
{
let comparisons: Vec<_> = std::mem::take(&mut lhs_rec.fields)
.into_iter()
.zip(std::mem::take(&mut rhs_rec.fields))
.map(|(l, r)| Expr::binary_op(l, op, r))
.collect();
if op.is_eq() {
Some(Expr::and_from_vec(comparisons))
} else {
Some(Expr::or_from_vec(comparisons))
}
}
(Expr::Record(rec), Expr::Value(stmt::Value::Record(val_rec)))
| (Expr::Value(stmt::Value::Record(val_rec)), Expr::Record(rec))
if (op.is_eq() || op.is_ne()) && rec.len() == val_rec.len() =>
{
let comparisons: Vec<_> = std::mem::take(&mut rec.fields)
.into_iter()
.zip(std::mem::take(&mut val_rec.fields))
.map(|(expr, val)| Expr::binary_op(expr, op, Expr::from(val)))
.collect();
if op.is_eq() {
Some(Expr::and_from_vec(comparisons))
} else {
Some(Expr::or_from_vec(comparisons))
}
}
(Expr::Match(m), _) if !op.is_arithmetic() && m.subject.is_stable() => {
let match_expr = lhs.take();
let other = rhs.take();
Some(self.eliminate_match_in_binary_op(op, match_expr, other, true))
}
(_, Expr::Match(m)) if !op.is_arithmetic() && m.subject.is_stable() => {
let other = lhs.take();
let match_expr = rhs.take();
Some(self.eliminate_match_in_binary_op(op, match_expr, other, false))
}
(Expr::Project(lhs), Expr::Project(rhs))
if lhs == rhs && lhs.base.is_stable() && (op.is_eq() || op.is_ne()) =>
{
Some(Expr::from(op.is_eq()))
}
_ => None,
};
if result.is_some() {
return result;
}
if self.is_always_null_derived_column(lhs) || self.is_always_null_derived_column(rhs) {
return Some(Expr::null());
}
None
}
fn is_always_null_derived_column(&self, expr: &Expr) -> bool {
let Expr::Reference(expr_ref) = expr else {
return false;
};
match self.cx.resolve_expr_reference(expr_ref) {
ResolvedRef::Derived(derived_ref) => derived_ref.is_column_always_null(),
_ => false,
}
}
fn eliminate_match_in_binary_op(
&mut self,
op: stmt::BinaryOp,
match_expr: Expr,
other: Expr,
match_on_lhs: bool,
) -> Expr {
let Expr::Match(match_expr) = match_expr else {
unreachable!()
};
let mut operands = Vec::new();
let patterns: Vec<_> = match_expr.arms.iter().map(|a| a.pattern.clone()).collect();
for arm in match_expr.arms {
let guard = Expr::binary_op(
(*match_expr.subject).clone(),
stmt::BinaryOp::Eq,
Expr::from(arm.pattern),
);
let comparison = if match_on_lhs {
Expr::binary_op(arm.expr, op, other.clone())
} else {
Expr::binary_op(other.clone(), op, arm.expr)
};
let mut term = Expr::and_from_vec(vec![guard, comparison]);
self.visit_expr_mut(&mut term);
if is_dead_filter_term(&term) {
continue;
}
operands.push(term);
}
{
let guards: Vec<Expr> = patterns
.into_iter()
.map(|pattern| {
Expr::not(Expr::binary_op(
(*match_expr.subject).clone(),
stmt::BinaryOp::Eq,
Expr::from(pattern),
))
})
.collect();
let comparison = if match_on_lhs {
Expr::binary_op(*match_expr.else_expr, op, other)
} else {
Expr::binary_op(other, op, *match_expr.else_expr)
};
let mut else_operands = guards;
else_operands.push(comparison);
let mut term = Expr::and_from_vec(else_operands);
self.visit_expr_mut(&mut term);
if !is_dead_filter_term(&term) {
operands.push(term);
}
}
Expr::or_from_vec(operands)
}
}
fn is_dead_filter_term(term: &Expr) -> bool {
if term.is_unsatisfiable() {
return true;
}
if contains_error(term) {
return true;
}
matches!(
term,
Expr::And(and) if and.operands.iter().any(Expr::is_value_null)
)
}
fn contains_error(expr: &Expr) -> bool {
use toasty_core::stmt::Visit;
struct FindError(bool);
impl Visit for FindError {
fn visit_expr_error(&mut self, _: &stmt::ExprError) {
self.0 = true;
}
}
let mut find = FindError(false);
find.visit_expr(expr);
find.0
}