use crate::chunk::DiskKey;
use crate::error::{Error, Result};
use crate::tree_write::{space_needed, LeafItem};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OwnedItem {
pub key: DiskKey,
pub data: Vec<u8>,
}
impl OwnedItem {
pub fn as_leaf_item(&self) -> LeafItem<'_> {
LeafItem {
key: self.key,
data: &self.data,
}
}
}
fn order(a: &DiskKey, b: &DiskKey) -> std::cmp::Ordering {
(a.objectid, a.key_type, a.offset).cmp(&(b.objectid, b.key_type, b.offset))
}
pub fn insert(nodesize: u32, items: &[OwnedItem], item: OwnedItem) -> Result<Vec<OwnedItem>> {
let at = match items.binary_search_by(|existing| order(&existing.key, &item.key)) {
Ok(_) => {
return Err(Error::UnsupportedFeature(format!(
"the leaf already holds an item under {:?}; replacing it is a different \
operation from inserting",
item.key
)))
}
Err(at) => at,
};
let mut out = Vec::with_capacity(items.len() + 1);
out.extend_from_slice(&items[..at]);
out.push(item);
out.extend_from_slice(&items[at..]);
let borrowed: Vec<LeafItem> = out.iter().map(|i| i.as_leaf_item()).collect();
let needed = space_needed(&borrowed);
let capacity = nodesize as usize;
if needed > capacity {
return Err(Error::UnsupportedFeature(format!(
"the item does not fit: {} items need {needed} bytes and a leaf holds \
{capacity}. `insert` produces one leaf by definition — use \
`insert_or_split`, which returns two when the contents no longer fit \
in one, and add the second to the parent.",
out.len()
)));
}
Ok(out)
}
pub fn delete(items: &[OwnedItem], key: &DiskKey) -> Result<Vec<OwnedItem>> {
let at = items
.binary_search_by(|existing| order(&existing.key, key))
.map_err(|_| {
Error::UnsupportedFeature(format!(
"the leaf holds no item under {key:?}, so there is nothing to remove and \
the caller's picture of the tree is wrong"
))
})?;
let mut out = Vec::with_capacity(items.len() - 1);
out.extend_from_slice(&items[..at]);
out.extend_from_slice(&items[at + 1..]);
Ok(out)
}
pub fn split(items: &[OwnedItem]) -> Result<(Vec<OwnedItem>, Vec<OwnedItem>)> {
if items.len() < 2 {
return Err(Error::UnsupportedFeature(format!(
"a leaf of {} item(s) cannot be split into two that both hold something",
items.len()
)));
}
let mid = items.len().div_ceil(2);
Ok((items[..mid].to_vec(), items[mid..].to_vec()))
}
pub fn insert_or_split(
nodesize: u32,
items: &[OwnedItem],
item: OwnedItem,
) -> Result<Vec<Vec<OwnedItem>>> {
if fits(nodesize, items, &item) {
return Ok(vec![insert(nodesize, items, item)?]);
}
let at = match items.binary_search_by(|existing| order(&existing.key, &item.key)) {
Ok(_) => {
return Err(Error::UnsupportedFeature(format!(
"the leaf already holds an item under {:?}; replacing it is a different \
operation from inserting",
item.key
)))
}
Err(at) => at,
};
let mut all = Vec::with_capacity(items.len() + 1);
all.extend_from_slice(&items[..at]);
all.push(item);
all.extend_from_slice(&items[at..]);
let (left, right) = split(&all)?;
Ok(vec![left, right])
}
pub fn fits(nodesize: u32, items: &[OwnedItem], item: &OwnedItem) -> bool {
let mut borrowed: Vec<LeafItem> = items.iter().map(|i| i.as_leaf_item()).collect();
borrowed.push(item.as_leaf_item());
space_needed(&borrowed) <= nodesize as usize
}
#[cfg(test)]
mod tests {
use super::*;
fn key(objectid: u64, key_type: u8, offset: u64) -> DiskKey {
DiskKey {
objectid,
key_type,
offset,
}
}
fn item(objectid: u64, len: usize) -> OwnedItem {
OwnedItem {
key: key(objectid, 1, 0),
data: vec![objectid as u8; len],
}
}
#[test]
fn insertion_keeps_the_order_a_search_depends_on() {
let items = vec![item(1, 8), item(5, 8), item(9, 8)];
let out = insert(4096, &items, item(7, 8)).expect("inserting");
let ids: Vec<u64> = out.iter().map(|i| i.key.objectid).collect();
assert_eq!(ids, vec![1, 5, 7, 9]);
let out = insert(4096, &items, item(0, 8)).expect("inserting at the front");
assert_eq!(out[0].key.objectid, 0);
let out = insert(4096, &items, item(99, 8)).expect("inserting at the back");
assert_eq!(out.last().unwrap().key.objectid, 99);
}
#[test]
fn inserting_over_an_existing_key_is_refused() {
let items = vec![item(1, 8), item(5, 8)];
let err = insert(4096, &items, item(5, 16)).expect_err("5 is already there");
assert!(err.to_string().contains("already holds"), "{err}");
}
#[test]
fn an_item_that_does_not_fit_is_refused_and_says_why() {
let items = vec![item(1, 3000)];
let err = insert(4096, &items, item(2, 3000)).expect_err("two 3000-byte items");
assert!(
err.to_string().contains("does not fit"),
"the refusal should name what went wrong: {err}"
);
assert!(
err.to_string().contains("insert_or_split"),
"the refusal should name the function that handles it: {err}"
);
assert!(!fits(4096, &items, &item(2, 3000)));
assert!(fits(4096, &items, &item(2, 8)));
}
#[test]
fn deletion_removes_exactly_one_item() {
let items = vec![item(1, 8), item(5, 8), item(9, 8)];
let out = delete(&items, &key(5, 1, 0)).expect("deleting");
let ids: Vec<u64> = out.iter().map(|i| i.key.objectid).collect();
assert_eq!(ids, vec![1, 9]);
}
#[test]
fn deleting_what_is_not_there_is_refused() {
let items = vec![item(1, 8)];
let err = delete(&items, &key(2, 1, 0)).expect_err("2 is not there");
assert!(err.to_string().contains("no item under"), "{err}");
}
#[test]
fn keys_that_share_an_objectid_order_by_type_then_offset() {
let mut items = vec![
OwnedItem {
key: key(1, 1, 0),
data: vec![0; 8],
},
OwnedItem {
key: key(1, 1, 9),
data: vec![0; 8],
},
];
items = insert(
4096,
&items,
OwnedItem {
key: key(1, 1, 4),
data: vec![0; 8],
},
)
.expect("inserting between two offsets of one objectid");
let offsets: Vec<u64> = items.iter().map(|i| i.key.offset).collect();
assert_eq!(offsets, vec![0, 4, 9]);
}
}