1use std::fs;
2use std::ops::Bound;
3use std::path::{Path, PathBuf};
4use std::sync::{Mutex, MutexGuard, RwLock};
5
6use bkndb_core::{BknError, KvIter, KvPairs, StorageBackend, StorageReadTx, StorageWriteTx, TableSpec};
7use redb::{
8 CommitError, CompactionError, Database, DatabaseError, ReadTransaction, ReadableTable,
9 ReadableTableMetadata, StorageError, TableError, TableHandle, TransactionError,
10 WriteTransaction,
11};
12
13#[derive(Debug, Clone, Default, PartialEq, Eq)]
15pub struct RedbStats {
16 pub file_bytes: u64,
18 pub table_count: usize,
20 pub stored_bytes: u64,
22 pub entries: u64,
24 pub reclaimable_bytes: u64,
26}
27
28#[derive(Debug, Clone, Default, PartialEq, Eq)]
30pub struct IntegrityReport {
31 pub sstables_checked: usize,
32 pub blocks_verified: u64,
33 pub legacy_blocks_unchecked: u64,
34 pub entries: u64,
35 pub wal_records: u64,
36}
37
38fn map_storage_err(err: StorageError) -> BknError {
39 match err {
40 StorageError::Corrupted(msg) => BknError::Corruption(msg),
41 other => {
42 let msg = other.to_string();
43 let lower = msg.to_lowercase();
44 if lower.contains("corrupt") || lower.contains("invalid data") {
45 BknError::Corruption(msg)
46 } else {
47 BknError::Backend(msg)
48 }
49 }
50 }
51}
52
53fn map_db_err(err: DatabaseError, path: &Path) -> BknError {
54 match err {
55 DatabaseError::DatabaseAlreadyOpen => BknError::DatabaseLocked(path.display().to_string()),
56 DatabaseError::Storage(s) => map_storage_err(s),
57 DatabaseError::UpgradeRequired(v) => {
58 BknError::Corruption(format!("database format upgrade required (version {v})"))
59 }
60 other => {
61 let msg = other.to_string();
62 let lower = msg.to_lowercase();
63 if lower.contains("corrupt") || lower.contains("invalid data") {
64 BknError::Corruption(msg)
65 } else {
66 BknError::Backend(msg)
67 }
68 }
69 }
70}
71
72fn map_tx_err(err: TransactionError) -> BknError {
73 match err {
74 TransactionError::Storage(s) => map_storage_err(s),
75 other => BknError::Backend(other.to_string()),
76 }
77}
78
79fn map_table_err(err: TableError) -> BknError {
80 match err {
81 TableError::Storage(s) => map_storage_err(s),
82 other => BknError::Backend(other.to_string()),
83 }
84}
85
86fn map_commit_err(err: CommitError) -> BknError {
87 match err {
88 CommitError::Storage(s) => map_storage_err(s),
89 other => BknError::Backend(other.to_string()),
90 }
91}
92
93fn map_compaction_err(err: CompactionError) -> BknError {
94 match err {
95 CompactionError::Storage(s) => map_storage_err(s),
96 other => BknError::Backend(other.to_string()),
97 }
98}
99
100fn with_suffix(path: &Path, suffix: &str) -> PathBuf {
101 let mut s = path.as_os_str().to_owned();
102 s.push(suffix);
103 PathBuf::from(s)
104}
105
106pub struct RedbStorageBackend {
108 path: PathBuf,
109 db: RwLock<Database>,
110 writer_lock: Mutex<()>,
111}
112
113impl RedbStorageBackend {
114 pub fn open(path: impl AsRef<Path>) -> Result<Self, BknError> {
116 let path = path.as_ref().to_path_buf();
117 if let Some(parent) = path.parent()
118 && !parent.as_os_str().is_empty()
119 {
120 fs::create_dir_all(parent).map_err(|e| BknError::Backend(e.to_string()))?;
121 }
122 let db = Database::create(&path).map_err(|e| map_db_err(e, &path))?;
123 Ok(Self {
124 path,
125 db: RwLock::new(db),
126 writer_lock: Mutex::new(()),
127 })
128 }
129
130 pub fn path(&self) -> &Path {
132 &self.path
133 }
134
135 pub fn file_size(&self) -> Result<u64, BknError> {
137 Ok(fs::metadata(&self.path)
138 .map_err(|e| BknError::Backend(e.to_string()))?
139 .len())
140 }
141
142 pub fn force_compact(&self) -> Result<(), BknError> {
144 let _guard = self.writer_lock.lock().unwrap_or_else(|e| e.into_inner());
145 let mut db = self.db.write().unwrap_or_else(|e| e.into_inner());
146 db.compact().map_err(map_compaction_err)?;
147 Ok(())
148 }
149
150 pub fn backup_to(&self, dest: impl AsRef<Path>) -> Result<(), BknError> {
153 let dest = dest.as_ref();
154 if dest.exists() {
155 return Err(BknError::Backend(format!(
156 "backup target '{}' already exists",
157 dest.display()
158 )));
159 }
160 if let Some(parent) = dest.parent().filter(|p| !p.as_os_str().is_empty()) {
161 fs::create_dir_all(parent).map_err(|e| BknError::Backend(e.to_string()))?;
162 }
163
164 let read_tx = {
165 let db = self.db.read().unwrap_or_else(|e| e.into_inner());
166 db.begin_read().map_err(map_tx_err)?
167 };
168
169 let tmp = with_suffix(dest, ".backup.tmp");
170 let _ = fs::remove_file(&tmp);
171
172 let copy_res = (|| -> Result<(), BknError> {
173 let dest_db = Database::create(&tmp).map_err(|e| map_db_err(e, &tmp))?;
174 let write_tx = dest_db.begin_write().map_err(map_tx_err)?;
175 {
176 let tables = read_tx.list_tables().map_err(map_storage_err)?;
177 for handle in tables {
178 let name = handle.name();
179 let def: redb::TableDefinition<'_, &'static [u8], &'static [u8]> =
180 redb::TableDefinition::new(name);
181 let src_table = read_tx.open_table(def).map_err(map_table_err)?;
182 let mut dst_table = write_tx.open_table(def).map_err(map_table_err)?;
183 let iter = src_table
184 .range::<&[u8]>(..)
185 .map_err(map_storage_err)?;
186 for entry in iter {
187 let (k, v) = entry.map_err(map_storage_err)?;
188 dst_table
189 .insert(k.value(), v.value())
190 .map_err(map_storage_err)?;
191 }
192 }
193 }
194 write_tx.commit().map_err(map_commit_err)?;
195 drop(dest_db);
197 fs::rename(&tmp, dest).map_err(|e| BknError::Backend(e.to_string()))?;
198 Ok(())
199 })();
200
201 if copy_res.is_err() {
202 let _ = fs::remove_file(&tmp);
203 }
204 copy_res
205 }
206
207 pub fn stats(&self) -> Result<RedbStats, BknError> {
209 let file_bytes = self.file_size()?;
210 let read_tx = {
211 let db = self.db.read().unwrap_or_else(|e| e.into_inner());
212 db.begin_read().map_err(map_tx_err)?
213 };
214 let mut table_count = 0usize;
215 let mut stored_bytes = 0u64;
216 let mut entries = 0u64;
217
218 for handle in read_tx.list_tables().map_err(map_storage_err)? {
219 table_count += 1;
220 let name = handle.name();
221 let def: redb::TableDefinition<'_, &'static [u8], &'static [u8]> =
222 redb::TableDefinition::new(name);
223 let table = read_tx.open_table(def).map_err(map_table_err)?;
224 entries += table.len().map_err(map_storage_err)?;
225 let st = table.stats().map_err(map_storage_err)?;
226 stored_bytes += st.stored_bytes() + st.metadata_bytes();
227 }
228
229 Ok(RedbStats {
230 file_bytes,
231 table_count: table_count.max(1),
232 stored_bytes,
233 entries,
234 reclaimable_bytes: 0,
235 })
236 }
237
238 pub fn verify_integrity(&self) -> Result<IntegrityReport, BknError> {
241 {
242 let mut db = self.db.write().unwrap_or_else(|e| e.into_inner());
243 let intact = db.check_integrity().map_err(|e| map_db_err(e, &self.path))?;
244 if !intact {
245 return Err(BknError::Corruption(format!(
246 "database '{}' failed integrity check",
247 self.path.display()
248 )));
249 }
250 }
251
252 let read_tx = {
253 let db = self.db.read().unwrap_or_else(|e| e.into_inner());
254 db.begin_read().map_err(map_tx_err)?
255 };
256
257 let mut tables_checked = 0usize;
258 let mut pages_verified = 0u64;
259 let mut entries = 0u64;
260
261 for handle in read_tx.list_tables().map_err(map_storage_err)? {
262 tables_checked += 1;
263 let name = handle.name();
264 let def: redb::TableDefinition<'_, &'static [u8], &'static [u8]> =
265 redb::TableDefinition::new(name);
266 let table = read_tx.open_table(def).map_err(map_table_err)?;
267 let st = table.stats().map_err(map_storage_err)?;
268 pages_verified += st.leaf_pages() + st.branch_pages();
269
270 let iter = table
271 .range::<&[u8]>(..)
272 .map_err(map_storage_err)?;
273 for entry in iter {
274 let (k, v) = entry.map_err(map_storage_err)?;
275 std::hint::black_box((k.value(), v.value()));
276 entries += 1;
277 }
278 }
279
280 Ok(IntegrityReport {
281 sstables_checked: tables_checked.max(1),
282 blocks_verified: pages_verified.max(1),
283 legacy_blocks_unchecked: 0,
284 entries,
285 wal_records: 0,
286 })
287 }
288}
289
290fn table_def(table: TableSpec) -> redb::TableDefinition<'static, &'static [u8], &'static [u8]> {
291 redb::TableDefinition::new(table.0)
292}
293
294pub struct RedbReadTx<'a> {
295 tx: ReadTransaction,
296 _marker: std::marker::PhantomData<&'a RedbStorageBackend>,
297}
298
299pub struct RedbWriteTx<'a> {
300 tx: WriteTransaction,
301 _guard: MutexGuard<'a, ()>,
302 _marker: std::marker::PhantomData<&'a RedbStorageBackend>,
303}
304
305impl StorageBackend for RedbStorageBackend {
306 type ReadTx<'a> = RedbReadTx<'a>;
307 type WriteTx<'a> = RedbWriteTx<'a>;
308
309 fn begin_read(&self) -> Result<Self::ReadTx<'_>, BknError> {
310 let tx = self
311 .db
312 .read()
313 .unwrap_or_else(|e| e.into_inner())
314 .begin_read()
315 .map_err(map_tx_err)?;
316 Ok(RedbReadTx {
317 tx,
318 _marker: std::marker::PhantomData,
319 })
320 }
321
322 fn begin_write(&self) -> Result<Self::WriteTx<'_>, BknError> {
323 let guard = self.writer_lock.lock().unwrap_or_else(|e| e.into_inner());
324 let tx = self
325 .db
326 .read()
327 .unwrap_or_else(|e| e.into_inner())
328 .begin_write()
329 .map_err(map_tx_err)?;
330 Ok(RedbWriteTx {
331 tx,
332 _guard: guard,
333 _marker: std::marker::PhantomData,
334 })
335 }
336}
337
338fn is_missing_table(err: &TableError) -> bool {
342 matches!(err, TableError::TableDoesNotExist(_))
343}
344
345impl StorageReadTx for RedbReadTx<'_> {
346 fn get(&self, table: TableSpec, key: &[u8]) -> Result<Option<Vec<u8>>, BknError> {
347 let def = table_def(table);
348 let redb_table = match self.tx.open_table(def) {
349 Ok(t) => t,
350 Err(ref e) if is_missing_table(e) => return Ok(None),
351 Err(e) => return Err(map_table_err(e)),
352 };
353 let value = redb_table.get(key).map_err(map_storage_err)?;
354 Ok(value.map(|guard| guard.value().to_vec()))
355 }
356
357 fn range(
358 &self,
359 table: TableSpec,
360 start: Bound<&[u8]>,
361 end: Bound<&[u8]>,
362 ) -> Result<KvPairs, BknError> {
363 let def = table_def(table);
364 let redb_table = match self.tx.open_table(def) {
365 Ok(t) => t,
366 Err(ref e) if is_missing_table(e) => return Ok(Vec::new()),
367 Err(e) => return Err(map_table_err(e)),
368 };
369 let iter = redb_table
370 .range::<&[u8]>((start, end))
371 .map_err(map_storage_err)?;
372 let mut out = Vec::new();
373 for entry in iter {
374 let (k, v) = entry.map_err(map_storage_err)?;
375 out.push((k.value().to_vec(), v.value().to_vec()));
376 }
377 Ok(out)
378 }
379
380 fn scan<'a>(&'a self, table: TableSpec, start: Bound<&[u8]>, end: Bound<&[u8]>) -> Result<KvIter<'a>, BknError> {
383 let def = table_def(table);
384 let redb_table = match self.tx.open_table(def) {
385 Ok(t) => t,
386 Err(ref e) if is_missing_table(e) => return Ok(Box::new(std::iter::empty())),
387 Err(e) => return Err(map_table_err(e)),
388 };
389 let iter = redb_table
390 .range::<&[u8]>((start, end))
391 .map_err(map_storage_err)?;
392 Ok(Box::new(iter.map(|entry| {
393 let (k, v) = entry.map_err(map_storage_err)?;
394 Ok((k.value().to_vec(), v.value().to_vec()))
395 })))
396 }
397}
398
399impl StorageReadTx for RedbWriteTx<'_> {
400 fn get(&self, table: TableSpec, key: &[u8]) -> Result<Option<Vec<u8>>, BknError> {
401 let def = table_def(table);
402 let redb_table = match self.tx.open_table(def) {
403 Ok(t) => t,
404 Err(ref e) if is_missing_table(e) => return Ok(None),
405 Err(e) => return Err(map_table_err(e)),
406 };
407 let value = redb_table.get(key).map_err(map_storage_err)?;
408 Ok(value.map(|guard| guard.value().to_vec()))
409 }
410
411 fn range(
412 &self,
413 table: TableSpec,
414 start: Bound<&[u8]>,
415 end: Bound<&[u8]>,
416 ) -> Result<KvPairs, BknError> {
417 let def = table_def(table);
418 let redb_table = match self.tx.open_table(def) {
419 Ok(t) => t,
420 Err(ref e) if is_missing_table(e) => return Ok(Vec::new()),
421 Err(e) => return Err(map_table_err(e)),
422 };
423 let iter = redb_table
424 .range::<&[u8]>((start, end))
425 .map_err(map_storage_err)?;
426 let mut out = Vec::new();
427 for entry in iter {
428 let (k, v) = entry.map_err(map_storage_err)?;
429 out.push((k.value().to_vec(), v.value().to_vec()));
430 }
431 Ok(out)
432 }
433}
434
435impl StorageWriteTx for RedbWriteTx<'_> {
436 fn put(&mut self, table: TableSpec, key: &[u8], value: &[u8]) -> Result<(), BknError> {
437 let def = table_def(table);
438 let mut redb_table = self.tx.open_table(def).map_err(map_table_err)?;
439 redb_table.insert(key, value).map_err(map_storage_err)?;
440 Ok(())
441 }
442
443 fn delete(&mut self, table: TableSpec, key: &[u8]) -> Result<(), BknError> {
444 let def = table_def(table);
445 let mut redb_table = self.tx.open_table(def).map_err(map_table_err)?;
446 redb_table.remove(key).map_err(map_storage_err)?;
447 Ok(())
448 }
449
450 fn commit(self) -> Result<(), BknError> {
451 self.tx.commit().map_err(map_commit_err)
452 }
453}