use uuid::Uuid;
use crate::error::Result;
use crate::page::{PageHeader, PageType};
use super::node::{LeafEntry, LeafNode};
use super::BTree;
pub struct BTreeCursor {
entries: Vec<LeafEntry>,
pos: usize,
next_leaf_id: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CursorEntry {
pub key: Uuid,
pub page_id: u32,
pub slot_id: u16,
}
impl BTree {
pub fn cursor(&mut self) -> Result<BTreeCursor> {
let first_leaf = self.find_first_leaf()?;
Ok(BTreeCursor {
entries: first_leaf.entries,
pos: 0,
next_leaf_id: first_leaf.next_leaf,
})
}
pub fn cursor_from(&mut self, start_key: &Uuid) -> Result<BTreeCursor> {
let key_bytes = *start_key.as_bytes();
let leaf = self.find_leaf(&key_bytes)?;
let pos = leaf
.entries
.binary_search_by(|e| e.key.cmp(&key_bytes))
.unwrap_or_else(|i| i);
Ok(BTreeCursor {
entries: leaf.entries,
pos,
next_leaf_id: leaf.next_leaf,
})
}
pub fn range(
&mut self,
start: Option<&Uuid>,
end: Option<&Uuid>,
) -> Result<Vec<CursorEntry>> {
let mut cursor = if let Some(s) = start {
self.cursor_from(s)?
} else {
self.cursor()?
};
let end_bytes = end.map(|e| *e.as_bytes());
let mut results = Vec::new();
while let Some(entry) = cursor.next_entry(self)? {
if let Some(ref end_key) = end_bytes
&& entry.key.as_bytes() >= end_key
{
break;
}
results.push(CursorEntry {
key: entry.key,
page_id: entry.page_id,
slot_id: entry.slot_id,
});
}
Ok(results)
}
pub fn scan_all(&mut self) -> Result<Vec<CursorEntry>> {
self.range(None, None)
}
fn find_first_leaf(&mut self) -> Result<LeafNode> {
let mut current_page_id = self.meta.root_page_id;
loop {
let buf = self.pm.read_page(current_page_id)?;
let header = PageHeader::read_from(&buf);
match header.page_type {
PageType::BTreeLeaf => return Ok(LeafNode::from_bytes(&buf)),
PageType::BTreeInternal => {
let node = super::node::InternalNode::from_bytes(&buf);
current_page_id = if node.entries.is_empty() {
node.right_child
} else {
node.entries[0].child_page_id
};
}
_ => return Err(crate::error::GrumpyError::PageNotFound(current_page_id)),
}
}
}
}
#[derive(Debug)]
pub struct CursorItem {
pub key: Uuid,
pub page_id: u32,
pub slot_id: u16,
}
impl BTreeCursor {
pub fn next_entry(&mut self, btree: &mut BTree) -> Result<Option<CursorItem>> {
loop {
if self.pos < self.entries.len() {
let entry = &self.entries[self.pos];
self.pos += 1;
return Ok(Some(CursorItem {
key: Uuid::from_bytes(entry.key),
page_id: entry.page_id,
slot_id: entry.slot_id,
}));
}
if self.next_leaf_id == 0 {
return Ok(None); }
let buf = btree.pm.read_page(self.next_leaf_id)?;
let leaf = LeafNode::from_bytes(&buf);
self.entries = leaf.entries;
self.pos = 0;
self.next_leaf_id = leaf.next_leaf;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn setup() -> (TempDir, BTree) {
let dir = TempDir::new().unwrap();
let btree = BTree::create(dir.path().join("index.db")).unwrap();
(dir, btree)
}
fn make_uuid(val: u128) -> Uuid {
Uuid::from_u128(val)
}
#[test]
fn test_cursor_empty_tree() {
let (_dir, mut btree) = setup();
let mut cursor = btree.cursor().unwrap();
assert!(cursor.next_entry(&mut btree).unwrap().is_none());
}
#[test]
fn test_cursor_full_scan() {
let (_dir, mut btree) = setup();
for i in 0..100u128 {
btree.insert(make_uuid(i), i as u32, 0).unwrap();
}
let results = btree.scan_all().unwrap();
assert_eq!(results.len(), 100);
for i in 1..results.len() {
assert!(
results[i - 1].key < results[i].key,
"entries should be in sorted order"
);
}
}
#[test]
fn test_cursor_range_scan() {
let (_dir, mut btree) = setup();
for i in 0..200u128 {
btree.insert(make_uuid(i), i as u32, 0).unwrap();
}
let start = make_uuid(50);
let end = make_uuid(100);
let results = btree.range(Some(&start), Some(&end)).unwrap();
assert_eq!(results.len(), 50, "range [50, 100) should have 50 entries");
for entry in &results {
assert!(entry.key >= start);
assert!(entry.key < end);
}
}
#[test]
fn test_cursor_range_unbounded_start() {
let (_dir, mut btree) = setup();
for i in 0..50u128 {
btree.insert(make_uuid(i), i as u32, 0).unwrap();
}
let end = make_uuid(25);
let results = btree.range(None, Some(&end)).unwrap();
assert_eq!(results.len(), 25);
}
#[test]
fn test_cursor_range_unbounded_end() {
let (_dir, mut btree) = setup();
for i in 0..50u128 {
btree.insert(make_uuid(i), i as u32, 0).unwrap();
}
let start = make_uuid(25);
let results = btree.range(Some(&start), None).unwrap();
assert_eq!(results.len(), 25);
}
#[test]
fn test_cursor_across_leaf_splits() {
let (_dir, mut btree) = setup();
let count = 1000u128;
for i in 0..count {
btree.insert(make_uuid(i), i as u32, 0).unwrap();
}
let results = btree.scan_all().unwrap();
assert_eq!(results.len(), count as usize);
for i in 1..results.len() {
assert!(results[i - 1].key < results[i].key);
}
}
#[test]
fn test_cursor_from_specific_key() {
let (_dir, mut btree) = setup();
for i in 0..100u128 {
btree.insert(make_uuid(i * 10), i as u32, 0).unwrap();
}
let start = make_uuid(250);
let mut cursor = btree.cursor_from(&start).unwrap();
if let Some(first) = cursor.next_entry(&mut btree).unwrap() {
assert!(first.key >= start, "first entry should be >= start_key");
}
}
#[test]
fn test_cursor_empty_range() {
let (_dir, mut btree) = setup();
for i in 0..50u128 {
btree.insert(make_uuid(i), i as u32, 0).unwrap();
}
let start = make_uuid(100);
let end = make_uuid(200);
let results = btree.range(Some(&start), Some(&end)).unwrap();
assert!(results.is_empty());
}
}