#[cfg(test)]
mod tests;
use crate::db::predicate::{
CompareOp, ComparePredicate, Predicate, compare_eq, compare_order, eval_ordered_compare_result,
};
use std::cmp::Ordering;
#[derive(Clone, Copy)]
enum ComparePairSimplification {
NoChange,
Contradiction,
KeepFirst,
KeepSecond,
ReplaceFirst(CompareOp),
ReplaceSecond(CompareOp),
}
pub(in crate::db::predicate) fn simplify_and_compare_constraints<E>(
mut predicates: Vec<Predicate>,
charge_pair: &mut impl FnMut() -> Result<(), E>,
) -> Result<Option<Vec<Predicate>>, E> {
let mut revisit = None;
let mut i = 0;
'left: while i < predicates.len() {
if revisit.is_some_and(|(frontier, _)| i >= frontier) {
revisit = None;
}
if !matches!(&predicates[i], Predicate::Compare(_)) {
i += 1;
continue;
}
let mut j = revisit.map_or_else(|| i.saturating_add(1), |(_, changed)| changed);
while j < predicates.len() {
charge_pair()?;
let simplification = match (&predicates[i], &predicates[j]) {
(Predicate::Compare(left), Predicate::Compare(right))
if left.field == right.field && left.coercion == right.coercion =>
{
simplify_compare_pair_for_and(left, right)
}
_ => ComparePairSimplification::NoChange,
};
match simplification {
ComparePairSimplification::NoChange => {
if revisit.is_some() {
break;
}
j += 1;
}
ComparePairSimplification::Contradiction => return Ok(None),
ComparePairSimplification::KeepFirst => {
predicates.remove(j);
if let Some((frontier, _)) = revisit.take() {
i = frontier;
continue 'left;
}
}
ComparePairSimplification::KeepSecond => {
predicates.remove(i);
revisit = revisit.map(|(frontier, changed)| (frontier - 1, changed - 1));
continue 'left;
}
ComparePairSimplification::ReplaceFirst(replacement) => {
if let Predicate::Compare(compare) = &mut predicates[i] {
compare.op = replacement;
}
predicates.remove(j);
revisit = Some((i, i));
i = 0;
continue 'left;
}
ComparePairSimplification::ReplaceSecond(replacement) => {
if let Predicate::Compare(compare) = &mut predicates[j] {
compare.op = replacement;
}
predicates.remove(i);
revisit = Some((i, j - 1));
i = 0;
continue 'left;
}
}
}
i += 1;
}
Ok(Some(predicates))
}
fn simplify_compare_pair_for_and(
left: &ComparePredicate,
right: &ComparePredicate,
) -> ComparePairSimplification {
match (left.op, right.op) {
(CompareOp::Eq, CompareOp::Eq) => simplify_eq_eq_pair(left, right),
(CompareOp::Eq, _) => simplify_eq_with_constraint_pair(left, right, true),
(_, CompareOp::Eq) => simplify_eq_with_constraint_pair(right, left, false),
_ => simplify_constraint_constraint_pair(left, right),
}
}
fn simplify_eq_eq_pair(
left: &ComparePredicate,
right: &ComparePredicate,
) -> ComparePairSimplification {
match compare_eq(&left.value, &right.value, &left.coercion) {
Some(true) => ComparePairSimplification::KeepFirst,
Some(false) => ComparePairSimplification::Contradiction,
None => ComparePairSimplification::NoChange,
}
}
fn simplify_eq_with_constraint_pair(
eq: &ComparePredicate,
constraint: &ComparePredicate,
eq_is_first: bool,
) -> ComparePairSimplification {
if !constraint.op.is_ordering_family() {
return ComparePairSimplification::NoChange;
}
let Some(ordering) = compare_order(&eq.value, &constraint.value, &eq.coercion) else {
return ComparePairSimplification::NoChange;
};
if !eval_ordered_compare_result(constraint.op, ordering) {
return ComparePairSimplification::Contradiction;
}
if eq_is_first {
ComparePairSimplification::KeepFirst
} else {
ComparePairSimplification::KeepSecond
}
}
fn simplify_constraint_constraint_pair(
left: &ComparePredicate,
right: &ComparePredicate,
) -> ComparePairSimplification {
let left_lower = left.op.lower_bound_inclusive();
let right_lower = right.op.lower_bound_inclusive();
let left_upper = left.op.upper_bound_inclusive();
let right_upper = right.op.upper_bound_inclusive();
if left_lower.is_some() && right_lower.is_some() {
return simplify_two_lower_bounds(left, right);
}
if left_upper.is_some() && right_upper.is_some() {
return simplify_two_upper_bounds(left, right);
}
if left_lower.is_some() && right_upper.is_some() {
return simplify_lower_upper_pair(left, right);
}
if left_upper.is_some() && right_lower.is_some() {
return match simplify_lower_upper_pair(right, left) {
ComparePairSimplification::KeepFirst => ComparePairSimplification::KeepSecond,
ComparePairSimplification::KeepSecond => ComparePairSimplification::KeepFirst,
ComparePairSimplification::ReplaceFirst(cmp) => {
ComparePairSimplification::ReplaceSecond(cmp)
}
ComparePairSimplification::ReplaceSecond(cmp) => {
ComparePairSimplification::ReplaceFirst(cmp)
}
ComparePairSimplification::NoChange => ComparePairSimplification::NoChange,
ComparePairSimplification::Contradiction => ComparePairSimplification::Contradiction,
};
}
ComparePairSimplification::NoChange
}
fn simplify_two_lower_bounds(
left: &ComparePredicate,
right: &ComparePredicate,
) -> ComparePairSimplification {
let Some(ordering) = compare_order(&left.value, &right.value, &left.coercion) else {
return ComparePairSimplification::NoChange;
};
let Some(left_inclusive) = left.op.lower_bound_inclusive() else {
return ComparePairSimplification::NoChange;
};
let Some(right_inclusive) = right.op.lower_bound_inclusive() else {
return ComparePairSimplification::NoChange;
};
match ordering {
Ordering::Greater => ComparePairSimplification::KeepFirst,
Ordering::Less => ComparePairSimplification::KeepSecond,
Ordering::Equal => {
if !left_inclusive && right_inclusive {
ComparePairSimplification::KeepFirst
} else if left_inclusive && !right_inclusive {
ComparePairSimplification::KeepSecond
} else {
ComparePairSimplification::KeepFirst
}
}
}
}
fn simplify_two_upper_bounds(
left: &ComparePredicate,
right: &ComparePredicate,
) -> ComparePairSimplification {
let Some(ordering) = compare_order(&left.value, &right.value, &left.coercion) else {
return ComparePairSimplification::NoChange;
};
let Some(left_inclusive) = left.op.upper_bound_inclusive() else {
return ComparePairSimplification::NoChange;
};
let Some(right_inclusive) = right.op.upper_bound_inclusive() else {
return ComparePairSimplification::NoChange;
};
match ordering {
Ordering::Less => ComparePairSimplification::KeepFirst,
Ordering::Greater => ComparePairSimplification::KeepSecond,
Ordering::Equal => {
if !left_inclusive && right_inclusive {
ComparePairSimplification::KeepFirst
} else if left_inclusive && !right_inclusive {
ComparePairSimplification::KeepSecond
} else {
ComparePairSimplification::KeepFirst
}
}
}
}
fn simplify_lower_upper_pair(
lower: &ComparePredicate,
upper: &ComparePredicate,
) -> ComparePairSimplification {
let Some(ordering) = compare_order(&lower.value, &upper.value, &lower.coercion) else {
return ComparePairSimplification::NoChange;
};
let Some(lower_inclusive) = lower.op.lower_bound_inclusive() else {
return ComparePairSimplification::NoChange;
};
let Some(upper_inclusive) = upper.op.upper_bound_inclusive() else {
return ComparePairSimplification::NoChange;
};
match ordering {
Ordering::Less => ComparePairSimplification::NoChange,
Ordering::Greater => ComparePairSimplification::Contradiction,
Ordering::Equal => {
if lower_inclusive && upper_inclusive {
ComparePairSimplification::ReplaceFirst(CompareOp::Eq)
} else {
ComparePairSimplification::Contradiction
}
}
}
}