Skip to main content

quantydb_sqlite/
page.rs

1//! Pages and b-tree page headers.
2//!
3//! Pages are numbered from 1 and are all the same size. Page 1 is special
4//! in exactly one way: the 100 byte database header sits in front of its
5//! b-tree header, so everything on page 1 is shifted by 100 bytes. Getting
6//! that wrong is the classic first bug in a SQLite reader, so the offset
7//! lives in one place here and nowhere else.
8//!
9//! Every offset read out of a page is checked against the usable size
10//! before it is used. A cell pointer that points into the page header, past
11//! the end of the page, or into the reserved trailer is a malformed file,
12//! not a slice to be taken on faith.
13
14use crate::error::{Result, SqliteError};
15
16/// What a b-tree page holds. Table pages are keyed by rowid, index pages by
17/// the indexed values.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum PageKind {
20    InteriorIndex,
21    InteriorTable,
22    LeafIndex,
23    LeafTable,
24}
25
26impl PageKind {
27    fn from_byte(b: u8) -> Option<PageKind> {
28        match b {
29            2 => Some(PageKind::InteriorIndex),
30            5 => Some(PageKind::InteriorTable),
31            10 => Some(PageKind::LeafIndex),
32            13 => Some(PageKind::LeafTable),
33            _ => None,
34        }
35    }
36
37    pub fn is_interior(self) -> bool {
38        matches!(self, PageKind::InteriorIndex | PageKind::InteriorTable)
39    }
40
41    pub fn is_table(self) -> bool {
42        matches!(self, PageKind::InteriorTable | PageKind::LeafTable)
43    }
44
45    /// Interior pages carry a right most child pointer, which makes their
46    /// header four bytes longer.
47    fn header_len(self) -> usize {
48        if self.is_interior() {
49            12
50        } else {
51            8
52        }
53    }
54}
55
56/// One b-tree page, with its header parsed and its cell pointers validated.
57pub struct BtreePage {
58    pub number: u32,
59    pub kind: PageKind,
60    /// Child page holding keys greater than every key on this page.
61    /// Interior pages only.
62    pub right_most: Option<u32>,
63    bytes: Vec<u8>,
64    usable: usize,
65    cell_pointers: Vec<u16>,
66}
67
68impl BtreePage {
69    /// Parse `bytes` as page `number`. `usable` is the page size minus the
70    /// reserved trailer, `body_offset` is 100 on page 1 and 0 elsewhere.
71    pub(crate) fn parse(
72        number: u32,
73        bytes: Vec<u8>,
74        usable: usize,
75        body_offset: usize,
76    ) -> Result<BtreePage> {
77        // an empty database has a page 1 that has been allocated but not
78        // written; every other page must carry a real header
79        if body_offset + 8 > usable {
80            return Err(SqliteError::malformed(
81                number,
82                "page is too small to hold a b-tree header",
83            ));
84        }
85
86        let kind = PageKind::from_byte(bytes[body_offset]).ok_or_else(|| {
87            SqliteError::malformed(
88                number,
89                format!(
90                    "page type {} is not one of 2, 5, 10 or 13",
91                    bytes[body_offset]
92                ),
93            )
94        })?;
95        let header_len = kind.header_len();
96        if body_offset + header_len > usable {
97            return Err(SqliteError::malformed(
98                number,
99                "page is too small to hold an interior b-tree header",
100            ));
101        }
102
103        let cell_count = be16(&bytes, body_offset + 3) as usize;
104        let pointers_end = body_offset + header_len + cell_count * 2;
105        if pointers_end > usable {
106            return Err(SqliteError::malformed(
107                number,
108                format!("{cell_count} cell pointers do not fit in {usable} usable bytes"),
109            ));
110        }
111
112        // 0 means 65536, which is the one page size that does not fit the
113        // field. an empty page points its content area at the very end.
114        let content_start = match be16(&bytes, body_offset + 5) {
115            0 => 65536,
116            n => n as usize,
117        };
118        if content_start > usable {
119            return Err(SqliteError::malformed(
120                number,
121                format!("cell content area starts at {content_start}, past the usable {usable}"),
122            ));
123        }
124        if cell_count > 0 && content_start < pointers_end {
125            return Err(SqliteError::malformed(
126                number,
127                format!(
128                    "cell content area starts at {content_start}, inside the header and pointer \
129                     array which end at {pointers_end}"
130                ),
131            ));
132        }
133
134        let mut cell_pointers = Vec::with_capacity(cell_count);
135        for i in 0..cell_count {
136            let at = be16(&bytes, body_offset + header_len + i * 2) as usize;
137            if at < pointers_end || at >= usable {
138                return Err(SqliteError::malformed(
139                    number,
140                    format!(
141                        "cell {i} points at offset {at}, outside the content area \
142                         {pointers_end}..{usable}"
143                    ),
144                ));
145            }
146            cell_pointers.push(at as u16);
147        }
148
149        let right_most = if kind.is_interior() {
150            let child = be32(&bytes, body_offset + 8);
151            if child == 0 {
152                return Err(SqliteError::malformed(
153                    number,
154                    "interior page has a right most child pointer of 0",
155                ));
156            }
157            Some(child)
158        } else {
159            None
160        };
161
162        Ok(BtreePage {
163            number,
164            kind,
165            right_most,
166            bytes,
167            usable,
168            cell_pointers,
169        })
170    }
171
172    pub fn cell_count(&self) -> usize {
173        self.cell_pointers.len()
174    }
175
176    /// The bytes of cell `index`, from where it starts to the end of the
177    /// usable area. Cells are variable length and their real end is only
178    /// known once the payload header has been read, so this is the widest
179    /// slice a cell may occupy, not its exact extent.
180    pub fn cell(&self, index: usize) -> Result<&[u8]> {
181        let at = *self.cell_pointers.get(index).ok_or_else(|| {
182            SqliteError::malformed(
183                self.number,
184                format!("cell {index} requested, page has {}", self.cell_count()),
185            )
186        })? as usize;
187        Ok(&self.bytes[at..self.usable])
188    }
189}
190
191pub(crate) fn be16(bytes: &[u8], at: usize) -> u16 {
192    u16::from_be_bytes([bytes[at], bytes[at + 1]])
193}
194
195pub(crate) fn be32(bytes: &[u8], at: usize) -> u32 {
196    u32::from_be_bytes([bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]])
197}