use crate::tree::Hash;
use super::commit::BranchCommitError;
use super::handle::{BranchError, BranchHandle, ShardId};
use super::lifecycle::create_branch_record;
use super::policy::{BranchKind, check_fork_into};
use super::refstore::BranchRefStore;
use super::registry::BranchRegistry;
use super::snapshot::Timestamp;
#[must_use]
pub fn fork(root: Hash) -> BranchHandle {
BranchHandle::new(root)
}
#[must_use]
pub fn fork_registered(root: Hash, registry: &BranchRegistry) -> BranchHandle {
let branch = fork(root);
let guard = registry.register_roots(anchor_role_pins(&branch));
branch.with_registry_guard(guard)
}
pub fn fork_shards<I>(roots: I) -> Result<BranchHandle, BranchError>
where
I: IntoIterator<Item = (ShardId, Hash)>,
{
BranchHandle::from_shard_roots(roots)
}
pub fn fork_shards_registered<I>(
roots: I,
registry: &BranchRegistry,
) -> Result<BranchHandle, BranchError>
where
I: IntoIterator<Item = (ShardId, Hash)>,
{
let branch = fork_shards(roots)?;
let guard = registry.register_roots(anchor_role_pins(&branch));
Ok(branch.with_registry_guard(guard))
}
pub fn create_branch_with_kind<I>(
name: &str,
roots: I,
kind: BranchKind,
refs: &mut BranchRefStore,
registry: &BranchRegistry,
timestamp: Timestamp,
) -> Result<BranchHandle, BranchCommitError>
where
I: IntoIterator<Item = (ShardId, Hash)>,
{
create_branch_record(name, roots, kind, None, refs, registry, timestamp)
}
pub fn fork_from_with_kind(
parent: &BranchHandle,
child_name: &str,
child_kind: BranchKind,
refs: &mut BranchRefStore,
registry: &BranchRegistry,
timestamp: Timestamp,
) -> Result<BranchHandle, BranchCommitError> {
if parent.registry_guard().is_none() {
return Err(BranchError::UnregisteredParent.into());
}
let binding = parent
.binding_snapshot()
.ok_or(BranchCommitError::UnnamedBranch)?;
let parent_record = refs.cas_check(&binding.name, binding.created, binding.seq)?;
let source = parent_record.name.clone();
let source_kind = parent_record.kind;
let source_lineage = parent_record
.resolved_namespace_lineage()
.map(str::to_owned);
let destination_lineage = match child_kind {
BranchKind::Namespace => Some(child_name.to_owned()),
BranchKind::Work => source_lineage.clone(),
};
check_fork_into(
&source,
source_kind,
source_lineage.as_deref(),
child_name,
child_kind,
destination_lineage.as_deref(),
)?;
let states = parent.lock_states_ascending()?;
let heads: Vec<(ShardId, Hash)> = states
.iter()
.map(|(shard_id, state)| (*shard_id, state.current_root))
.collect();
let stored_lineage = match child_kind {
BranchKind::Namespace => None,
BranchKind::Work => destination_lineage,
};
let child = create_branch_record(
child_name,
heads,
child_kind,
stored_lineage,
refs,
registry,
timestamp,
)?;
drop(states);
Ok(child)
}
fn anchor_role_pins(branch: &BranchHandle) -> impl Iterator<Item = Hash> + '_ {
branch.fork_points().flat_map(|anchor| [anchor, anchor])
}
#[cfg(test)]
mod tests {
use super::{fork, fork_registered, fork_shards, fork_shards_registered};
use crate::branch::{BranchError, BranchRegistry};
use crate::tree::Hash;
fn hash(byte: u8) -> Hash {
Hash::from_bytes([byte; 32])
}
#[test]
fn fork_records_current_root_as_fork_point_and_current_root() {
let root = hash(1);
let branch = fork(root);
assert_eq!(branch.fork_point(), root);
assert_eq!(branch.current_root(), root);
}
#[test]
fn fork_allocates_one_empty_buffer_for_single_shard() -> Result<(), BranchError> {
let branch = fork(hash(2));
assert_eq!(branch.shard_count(), 1);
assert!(!branch.shard_is_dirty(branch.primary_shard())?);
Ok(())
}
#[test]
fn fork_shards_allocates_one_empty_buffer_per_shard() -> Result<(), BranchError> {
let branch = fork_shards([(3, hash(3)), (5, hash(5)), (8, hash(8))])?;
assert_eq!(branch.shard_count(), 3);
for shard_id in [3, 5, 8] {
assert!(!branch.shard_is_dirty(shard_id)?);
}
Ok(())
}
#[test]
fn fork_shards_preserves_each_independent_root() -> Result<(), BranchError> {
let shard_three_root = hash(3);
let shard_five_root = hash(5);
let branch = fork_shards([(3, shard_three_root), (5, shard_five_root)])?;
assert_eq!(branch.shard_fork_point(3), Some(shard_three_root));
assert_eq!(branch.shard_current_root(3), Some(shard_three_root));
assert_eq!(branch.shard_fork_point(5), Some(shard_five_root));
assert_eq!(branch.shard_current_root(5), Some(shard_five_root));
Ok(())
}
#[test]
fn fork_single_shard_has_no_tree_store_to_read_or_copy() {
let roots_for_large_and_small_trees_are_both_just_hashes = [hash(9), hash(10)];
let first = fork(roots_for_large_and_small_trees_are_both_just_hashes[0]);
let second = fork(roots_for_large_and_small_trees_are_both_just_hashes[1]);
assert_eq!(first.shard_count(), 1);
assert_eq!(second.shard_count(), 1);
}
#[test]
fn fork_registered_registers_root_until_drop() {
let registry = BranchRegistry::new();
let root = hash(11);
let branch = fork_registered(root, ®istry);
assert!(registry.live_roots().contains(&root));
drop(branch);
assert!(!registry.live_roots().contains(&root));
}
#[test]
fn fork_shards_registered_registers_all_shard_roots() -> Result<(), BranchError> {
let registry = BranchRegistry::new();
let shard_three_root = hash(12);
let shard_five_root = hash(13);
let branch =
fork_shards_registered([(3, shard_three_root), (5, shard_five_root)], ®istry)?;
let live = registry.live_roots();
assert!(live.contains(&shard_three_root));
assert!(live.contains(&shard_five_root));
drop(branch);
assert!(registry.live_roots().is_empty());
Ok(())
}
}