Skip to main content

kevy_index/
value.rs

1//! [`IndexValue`] — the three scalar types an index can hold, with a
2//! total order (f64 via `total_cmp`, so NaN coerce-fails upstream and
3//! never enters a segment).
4
5use std::cmp::Ordering;
6
7/// One indexed scalar. Ordering is total within a type; the catalog
8/// guarantees a segment only ever holds one variant.
9#[derive(Debug, Clone, PartialEq)]
10pub enum IndexValue {
11    /// `TYPE i64`.
12    I64(i64),
13    /// `TYPE f64` (never NaN — coercion rejects it).
14    F64(f64),
15    /// `TYPE str` (raw bytes, memcmp order).
16    Str(Vec<u8>),
17}
18
19impl Eq for IndexValue {}
20
21impl Ord for IndexValue {
22    fn cmp(&self, other: &Self) -> Ordering {
23        match (self, other) {
24            (IndexValue::I64(a), IndexValue::I64(b)) => a.cmp(b),
25            (IndexValue::F64(a), IndexValue::F64(b)) => a.total_cmp(b),
26            (IndexValue::Str(a), IndexValue::Str(b)) => a.cmp(b),
27            // Cross-variant comparison means a catalog bug; order by
28            // discriminant to stay total rather than panic in a
29            // B-tree.
30            (a, b) => disc(a).cmp(&disc(b)),
31        }
32    }
33}
34
35impl PartialOrd for IndexValue {
36    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
37        Some(self.cmp(other))
38    }
39}
40
41fn disc(v: &IndexValue) -> u8 {
42    match v {
43        IndexValue::I64(_) => 0,
44        IndexValue::F64(_) => 1,
45        IndexValue::Str(_) => 2,
46    }
47}
48
49impl IndexValue {
50    /// Coerce raw field bytes per the declared type. `None` = the row
51    /// is excluded from the index (and counted as a coerce failure).
52    pub fn coerce(ty: crate::ValType, raw: &[u8]) -> Option<IndexValue> {
53        match ty {
54            // ANN kinds never coerce through IndexValue.
55            crate::ValType::Vector => None,
56            crate::ValType::I64 => std::str::from_utf8(raw)
57                .ok()?
58                .trim()
59                .parse::<i64>()
60                .ok()
61                .map(IndexValue::I64),
62            crate::ValType::F64 => {
63                let f = std::str::from_utf8(raw).ok()?.trim().parse::<f64>().ok()?;
64                if f.is_nan() {
65                    return None;
66                }
67                Some(IndexValue::F64(f))
68            }
69            crate::ValType::Str => Some(IndexValue::Str(raw.to_vec())),
70        }
71    }
72
73    /// Parse a query-side literal (same rules as [`Self::coerce`]).
74    pub fn parse_literal(ty: crate::ValType, raw: &[u8]) -> Option<IndexValue> {
75        Self::coerce(ty, raw)
76    }
77
78    /// Numeric view for aggregation (Str = 0.0; agg kinds only admit
79    /// numeric types at CREATE, so this arm is unreachable there).
80    pub fn as_f64(&self) -> f64 {
81        match self {
82            IndexValue::I64(v) => *v as f64,
83            IndexValue::F64(v) => *v,
84            IndexValue::Str(_) => 0.0,
85        }
86    }
87
88    /// Approximate heap bytes (for the memory formula / IDX.LIST).
89    pub fn approx_bytes(&self) -> usize {
90        match self {
91            IndexValue::I64(_) | IndexValue::F64(_) => 8,
92            IndexValue::Str(s) => s.len(),
93        }
94    }
95}