AkurAI Build
Menu

Public repository / beddeb15df67607b460f8291beb5c20f68db988e

AkurAI-Framework / crates/sqlexec/src/value.rs

Latest change d971623579ac6cb425eea3ca33f24af5983c18b2 - feat(sqlexec): SQL executor over the storage B+tree (CREATE/INSERT/SELECT) by Ólafur Búi Ólafsson

//! Runtime values — the typed cells that flow through the executor.
//!
//! A [`Value`] is what a column actually holds at runtime, as opposed to a
//! parser [`akurai_sql::Literal`] (which is pure syntax). The two map one to
//! one, but keeping them distinct lets the executor own ordering and coercion
//! rules without touching the parser crate.

use std::cmp::Ordering;

use akurai_sql::Literal;

/// A single typed cell.
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
    Null,
    Int(i64),
    Float(f64),
    Str(String),
}

impl Value {
    /// Lift a parser literal into a runtime value.
    pub fn from_literal(lit: &Literal) -> Value {
        match lit {
            Literal::Null => Value::Null,
            Literal::Int(i) => Value::Int(*i),
            Literal::Float(f) => Value::Float(*f),
            Literal::Str(s) => Value::Str(s.clone()),
        }
    }

    /// Is this the SQL `NULL`?
    pub fn is_null(&self) -> bool {
        matches!(self, Value::Null)
    }

    /// A total ordering over values, used by `ORDER BY`.
    ///
    /// `NULL` sorts before everything. Numbers (int and float) compare
    /// numerically against each other; otherwise values are ranked by type so
    /// the order is always total and never panics on `NaN`.
    pub fn order_key(&self, other: &Value) -> Ordering {
        match (self, other) {
            (Value::Null, Value::Null) => Ordering::Equal,
            (Value::Null, _) => Ordering::Less,
            (_, Value::Null) => Ordering::Greater,
            (Value::Str(a), Value::Str(b)) => a.cmp(b),
            _ => match (self.as_f64(), other.as_f64()) {
                (Some(a), Some(b)) => a.partial_cmp(&b).unwrap_or(Ordering::Equal),
                _ => self.type_rank().cmp(&other.type_rank()),
            },
        }
    }

    /// Numeric comparison for `WHERE`. Returns `None` when the two values are
    /// not comparable (e.g. number vs. string, or anything involving `NULL`),
    /// which the evaluator treats as "condition unknown / false".
    pub fn compare(&self, other: &Value) -> Option<Ordering> {
        match (self, other) {
            (Value::Null, _) | (_, Value::Null) => None,
            (Value::Str(a), Value::Str(b)) => Some(a.cmp(b)),
            (Value::Str(_), _) | (_, Value::Str(_)) => None,
            _ => self
                .as_f64()
                .zip(other.as_f64())
                .and_then(|(a, b)| a.partial_cmp(&b)),
        }
    }

    fn as_f64(&self) -> Option<f64> {
        match self {
            Value::Int(i) => Some(*i as f64),
            Value::Float(f) => Some(*f),
            _ => None,
        }
    }

    fn type_rank(&self) -> u8 {
        match self {
            Value::Null => 0,
            Value::Int(_) | Value::Float(_) => 1,
            Value::Str(_) => 2,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn compare_numbers_across_int_and_float() {
        assert_eq!(
            Value::Int(2).compare(&Value::Float(2.5)),
            Some(Ordering::Less)
        );
        assert_eq!(
            Value::Float(3.0).compare(&Value::Int(3)),
            Some(Ordering::Equal)
        );
    }

    #[test]
    fn compare_incomparable_is_none() {
        assert_eq!(Value::Int(1).compare(&Value::Str("a".into())), None);
        assert_eq!(Value::Null.compare(&Value::Int(1)), None);
    }

    #[test]
    fn order_key_puts_null_first() {
        let mut v = vec![Value::Int(2), Value::Null, Value::Int(1)];
        v.sort_by(|a, b| a.order_key(b));
        assert_eq!(v, vec![Value::Null, Value::Int(1), Value::Int(2)]);
    }
}