use crate::catalog::ValType;
use std::cmp::Ordering;
#[derive(Debug, Clone, PartialEq)]
pub enum IndexValue {
I64(i64),
F64(f64),
Str(Vec<u8>),
}
impl Eq for IndexValue {}
impl Ord for IndexValue {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(IndexValue::I64(a), IndexValue::I64(b)) => a.cmp(b),
(IndexValue::F64(a), IndexValue::F64(b)) => a.total_cmp(b),
(IndexValue::Str(a), IndexValue::Str(b)) => a.cmp(b),
(a, b) => disc(a).cmp(&disc(b)),
}
}
}
impl PartialOrd for IndexValue {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
fn disc(v: &IndexValue) -> u8 {
match v {
IndexValue::I64(_) => 0,
IndexValue::F64(_) => 1,
IndexValue::Str(_) => 2,
}
}
impl IndexValue {
pub fn coerce(ty: crate::ValType, raw: &[u8]) -> Option<IndexValue> {
match ty {
crate::ValType::Vector => None,
crate::ValType::I64 => std::str::from_utf8(raw)
.ok()?
.trim()
.parse::<i64>()
.ok()
.map(IndexValue::I64),
crate::ValType::F64 => {
let f = std::str::from_utf8(raw).ok()?.trim().parse::<f64>().ok()?;
if f.is_nan() {
return None;
}
Some(IndexValue::F64(f))
}
crate::ValType::Str => Some(IndexValue::Str(raw.to_vec())),
}
}
pub fn parse_literal(ty: crate::ValType, raw: &[u8]) -> Option<IndexValue> {
Self::coerce(ty, raw)
}
pub fn as_f64(&self) -> f64 {
match self {
IndexValue::I64(v) => *v as f64,
IndexValue::F64(v) => *v,
IndexValue::Str(_) => 0.0,
}
}
pub fn approx_bytes(&self) -> usize {
match self {
IndexValue::I64(_) | IndexValue::F64(_) => 8,
IndexValue::Str(s) => s.len(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ValueTest {
ty: ValType,
lo: IndexValue,
hi: IndexValue,
}
impl ValueTest {
pub fn range(ty: ValType, min: &[u8], max: &[u8]) -> Option<ValueTest> {
Some(ValueTest { ty, lo: IndexValue::coerce(ty, min)?, hi: IndexValue::coerce(ty, max)? })
}
pub fn eq(ty: ValType, v: &[u8]) -> Option<ValueTest> {
let v = IndexValue::coerce(ty, v)?;
Some(ValueTest { ty, lo: v.clone(), hi: v })
}
pub fn passes(&self, raw: &[u8]) -> bool {
IndexValue::coerce(self.ty, raw).is_some_and(|v| v >= self.lo && v <= self.hi)
}
}
pub fn order_key(ty: ValType, raw: &[u8]) -> Option<Vec<u8>> {
match IndexValue::coerce(ty, raw)? {
IndexValue::Str(v) => Some(v),
IndexValue::I64(v) => Some(((v as u64) ^ (1 << 63)).to_be_bytes().to_vec()),
IndexValue::F64(v) => {
let b = v.to_bits();
let m = if b >> 63 == 1 { !b } else { b | (1 << 63) };
Some(m.to_be_bytes().to_vec())
}
}
}
#[cfg(test)]
mod order_key_tests {
use super::*;
fn agrees(ty: ValType, raws: &[&str]) {
let mut vals: Vec<(IndexValue, Vec<u8>)> = raws
.iter()
.map(|r| {
(
IndexValue::coerce(ty, r.as_bytes()).expect("coerces"),
order_key(ty, r.as_bytes()).expect("encodes"),
)
})
.collect();
vals.sort_by(|a, b| a.1.cmp(&b.1));
for w in vals.windows(2) {
assert!(w[0].0 <= w[1].0, "{:?} then {:?} for {ty:?}", w[0].0, w[1].0);
}
}
#[test]
fn byte_order_matches_value_order() {
agrees(ValType::I64, &["-9223372036854775808", "-5", "-1", "0", "1", "5", "9223372036854775807"]);
agrees(ValType::F64, &["-1e308", "-1.5", "-0.5", "0", "0.5", "1.5", "1e308"]);
agrees(ValType::Str, &["", "a", "ab", "b", "z"]);
}
#[test]
fn a_value_that_does_not_coerce_has_no_key() {
assert!(order_key(ValType::I64, b"cheap").is_none());
assert!(order_key(ValType::F64, b"").is_none());
assert_eq!(order_key(ValType::Str, b"anything"), Some(b"anything".to_vec()));
}
}