use std::sync::Arc;
use crate::actor::VersionedValue;
use crate::shard::FragmentId;
use super::{
KvStore, PrefixDeleteOutcome, PrefixDeleteSupport, StoreBinding, StoreCommitDurability,
StoreError, StoreIdentity,
};
pub struct PrefixedStore {
inner: Arc<dyn KvStore>,
prefix: [u8; 4],
}
impl PrefixedStore {
pub fn new(inner: Arc<dyn KvStore>, fragment: FragmentId) -> Self {
Self {
inner,
prefix: fragment.0.to_be_bytes(),
}
}
}
impl KvStore for PrefixedStore {
fn bind(&self, _identity: &StoreIdentity) -> Result<StoreBinding, StoreError> {
Err(StoreError(
"PrefixedStore is a fragment view and cannot be attached as a Node base store"
.to_string(),
))
}
fn load(&self) -> Result<Vec<(Vec<u8>, VersionedValue)>, StoreError> {
let mut out = Vec::new();
for (key, value) in self.inner.load_prefix(&self.prefix)? {
if let Some(stripped) = key.strip_prefix(self.prefix.as_slice()) {
out.push((stripped.to_vec(), value));
}
}
Ok(out)
}
fn commit_durability(&self) -> StoreCommitDurability {
self.inner.commit_durability()
}
fn stable_fence(&self) -> Result<StoreCommitDurability, StoreError> {
self.inner.stable_fence()
}
fn write_batch(&self, writes: &[(Vec<u8>, VersionedValue)]) -> Result<(), StoreError> {
let prefixed: Vec<(Vec<u8>, VersionedValue)> = writes
.iter()
.map(|(k, v)| {
let mut buf = Vec::with_capacity(4 + k.len());
buf.extend_from_slice(&self.prefix);
buf.extend_from_slice(k);
(buf, v.clone())
})
.collect();
self.inner.write_batch(&prefixed)
}
fn prefix_delete_support(&self) -> PrefixDeleteSupport {
self.inner.prefix_delete_support()
}
fn delete_prefix(&self, prefix: &[u8]) -> Result<PrefixDeleteOutcome, StoreError> {
let mut prefixed = Vec::with_capacity(self.prefix.len() + prefix.len());
prefixed.extend_from_slice(&self.prefix);
prefixed.extend_from_slice(prefix);
self.inner.delete_prefix(&prefixed)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prefixed_store_isolates_fragments() {
use crate::shard::FragmentId;
use std::sync::Arc;
struct Rec {
written: std::sync::Mutex<Vec<(Vec<u8>, VersionedValue)>>,
}
impl KvStore for Rec {
fn load(&self) -> Result<Vec<(Vec<u8>, VersionedValue)>, StoreError> {
Ok(self.written.lock().unwrap().clone())
}
fn commit_durability(&self) -> StoreCommitDurability {
StoreCommitDurability::Stable
}
fn stable_fence(&self) -> Result<StoreCommitDurability, StoreError> {
Ok(StoreCommitDurability::Stable)
}
fn write_batch(&self, writes: &[(Vec<u8>, VersionedValue)]) -> Result<(), StoreError> {
self.written
.lock()
.unwrap()
.extend(writes.iter().map(|(k, v)| (k.clone(), v.clone())));
Ok(())
}
fn prefix_delete_support(&self) -> PrefixDeleteSupport {
PrefixDeleteSupport::Supported
}
fn delete_prefix(&self, prefix: &[u8]) -> Result<PrefixDeleteOutcome, StoreError> {
self.written
.lock()
.unwrap()
.retain(|(key, _)| !key.starts_with(prefix));
Ok(PrefixDeleteOutcome::Deleted)
}
}
let inner = Arc::new(Rec {
written: std::sync::Mutex::new(Vec::new()),
});
let f0 = PrefixedStore::new(inner.clone(), FragmentId(0));
let f1 = PrefixedStore::new(inner.clone(), FragmentId(1));
assert_eq!(f0.commit_durability(), StoreCommitDurability::Stable);
assert_eq!(f0.stable_fence().unwrap(), StoreCommitDurability::Stable);
assert_eq!(f0.prefix_delete_support(), PrefixDeleteSupport::Supported);
let v = VersionedValue::new(b"x".to_vec(), 1, "n".to_string());
f0.write_batch(&[(b"key".to_vec(), v.clone())]).unwrap();
f1.write_batch(&[(b"key".to_vec(), v.clone())]).unwrap();
let written = inner.written.lock().unwrap();
assert_eq!(written.len(), 2);
assert_ne!(written[0].0, written[1].0);
assert!(written[0].0.starts_with(&0u32.to_be_bytes()));
assert!(written[1].0.starts_with(&1u32.to_be_bytes()));
drop(written);
assert_eq!(f0.load().unwrap().len(), 1);
assert_eq!(f0.load().unwrap()[0].0, b"key");
assert_eq!(f1.load().unwrap().len(), 1);
assert_eq!(f1.load().unwrap()[0].0, b"key");
assert_eq!(f0.delete_prefix(&[]).unwrap(), PrefixDeleteOutcome::Deleted);
assert!(f0.load().unwrap().is_empty());
assert_eq!(f1.load().unwrap().len(), 1);
assert!(f1
.bind(&StoreIdentity::new("node".to_string(), 2))
.err()
.expect("a PrefixedStore was accepted as a Node base store")
.to_string()
.contains("cannot be attached as a Node base store"));
}
}