use std::cmp::Ordering;
use super::value::{Error, R, Val, eq, strict_eq, utf16_cmp};
use crate::ast::BinaryOp;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Comparator {
Eq,
Neq,
Lt,
Lte,
Gt,
Gte,
ListContains,
}
impl Comparator {
pub fn name(self) -> &'static str {
match self {
Self::Eq => "EQ",
Self::Neq => "NEQ",
Self::Lt => "LT",
Self::Lte => "LTE",
Self::Gt => "GT",
Self::Gte => "GTE",
Self::ListContains => "LIST_CONTAINS",
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Simple {
pub comparator: Comparator,
pub value: Val,
}
pub(crate) fn binary(op: BinaryOp, l: &Val, r: &Val) -> Option<R> {
let comparison = matches!(
op,
BinaryOp::Eq | BinaryOp::Ne | BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge
);
Some(match (l, r) {
(Val::Constraint(_), Val::Constraint(_)) if comparison || op == BinaryOp::In => {
Err(Error::type_error(
"constraint",
"bool,duration,float,int,latlng,map,null,string,bytes,timestamp,path,list,\
vector,set",
))
}
(Val::Constraint(c), v) if comparison => implies(c, op, v),
(v, Val::Constraint(c)) if comparison => implies(c, flip(op), v),
(v, Val::Constraint(c)) if op == BinaryOp::In => {
let contains = c
.iter()
.any(|s| s.comparator == Comparator::ListContains && strict_eq(&s.value, v));
if contains {
Ok(Val::Bool(true))
} else {
Err(Error::undecided())
}
}
_ => return None,
})
}
pub(crate) fn has_any(c: &[Simple], items: &[Val]) -> bool {
c.iter().any(|s| {
s.comparator == Comparator::ListContains && items.iter().any(|i| strict_eq(&s.value, i))
})
}
fn flip(op: BinaryOp) -> BinaryOp {
match op {
BinaryOp::Lt => BinaryOp::Gt,
BinaryOp::Le => BinaryOp::Ge,
BinaryOp::Gt => BinaryOp::Lt,
BinaryOp::Ge => BinaryOp::Le,
other => other,
}
}
fn implies(c: &[Simple], op: BinaryOp, v: &Val) -> R {
let implied = match v {
Val::Null => op == BinaryOp::Ne,
_ => c.iter().any(|s| s.implies(op, v)),
};
if implied {
Ok(Val::Bool(true))
} else {
Err(Error::undecided())
}
}
impl Simple {
fn implies(&self, op: BinaryOp, v: &Val) -> bool {
use BinaryOp::{Eq, Ge, Gt, Le, Lt, Ne};
use Ordering::{Equal, Greater, Less};
let Some(order) = relation(&self.value, v) else {
return false;
};
match (self.comparator, order) {
(Comparator::Eq, Equal) => matches!(op, Eq | Le | Ge),
(Comparator::Eq, Less) => matches!(op, Ne | Lt | Le),
(Comparator::Eq, Greater) => matches!(op, Ne | Gt | Ge),
(Comparator::Neq, Equal) => op == Ne,
(Comparator::Gt, Equal | Greater) | (Comparator::Gte, Greater) => {
matches!(op, Gt | Ge | Ne)
}
(Comparator::Gte, Equal) => op == Ge,
(Comparator::Lt, Equal | Less) | (Comparator::Lte, Less) => matches!(op, Lt | Le | Ne),
(Comparator::Lte, Equal) => op == Le,
_ => false,
}
}
}
fn relation(filter: &Val, rule: &Val) -> Option<Ordering> {
match (filter, rule) {
(Val::Int(a), Val::Int(b)) => Some(a.cmp(b)),
(Val::Float(a), Val::Float(b)) => a.partial_cmp(b),
(Val::Int(a), Val::Float(b)) => (*a as f64).partial_cmp(b),
(Val::Float(_), Val::Int(_)) => None,
(Val::Str(a), Val::Str(b)) => Some(utf16_cmp(a, b)),
(Val::Bytes(a), Val::Bytes(b)) => Some(a.cmp(b)),
(Val::Timestamp(s, n), Val::Timestamp(t, m)) => Some((s, n).cmp(&(t, m))),
(a, b) if eq(a, b) => Some(Ordering::Equal),
_ => None,
}
}