Skip to main content

fsqlite_types/
serial_type.rs

1/// SQLite record serial type encoding.
2///
3/// Each value in a record is preceded by a serial type (stored as a varint)
4/// that describes the type and size of the data that follows:
5///
6/// | Serial Type | Content Size | Meaning                    |
7/// |-------------|-------------|----------------------------|
8/// | 0           | 0           | NULL                       |
9/// | 1           | 1           | 8-bit signed integer       |
10/// | 2           | 2           | 16-bit big-endian integer  |
11/// | 3           | 3           | 24-bit big-endian integer  |
12/// | 4           | 4           | 32-bit big-endian integer  |
13/// | 5           | 6           | 48-bit big-endian integer  |
14/// | 6           | 8           | 64-bit big-endian integer  |
15/// | 7           | 8           | IEEE 754 float             |
16/// | 8           | 0           | Integer constant 0         |
17/// | 9           | 0           | Integer constant 1         |
18/// | 10, 11      | 0           | Reserved/internal          |
19/// | N >= 12 even| (N-12)/2    | BLOB of (N-12)/2 bytes     |
20/// | N >= 13 odd | (N-13)/2    | TEXT of (N-13)/2 bytes      |
21///
22/// Compute the number of bytes of data for a given serial type.
23///
24/// Returns `Some(0)` for reserved serial types 10 and 11 to match canonical
25/// SQLite's `sqlite3VdbeSerialTypeLen` size table. SQLite's file-format
26/// documentation says these values do not appear in well-formed database
27/// files, but they are reserved for SQLite's own internal/transient records.
28/// Treating their length as zero keeps record scanners aligned; higher layers
29/// can still decide whether to tolerate or reject such records.
30#[allow(clippy::inline_always)]
31#[inline(always)]
32pub const fn serial_type_len(serial_type: u64) -> Option<u64> {
33    match serial_type {
34        0 | 8 | 9 | 10 | 11 => Some(0),
35        1 => Some(1),
36        2 => Some(2),
37        3 => Some(3),
38        4 => Some(4),
39        5 => Some(6),
40        6 | 7 => Some(8),
41        n if n % 2 == 0 => Some((n - 12) / 2),
42        n => Some((n - 13) / 2),
43    }
44}
45
46/// Determine the serial type classification.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum SerialTypeClass {
49    /// SQL NULL (serial type 0).
50    Null,
51    /// Signed integer of 1-8 bytes (serial types 1-6).
52    Integer,
53    /// IEEE 754 double (serial type 7).
54    Float,
55    /// Integer constant 0 (serial type 8).
56    Zero,
57    /// Integer constant 1 (serial type 9).
58    One,
59    /// Reserved for future use (serial types 10, 11).
60    Reserved,
61    /// BLOB of `(N-12)/2` bytes (even serial types >= 12).
62    Blob,
63    /// TEXT of `(N-13)/2` bytes (odd serial types >= 13).
64    Text,
65}
66
67/// Classify a serial type value.
68#[allow(clippy::inline_always)]
69#[inline(always)]
70pub const fn classify_serial_type(serial_type: u64) -> SerialTypeClass {
71    match serial_type {
72        0 => SerialTypeClass::Null,
73        1..=6 => SerialTypeClass::Integer,
74        7 => SerialTypeClass::Float,
75        8 => SerialTypeClass::Zero,
76        9 => SerialTypeClass::One,
77        10 | 11 => SerialTypeClass::Reserved,
78        n if n % 2 == 0 => SerialTypeClass::Blob,
79        _ => SerialTypeClass::Text,
80    }
81}
82
83/// Compute the serial type for an integer value (choosing the smallest encoding).
84#[allow(clippy::cast_sign_loss)]
85pub const fn serial_type_for_integer(value: i64) -> u64 {
86    let u = if value < 0 {
87        !(value as u64)
88    } else {
89        value as u64
90    };
91
92    if u <= 127 {
93        if value == 0 {
94            return 8;
95        }
96        if value == 1 {
97            return 9;
98        }
99        1
100    } else if u <= 32767 {
101        2
102    } else if u <= 8_388_607 {
103        3
104    } else if u <= 2_147_483_647 {
105        4
106    } else if u <= 0x0000_7FFF_FFFF_FFFF {
107        5
108    } else {
109        6
110    }
111}
112
113/// Compute the serial type and payload length for an integer in one pass.
114#[allow(clippy::inline_always)]
115#[inline(always)]
116pub const fn integer_serial_type_and_len(value: i64) -> (u64, usize) {
117    let u = if value < 0 {
118        !(value as u64)
119    } else {
120        value as u64
121    };
122
123    if u <= 127 {
124        if value == 0 {
125            return (8, 0);
126        }
127        if value == 1 {
128            return (9, 0);
129        }
130        (1, 1)
131    } else if u <= 32767 {
132        (2, 2)
133    } else if u <= 8_388_607 {
134        (3, 3)
135    } else if u <= 2_147_483_647 {
136        (4, 4)
137    } else if u <= 0x0000_7FFF_FFFF_FFFF {
138        (5, 6)
139    } else {
140        (6, 8)
141    }
142}
143
144/// Compute the serial type for a text value of `len` bytes.
145pub const fn serial_type_for_text(len: u64) -> u64 {
146    len.saturating_mul(2).saturating_add(13)
147}
148
149/// Compute the serial type for a blob value of `len` bytes.
150pub const fn serial_type_for_blob(len: u64) -> u64 {
151    len.saturating_mul(2).saturating_add(12)
152}
153
154/// The sizes for serial types less than 128, matching C SQLite's
155/// `sqlite3SmallTypeSizes` lookup table.
156pub const SMALL_TYPE_SIZES: [u8; 128] = {
157    let mut table = [0u8; 128];
158    let mut i: usize = 0;
159    loop {
160        if i >= 128 {
161            break;
162        }
163        #[allow(clippy::cast_possible_truncation)]
164        let size = match serial_type_len(i as u64) {
165            Some(n) if n <= 255 => n as u8,
166            _ => 0,
167        };
168        table[i] = size;
169        i += 1;
170    }
171    table
172};
173
174/// Read a varint from a byte slice, returning `(value, bytes_consumed)`.
175///
176/// SQLite varints are 1-9 bytes. The high bit of each byte indicates whether
177/// more bytes follow (except the 9th byte which uses all 8 bits).
178#[allow(clippy::inline_always)]
179#[inline(always)]
180pub fn read_varint(buf: &[u8]) -> Option<(u64, usize)> {
181    if buf.is_empty() {
182        return None;
183    }
184
185    let first = buf[0];
186    // 1-byte fast path (~50% of varints in typical SQLite records).
187    if first < 0x80 {
188        return Some((u64::from(first), 1));
189    }
190
191    // 2-byte fast path (~30-40% of remaining varints: serial types 13-16383,
192    // header sizes 128-16383).  Avoids the loop + enumerate + skip overhead.
193    if buf.len() >= 2 {
194        let second = buf[1];
195        if second & 0x80 == 0 {
196            return Some(((u64::from(first & 0x7F) << 7) | u64::from(second), 2));
197        }
198    }
199
200    // General case: 3-9 byte varints. Keep this hand-unrolled like SQLite's
201    // B-tree parser hot path; rowids in real INSERT/seek workloads commonly
202    // live here, and the iterator/enumerate fallback was visible through
203    // `CellRef::parse` in the Wave 5 profiles.
204    let mut value = u64::from(first & 0x7F);
205
206    if buf.len() < 2 {
207        return None;
208    }
209    let byte = buf[1];
210    value = (value << 7) | u64::from(byte & 0x7F);
211
212    if buf.len() < 3 {
213        return None;
214    }
215    let byte = buf[2];
216    if byte & 0x80 == 0 {
217        return Some(((value << 7) | u64::from(byte), 3));
218    }
219    value = (value << 7) | u64::from(byte & 0x7F);
220
221    if buf.len() < 4 {
222        return None;
223    }
224    let byte = buf[3];
225    if byte & 0x80 == 0 {
226        return Some(((value << 7) | u64::from(byte), 4));
227    }
228    value = (value << 7) | u64::from(byte & 0x7F);
229
230    if buf.len() < 5 {
231        return None;
232    }
233    let byte = buf[4];
234    if byte & 0x80 == 0 {
235        return Some(((value << 7) | u64::from(byte), 5));
236    }
237    value = (value << 7) | u64::from(byte & 0x7F);
238
239    if buf.len() < 6 {
240        return None;
241    }
242    let byte = buf[5];
243    if byte & 0x80 == 0 {
244        return Some(((value << 7) | u64::from(byte), 6));
245    }
246    value = (value << 7) | u64::from(byte & 0x7F);
247
248    if buf.len() < 7 {
249        return None;
250    }
251    let byte = buf[6];
252    if byte & 0x80 == 0 {
253        return Some(((value << 7) | u64::from(byte), 7));
254    }
255    value = (value << 7) | u64::from(byte & 0x7F);
256
257    if buf.len() < 8 {
258        return None;
259    }
260    let byte = buf[7];
261    if byte & 0x80 == 0 {
262        return Some(((value << 7) | u64::from(byte), 8));
263    }
264    value = (value << 7) | u64::from(byte & 0x7F);
265
266    if buf.len() > 8 {
267        return Some(((value << 8) | u64::from(buf[8]), 9));
268    }
269
270    None
271}
272
273/// Compute the number of bytes needed to encode a value as a varint.
274pub const fn varint_len(value: u64) -> usize {
275    if value <= 0x7F {
276        1
277    } else if value <= 0x3FFF {
278        2
279    } else if value <= 0x001F_FFFF {
280        3
281    } else if value <= 0x0FFF_FFFF {
282        4
283    } else if value <= 0x07_FFFF_FFFF {
284        5
285    } else if value <= 0x03FF_FFFF_FFFF {
286        6
287    } else if value <= 0x01_FFFF_FFFF_FFFF {
288        7
289    } else if value <= 0xFF_FFFF_FFFF_FFFF {
290        8
291    } else {
292        9
293    }
294}
295
296/// Write a varint to a byte buffer, returning the number of bytes written.
297///
298/// The buffer must have at least 9 bytes available.
299#[allow(clippy::cast_possible_truncation)]
300pub fn write_varint(buf: &mut [u8], value: u64) -> usize {
301    let len = varint_len(value);
302
303    if len == 1 {
304        buf[0] = value as u8;
305    } else if len == 9 {
306        // First 8 bytes: each has high bit set, carries 7 bits
307        let mut v = value >> 8;
308        for i in (0..8).rev() {
309            buf[i] = (v as u8 & 0x7F) | 0x80;
310            v >>= 7;
311        }
312        buf[8] = value as u8;
313    } else {
314        let mut v = value;
315        for i in (0..len).rev() {
316            if i == len - 1 {
317                buf[i] = v as u8 & 0x7F;
318            } else {
319                buf[i] = (v as u8 & 0x7F) | 0x80;
320            }
321            v >>= 7;
322        }
323    }
324
325    len
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331
332    #[test]
333    fn serial_type_sizes() {
334        assert_eq!(serial_type_len(0), Some(0)); // NULL
335        assert_eq!(serial_type_len(1), Some(1)); // 8-bit int
336        assert_eq!(serial_type_len(2), Some(2)); // 16-bit int
337        assert_eq!(serial_type_len(3), Some(3)); // 24-bit int
338        assert_eq!(serial_type_len(4), Some(4)); // 32-bit int
339        assert_eq!(serial_type_len(5), Some(6)); // 48-bit int
340        assert_eq!(serial_type_len(6), Some(8)); // 64-bit int
341        assert_eq!(serial_type_len(7), Some(8)); // float
342        assert_eq!(serial_type_len(8), Some(0)); // constant 0
343        assert_eq!(serial_type_len(9), Some(0)); // constant 1
344        // Reserved serial types 10 and 11: canonical SQLite assigns these
345        // zero-length payloads in its internal size table. They are not
346        // expected in well-formed database files.
347        assert_eq!(serial_type_len(10), Some(0)); // reserved, zero-length
348        assert_eq!(serial_type_len(11), Some(0)); // reserved, zero-length
349    }
350
351    #[test]
352    fn serial_type_blob_text() {
353        // Even >= 12 is BLOB
354        assert_eq!(serial_type_len(12), Some(0)); // empty blob
355        assert_eq!(serial_type_len(14), Some(1)); // 1-byte blob
356        assert_eq!(serial_type_len(20), Some(4)); // 4-byte blob
357
358        // Odd >= 13 is TEXT
359        assert_eq!(serial_type_len(13), Some(0)); // empty text
360        assert_eq!(serial_type_len(15), Some(1)); // 1-byte text
361        assert_eq!(serial_type_len(21), Some(4)); // 4-byte text
362    }
363
364    #[test]
365    fn classification() {
366        assert_eq!(classify_serial_type(0), SerialTypeClass::Null);
367        assert_eq!(classify_serial_type(1), SerialTypeClass::Integer);
368        assert_eq!(classify_serial_type(6), SerialTypeClass::Integer);
369        assert_eq!(classify_serial_type(7), SerialTypeClass::Float);
370        assert_eq!(classify_serial_type(8), SerialTypeClass::Zero);
371        assert_eq!(classify_serial_type(9), SerialTypeClass::One);
372        assert_eq!(classify_serial_type(10), SerialTypeClass::Reserved);
373        assert_eq!(classify_serial_type(11), SerialTypeClass::Reserved);
374        assert_eq!(classify_serial_type(12), SerialTypeClass::Blob);
375        assert_eq!(classify_serial_type(13), SerialTypeClass::Text);
376        assert_eq!(classify_serial_type(14), SerialTypeClass::Blob);
377        assert_eq!(classify_serial_type(15), SerialTypeClass::Text);
378    }
379
380    #[test]
381    fn serial_type_for_integers() {
382        assert_eq!(serial_type_for_integer(0), 8);
383        assert_eq!(serial_type_for_integer(1), 9);
384        assert_eq!(serial_type_for_integer(2), 1);
385        assert_eq!(serial_type_for_integer(127), 1);
386        assert_eq!(serial_type_for_integer(-1), 1);
387        assert_eq!(serial_type_for_integer(-128), 1);
388        assert_eq!(serial_type_for_integer(128), 2);
389        assert_eq!(serial_type_for_integer(32767), 2);
390        assert_eq!(serial_type_for_integer(32768), 3);
391        assert_eq!(serial_type_for_integer(8_388_607), 3);
392        assert_eq!(serial_type_for_integer(8_388_608), 4);
393        assert_eq!(serial_type_for_integer(2_147_483_647), 4);
394        assert_eq!(serial_type_for_integer(2_147_483_648), 5);
395        assert_eq!(serial_type_for_integer(i64::MAX), 6);
396        assert_eq!(serial_type_for_integer(i64::MIN), 6);
397    }
398
399    #[test]
400    fn serial_type_for_text_and_blob() {
401        assert_eq!(serial_type_for_text(0), 13);
402        assert_eq!(serial_type_for_text(1), 15);
403        assert_eq!(serial_type_for_text(5), 23);
404        assert_eq!(serial_type_for_blob(0), 12);
405        assert_eq!(serial_type_for_blob(1), 14);
406        assert_eq!(serial_type_for_blob(5), 22);
407    }
408
409    #[test]
410    fn small_type_sizes_table() {
411        assert_eq!(SMALL_TYPE_SIZES[0], 0);
412        assert_eq!(SMALL_TYPE_SIZES[1], 1);
413        assert_eq!(SMALL_TYPE_SIZES[2], 2);
414        assert_eq!(SMALL_TYPE_SIZES[3], 3);
415        assert_eq!(SMALL_TYPE_SIZES[4], 4);
416        assert_eq!(SMALL_TYPE_SIZES[5], 6);
417        assert_eq!(SMALL_TYPE_SIZES[6], 8);
418        assert_eq!(SMALL_TYPE_SIZES[7], 8);
419        assert_eq!(SMALL_TYPE_SIZES[8], 0);
420        assert_eq!(SMALL_TYPE_SIZES[9], 0);
421    }
422
423    #[test]
424    fn varint_roundtrip() {
425        let test_values: &[u64] = &[
426            0,
427            1,
428            127,
429            128,
430            0x3FFF,
431            0x4000,
432            0x001F_FFFF,
433            0x0020_0000,
434            0x0FFF_FFFF,
435            0x1000_0000,
436            u64::from(u32::MAX),
437            u64::MAX / 2,
438            u64::MAX,
439        ];
440
441        let mut buf = [0u8; 9];
442        for &value in test_values {
443            let written = write_varint(&mut buf, value);
444            let (decoded, consumed) = read_varint(&buf[..written]).unwrap();
445            assert_eq!(decoded, value, "roundtrip failed for {value}");
446            assert_eq!(written, consumed, "length mismatch for {value}");
447            assert_eq!(
448                written,
449                varint_len(value),
450                "varint_len mismatch for {value}"
451            );
452        }
453    }
454
455    #[test]
456    fn varint_single_byte() {
457        let mut buf = [0u8; 9];
458        assert_eq!(write_varint(&mut buf, 0), 1);
459        assert_eq!(buf[0], 0);
460
461        assert_eq!(write_varint(&mut buf, 127), 1);
462        assert_eq!(buf[0], 127);
463    }
464
465    #[test]
466    fn varint_two_bytes() {
467        let mut buf = [0u8; 9];
468        let written = write_varint(&mut buf, 128);
469        assert_eq!(written, 2);
470        let (value, consumed) = read_varint(&buf[..written]).unwrap();
471        assert_eq!(value, 128);
472        assert_eq!(consumed, 2);
473    }
474
475    #[test]
476    fn varint_nine_bytes_uses_full_8bit_last_byte() {
477        // Pick a value that requires 9 bytes and has a low byte with the high bit set (0xFF).
478        // If the 9th byte were incorrectly treated as 7-bit, this would not round-trip.
479        let value: u64 = (1u64 << 56) | 0xFF;
480
481        let mut buf = [0u8; 9];
482        let written = write_varint(&mut buf, value);
483        assert_eq!(written, 9);
484        assert_eq!(buf[8], 0xFF);
485
486        // The first 8 bytes must all have the continuation bit set.
487        assert!(buf[..8].iter().all(|b| b & 0x80 != 0));
488
489        let (decoded, consumed) = read_varint(&buf).unwrap();
490        assert_eq!(decoded, value);
491        assert_eq!(consumed, 9);
492    }
493
494    #[test]
495    fn read_varint_empty() {
496        assert!(read_varint(&[]).is_none());
497    }
498
499    // -----------------------------------------------------------------------
500    // bd-1y7b: §11.2 Varint Edge Cases
501    // -----------------------------------------------------------------------
502
503    const BEAD_ID: &str = "bd-1y7b";
504
505    /// Byte-length boundary values: (min_value, max_value, expected_bytes).
506    const BYTE_BOUNDARIES: [(u64, u64, usize); 9] = [
507        (0, 0x7F, 1),                                  // 1 byte: [0, 127]
508        (0x80, 0x3FFF, 2),                             // 2 bytes: [128, 16383]
509        (0x4000, 0x001F_FFFF, 3),                      // 3 bytes: [16384, 2097151]
510        (0x0020_0000, 0x0FFF_FFFF, 4),                 // 4 bytes: [2097152, 268435455]
511        (0x1000_0000, 0x07_FFFF_FFFF, 5),              // 5 bytes: [268435456, 34359738367]
512        (0x08_0000_0000, 0x03FF_FFFF_FFFF, 6),         // 6 bytes
513        (0x0400_0000_0000, 0x01_FFFF_FFFF_FFFF, 7),    // 7 bytes
514        (0x02_0000_0000_0000, 0xFF_FFFF_FFFF_FFFF, 8), // 8 bytes
515        (0x0100_0000_0000_0000, u64::MAX, 9),          // 9 bytes
516    ];
517
518    #[test]
519    fn test_varint_1byte_boundary() {
520        let mut buf = [0u8; 9];
521        for value in [0u64, 1, 42, 126, 127] {
522            let written = write_varint(&mut buf, value);
523            assert_eq!(
524                written, 1,
525                "bead_id={BEAD_ID} case=1byte_boundary value={value}"
526            );
527            let (decoded, consumed) = read_varint(&buf[..written]).unwrap();
528            assert_eq!(decoded, value);
529            assert_eq!(consumed, 1);
530        }
531    }
532
533    #[test]
534    fn test_varint_2byte_boundary() {
535        let mut buf = [0u8; 9];
536        // min 2-byte: 128
537        let written = write_varint(&mut buf, 128);
538        assert_eq!(written, 2, "bead_id={BEAD_ID} case=2byte_min");
539        assert_eq!(
540            &buf[..2],
541            [0x81, 0x00],
542            "bead_id={BEAD_ID} case=2byte_min_bytes"
543        );
544        let (decoded, _) = read_varint(&buf[..2]).unwrap();
545        assert_eq!(decoded, 128);
546
547        // max 2-byte: 16383
548        let written = write_varint(&mut buf, 16383);
549        assert_eq!(written, 2, "bead_id={BEAD_ID} case=2byte_max");
550        assert_eq!(
551            &buf[..2],
552            [0xFF, 0x7F],
553            "bead_id={BEAD_ID} case=2byte_max_bytes"
554        );
555        let (decoded, _) = read_varint(&buf[..2]).unwrap();
556        assert_eq!(decoded, 16383);
557    }
558
559    #[test]
560    fn test_varint_3byte_boundary() {
561        let mut buf = [0u8; 9];
562        let written = write_varint(&mut buf, 16384);
563        assert_eq!(written, 3, "bead_id={BEAD_ID} case=3byte_min");
564        let (decoded, consumed) = read_varint(&buf[..written]).unwrap();
565        assert_eq!(decoded, 16384);
566        assert_eq!(consumed, 3);
567
568        let written = write_varint(&mut buf, 2_097_151);
569        assert_eq!(written, 3, "bead_id={BEAD_ID} case=3byte_max");
570        let (decoded, _) = read_varint(&buf[..written]).unwrap();
571        assert_eq!(decoded, 2_097_151);
572    }
573
574    #[test]
575    fn test_varint_4byte_boundary() {
576        let mut buf = [0u8; 9];
577        let written = write_varint(&mut buf, 2_097_152);
578        assert_eq!(written, 4, "bead_id={BEAD_ID} case=4byte_min");
579        let (decoded, _) = read_varint(&buf[..written]).unwrap();
580        assert_eq!(decoded, 2_097_152);
581
582        let written = write_varint(&mut buf, 268_435_455);
583        assert_eq!(written, 4, "bead_id={BEAD_ID} case=4byte_max");
584        let (decoded, _) = read_varint(&buf[..written]).unwrap();
585        assert_eq!(decoded, 268_435_455);
586    }
587
588    #[test]
589    fn test_varint_5byte_boundary() {
590        let mut buf = [0u8; 9];
591        let written = write_varint(&mut buf, 268_435_456);
592        assert_eq!(written, 5, "bead_id={BEAD_ID} case=5byte_min");
593        let (decoded, _) = read_varint(&buf[..written]).unwrap();
594        assert_eq!(decoded, 268_435_456);
595
596        let written = write_varint(&mut buf, 34_359_738_367);
597        assert_eq!(written, 5, "bead_id={BEAD_ID} case=5byte_max");
598        let (decoded, _) = read_varint(&buf[..written]).unwrap();
599        assert_eq!(decoded, 34_359_738_367);
600    }
601
602    #[test]
603    fn test_varint_6byte_boundary() {
604        let mut buf = [0u8; 9];
605        let written = write_varint(&mut buf, 34_359_738_368);
606        assert_eq!(written, 6, "bead_id={BEAD_ID} case=6byte_min");
607        let (decoded, _) = read_varint(&buf[..written]).unwrap();
608        assert_eq!(decoded, 34_359_738_368);
609
610        let written = write_varint(&mut buf, 4_398_046_511_103);
611        assert_eq!(written, 6, "bead_id={BEAD_ID} case=6byte_max");
612        let (decoded, _) = read_varint(&buf[..written]).unwrap();
613        assert_eq!(decoded, 4_398_046_511_103);
614    }
615
616    #[test]
617    fn test_varint_7byte_boundary() {
618        let mut buf = [0u8; 9];
619        let written = write_varint(&mut buf, 4_398_046_511_104);
620        assert_eq!(written, 7, "bead_id={BEAD_ID} case=7byte_min");
621        let (decoded, _) = read_varint(&buf[..written]).unwrap();
622        assert_eq!(decoded, 4_398_046_511_104);
623
624        let written = write_varint(&mut buf, 562_949_953_421_311);
625        assert_eq!(written, 7, "bead_id={BEAD_ID} case=7byte_max");
626        let (decoded, _) = read_varint(&buf[..written]).unwrap();
627        assert_eq!(decoded, 562_949_953_421_311);
628    }
629
630    #[test]
631    fn test_varint_8byte_boundary() {
632        let mut buf = [0u8; 9];
633        let written = write_varint(&mut buf, 562_949_953_421_312);
634        assert_eq!(written, 8, "bead_id={BEAD_ID} case=8byte_min");
635        let (decoded, _) = read_varint(&buf[..written]).unwrap();
636        assert_eq!(decoded, 562_949_953_421_312);
637
638        let written = write_varint(&mut buf, 72_057_594_037_927_935);
639        assert_eq!(written, 8, "bead_id={BEAD_ID} case=8byte_max");
640        let (decoded, _) = read_varint(&buf[..written]).unwrap();
641        assert_eq!(decoded, 72_057_594_037_927_935);
642    }
643
644    #[test]
645    fn test_varint_9byte_full_u64() {
646        let mut buf = [0u8; 9];
647
648        // min 9-byte value
649        let min9 = 72_057_594_037_927_936u64; // 2^56
650        let written = write_varint(&mut buf, min9);
651        assert_eq!(written, 9, "bead_id={BEAD_ID} case=9byte_min");
652        let (decoded, consumed) = read_varint(&buf).unwrap();
653        assert_eq!(decoded, min9);
654        assert_eq!(consumed, 9);
655
656        // u64::MAX
657        let written = write_varint(&mut buf, u64::MAX);
658        assert_eq!(written, 9, "bead_id={BEAD_ID} case=9byte_max");
659        assert_eq!(
660            buf,
661            [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
662            "bead_id={BEAD_ID} case=9byte_max_bytes u64::MAX must be all-0xFF"
663        );
664        let (decoded, consumed) = read_varint(&buf).unwrap();
665        assert_eq!(decoded, u64::MAX);
666        assert_eq!(consumed, 9);
667    }
668
669    #[test]
670    fn test_varint_9th_byte_all_bits() {
671        // Verify the 9th byte contributes ALL 8 bits, not just 7.
672        // Value chosen so the 9th byte has its high bit set (0x80+).
673        let mut buf = [0u8; 9];
674
675        for low_byte in [0x80u8, 0xFF, 0xAB, 0xFE] {
676            let value = (1u64 << 56) | u64::from(low_byte);
677            let written = write_varint(&mut buf, value);
678            assert_eq!(written, 9);
679            assert_eq!(
680                buf[8], low_byte,
681                "bead_id={BEAD_ID} case=9th_byte_all_bits low={low_byte:#04x}"
682            );
683            // First 8 bytes must all have continuation bit set.
684            for (i, &b) in buf[..8].iter().enumerate() {
685                assert_ne!(
686                    b & 0x80,
687                    0,
688                    "bead_id={BEAD_ID} case=continuation_bit byte={i}"
689                );
690            }
691            let (decoded, consumed) = read_varint(&buf).unwrap();
692            assert_eq!(decoded, value);
693            assert_eq!(consumed, 9);
694        }
695    }
696
697    #[test]
698    fn test_varint_signed_negative_rowid() {
699        let mut buf = [0u8; 9];
700
701        // i64::MIN as u64 via two's complement = 0x8000_0000_0000_0000
702        #[allow(clippy::cast_sign_loss)]
703        let min_u64 = i64::MIN as u64;
704        assert_eq!(min_u64, 0x8000_0000_0000_0000);
705
706        let written = write_varint(&mut buf, min_u64);
707        assert_eq!(written, 9, "bead_id={BEAD_ID} case=i64_min_length");
708        let (decoded, _) = read_varint(&buf[..written]).unwrap();
709        assert_eq!(decoded, min_u64);
710
711        // Cast back to i64
712        #[allow(clippy::cast_possible_wrap)]
713        let signed = decoded as i64;
714        assert_eq!(signed, i64::MIN, "bead_id={BEAD_ID} case=i64_min_roundtrip");
715    }
716
717    #[test]
718    fn test_varint_signed_minus_one() {
719        let mut buf = [0u8; 9];
720
721        // -1i64 as u64 = u64::MAX
722        #[allow(clippy::cast_sign_loss)]
723        let minus_one_u64 = (-1i64) as u64;
724        assert_eq!(minus_one_u64, u64::MAX);
725
726        let written = write_varint(&mut buf, minus_one_u64);
727        assert_eq!(written, 9, "bead_id={BEAD_ID} case=minus_one_length");
728        let (decoded, _) = read_varint(&buf[..written]).unwrap();
729
730        #[allow(clippy::cast_possible_wrap)]
731        let signed = decoded as i64;
732        assert_eq!(signed, -1, "bead_id={BEAD_ID} case=minus_one_roundtrip");
733    }
734
735    #[test]
736    fn test_varint_not_protobuf() {
737        // SQLite varint for u64::MAX: exactly 9 bytes.
738        // Protobuf LEB128 for u64::MAX: 10 bytes (7 bits per byte).
739        let mut buf = [0u8; 9];
740        let sqlite_len = write_varint(&mut buf, u64::MAX);
741        assert_eq!(
742            sqlite_len, 9,
743            "bead_id={BEAD_ID} case=not_protobuf SQLite u64::MAX must be 9 bytes"
744        );
745
746        // Compute protobuf LEB128 length for u64::MAX.
747        let protobuf_len = leb128_len(u64::MAX);
748        assert_eq!(
749            protobuf_len, 10,
750            "bead_id={BEAD_ID} case=not_protobuf protobuf u64::MAX must be 10 bytes"
751        );
752
753        // Also verify a mid-range 9-byte value.
754        let value = 1u64 << 56;
755        let sqlite_len = write_varint(&mut buf, value);
756        assert_eq!(sqlite_len, 9);
757        let protobuf_len = leb128_len(value);
758        assert_eq!(protobuf_len, 9); // protobuf is also 9 for 2^56 (57 bits / 7 = 9 bytes)
759
760        // But the BYTE SEQUENCES differ. Encode both and compare.
761        let mut leb_buf = [0u8; 10];
762        let leb_n = leb128_encode(&mut leb_buf, value);
763        assert_ne!(
764            &buf[..sqlite_len],
765            &leb_buf[..leb_n],
766            "bead_id={BEAD_ID} case=not_protobuf byte sequences must differ for 2^56"
767        );
768    }
769
770    /// Protobuf LEB128 encoding length (for comparison — NOT used by SQLite).
771    fn leb128_len(mut v: u64) -> usize {
772        let mut len = 1;
773        while v >= 0x80 {
774            v >>= 7;
775            len += 1;
776        }
777        len
778    }
779
780    /// Protobuf LEB128 encode (for comparison — NOT used by SQLite).
781    fn leb128_encode(buf: &mut [u8], mut v: u64) -> usize {
782        let mut i = 0;
783        while v >= 0x80 {
784            #[allow(clippy::cast_possible_truncation)]
785            {
786                buf[i] = (v as u8 & 0x7F) | 0x80;
787            }
788            v >>= 7;
789            i += 1;
790        }
791        #[allow(clippy::cast_possible_truncation)]
792        {
793            buf[i] = v as u8;
794        }
795        i + 1
796    }
797
798    #[test]
799    fn test_varint_all_boundaries_roundtrip() {
800        let mut buf = [0u8; 9];
801        for &(min_val, max_val, expected_len) in &BYTE_BOUNDARIES {
802            // Test min value
803            let written = write_varint(&mut buf, min_val);
804            assert_eq!(
805                written, expected_len,
806                "bead_id={BEAD_ID} case=boundary_min value={min_val} expected_len={expected_len}"
807            );
808            let (decoded, consumed) = read_varint(&buf[..written]).unwrap();
809            assert_eq!(decoded, min_val);
810            assert_eq!(consumed, expected_len);
811
812            // Test max value
813            let written = write_varint(&mut buf, max_val);
814            assert_eq!(
815                written, expected_len,
816                "bead_id={BEAD_ID} case=boundary_max value={max_val} expected_len={expected_len}"
817            );
818            let (decoded, consumed) = read_varint(&buf[..written]).unwrap();
819            assert_eq!(decoded, max_val);
820            assert_eq!(consumed, expected_len);
821
822            // Test varint_len matches
823            assert_eq!(varint_len(min_val), expected_len);
824            assert_eq!(varint_len(max_val), expected_len);
825        }
826    }
827
828    #[test]
829    fn test_varint_canonical_encoding() {
830        // Verify encoder always produces minimal-length encoding.
831        // For each boundary, the value just below the min should encode shorter.
832        for &(min_val, _, expected_len) in &BYTE_BOUNDARIES {
833            if min_val == 0 {
834                continue;
835            }
836            let below = min_val - 1;
837            let mut buf = [0u8; 9];
838            let written = write_varint(&mut buf, below);
839            assert!(
840                written < expected_len,
841                "bead_id={BEAD_ID} case=canonical value={below} written={written} \
842                 must be < {expected_len}"
843            );
844        }
845    }
846
847    #[test]
848    fn test_varint_decode_from_longer_buffer() {
849        // Decoder must read exactly N bytes and leave trailing bytes untouched.
850        let mut buf = [0xCC_u8; 16]; // fill with sentinel
851        let written = write_varint(&mut buf, 128); // 2 bytes
852        assert_eq!(written, 2);
853
854        // Read from the full 16-byte buffer.
855        let (decoded, consumed) = read_varint(&buf).unwrap();
856        assert_eq!(decoded, 128);
857        assert_eq!(
858            consumed, 2,
859            "bead_id={BEAD_ID} case=longer_buffer decoder must stop at 2 bytes"
860        );
861        // Trailing bytes must be untouched.
862        assert!(
863            buf[2..].iter().all(|&b| b == 0xCC),
864            "bead_id={BEAD_ID} case=longer_buffer trailing bytes must be untouched"
865        );
866    }
867
868    #[test]
869    fn test_varint_decode_truncated_returns_none() {
870        // A multi-byte varint with insufficient bytes should return None.
871        let mut buf = [0u8; 9];
872        let written = write_varint(&mut buf, 128); // 2 bytes: [0x81, 0x00]
873        assert_eq!(written, 2);
874
875        // Only provide 1 byte of the 2-byte encoding.
876        assert!(
877            read_varint(&buf[..1]).is_none(),
878            "bead_id={BEAD_ID} case=truncated_2byte"
879        );
880
881        // 9-byte value with only 8 bytes available.
882        let written = write_varint(&mut buf, u64::MAX);
883        assert_eq!(written, 9);
884        assert!(
885            read_varint(&buf[..8]).is_none(),
886            "bead_id={BEAD_ID} case=truncated_9byte"
887        );
888    }
889
890    #[test]
891    fn test_varint_golden_vectors() {
892        // Golden byte sequences derived from C SQLite's sqlite3PutVarint.
893        let cases: &[(u64, &[u8])] = &[
894            (0, &[0x00]),
895            (1, &[0x01]),
896            (127, &[0x7F]),
897            (128, &[0x81, 0x00]),
898            (129, &[0x81, 0x01]),
899            (16383, &[0xFF, 0x7F]),
900            (16384, &[0x81, 0x80, 0x00]),
901            (
902                u64::MAX,
903                &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
904            ),
905        ];
906
907        let mut buf = [0u8; 9];
908        for &(value, expected_bytes) in cases {
909            let written = write_varint(&mut buf, value);
910            assert_eq!(
911                &buf[..written],
912                expected_bytes,
913                "bead_id={BEAD_ID} case=golden_vector value={value}"
914            );
915            let (decoded, consumed) = read_varint(expected_bytes).unwrap();
916            assert_eq!(decoded, value);
917            assert_eq!(consumed, expected_bytes.len());
918        }
919    }
920
921    #[test]
922    fn test_varint_i64_max_and_nearby() {
923        let mut buf = [0u8; 9];
924
925        // i64::MAX = 2^63 - 1 = 0x7FFF_FFFF_FFFF_FFFF
926        #[allow(clippy::cast_sign_loss)]
927        let i64_max_u = i64::MAX as u64;
928        let written = write_varint(&mut buf, i64_max_u);
929        assert_eq!(written, 9, "bead_id={BEAD_ID} case=i64_max");
930        let (decoded, _) = read_varint(&buf[..written]).unwrap();
931        assert_eq!(decoded, i64_max_u);
932
933        // i64::MAX + 1 (first "negative" rowid as u64) = 0x8000_0000_0000_0000
934        let written = write_varint(&mut buf, i64_max_u + 1);
935        assert_eq!(written, 9);
936        let (decoded, _) = read_varint(&buf[..written]).unwrap();
937        assert_eq!(decoded, i64_max_u + 1);
938    }
939
940    // ================================================================
941    // Property-based tests (bd-309f)
942    // ================================================================
943    use proptest::prelude::*;
944
945    proptest! {
946        /// Varint roundtrip: write then read recovers the original value.
947        #[test]
948        fn prop_varint_roundtrip(value: u64) {
949            let mut buf = [0u8; 9];
950            let written = write_varint(&mut buf, value);
951            let (decoded, consumed) = read_varint(&buf[..written]).unwrap();
952            prop_assert_eq!(decoded, value);
953            prop_assert_eq!(consumed, written);
954        }
955
956        /// varint_len matches actual bytes written by write_varint.
957        #[test]
958        fn prop_varint_len_matches_write(value: u64) {
959            let mut buf = [0u8; 9];
960            let written = write_varint(&mut buf, value);
961            prop_assert_eq!(varint_len(value), written);
962        }
963
964        /// Varint encoding is canonical: no leading zero-value continuation bytes
965        /// (i.e. shorter encodings don't decode to the same value).
966        #[test]
967        fn prop_varint_canonical(value: u64) {
968            let mut buf = [0u8; 9];
969            let written = write_varint(&mut buf, value);
970            // If more than 1 byte, removing the first byte should NOT decode
971            // to the same value (proves minimality).
972            if written > 1 {
973                if let Some((alt, _)) = read_varint(&buf[1..written]) {
974                    prop_assert_ne!(alt, value, "shorter encoding yields same value — not canonical");
975                }
976            }
977        }
978
979        /// serial_type_for_integer always returns a type whose class is
980        /// Integer, Zero, or One (never Blob, Text, etc.).
981        #[test]
982        fn prop_integer_serial_type_class(value: i64) {
983            let st = serial_type_for_integer(value);
984            let class = classify_serial_type(st);
985            prop_assert!(
986                matches!(class, SerialTypeClass::Integer | SerialTypeClass::Zero | SerialTypeClass::One),
987                "integer value {value} got unexpected class {class:?} for serial type {st}"
988            );
989        }
990
991        /// serial_type_for_integer returns a type whose content size fits the value.
992        #[test]
993        fn prop_integer_serial_type_fits(value: i64) {
994            let st = serial_type_for_integer(value);
995            if let Some(size) = serial_type_len(st) {
996                // Zero-length types are only valid for 0 and 1
997                if size == 0 {
998                    prop_assert!(value == 0 || value == 1);
999                }
1000            }
1001        }
1002
1003        /// serial_type_for_text produces odd types >= 13 that classify as Text.
1004        #[test]
1005        fn prop_text_serial_type(len in 0u64..=1_000_000) {
1006            let st = serial_type_for_text(len);
1007            prop_assert!(st >= 13, "text type {st} < 13");
1008            prop_assert!(st % 2 == 1, "text type {st} is even");
1009            prop_assert_eq!(classify_serial_type(st), SerialTypeClass::Text);
1010            // Inverse: recover original length
1011            prop_assert_eq!(serial_type_len(st), Some(len));
1012        }
1013
1014        /// serial_type_for_blob produces even types >= 12 that classify as Blob.
1015        #[test]
1016        fn prop_blob_serial_type(len in 0u64..=1_000_000) {
1017            let st = serial_type_for_blob(len);
1018            prop_assert!(st >= 12, "blob type {st} < 12");
1019            prop_assert!(st % 2 == 0, "blob type {st} is odd");
1020            prop_assert_eq!(classify_serial_type(st), SerialTypeClass::Blob);
1021            // Inverse: recover original length
1022            prop_assert_eq!(serial_type_len(st), Some(len));
1023        }
1024
1025        /// Classification is exhaustive and deterministic for arbitrary serial types.
1026        #[test]
1027        fn prop_classification_deterministic(st: u64) {
1028            let class = classify_serial_type(st);
1029            // Re-classify to confirm determinism
1030            prop_assert_eq!(classify_serial_type(st), class);
1031            // Verify consistency with serial_type_len: every classified serial
1032            // type — including Reserved (10, 11) — now reports a finite
1033            // length. Canonical SQLite assigns reserved types zero payload
1034            // length; see serial_type_len's doc-comment.
1035            match class {
1036                SerialTypeClass::Reserved => {
1037                    prop_assert_eq!(serial_type_len(st), Some(0));
1038                }
1039                _ => {
1040                    prop_assert!(serial_type_len(st).is_some());
1041                }
1042            }
1043        }
1044
1045        /// SMALL_TYPE_SIZES matches serial_type_len for all indices 0..128.
1046        #[test]
1047        #[allow(clippy::cast_possible_truncation)]
1048        fn prop_small_type_table_consistent(i in 0u64..128) {
1049            let expected = match serial_type_len(i) {
1050                Some(n) if n <= 255 => n as u8,
1051                _ => 0,
1052            };
1053            prop_assert_eq!(SMALL_TYPE_SIZES[usize::try_from(i).unwrap()], expected);
1054        }
1055
1056        /// Varint encoding uses at most 9 bytes and at least 1 byte.
1057        #[test]
1058        fn prop_varint_len_bounds(value: u64) {
1059            let len = varint_len(value);
1060            prop_assert!((1..=9).contains(&len), "varint_len({value}) = {len}");
1061        }
1062
1063        /// For 9-byte varints, the first 8 bytes all have the continuation bit set.
1064        #[test]
1065        fn prop_nine_byte_varint_continuation_bits(value in 0x0100_0000_0000_0000u64..=u64::MAX) {
1066            let mut buf = [0u8; 9];
1067            let written = write_varint(&mut buf, value);
1068            if written == 9 {
1069                for (i, &byte) in buf[..8].iter().enumerate() {
1070                    prop_assert!(byte & 0x80 != 0, "byte {i} missing continuation bit for value {value}");
1071                }
1072            }
1073        }
1074
1075        /// read_varint on a truncated buffer returns None.
1076        #[test]
1077        fn prop_truncated_varint_returns_none(value: u64) {
1078            let mut buf = [0u8; 9];
1079            let written = write_varint(&mut buf, value);
1080            if written > 1 {
1081                // Truncate by removing the last byte
1082                prop_assert!(read_varint(&buf[..written - 1]).is_none() ||
1083                    read_varint(&buf[..written - 1]).unwrap().0 != value,
1084                    "truncated buffer should not decode to original value");
1085            }
1086        }
1087    }
1088}