use super::boundary::{BoundaryDetector, DEFAULT_TARGET_SIZE, key_sample};
const LEAF_ENTRY_OVERHEAD: u64 = 16;
const INTERNAL_ENTRY_OVERHEAD: u64 = 8 + 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TreePolicy {
V1(BoundaryDetector),
V2 {
leaf_target_bytes: u64,
internal_target_bytes: u64,
},
}
impl TreePolicy {
pub const V1_DEFAULT: Self = Self::V1(BoundaryDetector::new(DEFAULT_TARGET_SIZE));
#[must_use]
pub const fn v1_with_target(target_size: usize) -> Self {
Self::V1(BoundaryDetector::new(target_size))
}
#[must_use]
pub const fn v2(leaf_target_bytes: u64, internal_target_bytes: u64) -> Self {
Self::V2 {
leaf_target_bytes,
internal_target_bytes,
}
}
#[must_use]
pub const fn is_v2(&self) -> bool {
matches!(self, Self::V2 { .. })
}
#[must_use]
pub fn leaf_boundary_after(&self, key: &[u8], value_len: usize) -> bool {
match self {
Self::V1(detector) => detector.is_boundary(key),
Self::V2 {
leaf_target_bytes, ..
} => byte_boundary_after(
key,
leaf_entry_size(key.len(), value_len),
*leaf_target_bytes,
),
}
}
#[must_use]
pub fn leaf_boundary_before(&self, key_len: usize, value_len: usize) -> bool {
match self {
Self::V1(_) => false,
Self::V2 {
leaf_target_bytes, ..
} => {
*leaf_target_bytes > 0 && leaf_entry_size(key_len, value_len) >= *leaf_target_bytes
}
}
}
#[must_use]
pub fn internal_boundary_after(&self, separator: &[u8]) -> bool {
match self {
Self::V1(detector) => detector.is_boundary(separator),
Self::V2 {
internal_target_bytes,
..
} => byte_boundary_after(
separator,
internal_entry_size(separator.len()),
*internal_target_bytes,
),
}
}
}
fn byte_boundary_after(key: &[u8], entry_size: u64, target: u64) -> bool {
let sample = u128::from(key_sample(key));
sample * u128::from(target) < (u128::from(entry_size) << 64)
}
fn leaf_entry_size(key_len: usize, value_len: usize) -> u64 {
LEAF_ENTRY_OVERHEAD
.saturating_add(len_as_u64(key_len))
.saturating_add(len_as_u64(value_len))
}
fn internal_entry_size(separator_len: usize) -> u64 {
INTERNAL_ENTRY_OVERHEAD.saturating_add(len_as_u64(separator_len))
}
fn len_as_u64(len: usize) -> u64 {
u64::try_from(len).unwrap_or(u64::MAX)
}
#[cfg(test)]
mod tests {
use super::{TreePolicy, leaf_entry_size};
#[test]
fn v1_default_ignores_value_length() {
let policy = TreePolicy::V1_DEFAULT;
let short = policy.leaf_boundary_after(b"some-key", 1);
let long = policy.leaf_boundary_after(b"some-key", 1_000_000);
assert_eq!(short, long);
assert!(!policy.leaf_boundary_before(8, 1_000_000));
}
#[test]
fn v2_oversized_entry_is_always_a_singleton() {
let policy = TreePolicy::v2(64, 48);
assert!(policy.leaf_boundary_before(8, 64));
assert!(policy.leaf_boundary_after(b"a-key-88", 64));
}
#[test]
fn v2_threshold_equality_always_boundaries() {
let target = leaf_entry_size(4, 0); let policy = TreePolicy::v2(target, 48);
assert!(policy.leaf_boundary_after(b"abcd", 0));
assert!(policy.leaf_boundary_before(4, 0));
}
#[test]
fn v2_sub_target_entry_is_probabilistic() {
let policy = TreePolicy::v2(64 * 1024, 48 * 1024);
assert!(!policy.leaf_boundary_before(3, 3));
}
}