//! Defines the arithmetic operators used in the query language use crate::util::Variant; #[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Serialize)] pub enum LogicalOp { And, Or, } impl LogicalOp { pub fn negate(&self) -> LogicalOp { match self { LogicalOp::And => LogicalOp::Or, LogicalOp::Or => LogicalOp::And, } } } // Note: there are no `Between`/`NotBetween` variants. The parser rewrites // `x between a and b` into `x >= a and x <= b` (and the negation into // `x < a or x > b`), so a BETWEEN op never reaches evaluation. #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Serialize)] pub enum Op { Eq, Ne, Eeq, Ene, Gt, Gte, Lt, Lte, Rx, NotRx, Like, NotLike, In, NotIn, Exists, NotExists, } impl Op { pub fn from(text: &str) -> Option { match text.to_lowercase().as_str() { "=" | "==" | "eq" => Some(Op::Eq), "!=" | "<>" | "ne" => Some(Op::Ne), "===" | "eeq" => Some(Op::Eeq), "!==" | "ene" => Some(Op::Ene), ">" | "gt" => Some(Op::Gt), ">=" | "gte" | "ge" => Some(Op::Gte), "<" | "lt" => Some(Op::Lt), "<=" | "lte" | "le" => Some(Op::Lte), "~=" | "=~" | "regexp" | "rx" => Some(Op::Rx), "!=~" | "!~=" | "notrx" => Some(Op::NotRx), "like" => Some(Op::Like), "notlike" => Some(Op::NotLike), "in" => Some(Op::In), "notin" => Some(Op::NotIn), "exists" => Some(Op::Exists), "notexists" => Some(Op::NotExists), _ => None, } } pub fn from_with_not(text: &str, not: bool) -> Option { let op = Op::from(text); match op { Some(op) if not => Some(Self::negate(op)), _ => op, } } pub fn negate(op: Op) -> Op { match op { Op::Eq => Op::Ne, Op::Ne => Op::Eq, Op::Eeq => Op::Ene, Op::Ene => Op::Eeq, Op::Gt => Op::Lte, Op::Lte => Op::Gt, Op::Lt => Op::Gte, Op::Gte => Op::Lt, Op::Rx => Op::NotRx, Op::NotRx => Op::Rx, Op::Like => Op::NotLike, Op::NotLike => Op::Like, Op::In => Op::NotIn, Op::NotIn => Op::In, Op::Exists => Op::NotExists, Op::NotExists => Op::Exists, } } } #[derive(Debug, Clone, PartialOrd, PartialEq, Eq, Hash, Serialize)] pub enum ArithmeticOp { Add, Subtract, Divide, Multiply, Modulo, } impl ArithmeticOp { pub fn from(text: &str) -> Option { match text.to_lowercase().as_str() { "+" | "plus" => Some(ArithmeticOp::Add), "-" | "minus" => Some(ArithmeticOp::Subtract), "*" | "mul" => Some(ArithmeticOp::Multiply), "/" | "div" => Some(ArithmeticOp::Divide), "%" | "mod" => Some(ArithmeticOp::Modulo), _ => None, } } pub fn calc(&self, left: &Variant, right: &Variant) -> Result { let right_val = right.to_float(); if matches!(self, ArithmeticOp::Divide | ArithmeticOp::Modulo) && right_val == 0.0 { return Err("Division by zero".to_string()); } let result = match &self { ArithmeticOp::Add => left.to_float() + right_val, ArithmeticOp::Subtract => left.to_float() - right_val, ArithmeticOp::Multiply => left.to_float() * right_val, ArithmeticOp::Divide => left.to_float() / right_val, ArithmeticOp::Modulo => left.to_float() % right_val, }; if !result.is_finite() { return Err(format!("Arithmetic overflow: result is {}", result)); } Ok(Variant::from_float(result)) } } #[cfg(test)] mod tests { use super::*; #[test] fn op_from_between_is_none() { // BETWEEN is rewritten by the parser into >=/<= comparisons and has // no Op variant of its own. assert_eq!(Op::from("between"), None); assert_eq!(Op::from("notbetween"), None); } #[test] fn op_from_notin() { assert_eq!(Op::from("notin"), Some(Op::NotIn)); } #[test] fn op_from_notexists() { assert_eq!(Op::from("notexists"), Some(Op::NotExists)); } #[test] fn calc_divide_by_zero_returns_error() { let result = ArithmeticOp::Divide.calc( &Variant::from_float(1.0), &Variant::from_float(0.0), ); assert!(result.is_err()); } #[test] fn calc_modulo_by_zero_returns_error() { let result = ArithmeticOp::Modulo.calc( &Variant::from_float(1.0), &Variant::from_float(0.0), ); assert!(result.is_err()); } #[test] fn calc_zero_divided_by_zero_returns_error() { let result = ArithmeticOp::Divide.calc( &Variant::from_float(0.0), &Variant::from_float(0.0), ); assert!(result.is_err()); } #[test] fn op_from_notlike_exists() { assert_eq!(Op::from("notlike"), Some(Op::NotLike)); } #[test] fn op_from_notrx_exists() { assert_eq!(Op::from("notrx"), Some(Op::NotRx)); } #[test] fn op_negate_roundtrip() { let ops = vec![ Op::Eq, Op::Ne, Op::Eeq, Op::Ene, Op::Gt, Op::Gte, Op::Lt, Op::Lte, Op::Rx, Op::NotRx, Op::Like, Op::NotLike, Op::In, Op::NotIn, Op::Exists, Op::NotExists, ]; for op in ops { assert_eq!(Op::negate(Op::negate(op)), op); } } #[test] fn op_from_with_not_negates() { assert_eq!(Op::from_with_not("eq", true), Some(Op::Ne)); assert_eq!(Op::from_with_not("eq", false), Some(Op::Eq)); } #[test] fn op_from_with_not_unknown_returns_none() { assert_eq!(Op::from_with_not("garbage", true), None); assert_eq!(Op::from_with_not("garbage", false), None); } #[test] fn arithmetic_op_from_all_variants() { assert_eq!(ArithmeticOp::from("+"), Some(ArithmeticOp::Add)); assert_eq!(ArithmeticOp::from("plus"), Some(ArithmeticOp::Add)); assert_eq!(ArithmeticOp::from("-"), Some(ArithmeticOp::Subtract)); assert_eq!(ArithmeticOp::from("minus"), Some(ArithmeticOp::Subtract)); assert_eq!(ArithmeticOp::from("*"), Some(ArithmeticOp::Multiply)); assert_eq!(ArithmeticOp::from("mul"), Some(ArithmeticOp::Multiply)); assert_eq!(ArithmeticOp::from("/"), Some(ArithmeticOp::Divide)); assert_eq!(ArithmeticOp::from("div"), Some(ArithmeticOp::Divide)); assert_eq!(ArithmeticOp::from("%"), Some(ArithmeticOp::Modulo)); assert_eq!(ArithmeticOp::from("mod"), Some(ArithmeticOp::Modulo)); } #[test] fn arithmetic_op_from_unknown() { assert_eq!(ArithmeticOp::from("garbage"), None); } #[test] fn calc_basic_operations() { let a = Variant::from_float(10.0); let b = Variant::from_float(3.0); assert_eq!(ArithmeticOp::Add.calc(&a, &b).unwrap().to_float(), 13.0); assert_eq!(ArithmeticOp::Subtract.calc(&a, &b).unwrap().to_float(), 7.0); assert_eq!(ArithmeticOp::Multiply.calc(&a, &b).unwrap().to_float(), 30.0); let div = ArithmeticOp::Divide.calc(&a, &b).unwrap().to_float(); assert!((div - 10.0 / 3.0).abs() < 1e-10); assert_eq!(ArithmeticOp::Modulo.calc(&a, &b).unwrap().to_float(), 1.0); } #[test] fn calc_add_overflow_returns_error() { let result = ArithmeticOp::Add.calc( &Variant::from_float(f64::MAX), &Variant::from_float(f64::MAX), ); assert!(result.is_err(), "f64::MAX + f64::MAX should error, not silently become 0"); } #[test] fn calc_multiply_overflow_returns_error() { let result = ArithmeticOp::Multiply.calc( &Variant::from_float(f64::MAX), &Variant::from_float(2.0), ); assert!(result.is_err(), "f64::MAX * 2 should error, not silently become 0"); } #[test] fn calc_subtract_overflow_returns_error() { let result = ArithmeticOp::Subtract.calc( &Variant::from_float(f64::MAX), &Variant::from_float(-f64::MAX), ); assert!(result.is_err(), "f64::MAX - (-f64::MAX) should error, not silently become 0"); } #[test] fn logical_op_negate() { assert_eq!(LogicalOp::And.negate(), LogicalOp::Or); assert_eq!(LogicalOp::Or.negate(), LogicalOp::And); } }