Skip to main content

graphitesql/
value.rs

1//! The dynamic value model and SQLite "serial types".
2//!
3//! Every cell in a SQLite table or index stores a *record*: a header of serial
4//! type codes (varints) followed by the concatenated value bodies. This module
5//! models the five SQLite storage classes ([`Value`]) and the serial-type codes
6//! ([`SerialType`]) that describe how each value is laid out on disk.
7//!
8//! Record (de)serialization itself lives in the `format` module and builds on
9//! these types; keeping the value model here lets the rest of the engine reason
10//! about values without pulling in disk-format details.
11
12use alloc::string::String;
13use alloc::vec::Vec;
14use core::cmp::Ordering;
15
16/// A value's storage class, owning its data.
17///
18/// These are the five SQLite storage classes. Note that SQLite stores `BOOLEAN`
19/// as integers and has no separate date/time class — those are conventions on
20/// top of these five.
21#[derive(Debug, Clone, PartialEq)]
22pub enum Value {
23    /// SQL `NULL`.
24    Null,
25    /// A signed 64-bit integer.
26    Integer(i64),
27    /// An IEEE-754 double.
28    Real(f64),
29    /// A UTF-8 text value. (SQLite also supports UTF-16; graphitesql stores
30    /// text as UTF-8 internally and converts at the boundary.)
31    Text(String),
32    /// A binary blob.
33    Blob(Vec<u8>),
34}
35
36/// A borrowed view of a [`Value`], used on hot decode paths to avoid copying.
37#[derive(Debug, Clone, Copy, PartialEq)]
38pub enum ValueRef<'a> {
39    /// SQL `NULL`.
40    Null,
41    /// A signed 64-bit integer.
42    Integer(i64),
43    /// An IEEE-754 double.
44    Real(f64),
45    /// Borrowed UTF-8 text.
46    Text(&'a str),
47    /// Borrowed binary blob.
48    Blob(&'a [u8]),
49}
50
51impl ValueRef<'_> {
52    /// Copy this borrowed value into an owned [`Value`].
53    pub fn to_owned(&self) -> Value {
54        match *self {
55            ValueRef::Null => Value::Null,
56            ValueRef::Integer(i) => Value::Integer(i),
57            ValueRef::Real(r) => Value::Real(r),
58            ValueRef::Text(s) => Value::Text(String::from(s)),
59            ValueRef::Blob(b) => Value::Blob(Vec::from(b)),
60        }
61    }
62}
63
64/// Compare two values in SQLite's total ordering: `NULL` < numbers < text <
65/// blobs; numbers compared numerically, text by byte (the `BINARY` collation),
66/// blobs by `memcmp`. This is the order used for index keys, `ORDER BY`, and
67/// comparisons (collation refinements are layered on top elsewhere).
68pub fn cmp_values(a: &Value, b: &Value) -> Ordering {
69    fn class(v: &Value) -> u8 {
70        match v {
71            Value::Null => 0,
72            Value::Integer(_) | Value::Real(_) => 1,
73            Value::Text(_) => 2,
74            Value::Blob(_) => 3,
75        }
76    }
77    fn as_f64(v: &Value) -> f64 {
78        match v {
79            Value::Integer(i) => *i as f64,
80            Value::Real(r) => *r,
81            _ => 0.0,
82        }
83    }
84    match (a, b) {
85        (Value::Null, Value::Null) => Ordering::Equal,
86        (Value::Integer(_) | Value::Real(_), Value::Integer(_) | Value::Real(_)) => {
87            as_f64(a).partial_cmp(&as_f64(b)).unwrap_or(Ordering::Equal)
88        }
89        (Value::Text(x), Value::Text(y)) => x.as_bytes().cmp(y.as_bytes()),
90        (Value::Blob(x), Value::Blob(y)) => x.cmp(y),
91        _ => class(a).cmp(&class(b)),
92    }
93}
94
95/// A SQLite record serial type code.
96///
97/// The mapping from code to meaning (file-format spec, "Serial Type Codes Of
98/// The Record Format"):
99///
100/// | code | meaning | body bytes |
101/// |------|---------|------------|
102/// | 0 | NULL | 0 |
103/// | 1 | int, big-endian | 1 |
104/// | 2 | int | 2 |
105/// | 3 | int | 3 |
106/// | 4 | int | 4 |
107/// | 5 | int | 6 |
108/// | 6 | int | 8 |
109/// | 7 | IEEE-754 float | 8 |
110/// | 8 | integer 0 | 0 |
111/// | 9 | integer 1 | 0 |
112/// | 10, 11 | reserved | — |
113/// | N≥12 even | BLOB | (N-12)/2 |
114/// | N≥13 odd | TEXT | (N-13)/2 |
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub struct SerialType(pub u64);
117
118impl SerialType {
119    /// Number of bytes this serial type occupies in the record body, or `None`
120    /// for the reserved codes 10 and 11.
121    pub fn content_len(self) -> Option<usize> {
122        Some(match self.0 {
123            0 | 8 | 9 => 0,
124            1 => 1,
125            2 => 2,
126            3 => 3,
127            4 => 4,
128            5 => 6,
129            6 | 7 => 8,
130            10 | 11 => return None,
131            n if n % 2 == 0 => ((n - 12) / 2) as usize,
132            n => ((n - 13) / 2) as usize,
133        })
134    }
135
136    /// The smallest serial type that can losslessly represent `value`.
137    ///
138    /// This matches SQLite's choice: small integers collapse to the 0/1 literals
139    /// (codes 8/9) and otherwise to the narrowest of the 1/2/3/4/6/8-byte forms.
140    pub fn for_value(value: &Value) -> SerialType {
141        SerialType(match value {
142            Value::Null => 0,
143            Value::Integer(0) => 8,
144            Value::Integer(1) => 9,
145            Value::Integer(i) => {
146                let i = *i;
147                if (-0x80..=0x7f).contains(&i) {
148                    1
149                } else if (-0x8000..=0x7fff).contains(&i) {
150                    2
151                } else if (-0x80_0000..=0x7f_ffff).contains(&i) {
152                    3
153                } else if (-0x8000_0000..=0x7fff_ffff).contains(&i) {
154                    4
155                } else if (-0x8000_0000_0000..=0x7fff_ffff_ffff).contains(&i) {
156                    5
157                } else {
158                    6
159                }
160            }
161            Value::Real(_) => 7,
162            Value::Blob(b) => 12 + 2 * b.len() as u64,
163            Value::Text(s) => 13 + 2 * s.len() as u64,
164        })
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use alloc::string::ToString;
172    use alloc::vec;
173
174    #[test]
175    fn content_lengths() {
176        assert_eq!(SerialType(0).content_len(), Some(0));
177        assert_eq!(SerialType(1).content_len(), Some(1));
178        assert_eq!(SerialType(5).content_len(), Some(6));
179        assert_eq!(SerialType(6).content_len(), Some(8));
180        assert_eq!(SerialType(7).content_len(), Some(8));
181        assert_eq!(SerialType(8).content_len(), Some(0));
182        assert_eq!(SerialType(9).content_len(), Some(0));
183        assert_eq!(SerialType(10).content_len(), None);
184        assert_eq!(SerialType(11).content_len(), None);
185        // BLOB of 4 bytes -> 12 + 2*4 = 20.
186        assert_eq!(SerialType(20).content_len(), Some(4));
187        // TEXT of 5 bytes -> 13 + 2*5 = 23.
188        assert_eq!(SerialType(23).content_len(), Some(5));
189    }
190
191    #[test]
192    fn serial_type_selection_matches_sqlite() {
193        assert_eq!(SerialType::for_value(&Value::Null), SerialType(0));
194        assert_eq!(SerialType::for_value(&Value::Integer(0)), SerialType(8));
195        assert_eq!(SerialType::for_value(&Value::Integer(1)), SerialType(9));
196        assert_eq!(SerialType::for_value(&Value::Integer(2)), SerialType(1));
197        assert_eq!(SerialType::for_value(&Value::Integer(127)), SerialType(1));
198        assert_eq!(SerialType::for_value(&Value::Integer(128)), SerialType(2));
199        assert_eq!(SerialType::for_value(&Value::Integer(-1)), SerialType(1));
200        assert_eq!(
201            SerialType::for_value(&Value::Integer(i64::MAX)),
202            SerialType(6)
203        );
204        assert_eq!(SerialType::for_value(&Value::Real(1.5)), SerialType(7));
205        assert_eq!(
206            SerialType::for_value(&Value::Text("abc".to_string())),
207            SerialType(19) // 13 + 2*3
208        );
209        assert_eq!(
210            SerialType::for_value(&Value::Blob(vec![0u8; 4])),
211            SerialType(20) // 12 + 2*4
212        );
213    }
214
215    #[test]
216    fn value_ref_round_trips() {
217        assert_eq!(ValueRef::Integer(5).to_owned(), Value::Integer(5));
218        assert_eq!(ValueRef::Text("x").to_owned(), Value::Text("x".to_string()));
219    }
220}