Skip to main content

quantydb_sqlite/
cell.rs

1//! Cells: what a b-tree page actually stores, and where a payload goes when
2//! it does not fit.
3//!
4//! The four page kinds have four different cell layouts, and only three of
5//! them carry a payload at all. A table interior cell is just a child
6//! pointer and a rowid, which is why a table b-tree can be walked by key
7//! without touching a single record.
8//!
9//! The interesting part is the spill rule. A payload larger than the page
10//! keeps a prefix on the page and puts the rest in a chain of overflow
11//! pages, but the size of that prefix is not simply "as much as fits". The
12//! format picks it so that a payload never leaves an almost empty overflow
13//! page behind, using these definitions, all with truncating integer
14//! division:
15//!
16//! ```text
17//! U = usable page size
18//! X = U - 35                     for table leaf pages
19//! X = ((U - 12) * 64 / 255) - 23 for index pages
20//! M = ((U - 12) * 32 / 255) - 23
21//!
22//! P <= X          -> all P bytes stay local
23//! otherwise K = M + ((P - M) % (U - 4))
24//!   K <= X        -> K bytes stay local
25//!   K > X         -> M bytes stay local
26//! ```
27//!
28//! Getting this wrong does not fail loudly. It reads a payload that is off
29//! by a few bytes, which then decodes into a record that looks plausible
30//! and is wrong, so the fixture in tests/data deliberately holds values on
31//! both sides of every boundary this rule has.
32
33use crate::error::{Result, SqliteError};
34use crate::page::{BtreePage, PageKind};
35use crate::varint;
36
37/// A cell with its payload assembled, overflow pages included.
38#[derive(Debug, Clone, PartialEq)]
39pub enum Cell {
40    /// Child pointer plus the largest rowid in that subtree.
41    TableInterior { child: u32, rowid: i64 },
42    /// A row: its rowid and the record that holds its columns.
43    TableLeaf { rowid: i64, payload: Vec<u8> },
44    /// Child pointer plus the separator key that lives on this page.
45    IndexInterior { child: u32, payload: Vec<u8> },
46    /// An index entry: the indexed values and the rowid they point at.
47    IndexLeaf { payload: Vec<u8> },
48}
49
50/// Where a cell's payload lives before overflow pages are followed.
51pub(crate) struct PayloadRef<'a> {
52    pub total: usize,
53    pub local: &'a [u8],
54    pub overflow: Option<u32>,
55}
56
57pub(crate) enum CellLayout<'a> {
58    TableInterior { child: u32, rowid: i64 },
59    TableLeaf { rowid: i64, payload: PayloadRef<'a> },
60    IndexInterior { child: u32, payload: PayloadRef<'a> },
61    IndexLeaf { payload: PayloadRef<'a> },
62}
63
64/// How many bytes of a `total` byte payload stay on the page.
65pub(crate) fn local_len(kind: PageKind, usable: usize, total: usize) -> usize {
66    // the header validation guarantees usable >= 480, so none of these
67    // subtractions can wrap
68    let max_local = if kind == PageKind::LeafTable {
69        usable - 35
70    } else {
71        ((usable - 12) * 64 / 255) - 23
72    };
73    if total <= max_local {
74        return total;
75    }
76    let min_local = ((usable - 12) * 32 / 255) - 23;
77    let k = min_local + (total - min_local) % (usable - 4);
78    if k <= max_local {
79        k
80    } else {
81        min_local
82    }
83}
84
85/// Parse cell `index` of `page`. `payload_limit` is the largest payload the
86/// file could possibly hold, which stops a hostile length varint from
87/// asking for an allocation the file cannot back.
88pub(crate) fn parse<'a>(
89    page: &'a BtreePage,
90    index: usize,
91    usable: usize,
92    payload_limit: u64,
93) -> Result<CellLayout<'a>> {
94    let bytes = page.cell(index)?;
95    let number = page.number;
96    let malformed = |reason: String| SqliteError::malformed(number, reason);
97
98    Ok(match page.kind {
99        PageKind::InteriorTable => {
100            if bytes.len() < 4 {
101                return Err(malformed(format!(
102                    "cell {index} is too short for a child pointer"
103                )));
104            }
105            let child = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
106            if child == 0 {
107                return Err(malformed(format!("cell {index} points at page 0")));
108            }
109            let (rowid, _) = varint::read(&bytes[4..])?;
110            CellLayout::TableInterior { child, rowid }
111        }
112        PageKind::LeafTable => {
113            let (total, size_len) = varint::read_unsigned(bytes)?;
114            let (rowid, rowid_len) = varint::read(&bytes[size_len..])?;
115            let payload = payload_ref(
116                page.kind,
117                &bytes[size_len + rowid_len..],
118                total,
119                usable,
120                payload_limit,
121                &malformed,
122            )?;
123            CellLayout::TableLeaf { rowid, payload }
124        }
125        PageKind::InteriorIndex => {
126            if bytes.len() < 4 {
127                return Err(malformed(format!(
128                    "cell {index} is too short for a child pointer"
129                )));
130            }
131            let child = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
132            if child == 0 {
133                return Err(malformed(format!("cell {index} points at page 0")));
134            }
135            let (total, size_len) = varint::read_unsigned(&bytes[4..])?;
136            let payload = payload_ref(
137                page.kind,
138                &bytes[4 + size_len..],
139                total,
140                usable,
141                payload_limit,
142                &malformed,
143            )?;
144            CellLayout::IndexInterior { child, payload }
145        }
146        PageKind::LeafIndex => {
147            let (total, size_len) = varint::read_unsigned(bytes)?;
148            let payload = payload_ref(
149                page.kind,
150                &bytes[size_len..],
151                total,
152                usable,
153                payload_limit,
154                &malformed,
155            )?;
156            CellLayout::IndexLeaf { payload }
157        }
158    })
159}
160
161fn payload_ref<'a>(
162    kind: PageKind,
163    after_header: &'a [u8],
164    total: u64,
165    usable: usize,
166    payload_limit: u64,
167    malformed: &dyn Fn(String) -> SqliteError,
168) -> Result<PayloadRef<'a>> {
169    if total > payload_limit {
170        return Err(malformed(format!(
171            "a cell claims a {total} byte payload, more than the {payload_limit} bytes the file holds"
172        )));
173    }
174    let total = total as usize;
175    let local = local_len(kind, usable, total);
176    if local > after_header.len() {
177        return Err(malformed(format!(
178            "a cell keeps {local} bytes on the page but only {} are left on it",
179            after_header.len()
180        )));
181    }
182
183    let overflow = if local < total {
184        // the four bytes right after the local part are the first overflow
185        // page number
186        let rest = &after_header[local..];
187        if rest.len() < 4 {
188            return Err(malformed(
189                "a spilled cell has no room for its overflow pointer".to_string(),
190            ));
191        }
192        let first = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]);
193        if first == 0 {
194            return Err(malformed(
195                "a spilled cell points at overflow page 0".to_string(),
196            ));
197        }
198        Some(first)
199    } else {
200        None
201    };
202
203    Ok(PayloadRef {
204        total,
205        local: &after_header[..local],
206        overflow,
207    })
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn payloads_that_fit_stay_whole() {
216        // u = 512: a table leaf keeps up to 477 bytes
217        assert_eq!(local_len(PageKind::LeafTable, 512, 0), 0);
218        assert_eq!(local_len(PageKind::LeafTable, 512, 476), 476);
219        assert_eq!(local_len(PageKind::LeafTable, 512, 477), 477);
220        // u = 4096, the default page size
221        assert_eq!(local_len(PageKind::LeafTable, 4096, 4061), 4061);
222    }
223
224    #[test]
225    fn index_pages_keep_far_less_than_table_pages() {
226        // ((512 - 12) * 64 / 255) - 23 = 102
227        assert_eq!(local_len(PageKind::LeafIndex, 512, 102), 102);
228        assert_eq!(local_len(PageKind::InteriorIndex, 512, 102), 102);
229        assert!(local_len(PageKind::LeafIndex, 512, 103) < 103);
230    }
231
232    #[test]
233    fn spilled_payloads_follow_the_k_rule() {
234        // u = 512, so m = 39, x = 477 and u - 4 = 508
235        let m = 39;
236        for total in [478usize, 500, 546, 1000, 5000, 50000] {
237            let k = m + (total - m) % 508;
238            let expected = if k <= 477 { k } else { m };
239            assert_eq!(
240                local_len(PageKind::LeafTable, 512, total),
241                expected,
242                "payload of {total} bytes"
243            );
244        }
245    }
246
247    #[test]
248    fn the_local_part_never_exceeds_the_page() {
249        for usable in [480usize, 512, 1024, 4096, 65536] {
250            for total in [0usize, 1, 100, 479, 480, 1000, 65535, 65536, 1 << 20] {
251                for kind in [
252                    PageKind::LeafTable,
253                    PageKind::LeafIndex,
254                    PageKind::InteriorIndex,
255                ] {
256                    let local = local_len(kind, usable, total);
257                    assert!(local <= total, "{kind:?} {usable} {total}");
258                    // room for the four byte overflow pointer has to remain
259                    assert!(local + 4 <= usable, "{kind:?} {usable} {total}");
260                }
261            }
262        }
263    }
264}