diff --git a/Cargo.lock b/Cargo.lock index 5fe2762..1461d0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,7 +71,7 @@ dependencies = [ [[package]] name = "candystore" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "bytemuck", diff --git a/Cargo.toml b/Cargo.toml index 6fba273..0dee8c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "candystore" -version = "0.4.1" +version = "0.4.2" edition = "2021" license = "Apache-2.0" keywords = ["key-value", "database", "persistent", "store", "rocksdb"] diff --git a/candy-crasher/src/main.rs b/candy-crasher/src/main.rs index 10787cf..02115bb 100644 --- a/candy-crasher/src/main.rs +++ b/candy-crasher/src/main.rs @@ -336,7 +336,7 @@ fn main() -> Result<()> { if i % 65536 == 0 { println!("{i}"); } - store.push_to_list_tail("xxx", &i.to_le_bytes())?; + store.set_in_list("xxx", &i.to_le_bytes(), &i.to_le_bytes())?; } println!( "{}us", diff --git a/src/encodable.rs b/src/encodable.rs deleted file mode 100644 index 9b9c2e8..0000000 --- a/src/encodable.rs +++ /dev/null @@ -1,37 +0,0 @@ -use databuf::{Decode, Encode}; -use std::fmt::{Display, Formatter}; -use std::io::Write; -use uuid::{Bytes, Uuid}; - -#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct EncodableUuid(Uuid); - -impl From for EncodableUuid { - fn from(value: Uuid) -> Self { - Self(value) - } -} - -impl From for Uuid { - fn from(value: EncodableUuid) -> Self { - value.0 - } -} - -impl Encode for EncodableUuid { - fn encode(&self, c: &mut (impl Write + ?Sized)) -> std::io::Result<()> { - self.0.as_bytes().encode::(c) - } -} - -impl Decode<'_> for EncodableUuid { - fn decode(c: &mut &'_ [u8]) -> databuf::Result { - Ok(Self(Uuid::from_bytes(Bytes::decode::(c)?))) - } -} - -impl Display for EncodableUuid { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - Display::fmt(&self.0, f) - } -} diff --git a/src/lib.rs b/src/lib.rs index 030d295..66fac29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,6 @@ //! } //! ``` -mod encodable; mod hashing; mod lists; mod queues; diff --git a/src/lists.rs b/src/lists.rs index 5af3be0..ea60d71 100644 --- a/src/lists.rs +++ b/src/lists.rs @@ -1,5 +1,4 @@ use crate::{ - encodable::EncodableUuid, hashing::PartedHash, shard::{InsertMode, KVPair}, store::{CHAIN_NAMESPACE, ITEM_NAMESPACE, LIST_NAMESPACE}, @@ -8,7 +7,6 @@ use crate::{ use bytemuck::{bytes_of, from_bytes, Pod, Zeroable}; use parking_lot::MutexGuard; -use uuid::Uuid; #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] @@ -118,6 +116,24 @@ impl<'a> Iterator for ListIterator<'a> { None } + + fn size_hint(&self) -> (usize, Option) { + if let Some(ref list) = self.list { + if self.fwd { + ( + (list.tail_idx - self.idx) as usize, + Some((list.tail_idx - self.idx) as usize), + ) + } else { + ( + (self.idx + 1 - list.head_idx) as usize, + Some((self.idx + 1 - list.head_idx) as usize), + ) + } + } else { + (0, None) + } + } } #[derive(Debug)] @@ -129,11 +145,6 @@ enum InsertToListStatus { Replaced(Vec), } -enum InsertToListPos { - Head, - Tail, -} - impl CandyStore { const FIRST_LIST_IDX: u64 = 0x8000_0000_0000_0000; @@ -158,7 +169,6 @@ impl CandyStore { item_key: Vec, mut val: Vec, mode: InsertMode, - pos: InsertToListPos, ) -> Result { let (list_ph, list_key) = self.make_list_key(list_key); let (item_ph, item_key) = self.make_item_key(list_ph, item_key); @@ -227,17 +237,8 @@ impl CandyStore { crate::GetOrCreateStatus::ExistingValue(list_bytes) => { let mut list = *from_bytes::(&list_bytes); - let item_idx = match pos { - InsertToListPos::Tail => { - let idx = list.tail_idx; - list.tail_idx += 1; - idx - } - InsertToListPos::Head => { - list.head_idx -= 1; - list.head_idx - } - }; + let idx = list.tail_idx; + list.tail_idx += 1; // update list list.num_items += 1; @@ -247,14 +248,14 @@ impl CandyStore { self.set_raw( bytes_of(&ChainKey { list_ph, - idx: item_idx, + idx, namespace: CHAIN_NAMESPACE, }), bytes_of(&item_ph), )?; // create item - val.extend_from_slice(bytes_of(&item_idx)); + val.extend_from_slice(bytes_of(&idx)); self.set_raw(&item_key, &val)?; } } @@ -319,13 +320,7 @@ impl CandyStore { if promote { self.owned_remove_from_list(list_key.clone(), item_key.clone())?; } - match self._insert_to_list( - list_key, - item_key, - val, - InsertMode::Set, - InsertToListPos::Tail, - )? { + match self._insert_to_list(list_key, item_key, val, InsertMode::Set)? { InsertToListStatus::Created(_v) => Ok(SetStatus::CreatedNew), InsertToListStatus::Replaced(v) => Ok(SetStatus::PrevValue(v)), _ => unreachable!(), @@ -361,13 +356,7 @@ impl CandyStore { val: Vec, expected_val: Option<&[u8]>, ) -> Result { - match self._insert_to_list( - list_key, - item_key, - val, - InsertMode::Replace(expected_val), - InsertToListPos::Tail, - )? { + match self._insert_to_list(list_key, item_key, val, InsertMode::Replace(expected_val))? { InsertToListStatus::DoesNotExist => Ok(ReplaceStatus::DoesNotExist), InsertToListStatus::Replaced(v) => Ok(ReplaceStatus::PrevValue(v)), InsertToListStatus::WrongValue(v) => Ok(ReplaceStatus::WrongValue(v)), @@ -401,13 +390,7 @@ impl CandyStore { item_key: Vec, default_val: Vec, ) -> Result { - match self._insert_to_list( - list_key, - item_key, - default_val, - InsertMode::GetOrCreate, - InsertToListPos::Tail, - )? { + match self._insert_to_list(list_key, item_key, default_val, InsertMode::GetOrCreate)? { InsertToListStatus::ExistingValue(v) => Ok(GetOrCreateStatus::ExistingValue(v)), InsertToListStatus::Created(v) => Ok(GetOrCreateStatus::CreatedNew(v)), _ => unreachable!(), @@ -748,64 +731,6 @@ impl CandyStore { Ok(None) } - fn owned_push_to_list( - &self, - list_key: Vec, - val: Vec, - pos: InsertToListPos, - ) -> Result { - let uuid = Uuid::from_bytes(rand::random()); - let res = self._insert_to_list( - list_key, - uuid.as_bytes().to_vec(), - val, - InsertMode::GetOrCreate, - pos, - )?; - debug_assert!(matches!(res, InsertToListStatus::Created(_))); - Ok(EncodableUuid::from(uuid)) - } - - /// Pushed "value only" at the beginning (head) of the list. The key is actually a randomly-generated UUID, - /// which is returned to the caller and can be acted up like a regular list item. This is used to implement - /// double-ended queues, where elements are pushed/popped at the ends, thus the key is not meaningful. - pub fn push_to_list_head + ?Sized, B2: AsRef<[u8]> + ?Sized>( - &self, - list_key: &B1, - val: &B2, - ) -> Result { - self.owned_push_to_list_head(list_key.as_ref().to_owned(), val.as_ref().to_owned()) - } - - /// Owned version of [Self::push_to_list_head] - pub fn owned_push_to_list_head( - &self, - list_key: Vec, - val: Vec, - ) -> Result { - self.owned_push_to_list(list_key, val, InsertToListPos::Head) - } - - /// Pushed "value only" at the end (tail) of the list. The key is actually a randomly-generated UUID, - /// which is returned to the caller and can be acted up like a regular list item. This is used to implement - /// double-ended queues, where elements are pushed/popped at the ends, thus the key is not meaningful. - pub fn push_to_list_tail + ?Sized, B2: AsRef<[u8]> + ?Sized>( - &self, - list_key: &B1, - val: &B2, - ) -> Result { - self.owned_push_to_list_tail(list_key.as_ref().to_owned(), val.as_ref().to_owned()) - } - - /// Owned version of [Self::push_to_list_tail] - pub fn owned_push_to_list_tail( - &self, - list_key: Vec, - val: Vec, - ) -> Result { - self.owned_push_to_list(list_key, val, InsertToListPos::Tail) - } - /// Returns the estimated list length pub fn list_len + ?Sized>(&self, list_key: &B) -> Result { self.owned_list_len(list_key.as_ref().to_owned()) diff --git a/src/queues.rs b/src/queues.rs index 457bcea..05561fd 100644 --- a/src/queues.rs +++ b/src/queues.rs @@ -96,6 +96,17 @@ impl<'a> Iterator for QueueIterator<'a> { } } } + + fn size_hint(&self) -> (usize, Option) { + if let (Some(curr), Some(end)) = (self.curr, self.end) { + if self.fwd { + return ((end - curr) as usize, None); + } else { + return ((curr + 1 - end) as usize, None); + } + } + (0, None) + } } impl CandyStore { @@ -354,11 +365,10 @@ impl CandyStore { &self, queue_key: &B, ) -> Result>> { - let Some(res) = self.iter_queue(queue_key).next() else { - return Ok(None); - }; - let (_, v) = res?; - Ok(Some(v)) + for res in self.iter_queue(queue_key) { + return Ok(Some(res?.1)); + } + Ok(None) } /// Returns (without removing) the tail element of the queue, or None if the queue is empty @@ -366,11 +376,10 @@ impl CandyStore { &self, queue_key: &B, ) -> Result>> { - let Some(res) = self.iter_queue_backwards(queue_key).next() else { - return Ok(None); - }; - let (_, v) = res?; - Ok(Some(v)) + for res in self.iter_queue_backwards(queue_key) { + return Ok(Some(res?.1)); + } + Ok(None) } /// Returns a forward iterator (head to tail) over the elements of the queue. If the queue does not exist, diff --git a/src/typed.rs b/src/typed.rs index 36fbc4a..08bbbff 100644 --- a/src/typed.rs +++ b/src/typed.rs @@ -7,7 +7,6 @@ use crate::{ CandyStore, ListCompactionParams, }; -use crate::encodable::EncodableUuid; use crate::Result; use databuf::{config::num::LE, DecodeOwned, Encode}; @@ -490,38 +489,6 @@ where self.store.compact_list_if_needed(&list_key, params) } - /// Same as [CandyStore::push_to_list_head], but `list_key` is typed - pub fn push_head( - &self, - list_key: &Q1, - val: &Q2, - ) -> Result - where - L: Borrow, - EncodableUuid: From, - V: Borrow, - { - let list_key = Self::make_list_key(list_key); - let val = val.to_bytes::(); - self.store.owned_push_to_list_head(list_key, val) - } - - /// Same as [CandyStore::push_to_list_tail], but `list_key` is typed - pub fn push_tail( - &self, - list_key: &Q1, - val: &Q2, - ) -> Result - where - L: Borrow, - EncodableUuid: From, - V: Borrow, - { - let list_key = Self::make_list_key(list_key); - let val = val.to_bytes::(); - self.store.owned_push_to_list_tail(list_key, val) - } - /// Same as [CandyStore::pop_list_tail], but `list_key` is typed pub fn pop_tail(&self, list_key: &Q) -> Result> where diff --git a/tests/test_lists.rs b/tests/test_lists.rs index a798402..b5f9075 100644 --- a/tests/test_lists.rs +++ b/tests/test_lists.rs @@ -235,63 +235,6 @@ fn test_list_atomics() -> Result<()> { }) } -#[test] -fn test_queues() -> Result<()> { - run_in_tempdir(|dir| { - let db = CandyStore::open(dir, Config::default())?; - - db.push_to_list_tail("mylist", "hello1")?; - db.push_to_list_tail("mylist", "hello2")?; - db.push_to_list_tail("mylist", "hello3")?; - db.push_to_list_tail("mylist", "hello4")?; - - let mut items = vec![]; - while let Some((_uuid, v)) = db.pop_list_head("mylist")? { - items.push(v); - } - assert_eq!(items, vec![b"hello1", b"hello2", b"hello3", b"hello4"]); - - db.push_to_list_tail("mylist", "hello5")?; - db.push_to_list_tail("mylist", "hello6")?; - db.push_to_list_tail("mylist", "hello7")?; - db.push_to_list_tail("mylist", "hello8")?; - - let mut items = vec![]; - while let Some((_uuid, v)) = db.pop_list_tail("mylist")? { - items.push(v); - } - assert_eq!(items, vec![b"hello8", b"hello7", b"hello6", b"hello5"]); - - db.push_to_list_tail("mylist", "hello9")?; - db.push_to_list_tail("mylist", "hello10")?; - db.push_to_list_tail("mylist", "hello11")?; - db.push_to_list_tail("mylist", "hello12")?; - db.push_to_list_tail("mylist", "hello13")?; - db.push_to_list_tail("mylist", "hello14")?; - - assert_eq!(db.pop_list_head("mylist")?.unwrap().1, b"hello9"); - assert_eq!(db.pop_list_tail("mylist")?.unwrap().1, b"hello14"); - assert_eq!(db.pop_list_head("mylist")?.unwrap().1, b"hello10"); - assert_eq!(db.pop_list_tail("mylist")?.unwrap().1, b"hello13"); - assert_eq!(db.pop_list_head("mylist")?.unwrap().1, b"hello11"); - assert_eq!(db.pop_list_head("mylist")?.unwrap().1, b"hello12"); - assert_eq!(db.pop_list_head("mylist")?, None); - - db.push_to_list_head("mylist", "hello15")?; - db.push_to_list_head("mylist", "hello16")?; - db.push_to_list_head("mylist", "hello17")?; - db.push_to_list_head("mylist", "hello18")?; - - assert_eq!(db.pop_list_head("mylist")?.unwrap().1, b"hello18"); - assert_eq!(db.pop_list_head("mylist")?.unwrap().1, b"hello17"); - assert_eq!(db.pop_list_head("mylist")?.unwrap().1, b"hello16"); - assert_eq!(db.pop_list_head("mylist")?.unwrap().1, b"hello15"); - assert_eq!(db.pop_list_head("mylist")?, None); - - Ok(()) - }) -} - #[test] fn test_typed_queue() -> Result<()> { run_in_tempdir(|dir| {