diff --git a/fuzz/fuzz_targets/ops.rs b/fuzz/fuzz_targets/ops.rs index ca15422..e85fc3c 100644 --- a/fuzz/fuzz_targets/ops.rs +++ b/fuzz/fuzz_targets/ops.rs @@ -1,10 +1,11 @@ #![no_main] -use std::collections::HashMap; +use std::collections::BTreeMap; +use std::ops::Bound; -use ekv::config::MAX_VALUE_SIZE; +use ekv::config::{MAX_KEY_SIZE, MAX_VALUE_SIZE}; use ekv::flash::MemFlash; -use ekv::{Config, Database, WriteError}; +use ekv::{Config, Database, ReadError, WriteError}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use libfuzzer_sys::arbitrary::Arbitrary; use libfuzzer_sys::fuzz_target; @@ -19,6 +20,9 @@ struct Input { #[derive(Arbitrary, Debug)] enum Op { Insert(InsertOp), + Delete(DeleteOp), + Read(ReadOp), + ReadRange(ReadRangeOp), } #[derive(Arbitrary, Debug)] @@ -27,6 +31,22 @@ struct InsertOp { value_len: usize, } +#[derive(Arbitrary, Debug)] +struct DeleteOp { + key: u16, +} + +#[derive(Arbitrary, Debug)] +struct ReadOp { + key: u16, +} + +#[derive(Arbitrary, Debug)] +struct ReadRangeOp { + lower_bound: Bound, + upper_bound: Bound, +} + fn fuzz(ops: Input) { if std::env::var_os("RUST_LOG").is_some() { env_logger::init(); @@ -46,8 +66,9 @@ async fn fuzz_inner(ops: Input, dump: bool) { db.format().await.unwrap(); // Mirror hashmap. Should always match F - let mut m = HashMap::new(); + let mut m = BTreeMap::new(); + let mut kbuf = [0; MAX_KEY_SIZE]; let mut buf = [0; MAX_VALUE_SIZE]; for (i, op) in ops.ops.into_iter().enumerate() { @@ -75,6 +96,94 @@ async fn fuzz_inner(ops: Input, dump: bool) { // Write to mirror m.insert(key.to_vec(), val); } + Op::Delete(op) => { + let key = op.key.to_be_bytes(); + + // Write to DB + let mut wtx = db.write_transaction().await; + match wtx.delete(&key).await { + Ok(()) => {} + Err(WriteError::Full) => continue, + Err(e) => panic!("write error: {:?}", e), + } + wtx.commit().await.unwrap(); + + // Write to mirror + m.remove(&key[..]); + } + Op::Read(op) => { + let key = op.key.to_be_bytes(); + + // Read from DB + let rtx = db.read_transaction().await; + let got_val = match rtx.read(&key, &mut buf).await { + Ok(n) => Some(&buf[..n]), + Err(ReadError::KeyNotFound) => None, + Err(e) => panic!("write error: {:?}", e), + }; + + // Write to mirror + let want_val = m.get(&key[..]).map(|v| &v[..]); + + assert_eq!(got_val, want_val); + } + Op::ReadRange(op) => { + // ignore reversed ranges, otherwise BTreeMap::range panics. + if let (Bound::Excluded(l) | Bound::Included(l), Bound::Excluded(u) | Bound::Included(u)) = + (op.lower_bound, op.upper_bound) + { + if l > u { + continue; + } + } + // Tis also panics.... + if let (Bound::Excluded(l), Bound::Excluded(u)) = (op.lower_bound, op.upper_bound) { + if l == u { + continue; + } + } + + let mut lower_buf = [0; 2]; + let lower_bound = op.lower_bound.map(|k| { + lower_buf = k.to_be_bytes(); + &lower_buf[..] + }); + let mut upper_buf = [0; 2]; + let upper_bound = op.upper_bound.map(|k| { + upper_buf = k.to_be_bytes(); + &upper_buf[..] + }); + + // Get cursor from DB + let rtx = db.read_transaction().await; + let mut cur = match rtx.read_range((lower_bound, upper_bound)).await { + Ok(cur) => cur, + Err(e) => panic!("read_range error: {:?}", e), + }; + + // iterate the mirror map. + for (want_k, want_v) in m.range(( + op.lower_bound.map(|k| k.to_be_bytes().to_vec()), + op.upper_bound.map(|k| k.to_be_bytes().to_vec()), + )) { + match cur.next(&mut kbuf, &mut buf).await { + Err(e) => panic!("Cursor::next error: {:?}", e), + Ok(None) => panic!("Cursor returned None too early."), + Ok(Some((klen, vlen))) => { + let got_k = &kbuf[..klen]; + let got_v = &buf[..vlen]; + assert_eq!(want_k, got_k); + assert_eq!(want_v, got_v); + } + } + } + + match cur.next(&mut kbuf, &mut buf).await { + Err(e) => panic!("Cursor::next error: {:?}", e), + Ok(None) => {} + Ok(Some(_)) => panic!("Cursor::next didn't return None when it should."), + } + } } if dump { diff --git a/fuzz/fuzz_targets/read.rs b/fuzz/fuzz_targets/read.rs index 08e9763..c0dfbc7 100644 --- a/fuzz/fuzz_targets/read.rs +++ b/fuzz/fuzz_targets/read.rs @@ -1,6 +1,6 @@ #![no_main] use ekv::flash::MemFlash; -use ekv::{Bound, Config, Database}; +use ekv::{Config, Database}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use libfuzzer_sys::fuzz_target; @@ -57,19 +57,7 @@ async fn fuzz_inner(data: &[u8], dump: bool) { drop(rtx); let rtx = db.read_transaction().await; - if let Ok(mut cursor) = rtx - .read_range( - Some(Bound { - key: b"foo", - allow_equal: false, - }), - Some(Bound { - key: b"poo", - allow_equal: false, - }), - ) - .await - { + if let Ok(mut cursor) = rtx.read_range(&b"foo"[..]..&b"poo"[..]).await { let mut kbuf = [0; 64]; let mut vbuf = [0; 64]; while let Ok(Some((klen, vlen))) = cursor.next(&mut kbuf, &mut vbuf).await { diff --git a/src/cursor.rs b/src/cursor.rs index 9fc5c53..c167f90 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,4 +1,5 @@ use core::cmp::Ordering; +use core::ops::Bound; use embassy_sync::blocking_mutex::raw::RawMutex; use heapless::Vec; @@ -11,33 +12,20 @@ use crate::page::ReadError as PageReadError; use crate::record::{Inner, RecordHeader}; use crate::Database; -/// Upper or lower bound for a range read. -pub struct Bound<'a> { - /// Key. - pub key: &'a [u8], - /// Whether the bound includes entries with key equal to `self.key`. - /// - /// If false, only entries with strictly greater keys (for `lower_bound`) or - /// strictly smaller keys (for `upper_bound`) will be returned. - /// - /// If true, equal keys will also be returned. - pub allow_equal: bool, -} - /// Cursor for a range read. /// /// Returned by [`ReadTransaction::read_all()`](crate::ReadTransaction::read_all) and [`ReadTransaction::read_range()`](crate::ReadTransaction::read_range). pub struct Cursor<'a, F: Flash + 'a, M: RawMutex + 'a> { db: &'a Database, - upper_bound: Option>, + upper_bound: Bound<&'a [u8]>, readers: [Option; FILE_COUNT], } impl<'a, F: Flash + 'a, M: RawMutex + 'a> Cursor<'a, F, M> { pub(crate) async fn new( db: &'a Database, - lower_bound: Option>, - upper_bound: Option>, + lower_bound: Bound<&[u8]>, + upper_bound: Bound<&'a [u8]>, ) -> Result> { let inner = &mut *db.inner.lock().await; inner.files.remount_if_dirty(&mut inner.readers[0]).await?; @@ -46,10 +34,12 @@ impl<'a, F: Flash + 'a, M: RawMutex + 'a> Cursor<'a, F, M> { let mut readers: Vec, FILE_COUNT> = Vec::new(); for i in 0..FILE_COUNT { let file_id = i as FileID; - let r = if let Some(bound) = &lower_bound { - inner.search_lower_bound_file(file_id, bound).await? - } else { - Some(inner.files.read(&mut inner.readers[0], file_id).dehydrate()) + let r = match lower_bound { + Bound::Excluded(k) | Bound::Included(k) => { + let included = matches!(lower_bound, Bound::Included(_)); + inner.search_lower_bound_file(file_id, k, included).await? + } + Bound::Unbounded => Some(inner.files.read(&mut inner.readers[0], file_id).dehydrate()), }; let _ = readers.push(r); } @@ -107,13 +97,10 @@ impl<'a, F: Flash + 'a, M: RawMutex + 'a> Cursor<'a, F, M> { let got_key = &mut key_buf[..header.key_len]; r.read(m, got_key).await.map_err(no_eof)?; - let finished = match &self.upper_bound { - None => false, - Some(upper_bound) => match got_key[..].cmp(upper_bound.key) { - Ordering::Equal => !upper_bound.allow_equal, - Ordering::Less => false, - Ordering::Greater => true, - }, + let finished = match self.upper_bound { + Bound::Included(key) => key < got_key, + Bound::Excluded(key) => key <= got_key, + Bound::Unbounded => false, }; if finished { // reached the upper bound, remove this file. @@ -203,7 +190,8 @@ impl Inner { async fn search_lower_bound_file( &mut self, file_id: FileID, - bound: &Bound<'_>, + bound_key: &[u8], + bound_included: bool, ) -> Result, Error> { let r = self.files.read(&mut self.readers[0], file_id); let m = &mut self.files; @@ -229,10 +217,10 @@ impl Inner { s.reader().read(m, got_key).await.map_err(no_eof)?; // Found? - let dir = match got_key[..].cmp(bound.key) { + let dir = match got_key[..].cmp(bound_key) { Ordering::Equal => { // if equal is allowed, return it. - if bound.allow_equal { + if bound_included { return Ok(Some(dehydrated)); } // otherwise return the next key. @@ -265,10 +253,10 @@ impl Inner { r.read(m, got_key).await.map_err(no_eof)?; // Found? - match got_key[..].cmp(bound.key) { + match got_key[..].cmp(bound_key) { Ordering::Equal => { // if equal is allowed, return it. - if bound.allow_equal { + if bound_included { return Ok(Some(dehydrated)); } // otherwise return the next key. @@ -286,6 +274,9 @@ impl Inner { #[cfg(test)] mod tests { + use std::ops::Bound; + use std::ops::Bound::*; + use embassy_sync::blocking_mutex::raw::NoopRawMutex; use super::*; @@ -324,25 +315,15 @@ mod tests { async fn check_read_range( db: &Database, - lower: Option>, - upper: Option>, + lower: Bound<&[u8]>, + upper: Bound<&[u8]>, entries: &[(&[u8], &[u8])], ) { let rtx = db.read_transaction().await; - let cursor = rtx.read_range(lower, upper).await.unwrap(); + let cursor = rtx.read_range((lower, upper)).await.unwrap(); check_cursor(cursor, entries).await } - fn incl(key: &[u8]) -> Option> { - Some(Bound { key, allow_equal: true }) - } - fn excl(key: &[u8]) -> Option> { - Some(Bound { - key, - allow_equal: false, - }) - } - #[test_log::test(tokio::test)] async fn test_empty() { let mut f = MemFlash::new(); @@ -448,63 +429,63 @@ mod tests { (b"ee", b"e"), (b"ff", b"f"), ]; - check_read_range(&db, None, None, rows).await; - check_read_range(&db, None, incl(b"ff"), rows).await; - check_read_range(&db, None, incl(b"zz"), rows).await; - check_read_range(&db, None, excl(b"zz"), rows).await; - - check_read_range(&db, incl(b"aa"), None, rows).await; - check_read_range(&db, incl(b"aa"), incl(b"ff"), rows).await; - check_read_range(&db, incl(b"aa"), incl(b"zz"), rows).await; - check_read_range(&db, incl(b"aa"), excl(b"zz"), rows).await; - - check_read_range(&db, incl(b"0"), None, rows).await; - check_read_range(&db, incl(b"0"), incl(b"ff"), rows).await; - check_read_range(&db, incl(b"0"), incl(b"zz"), rows).await; - check_read_range(&db, incl(b"0"), excl(b"zz"), rows).await; - - check_read_range(&db, excl(b"0"), None, rows).await; - check_read_range(&db, excl(b"0"), incl(b"ff"), rows).await; - check_read_range(&db, excl(b"0"), incl(b"zz"), rows).await; - check_read_range(&db, excl(b"0"), excl(b"zz"), rows).await; + check_read_range(&db, Unbounded, Unbounded, rows).await; + check_read_range(&db, Unbounded, Included(b"ff"), rows).await; + check_read_range(&db, Unbounded, Included(b"zz"), rows).await; + check_read_range(&db, Unbounded, Excluded(b"zz"), rows).await; + + check_read_range(&db, Included(b"aa"), Unbounded, rows).await; + check_read_range(&db, Included(b"aa"), Included(b"ff"), rows).await; + check_read_range(&db, Included(b"aa"), Included(b"zz"), rows).await; + check_read_range(&db, Included(b"aa"), Excluded(b"zz"), rows).await; + + check_read_range(&db, Included(b"0"), Unbounded, rows).await; + check_read_range(&db, Included(b"0"), Included(b"ff"), rows).await; + check_read_range(&db, Included(b"0"), Included(b"zz"), rows).await; + check_read_range(&db, Included(b"0"), Excluded(b"zz"), rows).await; + + check_read_range(&db, Excluded(b"0"), Unbounded, rows).await; + check_read_range(&db, Excluded(b"0"), Included(b"ff"), rows).await; + check_read_range(&db, Excluded(b"0"), Included(b"zz"), rows).await; + check_read_range(&db, Excluded(b"0"), Excluded(b"zz"), rows).await; // match a few keys. let rows: &[(&[u8], &[u8])] = &[(b"cc", b"c"), (b"dd", b"d"), (b"ee", b"e")]; - check_read_range(&db, incl(b"cc"), incl(b"ee"), rows).await; - check_read_range(&db, incl(b"c0"), incl(b"ee"), rows).await; - check_read_range(&db, excl(b"c0"), incl(b"ee"), rows).await; - check_read_range(&db, excl(b"bb"), incl(b"ee"), rows).await; - - check_read_range(&db, incl(b"cc"), incl(b"ef"), rows).await; - check_read_range(&db, incl(b"c0"), incl(b"ef"), rows).await; - check_read_range(&db, excl(b"c0"), incl(b"ef"), rows).await; - check_read_range(&db, excl(b"bb"), incl(b"ef"), rows).await; - - check_read_range(&db, incl(b"cc"), excl(b"ef"), rows).await; - check_read_range(&db, incl(b"c0"), excl(b"ef"), rows).await; - check_read_range(&db, excl(b"c0"), excl(b"ef"), rows).await; - check_read_range(&db, excl(b"bb"), excl(b"ef"), rows).await; - - check_read_range(&db, incl(b"cc"), excl(b"ff"), rows).await; - check_read_range(&db, incl(b"c0"), excl(b"ff"), rows).await; - check_read_range(&db, excl(b"c0"), excl(b"ff"), rows).await; - check_read_range(&db, excl(b"bb"), excl(b"ff"), rows).await; + check_read_range(&db, Included(b"cc"), Included(b"ee"), rows).await; + check_read_range(&db, Included(b"c0"), Included(b"ee"), rows).await; + check_read_range(&db, Excluded(b"c0"), Included(b"ee"), rows).await; + check_read_range(&db, Excluded(b"bb"), Included(b"ee"), rows).await; + + check_read_range(&db, Included(b"cc"), Included(b"ef"), rows).await; + check_read_range(&db, Included(b"c0"), Included(b"ef"), rows).await; + check_read_range(&db, Excluded(b"c0"), Included(b"ef"), rows).await; + check_read_range(&db, Excluded(b"bb"), Included(b"ef"), rows).await; + + check_read_range(&db, Included(b"cc"), Excluded(b"ef"), rows).await; + check_read_range(&db, Included(b"c0"), Excluded(b"ef"), rows).await; + check_read_range(&db, Excluded(b"c0"), Excluded(b"ef"), rows).await; + check_read_range(&db, Excluded(b"bb"), Excluded(b"ef"), rows).await; + + check_read_range(&db, Included(b"cc"), Excluded(b"ff"), rows).await; + check_read_range(&db, Included(b"c0"), Excluded(b"ff"), rows).await; + check_read_range(&db, Excluded(b"c0"), Excluded(b"ff"), rows).await; + check_read_range(&db, Excluded(b"bb"), Excluded(b"ff"), rows).await; // empty to the left - check_read_range(&db, None, incl(b"0"), &[]).await; - check_read_range(&db, None, excl(b"0"), &[]).await; - check_read_range(&db, None, excl(b"aa"), &[]).await; + check_read_range(&db, Unbounded, Included(b"0"), &[]).await; + check_read_range(&db, Unbounded, Excluded(b"0"), &[]).await; + check_read_range(&db, Unbounded, Excluded(b"aa"), &[]).await; // empty to the right - check_read_range(&db, incl(b"z"), None, &[]).await; - check_read_range(&db, excl(b"z"), None, &[]).await; - check_read_range(&db, excl(b"ff"), None, &[]).await; + check_read_range(&db, Included(b"z"), Unbounded, &[]).await; + check_read_range(&db, Excluded(b"z"), Unbounded, &[]).await; + check_read_range(&db, Excluded(b"ff"), Unbounded, &[]).await; // empty in the middle - check_read_range(&db, excl(b"aa"), excl(b"bb"), &[]).await; - check_read_range(&db, incl(b"ax"), excl(b"bb"), &[]).await; - check_read_range(&db, excl(b"aa"), incl(b"ba"), &[]).await; - check_read_range(&db, incl(b"ax"), incl(b"ba"), &[]).await; + check_read_range(&db, Excluded(b"aa"), Excluded(b"bb"), &[]).await; + check_read_range(&db, Included(b"ax"), Excluded(b"bb"), &[]).await; + check_read_range(&db, Excluded(b"aa"), Included(b"ba"), &[]).await; + check_read_range(&db, Included(b"ax"), Included(b"ba"), &[]).await; } #[test_log::test(tokio::test)] @@ -545,62 +526,62 @@ mod tests { (b"ee", b"e"), (b"ff", b"f"), ]; - check_read_range(&db, None, None, rows).await; - check_read_range(&db, None, incl(b"ff"), rows).await; - check_read_range(&db, None, incl(b"zz"), rows).await; - check_read_range(&db, None, excl(b"zz"), rows).await; - - check_read_range(&db, incl(b"aa"), None, rows).await; - check_read_range(&db, incl(b"aa"), incl(b"ff"), rows).await; - check_read_range(&db, incl(b"aa"), incl(b"zz"), rows).await; - check_read_range(&db, incl(b"aa"), excl(b"zz"), rows).await; - - check_read_range(&db, incl(b"0"), None, rows).await; - check_read_range(&db, incl(b"0"), incl(b"ff"), rows).await; - check_read_range(&db, incl(b"0"), incl(b"zz"), rows).await; - check_read_range(&db, incl(b"0"), excl(b"zz"), rows).await; - - check_read_range(&db, excl(b"0"), None, rows).await; - check_read_range(&db, excl(b"0"), incl(b"ff"), rows).await; - check_read_range(&db, excl(b"0"), incl(b"zz"), rows).await; - check_read_range(&db, excl(b"0"), excl(b"zz"), rows).await; + check_read_range(&db, Unbounded, Unbounded, rows).await; + check_read_range(&db, Unbounded, Included(b"ff"), rows).await; + check_read_range(&db, Unbounded, Included(b"zz"), rows).await; + check_read_range(&db, Unbounded, Excluded(b"zz"), rows).await; + + check_read_range(&db, Included(b"aa"), Unbounded, rows).await; + check_read_range(&db, Included(b"aa"), Included(b"ff"), rows).await; + check_read_range(&db, Included(b"aa"), Included(b"zz"), rows).await; + check_read_range(&db, Included(b"aa"), Excluded(b"zz"), rows).await; + + check_read_range(&db, Included(b"0"), Unbounded, rows).await; + check_read_range(&db, Included(b"0"), Included(b"ff"), rows).await; + check_read_range(&db, Included(b"0"), Included(b"zz"), rows).await; + check_read_range(&db, Included(b"0"), Excluded(b"zz"), rows).await; + + check_read_range(&db, Excluded(b"0"), Unbounded, rows).await; + check_read_range(&db, Excluded(b"0"), Included(b"ff"), rows).await; + check_read_range(&db, Excluded(b"0"), Included(b"zz"), rows).await; + check_read_range(&db, Excluded(b"0"), Excluded(b"zz"), rows).await; // match a few keys. let rows: &[(&[u8], &[u8])] = &[(b"cc", b"c"), (b"dd", b"d"), (b"ee", b"e")]; - check_read_range(&db, incl(b"cc"), incl(b"ee"), rows).await; - check_read_range(&db, incl(b"c0"), incl(b"ee"), rows).await; - check_read_range(&db, excl(b"c0"), incl(b"ee"), rows).await; - check_read_range(&db, excl(b"bb"), incl(b"ee"), rows).await; - - check_read_range(&db, incl(b"cc"), incl(b"ef"), rows).await; - check_read_range(&db, incl(b"c0"), incl(b"ef"), rows).await; - check_read_range(&db, excl(b"c0"), incl(b"ef"), rows).await; - check_read_range(&db, excl(b"bb"), incl(b"ef"), rows).await; - - check_read_range(&db, incl(b"cc"), excl(b"ef"), rows).await; - check_read_range(&db, incl(b"c0"), excl(b"ef"), rows).await; - check_read_range(&db, excl(b"c0"), excl(b"ef"), rows).await; - check_read_range(&db, excl(b"bb"), excl(b"ef"), rows).await; - - check_read_range(&db, incl(b"cc"), excl(b"ff"), rows).await; - check_read_range(&db, incl(b"c0"), excl(b"ff"), rows).await; - check_read_range(&db, excl(b"c0"), excl(b"ff"), rows).await; - check_read_range(&db, excl(b"bb"), excl(b"ff"), rows).await; + check_read_range(&db, Included(b"cc"), Included(b"ee"), rows).await; + check_read_range(&db, Included(b"c0"), Included(b"ee"), rows).await; + check_read_range(&db, Excluded(b"c0"), Included(b"ee"), rows).await; + check_read_range(&db, Excluded(b"bb"), Included(b"ee"), rows).await; + + check_read_range(&db, Included(b"cc"), Included(b"ef"), rows).await; + check_read_range(&db, Included(b"c0"), Included(b"ef"), rows).await; + check_read_range(&db, Excluded(b"c0"), Included(b"ef"), rows).await; + check_read_range(&db, Excluded(b"bb"), Included(b"ef"), rows).await; + + check_read_range(&db, Included(b"cc"), Excluded(b"ef"), rows).await; + check_read_range(&db, Included(b"c0"), Excluded(b"ef"), rows).await; + check_read_range(&db, Excluded(b"c0"), Excluded(b"ef"), rows).await; + check_read_range(&db, Excluded(b"bb"), Excluded(b"ef"), rows).await; + + check_read_range(&db, Included(b"cc"), Excluded(b"ff"), rows).await; + check_read_range(&db, Included(b"c0"), Excluded(b"ff"), rows).await; + check_read_range(&db, Excluded(b"c0"), Excluded(b"ff"), rows).await; + check_read_range(&db, Excluded(b"bb"), Excluded(b"ff"), rows).await; // empty to the left - check_read_range(&db, None, incl(b"0"), &[]).await; - check_read_range(&db, None, excl(b"0"), &[]).await; - check_read_range(&db, None, excl(b"aa"), &[]).await; + check_read_range(&db, Unbounded, Included(b"0"), &[]).await; + check_read_range(&db, Unbounded, Excluded(b"0"), &[]).await; + check_read_range(&db, Unbounded, Excluded(b"aa"), &[]).await; // empty to the right - check_read_range(&db, incl(b"z"), None, &[]).await; - check_read_range(&db, excl(b"z"), None, &[]).await; - check_read_range(&db, excl(b"ff"), None, &[]).await; + check_read_range(&db, Included(b"z"), Unbounded, &[]).await; + check_read_range(&db, Excluded(b"z"), Unbounded, &[]).await; + check_read_range(&db, Excluded(b"ff"), Unbounded, &[]).await; // empty in the middle - check_read_range(&db, excl(b"aa"), excl(b"bb"), &[]).await; - check_read_range(&db, incl(b"ax"), excl(b"bb"), &[]).await; - check_read_range(&db, excl(b"aa"), incl(b"ba"), &[]).await; - check_read_range(&db, incl(b"ax"), incl(b"ba"), &[]).await; + check_read_range(&db, Excluded(b"aa"), Excluded(b"bb"), &[]).await; + check_read_range(&db, Included(b"ax"), Excluded(b"bb"), &[]).await; + check_read_range(&db, Excluded(b"aa"), Included(b"ba"), &[]).await; + check_read_range(&db, Included(b"ax"), Included(b"ba"), &[]).await; } } diff --git a/src/lib.rs b/src/lib.rs index 8145dd8..1fc1c7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ mod errors; pub mod flash; mod types; -pub use cursor::{Bound, Cursor}; +pub use cursor::Cursor; pub use errors::{CommitError, FormatError, MountError, ReadError, WriteError}; pub use record::{Config, Database, ReadTransaction, WriteTransaction}; diff --git a/src/record.rs b/src/record.rs index 8a54825..72e6bdb 100644 --- a/src/record.rs +++ b/src/record.rs @@ -1,7 +1,7 @@ use core::cell::RefCell; use core::cmp::Ordering; use core::future::poll_fn; -use core::ops::{Deref, DerefMut}; +use core::ops::{Deref, DerefMut, RangeBounds}; use core::task::Poll; use embassy_sync::blocking_mutex::raw::RawMutex; @@ -15,7 +15,7 @@ use crate::errors::{no_eof, CorruptedError, Error, MountError, ReadError, WriteE use crate::file::{FileID, FileManager, FileReader, FileSearcher, FileWriter, SeekDirection, PAGE_MAX_PAYLOAD_SIZE}; use crate::flash::Flash; use crate::page::{PageReader, ReadError as PageReadError}; -use crate::{Bound, CommitError, Cursor, FormatError}; +use crate::{CommitError, Cursor, FormatError}; const FILE_FLAG_COMPACT_DEST: u8 = 0x01; const FILE_FLAG_COMPACT_SRC: u8 = 0x02; @@ -249,24 +249,21 @@ impl<'a, F: Flash + 'a, M: RawMutex + 'a> ReadTransaction<'a, F, M> { /// Get a cursor for reading all the keys in the database. /// - /// This is equivalent to calling `read_range(None, None)`. + /// This is equivalent to calling `read_range(..)`. /// /// The cursor returns the keys in lexicographically ascending order. pub async fn read_all<'b>(&'b self) -> Result, Error> { - self.read_range(None, None).await + self.read_range(..).await } - /// Get a cursor for reading keys in the database that are between `lower_bound` and `upper_bound`. - /// - /// A `None` bound indicates no upper or lower bound. + /// Get a cursor for reading keys in the database that are in the given range. /// /// The cursor returns the keys in lexicographically ascending order. pub async fn read_range<'b>( &'b self, - lower_bound: Option>, - upper_bound: Option>, + range: impl RangeBounds<&'b [u8]>, ) -> Result, Error> { - Cursor::new(self.db, lower_bound, upper_bound).await + Cursor::new(self.db, range.start_bound().map(|x| *x), range.end_bound().map(|x| *x)).await } }