use core::cmp::Ordering;
use crate::{
raw::{
match_concrete_node_ptr, AttemptOptimisticPrefixMatch, ConcreteNodePtr, InnerNode,
LeafNode, NodePtr, OpaqueNodePtr, OptionalLeafPtr, PessimisticMismatch, PrefixMatch,
},
AsBytes,
};
#[rustfmt::skip]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum PrefixMatchBehavior {
#[default]
Pessimistic,
Optimistic,
}
impl PrefixMatchBehavior {
#[inline]
pub fn match_prefix<K, V, const PREFIX_LEN: usize>(
&mut self,
inner_node: &impl InnerNode<PREFIX_LEN, Key = K, Value = V>,
key_bytes: &[u8],
) -> Result<AttemptOptimisticPrefixMatch, PessimisticMismatch> {
let result = match self {
PrefixMatchBehavior::Pessimistic => {
inner_node.attempt_pessimistic_match_prefix(key_bytes)
},
PrefixMatchBehavior::Optimistic => inner_node
.optimistic_match_prefix(key_bytes)
.map(
|PrefixMatch { matched_bytes }| AttemptOptimisticPrefixMatch {
matched_bytes,
any_implicit_bytes: true,
},
)
.map_err(Into::into),
};
match &result {
Ok(AttemptOptimisticPrefixMatch {
any_implicit_bytes, ..
}) if *any_implicit_bytes => {
*self = PrefixMatchBehavior::Optimistic;
},
Err(PessimisticMismatch { prefix_byte, .. }) if prefix_byte.is_none() => {
*self = PrefixMatchBehavior::Optimistic;
},
_ => {},
}
result
}
pub fn compare_leaf_key<K: AsBytes, V, const PREFIX_LEN: usize>(
self,
leaf: &LeafNode<K, V, PREFIX_LEN>,
key_bytes: &[u8],
current_depth: usize,
) -> Ordering {
match self {
PrefixMatchBehavior::Pessimistic => {
let leaf_key_bytes = leaf.key_ref().as_bytes();
let current_depth = current_depth.min(leaf_key_bytes.len()).min(key_bytes.len());
let leaf_key_bytes = &leaf_key_bytes[current_depth..];
let key_bytes = &key_bytes[current_depth..];
leaf_key_bytes.cmp(key_bytes)
},
PrefixMatchBehavior::Optimistic => leaf.key_ref().as_bytes().cmp(key_bytes),
}
}
pub fn matches_leaf_key<K: AsBytes, V, const PREFIX_LEN: usize>(
self,
leaf: &LeafNode<K, V, PREFIX_LEN>,
key_bytes: &[u8],
current_depth: usize,
) -> bool {
match self {
PrefixMatchBehavior::Pessimistic => {
let leaf_key_bytes = leaf.key_ref().as_bytes();
let current_depth = current_depth.min(leaf_key_bytes.len()).min(key_bytes.len());
let leaf_key_bytes = &leaf_key_bytes[current_depth..];
let key_bytes = &key_bytes[current_depth..];
leaf_key_bytes.eq(key_bytes)
},
PrefixMatchBehavior::Optimistic => leaf.matches_full_key(key_bytes),
}
}
pub fn leaf_key_is_prefix<K: AsBytes, V, const PREFIX_LEN: usize>(
self,
leaf: &LeafNode<K, V, PREFIX_LEN>,
key_bytes: &[u8],
current_depth: usize,
) -> bool {
match self {
PrefixMatchBehavior::Pessimistic => {
let leaf_key_bytes = leaf.key_ref().as_bytes();
let current_depth = current_depth.min(leaf_key_bytes.len()).min(key_bytes.len());
let leaf_key_bytes = &leaf_key_bytes[current_depth..];
let key_bytes = &key_bytes[current_depth..];
key_bytes.starts_with(leaf_key_bytes)
},
PrefixMatchBehavior::Optimistic => key_bytes.starts_with(leaf.key_ref().as_bytes()),
}
}
}
pub unsafe fn search_unchecked<K, V, const PREFIX_LEN: usize>(
root: OpaqueNodePtr<K, V, PREFIX_LEN>,
key_bytes: &[u8],
) -> OptionalLeafPtr<K, V, PREFIX_LEN>
where
K: AsBytes,
{
let mut current_node = root;
let mut current_depth = 0;
let mut prefix_match_behavior = PrefixMatchBehavior::default();
loop {
current_node = match_concrete_node_ptr!(match (current_node.to_node_ptr()) {
InnerNode(inner_ptr) => unsafe {
check_prefix_lookup_child(
inner_ptr,
key_bytes,
&mut current_depth,
&mut prefix_match_behavior,
)
},
LeafNode(leaf_node_ptr) => {
let leaf = unsafe { leaf_node_ptr.as_ref() };
if prefix_match_behavior.matches_leaf_key(leaf, key_bytes, current_depth) {
return Some(leaf_node_ptr);
} else {
return None;
}
},
})?;
}
}
pub unsafe fn prefix_search_unchecked<K, V, const PREFIX_LEN: usize>(
root: OpaqueNodePtr<K, V, PREFIX_LEN>,
key_bytes: &[u8],
) -> OptionalLeafPtr<K, V, PREFIX_LEN>
where
K: AsBytes,
{
let mut current_node = root;
let mut current_depth = 0;
let mut prefix_match_behavior = PrefixMatchBehavior::default();
loop {
current_node = match_concrete_node_ptr!(match (current_node.to_node_ptr()) {
InnerNode(inner_ptr) => unsafe {
check_prefix_lookup_child(
inner_ptr,
key_bytes,
&mut current_depth,
&mut prefix_match_behavior,
)
},
LeafNode(leaf_node_ptr) => {
let leaf = unsafe { leaf_node_ptr.as_ref() };
if prefix_match_behavior.leaf_key_is_prefix(leaf, key_bytes, current_depth) {
return Some(leaf_node_ptr);
} else {
return None;
}
},
})?;
}
}
pub(crate) unsafe fn check_prefix_lookup_child<K, V, N, const PREFIX_LEN: usize>(
inner_ptr: NodePtr<PREFIX_LEN, N>,
key_bytes: &[u8],
current_depth: &mut usize,
prefix_match_behavior: &mut PrefixMatchBehavior,
) -> Option<OpaqueNodePtr<K, V, PREFIX_LEN>>
where
N: InnerNode<PREFIX_LEN, Key = K, Value = V>,
K: AsBytes,
{
let inner_node = unsafe { inner_ptr.as_ref() };
let match_prefix = prefix_match_behavior.match_prefix(inner_node, &key_bytes[*current_depth..]);
match match_prefix {
Err(_) => None,
Ok(AttemptOptimisticPrefixMatch { matched_bytes, .. }) => {
*current_depth += matched_bytes;
let next_key_fragment = if *current_depth < key_bytes.len() {
key_bytes[*current_depth]
} else {
return None;
};
let child_lookup = inner_node.lookup_child(next_key_fragment);
if child_lookup.is_some() {
*current_depth += 1;
}
child_lookup
},
}
}
#[cfg(test)]
mod tests;