use nebu::Goldilocks;
use super::{dim, NetworkStore, ShardStore};
use crate::types::Particle;
pub struct TieredStore {
hot: Box<dyn ShardStore>,
warm: Option<Box<dyn ShardStore>>,
cold: Option<Box<dyn ShardStore>>,
network: Option<Box<dyn NetworkStore>>,
}
impl TieredStore {
pub fn new(hot: Box<dyn ShardStore>) -> Self {
Self { hot, warm: None, cold: None, network: None }
}
pub fn with_warm(mut self, warm: Box<dyn ShardStore>) -> Self {
self.warm = Some(warm);
self
}
pub fn with_cold(mut self, cold: Box<dyn ShardStore>) -> Self {
self.cold = Some(cold);
self
}
pub fn with_network(mut self, net: Box<dyn NetworkStore>) -> Self {
self.network = Some(net);
self
}
pub fn promote(&mut self, dimension: u8, key: &[u8; 32]) -> bool {
let found = self.warm.as_ref()
.and_then(|w| w.get(dimension, key))
.or_else(|| self.cold.as_ref().and_then(|c| c.get(dimension, key)));
if let Some(slice) = found {
let owned = slice.to_vec();
self.hot.put(dimension, *key, owned);
true
} else {
false
}
}
pub fn evict(&mut self, dimension: u8, key: &[u8; 32]) {
if let Some(slice) = self.hot.get(dimension, key) {
let owned = slice.to_vec();
if let Some(warm) = &mut self.warm {
warm.put(dimension, *key, owned);
}
}
self.hot.remove(dimension, key);
}
pub fn fetch_content(&self, particle: &Particle) -> Option<Vec<u8>> {
self.network.as_ref()?.fetch(particle)
}
pub fn archive(&mut self) -> Option<[u8; 32]> {
self.cold.as_mut().map(|c| c.commit())
}
}
impl ShardStore for TieredStore {
fn get(&self, dimension: u8, key: &[u8; 32]) -> Option<&[Goldilocks]> {
if let Some(v) = self.hot.get(dimension, key) {
return Some(v);
}
if let Some(warm) = &self.warm {
if let Some(v) = warm.get(dimension, key) {
return Some(v);
}
}
if let Some(cold) = &self.cold {
if let Some(v) = cold.get(dimension, key) {
return Some(v);
}
}
None
}
fn put(&mut self, dimension: u8, key: [u8; 32], value: Vec<Goldilocks>) {
if dimension != dim::EPHEMERAL {
if let Some(warm) = &mut self.warm {
warm.put(dimension, key, value.clone());
}
}
self.hot.put(dimension, key, value);
}
fn dirty_entries(&self) -> &[(u8, [u8; 32], Vec<Goldilocks>)] {
self.hot.dirty_entries()
}
fn commit(&mut self) -> [u8; 32] {
let sub_root = self.hot.commit();
if let Some(warm) = &mut self.warm {
let _ = warm.commit();
}
sub_root
}
fn get_mut(&mut self, dimension: u8, key: &[u8; 32]) -> Option<&mut [Goldilocks]> {
self.hot.get_mut(dimension, key)
}
fn mark_dirty(&mut self, dimension: u8, key: [u8; 32]) {
self.hot.mark_dirty(dimension, key);
}
fn remove(&mut self, dimension: u8, key: &[u8; 32]) -> Option<Vec<Goldilocks>> {
let val = self.get(dimension, key).map(|s| s.to_vec())?;
self.hot.remove(dimension, key);
if let Some(warm) = &mut self.warm {
warm.remove(dimension, key);
}
if let Some(cold) = &mut self.cold {
cold.remove(dimension, key);
}
Some(val)
}
fn iter(&self, dimension: u8) -> Box<dyn Iterator<Item = (&[u8; 32], &[Goldilocks])> + '_> {
self.hot.iter(dimension)
}
}
impl Default for TieredStore {
fn default() -> Self {
Self::new(Box::new(super::mem::MemStore::new()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::mem::MemStore;
use nebu::Goldilocks;
fn g(v: u64) -> Goldilocks { Goldilocks::new(v) }
fn key(b: u8) -> [u8; 32] { [b; 32] }
#[test]
fn write_through_to_warm() {
let hot = Box::new(MemStore::new());
let warm = Box::new(MemStore::new());
let warm_ptr = &*warm as *const MemStore as usize;
let mut store = TieredStore::new(hot).with_warm(warm);
store.put(0, key(1), vec![g(42)]);
assert_eq!(store.hot.get(0, &key(1)), Some([g(42)].as_slice()));
assert_eq!(store.warm.as_ref().unwrap().get(0, &key(1)), Some([g(42)].as_slice()));
let _ = warm_ptr; }
#[test]
fn read_cascades_hot_then_warm() {
let hot = Box::new(MemStore::new());
let mut warm = Box::new(MemStore::new());
warm.put(0, key(2), vec![g(99)]);
let _ = hot.get(0, &key(2));
let store = TieredStore::new(hot).with_warm(warm);
assert_eq!(store.get(0, &key(2)), Some([g(99)].as_slice()));
}
#[test]
fn hot_hit_shadows_warm() {
let mut hot = Box::new(MemStore::new());
let mut warm = Box::new(MemStore::new());
hot.put(0, key(3), vec![g(1)]);
warm.put(0, key(3), vec![g(2)]);
let store = TieredStore::new(hot).with_warm(warm);
assert_eq!(store.get(0, &key(3)), Some([g(1)].as_slice()));
}
#[test]
fn promote_moves_warm_to_hot() {
let hot = Box::new(MemStore::new());
let mut warm = Box::new(MemStore::new());
warm.put(0, key(4), vec![g(77)]);
let mut store = TieredStore::new(hot).with_warm(warm);
assert!(store.hot.get(0, &key(4)).is_none());
let promoted = store.promote(0, &key(4));
assert!(promoted);
assert_eq!(store.hot.get(0, &key(4)), Some([g(77)].as_slice()));
}
#[test]
fn ephemeral_not_written_to_warm() {
let hot = Box::new(MemStore::new());
let warm = Box::new(MemStore::new());
let mut store = TieredStore::new(hot).with_warm(warm);
store.put(dim::EPHEMERAL, key(5), vec![g(123)]);
assert_eq!(store.hot.get(dim::EPHEMERAL, &key(5)), Some([g(123)].as_slice()));
assert!(store.warm.as_ref().unwrap().get(dim::EPHEMERAL, &key(5)).is_none(),
"EPHEMERAL must not be written to warm tier");
}
#[test]
fn ephemeral_not_in_dirty_after_commit() {
let mut store = TieredStore::default();
store.put(dim::EPHEMERAL, key(6), vec![g(7)]);
assert!(store.dirty_entries().is_empty(), "EPHEMERAL must not appear in dirty");
}
#[test]
fn remove_clears_from_all_tiers() {
let hot = Box::new(MemStore::new());
let warm = Box::new(MemStore::new());
let mut store = TieredStore::new(hot).with_warm(warm);
store.put(0, key(7), vec![g(55)]);
let removed = store.remove(0, &key(7));
assert_eq!(removed, Some(vec![g(55)]));
assert!(store.hot.get(0, &key(7)).is_none());
assert!(store.warm.as_ref().unwrap().get(0, &key(7)).is_none());
}
#[test]
fn get_mut_and_mark_dirty_roundtrip() {
let mut store = TieredStore::default();
store.put(0, key(8), vec![g(10), g(20)]);
store.commit();
{
let slice = store.get_mut(0, &key(8)).unwrap();
slice[0] = g(99);
}
store.mark_dirty(0, key(8));
let dirty = store.dirty_entries();
assert_eq!(dirty.len(), 1);
assert_eq!(dirty[0].2[0], g(99));
}
#[test]
fn iter_returns_dimension_entries() {
let mut store = TieredStore::default();
store.put(0, key(1), vec![g(1)]);
store.put(0, key(2), vec![g(2)]);
store.put(1, key(3), vec![g(3)]);
let dim0: Vec<_> = store.iter(0).collect();
assert_eq!(dim0.len(), 2);
let dim1: Vec<_> = store.iter(1).collect();
assert_eq!(dim1.len(), 1);
}
}