use crc32fast::Hasher;
use std::fmt::Write;
use std::hash::Hash;
use std::iter::zip;
fn crc32(bit: &[u8]) -> u32 {
let mut hasher = Hasher::new();
hasher.update(bit);
hasher.finalize()
}
pub type SerialisedKey = Vec<u8>;
pub const INTERESTING_NEW_SIZE: usize = 50;
pub const INTERESTING_SHRINKAGE_LIMIT: usize = 20;
pub fn search_key_plain(key: &Key) -> SerializedKey {
key.0.join(&b'\x00')
}
pub fn search_key_16(key: &Key) -> SerializedKey {
let mut result = String::new();
for bit in key.iter() {
write!(&mut result, "{:08X}\x00", crc32(bit)).unwrap();
}
result.pop();
result.as_bytes().to_vec()
}
pub fn search_key_255(key: &Key) -> SerializedKey {
let mut result = vec![];
for bit in key.iter() {
let crc = crc32(bit);
let crc_bytes = crc.to_be_bytes();
result.extend(crc_bytes);
result.push(0x00);
}
result.pop();
result
.iter()
.map(|b| if *b == 0x0A { b'_' } else { *b })
.collect()
}
#[derive(Clone)]
pub enum SearchKeyFunc {
Plain,
Hash16Way,
Hash255Way,
Custom {
name: Vec<u8>,
func: std::sync::Arc<dyn Fn(&Key) -> SerializedKey + Send + Sync>,
},
}
impl std::fmt::Debug for SearchKeyFunc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SearchKeyFunc::Plain => f.write_str("Plain"),
SearchKeyFunc::Hash16Way => f.write_str("Hash16Way"),
SearchKeyFunc::Hash255Way => f.write_str("Hash255Way"),
SearchKeyFunc::Custom { name, .. } => write!(f, "Custom({:?})", name),
}
}
}
impl PartialEq for SearchKeyFunc {
fn eq(&self, other: &Self) -> bool {
use SearchKeyFunc::*;
match (self, other) {
(Plain, Plain) | (Hash16Way, Hash16Way) | (Hash255Way, Hash255Way) => true,
(Custom { func: a, .. }, Custom { func: b, .. }) => std::sync::Arc::ptr_eq(a, b),
_ => false,
}
}
}
impl Eq for SearchKeyFunc {}
impl SearchKeyFunc {
pub fn from_name(name: &[u8]) -> Result<Self, Vec<u8>> {
match name {
b"plain" => Ok(SearchKeyFunc::Plain),
b"hash-16-way" => Ok(SearchKeyFunc::Hash16Way),
b"hash-255-way" => Ok(SearchKeyFunc::Hash255Way),
other => Err(other.to_vec()),
}
}
pub fn name(&self) -> &[u8] {
match self {
SearchKeyFunc::Plain => b"plain",
SearchKeyFunc::Hash16Way => b"hash-16-way",
SearchKeyFunc::Hash255Way => b"hash-255-way",
SearchKeyFunc::Custom { name, .. } => name.as_slice(),
}
}
pub fn apply(&self, key: &Key) -> SerializedKey {
match self {
SearchKeyFunc::Plain => search_key_plain(key),
SearchKeyFunc::Hash16Way => search_key_16(key),
SearchKeyFunc::Hash255Way => search_key_255(key),
SearchKeyFunc::Custom { func, .. } => func(key),
}
}
}
impl Default for SearchKeyFunc {
fn default() -> Self {
SearchKeyFunc::Plain
}
}
pub fn bytes_to_text_key(data: &[u8]) -> Result<(&[u8], &[u8]), String> {
let sections: Vec<&[u8]> = data.split(|&byte| byte == b'\n').collect();
let delimiter_position = sections[0].windows(2).position(|window| window == b": ");
if delimiter_position.is_none() {
return Err("Invalid key file".to_string());
}
let (_kind, file_id) = sections[0].split_at(delimiter_position.unwrap() + 2);
Ok((file_id, sections[3]))
}
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
pub struct Key(Vec<Vec<u8>>);
impl From<Vec<Vec<u8>>> for Key {
fn from(v: Vec<Vec<u8>>) -> Self {
Key(v)
}
}
impl Key {
pub fn serialize(&self) -> SerializedKey {
let mut result = vec![];
for bit in self.0.iter() {
result.extend(bit);
result.push(0x00);
}
result.pop();
result
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
self.0.iter().map(|v| v.as_slice())
}
}
impl std::ops::Index<usize> for Key {
type Output = Vec<u8>;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl std::fmt::Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let mut first = true;
for bit in &self.0 {
if !first {
write!(f, "/")?;
}
first = false;
write!(f, "{}", String::from_utf8_lossy(bit))?;
}
Ok(())
}
}
pub type SerializedKey = Vec<u8>;
pub type Value = Vec<u8>;
#[derive(Debug)]
pub enum Error {
InconsistentDeltaDelta(Vec<(Option<Key>, Option<Key>, Value)>, String),
DeserializeError(String),
AssertionFailed(String),
}
impl From<std::num::ParseIntError> for Error {
fn from(e: std::num::ParseIntError) -> Self {
Error::DeserializeError(format!("Failed to parse int: {}", e))
}
}
pub fn common_prefix_pair<'b>(prefix: &[u8], key: &'b [u8]) -> &'b [u8] {
if key.starts_with(prefix) {
return &key[..prefix.len()];
}
let mut p = 0;
for (left, right) in zip(prefix, key) {
if left != right {
break;
}
p += 1;
}
let p = p as usize;
&key[..p]
}
#[test]
fn test_common_prefix_pair() {
assert_eq!(common_prefix_pair(b"abc", b"abc"), b"abc");
assert_eq!(common_prefix_pair(b"abc", b"abcd"), b"abc");
assert_eq!(common_prefix_pair(b"abc", b"ab"), b"ab");
assert_eq!(common_prefix_pair(b"abc", b"bbd"), b"");
assert_eq!(common_prefix_pair(b"", b"bbc"), b"");
assert_eq!(common_prefix_pair(b"abc", b""), b"");
}
pub fn common_prefix_many<'a>(mut keys: impl Iterator<Item = &'a [u8]> + 'a) -> Option<&'a [u8]> {
let mut cp = keys.next()?;
for key in keys {
cp = common_prefix_pair(cp, key);
if cp.is_empty() {
break;
}
}
Some(cp)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedLeafNode {
pub maximum_size: usize,
pub key_width: usize,
pub length: usize,
pub common_serialised_prefix: Vec<u8>,
pub items: Vec<(Vec<Vec<u8>>, Vec<u8>)>,
pub raw_size: usize,
}
pub fn deserialise_leaf_node(data: &[u8]) -> Result<ParsedLeafNode, Error> {
let mut lines: Vec<&[u8]> = data.split(|&b| b == b'\n').collect();
let trailing = lines
.pop()
.ok_or_else(|| Error::DeserializeError("empty leaf node body".into()))?;
if !trailing.is_empty() {
return Err(Error::DeserializeError(
"leaf node did not end with final newline".into(),
));
}
if lines.len() < 5 {
return Err(Error::DeserializeError(
"leaf node truncated before item lines".into(),
));
}
if lines[0] != b"chkleaf:" {
return Err(Error::DeserializeError("not a serialised leaf node".into()));
}
let maximum_size = parse_decimal(lines[1], "maximum_size")?;
let width = parse_decimal(lines[2], "key_width")?;
let length = parse_decimal(lines[3], "length")?;
let prefix = lines[4];
let mut items: Vec<(Vec<Vec<u8>>, Vec<u8>)> = Vec::with_capacity(length);
let mut pos = 5usize;
while pos < lines.len() {
let mut full = Vec::with_capacity(prefix.len() + lines[pos].len());
full.extend_from_slice(prefix);
full.extend_from_slice(lines[pos]);
pos += 1;
let mut elements: Vec<Vec<u8>> = full.split(|&b| b == 0).map(|s| s.to_vec()).collect();
if elements.len() != width + 1 {
return Err(Error::DeserializeError(format!(
"incorrect number of elements ({} vs {}) for leaf line",
elements.len(),
width + 1
)));
}
let count_bytes = elements.pop().expect("just checked non-empty");
let num_value_lines = parse_decimal(&count_bytes, "value line count")?;
if pos + num_value_lines > lines.len() {
return Err(Error::DeserializeError(
"leaf node value line runs past end of body".into(),
));
}
let value_lines = &lines[pos..pos + num_value_lines];
pos += num_value_lines;
let value_len =
value_lines.iter().map(|l| l.len()).sum::<usize>() + num_value_lines.saturating_sub(1);
let mut value = Vec::with_capacity(value_len);
for (i, line) in value_lines.iter().enumerate() {
if i > 0 {
value.push(b'\n');
}
value.extend_from_slice(line);
}
items.push((elements, value));
}
if items.len() != length {
return Err(Error::DeserializeError(format!(
"item count ({}) mismatch: found {}",
length,
items.len()
)));
}
let suffix_bytes: usize = lines[5..].iter().map(|l| l.len()).sum();
let raw_size = suffix_bytes + length * prefix.len() + (lines.len() - 5);
Ok(ParsedLeafNode {
maximum_size,
key_width: width,
length,
common_serialised_prefix: prefix.to_vec(),
items,
raw_size,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedInternalNode {
pub maximum_size: usize,
pub key_width: usize,
pub length: usize,
pub search_prefix: Vec<u8>,
pub items: Vec<(Vec<u8>, Vec<u8>)>,
pub node_width: usize,
}
pub fn deserialise_internal_node(data: &[u8]) -> Result<ParsedInternalNode, Error> {
let mut lines: Vec<&[u8]> = data.split(|&b| b == b'\n').collect();
let trailing = lines
.pop()
.ok_or_else(|| Error::DeserializeError("empty internal node body".into()))?;
if !trailing.is_empty() {
return Err(Error::DeserializeError("last line must be ''".into()));
}
if lines.len() < 5 {
return Err(Error::DeserializeError(
"internal node truncated before item lines".into(),
));
}
if lines[0] != b"chknode:" {
return Err(Error::DeserializeError(
"not a serialised internal node".into(),
));
}
let maximum_size = parse_decimal(lines[1], "maximum_size")?;
let width = parse_decimal(lines[2], "key_width")?;
let length = parse_decimal(lines[3], "length")?;
let common_prefix = lines[4];
let mut items: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
let mut last_prefix_len = 0usize;
for suffix in &lines[5..] {
let mut full = Vec::with_capacity(common_prefix.len() + suffix.len());
full.extend_from_slice(common_prefix);
full.extend_from_slice(suffix);
let split_at = full
.iter()
.rposition(|&b| b == 0)
.ok_or_else(|| Error::DeserializeError("internal node line missing NUL".into()))?;
let prefix = full[..split_at].to_vec();
let flat_key = full[split_at + 1..].to_vec();
last_prefix_len = prefix.len();
items.push((prefix, flat_key));
}
if items.is_empty() {
return Err(Error::DeserializeError(
"internal node contained no items".into(),
));
}
Ok(ParsedInternalNode {
maximum_size,
key_width: width,
length,
search_prefix: common_prefix.to_vec(),
items,
node_width: last_prefix_len,
})
}
fn parse_decimal(bytes: &[u8], what: &str) -> Result<usize, Error> {
std::str::from_utf8(bytes)
.ok()
.and_then(|s| s.parse::<usize>().ok())
.ok_or_else(|| Error::DeserializeError(format!("invalid {}: {:?}", what, bytes)))
}
fn value_to_lines(value: &[u8]) -> Vec<Vec<u8>> {
let mut out = Vec::with_capacity(memchr::memchr_iter(b'\n', value).count() + 1);
let mut start = 0;
for nl in memchr::memchr_iter(b'\n', value) {
out.push(value[start..=nl].to_vec());
start = nl + 1;
}
let mut last = Vec::with_capacity(value.len() - start + 1);
last.extend_from_slice(&value[start..]);
last.push(b'\n');
out.push(last);
out
}
pub fn serialise_leaf_node(
maximum_size: usize,
key_width: usize,
items: &[(Vec<Vec<u8>>, Vec<u8>)],
common_prefix: Option<&[u8]>,
) -> Result<Vec<Vec<u8>>, Error> {
let mut out: Vec<Vec<u8>> = Vec::with_capacity(5 + items.len() * 2);
out.push(b"chkleaf:\n".to_vec());
out.push(format!("{}\n", maximum_size).into_bytes());
out.push(format!("{}\n", key_width).into_bytes());
out.push(format!("{}\n", items.len()).into_bytes());
let prefix_bytes = match common_prefix {
None => {
if !items.is_empty() {
return Err(Error::DeserializeError(
"common prefix is None but items is non-empty".into(),
));
}
out.push(b"\n".to_vec());
return Ok(out);
}
Some(p) => {
let mut line = p.to_vec();
line.push(b'\n');
out.push(line);
p
}
};
let prefix_len = prefix_bytes.len();
for (key, value) in items {
let value_lines = value_to_lines(value);
let mut header = key.join(&b'\x00');
header.push(b'\x00');
header.extend_from_slice(format!("{}\n", value_lines.len()).as_bytes());
if !header.starts_with(prefix_bytes) {
return Err(Error::DeserializeError(format!(
"serialised key {:?} does not start with common prefix {:?}",
header, prefix_bytes
)));
}
out.push(header[prefix_len..].to_vec());
out.extend(value_lines);
}
Ok(out)
}
#[derive(Debug, Clone)]
pub struct InternalNodeChild {
pub prefix: Vec<u8>,
pub flat_key: Vec<u8>,
}
pub fn serialise_internal_node(
maximum_size: usize,
key_width: usize,
length: usize,
search_prefix: &[u8],
items: &[InternalNodeChild],
) -> Result<Vec<Vec<u8>>, Error> {
let mut out: Vec<Vec<u8>> = Vec::with_capacity(5 + items.len());
out.push(b"chknode:\n".to_vec());
out.push(format!("{}\n", maximum_size).into_bytes());
out.push(format!("{}\n", key_width).into_bytes());
out.push(format!("{}\n", length).into_bytes());
let mut prefix_line = search_prefix.to_vec();
prefix_line.push(b'\n');
out.push(prefix_line);
let prefix_len = search_prefix.len();
for child in items {
let mut serialised = child.prefix.clone();
serialised.push(b'\x00');
serialised.extend_from_slice(&child.flat_key);
serialised.push(b'\n');
if !serialised.starts_with(search_prefix) {
return Err(Error::DeserializeError(format!(
"internal node entry {:?} does not start with prefix {:?}",
serialised, search_prefix
)));
}
out.push(serialised[prefix_len..].to_vec());
}
Ok(out)
}
pub fn leaf_node_key_value_len(key: &[Vec<u8>], value: &[u8]) -> usize {
let key_len: usize = if key.is_empty() {
0
} else {
key.iter().map(Vec::len).sum::<usize>() + (key.len() - 1)
};
let newline_count = memchr::memchr_iter(b'\n', value).count();
let newline_count_digits = if newline_count == 0 {
1
} else {
let mut n = newline_count;
let mut digits = 0;
while n > 0 {
n /= 10;
digits += 1;
}
digits
};
key_len + 1 + newline_count_digits + 1 + value.len() + 1
}
pub fn leaf_node_current_size(
maximum_size: usize,
key_width: usize,
length: usize,
raw_size: usize,
common_serialised_prefix: Option<&[u8]>,
) -> usize {
let (bytes_for_items, prefix_len) = match common_serialised_prefix {
None => (0, 0),
Some(prefix) => {
let prefix_len = prefix.len();
(raw_size - prefix_len * length, prefix_len)
}
};
9 + decimal_digits(maximum_size)
+ 1
+ decimal_digits(key_width)
+ 1
+ decimal_digits(length)
+ 1
+ prefix_len
+ 1
+ bytes_for_items
}
pub fn internal_node_current_size(
maximum_size: usize,
key_width: usize,
length: usize,
raw_size: usize,
) -> usize {
raw_size + decimal_digits(length) + decimal_digits(key_width) + decimal_digits(maximum_size)
}
#[inline]
fn decimal_digits(n: usize) -> usize {
if n == 0 {
return 1;
}
let mut n = n;
let mut digits = 0;
while n > 0 {
n /= 10;
digits += 1;
}
digits
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SearchPrefix {
Unknown,
Computed(Option<Vec<u8>>),
}
impl SearchPrefix {
pub fn expect_computed(&self) -> Option<&[u8]> {
match self {
SearchPrefix::Unknown => panic!("search prefix is Unknown"),
SearchPrefix::Computed(p) => p.as_deref(),
}
}
pub fn is_unknown(&self) -> bool {
matches!(self, SearchPrefix::Unknown)
}
}
#[derive(Debug, Clone)]
pub struct LeafNode {
pub key: Option<Vec<u8>>,
pub maximum_size: usize,
pub key_width: usize,
pub raw_size: usize,
pub items: indexmap::IndexMap<Vec<Vec<u8>>, Vec<u8>>,
pub search_prefix: SearchPrefix,
pub common_serialised_prefix: Option<Vec<u8>>,
pub search_key_func: SearchKeyFunc,
}
impl LeafNode {
pub fn new(search_key_func: SearchKeyFunc) -> Self {
Self {
key: None,
maximum_size: 0,
key_width: 1,
raw_size: 0,
items: indexmap::IndexMap::new(),
search_prefix: SearchPrefix::Computed(None),
common_serialised_prefix: None,
search_key_func,
}
}
pub fn from_parsed(parsed: ParsedLeafNode, search_key_func: SearchKeyFunc) -> Self {
let items_empty = parsed.items.is_empty();
let mut items = indexmap::IndexMap::with_capacity(parsed.items.len());
for (k, v) in parsed.items {
items.insert(k, v);
}
let (search_prefix, common_serialised_prefix) = if items_empty {
(SearchPrefix::Computed(None), None)
} else {
(SearchPrefix::Unknown, Some(parsed.common_serialised_prefix))
};
Self {
key: None,
maximum_size: parsed.maximum_size,
key_width: parsed.key_width,
raw_size: parsed.raw_size,
items,
search_prefix,
common_serialised_prefix,
search_key_func,
}
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn current_size(&self) -> usize {
leaf_node_current_size(
self.maximum_size,
self.key_width,
self.len(),
self.raw_size,
self.common_serialised_prefix.as_deref(),
)
}
pub fn compute_search_prefix(&mut self) -> Option<&[u8]> {
let keys: Vec<SerializedKey> = self
.items
.keys()
.map(|k| self.search_key_func.apply(&Key::from(k.clone())))
.collect();
let prefix = common_prefix_many(keys.iter().map(|k| k.as_slice())).map(|s| s.to_vec());
self.search_prefix = SearchPrefix::Computed(prefix);
match &self.search_prefix {
SearchPrefix::Computed(p) => p.as_deref(),
SearchPrefix::Unknown => unreachable!(),
}
}
pub fn compute_serialised_prefix(&mut self) -> Option<&[u8]> {
let keys: Vec<SerializedKey> = self
.items
.keys()
.map(|k| Key::from(k.clone()).serialize())
.collect();
let prefix = common_prefix_many(keys.iter().map(|k| k.as_slice())).map(|s| s.to_vec());
self.common_serialised_prefix = prefix;
self.common_serialised_prefix.as_deref()
}
pub fn are_search_keys_identical(&self) -> bool {
let keys = self
.items
.keys()
.map(|k| self.search_key_func.apply(&Key::from(k.clone())));
are_search_keys_identical(keys)
}
pub fn map_no_split(&mut self, key: Vec<Vec<u8>>, value: Vec<u8>) -> bool {
self.raw_size += leaf_node_key_value_len(&key, &value);
let key_obj = Key::from(key.clone());
let serialised_key = key_obj.serialize();
let search_key = self.search_key_func.apply(&key_obj);
self.items.insert(key, value);
self.common_serialised_prefix = Some(match self.common_serialised_prefix.take() {
None => serialised_key,
Some(prefix) => common_prefix_pair(&prefix, &serialised_key).to_vec(),
});
if self.search_prefix.is_unknown() {
self.compute_search_prefix();
}
let new_prefix = match &self.search_prefix {
SearchPrefix::Computed(None) => search_key.clone(),
SearchPrefix::Computed(Some(p)) => common_prefix_pair(p, &search_key).to_vec(),
SearchPrefix::Unknown => unreachable!("just recomputed above"),
};
self.search_prefix = SearchPrefix::Computed(Some(new_prefix.clone()));
if self.items.len() > 1
&& self.maximum_size > 0
&& self.current_size() > self.maximum_size
&& (search_key != new_prefix || !self.are_search_keys_identical())
{
return true;
}
false
}
pub fn unmap(&mut self, key: &[Vec<u8>]) -> Option<Vec<u8>> {
let removed = self.items.shift_remove(key)?;
self.raw_size -= leaf_node_key_value_len(key, &removed);
self.key = None;
self.compute_search_prefix();
self.compute_serialised_prefix();
Some(removed)
}
pub fn serialise<S>(&mut self, store: &S, cache: &dyn PageCache) -> Result<Vec<u8>, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
let mut sorted_items: Vec<(Vec<Vec<u8>>, Vec<u8>)> = self
.items
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
sorted_items.sort();
let lines = serialise_leaf_node(
self.maximum_size,
self.key_width,
&sorted_items,
self.common_serialised_prefix.as_deref(),
)?;
let (sha1, _size) = store
.add_lines(
&crate::versionedfile::Key::ContentAddressed(vec![]),
Some(&[]),
&lines,
)
.map_err(|e| Error::AssertionFailed(format!("add_lines failed: {:?}", e)))?;
let mut full_key = Vec::with_capacity(5 + sha1.len());
full_key.extend_from_slice(b"sha1:");
full_key.extend_from_slice(&sha1);
let data: Vec<u8> = lines.iter().flatten().copied().collect();
if data.len() != self.current_size() {
return Err(Error::AssertionFailed("Invalid _current_size".into()));
}
cache.insert(full_key.clone(), data);
self.key = Some(full_key.clone());
Ok(full_key)
}
pub fn split(&mut self) -> Result<(Vec<u8>, Vec<(Vec<u8>, Node)>), Error> {
let common_prefix = match &self.search_prefix {
SearchPrefix::Unknown => {
return Err(Error::AssertionFailed("Search prefix must be known".into()))
}
SearchPrefix::Computed(None) => Vec::new(),
SearchPrefix::Computed(Some(p)) => p.clone(),
};
let split_at = common_prefix.len() + 1;
let mut result: indexmap::IndexMap<Vec<u8>, Node> = indexmap::IndexMap::new();
let items = std::mem::take(&mut self.items);
for (key, value) in items {
let search_key = self.search_key_func.apply(&Key::from(key.clone()));
let mut prefix: Vec<u8> = if search_key.len() >= split_at {
search_key[..split_at].to_vec()
} else {
search_key.clone()
};
if prefix.len() < split_at {
prefix.resize(split_at, 0);
}
let existing = result.shift_remove(&prefix);
let mut sub_node = match existing {
Some(Node::Leaf(b)) => Node::Leaf(b),
Some(Node::Internal(b)) => Node::Internal(b),
None => {
let mut leaf = LeafNode::new(self.search_key_func.clone());
leaf.maximum_size = self.maximum_size;
leaf.key_width = self.key_width;
Node::Leaf(Box::new(leaf))
}
};
let map_res = sub_node.map(key, value)?;
match map_res {
MapResult::InPlace { .. } => {
result.insert(prefix, sub_node);
}
MapResult::Split {
common_serialised_prefix: sub_prefix,
children,
} => {
let mut new_internal =
InternalNode::new(sub_prefix.clone(), self.search_key_func.clone());
new_internal.maximum_size = self.maximum_size;
new_internal.key_width = self.key_width;
for (split_prefix, child) in children {
new_internal.add_node(split_prefix, child)?;
}
result.insert(prefix, Node::Internal(Box::new(new_internal)));
}
}
}
Ok((common_prefix, result.into_iter().collect()))
}
pub fn map(&mut self, key: Vec<Vec<u8>>, value: Vec<u8>) -> Result<MapResult, Error> {
if let Some(old_value) = self.items.get(&key) {
self.raw_size -= leaf_node_key_value_len(&key, old_value);
}
self.key = None;
if self.map_no_split(key, value) {
let (common_prefix, children) = self.split()?;
Ok(MapResult::Split {
common_serialised_prefix: common_prefix,
children,
})
} else {
let prefix = match &self.search_prefix {
SearchPrefix::Unknown => {
return Err(Error::AssertionFailed(
"search_prefix must be known after map".into(),
));
}
SearchPrefix::Computed(None) => Vec::new(),
SearchPrefix::Computed(Some(p)) => p.clone(),
};
Ok(MapResult::InPlace {
search_prefix: prefix,
})
}
}
pub fn iteritems(&self, key_filter: Option<&[Vec<Vec<u8>>]>) -> Vec<(Vec<Vec<u8>>, Vec<u8>)> {
let mut out: Vec<(Vec<Vec<u8>>, Vec<u8>)> = Vec::new();
let filter = match key_filter {
None => {
for (k, v) in self.items.iter() {
out.push((k.clone(), v.clone()));
}
return out;
}
Some(f) => f,
};
let mut short_filters: std::collections::HashMap<usize, Vec<&[Vec<u8>]>> =
std::collections::HashMap::new();
for key in filter.iter() {
if key.len() == self.key_width {
if let Some(v) = self.items.get(key) {
out.push((key.clone(), v.clone()));
}
} else {
short_filters.entry(key.len()).or_default().push(key);
}
}
if !short_filters.is_empty() {
for (k, v) in self.items.iter() {
for (length, candidates) in short_filters.iter() {
if k.len() >= *length && candidates.iter().any(|c| *c == &k[..*length]) {
out.push((k.clone(), v.clone()));
break;
}
}
}
}
out
}
}
#[derive(Debug, Clone)]
pub enum MapResult {
InPlace { search_prefix: Vec<u8> },
Split {
common_serialised_prefix: Vec<u8>,
children: Vec<(Vec<u8>, Node)>,
},
}
#[derive(Debug, Clone)]
pub enum Node {
Leaf(Box<LeafNode>),
Internal(Box<InternalNode>),
}
impl Node {
pub fn key(&self) -> Option<&[u8]> {
match self {
Node::Leaf(l) => l.key.as_deref(),
Node::Internal(n) => n.key.as_deref(),
}
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
match self {
Node::Leaf(l) => l.len(),
Node::Internal(n) => n.len,
}
}
pub fn maximum_size(&self) -> usize {
match self {
Node::Leaf(l) => l.maximum_size,
Node::Internal(n) => n.maximum_size,
}
}
pub fn refs(&self) -> Result<Vec<Vec<u8>>, Error> {
match self {
Node::Leaf(_) => Ok(Vec::new()),
Node::Internal(n) => n.refs(),
}
}
pub fn map(&mut self, key: Vec<Vec<u8>>, value: Vec<u8>) -> Result<MapResult, Error> {
match self {
Node::Leaf(l) => l.map(key, value),
Node::Internal(_) => Err(Error::AssertionFailed(
"Node::map on InternalNode requires a store — \
use Node::map_with_store"
.into(),
)),
}
}
pub fn serialise<S>(&mut self, store: &S, cache: &dyn PageCache) -> Result<Vec<Vec<u8>>, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
match self {
Node::Leaf(l) => {
let k = l.serialise(store, cache)?;
Ok(vec![k])
}
Node::Internal(n) => n.serialise(store, cache),
}
}
pub fn unmap_with_store<S>(
self,
store: &S,
cache: &dyn PageCache,
key: &[Vec<u8>],
check_remap: bool,
) -> Result<Node, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
match self {
Node::Leaf(mut leaf) => {
if leaf.unmap(key).is_none() {
return Err(Error::AssertionFailed(format!("key not found: {:?}", key)));
}
Ok(Node::Leaf(leaf))
}
Node::Internal(internal) => {
internal_unmap_with_store(*internal, store, cache, key, check_remap)
}
}
}
pub fn map_with_store<S>(
&mut self,
store: &S,
cache: &dyn PageCache,
key: Vec<Vec<u8>>,
value: Vec<u8>,
) -> Result<MapResult, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
match self {
Node::Leaf(l) => l.map(key, value),
Node::Internal(_) => {
let placeholder = Node::Internal(Box::new(InternalNode::new(
Vec::new(),
SearchKeyFunc::Plain,
)));
let owned = std::mem::replace(self, placeholder);
let internal = match owned {
Node::Internal(b) => b,
Node::Leaf(_) => unreachable!("matched Internal above"),
};
let (new_self, result) =
internal_map_with_store(*internal, store, cache, key, value)?;
*self = new_self;
Ok(result)
}
}
}
pub fn iteritems<S>(
&mut self,
store: &S,
cache: &dyn PageCache,
key_filter: Option<&[Vec<Vec<u8>>]>,
) -> Result<Vec<(Vec<Vec<u8>>, Vec<u8>)>, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
match self {
Node::Leaf(l) => Ok(l.iteritems(key_filter)),
Node::Internal(n) => {
let mut out: Vec<(Vec<Vec<u8>>, Vec<u8>)> = Vec::new();
let children = n.iter_nodes(store, cache, key_filter, None)?;
for (mut child, filter) in children {
let sub_filter = filter.as_deref();
out.extend(child.iteritems(store, cache, sub_filter)?);
}
Ok(out)
}
}
}
}
#[derive(Debug, Clone)]
pub enum NodeRef {
Unloaded(Vec<u8>),
Loaded(Node),
}
impl NodeRef {
pub fn len(&self) -> usize {
match self {
NodeRef::Unloaded(_) => {
0
}
NodeRef::Loaded(n) => n.len(),
}
}
}
#[derive(Debug, Clone)]
pub struct InternalNode {
pub key: Option<Vec<u8>>,
pub maximum_size: usize,
pub key_width: usize,
pub len: usize,
pub node_width: usize,
pub raw_size: usize,
pub items: indexmap::IndexMap<Vec<u8>, NodeRef>,
pub search_prefix: Option<Vec<u8>>,
pub search_key_func: SearchKeyFunc,
}
impl InternalNode {
pub fn new(prefix: Vec<u8>, search_key_func: SearchKeyFunc) -> Self {
Self {
key: None,
maximum_size: 0,
key_width: 1,
len: 0,
node_width: 0,
raw_size: 0,
items: indexmap::IndexMap::new(),
search_prefix: Some(prefix),
search_key_func,
}
}
pub fn from_parsed(parsed: ParsedInternalNode, search_key_func: SearchKeyFunc) -> Self {
let mut items: indexmap::IndexMap<Vec<u8>, NodeRef> =
indexmap::IndexMap::with_capacity(parsed.items.len());
for (prefix, flat_key) in parsed.items {
items.insert(prefix, NodeRef::Unloaded(flat_key));
}
Self {
key: None,
maximum_size: parsed.maximum_size,
key_width: parsed.key_width,
len: parsed.length,
node_width: parsed.node_width,
raw_size: 0,
items,
search_prefix: Some(parsed.search_prefix),
search_key_func,
}
}
pub fn current_size(&self) -> usize {
internal_node_current_size(self.maximum_size, self.key_width, self.len, self.raw_size)
}
pub fn add_node(&mut self, prefix: Vec<u8>, node: Node) -> Result<(), Error> {
let search_prefix = self.search_prefix.as_deref().ok_or_else(|| {
Error::AssertionFailed("InternalNode.add_node: search_prefix is None".into())
})?;
if !prefix.starts_with(search_prefix) {
return Err(Error::AssertionFailed(format!(
"prefixes mismatch: {:?} must start with {:?}",
prefix, search_prefix
)));
}
if prefix.len() != search_prefix.len() + 1 {
return Err(Error::AssertionFailed(format!(
"prefix wrong length: len({:?}) is not {}",
prefix,
search_prefix.len() + 1
)));
}
self.len += node.len();
if self.items.is_empty() {
self.node_width = prefix.len();
}
if self.node_width != search_prefix.len() + 1 {
return Err(Error::AssertionFailed(format!(
"node width mismatch: {} is not {}",
self.node_width,
search_prefix.len() + 1
)));
}
self.items.insert(prefix, NodeRef::Loaded(node));
self.key = None;
Ok(())
}
pub fn search_key(&self, key: &Key) -> SerializedKey {
let base = self.search_key_func.apply(key);
if base.len() >= self.node_width {
base[..self.node_width].to_vec()
} else {
let mut padded = Vec::with_capacity(self.node_width);
padded.extend_from_slice(&base);
padded.resize(self.node_width, 0);
padded
}
}
pub fn search_prefix_filter(&self, key: &Key) -> SerializedKey {
let base = self.search_key_func.apply(key);
if base.len() >= self.node_width {
base[..self.node_width].to_vec()
} else {
base
}
}
pub fn compute_search_prefix(&mut self) -> Option<&[u8]> {
let prefix =
common_prefix_many(self.items.keys().map(|k| k.as_slice())).map(|s| s.to_vec());
self.search_prefix = prefix;
self.search_prefix.as_deref()
}
pub fn serialise<S>(&mut self, store: &S, cache: &dyn PageCache) -> Result<Vec<Vec<u8>>, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
let mut out: Vec<Vec<u8>> = Vec::new();
let prefixes: Vec<Vec<u8>> = self.items.keys().cloned().collect();
for prefix in &prefixes {
let entry = self.items.shift_remove(prefix).unwrap();
let entry = match entry {
NodeRef::Unloaded(k) => NodeRef::Unloaded(k),
NodeRef::Loaded(mut node) => {
if node.key().is_none() {
let keys = node.serialise(store, cache)?;
out.extend(keys);
}
NodeRef::Loaded(node)
}
};
self.items.insert(prefix.clone(), entry);
}
let search_prefix = self
.search_prefix
.clone()
.ok_or_else(|| Error::AssertionFailed("search_prefix is None".into()))?;
let mut sorted_items: Vec<InternalNodeChild> = self
.items
.iter()
.map(|(prefix, child)| {
let flat_key = match child {
NodeRef::Unloaded(k) => k.clone(),
NodeRef::Loaded(n) => n
.key()
.map(|s| s.to_vec())
.ok_or_else(|| {
Error::AssertionFailed("loaded child has no key after serialise".into())
})
.unwrap_or_default(),
};
InternalNodeChild {
prefix: prefix.clone(),
flat_key,
}
})
.collect();
sorted_items.sort_by(|a, b| a.prefix.cmp(&b.prefix));
let lines = serialise_internal_node(
self.maximum_size,
self.key_width,
self.len,
&search_prefix,
&sorted_items,
)?;
let (sha1, _size) = store
.add_lines(
&crate::versionedfile::Key::ContentAddressed(vec![]),
Some(&[]),
&lines,
)
.map_err(|e| Error::AssertionFailed(format!("add_lines failed: {:?}", e)))?;
let mut full_key = Vec::with_capacity(5 + sha1.len());
full_key.extend_from_slice(b"sha1:");
full_key.extend_from_slice(&sha1);
let data: Vec<u8> = lines.iter().flatten().copied().collect();
cache.insert(full_key.clone(), data);
self.key = Some(full_key.clone());
out.push(full_key);
Ok(out)
}
pub fn check_remap<S>(mut self, store: &S, cache: &dyn PageCache) -> Result<Node, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
let mut new_leaf = LeafNode::new(self.search_key_func.clone());
new_leaf.maximum_size = self.maximum_size;
new_leaf.key_width = self.key_width;
let children = self.iter_nodes(store, cache, None, Some(16))?;
for (node, _) in children {
if let Node::Internal(_) = node {
return Ok(Node::Internal(Box::new(self)));
}
let leaf = match node {
Node::Leaf(l) => l,
Node::Internal(_) => unreachable!(),
};
for (k, v) in leaf.items {
if new_leaf.map_no_split(k, v) {
return Ok(Node::Internal(Box::new(self)));
}
}
}
Ok(Node::Leaf(Box::new(new_leaf)))
}
pub fn iter_nodes<S>(
&mut self,
store: &S,
cache: &dyn PageCache,
key_filter: Option<&[Vec<Vec<u8>>]>,
batch_size: Option<usize>,
) -> Result<Vec<(Node, Option<Vec<Vec<Vec<u8>>>>)>, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
let mut out: Vec<(Node, Option<Vec<Vec<Vec<u8>>>>)> = Vec::new();
let mut to_load: indexmap::IndexMap<Vec<u8>, (Vec<u8>, Option<Vec<Vec<Vec<u8>>>>)> =
indexmap::IndexMap::new();
match key_filter {
None => {
for (prefix, child) in self.items.iter() {
match child {
NodeRef::Loaded(node) => out.push((node.clone(), None)),
NodeRef::Unloaded(sha1_key) => {
to_load.insert(sha1_key.clone(), (prefix.clone(), None));
}
}
}
}
Some(filter) if filter.len() == 1 => {
let only_key = Key::from(filter[0].clone());
let search_prefix = self.search_prefix_filter(&only_key);
if search_prefix.len() == self.node_width {
match self.items.get(&search_prefix) {
None => return Ok(out),
Some(NodeRef::Loaded(node)) => {
out.push((node.clone(), Some(vec![filter[0].clone()])));
return Ok(out);
}
Some(NodeRef::Unloaded(sha1_key)) => {
to_load.insert(
sha1_key.clone(),
(search_prefix, Some(vec![filter[0].clone()])),
);
}
}
} else {
self.iter_nodes_general_filter(filter, &mut out, &mut to_load);
}
}
Some(filter) => {
self.iter_nodes_general_filter(filter, &mut out, &mut to_load);
}
}
if to_load.is_empty() {
return Ok(out);
}
let mut still_missing: indexmap::IndexMap<Vec<u8>, (Vec<u8>, Option<Vec<Vec<Vec<u8>>>>)> =
indexmap::IndexMap::new();
for (sha1_key, (prefix, filter)) in to_load.drain(..) {
if let Some(bytes) = cache.get(&sha1_key) {
let node =
deserialise_node(&bytes, sha1_key.clone(), self.search_key_func.clone())?;
self.items
.insert(prefix.clone(), NodeRef::Loaded(node.clone()));
out.push((node, filter));
} else {
still_missing.insert(sha1_key, (prefix, filter));
}
}
if still_missing.is_empty() {
return Ok(out);
}
let batch_size = batch_size.unwrap_or(still_missing.len());
let key_order: Vec<Vec<u8>> = still_missing.keys().cloned().collect();
for batch_start in (0..key_order.len()).step_by(batch_size.max(1)) {
let batch_end = (batch_start + batch_size).min(key_order.len());
let batch_keys: Vec<crate::versionedfile::Key> = key_order[batch_start..batch_end]
.iter()
.map(|k| crate::versionedfile::Key::Fixed(vec![k.clone()]))
.collect();
let stream = store
.get_record_stream(&batch_keys, "unordered", true)
.map_err(|e| {
Error::AssertionFailed(format!("get_record_stream failed: {:?}", e))
})?;
for record in stream {
let record =
record.map_err(|e| Error::AssertionFailed(format!("record error: {:?}", e)))?;
let rec_key = record.key();
let sha1_key = rec_key
.segments()
.first()
.ok_or_else(|| Error::AssertionFailed("record key has no segments".into()))?
.clone();
let bytes = record.to_fulltext().into_owned();
let (prefix, filter) = still_missing.shift_remove(&sha1_key).ok_or_else(|| {
Error::AssertionFailed(format!("store returned unexpected key {:?}", sha1_key))
})?;
let node =
deserialise_node(&bytes, sha1_key.clone(), self.search_key_func.clone())?;
cache.insert(sha1_key, bytes);
self.items.insert(prefix, NodeRef::Loaded(node.clone()));
out.push((node, filter));
}
}
Ok(out)
}
fn iter_nodes_general_filter(
&self,
filter: &[Vec<Vec<u8>>],
out: &mut Vec<(Node, Option<Vec<Vec<Vec<u8>>>>)>,
to_load: &mut indexmap::IndexMap<Vec<u8>, (Vec<u8>, Option<Vec<Vec<Vec<u8>>>>)>,
) {
let mut prefix_to_keys: indexmap::IndexMap<Vec<u8>, Vec<Vec<Vec<u8>>>> =
indexmap::IndexMap::new();
let mut length_filters: std::collections::HashMap<usize, indexmap::IndexSet<Vec<u8>>> =
std::collections::HashMap::new();
for key in filter.iter() {
let search_prefix = self.search_prefix_filter(&Key::from(key.clone()));
length_filters
.entry(search_prefix.len())
.or_default()
.insert(search_prefix.clone());
prefix_to_keys
.entry(search_prefix)
.or_default()
.push(key.clone());
}
if length_filters.len() == 1 && length_filters.contains_key(&self.node_width) {
let prefixes = &length_filters[&self.node_width];
for prefix in prefixes.iter() {
let Some(child) = self.items.get(prefix) else {
continue;
};
let node_filter = prefix_to_keys.get(prefix).cloned();
match child {
NodeRef::Loaded(node) => out.push((node.clone(), node_filter)),
NodeRef::Unloaded(sha1_key) => {
to_load.insert(sha1_key.clone(), (prefix.clone(), node_filter));
}
}
}
} else {
for (prefix, child) in self.items.iter() {
let mut node_keys: Vec<Vec<Vec<u8>>> = Vec::new();
for (length, length_filter) in length_filters.iter() {
if prefix.len() >= *length {
let sub_prefix = &prefix[..*length];
if length_filter.contains(sub_prefix) {
if let Some(keys) = prefix_to_keys.get(sub_prefix) {
node_keys.extend(keys.iter().cloned());
}
}
}
}
if !node_keys.is_empty() {
match child {
NodeRef::Loaded(node) => out.push((node.clone(), Some(node_keys))),
NodeRef::Unloaded(sha1_key) => {
to_load.insert(sha1_key.clone(), (prefix.clone(), Some(node_keys)));
}
}
}
}
}
}
pub fn refs(&self) -> Result<Vec<Vec<u8>>, Error> {
if self.key.is_none() {
return Err(Error::AssertionFailed(
"unserialised nodes have no refs".into(),
));
}
let mut out: Vec<Vec<u8>> = Vec::with_capacity(self.items.len());
for value in self.items.values() {
match value {
NodeRef::Unloaded(k) => out.push(k.clone()),
NodeRef::Loaded(n) => {
let k = n.key().ok_or_else(|| {
Error::AssertionFailed("InternalNode.refs: loaded child has no key".into())
})?;
out.push(k.to_vec());
}
}
}
Ok(out)
}
}
pub trait PageCache: Send + Sync {
fn get(&self, sha1_key: &[u8]) -> Option<Vec<u8>>;
fn insert(&self, sha1_key: Vec<u8>, bytes: Vec<u8>);
fn clear(&self);
}
pub struct InMemoryPageCache {
inner: std::sync::Mutex<lru::LruCache<Vec<u8>, Vec<u8>>>,
}
impl InMemoryPageCache {
pub const DEFAULT_CAPACITY: usize = 1024;
pub fn new() -> Self {
Self::with_capacity(Self::DEFAULT_CAPACITY)
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
inner: std::sync::Mutex::new(lru::LruCache::new(
std::num::NonZeroUsize::new(capacity)
.expect("InMemoryPageCache capacity must be > 0"),
)),
}
}
}
impl Default for InMemoryPageCache {
fn default() -> Self {
Self::new()
}
}
impl PageCache for InMemoryPageCache {
fn get(&self, sha1_key: &[u8]) -> Option<Vec<u8>> {
let mut g = self.inner.lock().expect("page cache mutex poisoned");
g.get(sha1_key).cloned()
}
fn insert(&self, sha1_key: Vec<u8>, bytes: Vec<u8>) {
let mut g = self.inner.lock().expect("page cache mutex poisoned");
g.put(sha1_key, bytes);
}
fn clear(&self) {
let mut g = self.inner.lock().expect("page cache mutex poisoned");
g.clear();
}
}
pub fn deserialise_node(
data: &[u8],
key: Vec<u8>,
search_key_func: SearchKeyFunc,
) -> Result<Node, Error> {
if data.starts_with(b"chkleaf:\n") {
let parsed = deserialise_leaf_node(data)?;
let mut leaf = LeafNode::from_parsed(parsed, search_key_func);
if data.len() != leaf.current_size() {
return Err(Error::AssertionFailed(
"_current_size computed incorrectly".into(),
));
}
leaf.key = Some(key);
Ok(Node::Leaf(Box::new(leaf)))
} else if data.starts_with(b"chknode:\n") {
let parsed = deserialise_internal_node(data)?;
let mut internal = InternalNode::from_parsed(parsed, search_key_func);
internal.key = Some(key);
Ok(Node::Internal(Box::new(internal)))
} else {
Err(Error::AssertionFailed("Unknown node type.".into()))
}
}
fn internal_map_with_store<S>(
mut node: InternalNode,
store: &S,
cache: &dyn PageCache,
key: Vec<Vec<u8>>,
value: Vec<u8>,
) -> Result<(Node, MapResult), Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
if node.items.is_empty() {
return Err(Error::AssertionFailed(
"can't map in an empty InternalNode".into(),
));
}
let key_obj = Key::from(key.clone());
let search_key = node.search_key(&key_obj);
let search_prefix = node
.search_prefix
.clone()
.ok_or_else(|| Error::AssertionFailed("search_prefix is None".into()))?;
if node.node_width != search_prefix.len() + 1 {
return Err(Error::AssertionFailed(format!(
"node width mismatch: {} is not {}",
node.node_width,
search_prefix.len() + 1
)));
}
if !search_key.starts_with(&search_prefix) {
let new_prefix = common_prefix_pair(&search_prefix, &search_key).to_vec();
let mut new_parent = InternalNode::new(new_prefix.clone(), node.search_key_func.clone());
new_parent.maximum_size = node.maximum_size;
new_parent.key_width = node.key_width;
let wrap_prefix = search_prefix[..new_prefix.len() + 1].to_vec();
new_parent.add_node(wrap_prefix, Node::Internal(Box::new(node)))?;
return internal_map_with_store(new_parent, store, cache, key, value);
}
let mut children = node.iter_nodes(store, cache, Some(&[key.clone()]), None)?;
let (mut child_node, _filter) = if children.is_empty() {
let mut leaf = LeafNode::new(node.search_key_func.clone());
leaf.maximum_size = node.maximum_size;
leaf.key_width = node.key_width;
let new_leaf = Node::Leaf(Box::new(leaf));
node.items
.insert(search_key.clone(), NodeRef::Loaded(new_leaf.clone()));
(new_leaf, None)
} else {
children.remove(0)
};
let old_len = child_node.len();
let old_size = match &child_node {
Node::Leaf(l) => Some(l.current_size()),
Node::Internal(_) => None,
};
let map_res = child_node.map_with_store(store, cache, key, value)?;
match map_res {
MapResult::InPlace { .. } => {
let new_len = child_node.len();
node.len = node.len + new_len - old_len;
node.items
.insert(search_key.clone(), NodeRef::Loaded(child_node.clone()));
node.key = None;
let new_self = match &child_node {
Node::Leaf(child_leaf) => {
if old_size.is_none() {
node.check_remap(store, cache)?
} else {
let new_size = child_leaf.current_size();
let shrinkage = old_size.unwrap().saturating_sub(new_size);
if (shrinkage > 0 && new_size < INTERESTING_NEW_SIZE)
|| shrinkage > INTERESTING_SHRINKAGE_LIMIT
{
node.check_remap(store, cache)?
} else {
Node::Internal(Box::new(node))
}
}
}
Node::Internal(_) => Node::Internal(Box::new(node)),
};
let prefix = match &new_self {
Node::Leaf(l) => match &l.search_prefix {
SearchPrefix::Computed(Some(p)) => p.clone(),
SearchPrefix::Computed(None) => Vec::new(),
SearchPrefix::Unknown => {
return Err(Error::AssertionFailed(
"search_prefix unknown after map".into(),
))
}
},
Node::Internal(n) => n
.search_prefix
.clone()
.ok_or_else(|| Error::AssertionFailed("search_prefix is None".into()))?,
};
Ok((
new_self,
MapResult::InPlace {
search_prefix: prefix,
},
))
}
MapResult::Split {
common_serialised_prefix: child_prefix,
children: split_children,
} => {
let mut intermediate =
InternalNode::new(child_prefix.clone(), node.search_key_func.clone());
intermediate.maximum_size = node.maximum_size;
intermediate.key_width = node.key_width;
for (sp, ch) in split_children {
intermediate.add_node(sp, ch)?;
}
let new_child = Node::Internal(Box::new(intermediate));
let new_len = new_child.len();
node.items.insert(search_key, NodeRef::Loaded(new_child));
node.len = node.len + new_len - old_len;
node.key = None;
let prefix = node.search_prefix.clone().unwrap_or_default();
Ok((
Node::Internal(Box::new(node)),
MapResult::InPlace {
search_prefix: prefix,
},
))
}
}
}
fn internal_unmap_with_store<S>(
mut node: InternalNode,
store: &S,
cache: &dyn PageCache,
key: &[Vec<u8>],
check_remap: bool,
) -> Result<Node, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
if node.items.is_empty() {
return Err(Error::AssertionFailed(
"can't unmap in an empty InternalNode".into(),
));
}
let key_obj = Key::from(key.to_vec());
let children = node.iter_nodes(store, cache, Some(&[key.to_vec()]), None)?;
let (child, _filter) = children.into_iter().next().ok_or_else(|| {
Error::AssertionFailed(format!("key not found in internal node: {:?}", key))
})?;
node.len -= 1;
let unmapped = child.unmap_with_store(store, cache, key, true)?;
node.key = None;
let search_key = node.search_key(&key_obj);
let was_internal_after = matches!(unmapped, Node::Internal(_));
if unmapped.len() == 0 {
node.items.shift_remove(&search_key);
} else {
node.items.insert(search_key, NodeRef::Loaded(unmapped));
}
if node.items.len() == 1 {
let only = node
.items
.into_iter()
.next()
.expect("just checked len == 1")
.1;
return Ok(match only {
NodeRef::Loaded(n) => n,
NodeRef::Unloaded(sha1_key) => {
let bytes = match cache.get(&sha1_key) {
Some(b) => b,
None => {
let stream = store
.get_record_stream(
&[crate::versionedfile::Key::Fixed(vec![sha1_key.clone()])],
"unordered",
true,
)
.map_err(|e| {
Error::AssertionFailed(format!("get_record_stream: {:?}", e))
})?;
let record = stream
.into_iter()
.next()
.ok_or_else(|| {
Error::AssertionFailed(
"store returned no record for collapse child".into(),
)
})?
.map_err(|e| Error::AssertionFailed(format!("record: {:?}", e)))?;
let data = record.to_fulltext().into_owned();
cache.insert(sha1_key.clone(), data.clone());
data
}
};
deserialise_node(&bytes, sha1_key, node.search_key_func.clone())?
}
});
}
if was_internal_after {
return Ok(Node::Internal(Box::new(node)));
}
if check_remap {
node.check_remap(store, cache)
} else {
Ok(Node::Internal(Box::new(node)))
}
}
pub fn are_search_keys_identical<I, S>(search_keys: I) -> bool
where
I: IntoIterator<Item = S>,
S: AsRef<[u8]>,
{
let mut iter = search_keys.into_iter();
let first = match iter.next() {
Some(k) => k,
None => return true,
};
let first = first.as_ref();
iter.all(|k| k.as_ref() == first)
}
#[test]
fn test_common_prefix_many() {
assert_eq!(
common_prefix_many(vec![&b"abc"[..], &b"abc"[..]].into_iter()),
Some(&b"abc"[..])
);
assert_eq!(
common_prefix_many(vec![&b"abc"[..], &b"abcd"[..]].into_iter()),
Some(&b"abc"[..])
);
assert_eq!(
common_prefix_many(vec![&b"abc"[..], &b"ab"[..]].into_iter()),
Some(&b"ab"[..])
);
assert_eq!(
common_prefix_many(vec![&b"abc"[..], &b"bbd"[..]].into_iter()),
Some(&b""[..])
);
assert_eq!(
common_prefix_many(vec![&b"abcd"[..], &b"abc"[..], &b"abc"[..]].into_iter()),
Some(&b"abc"[..])
);
assert_eq!(common_prefix_many(vec![].into_iter()), None);
}
pub struct CHKMap<S>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
pub store: std::sync::Arc<S>,
pub cache: std::sync::Arc<dyn PageCache>,
pub search_key_func: SearchKeyFunc,
pub root: NodeRef,
}
impl<S> CHKMap<S>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
pub fn new(
store: std::sync::Arc<S>,
cache: std::sync::Arc<dyn PageCache>,
root_key: Option<Vec<u8>>,
search_key_func: SearchKeyFunc,
) -> Self {
let root = match root_key {
None => NodeRef::Loaded(Node::Leaf(Box::new(LeafNode::new(search_key_func.clone())))),
Some(k) => NodeRef::Unloaded(k),
};
Self {
store,
cache,
search_key_func,
root,
}
}
pub fn len(&mut self) -> Result<usize, Error> {
self.ensure_root()?;
Ok(match &self.root {
NodeRef::Loaded(n) => n.len(),
NodeRef::Unloaded(_) => unreachable!("just ensured root"),
})
}
pub fn is_empty(&mut self) -> Result<bool, Error> {
Ok(self.len()? == 0)
}
pub fn key(&self) -> Option<Vec<u8>> {
match &self.root {
NodeRef::Unloaded(k) => Some(k.clone()),
NodeRef::Loaded(n) => n.key().map(|s| s.to_vec()),
}
}
pub fn ensure_root(&mut self) -> Result<(), Error> {
if let NodeRef::Unloaded(sha1_key) = &self.root {
let key = sha1_key.clone();
let bytes = read_node_bytes(&*self.store, &*self.cache, &key)?;
let node = deserialise_node(&bytes, key, self.search_key_func.clone())?;
self.root = NodeRef::Loaded(node);
}
Ok(())
}
pub fn map(&mut self, key: Vec<Vec<u8>>, value: Vec<u8>) -> Result<(), Error> {
self.ensure_root()?;
let root = match &mut self.root {
NodeRef::Loaded(n) => n,
NodeRef::Unloaded(_) => unreachable!("just ensured root"),
};
let res = root.map_with_store(&*self.store, &*self.cache, key, value)?;
if let MapResult::Split {
common_serialised_prefix,
children,
} = res
{
let (mxs, kw) = match children.first() {
Some((_, n)) => (
n.maximum_size(),
match n {
Node::Leaf(l) => l.key_width,
Node::Internal(i) => i.key_width,
},
),
None => (0, 1),
};
let mut new_root =
InternalNode::new(common_serialised_prefix, self.search_key_func.clone());
new_root.maximum_size = mxs;
new_root.key_width = kw;
for (prefix, child) in children {
new_root.add_node(prefix, child)?;
}
self.root = NodeRef::Loaded(Node::Internal(Box::new(new_root)));
}
Ok(())
}
pub fn unmap(&mut self, key: &[Vec<u8>], check_remap: bool) -> Result<(), Error> {
self.ensure_root()?;
let placeholder = Node::Leaf(Box::new(LeafNode::new(self.search_key_func.clone())));
let old_root = std::mem::replace(&mut self.root, NodeRef::Loaded(placeholder));
let old_node = match old_root {
NodeRef::Loaded(n) => n,
NodeRef::Unloaded(_) => unreachable!("just ensured root"),
};
let new_root = old_node.unmap_with_store(&*self.store, &*self.cache, key, check_remap)?;
self.root = NodeRef::Loaded(new_root);
Ok(())
}
pub fn iteritems(
&mut self,
key_filter: Option<&[Vec<Vec<u8>>]>,
) -> Result<Vec<(Vec<Vec<u8>>, Vec<u8>)>, Error> {
self.ensure_root()?;
let root = match &mut self.root {
NodeRef::Loaded(n) => n,
NodeRef::Unloaded(_) => unreachable!("just ensured root"),
};
root.iteritems(&*self.store, &*self.cache, key_filter)
}
pub fn check_remap(&mut self) -> Result<(), Error> {
self.ensure_root()?;
let placeholder = Node::Leaf(Box::new(LeafNode::new(self.search_key_func.clone())));
let old_root = std::mem::replace(&mut self.root, NodeRef::Loaded(placeholder));
let old_node = match old_root {
NodeRef::Loaded(n) => n,
NodeRef::Unloaded(_) => unreachable!("just ensured root"),
};
let new_root = match old_node {
Node::Internal(b) => b.check_remap(&*self.store, &*self.cache)?,
other @ Node::Leaf(_) => other,
};
self.root = NodeRef::Loaded(new_root);
Ok(())
}
pub fn from_dict(
store: std::sync::Arc<S>,
cache: std::sync::Arc<dyn PageCache>,
initial_value: indexmap::IndexMap<Vec<Vec<u8>>, Vec<u8>>,
maximum_size: usize,
key_width: usize,
search_key_func: SearchKeyFunc,
) -> Result<Vec<u8>, Error> {
let mut leaf = LeafNode::new(search_key_func.clone());
leaf.maximum_size = maximum_size;
leaf.key_width = key_width;
leaf.raw_size = initial_value
.iter()
.map(|(k, v)| leaf_node_key_value_len(k, v))
.sum();
leaf.items = initial_value;
leaf.compute_search_prefix();
leaf.compute_serialised_prefix();
let mut node =
if leaf.items.len() > 1 && maximum_size > 0 && leaf.current_size() > maximum_size {
let (prefix, children) = leaf.split()?;
if children.len() == 1 {
return Err(Error::AssertionFailed(
"Failed to split using node._split".into(),
));
}
let mut internal = InternalNode::new(prefix, search_key_func);
internal.maximum_size = maximum_size;
internal.key_width = key_width;
for (split_prefix, child) in children {
internal.add_node(split_prefix, child)?;
}
Node::Internal(Box::new(internal))
} else {
Node::Leaf(Box::new(leaf))
};
let keys = node.serialise(&*store, &*cache)?;
Ok(keys
.last()
.cloned()
.ok_or_else(|| Error::AssertionFailed("from_dict produced no keys".into()))?)
}
pub fn iter_changes(
&mut self,
basis: &mut CHKMap<S>,
) -> Result<Vec<(Vec<Vec<u8>>, Option<Vec<u8>>, Option<Vec<u8>>)>, Error> {
let mut out: Vec<(Vec<Vec<u8>>, Option<Vec<u8>>, Option<Vec<u8>>)> = Vec::new();
if let (Some(a), Some(b)) = (self.key(), basis.key()) {
if a == b {
return Ok(out);
}
}
self.ensure_root()?;
basis.ensure_root()?;
let mut self_pending: std::collections::BinaryHeap<std::cmp::Reverse<PendingItem>> =
std::collections::BinaryHeap::new();
let mut basis_pending: std::collections::BinaryHeap<std::cmp::Reverse<PendingItem>> =
std::collections::BinaryHeap::new();
let self_root_ref = match &self.root {
NodeRef::Loaded(n) => n.clone(),
NodeRef::Unloaded(_) => unreachable!("just ensured root"),
};
let basis_root_ref = match &basis.root {
NodeRef::Loaded(n) => n.clone(),
NodeRef::Unloaded(_) => unreachable!("just ensured root"),
};
process_common_prefix_nodes(
&self_root_ref,
None,
&basis_root_ref,
None,
&mut self_pending,
&mut basis_pending,
self.search_key_func.clone(),
);
let excluded_keys: std::collections::HashSet<Vec<u8>> = std::collections::HashSet::new();
loop {
if self_pending.is_empty() && basis_pending.is_empty() {
break;
}
if self_pending.is_empty() {
for std::cmp::Reverse(item) in basis_pending.drain() {
if path_excluded(&item.path, &excluded_keys) {
continue;
}
drain_pending_item(
item,
true,
&mut basis.root,
&*basis.store,
&*basis.cache,
basis.search_key_func.clone(),
&mut out,
)?;
}
break;
}
if basis_pending.is_empty() {
for std::cmp::Reverse(item) in self_pending.drain() {
if path_excluded(&item.path, &excluded_keys) {
continue;
}
drain_pending_item(
item,
false,
&mut self.root,
&*self.store,
&*self.cache,
self.search_key_func.clone(),
&mut out,
)?;
}
break;
}
let self_top = &self_pending.peek().unwrap().0;
let basis_top = &basis_pending.peek().unwrap().0;
match self_top.prefix.cmp(&basis_top.prefix) {
std::cmp::Ordering::Less => {
let item = self_pending.pop().unwrap().0;
if path_excluded(&item.path, &excluded_keys) {
continue;
}
match item.payload {
Payload::Value { key, value } => {
out.push((key, None, Some(value)));
}
Payload::Subtree(child) => {
let node = resolve_noderef(
child,
&*self.store,
&*self.cache,
self.search_key_func.clone(),
)?;
expand_node(
node,
item.path,
self.search_key_func.clone(),
&mut self_pending,
);
}
}
}
std::cmp::Ordering::Greater => {
let item = basis_pending.pop().unwrap().0;
if path_excluded(&item.path, &excluded_keys) {
continue;
}
match item.payload {
Payload::Value { key, value } => {
out.push((key, Some(value), None));
}
Payload::Subtree(child) => {
let node = resolve_noderef(
child,
&*basis.store,
&*basis.cache,
basis.search_key_func.clone(),
)?;
expand_node(
node,
item.path,
basis.search_key_func.clone(),
&mut basis_pending,
);
}
}
}
std::cmp::Ordering::Equal => {
let self_is_value = matches!(self_top.payload, Payload::Value { .. });
let basis_is_value = matches!(basis_top.payload, Payload::Value { .. });
if self_is_value && basis_is_value {
let self_item = self_pending.pop().unwrap().0;
let basis_item = basis_pending.pop().unwrap().0;
match (self_item.payload, basis_item.payload) {
(
Payload::Value { key: sk, value: sv },
Payload::Value {
key: _bk,
value: bv,
},
) => {
if sv != bv {
out.push((sk, Some(bv), Some(sv)));
}
}
_ => unreachable!("just checked Value/Value"),
}
continue;
}
let self_child_key = subtree_child_sha1(&self_top.payload);
let basis_child_key = subtree_child_sha1(&basis_top.payload);
if self_child_key.is_some() && self_child_key == basis_child_key {
self_pending.pop();
basis_pending.pop();
continue;
}
if !self_is_value && !basis_is_value {
let self_item = self_pending.pop().unwrap().0;
let basis_item = basis_pending.pop().unwrap().0;
let self_child = match self_item.payload {
Payload::Subtree(c) => c,
_ => unreachable!(),
};
let basis_child = match basis_item.payload {
Payload::Subtree(c) => c,
_ => unreachable!(),
};
let self_node = resolve_noderef(
self_child,
&*self.store,
&*self.cache,
self.search_key_func.clone(),
)?;
let basis_node = resolve_noderef(
basis_child,
&*basis.store,
&*basis.cache,
basis.search_key_func.clone(),
)?;
process_common_prefix_nodes(
&self_node,
Some(item_path_extend(&self_item.path, &self_node)),
&basis_node,
Some(item_path_extend(&basis_item.path, &basis_node)),
&mut self_pending,
&mut basis_pending,
self.search_key_func.clone(),
);
continue;
}
if !self_is_value {
let item = self_pending.pop().unwrap().0;
if path_excluded(&item.path, &excluded_keys) {
continue;
}
let child = match item.payload {
Payload::Subtree(c) => c,
_ => unreachable!(),
};
let node = resolve_noderef(
child,
&*self.store,
&*self.cache,
self.search_key_func.clone(),
)?;
expand_node(
node,
item.path,
self.search_key_func.clone(),
&mut self_pending,
);
}
if !basis_is_value {
let item = basis_pending.pop().unwrap().0;
if path_excluded(&item.path, &excluded_keys) {
continue;
}
let child = match item.payload {
Payload::Subtree(c) => c,
_ => unreachable!(),
};
let node = resolve_noderef(
child,
&*basis.store,
&*basis.cache,
basis.search_key_func.clone(),
)?;
expand_node(
node,
item.path,
basis.search_key_func.clone(),
&mut basis_pending,
);
}
}
}
}
Ok(out)
}
pub fn apply_delta(
&mut self,
delta: Vec<(Option<Vec<Vec<u8>>>, Option<Vec<Vec<u8>>>, Vec<u8>)>,
) -> Result<Vec<u8>, Error> {
let new_only: Vec<Vec<Vec<u8>>> = delta
.iter()
.filter_map(|(old, new, _v)| match (old, new) {
(None, Some(k)) => Some(k.clone()),
_ => None,
})
.collect();
if !new_only.is_empty() {
let existing = self.iteritems(Some(&new_only))?;
if !existing.is_empty() {
return Err(Error::InconsistentDeltaDelta(
delta
.iter()
.map(|(o, n, v)| {
(
o.as_ref().map(|k| Key::from(k.clone())),
n.as_ref().map(|k| Key::from(k.clone())),
v.clone(),
)
})
.collect(),
format!("New items are already in the map {:?}.", existing),
));
}
}
let mut has_deletes = false;
for (old, new, _value) in &delta {
if let Some(old_k) = old {
if Some(old_k) != new.as_ref() {
self.unmap(old_k, false)?;
has_deletes = true;
}
}
}
for (_old, new, value) in &delta {
if let Some(new_k) = new {
self.map(new_k.clone(), value.clone())?;
}
}
if has_deletes {
self.check_remap()?;
}
self.save()
}
pub fn save(&mut self) -> Result<Vec<u8>, Error> {
match &self.root {
NodeRef::Unloaded(k) => return Ok(k.clone()),
NodeRef::Loaded(_) => {}
}
let root = match &mut self.root {
NodeRef::Loaded(n) => n,
NodeRef::Unloaded(_) => unreachable!(),
};
let keys = root.serialise(&*self.store, &*self.cache)?;
Ok(keys
.last()
.cloned()
.ok_or_else(|| Error::AssertionFailed("save returned no keys".into()))?)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DifferenceRecord {
pub page_key: Option<Vec<u8>>,
pub page_bytes: Option<Vec<u8>>,
pub items: Vec<(Vec<Vec<u8>>, Vec<u8>)>,
}
pub fn iter_interesting_nodes<S>(
store: &S,
cache: &dyn PageCache,
interesting_root_keys: &[Vec<u8>],
uninteresting_root_keys: &[Vec<u8>],
search_key_func: SearchKeyFunc,
) -> Result<Vec<DifferenceRecord>, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
let mut diff = CHKMapDifferenceState::new(
interesting_root_keys.to_vec(),
uninteresting_root_keys.to_vec(),
search_key_func,
);
diff.process(store, cache)
}
struct CHKMapDifferenceState {
new_root_keys: Vec<Vec<u8>>,
old_root_keys: Vec<Vec<u8>>,
all_old_chks: std::collections::HashSet<Vec<u8>>,
all_old_items: std::collections::HashSet<(Vec<Vec<u8>>, Vec<u8>)>,
processed_new_refs: std::collections::HashSet<Vec<u8>>,
old_queue: Vec<Vec<u8>>,
new_queue: Vec<Vec<u8>>,
new_item_queue: Vec<(Vec<Vec<u8>>, Vec<u8>)>,
search_key_func: SearchKeyFunc,
}
impl CHKMapDifferenceState {
fn new(
new_root_keys: Vec<Vec<u8>>,
old_root_keys: Vec<Vec<u8>>,
search_key_func: SearchKeyFunc,
) -> Self {
let mut all_old_chks: std::collections::HashSet<Vec<u8>> = std::collections::HashSet::new();
for k in &old_root_keys {
all_old_chks.insert(k.clone());
}
Self {
new_root_keys,
old_root_keys,
all_old_chks,
all_old_items: std::collections::HashSet::new(),
processed_new_refs: std::collections::HashSet::new(),
old_queue: Vec::new(),
new_queue: Vec::new(),
new_item_queue: Vec::new(),
search_key_func: search_key_func.clone(),
}
}
fn process<S>(
&mut self,
store: &S,
cache: &dyn PageCache,
) -> Result<Vec<DifferenceRecord>, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
let mut out: Vec<DifferenceRecord> = Vec::new();
let root_records = self.read_all_roots(store, cache)?;
for rec in root_records {
out.push(rec);
}
while !self.old_queue.is_empty() {
self.process_next_old(store, cache)?;
}
let flushed = self.flush_new_queue(store, cache)?;
out.extend(flushed);
Ok(out)
}
#[allow(clippy::type_complexity)]
fn read_nodes<S>(
&self,
store: &S,
cache: &dyn PageCache,
keys: &[Vec<u8>],
) -> Result<
Vec<(
Vec<u8>,
Vec<u8>,
Vec<(Vec<u8>, Vec<u8>)>,
Vec<(Vec<Vec<u8>>, Vec<u8>)>,
)>,
Error,
>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
let key_objs: Vec<crate::versionedfile::Key> = keys
.iter()
.map(|k| crate::versionedfile::Key::Fixed(vec![k.clone()]))
.collect();
let stream = store
.get_record_stream(&key_objs, "unordered", true)
.map_err(|e| Error::AssertionFailed(format!("get_record_stream: {:?}", e)))?;
let mut out = Vec::new();
for record in stream {
let record = record.map_err(|e| Error::AssertionFailed(format!("record: {:?}", e)))?;
if record.storage_kind() == "absent" {
return Err(Error::AssertionFailed(format!(
"absent record: {:?}",
record.key()
)));
}
let sha1 = record.key().segments().first().cloned().unwrap_or_default();
let bytes = record.to_fulltext().into_owned();
cache.insert(sha1.clone(), bytes.clone());
let node = deserialise_node(&bytes, sha1.clone(), self.search_key_func.clone())?;
let (prefix_refs, items) = match node {
Node::Internal(internal) => {
let refs: Vec<(Vec<u8>, Vec<u8>)> = internal
.items
.into_iter()
.filter_map(|(prefix, child)| match child {
NodeRef::Unloaded(k) => Some((prefix, k)),
NodeRef::Loaded(n) => n.key().map(|s| (prefix, s.to_vec())),
})
.collect();
(refs, Vec::new())
}
Node::Leaf(leaf) => {
let items: Vec<(Vec<Vec<u8>>, Vec<u8>)> = leaf.items.into_iter().collect();
(Vec::new(), items)
}
};
out.push((sha1, bytes, prefix_refs, items));
}
Ok(out)
}
fn read_old_roots<S>(
&mut self,
store: &S,
cache: &dyn PageCache,
) -> Result<Vec<(Vec<u8>, Vec<u8>)>, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
let mut old_chks_to_enqueue: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
let nodes = self.read_nodes(store, cache, &self.old_root_keys.clone())?;
for (_sha1, _bytes, prefix_refs, items) in nodes {
let filtered: Vec<(Vec<u8>, Vec<u8>)> = prefix_refs
.into_iter()
.filter(|(_p, r)| !self.all_old_chks.contains(r))
.collect();
for (_p, r) in &filtered {
self.all_old_chks.insert(r.clone());
}
for item in items {
self.all_old_items.insert(item);
}
old_chks_to_enqueue.extend(filtered);
}
Ok(old_chks_to_enqueue)
}
fn enqueue_old(
&mut self,
new_prefixes: &std::collections::HashSet<Vec<u8>>,
old_chks_to_enqueue: Vec<(Vec<u8>, Vec<u8>)>,
) {
for (prefix, refk) in old_chks_to_enqueue {
let mut interesting = false;
for i in (1..=prefix.len()).rev() {
if new_prefixes.contains(&prefix[..i]) {
interesting = true;
break;
}
}
if interesting {
self.old_queue.push(refk);
}
}
}
fn read_all_roots<S>(
&mut self,
store: &S,
cache: &dyn PageCache,
) -> Result<Vec<DifferenceRecord>, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
let mut out: Vec<DifferenceRecord> = Vec::new();
if self.old_root_keys.is_empty() {
self.new_queue = self.new_root_keys.clone();
return Ok(out);
}
let old_chks_to_enqueue = self.read_old_roots(store, cache)?;
let mut new_keys: Vec<Vec<u8>> = self
.new_root_keys
.iter()
.filter(|k| !self.all_old_chks.contains(*k))
.cloned()
.collect();
new_keys.sort();
new_keys.dedup();
let mut new_prefixes: std::collections::HashSet<Vec<u8>> = std::collections::HashSet::new();
for k in &new_keys {
self.processed_new_refs.insert(k.clone());
}
let nodes = self.read_nodes(store, cache, &new_keys)?;
for (sha1, bytes, prefix_refs, items) in nodes {
let filtered: Vec<(Vec<u8>, Vec<u8>)> = prefix_refs
.into_iter()
.filter(|(_p, r)| {
!self.all_old_chks.contains(r) && !self.processed_new_refs.contains(r)
})
.collect();
let refs: Vec<Vec<u8>> = filtered.iter().map(|(_, r)| r.clone()).collect();
for (p, _) in &filtered {
new_prefixes.insert(p.clone());
}
self.new_queue.extend(refs.clone());
let new_items: Vec<(Vec<Vec<u8>>, Vec<u8>)> = items
.into_iter()
.filter(|it| !self.all_old_items.contains(it))
.collect();
for (k, _v) in &new_items {
let prefix = self.search_key_func.apply(&Key::from(k.clone()));
new_prefixes.insert(prefix);
}
self.new_item_queue.extend(new_items);
for r in refs {
self.processed_new_refs.insert(r);
}
out.push(DifferenceRecord {
page_key: Some(sha1),
page_bytes: Some(bytes),
items: Vec::new(),
});
}
let snapshot: Vec<Vec<u8>> = new_prefixes.iter().cloned().collect();
for p in snapshot {
for i in 1..p.len() {
new_prefixes.insert(p[..i].to_vec());
}
}
self.enqueue_old(&new_prefixes, old_chks_to_enqueue);
Ok(out)
}
fn flush_new_queue<S>(
&mut self,
store: &S,
cache: &dyn PageCache,
) -> Result<Vec<DifferenceRecord>, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
let mut out: Vec<DifferenceRecord> = Vec::new();
let mut refs: std::collections::HashSet<Vec<u8>> = self.new_queue.drain(..).collect();
let queued = std::mem::take(&mut self.new_item_queue);
let new_items: Vec<(Vec<Vec<u8>>, Vec<u8>)> = queued
.into_iter()
.filter(|it| !self.all_old_items.contains(it))
.collect();
if !new_items.is_empty() {
out.push(DifferenceRecord {
page_key: None,
page_bytes: None,
items: new_items,
});
}
for k in &self.all_old_chks {
refs.remove(k);
}
for k in &refs {
self.processed_new_refs.insert(k.clone());
}
while !refs.is_empty() {
let mut next_refs: std::collections::HashSet<Vec<u8>> =
std::collections::HashSet::new();
let refs_vec: Vec<Vec<u8>> = refs.into_iter().collect();
let nodes = self.read_nodes(store, cache, &refs_vec)?;
for (sha1, bytes, prefix_refs, items) in nodes {
let items: Vec<(Vec<Vec<u8>>, Vec<u8>)> = if self.all_old_items.is_empty() {
items
} else {
items
.into_iter()
.filter(|it| !self.all_old_items.contains(it))
.collect()
};
out.push(DifferenceRecord {
page_key: Some(sha1),
page_bytes: Some(bytes),
items,
});
for (_p, r) in prefix_refs {
next_refs.insert(r);
}
}
for k in &self.all_old_chks {
next_refs.remove(k);
}
for k in &self.processed_new_refs {
next_refs.remove(k);
}
for k in &next_refs {
self.processed_new_refs.insert(k.clone());
}
refs = next_refs;
}
Ok(out)
}
fn process_next_old<S>(&mut self, store: &S, cache: &dyn PageCache) -> Result<(), Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
let refs: Vec<Vec<u8>> = std::mem::take(&mut self.old_queue);
let nodes = self.read_nodes(store, cache, &refs)?;
for (_sha1, _bytes, prefix_refs, items) in nodes {
for item in items {
self.all_old_items.insert(item);
}
let new_refs: Vec<Vec<u8>> = prefix_refs
.into_iter()
.filter_map(|(_p, r)| {
if self.all_old_chks.contains(&r) {
None
} else {
Some(r)
}
})
.collect();
for r in &new_refs {
self.all_old_chks.insert(r.clone());
}
self.old_queue.extend(new_refs);
}
Ok(())
}
}
#[derive(Debug)]
struct PendingItem {
prefix: Vec<u8>,
payload: Payload,
path: Vec<Vec<u8>>,
}
#[derive(Debug)]
enum Payload {
Subtree(NodeRef),
Value { key: Vec<Vec<u8>>, value: Vec<u8> },
}
impl PartialEq for PendingItem {
fn eq(&self, other: &Self) -> bool {
self.prefix == other.prefix
}
}
impl Eq for PendingItem {}
impl PartialOrd for PendingItem {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PendingItem {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.prefix.cmp(&other.prefix)
}
}
fn path_excluded(path: &[Vec<u8>], excluded_keys: &std::collections::HashSet<Vec<u8>>) -> bool {
path.iter().any(|k| excluded_keys.contains(k))
}
fn resolve_noderef<S>(
nref: NodeRef,
store: &S,
cache: &dyn PageCache,
search_key_func: SearchKeyFunc,
) -> Result<Node, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
match nref {
NodeRef::Loaded(n) => Ok(n),
NodeRef::Unloaded(sha1_key) => {
let bytes = read_node_bytes(store, cache, &sha1_key)?;
deserialise_node(&bytes, sha1_key, search_key_func)
}
}
}
fn item_path_extend(parent_path: &[Vec<u8>], node: &Node) -> Vec<Vec<u8>> {
let mut out = Vec::with_capacity(parent_path.len() + 1);
out.extend_from_slice(parent_path);
if let Some(k) = node.key() {
out.push(k.to_vec());
}
out
}
fn expand_node(
node: Node,
parent_path: Vec<Vec<u8>>,
_search_key_func: SearchKeyFunc,
pending: &mut std::collections::BinaryHeap<std::cmp::Reverse<PendingItem>>,
) {
let path = item_path_extend(&parent_path, &node);
match node {
Node::Leaf(leaf) => {
for (k, v) in leaf.items {
let search_key = leaf.search_key_func.apply(&Key::from(k.clone()));
pending.push(std::cmp::Reverse(PendingItem {
prefix: search_key,
payload: Payload::Value { key: k, value: v },
path: path.clone(),
}));
}
}
Node::Internal(internal) => {
for (prefix, child) in internal.items {
pending.push(std::cmp::Reverse(PendingItem {
prefix,
payload: Payload::Subtree(child),
path: path.clone(),
}));
}
}
}
}
fn subtree_child_sha1(payload: &Payload) -> Option<Vec<u8>> {
match payload {
Payload::Subtree(NodeRef::Unloaded(k)) => Some(k.clone()),
Payload::Subtree(NodeRef::Loaded(n)) => n.key().map(|s| s.to_vec()),
Payload::Value { .. } => None,
}
}
fn process_common_prefix_nodes(
self_node: &Node,
self_parent_path: Option<Vec<Vec<u8>>>,
basis_node: &Node,
basis_parent_path: Option<Vec<Vec<u8>>>,
self_pending: &mut std::collections::BinaryHeap<std::cmp::Reverse<PendingItem>>,
basis_pending: &mut std::collections::BinaryHeap<std::cmp::Reverse<PendingItem>>,
search_key_func: SearchKeyFunc,
) {
let self_path = self_parent_path.unwrap_or_else(|| {
self_node
.key()
.map(|s| vec![s.to_vec()])
.unwrap_or_default()
});
let basis_path = basis_parent_path.unwrap_or_else(|| {
basis_node
.key()
.map(|s| vec![s.to_vec()])
.unwrap_or_default()
});
match (self_node, basis_node) {
(Node::Internal(s), Node::Internal(b)) => {
let self_set: std::collections::HashMap<Vec<u8>, Option<Vec<u8>>> = s
.items
.iter()
.map(|(p, c)| (p.clone(), node_ref_sha1(c)))
.collect();
let basis_set: std::collections::HashMap<Vec<u8>, Option<Vec<u8>>> = b
.items
.iter()
.map(|(p, c)| (p.clone(), node_ref_sha1(c)))
.collect();
for (prefix, child) in s.items.iter() {
let s_sha1 = node_ref_sha1(child);
if basis_set.get(prefix) != Some(&s_sha1) {
self_pending.push(std::cmp::Reverse(PendingItem {
prefix: prefix.clone(),
payload: Payload::Subtree(child.clone()),
path: self_path.clone(),
}));
}
}
for (prefix, child) in b.items.iter() {
let b_sha1 = node_ref_sha1(child);
if self_set.get(prefix) != Some(&b_sha1) {
basis_pending.push(std::cmp::Reverse(PendingItem {
prefix: prefix.clone(),
payload: Payload::Subtree(child.clone()),
path: basis_path.clone(),
}));
}
}
}
(Node::Leaf(s), Node::Leaf(b)) => {
for (k, v) in s.items.iter() {
if b.items.get(k) != Some(v) {
let prefix = search_key_func.apply(&Key::from(k.clone()));
self_pending.push(std::cmp::Reverse(PendingItem {
prefix,
payload: Payload::Value {
key: k.clone(),
value: v.clone(),
},
path: self_path.clone(),
}));
}
}
for (k, v) in b.items.iter() {
if s.items.get(k) != Some(v) {
let prefix = search_key_func.apply(&Key::from(k.clone()));
basis_pending.push(std::cmp::Reverse(PendingItem {
prefix,
payload: Payload::Value {
key: k.clone(),
value: v.clone(),
},
path: basis_path.clone(),
}));
}
}
}
_ => {
expand_node(
self_node.clone(),
self_path.clone(),
search_key_func.clone(),
self_pending,
);
expand_node(
basis_node.clone(),
basis_path,
search_key_func,
basis_pending,
);
}
}
}
fn node_ref_sha1(nref: &NodeRef) -> Option<Vec<u8>> {
match nref {
NodeRef::Unloaded(k) => Some(k.clone()),
NodeRef::Loaded(n) => n.key().map(|s| s.to_vec()),
}
}
fn drain_pending_item<S>(
item: PendingItem,
is_basis: bool,
other_root: &mut NodeRef,
store: &S,
cache: &dyn PageCache,
search_key_func: SearchKeyFunc,
out: &mut Vec<(Vec<Vec<u8>>, Option<Vec<u8>>, Option<Vec<u8>>)>,
) -> Result<(), Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
let _ = other_root;
match item.payload {
Payload::Value { key, value } => {
if is_basis {
out.push((key, Some(value), None));
} else {
out.push((key, None, Some(value)));
}
Ok(())
}
Payload::Subtree(child) => {
let mut node = resolve_noderef(child, store, cache, search_key_func)?;
let items = node.iteritems(store, cache, None)?;
for (k, v) in items {
if is_basis {
out.push((k, Some(v), None));
} else {
out.push((k, None, Some(v)));
}
}
Ok(())
}
}
}
fn read_node_bytes<S>(store: &S, cache: &dyn PageCache, sha1_key: &[u8]) -> Result<Vec<u8>, Error>
where
S: crate::versionedfile::VersionedFiles + ?Sized,
{
if let Some(b) = cache.get(sha1_key) {
return Ok(b);
}
let stream = store
.get_record_stream(
&[crate::versionedfile::Key::Fixed(vec![sha1_key.to_vec()])],
"unordered",
true,
)
.map_err(|e| Error::AssertionFailed(format!("get_record_stream: {:?}", e)))?;
let record = stream
.into_iter()
.next()
.ok_or_else(|| Error::AssertionFailed("store returned no record".into()))?
.map_err(|e| Error::AssertionFailed(format!("record: {:?}", e)))?;
let data = record.to_fulltext().into_owned();
cache.insert(sha1_key.to_vec(), data.clone());
Ok(data)
}
#[cfg(test)]
pub(crate) mod testing {
pub struct FakeChkStore {
pub(crate) pages: std::sync::Mutex<std::collections::HashMap<Vec<u8>, Vec<u8>>>,
}
impl FakeChkStore {
pub fn new() -> Self {
Self {
pages: std::sync::Mutex::new(std::collections::HashMap::new()),
}
}
}
impl crate::versionedfile::VersionedFiles for FakeChkStore {
fn get_parent_map(
&self,
_keys: &[crate::versionedfile::Key],
) -> Result<
std::collections::HashMap<crate::versionedfile::Key, Vec<crate::versionedfile::Key>>,
crate::knit::KnitError,
> {
Ok(std::collections::HashMap::new())
}
fn get_record_stream(
&self,
keys: &[crate::versionedfile::Key],
_ordering: &str,
_include_delta_closure: bool,
) -> Result<
Box<
dyn Iterator<
Item = Result<
Box<dyn crate::versionedfile::ContentFactory>,
crate::knit::KnitError,
>,
>,
>,
crate::knit::KnitError,
> {
let pages = self.pages.lock().unwrap();
let mut records: Vec<
Result<Box<dyn crate::versionedfile::ContentFactory>, crate::knit::KnitError>,
> = Vec::with_capacity(keys.len());
for key in keys {
let full = key.segments().first().cloned().unwrap_or_default();
let bare: &[u8] = if full.starts_with(b"sha1:") {
&full[5..]
} else {
&full
};
if let Some(data) = pages.get(bare) {
records.push(Ok(
Box::new(crate::versionedfile::FulltextContentFactory::new(
Some(bare.to_vec()),
key.clone(),
Some(vec![]),
data.clone(),
)) as Box<dyn crate::versionedfile::ContentFactory>,
));
}
}
Ok(Box::new(records.into_iter()))
}
fn get_sha1s(
&self,
_keys: &[crate::versionedfile::Key],
) -> Result<
std::collections::HashMap<crate::versionedfile::Key, Vec<u8>>,
crate::knit::KnitError,
> {
Ok(std::collections::HashMap::new())
}
fn keys(&self) -> Result<Vec<crate::versionedfile::Key>, crate::knit::KnitError> {
let p = self.pages.lock().unwrap();
Ok(p.keys()
.map(|k| crate::versionedfile::Key::Fixed(vec![k.clone()]))
.collect())
}
fn add_lines(
&self,
_key: &crate::versionedfile::Key,
_parents: Option<&[crate::versionedfile::Key]>,
lines: &[Vec<u8>],
) -> Result<(Vec<u8>, usize), crate::knit::KnitError> {
use sha1::{Digest, Sha1};
let data: Vec<u8> = lines.iter().flatten().copied().collect();
let mut hasher = Sha1::new();
hasher.update(&data);
let digest = hasher.finalize();
let sha1_hex: String = digest.iter().map(|b| format!("{:02x}", b)).collect();
let sha1_bytes = sha1_hex.into_bytes();
let size = data.len();
self.pages.lock().unwrap().insert(sha1_bytes.clone(), data);
Ok((sha1_bytes, size))
}
fn insert_record_stream(
&self,
_stream: Box<dyn Iterator<Item = Box<dyn crate::versionedfile::ContentFactory>>>,
) -> Result<(), crate::knit::KnitError> {
Err(crate::knit::KnitError::NotImplemented(
"insert_record_stream",
))
}
fn iter_lines_added_or_present_in_keys(
&self,
_keys: &[crate::versionedfile::Key],
) -> Result<Vec<(Vec<u8>, crate::versionedfile::Key)>, crate::knit::KnitError> {
Err(crate::knit::KnitError::NotImplemented(
"iter_lines_added_or_present_in_keys",
))
}
fn annotate(
&self,
_key: &crate::versionedfile::Key,
) -> Result<Vec<(crate::versionedfile::Key, Vec<u8>)>, crate::knit::KnitError> {
Err(crate::knit::KnitError::NotImplemented("annotate"))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn key(parts: &[&[u8]]) -> Key {
Key::from(parts.iter().map(|p| p.to_vec()).collect::<Vec<_>>())
}
#[test]
fn search_key_plain_joins_with_nul() {
assert_eq!(
search_key_plain(&key(&[b"foo", b"bar"])),
b"foo\x00bar".to_vec()
);
assert_eq!(search_key_plain(&key(&[b"only"])), b"only".to_vec());
}
#[test]
fn search_key_16_is_uppercase_hex() {
let out = search_key_16(&key(&[b"hello"]));
let s = std::str::from_utf8(&out).unwrap();
assert_eq!(s.len(), 8);
assert!(s
.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_ascii_lowercase()));
}
#[test]
fn search_key_16_joins_multi_element_keys_with_nul() {
assert_eq!(search_key_16(&key(&[b"a", b"a"])), b"E8B7BE43\x00E8B7BE43");
assert_eq!(search_key_16(&key(&[b"a", b"b"])), b"E8B7BE43\x0071BEEFF9");
assert_eq!(search_key_16(&key(&[b"b", b""])), b"71BEEFF9\x0000000000");
}
#[test]
fn search_key_255_uses_raw_be_bytes_with_lf_replaced() {
let out = search_key_255(&key(&[b"hello"]));
assert_eq!(out.len(), 4);
assert!(!out.contains(&b'\n'));
}
#[test]
fn search_key_255_replaces_lf_with_underscore() {
for input in [b"abc".as_slice(), b"x", b"y"] {
let out = search_key_255(&key(&[input]));
assert!(!out.contains(&b'\n'));
}
}
#[test]
fn search_key_255_multi_element_keys_use_nul_separator() {
assert_eq!(
search_key_255(&key(&[b"a", b"b"])),
b"\xe8\xb7\xbeC\x00q\xbe\xef\xf9"
);
}
#[test]
fn bytes_to_text_key_parses_file_record() {
let bytes = b"file: file-id\nparent-id\nname\nrevision-id\n\
da39a3ee5e6b4b0d3255bfef95601890afd80709\n100\nN";
let (file_id, revision_id) = bytes_to_text_key(bytes).unwrap();
assert_eq!(file_id, b"file-id");
assert_eq!(revision_id, b"revision-id");
}
#[test]
fn bytes_to_text_key_rejects_missing_separator() {
let bytes = b"file:file-id\nparent-id\nname\nrevision-id\n\
da39a3ee5e6b4b0d3255bfef95601890afd80709\n100\nN";
assert!(bytes_to_text_key(bytes).is_err());
}
#[test]
fn key_serialize_joins_parts_with_nul() {
assert_eq!(key(&[b"foo", b"bar"]).serialize(), b"foo\x00bar".to_vec());
assert_eq!(key(&[b"alone"]).serialize(), b"alone".to_vec());
}
#[test]
fn key_len_returns_part_count() {
assert_eq!(key(&[b"foo", b"bar"]).len(), 2);
assert_eq!(key(&[b"a"]).len(), 1);
}
const LEAF_FIXTURE: &[u8] = b"chkleaf:\n100\n1\n2\nalph\n2\x002\nv2\nv2line2\na\x001\nv1\n";
#[test]
fn deserialise_leaf_fixture_items_match() {
let p = deserialise_leaf_node(LEAF_FIXTURE).unwrap();
assert_eq!(p.maximum_size, 100);
assert_eq!(p.key_width, 1);
assert_eq!(p.length, 2);
assert_eq!(p.common_serialised_prefix, b"alph");
assert_eq!(p.items.len(), 2);
assert_eq!(p.items[0].0, vec![b"alph2".to_vec()]);
assert_eq!(p.items[0].1, b"v2\nv2line2");
assert_eq!(p.items[1].0, vec![b"alpha".to_vec()]);
assert_eq!(p.items[1].1, b"v1");
}
#[test]
fn deserialise_leaf_raw_size_matches_python_formula() {
let p = deserialise_leaf_node(LEAF_FIXTURE).unwrap();
assert_eq!(p.raw_size, 30);
}
#[test]
fn deserialise_leaf_empty_items() {
let data = b"chkleaf:\n100\n1\n0\n\n";
let p = deserialise_leaf_node(data).unwrap();
assert_eq!(p.length, 0);
assert!(p.items.is_empty());
assert_eq!(p.common_serialised_prefix, b"");
assert_eq!(p.raw_size, 0);
}
#[test]
fn deserialise_leaf_multi_element_key() {
let data = b"chkleaf:\n200\n2\n1\n\nkey1\x00sub\x001\nhello\n";
let p = deserialise_leaf_node(data).unwrap();
assert_eq!(p.key_width, 2);
assert_eq!(p.items.len(), 1);
assert_eq!(p.items[0].0, vec![b"key1".to_vec(), b"sub".to_vec()]);
assert_eq!(p.items[0].1, b"hello");
}
#[test]
fn deserialise_leaf_rejects_missing_trailing_newline() {
let data = b"chkleaf:\n100\n1\n0\n";
assert!(matches!(
deserialise_leaf_node(data),
Err(Error::DeserializeError(_))
));
}
#[test]
fn deserialise_leaf_rejects_bad_magic() {
let data = b"notaleaf:\n100\n1\n0\n\n";
assert!(matches!(
deserialise_leaf_node(data),
Err(Error::DeserializeError(_))
));
}
#[test]
fn deserialise_leaf_rejects_wrong_element_count() {
let data = b"chkleaf:\n100\n1\n1\n\nfoo\x00bar\x001\nval\n";
assert!(matches!(
deserialise_leaf_node(data),
Err(Error::DeserializeError(_))
));
}
#[test]
fn deserialise_leaf_rejects_length_mismatch() {
let data = b"chkleaf:\n100\n1\n2\n\nfoo\x001\nval\n";
assert!(matches!(
deserialise_leaf_node(data),
Err(Error::DeserializeError(_))
));
}
const INTERNAL_FIXTURE: &[u8] = b"chknode:\n200\n1\n2\npre\nbar\x00sha1:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\nfoo\x00sha1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n";
#[test]
fn deserialise_internal_fixture_items_match() {
let p = deserialise_internal_node(INTERNAL_FIXTURE).unwrap();
assert_eq!(p.maximum_size, 200);
assert_eq!(p.key_width, 1);
assert_eq!(p.length, 2);
assert_eq!(p.search_prefix, b"pre");
assert_eq!(p.items.len(), 2);
assert_eq!(p.items[0].0, b"prebar");
assert_eq!(
p.items[0].1,
b"sha1:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
);
assert_eq!(p.items[1].0, b"prefoo");
assert_eq!(
p.items[1].1,
b"sha1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
);
assert_eq!(p.node_width, b"prefoo".len());
}
#[test]
fn deserialise_internal_rejects_empty_items() {
let data = b"chknode:\n100\n1\n0\n\n";
assert!(matches!(
deserialise_internal_node(data),
Err(Error::DeserializeError(_))
));
}
#[test]
fn deserialise_internal_rejects_bad_magic() {
let data = b"notchk:\n100\n1\n1\n\nfoo\x00sha1:aaaa\n";
assert!(matches!(
deserialise_internal_node(data),
Err(Error::DeserializeError(_))
));
}
#[test]
fn deserialise_internal_rejects_missing_trailing_newline() {
let data = b"chknode:\n100\n1\n1\n\nfoo\x00sha1:aaaa";
assert!(matches!(
deserialise_internal_node(data),
Err(Error::DeserializeError(_))
));
}
#[test]
fn deserialise_internal_rejects_line_without_nul() {
let data = b"chknode:\n100\n1\n1\n\nfoobar\n";
assert!(matches!(
deserialise_internal_node(data),
Err(Error::DeserializeError(_))
));
}
#[test]
fn serialise_leaf_node_roundtrips_through_deserialise() {
let items: Vec<(Vec<Vec<u8>>, Vec<u8>)> = vec![
(vec![b"a".to_vec()], b"v1".to_vec()),
(vec![b"alpha".to_vec()], b"v2\nv2line2".to_vec()),
];
let out = serialise_leaf_node(100, 1, &items, Some(b"a")).unwrap();
let blob: Vec<u8> = out.iter().flatten().copied().collect();
let parsed = deserialise_leaf_node(&blob).unwrap();
assert_eq!(parsed.maximum_size, 100);
assert_eq!(parsed.key_width, 1);
assert_eq!(parsed.length, 2);
assert_eq!(parsed.common_serialised_prefix, b"a");
assert_eq!(parsed.items.len(), 2);
assert_eq!(parsed.items[0].0, vec![b"a".to_vec()]);
assert_eq!(parsed.items[0].1, b"v1".to_vec());
assert_eq!(parsed.items[1].0, vec![b"alpha".to_vec()]);
assert_eq!(parsed.items[1].1, b"v2\nv2line2".to_vec());
}
#[test]
fn serialise_empty_leaf_node_has_blank_prefix() {
let out = serialise_leaf_node(100, 1, &[], None).unwrap();
let blob: Vec<u8> = out.iter().flatten().copied().collect();
assert_eq!(blob, b"chkleaf:\n100\n1\n0\n\n");
}
#[test]
fn leaf_node_key_value_len_matches_python_layout() {
let key = vec![b"foo".to_vec()];
let value = b"hello\nworld";
let got = leaf_node_key_value_len(&key, value);
assert_eq!(got, 3 + 1 + 1 + 1 + 11 + 1);
}
#[test]
fn leaf_node_key_value_len_multi_segment_key_joins_with_nul() {
let key = vec![b"a".to_vec(), b"bc".to_vec()];
let value = b"x";
let got = leaf_node_key_value_len(&key, value);
assert_eq!(got, 9);
}
#[test]
fn leaf_node_current_size_includes_header_and_drops_prefix() {
let got = leaf_node_current_size(100, 1, 2, 20, Some(b"ab"));
assert_eq!(got, 36);
}
#[test]
fn leaf_node_current_size_empty_prefix() {
let got = leaf_node_current_size(15, 1, 0, 0, None);
assert_eq!(got, 17);
}
#[test]
fn internal_node_current_size_adds_header_digits() {
let got = internal_node_current_size(100, 1, 3, 50);
assert_eq!(got, 55);
}
fn key_vec(parts: &[&[u8]]) -> Vec<Vec<u8>> {
parts.iter().map(|p| p.to_vec()).collect()
}
fn build_map_key(
store: std::sync::Arc<FakeChkStore>,
cache: std::sync::Arc<dyn PageCache>,
entries: &[(&[&[u8]], &[u8])],
maximum_size: usize,
) -> Vec<u8> {
let mut initial: indexmap::IndexMap<Vec<Vec<u8>>, Vec<u8>> = indexmap::IndexMap::new();
for (key, value) in entries {
initial.insert(key_vec(key), value.to_vec());
}
CHKMap::from_dict(store, cache, initial, maximum_size, 1, SearchKeyFunc::Plain).unwrap()
}
fn collect_interesting_items(
store: &FakeChkStore,
cache: &dyn PageCache,
interesting: &[Vec<u8>],
old: &[Vec<u8>],
) -> Vec<(Vec<Vec<u8>>, Vec<u8>)> {
let records =
iter_interesting_nodes(store, cache, interesting, old, SearchKeyFunc::Plain).unwrap();
let mut items: Vec<(Vec<Vec<u8>>, Vec<u8>)> =
records.into_iter().flat_map(|r| r.items).collect();
items.sort();
items
}
#[test]
fn leaf_node_new_starts_empty() {
let n = LeafNode::new(SearchKeyFunc::Plain);
assert_eq!(n.len(), 0);
assert!(n.is_empty());
assert_eq!(n.maximum_size, 0);
assert_eq!(n.key_width, 1);
assert_eq!(n.raw_size, 0);
assert!(matches!(n.search_prefix, SearchPrefix::Computed(None)));
assert!(n.common_serialised_prefix.is_none());
assert!(n.key.is_none());
}
#[test]
fn leaf_node_map_no_split_first_insert_sets_prefixes() {
let mut n = LeafNode::new(SearchKeyFunc::Plain);
let split = n.map_no_split(key_vec(&[b"foo"]), b"bar".to_vec());
assert!(!split);
assert_eq!(n.len(), 1);
assert_eq!(n.common_serialised_prefix.as_deref(), Some(&b"foo"[..]));
assert_eq!(
n.search_prefix,
SearchPrefix::Computed(Some(b"foo".to_vec()))
);
assert_eq!(
n.raw_size,
leaf_node_key_value_len(&[b"foo".to_vec()], b"bar")
);
}
#[test]
fn leaf_node_map_no_split_shrinks_prefix_on_divergent_key() {
let mut n = LeafNode::new(SearchKeyFunc::Plain);
n.map_no_split(key_vec(&[b"alpha"]), b"v1".to_vec());
n.map_no_split(key_vec(&[b"alphabet"]), b"v2".to_vec());
assert_eq!(n.common_serialised_prefix.as_deref(), Some(&b"alpha"[..]));
assert_eq!(
n.search_prefix,
SearchPrefix::Computed(Some(b"alpha".to_vec()))
);
}
#[test]
fn leaf_node_map_no_split_signals_split_when_oversize() {
let mut n = LeafNode::new(SearchKeyFunc::Plain);
n.maximum_size = 10;
let _ = n.map_no_split(key_vec(&[b"foo"]), b"v1".to_vec());
let split = n.map_no_split(key_vec(&[b"bar"]), b"v2".to_vec());
assert!(split);
}
#[test]
fn leaf_node_unmap_removes_entry_and_recomputes_prefixes() {
let mut n = LeafNode::new(SearchKeyFunc::Plain);
n.map_no_split(key_vec(&[b"foo"]), b"v1".to_vec());
n.map_no_split(key_vec(&[b"foobar"]), b"v2".to_vec());
assert_eq!(n.common_serialised_prefix.as_deref(), Some(&b"foo"[..]));
let removed = n.unmap(&key_vec(&[b"foobar"]));
assert_eq!(removed, Some(b"v2".to_vec()));
assert_eq!(n.common_serialised_prefix.as_deref(), Some(&b"foo"[..]));
assert_eq!(n.len(), 1);
assert!(n.key.is_none());
}
#[test]
fn leaf_node_unmap_returns_none_for_missing_key() {
let mut n = LeafNode::new(SearchKeyFunc::Plain);
n.map_no_split(key_vec(&[b"foo"]), b"v1".to_vec());
assert_eq!(n.unmap(&key_vec(&[b"missing"])), None);
assert_eq!(n.len(), 1);
}
#[test]
fn leaf_node_compute_search_prefix_resolves_unknown_state() {
let blob: &[u8] = b"chkleaf:\n100\n1\n2\nalph\n2\x002\nv2\nv2line2\na\x001\nv1\n";
let parsed = deserialise_leaf_node(blob).unwrap();
let mut n = LeafNode::from_parsed(parsed, SearchKeyFunc::Plain);
assert!(n.search_prefix.is_unknown());
let prefix = n.compute_search_prefix().map(|s| s.to_vec());
assert_eq!(prefix, Some(b"alph".to_vec()));
assert!(!n.search_prefix.is_unknown());
}
#[test]
fn leaf_node_from_parsed_marks_search_prefix_unknown() {
let blob: &[u8] = b"chkleaf:\n100\n1\n2\nalph\n2\x002\nv2\nv2line2\na\x001\nv1\n";
let parsed = deserialise_leaf_node(blob).unwrap();
let n = LeafNode::from_parsed(parsed, SearchKeyFunc::Plain);
assert_eq!(n.len(), 2);
assert!(n.search_prefix.is_unknown());
assert_eq!(n.common_serialised_prefix.as_deref(), Some(&b"alph"[..]));
assert!(n.current_size() > 0);
}
#[test]
fn leaf_node_from_parsed_empty_keeps_prefix_none() {
let blob: &[u8] = b"chkleaf:\n100\n1\n0\n\n";
let parsed = deserialise_leaf_node(blob).unwrap();
let n = LeafNode::from_parsed(parsed, SearchKeyFunc::Plain);
assert_eq!(n.len(), 0);
assert!(matches!(n.search_prefix, SearchPrefix::Computed(None)));
assert!(n.common_serialised_prefix.is_none());
}
#[test]
fn internal_node_new_starts_empty_with_prefix() {
let n = InternalNode::new(b"pre".to_vec(), SearchKeyFunc::Plain);
assert_eq!(n.len, 0);
assert_eq!(n.node_width, 0);
assert_eq!(n.maximum_size, 0);
assert_eq!(n.search_prefix.as_deref(), Some(&b"pre"[..]));
assert!(n.items.is_empty());
assert!(n.key.is_none());
}
#[test]
fn internal_node_from_parsed_marks_children_unloaded() {
let blob: &[u8] = b"chknode:\n200\n1\n2\npre\nbar\x00sha1:bbbb\nfoo\x00sha1:aaaa\n";
let parsed = deserialise_internal_node(blob).unwrap();
let n = InternalNode::from_parsed(parsed, SearchKeyFunc::Plain);
assert_eq!(n.len, 2);
assert_eq!(n.search_prefix.as_deref(), Some(&b"pre"[..]));
assert_eq!(n.maximum_size, 200);
assert_eq!(n.key_width, 1);
assert_eq!(n.items.len(), 2);
match n.items.get(&b"prebar".to_vec()) {
Some(NodeRef::Unloaded(k)) => assert_eq!(k, b"sha1:bbbb"),
other => panic!("expected Unloaded for prebar, got {:?}", other),
}
match n.items.get(&b"prefoo".to_vec()) {
Some(NodeRef::Unloaded(k)) => assert_eq!(k, b"sha1:aaaa"),
other => panic!("expected Unloaded for prefoo, got {:?}", other),
}
}
use super::testing::FakeChkStore;
#[test]
fn iter_interesting_nodes_no_old_yields_all_new() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut m = CHKMap::new(store.clone(), cache.clone(), None, SearchKeyFunc::Plain);
m.map(key_vec(&[b"a"]), b"v1".to_vec()).unwrap();
m.map(key_vec(&[b"b"]), b"v2".to_vec()).unwrap();
let root = m.save().unwrap();
let records =
iter_interesting_nodes(&*store, &*cache, &[root.clone()], &[], SearchKeyFunc::Plain)
.unwrap();
assert!(records.iter().any(|r| r.page_key.as_deref() == Some(&root)));
let all_items: Vec<(Vec<Vec<u8>>, Vec<u8>)> =
records.iter().flat_map(|r| r.items.clone()).collect();
if all_items.is_empty() {
assert!(!records.is_empty());
} else {
let mut sorted_items: Vec<(Vec<Vec<u8>>, Vec<u8>)> = all_items;
sorted_items.sort();
assert_eq!(sorted_items.len(), 2);
}
}
#[test]
fn iter_interesting_nodes_identical_old_and_new_yields_root_only() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut m = CHKMap::new(store.clone(), cache.clone(), None, SearchKeyFunc::Plain);
m.map(key_vec(&[b"a"]), b"v1".to_vec()).unwrap();
m.map(key_vec(&[b"b"]), b"v2".to_vec()).unwrap();
let root = m.save().unwrap();
let records = iter_interesting_nodes(
&*store,
&*cache,
&[root.clone()],
&[root.clone()],
SearchKeyFunc::Plain,
)
.unwrap();
let interesting_items: Vec<_> = records.iter().flat_map(|r| r.items.clone()).collect();
assert!(interesting_items.is_empty());
}
#[test]
fn iter_interesting_nodes_empty_to_one_key() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let target = build_map_key(store.clone(), cache.clone(), &[(&[b"a"], b"content")], 10);
let items = collect_interesting_items(&*store, &*cache, &[target], &[]);
assert_eq!(items, vec![(key_vec(&[b"a"]), b"content".to_vec())]);
}
#[test]
fn iter_interesting_nodes_none_to_one_key() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let basis = build_map_key(store.clone(), cache.clone(), &[], 10);
let target = build_map_key(store.clone(), cache.clone(), &[(&[b"a"], b"content")], 10);
let items = collect_interesting_items(&*store, &*cache, &[target], &[basis]);
assert_eq!(items, vec![(key_vec(&[b"a"]), b"content".to_vec())]);
}
#[test]
fn iter_interesting_nodes_one_to_none_key() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let basis = build_map_key(store.clone(), cache.clone(), &[(&[b"a"], b"content")], 10);
let target = build_map_key(store.clone(), cache.clone(), &[], 10);
let items = collect_interesting_items(&*store, &*cache, &[target], &[basis]);
assert!(items.is_empty());
}
#[test]
fn iter_interesting_nodes_common_pages() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let basis = build_map_key(
store.clone(),
cache.clone(),
&[
(&[b"a"], b"content"),
(&[b"b"], b"content"),
(&[b"c"], b"content"),
],
10,
);
let target = build_map_key(
store.clone(),
cache.clone(),
&[
(&[b"a"], b"content"),
(&[b"b"], b"other content"),
(&[b"c"], b"content"),
],
10,
);
let items = collect_interesting_items(&*store, &*cache, &[target], &[basis]);
assert_eq!(items, vec![(key_vec(&[b"b"]), b"other content".to_vec())]);
}
#[test]
fn iter_interesting_nodes_common_sub_page() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let basis = build_map_key(
store.clone(),
cache.clone(),
&[(&[b"aaa"], b"common"), (&[b"c"], b"common")],
10,
);
let target = build_map_key(
store.clone(),
cache.clone(),
&[
(&[b"aaa"], b"common"),
(&[b"aab"], b"new"),
(&[b"c"], b"common"),
],
10,
);
let items = collect_interesting_items(&*store, &*cache, &[target], &[basis]);
assert_eq!(items, vec![(key_vec(&[b"aab"]), b"new".to_vec())]);
}
#[test]
fn iter_interesting_nodes_common_leaf_multiple_targets() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let basis = build_map_key(store.clone(), cache.clone(), &[], 10);
let target1 = build_map_key(store.clone(), cache.clone(), &[(&[b"aaa"], b"common")], 10);
let target2 = build_map_key(
store.clone(),
cache.clone(),
&[(&[b"aaa"], b"common"), (&[b"bbb"], b"new")],
10,
);
let target3 = build_map_key(
store.clone(),
cache.clone(),
&[
(&[b"aaa"], b"common"),
(&[b"aac"], b"other"),
(&[b"bbb"], b"new"),
],
10,
);
let items =
collect_interesting_items(&*store, &*cache, &[target1, target2, target3], &[basis]);
assert_eq!(
items,
vec![
(key_vec(&[b"aaa"]), b"common".to_vec()),
(key_vec(&[b"aac"]), b"other".to_vec()),
(key_vec(&[b"bbb"]), b"new".to_vec()),
]
);
}
fn reopen_map(
store: std::sync::Arc<FakeChkStore>,
cache: std::sync::Arc<dyn PageCache>,
entries: &[(&[&[u8]], &[u8])],
maximum_size: usize,
) -> CHKMap<FakeChkStore> {
let root = build_map_key(store.clone(), cache.clone(), entries, maximum_size);
CHKMap::new(store, cache, Some(root), SearchKeyFunc::Plain)
}
#[test]
fn chkmap_iter_changes_empty_to_ab() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut basis = reopen_map(store.clone(), cache.clone(), &[], 10);
let mut target = reopen_map(
store.clone(),
cache.clone(),
&[(&[b"a"], b"content here"), (&[b"b"], b"more content")],
10,
);
let mut changes = target.iter_changes(&mut basis).unwrap();
changes.sort();
assert_eq!(
changes,
vec![
(key_vec(&[b"a"]), None, Some(b"content here".to_vec())),
(key_vec(&[b"b"]), None, Some(b"more content".to_vec())),
]
);
}
#[test]
fn chkmap_iter_changes_ab_to_empty() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut basis = reopen_map(
store.clone(),
cache.clone(),
&[(&[b"a"], b"content here"), (&[b"b"], b"more content")],
10,
);
let mut target = reopen_map(store.clone(), cache.clone(), &[], 10);
let mut changes = target.iter_changes(&mut basis).unwrap();
changes.sort();
assert_eq!(
changes,
vec![
(key_vec(&[b"a"]), Some(b"content here".to_vec()), None),
(key_vec(&[b"b"]), Some(b"more content".to_vec()), None),
]
);
}
#[test]
fn chkmap_iter_changes_mixed_node_length() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut basis = reopen_map(
store.clone(),
cache.clone(),
&[
(&[b"aaa"], b"foo bar"),
(&[b"aab"], b"common altered a"),
(&[b"b"], b"foo bar b"),
],
10,
);
let mut target = reopen_map(
store.clone(),
cache.clone(),
&[
(&[b"aaa"], b"foo bar"),
(&[b"aab"], b"common altered b"),
(&[b"at"], b"foo bar t"),
],
10,
);
let mut changes = target.iter_changes(&mut basis).unwrap();
changes.sort();
assert_eq!(
changes,
vec![
(
key_vec(&[b"aab"]),
Some(b"common altered a".to_vec()),
Some(b"common altered b".to_vec()),
),
(key_vec(&[b"at"]), None, Some(b"foo bar t".to_vec())),
(key_vec(&[b"b"]), Some(b"foo bar b".to_vec()), None),
]
);
}
#[test]
fn chkmap_iter_changes_ab_ab_is_empty() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut basis = reopen_map(
store.clone(),
cache.clone(),
&[(&[b"a"], b"content here"), (&[b"b"], b"more content")],
10,
);
let mut target = reopen_map(
store.clone(),
cache.clone(),
&[(&[b"a"], b"content here"), (&[b"b"], b"more content")],
10,
);
let changes = target.iter_changes(&mut basis).unwrap();
assert!(changes.is_empty());
}
#[test]
fn chkmap_iter_changes_identical_returns_empty() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut a = CHKMap::new(store.clone(), cache.clone(), None, SearchKeyFunc::Plain);
a.map(key_vec(&[b"k"]), b"v".to_vec()).unwrap();
let root = a.save().unwrap();
let mut a2 = CHKMap::new(
store.clone(),
cache.clone(),
Some(root),
SearchKeyFunc::Plain,
);
let changes = a.iter_changes(&mut a2).unwrap();
assert!(changes.is_empty());
}
#[test]
fn chkmap_iter_changes_detects_add_remove_modify() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut basis = CHKMap::new(store.clone(), cache.clone(), None, SearchKeyFunc::Plain);
basis.map(key_vec(&[b"a"]), b"1".to_vec()).unwrap();
basis.map(key_vec(&[b"b"]), b"2".to_vec()).unwrap();
let mut me = CHKMap::new(store.clone(), cache.clone(), None, SearchKeyFunc::Plain);
me.map(key_vec(&[b"a"]), b"1".to_vec()).unwrap();
me.map(key_vec(&[b"c"]), b"3".to_vec()).unwrap();
let mut changes = me.iter_changes(&mut basis).unwrap();
changes.sort();
assert_eq!(
changes,
vec![
(key_vec(&[b"b"]), Some(b"2".to_vec()), None),
(key_vec(&[b"c"]), None, Some(b"3".to_vec())),
]
);
}
#[test]
fn chkmap_iter_changes_detects_modification() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut basis = CHKMap::new(store.clone(), cache.clone(), None, SearchKeyFunc::Plain);
basis.map(key_vec(&[b"a"]), b"old".to_vec()).unwrap();
let mut me = CHKMap::new(store.clone(), cache.clone(), None, SearchKeyFunc::Plain);
me.map(key_vec(&[b"a"]), b"new".to_vec()).unwrap();
let changes = me.iter_changes(&mut basis).unwrap();
assert_eq!(
changes,
vec![(
key_vec(&[b"a"]),
Some(b"old".to_vec()),
Some(b"new".to_vec())
)]
);
}
#[test]
fn node_refs_leaf_returns_empty() {
let leaf = LeafNode::new(SearchKeyFunc::Plain);
let node = Node::Leaf(Box::new(leaf));
assert!(node.refs().unwrap().is_empty());
}
#[test]
fn chkmap_from_dict_returns_loadable_root_key() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut initial: indexmap::IndexMap<Vec<Vec<u8>>, Vec<u8>> = indexmap::IndexMap::new();
initial.insert(key_vec(&[b"a"]), b"v1".to_vec());
initial.insert(key_vec(&[b"b"]), b"v2".to_vec());
let root_key = CHKMap::from_dict(
store.clone(),
cache.clone(),
initial,
0,
1,
SearchKeyFunc::Plain,
)
.unwrap();
assert!(root_key.starts_with(b"sha1:"));
let mut m = CHKMap::new(store, cache, Some(root_key), SearchKeyFunc::Plain);
let items = m.iteritems(None).unwrap();
assert_eq!(items.len(), 2);
}
#[test]
fn chkmap_apply_delta_inserts_and_deletes() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut m = CHKMap::new(store.clone(), cache.clone(), None, SearchKeyFunc::Plain);
m.map(key_vec(&[b"a"]), b"v1".to_vec()).unwrap();
m.map(key_vec(&[b"b"]), b"v2".to_vec()).unwrap();
let delta = vec![
(Some(key_vec(&[b"b"])), None, Vec::new()),
(None, Some(key_vec(&[b"c"])), b"v3".to_vec()),
];
let new_root = m.apply_delta(delta).unwrap();
assert!(new_root.starts_with(b"sha1:"));
let mut items = m.iteritems(None).unwrap();
items.sort();
assert_eq!(
items,
vec![
(key_vec(&[b"a"]), b"v1".to_vec()),
(key_vec(&[b"c"]), b"v3".to_vec()),
]
);
}
#[test]
fn chkmap_apply_delta_rejects_insert_collisions() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut m = CHKMap::new(store.clone(), cache.clone(), None, SearchKeyFunc::Plain);
m.map(key_vec(&[b"a"]), b"v1".to_vec()).unwrap();
let delta = vec![(None, Some(key_vec(&[b"a"])), b"v2".to_vec())];
let err = m.apply_delta(delta).unwrap_err();
assert!(matches!(err, Error::InconsistentDeltaDelta(_, _)));
}
#[test]
fn chkmap_empty_starts_with_leaf_root() {
let store = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut m = CHKMap::new(store, cache, None, SearchKeyFunc::Plain);
assert!(m.is_empty().unwrap());
assert!(m.key().is_none());
}
#[test]
fn chkmap_map_and_iteritems_roundtrip() {
let store = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut m = CHKMap::new(store, cache, None, SearchKeyFunc::Plain);
m.map(key_vec(&[b"foo"]), b"v1".to_vec()).unwrap();
m.map(key_vec(&[b"bar"]), b"v2".to_vec()).unwrap();
let mut items = m.iteritems(None).unwrap();
items.sort();
assert_eq!(
items,
vec![
(key_vec(&[b"bar"]), b"v2".to_vec()),
(key_vec(&[b"foo"]), b"v1".to_vec()),
]
);
assert_eq!(m.len().unwrap(), 2);
}
#[test]
fn chkmap_unmap_removes_entry() {
let store = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut m = CHKMap::new(store, cache, None, SearchKeyFunc::Plain);
m.map(key_vec(&[b"foo"]), b"v1".to_vec()).unwrap();
m.map(key_vec(&[b"bar"]), b"v2".to_vec()).unwrap();
m.unmap(&key_vec(&[b"foo"]), true).unwrap();
let items = m.iteritems(None).unwrap();
assert_eq!(items, vec![(key_vec(&[b"bar"]), b"v2".to_vec())]);
}
#[test]
fn chkmap_save_round_trips_through_demand_load() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut m = CHKMap::new(store.clone(), cache.clone(), None, SearchKeyFunc::Plain);
m.map(key_vec(&[b"foo"]), b"v1".to_vec()).unwrap();
m.map(key_vec(&[b"bar"]), b"v2".to_vec()).unwrap();
let root_key = m.save().unwrap();
assert!(root_key.starts_with(b"sha1:"));
let mut m2 = CHKMap::new(
store.clone(),
cache.clone(),
Some(root_key),
SearchKeyFunc::Plain,
);
let mut items = m2.iteritems(None).unwrap();
items.sort();
assert_eq!(
items,
vec![
(key_vec(&[b"bar"]), b"v2".to_vec()),
(key_vec(&[b"foo"]), b"v1".to_vec()),
]
);
}
#[test]
fn chkmap_map_split_promotes_root_to_internal() {
let store = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut m = CHKMap::new(store, cache, None, SearchKeyFunc::Plain);
if let NodeRef::Loaded(Node::Leaf(l)) = &mut m.root {
l.maximum_size = 10;
}
m.map(key_vec(&[b"foo"]), b"v1".to_vec()).unwrap();
m.map(key_vec(&[b"bar"]), b"v2".to_vec()).unwrap();
match &m.root {
NodeRef::Loaded(Node::Internal(_)) => {}
other => panic!("expected Internal root after split, got {:?}", other),
}
let mut items = m.iteritems(None).unwrap();
items.sort();
assert_eq!(items.len(), 2);
}
#[test]
fn chkmap_apply_delta_is_deterministic_regardless_of_order() {
fn build(order: &[(&[u8], &[u8])]) -> Vec<u8> {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> =
std::sync::Arc::new(InMemoryPageCache::new());
let mut m = CHKMap::new(store, cache, None, SearchKeyFunc::Plain);
if let NodeRef::Loaded(Node::Leaf(l)) = &mut m.root {
l.maximum_size = 10;
}
let delta: Vec<_> = order
.iter()
.map(|(k, v)| (None, Some(key_vec(&[k])), v.to_vec()))
.collect();
m.apply_delta(delta).unwrap();
m.save().unwrap()
}
let root1 = build(&[
(b"aaa", b"common"),
(b"bba", b"target2"),
(b"bbb", b"common"),
]);
let root2 = build(&[
(b"bbb", b"common"),
(b"bba", b"target2"),
(b"aaa", b"common"),
]);
assert_eq!(root1, root2);
}
#[test]
fn chkmap_multi_level_split_round_trips() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut m = CHKMap::new(store.clone(), cache.clone(), None, SearchKeyFunc::Plain);
if let NodeRef::Loaded(Node::Leaf(l)) = &mut m.root {
l.maximum_size = 10;
}
let keys: &[&[u8]] = &[b"aaa", b"aab", b"aac", b"aba", b"abb", b"baa", b"bbb"];
for k in keys {
m.map(key_vec(&[k]), b"v".to_vec()).unwrap();
}
assert!(matches!(&m.root, NodeRef::Loaded(Node::Internal(_))));
let mut items = m.iteritems(None).unwrap();
items.sort();
let mut expected: Vec<_> = keys
.iter()
.map(|k| (key_vec(&[k]), b"v".to_vec()))
.collect();
expected.sort();
assert_eq!(items, expected);
let root_key = m.save().unwrap();
let mut reloaded = CHKMap::new(
store.clone(),
cache.clone(),
Some(root_key),
SearchKeyFunc::Plain,
);
let mut items2 = reloaded.iteritems(None).unwrap();
items2.sort();
assert_eq!(items2, expected);
}
#[test]
fn chkmap_collapses_internal_to_leaf_on_size_shrink() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut m = CHKMap::new(store, cache, None, SearchKeyFunc::Plain);
if let NodeRef::Loaded(Node::Leaf(l)) = &mut m.root {
l.maximum_size = 35;
}
m.map(key_vec(&[b"aaa"]), b"v".to_vec()).unwrap();
m.map(key_vec(&[b"aab"]), b"very long value that splits".to_vec())
.unwrap();
assert!(
matches!(&m.root, NodeRef::Loaded(Node::Internal(_))),
"expected split to internal node"
);
m.map(key_vec(&[b"aab"]), b"v".to_vec()).unwrap();
assert!(
matches!(&m.root, NodeRef::Loaded(Node::Leaf(_))),
"expected collapse back to leaf"
);
let mut items = m.iteritems(None).unwrap();
items.sort();
assert_eq!(
items,
vec![
(key_vec(&[b"aaa"]), b"v".to_vec()),
(key_vec(&[b"aab"]), b"v".to_vec()),
]
);
}
fn root_child_prefixes(root: &NodeRef) -> Vec<Vec<u8>> {
match root {
NodeRef::Loaded(Node::Internal(internal)) => {
let mut prefixes: Vec<Vec<u8>> = internal.items.keys().cloned().collect();
prefixes.sort();
prefixes
}
other => panic!("expected internal root, got {:?}", other),
}
}
#[test]
fn chkmap_search_key_16_tree_layout() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut m = CHKMap::new(store.clone(), cache.clone(), None, SearchKeyFunc::Hash16Way);
if let NodeRef::Loaded(Node::Leaf(l)) = &mut m.root {
l.maximum_size = 10;
}
m.map(key_vec(&[b"1"]), b"foo".to_vec()).unwrap();
m.map(key_vec(&[b"2"]), b"bar".to_vec()).unwrap();
m.map(key_vec(&[b"3"]), b"baz".to_vec()).unwrap();
assert_eq!(
root_child_prefixes(&m.root),
vec![b"1".to_vec(), b"6".to_vec(), b"8".to_vec()]
);
let mut items = m.iteritems(None).unwrap();
items.sort();
assert_eq!(
items,
vec![
(key_vec(&[b"1"]), b"foo".to_vec()),
(key_vec(&[b"2"]), b"bar".to_vec()),
(key_vec(&[b"3"]), b"baz".to_vec()),
]
);
let root_key = m.save().unwrap();
let mut reloaded = CHKMap::new(
store.clone(),
cache.clone(),
Some(root_key),
SearchKeyFunc::Hash16Way,
);
assert_eq!(
reloaded.iteritems(Some(&[key_vec(&[b"1"])])).unwrap(),
vec![(key_vec(&[b"1"]), b"foo".to_vec())]
);
}
#[test]
fn chkmap_search_key_255_tree_layout() {
let store: std::sync::Arc<FakeChkStore> = std::sync::Arc::new(FakeChkStore::new());
let cache: std::sync::Arc<dyn PageCache> = std::sync::Arc::new(InMemoryPageCache::new());
let mut m = CHKMap::new(
store.clone(),
cache.clone(),
None,
SearchKeyFunc::Hash255Way,
);
if let NodeRef::Loaded(Node::Leaf(l)) = &mut m.root {
l.maximum_size = 10;
}
m.map(key_vec(&[b"1"]), b"foo".to_vec()).unwrap();
m.map(key_vec(&[b"2"]), b"bar".to_vec()).unwrap();
m.map(key_vec(&[b"3"]), b"baz".to_vec()).unwrap();
assert_eq!(
root_child_prefixes(&m.root),
vec![vec![0x1a], b"m".to_vec(), vec![0x83]]
);
let mut items = m.iteritems(None).unwrap();
items.sort();
assert_eq!(
items,
vec![
(key_vec(&[b"1"]), b"foo".to_vec()),
(key_vec(&[b"2"]), b"bar".to_vec()),
(key_vec(&[b"3"]), b"baz".to_vec()),
]
);
}
#[test]
fn internal_node_serialise_writes_children_then_self() {
let store = FakeChkStore::new();
let cache = InMemoryPageCache::new();
let mut leaf_a = LeafNode::new(SearchKeyFunc::Plain);
leaf_a.map_no_split(key_vec(&[b"a"]), b"va".to_vec());
let mut leaf_b = LeafNode::new(SearchKeyFunc::Plain);
leaf_b.map_no_split(key_vec(&[b"b"]), b"vb".to_vec());
let mut internal = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
internal
.add_node(b"a".to_vec(), Node::Leaf(Box::new(leaf_a)))
.unwrap();
internal
.add_node(b"b".to_vec(), Node::Leaf(Box::new(leaf_b)))
.unwrap();
let written = internal.serialise(&store, &cache).unwrap();
assert_eq!(written.len(), 3);
assert_eq!(
internal.key.as_deref(),
Some(written.last().unwrap().as_slice())
);
for k in &written {
assert!(cache.get(k).is_some(), "missing {:?}", k);
}
}
#[test]
fn internal_node_serialise_skips_already_serialised_children() {
let store = FakeChkStore::new();
let cache = InMemoryPageCache::new();
let mut leaf_a = LeafNode::new(SearchKeyFunc::Plain);
leaf_a.map_no_split(key_vec(&[b"a"]), b"va".to_vec());
let _child_key = leaf_a.serialise(&store, &cache).unwrap();
let mut internal = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
internal
.add_node(b"a".to_vec(), Node::Leaf(Box::new(leaf_a)))
.unwrap();
let written = internal.serialise(&store, &cache).unwrap();
assert_eq!(written.len(), 1);
assert!(written[0].starts_with(b"sha1:"));
}
#[test]
fn node_unmap_with_store_removes_from_leaf() {
let store = FakeChkStore::new();
let cache = InMemoryPageCache::new();
let mut leaf = LeafNode::new(SearchKeyFunc::Plain);
leaf.map_no_split(key_vec(&[b"a"]), b"va".to_vec());
leaf.map_no_split(key_vec(&[b"b"]), b"vb".to_vec());
let node = Node::Leaf(Box::new(leaf));
let new_node = node
.unmap_with_store(&store, &cache, &key_vec(&[b"a"]), true)
.unwrap();
assert_eq!(new_node.len(), 1);
}
#[test]
fn node_unmap_with_store_collapses_internal_to_only_child() {
let store = FakeChkStore::new();
let cache = InMemoryPageCache::new();
let mut leaf_a = LeafNode::new(SearchKeyFunc::Plain);
leaf_a.map_no_split(key_vec(&[b"a"]), b"va".to_vec());
let mut leaf_b = LeafNode::new(SearchKeyFunc::Plain);
leaf_b.map_no_split(key_vec(&[b"b"]), b"vb".to_vec());
let mut internal = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
internal
.add_node(b"a".to_vec(), Node::Leaf(Box::new(leaf_a)))
.unwrap();
internal
.add_node(b"b".to_vec(), Node::Leaf(Box::new(leaf_b)))
.unwrap();
let node = Node::Internal(Box::new(internal));
let new_node = node
.unmap_with_store(&store, &cache, &key_vec(&[b"a"]), true)
.unwrap();
match &new_node {
Node::Leaf(l) => {
assert_eq!(l.len(), 1);
assert!(l.items.contains_key(&key_vec(&[b"b"])));
}
Node::Internal(_) => panic!("expected collapse to leaf"),
}
}
#[test]
fn node_unmap_with_store_raises_for_missing_key() {
let store = FakeChkStore::new();
let cache = InMemoryPageCache::new();
let mut leaf = LeafNode::new(SearchKeyFunc::Plain);
leaf.map_no_split(key_vec(&[b"a"]), b"va".to_vec());
let node = Node::Leaf(Box::new(leaf));
let err = node
.unmap_with_store(&store, &cache, &key_vec(&[b"absent"]), true)
.unwrap_err();
assert!(matches!(err, Error::AssertionFailed(_)));
}
#[test]
fn node_map_with_store_descends_into_internal_child() {
let store = FakeChkStore::new();
let cache = InMemoryPageCache::new();
let mut leaf_a = LeafNode::new(SearchKeyFunc::Plain);
leaf_a.map_no_split(key_vec(&[b"alpha"]), b"va".to_vec());
let mut leaf_b = LeafNode::new(SearchKeyFunc::Plain);
leaf_b.map_no_split(key_vec(&[b"beta"]), b"vb".to_vec());
let mut internal = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
internal
.add_node(b"a".to_vec(), Node::Leaf(Box::new(leaf_a)))
.unwrap();
internal
.add_node(b"b".to_vec(), Node::Leaf(Box::new(leaf_b)))
.unwrap();
let mut node = Node::Internal(Box::new(internal));
let r = node
.map_with_store(&store, &cache, key_vec(&[b"apple"]), b"vap".to_vec())
.unwrap();
assert!(matches!(r, MapResult::InPlace { .. }));
let mut all_items = node.iteritems(&store, &cache, None).unwrap();
all_items.sort();
assert_eq!(
all_items,
vec![
(key_vec(&[b"alpha"]), b"va".to_vec()),
(key_vec(&[b"apple"]), b"vap".to_vec()),
(key_vec(&[b"beta"]), b"vb".to_vec()),
]
);
assert_eq!(node.len(), 3);
}
#[test]
fn node_map_with_store_creates_new_child_if_no_match() {
let store = FakeChkStore::new();
let cache = InMemoryPageCache::new();
let mut leaf_a = LeafNode::new(SearchKeyFunc::Plain);
leaf_a.map_no_split(key_vec(&[b"a"]), b"va".to_vec());
let mut internal = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
internal
.add_node(b"a".to_vec(), Node::Leaf(Box::new(leaf_a)))
.unwrap();
let mut node = Node::Internal(Box::new(internal));
let r = node
.map_with_store(&store, &cache, key_vec(&[b"b"]), b"vb".to_vec())
.unwrap();
assert!(matches!(r, MapResult::InPlace { .. }));
let mut items = node.iteritems(&store, &cache, None).unwrap();
items.sort();
assert_eq!(
items,
vec![
(key_vec(&[b"a"]), b"va".to_vec()),
(key_vec(&[b"b"]), b"vb".to_vec()),
]
);
}
#[test]
fn leaf_node_map_in_place_no_split() {
let mut n = LeafNode::new(SearchKeyFunc::Plain);
let r = n.map(key_vec(&[b"foo"]), b"bar".to_vec()).unwrap();
match r {
MapResult::InPlace { search_prefix } => {
assert_eq!(search_prefix, b"foo".to_vec());
}
MapResult::Split { .. } => panic!("did not expect split"),
}
assert_eq!(n.len(), 1);
}
#[test]
fn leaf_node_map_replacing_existing_key_does_not_grow_len() {
let mut n = LeafNode::new(SearchKeyFunc::Plain);
n.map(key_vec(&[b"foo"]), b"bar".to_vec()).unwrap();
let r = n.map(key_vec(&[b"foo"]), b"BAZ".to_vec()).unwrap();
assert!(matches!(r, MapResult::InPlace { .. }));
assert_eq!(n.len(), 1);
assert_eq!(n.items.get(&key_vec(&[b"foo"])), Some(&b"BAZ".to_vec()));
}
#[test]
fn leaf_node_map_overflow_splits_into_internal_subtree() {
let mut n = LeafNode::new(SearchKeyFunc::Plain);
n.maximum_size = 10;
let r1 = n.map(key_vec(&[b"foo"]), b"v1".to_vec()).unwrap();
assert!(matches!(r1, MapResult::InPlace { .. }));
let r2 = n.map(key_vec(&[b"bar"]), b"v2".to_vec()).unwrap();
match r2 {
MapResult::Split {
common_serialised_prefix,
children,
} => {
assert!(common_serialised_prefix.is_empty());
assert_eq!(children.len(), 2);
let prefixes: Vec<&[u8]> = children.iter().map(|(p, _)| p.as_slice()).collect();
assert!(prefixes.contains(&&b"f"[..]));
assert!(prefixes.contains(&&b"b"[..]));
}
MapResult::InPlace { .. } => panic!("expected split"),
}
}
#[test]
fn node_iteritems_recurses_into_internal_children() {
let store = FakeChkStore::new();
let cache = InMemoryPageCache::new();
let mut la = LeafNode::new(SearchKeyFunc::Plain);
la.map_no_split(key_vec(&[b"a"]), b"va".to_vec());
let mut lb = LeafNode::new(SearchKeyFunc::Plain);
lb.map_no_split(key_vec(&[b"b"]), b"vb".to_vec());
let key_a = la.serialise(&store, &cache).unwrap();
let key_b = lb.serialise(&store, &cache).unwrap();
let mut internal = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
internal.node_width = 1;
internal
.items
.insert(b"a".to_vec(), NodeRef::Unloaded(key_a));
internal
.items
.insert(b"b".to_vec(), NodeRef::Unloaded(key_b));
internal.len = 2;
let mut node = Node::Internal(Box::new(internal));
let mut items = node.iteritems(&store, &cache, None).unwrap();
items.sort();
assert_eq!(
items,
vec![
(key_vec(&[b"a"]), b"va".to_vec()),
(key_vec(&[b"b"]), b"vb".to_vec()),
]
);
}
#[test]
fn node_iteritems_with_filter_only_yields_matching_keys() {
let store = FakeChkStore::new();
let cache = InMemoryPageCache::new();
let mut la = LeafNode::new(SearchKeyFunc::Plain);
la.map_no_split(key_vec(&[b"a"]), b"va".to_vec());
let mut lb = LeafNode::new(SearchKeyFunc::Plain);
lb.map_no_split(key_vec(&[b"b"]), b"vb".to_vec());
let key_a = la.serialise(&store, &cache).unwrap();
let key_b = lb.serialise(&store, &cache).unwrap();
let mut internal = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
internal.node_width = 1;
internal
.items
.insert(b"a".to_vec(), NodeRef::Unloaded(key_a));
internal
.items
.insert(b"b".to_vec(), NodeRef::Unloaded(key_b));
internal.len = 2;
let mut node = Node::Internal(Box::new(internal));
let items = node
.iteritems(&store, &cache, Some(&[key_vec(&[b"a"])]))
.unwrap();
assert_eq!(items, vec![(key_vec(&[b"a"]), b"va".to_vec())]);
}
#[test]
fn internal_node_iter_nodes_returns_loaded_children_in_order() {
let mut n = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
n.node_width = 1;
let mut leaf_a = LeafNode::new(SearchKeyFunc::Plain);
leaf_a.map_no_split(key_vec(&[b"a"]), b"va".to_vec());
let mut leaf_b = LeafNode::new(SearchKeyFunc::Plain);
leaf_b.map_no_split(key_vec(&[b"b"]), b"vb".to_vec());
n.add_node(b"a".to_vec(), Node::Leaf(Box::new(leaf_a)))
.unwrap();
n.add_node(b"b".to_vec(), Node::Leaf(Box::new(leaf_b)))
.unwrap();
let store = FakeChkStore::new();
let cache = InMemoryPageCache::new();
let yielded = n.iter_nodes(&store, &cache, None, None).unwrap();
assert_eq!(yielded.len(), 2);
assert!(yielded.iter().all(|(_, f)| f.is_none()));
let lens: Vec<usize> = yielded.iter().map(|(n, _)| n.len()).collect();
assert_eq!(lens, vec![1, 1]);
}
#[test]
fn internal_node_iter_nodes_demand_loads_via_store() {
let store = FakeChkStore::new();
let cache = InMemoryPageCache::new();
let mut leaf = LeafNode::new(SearchKeyFunc::Plain);
leaf.map_no_split(key_vec(&[b"a"]), b"va".to_vec());
let child_key = leaf.serialise(&store, &cache).unwrap();
let mut n = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
n.node_width = 1;
n.items
.insert(b"a".to_vec(), NodeRef::Unloaded(child_key.clone()));
n.len = 1;
cache.clear();
let yielded = n.iter_nodes(&store, &cache, None, None).unwrap();
assert_eq!(yielded.len(), 1);
assert_eq!(yielded[0].0.len(), 1);
match n.items.get(&b"a".to_vec()) {
Some(NodeRef::Loaded(_)) => {}
other => panic!("expected Loaded after iter_nodes, got {:?}", other),
}
assert!(cache.get(&child_key).is_some());
}
#[test]
fn internal_node_iter_nodes_single_key_filter_dict_lookup() {
let mut n = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
n.node_width = 1;
let mut leaf_a = LeafNode::new(SearchKeyFunc::Plain);
leaf_a.map_no_split(key_vec(&[b"a"]), b"va".to_vec());
let mut leaf_b = LeafNode::new(SearchKeyFunc::Plain);
leaf_b.map_no_split(key_vec(&[b"b"]), b"vb".to_vec());
n.add_node(b"a".to_vec(), Node::Leaf(Box::new(leaf_a)))
.unwrap();
n.add_node(b"b".to_vec(), Node::Leaf(Box::new(leaf_b)))
.unwrap();
let store = FakeChkStore::new();
let cache = InMemoryPageCache::new();
let filter = [key_vec(&[b"a"])];
let yielded = n.iter_nodes(&store, &cache, Some(&filter), None).unwrap();
assert_eq!(yielded.len(), 1);
assert_eq!(yielded[0].1.as_deref(), Some(&[key_vec(&[b"a"])][..]));
}
#[test]
fn internal_node_iter_nodes_single_key_miss_yields_nothing() {
let mut n = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
n.node_width = 1;
let mut leaf_a = LeafNode::new(SearchKeyFunc::Plain);
leaf_a.map_no_split(key_vec(&[b"a"]), b"va".to_vec());
n.add_node(b"a".to_vec(), Node::Leaf(Box::new(leaf_a)))
.unwrap();
let store = FakeChkStore::new();
let cache = InMemoryPageCache::new();
let filter = [key_vec(&[b"z"])];
let yielded = n.iter_nodes(&store, &cache, Some(&filter), None).unwrap();
assert!(yielded.is_empty());
}
#[test]
fn internal_node_iter_nodes_hits_cache_before_store() {
let store = FakeChkStore::new();
let cache = InMemoryPageCache::new();
let mut leaf = LeafNode::new(SearchKeyFunc::Plain);
leaf.map_no_split(key_vec(&[b"a"]), b"va".to_vec());
let child_key = leaf.serialise(&store, &cache).unwrap();
let mut n = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
n.node_width = 1;
n.items
.insert(b"a".to_vec(), NodeRef::Unloaded(child_key.clone()));
n.len = 1;
store
.pages
.lock()
.unwrap()
.insert(child_key[5..].to_vec(), b"corrupt".to_vec());
let yielded = n.iter_nodes(&store, &cache, None, None).unwrap();
assert_eq!(yielded.len(), 1);
assert_eq!(yielded[0].0.len(), 1);
}
#[test]
fn leaf_node_serialise_caches_and_sets_key() {
let mut n = LeafNode::new(SearchKeyFunc::Plain);
n.map_no_split(key_vec(&[b"foo"]), b"bar".to_vec());
let store = FakeChkStore::new();
let cache = InMemoryPageCache::new();
let sha1_key = n.serialise(&store, &cache).unwrap();
assert!(sha1_key.starts_with(b"sha1:"));
assert_eq!(n.key.as_deref(), Some(sha1_key.as_slice()));
let cached = cache.get(&sha1_key).expect("page cache miss");
assert!(cached.starts_with(b"chkleaf:\n"));
let node = deserialise_node(&cached, sha1_key, SearchKeyFunc::Plain).unwrap();
match node {
Node::Leaf(leaf) => {
assert_eq!(leaf.len(), 1);
assert_eq!(leaf.items.get(&key_vec(&[b"foo"])), Some(&b"bar".to_vec()));
}
Node::Internal(_) => panic!("expected leaf"),
}
}
#[test]
fn in_memory_page_cache_roundtrips() {
let c = InMemoryPageCache::new();
assert_eq!(c.get(b"sha1:absent"), None);
c.insert(b"sha1:x".to_vec(), b"page-bytes".to_vec());
assert_eq!(c.get(b"sha1:x").as_deref(), Some(&b"page-bytes"[..]));
}
#[test]
fn in_memory_page_cache_clear_drops_all_entries() {
let c = InMemoryPageCache::new();
c.insert(b"sha1:x".to_vec(), b"v1".to_vec());
c.insert(b"sha1:y".to_vec(), b"v2".to_vec());
c.clear();
assert_eq!(c.get(b"sha1:x"), None);
assert_eq!(c.get(b"sha1:y"), None);
}
#[test]
fn in_memory_page_cache_evicts_when_full() {
let c = InMemoryPageCache::with_capacity(2);
c.insert(b"k1".to_vec(), b"v1".to_vec());
c.insert(b"k2".to_vec(), b"v2".to_vec());
c.insert(b"k3".to_vec(), b"v3".to_vec()); assert_eq!(c.get(b"k1"), None);
assert_eq!(c.get(b"k2").as_deref(), Some(&b"v2"[..]));
assert_eq!(c.get(b"k3").as_deref(), Some(&b"v3"[..]));
}
#[test]
fn deserialise_node_dispatches_to_leaf() {
let blob: &[u8] = b"chkleaf:\n100\n1\n2\nalph\n2\x002\nv2\nv2line2\na\x001\nv1\n";
let node = deserialise_node(blob, b"sha1:abcd".to_vec(), SearchKeyFunc::Plain).unwrap();
match node {
Node::Leaf(leaf) => {
assert_eq!(leaf.key.as_deref(), Some(&b"sha1:abcd"[..]));
assert_eq!(leaf.len(), 2);
}
Node::Internal(_) => panic!("expected leaf"),
}
}
#[test]
fn deserialise_node_dispatches_to_internal() {
let blob: &[u8] = b"chknode:\n200\n1\n2\npre\nbar\x00sha1:bbbb\nfoo\x00sha1:aaaa\n";
let node = deserialise_node(blob, b"sha1:root".to_vec(), SearchKeyFunc::Plain).unwrap();
match node {
Node::Internal(internal) => {
assert_eq!(internal.key.as_deref(), Some(&b"sha1:root"[..]));
assert_eq!(internal.len, 2);
}
Node::Leaf(_) => panic!("expected internal"),
}
}
#[test]
fn deserialise_node_rejects_unknown_magic() {
let blob: &[u8] = b"unknown:\n";
let err = deserialise_node(blob, b"sha1:x".to_vec(), SearchKeyFunc::Plain).unwrap_err();
assert!(matches!(err, Error::AssertionFailed(_)));
}
#[test]
fn internal_node_add_node_validates_prefix_length() {
let mut n = InternalNode::new(b"pre".to_vec(), SearchKeyFunc::Plain);
let leaf = Node::Leaf(Box::new(LeafNode::new(SearchKeyFunc::Plain)));
let too_long = n.add_node(b"preXY".to_vec(), leaf.clone());
assert!(matches!(too_long, Err(Error::AssertionFailed(_))));
let mismatch = n.add_node(b"qrZ".to_vec(), leaf.clone());
assert!(matches!(mismatch, Err(Error::AssertionFailed(_))));
n.add_node(b"preX".to_vec(), leaf).unwrap();
assert_eq!(n.items.len(), 1);
assert_eq!(n.node_width, 4);
}
#[test]
fn internal_node_add_node_clears_key_and_grows_len() {
let mut n = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
n.key = Some(b"sha1:old".to_vec());
let mut leaf = LeafNode::new(SearchKeyFunc::Plain);
leaf.map_no_split(key_vec(&[b"k1"]), b"v1".to_vec());
leaf.map_no_split(key_vec(&[b"k2"]), b"v2".to_vec());
n.add_node(b"a".to_vec(), Node::Leaf(Box::new(leaf)))
.unwrap();
assert_eq!(n.len, 2);
assert!(n.key.is_none());
}
#[test]
fn internal_node_search_key_pads_with_nul() {
let mut n = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
n.node_width = 5;
assert_eq!(n.search_key(&Key::from(vec![b"k".to_vec()])), b"k\0\0\0\0");
}
#[test]
fn internal_node_search_key_truncates_when_longer() {
let mut n = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
n.node_width = 3;
assert_eq!(n.search_key(&Key::from(vec![b"longkey".to_vec()])), b"lon");
}
#[test]
fn internal_node_search_prefix_filter_does_not_pad() {
let mut n = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
n.node_width = 5;
assert_eq!(
n.search_prefix_filter(&Key::from(vec![b"k".to_vec()])),
b"k"
);
n.node_width = 3;
assert_eq!(
n.search_prefix_filter(&Key::from(vec![b"longkey".to_vec()])),
b"lon"
);
}
#[test]
fn internal_node_compute_search_prefix_from_children() {
let mut n = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
n.items
.insert(b"abc".to_vec(), NodeRef::Unloaded(b"sha1:x".to_vec()));
n.items
.insert(b"abd".to_vec(), NodeRef::Unloaded(b"sha1:y".to_vec()));
n.items
.insert(b"abe".to_vec(), NodeRef::Unloaded(b"sha1:z".to_vec()));
let prefix = n.compute_search_prefix().map(<[u8]>::to_vec);
assert_eq!(prefix, Some(b"ab".to_vec()));
assert_eq!(n.search_prefix.as_deref(), Some(&b"ab"[..]));
}
#[test]
fn internal_node_refs_returns_child_keys() {
let mut n = InternalNode::new(b"p".to_vec(), SearchKeyFunc::Plain);
n.node_width = 2;
n.items
.insert(b"pa".to_vec(), NodeRef::Unloaded(b"sha1:a".to_vec()));
let mut leaf = LeafNode::new(SearchKeyFunc::Plain);
leaf.key = Some(b"sha1:b".to_vec());
n.items
.insert(b"pb".to_vec(), NodeRef::Loaded(Node::Leaf(Box::new(leaf))));
n.key = Some(b"sha1:self".to_vec());
let refs = n.refs().unwrap();
assert_eq!(refs.len(), 2);
assert!(refs.contains(&b"sha1:a".to_vec()));
assert!(refs.contains(&b"sha1:b".to_vec()));
}
#[test]
fn internal_node_refs_errors_when_unserialised() {
let n = InternalNode::new(b"".to_vec(), SearchKeyFunc::Plain);
assert!(matches!(n.refs(), Err(Error::AssertionFailed(_))));
}
#[test]
fn node_wraps_leaf_and_internal() {
let leaf = Node::Leaf(Box::new(LeafNode::new(SearchKeyFunc::Plain)));
let internal = Node::Internal(Box::new(InternalNode::new(
b"".to_vec(),
SearchKeyFunc::Plain,
)));
assert_eq!(leaf.len(), 0);
assert_eq!(internal.len(), 0);
assert_eq!(leaf.maximum_size(), 0);
assert_eq!(internal.maximum_size(), 0);
assert_eq!(leaf.key(), None);
assert_eq!(internal.key(), None);
}
#[test]
fn node_len_reflects_inner_state() {
let mut leaf = LeafNode::new(SearchKeyFunc::Plain);
leaf.map_no_split(key_vec(&[b"foo"]), b"bar".to_vec());
leaf.map_no_split(key_vec(&[b"baz"]), b"qux".to_vec());
let node = Node::Leaf(Box::new(leaf));
assert_eq!(node.len(), 2);
}
#[test]
fn leaf_node_iteritems_returns_all_with_no_filter() {
let mut n = LeafNode::new(SearchKeyFunc::Plain);
n.map_no_split(key_vec(&[b"foo"]), b"bar".to_vec());
n.map_no_split(key_vec(&[b"baz"]), b"qux".to_vec());
let items = n.iteritems(None);
assert_eq!(items.len(), 2);
let keys: Vec<&[Vec<u8>]> = items.iter().map(|(k, _)| k.as_slice()).collect();
assert!(keys.contains(&key_vec(&[b"foo"]).as_slice()));
assert!(keys.contains(&key_vec(&[b"baz"]).as_slice()));
}
#[test]
fn leaf_node_iteritems_exact_match_filter() {
let mut n = LeafNode::new(SearchKeyFunc::Plain);
n.map_no_split(key_vec(&[b"foo"]), b"bar".to_vec());
n.map_no_split(key_vec(&[b"baz"]), b"qux".to_vec());
let items = n.iteritems(Some(&[key_vec(&[b"foo"])]));
assert_eq!(items, vec![(key_vec(&[b"foo"]), b"bar".to_vec())]);
}
#[test]
fn leaf_node_iteritems_exact_miss_yields_nothing() {
let mut n = LeafNode::new(SearchKeyFunc::Plain);
n.map_no_split(key_vec(&[b"foo"]), b"bar".to_vec());
let items = n.iteritems(Some(&[key_vec(&[b"absent"])]));
assert!(items.is_empty());
}
#[test]
fn leaf_node_iteritems_short_prefix_filter_matches_items() {
let mut n = LeafNode::new(SearchKeyFunc::Plain);
n.key_width = 2;
n.map_no_split(key_vec(&[b"foo", b"sub1"]), b"v1".to_vec());
n.map_no_split(key_vec(&[b"bar", b"sub2"]), b"v2".to_vec());
let items = n.iteritems(Some(&[key_vec(&[b"foo"])]));
assert_eq!(items, vec![(key_vec(&[b"foo", b"sub1"]), b"v1".to_vec())]);
}
#[test]
fn leaf_node_iteritems_mixed_filter_lengths() {
let mut n = LeafNode::new(SearchKeyFunc::Plain);
n.key_width = 2;
n.map_no_split(key_vec(&[b"foo", b"sub"]), b"v1".to_vec());
n.map_no_split(key_vec(&[b"bar", b"sub"]), b"v2".to_vec());
n.map_no_split(key_vec(&[b"baz", b"sub"]), b"v3".to_vec());
let items = n.iteritems(Some(&[
key_vec(&[b"foo", b"sub"]), key_vec(&[b"bar"]), ]));
assert_eq!(items.len(), 2);
assert_eq!(items[0], (key_vec(&[b"foo", b"sub"]), b"v1".to_vec()));
assert_eq!(items[1], (key_vec(&[b"bar", b"sub"]), b"v2".to_vec()));
}
#[test]
fn search_key_func_from_name_resolves_known_variants() {
assert_eq!(
SearchKeyFunc::from_name(b"plain").unwrap(),
SearchKeyFunc::Plain
);
assert_eq!(
SearchKeyFunc::from_name(b"hash-16-way").unwrap(),
SearchKeyFunc::Hash16Way
);
assert_eq!(
SearchKeyFunc::from_name(b"hash-255-way").unwrap(),
SearchKeyFunc::Hash255Way
);
}
#[test]
fn search_key_func_from_name_returns_unknown_bytes() {
let err = SearchKeyFunc::from_name(b"unrecognised").unwrap_err();
assert_eq!(err, b"unrecognised".to_vec());
}
#[test]
fn search_key_func_name_roundtrips() {
for variant in [
SearchKeyFunc::Plain,
SearchKeyFunc::Hash16Way,
SearchKeyFunc::Hash255Way,
] {
let parsed = SearchKeyFunc::from_name(variant.name()).unwrap();
assert_eq!(parsed, variant);
}
}
#[test]
fn search_key_func_apply_matches_free_functions() {
let k = key(&[b"foo", b"bar"]);
assert_eq!(SearchKeyFunc::Plain.apply(&k), search_key_plain(&k));
assert_eq!(SearchKeyFunc::Hash16Way.apply(&k), search_key_16(&k));
assert_eq!(SearchKeyFunc::Hash255Way.apply(&k), search_key_255(&k));
}
#[test]
fn search_key_func_default_is_plain() {
assert_eq!(SearchKeyFunc::default(), SearchKeyFunc::Plain);
}
#[test]
fn are_search_keys_identical_returns_true_for_empty() {
let empty: [&[u8]; 0] = [];
assert!(are_search_keys_identical(empty));
}
#[test]
fn are_search_keys_identical_returns_true_for_single() {
assert!(are_search_keys_identical([b"hash".as_slice()]));
}
#[test]
fn are_search_keys_identical_returns_true_when_all_match() {
assert!(are_search_keys_identical([
b"same".as_slice(),
b"same",
b"same"
]));
}
#[test]
fn are_search_keys_identical_returns_false_when_one_differs() {
assert!(!are_search_keys_identical([
b"same".as_slice(),
b"same",
b"other"
]));
assert!(!are_search_keys_identical([b"first".as_slice(), b"second"]));
}
#[test]
fn serialise_internal_node_roundtrips_through_deserialise() {
let items = vec![
InternalNodeChild {
prefix: b"prebar".to_vec(),
flat_key: b"sha1:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_vec(),
},
InternalNodeChild {
prefix: b"prefoo".to_vec(),
flat_key: b"sha1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_vec(),
},
];
let out = serialise_internal_node(200, 1, 2, b"pre", &items).unwrap();
let blob: Vec<u8> = out.iter().flatten().copied().collect();
assert_eq!(blob, INTERNAL_FIXTURE);
}
}