Skip to main content

rushdb_types/
encoding.rs

1use crate::error::TypesError;
2use crate::value::Scalar;
3
4fn u64be(v: u64) -> [u8; 8] {
5    v.to_be_bytes()
6}
7
8/// Order-preserving sign flip for signed integers.
9fn int_encode(i: i64) -> [u8; 8] {
10    u64be(u64::from_ne_bytes(i.to_ne_bytes()) ^ 0x8000_0000_0000_0000)
11}
12
13fn int_decode(b: &[u8]) -> i64 {
14    let raw = u64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]);
15    i64::from_ne_bytes((raw ^ 0x8000_0000_0000_0000).to_ne_bytes())
16}
17
18/// Total order for f64: positive floats have the sign bit flipped, negatives
19/// are bit-inverted. -0.0 and +0.0 are distinct keys.
20fn float_encode(f: f64) -> [u8; 8] {
21    let bits = f.to_bits();
22    let transformed = if bits & 0x8000_0000_0000_0000 == 0 {
23        bits | 0x8000_0000_0000_0000
24    } else {
25        !bits
26    };
27    u64be(transformed)
28}
29
30fn float_decode(b: &[u8]) -> f64 {
31    let raw = u64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]);
32    let original = if raw & 0x8000_0000_0000_0000 == 0 {
33        !raw
34    } else {
35        raw & !0x8000_0000_0000_0000
36    };
37    f64::from_bits(original)
38}
39
40/// Stable one-byte tag for a logical type used in encoded keys.
41#[must_use]
42pub fn logical_tag(ty: crate::model::LogicalType) -> u8 {
43    use crate::model::LogicalType::{Boolean, DateTime, Float64, Int64, Utf8};
44    match ty {
45        Boolean => 1,
46        Int64 => 2,
47        Float64 => 3,
48        Utf8 => 4,
49        DateTime => 5,
50    }
51}
52
53/// Decode a logical type from its one-byte tag.
54///
55/// # Errors
56///
57/// Returns [`TypesError::UnknownTypeTag`] if `tag` is not a known type tag.
58pub fn logical_from_tag(tag: u8) -> Result<crate::model::LogicalType, TypesError> {
59    use crate::model::LogicalType::{Boolean, DateTime, Float64, Int64, Utf8};
60    Ok(match tag {
61        1 => Boolean,
62        2 => Int64,
63        3 => Float64,
64        4 => Utf8,
65        5 => DateTime,
66        other => return Err(TypesError::UnknownTypeTag(other)),
67    })
68}
69
70/// Encode a scalar with its logical type tag as a one-byte prefix.
71///
72/// # Panics
73///
74/// Panics if `s` does not match `ty`; use [`encode_order_preserving`] to handle
75/// a mismatch instead.
76#[must_use]
77pub fn encode_value_with_prefix(ty: crate::model::LogicalType, s: &Scalar) -> Vec<u8> {
78    let mut out = vec![logical_tag(ty)];
79    out.extend_from_slice(&encode_order_preserving(ty, s).expect("encodable"));
80    out
81}
82
83/// Encode a scalar into bytes whose lexicographic order matches value order.
84///
85/// # Errors
86///
87/// Returns [`TypesError::Unencodable`] if `s` does not match `ty`.
88pub fn encode_order_preserving(
89    ty: crate::model::LogicalType,
90    s: &Scalar,
91) -> Result<Vec<u8>, TypesError> {
92    use crate::model::LogicalType::{Boolean, DateTime, Float64, Int64, Utf8};
93    let err = || TypesError::Unencodable("scalar/type mismatch");
94    Ok(match ty {
95        Boolean => match s {
96            Scalar::Boolean(true) => vec![1],
97            Scalar::Boolean(false) => vec![0],
98            _ => return Err(err()),
99        },
100        Int64 => match s {
101            Scalar::Int64(i) => int_encode(*i).to_vec(),
102            _ => return Err(err()),
103        },
104        DateTime => match s {
105            Scalar::DateTime(ms) => int_encode(*ms).to_vec(),
106            _ => return Err(err()),
107        },
108        Float64 => match s {
109            Scalar::Float64(f) if !f.is_nan() => float_encode(*f).to_vec(),
110            _ => return Err(err()),
111        },
112        Utf8 => match s {
113            Scalar::Utf8(st) => {
114                let mut b = st.as_bytes().to_vec();
115                b.push(0x00);
116                b
117            }
118            _ => return Err(err()),
119        },
120    })
121}
122
123/// Decode bytes produced by [`encode_order_preserving`] back into a scalar.
124///
125/// # Errors
126///
127/// Returns [`TypesError::MalformedKey`] if the bytes are not a valid encoding
128/// of `ty` (for example a boolean byte other than `0`/`1`, or a string without
129/// its trailing NUL terminator).
130pub fn decode_order_preserving(
131    ty: crate::model::LogicalType,
132    b: &[u8],
133) -> Result<Scalar, TypesError> {
134    use crate::model::LogicalType::{Boolean, DateTime, Float64, Int64, Utf8};
135    Ok(match ty {
136        Boolean => match b {
137            [0] => Scalar::Boolean(false),
138            [1] => Scalar::Boolean(true),
139            _ => return Err(TypesError::MalformedKey),
140        },
141        Int64 => {
142            let raw: [u8; 8] = b.try_into().map_err(|_| TypesError::MalformedKey)?;
143            Scalar::Int64(int_decode(&raw))
144        }
145        DateTime => {
146            let raw: [u8; 8] = b.try_into().map_err(|_| TypesError::MalformedKey)?;
147            Scalar::DateTime(int_decode(&raw))
148        }
149        Float64 => {
150            let raw: [u8; 8] = b.try_into().map_err(|_| TypesError::MalformedKey)?;
151            let value = float_decode(&raw);
152            if value.is_nan() {
153                return Err(TypesError::MalformedKey);
154            }
155            Scalar::Float64(value)
156        }
157        Utf8 => {
158            let trimmed = b.strip_suffix(&[0x00]).ok_or(TypesError::MalformedKey)?;
159            Scalar::Utf8(String::from_utf8(trimmed.to_vec()).map_err(|_| TypesError::MalformedKey)?)
160        }
161    })
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use crate::model::LogicalType;
168
169    #[test]
170    fn order_preserving_decoder_rejects_invalid_fixed_widths() {
171        for ty in [
172            LogicalType::Int64,
173            LogicalType::DateTime,
174            LogicalType::Float64,
175        ] {
176            assert!(matches!(
177                decode_order_preserving(ty, &[]),
178                Err(TypesError::MalformedKey)
179            ));
180            assert!(matches!(
181                decode_order_preserving(ty, &[0; 9]),
182                Err(TypesError::MalformedKey)
183            ));
184        }
185        assert!(matches!(
186            decode_order_preserving(LogicalType::Boolean, &[0, 0]),
187            Err(TypesError::MalformedKey)
188        ));
189    }
190
191    #[test]
192    fn float_order_encoding_rejects_nan() {
193        assert!(matches!(
194            encode_order_preserving(LogicalType::Float64, &Scalar::Float64(f64::NAN)),
195            Err(TypesError::Unencodable(_))
196        ));
197        assert!(matches!(
198            decode_order_preserving(LogicalType::Float64, &float_encode(f64::NAN)),
199            Err(TypesError::MalformedKey)
200        ));
201    }
202}