use redb::{ Database, Error, ReadableTable, RedbKey, RedbValue, Table, TableDefinition, TableHandle, WriteTransaction, }; use std::fs::{File, OpenOptions}; use std::io::{Read, Seek, SeekFrom, Write}; use std::marker::PhantomData; const TABLE: TableDefinition = TableDefinition::new("my_data"); struct SpecialValuesDb { database: Database, file: File, } impl SpecialValuesDb { fn new() -> Self { SpecialValuesDb { database: Database::create("index.redb").unwrap(), file: OpenOptions::new() .write(true) .create(true) .read(true) .open("values.dat") .unwrap(), } } fn begin_txn(&mut self) -> SpecialValuesTransaction { SpecialValuesTransaction { inner: self.database.begin_write().unwrap(), file: &mut self.file, } } } struct SpecialValuesTransaction<'db> { inner: WriteTransaction<'db>, file: &'db mut File, } impl<'db> SpecialValuesTransaction<'db> { fn open_table<'txn, K: RedbKey + 'static, V: RedbValue + 'static>( &'txn mut self, table: TableDefinition, ) -> SpecialValuesTable<'db, 'txn, K, V> { let def: TableDefinition = TableDefinition::new(table.name()); SpecialValuesTable { inner: self.inner.open_table(def).unwrap(), file: self.file, _value_type: Default::default(), } } fn commit(self) { self.file.sync_all().unwrap(); self.inner.commit().unwrap(); } } struct SpecialValuesTable<'db, 'txn, K: RedbKey + 'static, V: RedbValue + 'static> { inner: Table<'db, 'txn, K, (u64, u64)>, file: &'txn mut File, _value_type: PhantomData, } impl<'db, 'txn, K: RedbKey + 'static, V: RedbValue + 'static> SpecialValuesTable<'db, 'txn, K, V> { fn insert(&mut self, key: K::SelfType<'_>, value: V::SelfType<'_>) { // Append to end of file let offset = self.file.seek(SeekFrom::End(0)).unwrap(); let value = V::as_bytes(&value); self.file.write_all(value.as_ref()).unwrap(); self.inner .insert(key, (offset, value.as_ref().len() as u64)) .unwrap(); } fn get(&mut self, key: K::SelfType<'_>) -> ValueAccessor { let (offset, length) = self.inner.get(key).unwrap().unwrap().value(); self.file.seek(SeekFrom::Start(offset)).unwrap(); let mut data = vec![0u8; length as usize]; self.file.read_exact(data.as_mut_slice()).unwrap(); ValueAccessor { data, _value_type: Default::default(), } } } struct ValueAccessor { data: Vec, _value_type: PhantomData, } impl ValueAccessor { fn value(&self) -> V::SelfType<'_> { V::from_bytes(&self.data) } } /// redb is not designed to support very large values, or values with special requirements (such as alignment or mutability). /// There's a hard limit of slightly less than 4GiB per value, and performance is likely to be poor when mutating values above a few megabytes. /// Additionally, because redb is copy-on-write, mutating a value in-place is not possible, and therefore mutating large values is slow. /// Storing values with alignment requirements is also not supported. /// /// This example demonstrates one way to handle such values, via a sidecar file. fn main() -> Result<(), Error> { let mut db = SpecialValuesDb::new(); let mut txn = db.begin_txn(); { let mut table = txn.open_table(TABLE); table.insert(0, "hello world"); } txn.commit(); let mut txn = db.begin_txn(); let mut table = txn.open_table(TABLE); assert_eq!(table.get(0).value(), "hello world"); Ok(()) }