1use crate::error::{Result, SqliteError};
34use crate::page::{BtreePage, PageKind};
35use crate::varint;
36
37#[derive(Debug, Clone, PartialEq)]
39pub enum Cell {
40 TableInterior { child: u32, rowid: i64 },
42 TableLeaf { rowid: i64, payload: Vec<u8> },
44 IndexInterior { child: u32, payload: Vec<u8> },
46 IndexLeaf { payload: Vec<u8> },
48}
49
50pub(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
64pub(crate) fn local_len(kind: PageKind, usable: usize, total: usize) -> usize {
66 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
85pub(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 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 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 assert_eq!(local_len(PageKind::LeafTable, 4096, 4061), 4061);
222 }
223
224 #[test]
225 fn index_pages_keep_far_less_than_table_pages() {
226 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 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 assert!(local + 4 <= usable, "{kind:?} {usable} {total}");
260 }
261 }
262 }
263 }
264}