Skip to main content

sqlite_diff_rs/encoding/
serial.rs

1//! `SQLite` changeset value encoding.
2//!
3//! **IMPORTANT**: `SQLite` changesets use a DIFFERENT encoding than database records!
4//!
5//! Changeset value types (used in this module):
6//! - 0: Undefined (special marker for unchanged columns in UPDATE)
7//! - 1: INTEGER (always 8 bytes, big-endian i64)
8//! - 2: FLOAT (8 bytes, big-endian IEEE 754)
9//! - 3: TEXT (varint length + UTF-8 bytes)
10//! - 4: BLOB (varint length + raw bytes)
11//! - 5: NULL (no data follows)
12//!
13//! This is NOT the same as `SQLite` database record serial types!
14//! Database records use types 0-9 plus computed types for variable-length data.
15
16use alloc::string::{String, ToString};
17use alloc::vec::Vec;
18use core::hash::{Hash, Hasher};
19
20use super::varint::encode_varint_simple;
21
22/// A value that can be encoded in `SQLite` changeset format.
23#[derive(Debug, Clone)]
24pub enum Value<S, B> {
25    /// SQL NULL
26    Null,
27    /// 64-bit signed integer (always encoded as 8 bytes big-endian)
28    Integer(i64),
29    /// IEEE 754 floating point (8 bytes big-endian)
30    Real(f64),
31    /// UTF-8 text (varint length + bytes)
32    Text(S),
33    /// Binary blob (varint length + bytes)
34    Blob(B),
35}
36
37impl<S: Copy, B: Copy> Copy for Value<S, B> {}
38
39/// Internal type for representing values in changesets, where `None` means "undefined"
40/// (unchanged column in UPDATE operations).
41pub(crate) type MaybeValue<S, B> = Option<Value<S, B>>;
42
43// Custom PartialEq to handle f64 bit-equality
44impl<S: AsRef<str> + PartialEq, B: AsRef<[u8]> + PartialEq> PartialEq for Value<S, B> {
45    fn eq(&self, other: &Self) -> bool {
46        match (self, other) {
47            (Value::Integer(a), Value::Integer(b)) => a == b,
48            (Value::Real(a), Value::Real(b)) => a.to_bits() == b.to_bits(),
49            (Value::Text(a), Value::Text(b)) => a == b,
50            (Value::Blob(a), Value::Blob(b)) => a == b,
51            (Value::Null, Value::Null) => true,
52            _ => false,
53        }
54    }
55}
56
57impl<S: AsRef<str> + Eq, B: AsRef<[u8]> + Eq> Eq for Value<S, B> {}
58
59impl<S: AsRef<str> + Hash, B: AsRef<[u8]> + Hash> Hash for Value<S, B> {
60    fn hash<H: Hasher>(&self, state: &mut H) {
61        core::mem::discriminant(self).hash(state);
62        match self {
63            Value::Integer(v) => v.hash(state),
64            Value::Real(v) => v.to_bits().hash(state),
65            Value::Text(v) => v.hash(state),
66            Value::Blob(v) => v.hash(state),
67            Value::Null => {}
68        }
69    }
70}
71
72// From implementations for common types
73impl<S: AsRef<str>, B: AsRef<[u8]>> From<i64> for Value<S, B> {
74    #[inline]
75    fn from(v: i64) -> Self {
76        Value::Integer(v)
77    }
78}
79
80impl<S: AsRef<str>, B: AsRef<[u8]>> From<i32> for Value<S, B> {
81    #[inline]
82    fn from(v: i32) -> Self {
83        Value::Integer(i64::from(v))
84    }
85}
86
87impl<B: AsRef<[u8]>> From<String> for Value<String, B> {
88    #[inline]
89    fn from(v: String) -> Self {
90        Value::Text(v)
91    }
92}
93
94impl<B: AsRef<[u8]>> From<&str> for Value<String, B> {
95    #[inline]
96    fn from(v: &str) -> Self {
97        Value::Text(v.to_string())
98    }
99}
100
101impl<S: AsRef<str>, B: AsRef<[u8]>> From<f64> for Value<S, B> {
102    #[inline]
103    fn from(v: f64) -> Self {
104        Value::Real(v)
105    }
106}
107
108impl<S: AsRef<str>> From<Vec<u8>> for Value<S, Vec<u8>> {
109    fn from(v: Vec<u8>) -> Self {
110        Value::Blob(v)
111    }
112}
113
114impl<S: AsRef<str>> From<&[u8]> for Value<S, Vec<u8>> {
115    fn from(v: &[u8]) -> Self {
116        Value::Blob(v.to_vec())
117    }
118}
119
120impl<T: Into<Value<String, Vec<u8>>>> From<Option<T>> for Value<String, Vec<u8>> {
121    fn from(opt: Option<T>) -> Self {
122        match opt {
123            Some(v) => v.into(),
124            None => Value::Null,
125        }
126    }
127}
128
129impl<S: AsRef<str>, B: AsRef<[u8]>> Value<S, B> {
130    /// Convert to an owned Value by cloning the underlying data.
131    pub fn to_owned(&self) -> Value<String, Vec<u8>> {
132        match self {
133            Value::Null => Value::Null,
134            Value::Integer(v) => Value::Integer(*v),
135            Value::Real(v) => Value::Real(*v),
136            Value::Text(s) => Value::Text(s.as_ref().to_string()),
137            Value::Blob(b) => Value::Blob(b.as_ref().to_vec()),
138        }
139    }
140
141    /// Borrow as a reference Value.
142    pub fn as_ref(&self) -> Value<&str, &[u8]> {
143        match self {
144            Value::Null => Value::Null,
145            Value::Integer(v) => Value::Integer(*v),
146            Value::Real(v) => Value::Real(*v),
147            Value::Text(s) => Value::Text(s.as_ref()),
148            Value::Blob(b) => Value::Blob(b.as_ref()),
149        }
150    }
151}
152
153mod display;
154
155/// Encode the "undefined" marker (type 0) into the changeset binary format.
156///
157/// This is used for unchanged columns in UPDATE operations.
158#[inline]
159pub(crate) fn encode_undefined(out: &mut Vec<u8>) {
160    out.push(0x00);
161}
162
163/// Encode a Maybe value (Option<Value>) into the changeset binary format.
164///
165/// `SQLite` changesets use a DIFFERENT encoding than database records:
166/// - Type 0: Undefined (special marker for unchanged columns in UPDATE)
167/// - Type 1: INTEGER (always 8 bytes, big-endian i64)
168/// - Type 2: FLOAT (8 bytes, big-endian IEEE 754)
169/// - Type 3: TEXT (varint length + UTF-8 bytes)
170/// - Type 4: BLOB (varint length + raw bytes)
171/// - Type 5: NULL (no data follows)
172///
173/// This is NOT the same as `SQLite` serial types used in database records!
174pub(crate) fn encode_value<S: AsRef<str>, B: AsRef<[u8]>>(
175    out: &mut Vec<u8>,
176    value: Option<&Value<S, B>>,
177) {
178    match value {
179        None => encode_undefined(out),
180        Some(v) => encode_defined_value(out, v),
181    }
182}
183
184/// Encode a defined (non-undefined) value into the changeset binary format.
185pub(crate) fn encode_defined_value<S: AsRef<str>, B: AsRef<[u8]>>(
186    out: &mut Vec<u8>,
187    value: &Value<S, B>,
188) {
189    match value {
190        Value::Null => {
191            // NULL is type 5 in changeset format
192            out.push(0x05);
193        }
194        Value::Integer(v) => {
195            // INTEGER is type 1, always 8 bytes big-endian
196            out.push(0x01);
197            out.extend(v.to_be_bytes());
198        }
199        Value::Real(v) => {
200            // SQLite converts NaN to NULL, but preserves Infinity
201            if v.is_nan() {
202                out.push(0x05); // NULL
203            } else {
204                // FLOAT is type 2, 8 bytes big-endian IEEE 754
205                // SQLite normalizes -0.0 to 0.0
206                out.push(0x02);
207                let normalized = if *v == 0.0 { 0.0 } else { *v };
208                out.extend(normalized.to_be_bytes());
209            }
210        }
211        Value::Text(s) => {
212            // TEXT is type 3, varint length + UTF-8 bytes
213            let s = s.as_ref();
214            out.push(0x03);
215            out.extend(encode_varint_simple(s.len() as u64));
216            out.extend(s.as_bytes());
217        }
218        Value::Blob(b) => {
219            // BLOB is type 4, varint length + raw bytes
220            let b = b.as_ref();
221            out.push(0x04);
222            out.extend(encode_varint_simple(b.len() as u64));
223            out.extend(b);
224        }
225    }
226}
227
228/// Decode a value from changeset binary format.
229///
230/// `SQLite` changesets use the following type codes:
231/// - 0: Undefined (unchanged column in UPDATE)
232/// - 1: INTEGER (8 bytes big-endian)
233/// - 2: FLOAT (8 bytes big-endian IEEE 754)
234/// - 3: TEXT (varint length + UTF-8)
235/// - 4: BLOB (varint length + raw bytes)
236/// - 5: NULL
237///
238/// Returns the value (None for Undefined) and number of bytes consumed.
239#[must_use]
240pub(crate) fn decode_value(data: &[u8]) -> Option<(MaybeValue<String, Vec<u8>>, usize)> {
241    use super::varint::decode_varint;
242
243    if data.is_empty() {
244        return None;
245    }
246
247    let type_code = data[0];
248    let data = &data[1..];
249
250    match type_code {
251        0 => {
252            // Undefined marker
253            Some((None, 1))
254        }
255        1 => {
256            // INTEGER: 8 bytes big-endian
257            if data.len() < 8 {
258                return None;
259            }
260            let v = i64::from_be_bytes([
261                data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
262            ]);
263            Some((Some(Value::Integer(v)), 9))
264        }
265        2 => {
266            // FLOAT: 8 bytes big-endian IEEE 754
267            if data.len() < 8 {
268                return None;
269            }
270            let v = f64::from_be_bytes([
271                data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
272            ]);
273            // SQLite normalizes NaN to NULL and -0.0 to 0.0, so we do the same
274            // during decoding to ensure roundtrip consistency
275            if v.is_nan() {
276                Some((Some(Value::Null), 9))
277            } else {
278                // Normalize -0.0 to 0.0
279                let normalized = if v == 0.0 { 0.0 } else { v };
280                Some((Some(Value::Real(normalized)), 9))
281            }
282        }
283        3 => {
284            // TEXT: varint length + UTF-8 bytes
285            let (len, len_bytes) = decode_varint(data)?;
286            let len = usize::try_from(len).ok()?;
287            let data = &data[len_bytes..];
288            if data.len() < len {
289                return None;
290            }
291            let text = String::from_utf8(data[..len].to_vec()).ok()?;
292            Some((Some(Value::Text(text)), 1 + len_bytes + len))
293        }
294        4 => {
295            // BLOB: varint length + raw bytes
296            let (len, len_bytes) = decode_varint(data)?;
297            let len = usize::try_from(len).ok()?;
298            let data = &data[len_bytes..];
299            if data.len() < len {
300                return None;
301            }
302            Some((Some(Value::Blob(data[..len].to_vec())), 1 + len_bytes + len))
303        }
304        5 => {
305            // NULL
306            Some((Some(Value::Null), 1))
307        }
308        _ => None,
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315    use alloc::vec;
316
317    // Type alias for common test usage
318    type TestValue = Value<String, Vec<u8>>;
319
320    #[test]
321    fn test_encode_decode_null() {
322        let mut buf = Vec::new();
323        encode_value(&mut buf, Some(&TestValue::Null));
324        let (decoded, len) = decode_value(&buf).unwrap();
325        assert_eq!(decoded, Some(TestValue::Null));
326        assert_eq!(len, buf.len());
327    }
328
329    #[test]
330    fn test_encode_decode_integers() {
331        for v in [
332            0,
333            1,
334            -1,
335            127,
336            -128,
337            32767,
338            -32768,
339            i64::from(i32::MAX),
340            i64::MAX,
341        ] {
342            let mut buf = Vec::new();
343            encode_value(&mut buf, Some(&TestValue::Integer(v)));
344            let (decoded, len) = decode_value(&buf).unwrap();
345            assert_eq!(decoded, Some(TestValue::Integer(v)), "Failed for {v}");
346            assert_eq!(len, buf.len());
347            // All integers should be encoded as type 1 + 8 bytes = 9 bytes total
348            assert_eq!(buf.len(), 9, "Integer {v} should be 9 bytes");
349        }
350    }
351
352    #[test]
353    fn test_encode_decode_real() {
354        let mut buf = Vec::new();
355        encode_value(&mut buf, Some(&TestValue::Real(6.14159)));
356        let (decoded, len) = decode_value(&buf).unwrap();
357        assert_eq!(decoded, Some(TestValue::Real(6.14159)));
358        assert_eq!(len, buf.len());
359        // Float is type 2 + 8 bytes = 9 bytes total
360        assert_eq!(buf.len(), 9);
361    }
362
363    #[test]
364    fn test_encode_decode_text() {
365        let mut buf = Vec::new();
366        // Use reference type for encoding
367        let ref_value: Value<&str, &[u8]> = Value::Text("hello");
368        encode_value(&mut buf, Some(&ref_value));
369        let (decoded, len) = decode_value(&buf).unwrap();
370        assert_eq!(decoded, Some(TestValue::Text("hello".to_string())));
371        assert_eq!(len, buf.len());
372        // Text is type 3 + varint(5) + "hello" = 1 + 1 + 5 = 7 bytes
373        assert_eq!(buf.len(), 7);
374    }
375
376    #[test]
377    fn test_encode_decode_blob() {
378        let mut buf = Vec::new();
379        // Use reference type for encoding
380        let data: &[u8] = &[1, 2, 3, 4, 5];
381        let ref_value: Value<&str, &[u8]> = Value::Blob(data);
382        encode_value(&mut buf, Some(&ref_value));
383        let (decoded, len) = decode_value(&buf).unwrap();
384        assert_eq!(decoded, Some(TestValue::Blob(vec![1, 2, 3, 4, 5])));
385        assert_eq!(len, buf.len());
386        // Blob is type 4 + varint(5) + data = 1 + 1 + 5 = 7 bytes
387        assert_eq!(buf.len(), 7);
388    }
389
390    #[test]
391    fn test_encode_decode_undefined() {
392        let mut buf = Vec::new();
393        encode_value::<String, Vec<u8>>(&mut buf, None);
394        let (decoded, len) = decode_value(&buf).unwrap();
395        assert_eq!(decoded, None);
396        assert_eq!(len, buf.len());
397        // Undefined is type 0, just 1 byte
398        assert_eq!(buf.len(), 1);
399    }
400
401    #[test]
402    fn test_changeset_encoding_matches_rusqlite() {
403        // Test that our encoding matches what rusqlite produces
404
405        // Integer 1 should be: type 1 + 8 bytes (big-endian)
406        let mut buf = Vec::new();
407        encode_value(&mut buf, Some(&TestValue::Integer(1)));
408        assert_eq!(
409            buf,
410            vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
411        );
412
413        // Integer 100 should be: type 1 + 8 bytes (big-endian)
414        let mut buf = Vec::new();
415        encode_value(&mut buf, Some(&TestValue::Integer(100)));
416        assert_eq!(
417            buf,
418            vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64]
419        );
420
421        // Text "alice" should be: type 3 + varint(5) + "alice"
422        // Using reference type here
423        let mut buf = Vec::new();
424        let ref_value: Value<&str, &[u8]> = Value::Text("alice");
425        encode_value(&mut buf, Some(&ref_value));
426        assert_eq!(buf, vec![0x03, 0x05, b'a', b'l', b'i', b'c', b'e']);
427
428        // NULL should be type 5
429        let mut buf = Vec::new();
430        encode_value(&mut buf, Some(&Value::Null::<&str, &[u8]>));
431        assert_eq!(buf, vec![0x05]);
432    }
433
434    #[test]
435    fn test_cross_type_equality() {
436        // Test that Value<&str, &[u8]> and Value<String, Vec<u8>> can be compared via conversion
437        let owned: Value<String, Vec<u8>> = Value::Text("hello".to_string());
438        let borrowed: Value<&str, &[u8]> = Value::Text("hello");
439        assert_eq!(owned.as_ref(), borrowed);
440        assert_eq!(borrowed.to_owned(), owned);
441
442        let owned_blob: Value<String, Vec<u8>> = Value::Blob(vec![1, 2, 3]);
443        let data: &[u8] = &[1, 2, 3];
444        let borrowed_blob: Value<&str, &[u8]> = Value::Blob(data);
445        assert_eq!(owned_blob.as_ref(), borrowed_blob);
446    }
447
448    #[test]
449    fn test_to_owned_and_as_ref() {
450        let owned: Value<String, Vec<u8>> = Value::Text("hello".to_string());
451        let borrowed = owned.as_ref();
452        assert_eq!(owned.as_ref(), borrowed);
453
454        let back_to_owned = borrowed.to_owned();
455        assert_eq!(owned, back_to_owned);
456    }
457
458    #[test]
459    fn test_value_from_impls() {
460        type V = Value<String, Vec<u8>>;
461
462        let v: V = 42i64.into();
463        assert_eq!(v, Value::Integer(42));
464
465        let v: V = 7i32.into();
466        assert_eq!(v, Value::Integer(7));
467
468        let v: V = 3.5f64.into();
469        assert_eq!(v, Value::Real(3.5));
470
471        let v: V = "hello".into();
472        assert_eq!(v, Value::Text("hello".into()));
473
474        let v: V = String::from("world").into();
475        assert_eq!(v, Value::Text("world".into()));
476
477        let v: V = vec![1u8, 2, 3].into();
478        assert_eq!(v, Value::Blob(vec![1, 2, 3]));
479
480        let slice: &[u8] = &[4, 5, 6];
481        let v: V = slice.into();
482        assert_eq!(v, Value::Blob(vec![4, 5, 6]));
483
484        let v: V = Some(99i64).into();
485        assert_eq!(v, Value::Integer(99));
486
487        let v: V = None::<i64>.into();
488        assert_eq!(v, Value::Null);
489    }
490}