use super::nodes::{CompressedPrefix, MAX_PREFIX_LEN};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrefixMatchResult {
FullMatch {
matched: usize,
},
PartialMatch {
mismatch_pos: usize,
prefix_byte: u8,
key_byte: u8,
},
KeyTooShort {
matched: usize,
},
}
pub fn prefix_mismatch(
prefix: &CompressedPrefix,
prefix_len: usize,
key: &[u8],
key_offset: usize,
) -> PrefixMatchResult {
let remaining_key = &key[key_offset..];
let check_len = prefix_len.min(MAX_PREFIX_LEN);
if remaining_key.len() < check_len {
for i in 0..remaining_key.len() {
if prefix.bytes[i] != remaining_key[i] {
return PrefixMatchResult::PartialMatch {
mismatch_pos: i,
prefix_byte: prefix.bytes[i],
key_byte: remaining_key[i],
};
}
}
return PrefixMatchResult::KeyTooShort {
matched: remaining_key.len(),
};
}
for i in 0..check_len {
if prefix.bytes[i] != remaining_key[i] {
return PrefixMatchResult::PartialMatch {
mismatch_pos: i,
prefix_byte: prefix.bytes[i],
key_byte: remaining_key[i],
};
}
}
PrefixMatchResult::FullMatch { matched: check_len }
}
#[derive(Debug, Clone)]
pub struct SplitPrefix {
pub before: CompressedPrefix,
pub before_len: usize,
pub split_byte: u8,
pub after: CompressedPrefix,
pub after_len: usize,
}
pub fn split_prefix(prefix: &CompressedPrefix, prefix_len: usize, split_pos: usize) -> SplitPrefix {
assert!(
split_pos < prefix_len,
"split position {} must be less than prefix length {}",
split_pos,
prefix_len
);
assert!(
split_pos < MAX_PREFIX_LEN,
"split position {} must be less than MAX_PREFIX_LEN {}",
split_pos,
MAX_PREFIX_LEN
);
let mut before = CompressedPrefix::empty();
if split_pos > 0 {
before.bytes[..split_pos].copy_from_slice(&prefix.bytes[..split_pos]);
}
let split_byte = prefix.bytes[split_pos];
let mut after = CompressedPrefix::empty();
let after_len = prefix_len.saturating_sub(split_pos + 1);
if after_len > 0 {
let after_start = split_pos + 1;
let copy_len = after_len.min(MAX_PREFIX_LEN);
after.bytes[..copy_len].copy_from_slice(&prefix.bytes[after_start..after_start + copy_len]);
}
SplitPrefix {
before,
before_len: split_pos,
split_byte,
after,
after_len,
}
}
pub fn extend_prefix(
base_prefix: &CompressedPrefix,
base_len: usize,
prepend_bytes: &[u8],
prepend_edge: u8,
) -> (CompressedPrefix, usize) {
let mut result = CompressedPrefix::empty();
let total_len = prepend_bytes.len() + 1 + base_len; let actual_len = total_len.min(MAX_PREFIX_LEN);
let mut write_pos = 0;
let prepend_copy = prepend_bytes.len().min(MAX_PREFIX_LEN);
if prepend_copy > 0 && write_pos < MAX_PREFIX_LEN {
let copy_len = prepend_copy.min(MAX_PREFIX_LEN - write_pos);
result.bytes[write_pos..write_pos + copy_len].copy_from_slice(&prepend_bytes[..copy_len]);
write_pos += copy_len;
}
if write_pos < MAX_PREFIX_LEN {
result.bytes[write_pos] = prepend_edge;
write_pos += 1;
}
if write_pos < MAX_PREFIX_LEN && base_len > 0 {
let copy_len = base_len.min(MAX_PREFIX_LEN - write_pos);
result.bytes[write_pos..write_pos + copy_len]
.copy_from_slice(&base_prefix.bytes[..copy_len]);
}
(result, actual_len)
}
pub fn truncate_prefix(
prefix: &CompressedPrefix,
prefix_len: usize,
remove_count: usize,
) -> (CompressedPrefix, usize) {
if remove_count >= prefix_len {
return (CompressedPrefix::empty(), 0);
}
let new_len = prefix_len - remove_count;
let mut result = CompressedPrefix::empty();
let copy_len = new_len.min(MAX_PREFIX_LEN);
result.bytes[..copy_len].copy_from_slice(&prefix.bytes[remove_count..remove_count + copy_len]);
(result, new_len)
}
pub fn common_prefix_len(a: &[u8], b: &[u8]) -> usize {
let max_len = a.len().min(b.len());
for i in 0..max_len {
if a[i] != b[i] {
return i;
}
}
max_len
}
pub fn make_common_prefix(a: &[u8], b: &[u8]) -> (CompressedPrefix, usize) {
let common_len = common_prefix_len(a, b);
let prefix_len = common_len.min(MAX_PREFIX_LEN);
let prefix = CompressedPrefix::from_bytes(&a[..prefix_len]);
(prefix, common_len)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prefix_mismatch_full_match() {
let prefix = CompressedPrefix::from_bytes(b"hello");
let result = prefix_mismatch(&prefix, 5, b"hello world", 0);
assert_eq!(result, PrefixMatchResult::FullMatch { matched: 5 });
}
#[test]
fn test_prefix_mismatch_partial() {
let prefix = CompressedPrefix::from_bytes(b"hello");
let result = prefix_mismatch(&prefix, 5, b"help me", 0);
assert_eq!(
result,
PrefixMatchResult::PartialMatch {
mismatch_pos: 3,
prefix_byte: b'l',
key_byte: b'p',
}
);
}
#[test]
fn test_prefix_mismatch_key_too_short() {
let prefix = CompressedPrefix::from_bytes(b"hello");
let result = prefix_mismatch(&prefix, 5, b"hel", 0);
assert_eq!(result, PrefixMatchResult::KeyTooShort { matched: 3 });
}
#[test]
fn test_prefix_mismatch_with_offset() {
let prefix = CompressedPrefix::from_bytes(b"world");
let result = prefix_mismatch(&prefix, 5, b"hello world", 6);
assert_eq!(result, PrefixMatchResult::FullMatch { matched: 5 });
}
#[test]
fn test_prefix_mismatch_at_start() {
let prefix = CompressedPrefix::from_bytes(b"hello");
let result = prefix_mismatch(&prefix, 5, b"world", 0);
assert_eq!(
result,
PrefixMatchResult::PartialMatch {
mismatch_pos: 0,
prefix_byte: b'h',
key_byte: b'w',
}
);
}
#[test]
fn test_split_prefix_middle() {
let prefix = CompressedPrefix::from_bytes(b"hello");
let split = split_prefix(&prefix, 5, 2);
assert_eq!(split.before.as_slice(split.before_len), b"he");
assert_eq!(split.before_len, 2);
assert_eq!(split.split_byte, b'l');
assert_eq!(split.after.as_slice(split.after_len), b"lo");
assert_eq!(split.after_len, 2);
}
#[test]
fn test_split_prefix_at_start() {
let prefix = CompressedPrefix::from_bytes(b"hello");
let split = split_prefix(&prefix, 5, 0);
assert_eq!(split.before_len, 0);
assert_eq!(split.split_byte, b'h');
assert_eq!(split.after.as_slice(split.after_len), b"ello");
assert_eq!(split.after_len, 4);
}
#[test]
fn test_split_prefix_at_end() {
let prefix = CompressedPrefix::from_bytes(b"hello");
let split = split_prefix(&prefix, 5, 4);
assert_eq!(split.before.as_slice(split.before_len), b"hell");
assert_eq!(split.before_len, 4);
assert_eq!(split.split_byte, b'o');
assert_eq!(split.after_len, 0);
}
#[test]
fn test_extend_prefix() {
let base = CompressedPrefix::from_bytes(b"world");
let (result, len) = extend_prefix(&base, 5, b"hel", b'l');
assert_eq!(len, 9);
assert_eq!(&result.bytes[..9], b"hellworld");
}
#[test]
fn test_extend_prefix_truncation() {
let base = CompressedPrefix::from_bytes(b"world");
let prepend = b"this is a very long prefix that will be truncated";
let (result, len) = extend_prefix(&base, 5, prepend, b'!');
assert_eq!(len, MAX_PREFIX_LEN);
assert_eq!(result.bytes.len(), MAX_PREFIX_LEN);
}
#[test]
fn test_truncate_prefix() {
let prefix = CompressedPrefix::from_bytes(b"hello world");
let (result, len) = truncate_prefix(&prefix, 11, 6);
assert_eq!(len, 5);
assert_eq!(result.as_slice(len), b"world");
}
#[test]
fn test_truncate_prefix_all() {
let prefix = CompressedPrefix::from_bytes(b"hello");
let (result, len) = truncate_prefix(&prefix, 5, 5);
assert_eq!(len, 0);
assert_eq!(result.as_slice(len), b"");
}
#[test]
fn test_truncate_prefix_overflow() {
let prefix = CompressedPrefix::from_bytes(b"hello");
let (result, len) = truncate_prefix(&prefix, 5, 10);
assert_eq!(len, 0);
assert_eq!(result.as_slice(len), b"");
}
#[test]
fn test_common_prefix_len() {
assert_eq!(common_prefix_len(b"hello", b"help"), 3);
assert_eq!(common_prefix_len(b"hello", b"hello"), 5);
assert_eq!(common_prefix_len(b"hello", b"world"), 0);
assert_eq!(common_prefix_len(b"", b"hello"), 0);
assert_eq!(common_prefix_len(b"h", b"hello"), 1);
}
#[test]
fn test_make_common_prefix() {
let (prefix, len) = make_common_prefix(b"hello", b"help");
assert_eq!(len, 3);
assert_eq!(prefix.as_slice(len), b"hel");
}
#[test]
fn test_make_common_prefix_none() {
let (prefix, len) = make_common_prefix(b"hello", b"world");
assert_eq!(len, 0);
assert_eq!(prefix.as_slice(len), b"");
}
#[test]
fn test_make_common_prefix_exact() {
let (prefix, len) = make_common_prefix(b"hello", b"hello");
assert_eq!(len, 5);
assert_eq!(prefix.as_slice(len), b"hello");
}
}