use crate::page::{PAGE_HEADER_SIZE, PAGE_SIZE, PageHeader, PageType};
pub const KEY_SIZE: usize = 16;
pub const INTERNAL_ENTRY_SIZE: usize = KEY_SIZE + 4;
pub const LEAF_ENTRY_SIZE: usize = KEY_SIZE + 4 + 2;
const USABLE_SPACE: usize = PAGE_SIZE - PAGE_HEADER_SIZE;
pub const INTERNAL_MAX_KEYS: usize = (USABLE_SPACE - 6) / INTERNAL_ENTRY_SIZE;
pub const LEAF_MAX_ENTRIES: usize = (USABLE_SPACE - 10) / LEAF_ENTRY_SIZE;
pub const MIN_OCCUPANCY_PERCENT: usize = 40;
pub const INTERNAL_MIN_KEYS: usize = INTERNAL_MAX_KEYS * MIN_OCCUPANCY_PERCENT / 100;
pub const LEAF_MIN_ENTRIES: usize = LEAF_MAX_ENTRIES * MIN_OCCUPANCY_PERCENT / 100;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InternalEntry {
pub key: [u8; KEY_SIZE],
pub child_page_id: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LeafEntry {
pub key: [u8; KEY_SIZE],
pub page_id: u32,
pub slot_id: u16,
}
#[derive(Debug, Clone)]
pub struct InternalNode {
pub page_id: u32,
pub num_keys: u16,
pub right_child: u32,
pub entries: Vec<InternalEntry>,
}
impl InternalNode {
pub fn new(page_id: u32) -> Self {
Self {
page_id,
num_keys: 0,
right_child: 0,
entries: Vec::new(),
}
}
pub fn from_bytes(buf: &[u8; PAGE_SIZE]) -> Self {
let header = PageHeader::read_from(buf);
let num_keys = u16::from_le_bytes(buf[32..34].try_into().unwrap());
let right_child = u32::from_le_bytes(buf[34..38].try_into().unwrap());
let mut entries = Vec::with_capacity(num_keys as usize);
for i in 0..num_keys as usize {
let base = 38 + i * INTERNAL_ENTRY_SIZE;
let mut key = [0u8; KEY_SIZE];
key.copy_from_slice(&buf[base..base + KEY_SIZE]);
let child_page_id = u32::from_le_bytes(
buf[base + KEY_SIZE..base + KEY_SIZE + 4]
.try_into()
.unwrap(),
);
entries.push(InternalEntry { key, child_page_id });
}
Self {
page_id: header.page_id,
num_keys,
right_child,
entries,
}
}
pub fn to_bytes(&self) -> [u8; PAGE_SIZE] {
let mut buf = [0u8; PAGE_SIZE];
let header = PageHeader::new(self.page_id, PageType::BTreeInternal);
header.write_to(&mut buf);
buf[32..34].copy_from_slice(&self.num_keys.to_le_bytes());
buf[34..38].copy_from_slice(&self.right_child.to_le_bytes());
for (i, entry) in self.entries.iter().enumerate() {
let base = 38 + i * INTERNAL_ENTRY_SIZE;
buf[base..base + KEY_SIZE].copy_from_slice(&entry.key);
buf[base + KEY_SIZE..base + KEY_SIZE + 4]
.copy_from_slice(&entry.child_page_id.to_le_bytes());
}
buf
}
pub fn find_child(&self, key: &[u8; KEY_SIZE]) -> u32 {
for entry in &self.entries {
if key < &entry.key {
return entry.child_page_id;
}
}
self.right_child
}
pub fn insert_entry(&mut self, key: [u8; KEY_SIZE], right_child_page_id: u32) {
let pos = self
.entries
.binary_search_by(|e| e.key.cmp(&key))
.unwrap_or_else(|i| i);
if pos < self.entries.len() {
let old_child = self.entries[pos].child_page_id;
self.entries.insert(
pos,
InternalEntry {
key,
child_page_id: old_child,
},
);
self.entries[pos + 1].child_page_id = right_child_page_id;
} else {
self.entries.push(InternalEntry {
key,
child_page_id: self.right_child,
});
self.right_child = right_child_page_id;
}
self.num_keys += 1;
}
pub fn is_overfull(&self) -> bool {
self.num_keys as usize > INTERNAL_MAX_KEYS
}
pub fn is_underfull(&self) -> bool {
(self.num_keys as usize) < INTERNAL_MIN_KEYS
}
}
#[derive(Debug, Clone)]
pub struct LeafNode {
pub page_id: u32,
pub num_entries: u16,
pub next_leaf: u32,
pub prev_leaf: u32,
pub entries: Vec<LeafEntry>,
}
impl LeafNode {
pub fn new(page_id: u32) -> Self {
Self {
page_id,
num_entries: 0,
next_leaf: 0,
prev_leaf: 0,
entries: Vec::new(),
}
}
pub fn from_bytes(buf: &[u8; PAGE_SIZE]) -> Self {
let header = PageHeader::read_from(buf);
let num_entries = u16::from_le_bytes(buf[32..34].try_into().unwrap());
let next_leaf = u32::from_le_bytes(buf[34..38].try_into().unwrap());
let prev_leaf = u32::from_le_bytes(buf[38..42].try_into().unwrap());
let mut entries = Vec::with_capacity(num_entries as usize);
for i in 0..num_entries as usize {
let base = 42 + i * LEAF_ENTRY_SIZE;
let mut key = [0u8; KEY_SIZE];
key.copy_from_slice(&buf[base..base + KEY_SIZE]);
let page_id = u32::from_le_bytes(
buf[base + KEY_SIZE..base + KEY_SIZE + 4]
.try_into()
.unwrap(),
);
let slot_id = u16::from_le_bytes(
buf[base + KEY_SIZE + 4..base + KEY_SIZE + 6]
.try_into()
.unwrap(),
);
entries.push(LeafEntry {
key,
page_id,
slot_id,
});
}
Self {
page_id: header.page_id,
num_entries,
next_leaf,
prev_leaf,
entries,
}
}
pub fn to_bytes(&self) -> [u8; PAGE_SIZE] {
let mut buf = [0u8; PAGE_SIZE];
let header = PageHeader::new(self.page_id, PageType::BTreeLeaf);
header.write_to(&mut buf);
buf[32..34].copy_from_slice(&self.num_entries.to_le_bytes());
buf[34..38].copy_from_slice(&self.next_leaf.to_le_bytes());
buf[38..42].copy_from_slice(&self.prev_leaf.to_le_bytes());
for (i, entry) in self.entries.iter().enumerate() {
let base = 42 + i * LEAF_ENTRY_SIZE;
buf[base..base + KEY_SIZE].copy_from_slice(&entry.key);
buf[base + KEY_SIZE..base + KEY_SIZE + 4].copy_from_slice(&entry.page_id.to_le_bytes());
buf[base + KEY_SIZE + 4..base + KEY_SIZE + 6]
.copy_from_slice(&entry.slot_id.to_le_bytes());
}
buf
}
pub fn search(&self, key: &[u8; KEY_SIZE]) -> Option<usize> {
self.entries.binary_search_by(|e| e.key.cmp(key)).ok()
}
pub fn insert_entry(&mut self, entry: LeafEntry) {
let pos = self
.entries
.binary_search_by(|e| e.key.cmp(&entry.key))
.unwrap_or_else(|i| i);
self.entries.insert(pos, entry);
self.num_entries += 1;
}
pub fn remove_entry(&mut self, key: &[u8; KEY_SIZE]) -> Option<LeafEntry> {
if let Ok(idx) = self.entries.binary_search_by(|e| e.key.cmp(key)) {
self.num_entries -= 1;
Some(self.entries.remove(idx))
} else {
None
}
}
pub fn is_overfull(&self) -> bool {
self.num_entries as usize > LEAF_MAX_ENTRIES
}
pub fn is_underfull(&self) -> bool {
(self.num_entries as usize) < LEAF_MIN_ENTRIES
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_key(val: u8) -> [u8; KEY_SIZE] {
let mut k = [0u8; KEY_SIZE];
k[15] = val;
k
}
#[test]
fn test_internal_node_round_trip() {
let mut node = InternalNode::new(5);
node.right_child = 99;
node.entries.push(InternalEntry {
key: make_key(10),
child_page_id: 2,
});
node.entries.push(InternalEntry {
key: make_key(20),
child_page_id: 3,
});
node.num_keys = 2;
let buf = node.to_bytes();
let restored = InternalNode::from_bytes(&buf);
assert_eq!(restored.page_id, 5);
assert_eq!(restored.num_keys, 2);
assert_eq!(restored.right_child, 99);
assert_eq!(restored.entries.len(), 2);
assert_eq!(restored.entries[0].key, make_key(10));
assert_eq!(restored.entries[0].child_page_id, 2);
assert_eq!(restored.entries[1].key, make_key(20));
assert_eq!(restored.entries[1].child_page_id, 3);
}
#[test]
fn test_leaf_node_round_trip() {
let mut node = LeafNode::new(7);
node.next_leaf = 8;
node.prev_leaf = 6;
node.entries.push(LeafEntry {
key: make_key(5),
page_id: 100,
slot_id: 0,
});
node.entries.push(LeafEntry {
key: make_key(15),
page_id: 101,
slot_id: 3,
});
node.num_entries = 2;
let buf = node.to_bytes();
let restored = LeafNode::from_bytes(&buf);
assert_eq!(restored.page_id, 7);
assert_eq!(restored.num_entries, 2);
assert_eq!(restored.next_leaf, 8);
assert_eq!(restored.prev_leaf, 6);
assert_eq!(restored.entries[0].key, make_key(5));
assert_eq!(restored.entries[0].page_id, 100);
assert_eq!(restored.entries[1].slot_id, 3);
}
#[test]
fn test_internal_max_capacity() {
let mut node = InternalNode::new(1);
node.right_child = 999;
for i in 0..INTERNAL_MAX_KEYS {
let mut key = [0u8; KEY_SIZE];
key[14..16].copy_from_slice(&(i as u16).to_le_bytes());
node.entries.push(InternalEntry {
key,
child_page_id: i as u32 + 1,
});
}
node.num_keys = INTERNAL_MAX_KEYS as u16;
let buf = node.to_bytes();
let restored = InternalNode::from_bytes(&buf);
assert_eq!(restored.num_keys as usize, INTERNAL_MAX_KEYS);
assert_eq!(restored.entries.len(), INTERNAL_MAX_KEYS);
}
#[test]
fn test_leaf_max_capacity() {
let mut node = LeafNode::new(1);
for i in 0..LEAF_MAX_ENTRIES {
let mut key = [0u8; KEY_SIZE];
key[14..16].copy_from_slice(&(i as u16).to_le_bytes());
node.entries.push(LeafEntry {
key,
page_id: i as u32,
slot_id: 0,
});
}
node.num_entries = LEAF_MAX_ENTRIES as u16;
let buf = node.to_bytes();
let restored = LeafNode::from_bytes(&buf);
assert_eq!(restored.num_entries as usize, LEAF_MAX_ENTRIES);
assert_eq!(restored.entries.len(), LEAF_MAX_ENTRIES);
}
#[test]
fn test_internal_find_child() {
let mut node = InternalNode::new(1);
node.entries = vec![
InternalEntry {
key: make_key(10),
child_page_id: 100,
},
InternalEntry {
key: make_key(20),
child_page_id: 101,
},
InternalEntry {
key: make_key(30),
child_page_id: 102,
},
];
node.num_keys = 3;
node.right_child = 103;
assert_eq!(node.find_child(&make_key(5)), 100); assert_eq!(node.find_child(&make_key(10)), 101); assert_eq!(node.find_child(&make_key(15)), 101); assert_eq!(node.find_child(&make_key(20)), 102); assert_eq!(node.find_child(&make_key(25)), 102); assert_eq!(node.find_child(&make_key(30)), 103); assert_eq!(node.find_child(&make_key(99)), 103); }
#[test]
fn test_leaf_search() {
let mut node = LeafNode::new(1);
node.insert_entry(LeafEntry {
key: make_key(10),
page_id: 1,
slot_id: 0,
});
node.insert_entry(LeafEntry {
key: make_key(20),
page_id: 2,
slot_id: 1,
});
node.insert_entry(LeafEntry {
key: make_key(30),
page_id: 3,
slot_id: 2,
});
assert_eq!(node.search(&make_key(20)), Some(1));
assert_eq!(node.search(&make_key(5)), None);
assert_eq!(node.search(&make_key(15)), None);
}
#[test]
fn test_leaf_insert_sorted() {
let mut node = LeafNode::new(1);
node.insert_entry(LeafEntry {
key: make_key(30),
page_id: 1,
slot_id: 0,
});
node.insert_entry(LeafEntry {
key: make_key(10),
page_id: 2,
slot_id: 0,
});
node.insert_entry(LeafEntry {
key: make_key(20),
page_id: 3,
slot_id: 0,
});
assert_eq!(node.entries[0].key, make_key(10));
assert_eq!(node.entries[1].key, make_key(20));
assert_eq!(node.entries[2].key, make_key(30));
assert_eq!(node.num_entries, 3);
}
#[test]
fn test_leaf_remove_entry() {
let mut node = LeafNode::new(1);
node.insert_entry(LeafEntry {
key: make_key(10),
page_id: 1,
slot_id: 0,
});
node.insert_entry(LeafEntry {
key: make_key(20),
page_id: 2,
slot_id: 0,
});
node.insert_entry(LeafEntry {
key: make_key(30),
page_id: 3,
slot_id: 0,
});
let removed = node.remove_entry(&make_key(20));
assert!(removed.is_some());
assert_eq!(removed.unwrap().page_id, 2);
assert_eq!(node.num_entries, 2);
assert!(node.search(&make_key(20)).is_none());
assert!(node.remove_entry(&make_key(99)).is_none());
}
#[test]
fn test_constants() {
#[allow(clippy::assertions_on_constants)]
{
assert!(
INTERNAL_MAX_KEYS > 100,
"fan-out should be large: {INTERNAL_MAX_KEYS}"
);
assert!(
LEAF_MAX_ENTRIES > 100,
"leaf capacity should be large: {LEAF_MAX_ENTRIES}"
);
assert!(
INTERNAL_MIN_KEYS > 0,
"internal min keys: {INTERNAL_MIN_KEYS}"
);
assert!(LEAF_MIN_ENTRIES > 0, "leaf min entries: {LEAF_MIN_ENTRIES}");
}
}
}