use super::super::select_ast::ArithmeticOperator;
use crate::{types::Value, Error, Result};
pub(super) fn values_equal(a: &Value, b: &Value) -> bool {
if a == b {
return true;
}
if same_numeric_family(a, b) {
if let (Some(x), Some(y)) = (a.as_f64(), b.as_f64()) {
return x == y;
}
}
false
}
pub(super) fn same_numeric_family(a: &Value, b: &Value) -> bool {
a.as_f64().is_some() && b.as_f64().is_some()
}
pub(super) fn compare_values_ordering(a: &Value, b: &Value) -> std::cmp::Ordering {
try_compare_values(a, b).unwrap_or(std::cmp::Ordering::Equal)
}
pub(super) fn try_compare_values(a: &Value, b: &Value) -> Result<std::cmp::Ordering> {
use std::cmp::Ordering;
if same_numeric_family(a, b) {
if let (Some(x), Some(y)) = (a.as_f64(), b.as_f64()) {
return Ok(x.partial_cmp(&y).unwrap_or(Ordering::Equal));
}
}
if std::mem::discriminant(a) == std::mem::discriminant(b) {
return a.partial_cmp(b).ok_or_else(|| {
Error::query_execution("Cannot compare incompatible types".to_string())
});
}
log::debug!(
"Cannot compare values of incompatible types: {:?} vs {:?}",
a.data_type(),
b.data_type()
);
Err(Error::query_execution(
"Cannot compare incompatible types".to_string(),
))
}
pub(super) fn eval_arithmetic(op: &ArithmeticOperator, left: Value, right: Value) -> Result<Value> {
use ArithmeticOperator::*;
macro_rules! int_op {
($a:expr, $b:expr, $ctor:expr) => {
match op {
Add => Ok($ctor($a + $b)),
Subtract => Ok($ctor($a - $b)),
Multiply => Ok($ctor($a * $b)),
Divide => {
if $b == 0 {
Err(Error::query_execution("Division by zero".to_string()))
} else {
Ok($ctor($a / $b))
}
}
Modulo => {
if $b == 0 {
Err(Error::query_execution("Modulo by zero".to_string()))
} else {
Ok($ctor($a % $b))
}
}
}
};
}
match (left, right) {
(Value::Integer(a), Value::Integer(b)) => int_op!(a, b, Value::Integer),
(Value::BigInt(a), Value::BigInt(b)) => int_op!(a, b, Value::BigInt),
(Value::Float(a), Value::Float(b)) => match op {
Add => Ok(Value::Float(a + b)),
Subtract => Ok(Value::Float(a - b)),
Multiply => Ok(Value::Float(a * b)),
Divide => Ok(Value::Float(a / b)),
Modulo => Ok(Value::Float(a % b)),
},
_ => Err(Error::query_execution(
"Incompatible types for arithmetic".to_string(),
)),
}
}
pub(super) fn const_arithmetic(
op: &ArithmeticOperator,
left: Value,
right: Value,
) -> Result<Value> {
use ArithmeticOperator::*;
if matches!(op, Modulo) {
return match (left, right) {
(Value::Integer(a), Value::Integer(b)) => {
eval_arithmetic(op, Value::Integer(a), Value::Integer(b))
}
(Value::BigInt(a), Value::BigInt(b)) => {
eval_arithmetic(op, Value::BigInt(a), Value::BigInt(b))
}
_ => Err(Error::query_execution(
"Modulo only supported for integers".to_string(),
)),
};
}
let verb = match op {
Add => "add",
Subtract => "subtract",
Multiply => "multiply",
Divide => "divide",
Modulo => unreachable!("handled above"),
};
match (left, right) {
(Value::Integer(a), Value::Integer(b)) => {
eval_arithmetic(op, Value::Integer(a), Value::Integer(b))
}
(Value::BigInt(a), Value::BigInt(b)) => {
eval_arithmetic(op, Value::BigInt(a), Value::BigInt(b))
}
(Value::Float(a), Value::Float(b)) => {
if matches!(op, Divide) && b == 0.0 {
return Err(Error::query_execution("Division by zero".to_string()));
}
eval_arithmetic(op, Value::Float(a), Value::Float(b))
}
_ => Err(Error::query_execution(format!(
"Cannot {} incompatible types",
verb
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_value_comparison() {
use std::cmp::Ordering;
assert_eq!(
try_compare_values(&Value::Integer(5), &Value::Integer(3)).unwrap(),
Ordering::Greater
);
assert_eq!(
try_compare_values(&Value::Integer(3), &Value::Integer(5)).unwrap(),
Ordering::Less
);
assert_eq!(
try_compare_values(&Value::Integer(5), &Value::Integer(5)).unwrap(),
Ordering::Equal
);
}
}