use std::io;
use std::sync::RwLock;
use anyhow::Context;
use redb::{Database, ReadableDatabase, StorageBackend};
use uuid::Uuid;
use super::BLOBS;
pub struct RedbBytes(Database);
impl RedbBytes {
pub fn parse(bytes: Vec<u8>) -> anyhow::Result<Self> {
let db = Database::builder()
.create_with_backend(BytesBackend(RwLock::new(bytes)))
.context("Failed to open in-memory redb")?;
Ok(Self(db))
}
pub fn blob(&self, key: Uuid) -> anyhow::Result<Vec<u8>> {
let table = self.0.begin_read()?.open_table(BLOBS)?;
let value = table
.get(key.as_bytes())?
.with_context(|| format!("Missing key {key} in redb bytes"))?;
Ok(value.value().to_vec())
}
}
#[derive(Debug)]
struct BytesBackend(RwLock<Vec<u8>>);
fn out_of_range() -> io::Error {
io::Error::new(io::ErrorKind::InvalidInput, "Index out-of-range")
}
impl StorageBackend for BytesBackend {
fn len(&self) -> Result<u64, io::Error> {
Ok(self.0.read().unwrap().len() as u64)
}
fn read(&self, offset: u64, out: &mut [u8]) -> Result<(), io::Error> {
let data = self.0.read().unwrap();
let offset = usize::try_from(offset).map_err(|_| out_of_range())?;
let end = offset.checked_add(out.len()).ok_or_else(out_of_range)?;
if end > data.len() {
return Err(out_of_range());
}
out.copy_from_slice(&data[offset..end]);
Ok(())
}
fn set_len(&self, len: u64) -> Result<(), io::Error> {
let len = usize::try_from(len).map_err(|_| out_of_range())?;
self.0.write().unwrap().resize(len, 0);
Ok(())
}
fn sync_data(&self) -> Result<(), io::Error> {
Ok(())
}
fn write(&self, offset: u64, data: &[u8]) -> Result<(), io::Error> {
let mut guard = self.0.write().unwrap();
let offset = usize::try_from(offset).map_err(|_| out_of_range())?;
let end = offset.checked_add(data.len()).ok_or_else(out_of_range)?;
if end > guard.len() {
return Err(out_of_range());
}
guard[offset..end].copy_from_slice(data);
Ok(())
}
}
#[cfg(test)]
mod tests {
use tempfile::tempdir;
use uuid::Uuid;
use super::RedbBytes;
use crate::redbstore::{REDB_FILE_NAME, RedbPath, insert_bytes};
#[test]
fn reads_blobs_from_cleanly_closed_db_bytes() {
let dir = tempdir().unwrap();
let key = Uuid::new_v4();
{
let path = RedbPath::in_dir(dir.path());
insert_bytes(&path, key, b"payload", "persisted");
}
let bytes = std::fs::read(dir.path().join(REDB_FILE_NAME)).unwrap();
let db = RedbBytes::parse(bytes).unwrap();
assert_eq!(db.blob(key).unwrap(), b"payload");
assert!(db.blob(Uuid::new_v4()).is_err());
}
}