use core::fmt;
use std::collections::HashSet;
use std::sync::Mutex;
use crate::error::StoreError;
use crate::id::Cid;
pub trait OpHeadsStore: Send + Sync + fmt::Debug {
fn current(&self) -> Result<Vec<Cid>, StoreError>;
fn update(&self, new: Cid, supersedes: &[Cid]) -> Result<(), StoreError>;
}
#[derive(Default)]
pub struct MemoryOpHeadsStore {
heads: Mutex<HashSet<Cid>>,
}
impl MemoryOpHeadsStore {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn len(&self) -> usize {
self.heads.lock().expect("mutex not poisoned").len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl fmt::Debug for MemoryOpHeadsStore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MemoryOpHeadsStore {{ n_heads: {} }}", self.len())
}
}
impl OpHeadsStore for MemoryOpHeadsStore {
fn current(&self) -> Result<Vec<Cid>, StoreError> {
let mut v: Vec<Cid> = self
.heads
.lock()
.expect("mutex not poisoned")
.iter()
.cloned()
.collect();
v.sort();
Ok(v)
}
fn update(&self, new: Cid, supersedes: &[Cid]) -> Result<(), StoreError> {
let mut h = self.heads.lock().expect("mutex not poisoned");
h.insert(new);
for s in supersedes {
h.remove(s);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::id::{CODEC_RAW, Multihash};
fn raw(n: u32) -> Cid {
Cid::new(CODEC_RAW, Multihash::sha2_256(&n.to_be_bytes()))
}
#[test]
fn empty_store_has_no_heads() {
let s = MemoryOpHeadsStore::new();
assert!(s.current().unwrap().is_empty());
assert_eq!(s.len(), 0);
}
#[test]
fn single_head_round_trips() {
let s = MemoryOpHeadsStore::new();
let root = raw(1);
s.update(root.clone(), &[]).unwrap();
assert_eq!(s.current().unwrap(), vec![root]);
}
#[test]
fn update_supersedes_old_head() {
let s = MemoryOpHeadsStore::new();
let root = raw(1);
s.update(root.clone(), &[]).unwrap();
let child = raw(2);
s.update(child.clone(), &[root]).unwrap();
assert_eq!(s.current().unwrap(), vec![child]);
}
#[test]
fn concurrent_commits_produce_multiple_heads() {
let s = MemoryOpHeadsStore::new();
let root = raw(1);
s.update(root.clone(), &[]).unwrap();
let left = raw(10);
let right = raw(11);
s.update(left.clone(), std::slice::from_ref(&root)).unwrap();
s.update(right.clone(), &[root]).unwrap();
let heads = s.current().unwrap();
assert_eq!(heads.len(), 2, "two concurrent heads expected");
assert!(heads.contains(&left));
assert!(heads.contains(&right));
}
#[test]
fn supersedes_returns_sorted() {
let s = MemoryOpHeadsStore::new();
s.update(raw(3), &[]).unwrap();
s.update(raw(1), &[]).unwrap();
s.update(raw(2), &[]).unwrap();
let heads = s.current().unwrap();
assert!(heads.windows(2).all(|w| w[0] < w[1]));
}
}