use std::borrow::Borrow;
use std::marker::PhantomData;
use cid::Cid;
use forest_hash_utils::BytesKey;
use fvm_ipld_blockstore::Blockstore;
use fvm_ipld_encoding::CborStore;
use multihash_codetable::Code;
use serde::de::DeserializeOwned;
use serde::{Serialize, Serializer};
use crate::iter::IterImpl;
use crate::node::Node;
use crate::pointer::version::Version;
use crate::{Config, Error, Hash, HashAlgorithm, Sha256, pointer::version};
pub type Hamt<BS, V, K = BytesKey, H = Sha256> = HamtImpl<BS, V, K, H, version::V3>;
pub type Hamtv0<BS, V, K = BytesKey, H = Sha256> = HamtImpl<BS, V, K, H, version::V0>;
#[derive(Debug)]
#[doc(hidden)]
pub struct HamtImpl<BS, V, K = BytesKey, H = Sha256, Ver = version::V3> {
root: Node<K, V, H, Ver>,
store: BS,
conf: Config,
hash: PhantomData<H>,
flushed_cid: Option<Cid>,
}
impl<BS, V, K, H, Ver> Serialize for HamtImpl<BS, V, K, H, Ver>
where
K: Serialize,
V: Serialize,
H: HashAlgorithm,
Ver: Version,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.root.serialize(serializer)
}
}
impl<K: PartialEq, V: PartialEq, S: Blockstore, H: HashAlgorithm, Ver> PartialEq
for HamtImpl<S, V, K, H, Ver>
{
fn eq(&self, other: &Self) -> bool {
self.root == other.root
}
}
impl<BS, V, K, H, Ver> HamtImpl<BS, V, K, H, Ver>
where
K: Hash + Eq + PartialOrd + Serialize + DeserializeOwned,
V: Serialize + DeserializeOwned,
BS: Blockstore,
Ver: Version,
H: HashAlgorithm,
{
#[deprecated = "specify a bit-width explicitly"]
pub fn new(store: BS) -> Self {
Self::new_with_config(store, Config::default())
}
pub fn new_with_config(store: BS, conf: Config) -> Self {
Self {
root: Node::default(),
store,
conf,
hash: Default::default(),
flushed_cid: None,
}
}
pub fn new_with_bit_width(store: BS, bit_width: u32) -> Self {
Self::new_with_config(
store,
Config {
bit_width,
..Default::default()
},
)
}
#[deprecated = "specify a bit-width explicitly"]
pub fn load(cid: &Cid, store: BS) -> Result<Self, Error> {
Self::load_with_config(cid, store, Config::default())
}
pub fn load_with_config(cid: &Cid, store: BS, conf: Config) -> Result<Self, Error> {
Ok(Self {
root: Node::load(&conf, &store, cid, 0)?,
store,
conf,
hash: Default::default(),
flushed_cid: Some(*cid),
})
}
pub fn load_with_bit_width(cid: &Cid, store: BS, bit_width: u32) -> Result<Self, Error> {
Self::load_with_config(
cid,
store,
Config {
bit_width,
..Default::default()
},
)
}
pub fn set_root(&mut self, cid: &Cid) -> Result<(), Error> {
self.root = Node::load(&self.conf, &self.store, cid, 0)?;
self.flushed_cid = Some(*cid);
Ok(())
}
pub fn store(&self) -> &BS {
&self.store
}
pub fn set(&mut self, key: K, value: V) -> Result<Option<V>, Error>
where
V: PartialEq,
{
let (old, modified) = self
.root
.set(key, value, self.store.borrow(), &self.conf, true)?;
if modified {
self.flushed_cid = None;
}
Ok(old)
}
pub fn set_if_absent(&mut self, key: K, value: V) -> Result<bool, Error>
where
V: PartialEq,
{
let set = self
.root
.set(key, value, self.store.borrow(), &self.conf, false)
.map(|(_, set)| set)?;
if set {
self.flushed_cid = None;
}
Ok(set)
}
#[inline]
pub fn get<Q>(&self, k: &Q) -> Result<Option<&V>, Error>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
V: DeserializeOwned,
{
match self.root.get(k, self.store.borrow(), &self.conf)? {
Some(v) => Ok(Some(v)),
None => Ok(None),
}
}
#[inline]
pub fn contains_key<Q>(&self, k: &Q) -> Result<bool, Error>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
Ok(self.root.get(k, self.store.borrow(), &self.conf)?.is_some())
}
pub fn delete<Q>(&mut self, k: &Q) -> Result<Option<(K, V)>, Error>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let deleted = self.root.remove_entry(k, self.store.borrow(), &self.conf)?;
if deleted.is_some() {
self.flushed_cid = None;
}
Ok(deleted)
}
pub fn flush(&mut self) -> Result<Cid, Error> {
if let Some(cid) = self.flushed_cid {
return Ok(cid);
}
self.root.flush(self.store.borrow())?;
let cid = self.store.put_cbor(&self.root, Code::Blake2b256)?;
self.flushed_cid = Some(cid);
Ok(cid)
}
pub fn is_empty(&self) -> bool {
self.root.is_empty()
}
pub fn clear(&mut self) {
if self.is_empty() {
return; }
self.root = Node::default(); self.flushed_cid = None; }
#[inline]
pub fn for_each<F>(&self, mut f: F) -> Result<(), Error>
where
V: DeserializeOwned,
F: FnMut(&K, &V) -> anyhow::Result<()>,
{
for res in self {
let (k, v) = res?;
(f)(k, v)?;
}
Ok(())
}
pub fn for_each_cacheless<F>(&self, mut f: F) -> Result<(), Error>
where
V: DeserializeOwned,
F: FnMut(&K, &V) -> anyhow::Result<()>,
{
self.root
.for_each_cacheless(&self.store, &self.conf, &mut f)
}
#[inline]
pub fn for_each_ranged<Q, F>(
&self,
starting_key: Option<&Q>,
max: Option<usize>,
mut f: F,
) -> Result<(usize, Option<K>), Error>
where
K: Borrow<Q> + Clone,
Q: Eq + Hash + ?Sized,
V: DeserializeOwned,
F: FnMut(&K, &V) -> anyhow::Result<()>,
{
let mut iter = match &starting_key {
Some(key) => self.iter_from(key)?,
None => self.iter(),
}
.fuse();
let mut traversed = 0usize;
for res in iter.by_ref().take(max.unwrap_or(usize::MAX)) {
let (k, v) = res?;
(f)(k, v)?;
traversed += 1;
}
let next = iter.next().transpose()?.map(|kv| kv.0).cloned();
Ok((traversed, next))
}
pub fn into_store(self) -> BS {
self.store
}
}
impl<BS, V, K, H, Ver> HamtImpl<BS, V, K, H, Ver>
where
K: DeserializeOwned + PartialOrd,
V: DeserializeOwned,
Ver: Version,
BS: Blockstore,
{
pub fn iter(&self) -> IterImpl<'_, BS, V, K, H, Ver> {
IterImpl::new(&self.store, &self.root, &self.conf)
}
pub fn iter_from<Q>(&self, key: &Q) -> Result<IterImpl<'_, BS, V, K, H, Ver>, Error>
where
H: HashAlgorithm,
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
IterImpl::new_from(&self.store, &self.root, key, &self.conf)
}
}
impl<'a, BS, V, K, H, Ver> IntoIterator for &'a HamtImpl<BS, V, K, H, Ver>
where
K: DeserializeOwned + PartialOrd,
V: DeserializeOwned,
Ver: Version,
BS: Blockstore,
{
type Item = Result<(&'a K, &'a V), Error>;
type IntoIter = IterImpl<'a, BS, V, K, H, Ver>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
use fvm_ipld_blockstore::MemoryBlockstore;
#[test]
fn test_clear() {
let store = MemoryBlockstore::default();
let mut hamt: Hamt<_, _, usize> = Hamt::new_with_config(store, Config::default());
assert!(hamt.is_empty());
hamt.clear();
assert!(hamt.is_empty());
hamt.set(1, "a".to_string()).unwrap();
hamt.set(2, "b".to_string()).unwrap();
assert_eq!(hamt.get(&1).unwrap(), Some(&"a".to_string()));
assert_eq!(hamt.get(&2).unwrap(), Some(&"b".to_string()));
assert!(!hamt.is_empty());
hamt.clear();
assert!(hamt.is_empty());
assert_eq!(hamt.get(&1).unwrap(), None);
assert_eq!(hamt.get(&2).unwrap(), None);
hamt.set(3, "c".to_string()).unwrap();
assert_eq!(hamt.get(&3).unwrap(), Some(&"c".to_string()));
}
}