use std::collections::BTreeMap;
use std::fmt;
use std::sync::{Arc, Mutex, MutexGuard};
use crate::store::NodeStore;
use crate::tree::{Cursor, Hash, TreeError};
use crate::wal::{LookupResult, WalBuffer};
use super::registry::BranchRegistryGuard;
pub use crate::ids::ShardId;
pub const DEFAULT_SHARD_ID: ShardId = 0;
pub type BranchWalBuffer = Arc<Mutex<WalBuffer>>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BranchError {
NoShards,
DuplicateShard { shard_id: ShardId },
UnknownShard { shard_id: ShardId },
BufferPoisoned { shard_id: ShardId },
Tree(TreeError),
}
impl fmt::Display for BranchError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoShards => write!(formatter, "branch requires at least one shard"),
Self::DuplicateShard { shard_id } => write!(
formatter,
"branch shard {shard_id} was provided more than once"
),
Self::UnknownShard { shard_id } => {
write!(formatter, "branch shard {shard_id} is not present")
}
Self::BufferPoisoned { shard_id } => {
write!(formatter, "branch shard {shard_id} WAL buffer is poisoned")
}
Self::Tree(error) => write!(formatter, "branch tree read error: {error}"),
}
}
}
impl std::error::Error for BranchError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Tree(error) => Some(error),
Self::NoShards
| Self::DuplicateShard { .. }
| Self::UnknownShard { .. }
| Self::BufferPoisoned { .. } => None,
}
}
}
impl From<TreeError> for BranchError {
fn from(error: TreeError) -> Self {
Self::Tree(error)
}
}
#[derive(Debug, Clone)]
pub struct BranchHandle {
primary_shard: ShardId,
primary: BranchShard,
additional_shards: BTreeMap<ShardId, BranchShard>,
registry_guard: Option<Arc<BranchRegistryGuard>>,
}
impl BranchHandle {
#[must_use]
pub fn new(root: Hash) -> Self {
Self {
primary_shard: DEFAULT_SHARD_ID,
primary: BranchShard::new(root),
additional_shards: BTreeMap::new(),
registry_guard: None,
}
}
pub fn from_shard_roots<I>(roots: I) -> Result<Self, BranchError>
where
I: IntoIterator<Item = (ShardId, Hash)>,
{
let mut iter = roots.into_iter();
let Some((primary_shard, primary_root)) = iter.next() else {
return Err(BranchError::NoShards);
};
let mut additional_shards = BTreeMap::new();
for (shard_id, root) in iter {
if shard_id == primary_shard
|| additional_shards
.insert(shard_id, BranchShard::new(root))
.is_some()
{
return Err(BranchError::DuplicateShard { shard_id });
}
}
Ok(Self {
primary_shard,
primary: BranchShard::new(primary_root),
additional_shards,
registry_guard: None,
})
}
#[must_use]
pub const fn fork_point(&self) -> Hash {
self.primary.fork_point
}
#[must_use]
pub const fn current_root(&self) -> Hash {
self.primary.current_root
}
#[must_use]
pub const fn primary_shard(&self) -> ShardId {
self.primary_shard
}
#[must_use]
pub fn shard_count(&self) -> usize {
self.additional_shards.len().saturating_add(1)
}
#[must_use]
pub fn contains_shard(&self, shard_id: ShardId) -> bool {
self.shard_state(shard_id).is_some()
}
#[must_use]
pub fn shard_fork_point(&self, shard_id: ShardId) -> Option<Hash> {
self.shard_state(shard_id).map(|state| state.fork_point)
}
#[must_use]
pub fn shard_current_root(&self, shard_id: ShardId) -> Option<Hash> {
self.shard_state(shard_id).map(|state| state.current_root)
}
#[must_use]
pub fn shard_buffer(&self, shard_id: ShardId) -> Option<&BranchWalBuffer> {
self.shard_state(shard_id).map(|state| &state.buffer)
}
pub fn shard_ids(&self) -> impl Iterator<Item = ShardId> + '_ {
std::iter::once(self.primary_shard).chain(self.additional_shards.keys().copied())
}
pub fn put<K, V>(&self, shard_id: ShardId, key: K, value: V) -> Result<(), BranchError>
where
K: AsRef<[u8]>,
V: AsRef<[u8]>,
{
let mut buffer = self.lock_buffer(shard_id)?;
buffer.put(key, value);
drop(buffer);
Ok(())
}
pub fn delete<K>(&self, shard_id: ShardId, key: K) -> Result<(), BranchError>
where
K: AsRef<[u8]>,
{
let mut buffer = self.lock_buffer(shard_id)?;
buffer.delete(key);
drop(buffer);
Ok(())
}
pub fn get<S>(
&self,
shard_id: ShardId,
store: &S,
key: &[u8],
) -> Result<Option<Vec<u8>>, BranchError>
where
S: NodeStore + ?Sized,
{
let lookup = {
let buffer = self.lock_buffer(shard_id)?;
buffer.get(key)
};
match lookup {
LookupResult::BufferedValue(value) => Ok(Some(value)),
LookupResult::BufferedDelete => Ok(None),
LookupResult::NotBuffered => {
let root = self
.shard_current_root(shard_id)
.ok_or(BranchError::UnknownShard { shard_id })?;
Cursor::new(store, root).get(key).map_err(BranchError::from)
}
}
}
pub(crate) fn fork_points(&self) -> impl Iterator<Item = Hash> + '_ {
std::iter::once(self.primary.fork_point).chain(
self.additional_shards
.values()
.map(|state| state.fork_point),
)
}
pub(crate) fn with_registry_guard(mut self, guard: BranchRegistryGuard) -> Self {
self.registry_guard = Some(Arc::new(guard));
self
}
fn shard_state(&self, shard_id: ShardId) -> Option<&BranchShard> {
if shard_id == self.primary_shard {
Some(&self.primary)
} else {
self.additional_shards.get(&shard_id)
}
}
fn lock_buffer(&self, shard_id: ShardId) -> Result<MutexGuard<'_, WalBuffer>, BranchError> {
let buffer = self
.shard_buffer(shard_id)
.ok_or(BranchError::UnknownShard { shard_id })?;
match buffer.lock() {
Ok(guard) => Ok(guard),
Err(poisoned) => {
drop(poisoned.into_inner());
Err(BranchError::BufferPoisoned { shard_id })
}
}
}
}
#[derive(Debug, Clone)]
struct BranchShard {
fork_point: Hash,
current_root: Hash,
buffer: BranchWalBuffer,
}
impl BranchShard {
fn new(root: Hash) -> Self {
Self {
fork_point: root,
current_root: root,
buffer: Arc::new(Mutex::new(WalBuffer::new())),
}
}
}
#[cfg(test)]
#[path = "handle_tests.rs"]
mod tests;