use crate::api::{
PrefixResult, StorageBackend, StorageLockedView, StorageReadView, StorageWriteBatch,
};
use crate::error::StoreError;
use rustc_hash::FxHashMap;
use std::path::Path;
use std::sync::{Arc, RwLock};
type Table = FxHashMap<Vec<u8>, Vec<u8>>;
type Database = FxHashMap<&'static str, Table>;
#[derive(Debug)]
pub struct InMemoryBackend {
inner: Arc<RwLock<Arc<Database>>>,
}
impl InMemoryBackend {
pub fn open() -> Result<Self, StoreError> {
Ok(Self {
inner: Arc::new(RwLock::new(Arc::new(Database::default()))),
})
}
}
impl StorageBackend for InMemoryBackend {
fn clear_table(&self, table: &str) -> Result<(), StoreError> {
let mut db = self
.inner
.write()
.map_err(|_| StoreError::Custom("Failed to acquire write lock".to_string()))?;
let db_mut = Arc::make_mut(&mut *db);
if let Some(table_ref) = db_mut.get_mut(table) {
table_ref.clear();
}
Ok(())
}
fn begin_read(&self) -> Result<Arc<dyn StorageReadView>, StoreError> {
let snapshot = self
.inner
.read()
.map_err(|_| StoreError::Custom("Failed to acquire read lock".to_string()))?
.clone();
Ok(Arc::new(InMemoryReadTx { snapshot }))
}
fn begin_write(&self) -> Result<Box<dyn StorageWriteBatch + 'static>, StoreError> {
Ok(Box::new(InMemoryWriteTx {
backend: self.inner.clone(),
}))
}
fn begin_locked(
&self,
table_name: &'static str,
) -> Result<Box<dyn StorageLockedView>, StoreError> {
let snapshot = self
.inner
.read()
.map_err(|_| StoreError::Custom("Failed to acquire read lock".to_string()))?
.clone();
Ok(Box::new(InMemoryLocked {
snapshot,
table_name,
}))
}
fn create_checkpoint(&self, _path: &Path) -> Result<(), StoreError> {
Ok(())
}
}
pub struct InMemoryLocked {
snapshot: Arc<Database>,
table_name: &'static str,
}
impl StorageLockedView for InMemoryLocked {
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StoreError> {
Ok(self
.snapshot
.get(&self.table_name)
.and_then(|table_ref| table_ref.get(key))
.cloned())
}
}
pub struct InMemoryReadTx {
snapshot: Arc<Database>,
}
impl StorageReadView for InMemoryReadTx {
fn get(&self, table: &str, key: &[u8]) -> Result<Option<Vec<u8>>, StoreError> {
Ok(self
.snapshot
.get(table)
.and_then(|table_ref| table_ref.get(key))
.cloned())
}
fn prefix_iterator(
&self,
table: &str,
prefix: &[u8],
) -> Result<Box<dyn Iterator<Item = PrefixResult> + '_>, StoreError> {
let table_data = self.snapshot.get(table).cloned().unwrap_or_default();
let mut entries: Vec<(Vec<u8>, Vec<u8>)> = table_data
.into_iter()
.filter(|(key, _)| key.as_slice() >= prefix)
.collect();
entries.sort_unstable_by(|(left, _), (right, _)| left.cmp(right));
let results: Vec<PrefixResult> = entries
.into_iter()
.map(|(k, v)| Ok((k.into_boxed_slice(), v.into_boxed_slice())))
.collect();
Ok(Box::new(results.into_iter()))
}
fn first_key(&self, table: &'static str) -> Result<Option<Vec<u8>>, StoreError> {
let Some(table_data) = self.snapshot.get(table) else {
return Ok(None);
};
Ok(table_data.keys().min().cloned())
}
fn last_key(&self, table: &'static str) -> Result<Option<Vec<u8>>, StoreError> {
let Some(table_data) = self.snapshot.get(table) else {
return Ok(None);
};
Ok(table_data.keys().max().cloned())
}
}
pub struct InMemoryWriteTx {
backend: Arc<RwLock<Arc<Database>>>,
}
impl StorageWriteBatch for InMemoryWriteTx {
fn put_batch(
&mut self,
table: &'static str,
batch: Vec<(Vec<u8>, Vec<u8>)>,
) -> Result<(), StoreError> {
let mut db = self
.backend
.write()
.map_err(|_| StoreError::Custom("Failed to acquire write lock".to_string()))?;
let db_mut = Arc::make_mut(&mut *db);
let table_ref = db_mut.entry(table).or_default();
for (key, value) in batch {
table_ref.insert(key, value);
}
Ok(())
}
fn delete(&mut self, table: &str, key: &[u8]) -> Result<(), StoreError> {
let mut db = self
.backend
.write()
.map_err(|_| StoreError::Custom("Failed to acquire write lock".to_string()))?;
let db_mut = Arc::make_mut(&mut *db);
if let Some(table_ref) = db_mut.get_mut(table) {
table_ref.remove(key);
}
Ok(())
}
fn delete_range(
&mut self,
table: &'static str,
start: &[u8],
end: &[u8],
) -> Result<(), StoreError> {
let mut db = self
.backend
.write()
.map_err(|_| StoreError::Custom("Failed to acquire write lock".to_string()))?;
let db_mut = Arc::make_mut(&mut *db);
if let Some(table_ref) = db_mut.get_mut(table) {
table_ref.retain(|k, _| !(k.as_slice() >= start && k.as_slice() < end));
}
Ok(())
}
fn merge(&mut self, table: &'static str, key: &[u8], operand: &[u8]) -> Result<(), StoreError> {
if table != crate::api::tables::TRANSACTION_LOCATIONS {
return Err(StoreError::Custom(format!(
"merge not supported for table {table}"
)));
}
let mut db = self
.backend
.write()
.map_err(|_| StoreError::Custom("Failed to acquire write lock".to_string()))?;
let db_mut = Arc::make_mut(&mut *db);
let table_ref = db_mut.entry(table).or_default();
let existing = table_ref.get(key).map(|v| v.as_slice());
let merged = crate::store::tx_locations_merge(existing, std::iter::once(operand))
.ok_or_else(|| StoreError::Custom("tx_locations_merge returned None".to_string()))?;
table_ref.insert(key.to_vec(), merged);
Ok(())
}
fn commit(&mut self) -> Result<(), StoreError> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prefix_iterator_seeks_and_walks_forward() {
let backend = InMemoryBackend::open().unwrap();
let mut write = backend.begin_write().unwrap();
let keys = ["b1", "a2", "c", "a1"].map(|key| (key.as_bytes().to_vec(), vec![]));
write.put_batch("table", keys.to_vec()).unwrap();
write.commit().unwrap();
let read = backend.begin_read().unwrap();
let keys_from = |start: &[u8]| {
read.prefix_iterator("table", start)
.unwrap()
.map(|item| String::from_utf8(item.unwrap().0.to_vec()).unwrap())
.collect::<Vec<_>>()
};
assert_eq!(keys_from(b""), ["a1", "a2", "b1", "c"]);
assert_eq!(keys_from(b"a2"), ["a2", "b1", "c"]);
assert_eq!(keys_from(b"a3"), ["b1", "c"]);
assert_eq!(keys_from(b"d"), Vec::<String>::new());
}
}