1mod btree;
5mod meta;
6mod page;
7
8use std::fs::File;
9use std::io::Read;
10use std::path::Path;
11
12pub use meta::{BRANCH_PAGE_FLAG, BUCKET_VALUE_FLAG, LEAF_PAGE_FLAG, MAGIC, META_PAGE_FLAG, META_STRUCT_OFFSET};
13use meta::{parse_meta_at, parse_page_size, BucketHeader};
14use page::{collect_leaf_entries, LeafEntry};
15use crate::btree::{collect_tree_entries, find_in_page, find_in_tree};
16
17#[derive(Debug, thiserror::Error)]
18pub enum Error {
19 #[error("io: {0}")]
20 Io(#[from] std::io::Error),
21 #[error("invalid bolt magic")]
22 InvalidMagic,
23 #[error("unsupported page size {0}")]
24 InvalidPageSize(u32),
25 #[error("invalid page id {0}")]
26 InvalidPageId(u64),
27 #[error("corrupt bolt structure: {0}")]
28 Corrupt(&'static str),
29}
30
31pub type Result<T> = std::result::Result<T, Error>;
32
33pub struct Bolt {
35 data: Vec<u8>,
36 page_size: usize,
37 root: BucketHeader,
38}
39
40pub struct Stats {
42 pub page_size: usize,
43 pub page_count: usize,
44 pub bytes: usize,
45}
46
47pub struct Tx<'a> {
49 db: &'a Bolt,
50}
51
52pub struct Bucket<'a> {
54 db: &'a Bolt,
55 root: u64,
56 inline: Option<Vec<u8>>, }
58
59pub struct BucketCursor<'a> {
61 entries: Vec<LeafEntry>,
62 idx: usize,
63 _db: &'a Bolt,
64}
65
66impl Bolt {
67 pub fn open_ro<P: AsRef<Path>>(path: P) -> Result<Self> {
70 let mut file = File::open(path)?;
71 let mut data = Vec::new();
72 file.read_to_end(&mut data)?;
73 if data.len() < 4096 {
74 return Err(Error::Corrupt("file too small"));
75 }
76
77 let page_size = parse_page_size(&data)? as usize;
78 let meta0 = parse_meta_at(&data, page_size, 0).ok();
79 let meta1 = if data.len() >= page_size * 2 {
80 parse_meta_at(&data, page_size, page_size).ok()
81 } else {
82 None
83 };
84 let meta = match (meta0, meta1) {
85 (Some(m0), Some(m1)) => if m1.txid >= m0.txid { m1 } else { m0 },
86 (Some(m0), None) => m0,
87 (None, Some(m1)) => m1,
88 (None, None) => return Err(Error::Corrupt("no valid meta pages")),
89 };
90
91 Ok(Self {
92 data,
93 page_size,
94 root: meta.root,
95 })
96 }
97
98 pub fn stats(&self) -> Stats {
99 Stats {
100 page_size: self.page_size,
101 page_count: self.data.len() / self.page_size,
102 bytes: self.data.len(),
103 }
104 }
105
106 pub fn begin(&self) -> Result<Tx<'_>> {
107 Ok(Tx { db: self })
108 }
109
110 fn read_page(&self, pgid: u64) -> Result<&[u8]> {
111 let offset = (pgid as usize)
112 .checked_mul(self.page_size)
113 .ok_or(Error::InvalidPageId(pgid))?;
114 if offset + self.page_size > self.data.len() {
115 return Err(Error::InvalidPageId(pgid));
116 }
117 let base = &self.data[offset..offset + self.page_size];
118 let overflow = u32::from_le_bytes(base[12..16].try_into().unwrap()) as usize;
119 let end = offset + self.page_size * (1 + overflow);
120 if end > self.data.len() {
121 return Err(Error::InvalidPageId(pgid));
122 }
123 Ok(&self.data[offset..end])
124 }
125}
126
127impl<'a> Tx<'a> {
128 pub fn bucket_path(&'a self, parts: &[&[u8]]) -> Option<Bucket<'a>> {
129 let mut current = self.root_bucket();
130 for name in parts {
131 current = current.bucket(name)?;
132 }
133 Some(current)
134 }
135
136 pub fn bucket(&'a self, name: &[u8]) -> Option<Bucket<'a>> {
137 let root = self.root_bucket();
138 root.bucket(name)
139 }
140
141 fn root_bucket(&'a self) -> Bucket<'a> {
142 Bucket {
143 db: self.db,
144 root: self.db.root.root,
145 inline: None,
146 }
147 }
148}
149
150impl<'a> Bucket<'a> {
151 pub fn bucket(&self, name: &[u8]) -> Option<Bucket<'a>> {
152 let entry = self.find_entry(name)?;
153 if entry.flags & BUCKET_VALUE_FLAG == 0 {
154 return None;
155 }
156 let hdr = ok_opt(parse_bucket_header(&entry.value))?;
157 let inline = if hdr.root == 0 && entry.value.len() > 16 {
158 Some(entry.value[16..].to_vec())
159 } else {
160 None
161 };
162 Some(Bucket {
163 db: self.db,
164 root: hdr.root,
165 inline,
166 })
167 }
168
169 pub fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
170 self.find_entry(key).map(|e| e.value)
171 }
172
173 pub fn iter_buckets(&self) -> Vec<(Vec<u8>, Bucket<'a>)> {
174 let mut out = Vec::new();
175 if let Ok(entries) = self.collect_entries() {
176 for e in entries {
177 if e.flags & BUCKET_VALUE_FLAG != 0 {
178 if let Some(hdr) = ok_opt(parse_bucket_header(&e.value)) {
179 let inline = if hdr.root == 0 && e.value.len() > 16 {
180 Some(e.value[16..].to_vec())
181 } else {
182 None
183 };
184 out.push((e.key, Bucket { db: self.db, root: hdr.root, inline }));
185 }
186 }
187 }
188 }
189 out
190 }
191
192 fn find_entry(&self, key: &[u8]) -> Option<LeafEntry> {
193 if self.root == 0 {
194 let data = self.inline.as_ref()?;
195 return find_in_page(self.db.page_size, data, key).ok().flatten();
196 }
197 find_in_tree(self.db, self.root, key).ok().flatten()
198 }
199
200 fn collect_entries(&self) -> Result<Vec<LeafEntry>> {
201 if self.root == 0 {
202 let data = self.inline.as_ref().ok_or(Error::Corrupt("inline bucket missing data"))?;
203 return collect_leaf_entries(self.db.page_size, data);
204 }
205 collect_tree_entries(self.db, self.root)
206 }
207
208 pub fn cursor(&self) -> Result<BucketCursor<'a>> {
209 let entries = self.collect_entries()?;
210 Ok(BucketCursor { entries, idx: 0, _db: self.db })
211 }
212}
213
214fn parse_bucket_header(buf: &[u8]) -> Result<BucketHeader> {
215 if buf.len() < 16 {
216 return Err(Error::Corrupt("bucket header too small"));
217 }
218 Ok(BucketHeader {
219 root: u64::from_le_bytes(buf[..8].try_into().unwrap()),
220 _sequence: u64::from_le_bytes(buf[8..16].try_into().unwrap()),
221 })
222}
223
224pub(crate) fn ok_opt<T>(res: Result<T>) -> Option<T> {
225 res.ok()
226}
227
228pub struct CursorEntry {
229 pub key: Vec<u8>,
230 pub value: Vec<u8>,
231 pub flags: u32,
232}
233
234impl<'a> Iterator for BucketCursor<'a> {
235 type Item = CursorEntry;
236
237 fn next(&mut self) -> Option<Self::Item> {
238 if self.idx >= self.entries.len() {
239 return None;
240 }
241 let entry = self.entries[self.idx].clone();
242 self.idx += 1;
243 Some(CursorEntry { key: entry.key, value: entry.value, flags: entry.flags })
244 }
245}
246
247#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn ok_opt_maps_result_option() {
254 assert_eq!(ok_opt::<u32>(Ok(5)), Some(5));
255 assert!(ok_opt::<u32>(Err(Error::InvalidMagic)).is_none());
256 }
257
258 #[test]
259 fn stats_reports_page_count() {
260 let mut db = Bolt {
261 data: vec![0u8; 8192],
262 page_size: 4096,
263 root: BucketHeader { root: 0, _sequence: 0 },
264 };
265 let stats = db.stats();
266 assert_eq!(stats.page_size, 4096);
267 assert_eq!(stats.page_count, 2);
268 assert_eq!(stats.bytes, 8192);
269 db.data[0] = 0;
271 }
272}