datasources 0.1.5

Async datasource interfaces, middleware, registry, and test doubles
Documentation
use super::expression::Expression;
use super::operator::Operator;
use super::r#where::Where;
use crate::i_where::IWhere;
use crate::row::Row;
use crate::value::Value;

pub struct ExpressionEvaluator;

impl ExpressionEvaluator {
    pub fn evaluate(&self, expression: &Expression, parameters: &Row) -> Value {
        match expression {
            Expression::Identifier(id) => parameters.get(&id.name).cloned().unwrap_or(Value::Null),
            Expression::Literal(lit) => lit.value.clone(),
            Expression::Operator(Operator::Not { argument }) => {
                Value::Bool(!is_true(&self.evaluate(argument, parameters)))
            }
            Expression::Operator(Operator::And { left, right }) => Value::Bool(
                is_true(&self.evaluate(left, parameters))
                    && is_true(&self.evaluate(right, parameters)),
            ),
            Expression::Operator(Operator::Or { left, right }) => Value::Bool(
                is_true(&self.evaluate(left, parameters))
                    || is_true(&self.evaluate(right, parameters)),
            ),
            Expression::Operator(Operator::IsNull { argument }) => {
                Value::Bool(matches!(self.evaluate(argument, parameters), Value::Null))
            }
            Expression::Operator(Operator::IsNotNull { argument }) => {
                Value::Bool(!matches!(self.evaluate(argument, parameters), Value::Null))
            }
            Expression::Operator(Operator::Like { left, right }) => like(
                self.evaluate(left, parameters),
                self.evaluate(right, parameters),
            ),
            Expression::Operator(Operator::NotLike { left, right }) => {
                Value::Bool(!is_true(&like(
                    self.evaluate(left, parameters),
                    self.evaluate(right, parameters),
                )))
            }
            Expression::Operator(Operator::Between { value, low, high }) => between(
                self.evaluate(value, parameters),
                self.evaluate(low, parameters),
                self.evaluate(high, parameters),
            ),
            Expression::Operator(Operator::NotBetween { value, low, high }) => {
                Value::Bool(!is_true(&between(
                    self.evaluate(value, parameters),
                    self.evaluate(low, parameters),
                    self.evaluate(high, parameters),
                )))
            }
            Expression::Operator(Operator::NotIn { left, right }) => {
                Value::Bool(!self.contains(left, right, parameters))
            }
            Expression::Operator(Operator::Exists { .. }) | Expression::Subquery(_) => {
                panic!("subquery is not evaluable")
            }
            Expression::Operator(Operator::In { left, right }) => {
                Value::Bool(self.contains(left, right, parameters))
            }
            Expression::Operator(Operator::Equals { left, right }) => {
                Value::Bool(self.evaluate(left, parameters) == self.evaluate(right, parameters))
            }
            Expression::Operator(Operator::NotEquals { left, right }) => {
                Value::Bool(self.evaluate(left, parameters) != self.evaluate(right, parameters))
            }
            Expression::Operator(Operator::GreaterThan { left, right }) => Value::Bool(
                compare(
                    &self.evaluate(left, parameters),
                    &self.evaluate(right, parameters),
                ) > 0,
            ),
            Expression::Operator(Operator::LessThan { left, right }) => Value::Bool(
                compare(
                    &self.evaluate(left, parameters),
                    &self.evaluate(right, parameters),
                ) < 0,
            ),
            Expression::Operator(Operator::GreaterThanOrEqual { left, right }) => Value::Bool(
                compare(
                    &self.evaluate(left, parameters),
                    &self.evaluate(right, parameters),
                ) >= 0,
            ),
            Expression::Operator(Operator::LessThanOrEqual { left, right }) => Value::Bool(
                compare(
                    &self.evaluate(left, parameters),
                    &self.evaluate(right, parameters),
                ) <= 0,
            ),
            Expression::Array(_) => panic!("array is not a scalar"),
        }
    }

    pub fn evaluate_where(&self, statement: &dyn IWhere, parameters: &Row) -> Value {
        statement
            .as_any()
            .downcast_ref::<Where>()
            .map(|where_clause| self.evaluate(&where_clause.condition, parameters))
            .unwrap_or_else(|| panic!("text where is not evaluable"))
    }

    fn contains(&self, left: &Expression, right: &Expression, parameters: &Row) -> bool {
        match right {
            Expression::Array(arr) => {
                let want = self.evaluate(left, parameters);
                arr.elements
                    .iter()
                    .any(|element| self.evaluate(element, parameters) == want)
            }
            _ => panic!("empty in"),
        }
    }
}

fn is_true(value: &Value) -> bool {
    matches!(value, Value::Bool(true))
}

fn like(value: Value, pattern: Value) -> Value {
    match (value, pattern) {
        (Value::Null, _) | (_, Value::Null) => Value::Bool(false),
        (Value::Text(text), Value::Text(pattern)) => Value::Bool(wild(&text, &pattern)),
        _ => panic!("empty like"),
    }
}

fn wild(text: &str, pattern: &str) -> bool {
    fn walk(text: &[char], pattern: &[char]) -> bool {
        match pattern.split_first() {
            None => text.is_empty(),
            Some(('%', rest)) => (0..=text.len()).any(|i| walk(&text[i..], rest)),
            Some(('_', rest)) => text
                .split_first()
                .is_some_and(|(_, rest_text)| walk(rest_text, rest)),
            Some((ch, rest)) => text
                .split_first()
                .is_some_and(|(head, rest_text)| head == ch && walk(rest_text, rest)),
        }
    }
    let text: Vec<char> = text.chars().collect();
    let pattern: Vec<char> = pattern.chars().collect();
    walk(&text, &pattern)
}

fn between(value: Value, low: Value, high: Value) -> Value {
    if matches!(value, Value::Null) || matches!(low, Value::Null) || matches!(high, Value::Null) {
        return Value::Bool(false);
    }
    Value::Bool(compare(&value, &low) >= 0 && compare(&value, &high) <= 0)
}

fn compare(left: &Value, right: &Value) -> i32 {
    match (left, right) {
        (Value::Int(a), Value::Int(b)) => a.cmp(b) as i32,
        (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).expect("float") as i32,
        (Value::Decimal(a), Value::Decimal(b)) => a.cmp(b) as i32,
        (Value::Int(a), Value::Float(b)) => (*a as f64).partial_cmp(b).expect("float") as i32,
        (Value::Float(a), Value::Int(b)) => a.partial_cmp(&(*b as f64)).expect("float") as i32,
        (Value::Text(a), Value::Text(b)) => a.cmp(b) as i32,
        _ => panic!("empty compare"),
    }
}