use alloc::string::String;
use alloc::vec::Vec;
use core::cmp::Ordering;
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Integer(i64),
Real(f64),
Text(String),
Blob(Vec<u8>),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ValueRef<'a> {
Null,
Integer(i64),
Real(f64),
Text(&'a str),
Blob(&'a [u8]),
}
impl ValueRef<'_> {
pub fn to_owned(&self) -> Value {
match *self {
ValueRef::Null => Value::Null,
ValueRef::Integer(i) => Value::Integer(i),
ValueRef::Real(r) => Value::Real(r),
ValueRef::Text(s) => Value::Text(String::from(s)),
ValueRef::Blob(b) => Value::Blob(Vec::from(b)),
}
}
}
pub fn cmp_values(a: &Value, b: &Value) -> Ordering {
fn class(v: &Value) -> u8 {
match v {
Value::Null => 0,
Value::Integer(_) | Value::Real(_) => 1,
Value::Text(_) => 2,
Value::Blob(_) => 3,
}
}
fn as_f64(v: &Value) -> f64 {
match v {
Value::Integer(i) => *i as f64,
Value::Real(r) => *r,
_ => 0.0,
}
}
match (a, b) {
(Value::Null, Value::Null) => Ordering::Equal,
(Value::Integer(_) | Value::Real(_), Value::Integer(_) | Value::Real(_)) => {
as_f64(a).partial_cmp(&as_f64(b)).unwrap_or(Ordering::Equal)
}
(Value::Text(x), Value::Text(y)) => x.as_bytes().cmp(y.as_bytes()),
(Value::Blob(x), Value::Blob(y)) => x.cmp(y),
_ => class(a).cmp(&class(b)),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SerialType(pub u64);
impl SerialType {
pub fn content_len(self) -> Option<usize> {
Some(match self.0 {
0 | 8 | 9 => 0,
1 => 1,
2 => 2,
3 => 3,
4 => 4,
5 => 6,
6 | 7 => 8,
10 | 11 => return None,
n if n % 2 == 0 => ((n - 12) / 2) as usize,
n => ((n - 13) / 2) as usize,
})
}
pub fn for_value(value: &Value) -> SerialType {
SerialType(match value {
Value::Null => 0,
Value::Integer(0) => 8,
Value::Integer(1) => 9,
Value::Integer(i) => {
let i = *i;
if (-0x80..=0x7f).contains(&i) {
1
} else if (-0x8000..=0x7fff).contains(&i) {
2
} else if (-0x80_0000..=0x7f_ffff).contains(&i) {
3
} else if (-0x8000_0000..=0x7fff_ffff).contains(&i) {
4
} else if (-0x8000_0000_0000..=0x7fff_ffff_ffff).contains(&i) {
5
} else {
6
}
}
Value::Real(_) => 7,
Value::Blob(b) => 12 + 2 * b.len() as u64,
Value::Text(s) => 13 + 2 * s.len() as u64,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
use alloc::vec;
#[test]
fn content_lengths() {
assert_eq!(SerialType(0).content_len(), Some(0));
assert_eq!(SerialType(1).content_len(), Some(1));
assert_eq!(SerialType(5).content_len(), Some(6));
assert_eq!(SerialType(6).content_len(), Some(8));
assert_eq!(SerialType(7).content_len(), Some(8));
assert_eq!(SerialType(8).content_len(), Some(0));
assert_eq!(SerialType(9).content_len(), Some(0));
assert_eq!(SerialType(10).content_len(), None);
assert_eq!(SerialType(11).content_len(), None);
assert_eq!(SerialType(20).content_len(), Some(4));
assert_eq!(SerialType(23).content_len(), Some(5));
}
#[test]
fn serial_type_selection_matches_sqlite() {
assert_eq!(SerialType::for_value(&Value::Null), SerialType(0));
assert_eq!(SerialType::for_value(&Value::Integer(0)), SerialType(8));
assert_eq!(SerialType::for_value(&Value::Integer(1)), SerialType(9));
assert_eq!(SerialType::for_value(&Value::Integer(2)), SerialType(1));
assert_eq!(SerialType::for_value(&Value::Integer(127)), SerialType(1));
assert_eq!(SerialType::for_value(&Value::Integer(128)), SerialType(2));
assert_eq!(SerialType::for_value(&Value::Integer(-1)), SerialType(1));
assert_eq!(
SerialType::for_value(&Value::Integer(i64::MAX)),
SerialType(6)
);
assert_eq!(SerialType::for_value(&Value::Real(1.5)), SerialType(7));
assert_eq!(
SerialType::for_value(&Value::Text("abc".to_string())),
SerialType(19) );
assert_eq!(
SerialType::for_value(&Value::Blob(vec![0u8; 4])),
SerialType(20) );
}
#[test]
fn value_ref_round_trips() {
assert_eq!(ValueRef::Integer(5).to_owned(), Value::Integer(5));
assert_eq!(ValueRef::Text("x").to_owned(), Value::Text("x".to_string()));
}
}