Skip to content

Commit

Permalink
Add example showing one way of storing out of band values
Browse files Browse the repository at this point in the history
  • Loading branch information
cberner committed Feb 11, 2023
1 parent 1d6c2f3 commit f5eba2d
Showing 1 changed file with 120 additions and 0 deletions.
120 changes: 120 additions & 0 deletions examples/special_values.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use redb::{
Database, Error, ReadableTable, RedbKey, RedbValue, Table, TableDefinition, WriteTransaction,
};
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::marker::PhantomData;

const TABLE: TableDefinition<u64, &str> = 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<K, V>,
) -> SpecialValuesTable<'db, 'txn, K, V> {
let def: TableDefinition<K, (u64, u64)> = 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<V>,
}

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<V> {
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<V: RedbValue + 'static> {
data: Vec<u8>,
_value_type: PhantomData<V>,
}

impl<V: RedbValue + 'static> ValueAccessor<V> {
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(())
}

0 comments on commit f5eba2d

Please sign in to comment.