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)]);
}
}