use crate::bucket::{BucketCell, BucketIApi, BucketRwCell, BucketRwIApi};
use crate::common::page::{CoerciblePage, RefPage, BUCKET_LEAF_FLAG};
use crate::common::tree::{MappedBranchPage, MappedLeafPage, TreePage};
use crate::common::{BVec, PgId};
use crate::node::NodeRwCell;
use crate::tx::{TxCell, TxIApi, TxRwCell};
use crate::Error::IncompatibleValue;
use bumpalo::Bump;
use std::marker::PhantomData;
pub trait CursorApi<'tx> {
fn first(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)>;
fn last(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)>;
fn next(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)>;
fn prev(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)>;
fn seek<T: AsRef<[u8]>>(&mut self, seek: T) -> Option<(&'tx [u8], Option<&'tx [u8]>)>;
}
pub trait CursorRwApi<'tx>: CursorApi<'tx> {
fn delete(&mut self) -> crate::Result<()>;
}
pub(crate) enum CursorWrapper<'tx> {
R(InnerCursor<'tx, TxCell<'tx>, BucketCell<'tx>>),
RW(InnerCursor<'tx, TxRwCell<'tx>, BucketRwCell<'tx>>),
}
pub struct CursorImpl<'tx> {
c: CursorWrapper<'tx>,
}
impl<'tx> From<InnerCursor<'tx, TxCell<'tx>, BucketCell<'tx>>> for CursorImpl<'tx> {
fn from(value: InnerCursor<'tx, TxCell<'tx>, BucketCell<'tx>>) -> Self {
CursorImpl {
c: CursorWrapper::R(value),
}
}
}
impl<'tx> From<InnerCursor<'tx, TxRwCell<'tx>, BucketRwCell<'tx>>> for CursorImpl<'tx> {
fn from(value: InnerCursor<'tx, TxRwCell<'tx>, BucketRwCell<'tx>>) -> Self {
CursorImpl {
c: CursorWrapper::RW(value),
}
}
}
impl<'tx> CursorApi<'tx> for CursorImpl<'tx> {
fn first(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)> {
match &mut self.c {
CursorWrapper::R(r) => r.api_first(),
CursorWrapper::RW(rw) => rw.api_first(),
}
}
fn last(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)> {
match &mut self.c {
CursorWrapper::R(r) => r.api_last(),
CursorWrapper::RW(rw) => rw.api_last(),
}
}
fn next(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)> {
match &mut self.c {
CursorWrapper::R(r) => r.api_next(),
CursorWrapper::RW(rw) => rw.api_next(),
}
}
fn prev(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)> {
match &mut self.c {
CursorWrapper::R(r) => r.api_prev(),
CursorWrapper::RW(rw) => rw.api_prev(),
}
}
fn seek<T: AsRef<[u8]>>(&mut self, seek: T) -> Option<(&'tx [u8], Option<&'tx [u8]>)> {
match &mut self.c {
CursorWrapper::R(r) => r.api_seek(seek.as_ref()),
CursorWrapper::RW(rw) => rw.api_seek(seek.as_ref()),
}
}
}
pub struct CursorRwImpl<'tx> {
c: InnerCursor<'tx, TxRwCell<'tx>, BucketRwCell<'tx>>,
}
impl<'tx> CursorRwImpl<'tx> {
pub(crate) fn new(c: InnerCursor<'tx, TxRwCell<'tx>, BucketRwCell<'tx>>) -> Self {
CursorRwImpl { c }
}
}
impl<'tx> From<InnerCursor<'tx, TxRwCell<'tx>, BucketRwCell<'tx>>> for CursorRwImpl<'tx> {
fn from(value: InnerCursor<'tx, TxRwCell<'tx>, BucketRwCell<'tx>>) -> Self {
CursorRwImpl::new(value)
}
}
impl<'tx> CursorApi<'tx> for CursorRwImpl<'tx> {
fn first(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)> {
self.c.api_first()
}
fn last(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)> {
self.c.api_last()
}
fn next(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)> {
self.c.api_next()
}
fn prev(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)> {
self.c.api_prev()
}
fn seek<T: AsRef<[u8]>>(&mut self, seek: T) -> Option<(&'tx [u8], Option<&'tx [u8]>)> {
self.c.api_seek(seek.as_ref())
}
}
impl<'tx> CursorRwApi<'tx> for CursorRwImpl<'tx> {
fn delete(&mut self) -> crate::Result<()> {
self.c.api_delete()
}
}
pub(crate) trait CursorIApi<'tx>: Clone {
fn api_first(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)>;
fn i_first(&mut self) -> Option<(&'tx [u8], &'tx [u8], u32)>;
fn api_next(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)>;
fn i_next(&mut self) -> Option<(&'tx [u8], &'tx [u8], u32)>;
fn api_prev(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)>;
fn i_prev(&mut self) -> Option<(&'tx [u8], &'tx [u8], u32)>;
fn api_last(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)>;
fn i_last(&mut self);
fn key_value(&self) -> Option<(&'tx [u8], &'tx [u8], u32)>;
fn api_seek(&mut self, seek: &[u8]) -> Option<(&'tx [u8], Option<&'tx [u8]>)>;
fn i_seek(&mut self, seek: &[u8]) -> Option<(&'tx [u8], &'tx [u8], u32)>;
fn go_to_first_element_on_the_stack(&mut self);
fn search(&mut self, key: &[u8], pgid: PgId);
fn search_inodes(&mut self, key: &[u8]);
fn search_node(&mut self, key: &[u8], node: NodeRwCell<'tx>);
fn search_page(&mut self, key: &[u8], page: &RefPage);
}
pub(crate) trait CursorRwIApi<'tx>: CursorIApi<'tx> {
fn node(&mut self) -> NodeRwCell<'tx>;
fn api_delete(&mut self) -> crate::Result<()>;
}
#[derive(Copy, Clone)]
pub enum PageNode<'tx> {
Page(RefPage<'tx>),
Node(NodeRwCell<'tx>),
}
#[derive(Clone)]
pub struct ElemRef<'tx> {
pn: PageNode<'tx>,
index: i32,
}
impl<'tx> ElemRef<'tx> {
fn count(&self) -> u32 {
match &self.pn {
PageNode::Page(r) => r.count as u32,
PageNode::Node(n) => n.cell.borrow().inodes.len() as u32,
}
}
fn is_leaf(&self) -> bool {
match &self.pn {
PageNode::Page(r) => r.is_leaf(),
PageNode::Node(n) => n.cell.borrow().is_leaf,
}
}
}
#[derive(Clone)]
pub(crate) struct InnerCursor<'tx, T: TxIApi<'tx>, B: BucketIApi<'tx, T>> {
bucket: B,
stack: BVec<'tx, ElemRef<'tx>>,
phantom_t: PhantomData<T>,
}
impl<'tx, T: TxIApi<'tx>, B: BucketIApi<'tx, T>> InnerCursor<'tx, T, B> {
pub(crate) fn new(cell: B, bump: &'tx Bump) -> Self {
cell
.tx()
.split_r()
.stats
.as_ref()
.unwrap()
.inc_cursor_count(1);
InnerCursor {
bucket: cell,
stack: BVec::with_capacity_in(0, bump),
phantom_t: PhantomData,
}
}
}
impl<'tx, T: TxIApi<'tx>, B: BucketIApi<'tx, T>> CursorIApi<'tx> for InnerCursor<'tx, T, B> {
fn api_first(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)> {
let (k, v, flags) = self.i_first()?;
if (flags & BUCKET_LEAF_FLAG) != 0 {
return Some((k, None));
}
Some((k, Some(v)))
}
fn i_first(&mut self) -> Option<(&'tx [u8], &'tx [u8], u32)> {
self.stack.clear();
let root = self.bucket.root();
let pn = self.bucket.page_node(root);
self.stack.push(ElemRef { pn, index: 0 });
self.go_to_first_element_on_the_stack();
if self.stack.last().unwrap().count() == 0 {
self.i_next();
}
let (k, v, flags) = self.key_value()?;
if (flags & BUCKET_LEAF_FLAG) != 0 {
return Some((k, &[], flags));
}
Some((k, v, flags))
}
fn api_next(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)> {
let (k, v, flags) = self.i_next()?;
if flags & BUCKET_LEAF_FLAG != 0 {
Some((k, None))
} else {
Some((k, Some(v)))
}
}
fn i_next(&mut self) -> Option<(&'tx [u8], &'tx [u8], u32)> {
loop {
let mut stack_exhausted = true;
let mut new_stack_depth = 0;
for (depth, elem) in self.stack.iter_mut().enumerate().rev() {
new_stack_depth = depth + 1;
if elem.index < elem.count() as i32 - 1 {
elem.index += 1;
stack_exhausted = false;
break;
}
}
if stack_exhausted {
return None;
}
self.stack.truncate(new_stack_depth);
self.go_to_first_element_on_the_stack();
if let Some(elem) = self.stack.last() {
if elem.count() == 0 {
continue;
}
}
return self.key_value();
}
}
fn api_prev(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)> {
let (k, v, flags) = self.i_prev()?;
if flags & BUCKET_LEAF_FLAG != 0 {
Some((k, None))
} else {
Some((k, Some(v)))
}
}
fn i_prev(&mut self) -> Option<(&'tx [u8], &'tx [u8], u32)> {
let mut new_stack_depth = 0;
let mut stack_exhausted = true;
for (depth, elem) in self.stack.iter_mut().enumerate().rev() {
new_stack_depth = depth + 1;
if elem.index > 0 {
elem.index -= 1;
stack_exhausted = false;
break;
}
}
if stack_exhausted {
self.stack.truncate(0);
} else {
self.stack.truncate(new_stack_depth);
}
if self.stack.is_empty() {
return None;
}
self.i_last();
self.key_value()
}
fn api_last(&mut self) -> Option<(&'tx [u8], Option<&'tx [u8]>)> {
self.stack.truncate(0);
let root = self.bucket.root();
let pn = self.bucket.page_node(root);
let mut elem_ref = ElemRef { pn, index: 0 };
elem_ref.index = elem_ref.count() as i32 - 1;
self.stack.push(elem_ref);
self.i_last();
while !self.stack.is_empty() && self.stack.last().unwrap().count() == 0 {
self.i_prev();
}
if self.stack.is_empty() {
return None;
}
let (k, v, flags) = self.key_value().unwrap();
if flags & BUCKET_LEAF_FLAG != 0 {
Some((k, None))
} else {
Some((k, Some(v)))
}
}
fn i_last(&mut self) {
loop {
if let Some(elem) = self.stack.last() {
if elem.is_leaf() {
break;
}
let pgid = match &elem.pn {
PageNode::Page(page) => {
let branch_page = MappedBranchPage::coerce_ref(page).unwrap();
branch_page.get_elem(elem.index as u16).unwrap().pgid()
}
PageNode::Node(node) => node.cell.borrow().inodes[elem.index as usize].pgid(),
};
let pn = self.bucket.page_node(pgid);
let mut next_elem = ElemRef { pn, index: 0 };
next_elem.index = next_elem.count() as i32 - 1;
self.stack.push(next_elem);
}
}
}
fn key_value(&self) -> Option<(&'tx [u8], &'tx [u8], u32)> {
let elem_ref = self.stack.last().unwrap();
let pn_count = elem_ref.count();
if pn_count == 0 || elem_ref.index as u32 > pn_count {
return None;
}
match &elem_ref.pn {
PageNode::Page(r) => {
let l = MappedLeafPage::coerce_ref(r).unwrap();
l.get_elem(elem_ref.index as u16)
.map(|inode| (inode.key(), inode.value(), inode.flags()))
}
PageNode::Node(n) => {
let ref_node = n.cell.borrow();
ref_node
.inodes
.get(elem_ref.index as usize)
.map(|inode| (inode.key(), inode.value(), inode.flags()))
}
}
}
fn api_seek(&mut self, seek: &[u8]) -> Option<(&'tx [u8], Option<&'tx [u8]>)> {
let mut vals = self.i_seek(seek);
if let Some(elem_ref) = self.stack.last() {
if elem_ref.index >= elem_ref.count() as i32 {
vals = self.i_next();
}
}
let (k, v, flags) = vals?;
if flags & BUCKET_LEAF_FLAG != 0 {
Some((k, None))
} else {
Some((k, Some(v)))
}
}
fn i_seek(&mut self, seek: &[u8]) -> Option<(&'tx [u8], &'tx [u8], u32)> {
self.stack.truncate(0);
let root = self.bucket.root();
self.search(seek, root);
self.key_value()
}
fn go_to_first_element_on_the_stack(&mut self) {
loop {
let _slice = self.stack.as_slice();
let pgid = {
let r = self.stack.last().unwrap();
if r.is_leaf() {
break;
}
match r.pn {
PageNode::Page(page) => {
let branch_page = MappedBranchPage::coerce_ref(&page).unwrap();
let elem = branch_page.get_elem(r.index as u16).unwrap();
elem.pgid()
}
PageNode::Node(node) => {
let node_borrow = node.cell.borrow();
node_borrow.inodes[r.index as usize].pgid()
}
}
};
let pn = self.bucket.page_node(pgid);
self.stack.push(ElemRef { pn, index: 0 })
}
}
fn search(&mut self, key: &[u8], pgid: PgId) {
let pn = self.bucket.page_node(pgid);
if let PageNode::Page(page) = &pn {
if !page.is_leaf() && !page.is_branch() {
panic!("invalid page type: {}, {:X}", page.id, page.flags);
}
}
let elem = ElemRef { pn, index: 0 };
let elem_is_leaf = elem.is_leaf();
self.stack.push(elem);
if elem_is_leaf {
self.search_inodes(key);
return;
}
match &pn {
PageNode::Page(page) => self.search_page(key, page),
PageNode::Node(node) => self.search_node(key, *node),
}
}
fn search_inodes(&mut self, key: &[u8]) {
if let Some(elem) = self.stack.last_mut() {
let index = match &elem.pn {
PageNode::Page(page) => {
let leaf_page = MappedLeafPage::coerce_ref(page).unwrap();
leaf_page
.elements()
.partition_point(|elem| unsafe { elem.key(leaf_page.page_ptr().cast_const()) } < key)
}
PageNode::Node(node) => node
.cell
.borrow()
.inodes
.partition_point(|inode| inode.key() < key),
};
elem.index = index as i32;
}
}
fn search_node(&mut self, key: &[u8], node: NodeRwCell<'tx>) {
let (index, pgid) = {
let w = node.cell.borrow();
let r = w.inodes.binary_search_by_key(&key, |inode| inode.key());
let index = r.unwrap_or_else(|index| if index > 0 { index - 1 } else { index });
(index as u32, w.inodes[index].pgid())
};
if let Some(elem) = self.stack.last_mut() {
elem.index = index as i32;
}
self.search(key, pgid)
}
fn search_page(&mut self, key: &[u8], page: &RefPage) {
let branch_page = MappedBranchPage::coerce_ref(page).unwrap();
let elements = branch_page.elements();
debug_assert_ne!(0, elements.len());
let r = branch_page
.elements()
.binary_search_by_key(&key, |elem| unsafe {
elem.key(branch_page.page_ptr().cast_const())
});
let index = r.unwrap_or_else(|index| if index > 0 { index - 1 } else { index });
if let Some(elem) = self.stack.last_mut() {
elem.index = index as i32;
}
let pgid = branch_page.elements()[index].pgid();
self.search(key, pgid)
}
}
impl<'tx, B: BucketRwIApi<'tx>> CursorRwIApi<'tx> for InnerCursor<'tx, TxRwCell<'tx>, B> {
fn node(&mut self) -> NodeRwCell<'tx> {
assert!(
!self.stack.is_empty(),
"accessing a node with a zero-length cursor stack"
);
if let Some(elem_ref) = self.stack.last() {
if let PageNode::Node(node) = elem_ref.pn {
if node.cell.borrow().is_leaf {
return node;
}
}
}
let mut n = {
match &self.stack.first().unwrap().pn {
PageNode::Page(page) => self.bucket.node(page.id, None),
PageNode::Node(node) => *node,
}
};
let _stack = &self.stack[0..self.stack.len() - 1];
for elem in &self.stack[0..self.stack.len() - 1] {
assert!(!n.cell.borrow().is_leaf, "expected branch node");
n = n.child_at(elem.index as u32);
}
assert!(n.cell.borrow().is_leaf, "expected leaf node");
n
}
fn api_delete(&mut self) -> crate::Result<()> {
let (k, _, flags) = self.key_value().unwrap();
if flags & BUCKET_LEAF_FLAG != 0 {
return Err(IncompatibleValue);
}
self.node().del(k);
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::test_support::TestDb;
use crate::{
BucketApi, BucketRwApi, CursorApi, CursorRwApi, DbApi, DbRwAPI, Error, TxApi, TxRwRefApi,
};
#[test]
fn test_cursor_seek() -> crate::Result<()> {
let mut db = TestDb::new()?;
db.update(|mut tx| {
let mut b = tx.create_bucket(b"widgets")?;
b.put(b"foo", b"0001")?;
b.put(b"bar", b"0002")?;
b.put(b"baz", b"0003")?;
let _ = b.create_bucket(b"bkt")?;
Ok(())
})?;
db.view(|tx| {
let b = tx.bucket(b"widgets").unwrap();
let mut c = b.cursor();
assert_eq!(
(b"bar".as_slice(), Some(b"0002".as_slice())),
c.seek(b"bar").unwrap()
);
assert_eq!(
(b"baz".as_slice(), Some(b"0003".as_slice())),
c.seek(b"bas").unwrap()
);
assert_eq!(
(b"bar".as_slice(), Some(b"0002".as_slice())),
c.seek(b"").unwrap()
);
assert_eq!(None, c.seek(b"zzz"));
assert_eq!((b"bkt".as_slice(), None), c.seek(b"bkt").unwrap());
Ok(())
})
}
#[test]
#[cfg(not(miri))]
fn test_cursor_delete() -> crate::Result<()> {
let mut db = TestDb::new()?;
let count = 1000u64;
let value = [0u8; 100];
db.update(|mut tx| {
let mut b = tx.create_bucket(b"widgets")?;
for i in 0..count {
let be_i = i.to_be_bytes();
b.put(be_i, value)?;
}
let _ = b.create_bucket(b"sub")?;
Ok(())
})?;
db.must_check();
db.update(|mut tx| {
let b = tx.bucket_mut(b"widgets").unwrap();
let mut c = b.cursor_mut();
let bound = (count / 2).to_be_bytes();
let (mut key, _) = c.first().unwrap();
while key < bound.as_slice() {
c.delete()?;
key = c.next().unwrap().0;
}
c.seek(b"sub");
assert_eq!(Err(Error::IncompatibleValue), c.delete());
Ok(())
})?;
db.must_check();
db.view(|tx| {
let b = tx.bucket(b"widgets").unwrap();
let stats = b.stats();
assert_eq!((count / 2) + 1, stats.key_n as u64);
Ok(())
})?;
Ok(())
}
#[test]
#[cfg(not(miri))]
fn test_cursor_seek_large() -> crate::Result<()> {
let mut db = TestDb::new()?;
let count = 1000u64;
let value = [0u8; 100];
db.update(|mut tx| {
let mut b = tx.create_bucket(b"widgets")?;
for i in (0..count).step_by(100) {
for j in (i..i + 100).step_by(2) {
let k = j.to_be_bytes();
b.put(k, value)?;
}
}
Ok(())
})?;
db.view(|tx| {
let b = tx.bucket(b"widgets").unwrap();
let mut c = b.cursor();
for i in 0..count {
let seek = i.to_be_bytes();
let sought = c.seek(seek);
if i == count - 1 {
assert!(sought.is_none(), "expected None");
continue;
}
let k = sought.unwrap().0;
let num = u64::from_be_bytes(k.try_into().unwrap());
if i % 2 == 0 {
assert_eq!(num, i, "unexpected num: {}", num)
} else {
assert_eq!(num, i + 1, "unexpected num: {}", num)
}
}
Ok(())
})?;
Ok(())
}
#[test]
fn test_cursor_empty_bucket() -> crate::Result<()> {
let mut db = TestDb::new()?;
db.update(|mut tx| {
let _ = tx.create_bucket(b"widgets")?;
Ok(())
})?;
db.view(|tx| {
let b = tx.bucket(b"widgets").unwrap();
let mut c = b.cursor();
let kv = c.first();
assert_eq!(None, kv, "unexpected kv: {:?}", kv);
Ok(())
})?;
Ok(())
}
#[test]
fn test_cursor_empty_bucket_reverse() -> crate::Result<()> {
let mut db = TestDb::new()?;
db.update(|mut tx| {
let _ = tx.create_bucket(b"widgets")?;
Ok(())
})?;
db.view(|tx| {
let b = tx.bucket(b"widgets").unwrap();
let mut c = b.cursor();
let kv = c.last();
assert_eq!(None, kv, "unexpected kv: {:?}", kv);
Ok(())
})?;
Ok(())
}
#[test]
fn test_cursor_iterate_leaf() -> crate::Result<()> {
let mut db = TestDb::new()?;
db.update(|mut tx| {
let mut b = tx.create_bucket(b"widgets")?;
b.put(b"baz", [])?;
b.put(b"foo", [0])?;
b.put(b"bar", [1])?;
Ok(())
})?;
let tx = db.begin()?;
{
let b = tx.bucket(b"widgets").unwrap();
let mut c = b.cursor();
assert_eq!(Some((b"bar".as_slice(), Some([1].as_slice()))), c.first());
assert_eq!(Some((b"baz".as_slice(), Some([].as_slice()))), c.next());
assert_eq!(Some((b"foo".as_slice(), Some([0].as_slice()))), c.next());
assert_eq!(None, c.next());
assert_eq!(None, c.next());
}
Ok(())
}
#[test]
fn test_cursor_leaf_root_reverse() -> crate::Result<()> {
let mut db = TestDb::new()?;
db.update(|mut tx| {
let mut b = tx.create_bucket(b"widgets")?;
b.put(b"baz", [])?;
b.put(b"foo", [0])?;
b.put(b"bar", [1])?;
Ok(())
})?;
let tx = db.begin()?;
{
let b = tx.bucket(b"widgets").unwrap();
let mut c = b.cursor();
assert_eq!(Some((b"foo".as_slice(), Some([0].as_slice()))), c.last());
assert_eq!(Some((b"baz".as_slice(), Some([].as_slice()))), c.prev());
assert_eq!(Some((b"bar".as_slice(), Some([1].as_slice()))), c.prev());
assert_eq!(None, c.prev());
assert_eq!(None, c.prev());
}
Ok(())
}
#[test]
fn test_cursor_restart() -> crate::Result<()> {
let mut db = TestDb::new()?;
db.update(|mut tx| {
let mut b = tx.create_bucket(b"widgets")?;
b.put("foo", [])?;
b.put("bar", [])?;
Ok(())
})?;
let tx = db.begin()?;
{
let b = tx.bucket(b"widgets").unwrap();
let mut c = b.cursor();
assert_eq!(Some((b"bar".as_slice(), Some([].as_slice()))), c.first());
assert_eq!(Some((b"foo".as_slice(), Some([].as_slice()))), c.next());
assert_eq!(Some((b"bar".as_slice(), Some([].as_slice()))), c.first());
assert_eq!(Some((b"foo".as_slice(), Some([].as_slice()))), c.next());
}
Ok(())
}
#[test]
fn test_cursor_first_empty_pages() -> crate::Result<()> {
let mut db = TestDb::new()?;
db.update(|mut tx| {
let mut b = tx.create_bucket(b"widgets")?;
for i in 1..1000u64 {
b.put(bytemuck::bytes_of(&i), [])?;
}
Ok(())
})?;
db.update(|mut tx| {
let mut b = tx.bucket_mut(b"widgets").unwrap();
for i in 1..600u64 {
b.delete(bytemuck::bytes_of(&i))?;
}
let mut c = b.cursor();
let mut kv = c.first();
let mut n = 0;
while kv.is_some() {
n += 1;
kv = c.next();
}
assert_eq!(400, n, "unexpected key count");
Ok(())
})
}
#[test]
fn test_cursor_last_empty_pages() -> crate::Result<()> {
let mut db = TestDb::new()?;
db.update(|mut tx| {
let mut b = tx.create_bucket(b"widgets")?;
for i in 0..1000u64 {
b.put(bytemuck::bytes_of(&i), [])?;
}
Ok(())
})?;
db.update(|mut tx| {
let mut b = tx.bucket_mut(b"widgets").unwrap();
for i in 200..1000u64 {
b.delete(bytemuck::bytes_of(&i))?;
}
let mut c = b.cursor();
let mut kv = c.last();
let mut n = 0;
while kv.is_some() {
n += 1;
kv = c.prev();
}
assert_eq!(200, n, "unexpected key count");
Ok(())
})
}
#[test]
#[ignore]
fn test_cursor_quick_check() {
todo!()
}
#[test]
#[ignore]
fn test_cursor_quick_check_reverse() {
todo!()
}
#[test]
#[ignore]
fn test_cursor_quick_check_buckets_only() {
todo!()
}
#[test]
#[ignore]
fn test_cursor_quick_check_buckets_only_reverse() {
todo!()
}
}