diff --git a/contracts/cw721-base/src/contract.rs b/contracts/cw721-base/src/contract.rs index c166c7cec..b43b035d6 100644 --- a/contracts/cw721-base/src/contract.rs +++ b/contracts/cw721-base/src/contract.rs @@ -15,7 +15,7 @@ use crate::msg::{HandleMsg, InitMsg, MintMsg, MinterResponse, QueryMsg}; use crate::state::{ increment_tokens, num_tokens, tokens, Approval, TokenInfo, CONTRACT_INFO, MINTER, OPERATORS, }; -use cw_storage_plus::Bound; +use cw_storage_plus::{Bound, PkOwned}; // version info for migration info const CONTRACT_NAME: &str = "crates.io:cw721-base"; @@ -497,14 +497,22 @@ fn query_tokens( let start = start_after.map(Bound::exclusive); let owner_raw = deps.api.canonical_address(&owner)?; - let tokens: Result, _> = tokens() + let res: Result, _> = tokens() .idx .owner - .pks(deps.storage, &owner_raw, start, None, Order::Ascending) + .pks( + deps.storage, + PkOwned(owner_raw.into()), + start, + None, + Order::Ascending, + ) .take(limit) - .map(String::from_utf8) .collect(); - let tokens = tokens.map_err(StdError::invalid_utf8)?; + let pks = res?; + + let res: Result, _> = pks.iter().map(|v| String::from_utf8(v.to_vec())).collect(); + let tokens = res.map_err(StdError::invalid_utf8)?; Ok(TokensResponse { tokens }) } diff --git a/contracts/cw721-base/src/state.rs b/contracts/cw721-base/src/state.rs index a18931bb8..fd9910b26 100644 --- a/contracts/cw721-base/src/state.rs +++ b/contracts/cw721-base/src/state.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use cosmwasm_std::{CanonicalAddr, StdResult, Storage}; use cw721::{ContractInfoResponse, Expiration}; -use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex}; +use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex, PkOwned}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct TokenInfo { @@ -46,7 +46,8 @@ pub fn increment_tokens(storage: &mut dyn Storage) -> StdResult { } pub struct TokenIndexes<'a> { - pub owner: MultiIndex<'a, TokenInfo>, + // pk goes to second tuple element + pub owner: MultiIndex<'a, (PkOwned, PkOwned), TokenInfo>, } impl<'a> IndexList for TokenIndexes<'a> { @@ -58,7 +59,11 @@ impl<'a> IndexList for TokenIndexes<'a> { pub fn tokens<'a>() -> IndexedMap<'a, &'a str, TokenInfo, TokenIndexes<'a>> { let indexes = TokenIndexes { - owner: MultiIndex::new(|d| d.owner.to_vec(), "tokens", "tokens__owner"), + owner: MultiIndex::new( + |d, k| (PkOwned(d.owner.to_vec()), PkOwned(k)), + "tokens", + "tokens__owner", + ), }; IndexedMap::new("tokens", indexes) } diff --git a/packages/storage-plus/src/indexed_map.rs b/packages/storage-plus/src/indexed_map.rs index 71e0abade..e7f5f30aa 100644 --- a/packages/storage-plus/src/indexed_map.rs +++ b/packages/storage-plus/src/indexed_map.rs @@ -161,7 +161,7 @@ where mod test { use super::*; - use crate::indexes::{index_string, index_string_tuple, MultiIndex, UniqueIndex}; + use crate::indexes::{index_string_tuple, index_triple, MultiIndex, UniqueIndex}; use crate::{PkOwned, U32Key}; use cosmwasm_std::testing::MockStorage; use cosmwasm_std::{MemoryStorage, Order}; @@ -175,7 +175,8 @@ mod test { } struct DataIndexes<'a> { - pub name: MultiIndex<'a, Data>, + // Second arg is for storing pk + pub name: MultiIndex<'a, (PkOwned, PkOwned), Data>, pub age: UniqueIndex<'a, U32Key, Data>, pub name_lastname: UniqueIndex<'a, (PkOwned, PkOwned), Data>, } @@ -188,10 +189,28 @@ mod test { } } + // For composite multi index tests + struct DataCompositeMultiIndex<'a> { + // Third arg needed for storing pk + pub name_age: MultiIndex<'a, (PkOwned, U32Key, PkOwned), Data>, + } + + // Future Note: this can likely be macro-derived + impl<'a> IndexList for DataCompositeMultiIndex<'a> { + fn get_indexes(&'_ self) -> Box> + '_> { + let v: Vec<&dyn Index> = vec![&self.name_age]; + Box::new(v.into_iter()) + } + } + // Can we make it easier to define this? (less wordy generic) fn build_map<'a>() -> IndexedMap<'a, &'a [u8], Data, DataIndexes<'a>> { let indexes = DataIndexes { - name: MultiIndex::new(|d| index_string(&d.name), "data", "data__name"), + name: MultiIndex::new( + |d, k| (PkOwned(d.name.as_bytes().to_vec()), PkOwned(k)), + "data", + "data__name", + ), age: UniqueIndex::new(|d| U32Key::new(d.age), "data__age"), name_lastname: UniqueIndex::new( |d| index_string_tuple(&d.name, &d.last_name), @@ -269,19 +288,22 @@ mod test { let count = map .idx .name - .all_items(&store, &index_string("Maria")) - .unwrap() + .prefix(PkOwned(b"Maria".to_vec())) + .range(&store, None, None, Order::Ascending) + .collect::>() .len(); assert_eq!(2, count); // TODO: we load by wrong keys - get full storage key! // load it by secondary index (we must know how to compute this) - // let marias: StdResult> = map - let marias = map + // let marias: Vec<_>> = map + let marias: Vec<_> = map .idx .name - .all_items(&store, &index_string("Maria")) + .prefix(PkOwned(b"Maria".to_vec())) + .range(&store, None, None, Order::Ascending) + .collect::>() .unwrap(); assert_eq!(2, marias.len()); let (k, v) = &marias[0]; @@ -292,8 +314,9 @@ mod test { let count = map .idx .name - .all_items(&store, &index_string("Marib")) - .unwrap() + .prefix(PkOwned(b"Marib".to_vec())) + .range(&store, None, None, Order::Ascending) + .collect::>() .len(); assert_eq!(0, count); @@ -301,8 +324,9 @@ mod test { let count = map .idx .name - .all_items(&store, &index_string("Mari`")) - .unwrap() + .prefix(PkOwned(b"Mari`".to_vec())) + .range(&store, None, None, Order::Ascending) + .collect::>() .len(); assert_eq!(0, count); @@ -310,8 +334,9 @@ mod test { let count = map .idx .name - .all_items(&store, &index_string("Maria5")) - .unwrap() + .prefix(PkOwned(b"Maria5".to_vec())) + .range(&store, None, None, Order::Ascending) + .collect::>() .len(); assert_eq!(0, count); @@ -327,6 +352,127 @@ mod test { assert_eq!(None, aged); } + #[test] + fn range_simple_key_by_multi_index() { + let mut store = MockStorage::new(); + let map = build_map(); + + // save data + let data1 = Data { + name: "Maria".to_string(), + last_name: "".to_string(), + age: 42, + }; + let pk: &[u8] = b"5627"; + map.save(&mut store, pk, &data1).unwrap(); + + let data2 = Data { + name: "Juan".to_string(), + last_name: "Perez".to_string(), + age: 13, + }; + let pk: &[u8] = b"5628"; + map.save(&mut store, pk, &data2).unwrap(); + + let data3 = Data { + name: "Maria".to_string(), + last_name: "Williams".to_string(), + age: 24, + }; + let pk: &[u8] = b"5629"; + map.save(&mut store, pk, &data3).unwrap(); + + let data4 = Data { + name: "Maria Luisa".to_string(), + last_name: "Bemberg".to_string(), + age: 12, + }; + let pk: &[u8] = b"5630"; + map.save(&mut store, pk, &data4).unwrap(); + + let marias: Vec<_> = map + .idx + .name + .prefix(PkOwned(b"Maria".to_vec())) + .range(&store, None, None, Order::Descending) + .collect::>() + .unwrap(); + let count = marias.len(); + assert_eq!(2, count); + + // Sorted by (descending) pk + assert_eq!(marias[0].0, b"5629"); + assert_eq!(marias[1].0, b"5627"); + // Data is correct + assert_eq!(marias[0].1, data3); + assert_eq!(marias[1].1, data1); + } + + #[test] + fn range_composite_key_by_multi_index() { + let mut store = MockStorage::new(); + + let indexes = DataCompositeMultiIndex { + name_age: MultiIndex::new( + |d, k| index_triple(&d.name, d.age, k), + "data", + "data__name_age", + ), + }; + let map = IndexedMap::new("data", indexes); + + // save data + let data1 = Data { + name: "Maria".to_string(), + last_name: "".to_string(), + age: 42, + }; + let pk1: &[u8] = b"5627"; + map.save(&mut store, pk1, &data1).unwrap(); + + let data2 = Data { + name: "Juan".to_string(), + last_name: "Perez".to_string(), + age: 13, + }; + let pk2: &[u8] = b"5628"; + map.save(&mut store, pk2, &data2).unwrap(); + + let data3 = Data { + name: "Maria".to_string(), + last_name: "Young".to_string(), + age: 24, + }; + let pk3: &[u8] = b"5629"; + map.save(&mut store, pk3, &data3).unwrap(); + + let data4 = Data { + name: "Maria Luisa".to_string(), + last_name: "Bemberg".to_string(), + age: 43, + }; + let pk4: &[u8] = b"5630"; + map.save(&mut store, pk4, &data4).unwrap(); + + let marias: Vec<_> = map + .idx + .name_age + .sub_prefix(PkOwned(b"Maria".to_vec())) + .range(&store, None, None, Order::Descending) + .collect::>() + .unwrap(); + let count = marias.len(); + assert_eq!(2, count); + + // Pks (sorted by age descending) + assert_eq!(pk1, marias[0].0); + assert_eq!(pk3, marias[1].0); + + // Data + assert_eq!(data1, marias[0].1); + assert_eq!(data3, marias[1].1); + } + #[test] fn unique_index_enforced() { let mut store = MockStorage::new(); @@ -401,7 +547,13 @@ mod test { -> usize { map.idx .name - .pks(store, &index_string(name), None, None, Order::Ascending) + .pks( + store, + PkOwned(name.as_bytes().to_vec()), + None, + None, + Order::Ascending, + ) .count() }; @@ -436,10 +588,10 @@ mod test { #[test] fn unique_index_simple_key_range() { let mut store = MockStorage::new(); - let mut map = build_map(); + let map = build_map(); // save data - let (pks, datas) = save_data(&mut store, &mut map); + let (pks, datas) = save_data(&mut store, &map); let res: StdResult> = map .idx @@ -467,10 +619,10 @@ mod test { #[test] fn unique_index_composite_key_range() { let mut store = MockStorage::new(); - let mut map = build_map(); + let map = build_map(); // save data - let (pks, datas) = save_data(&mut store, &mut map); + let (pks, datas) = save_data(&mut store, &map); let res: StdResult> = map .idx diff --git a/packages/storage-plus/src/indexes.rs b/packages/storage-plus/src/indexes.rs index edbfa59de..5491f4938 100644 --- a/packages/storage-plus/src/indexes.rs +++ b/packages/storage-plus/src/indexes.rs @@ -6,28 +6,27 @@ use serde::{Deserialize, Serialize}; use cosmwasm_std::{from_slice, Binary, Order, StdError, StdResult, Storage, KV}; +use crate::helpers::namespaces_with_key; use crate::keys::EmptyPrefix; use crate::map::Map; -use crate::prefix::range_with_prefix; -use crate::{Bound, PkOwned, Prefix, Prefixer, PrimaryKey}; +use crate::{Bound, PkOwned, Prefix, Prefixer, PrimaryKey, U32Key}; -/// MARKER is stored in the multi-index as value, but we only look at the key (which is pk) -const MARKER: u32 = 1; +pub fn index_string(data: &str) -> PkOwned { + PkOwned(data.as_bytes().to_vec()) +} + +pub fn index_tuple(name: &str, age: u32) -> (PkOwned, U32Key) { + (index_string(name), U32Key::new(age)) +} -pub fn index_string(data: &str) -> Vec { - data.as_bytes().to_vec() +pub fn index_triple(name: &str, age: u32, pk: Vec) -> (PkOwned, U32Key, PkOwned) { + (index_string(name), U32Key::new(age), PkOwned(pk)) } pub fn index_string_tuple(data1: &str, data2: &str) -> (PkOwned, PkOwned) { - (PkOwned(index_string(data1)), PkOwned(index_string(data2))) + (index_string(data1), index_string(data2)) } -// 2 main variants: -// * store (namespace, index_name, idx_value, key) -> b"1" - allows many and references pk -// * store (namespace, index_name, idx_value) -> {key, value} - allows one and copies pk and data -// // this would be the primary key - we abstract that too??? -// * store (namespace, index_name, pk) -> value - allows one with data -// // Note: we cannot store traits with generic functions inside `Box`, // so I pull S: Storage to a top-level pub trait Index @@ -38,90 +37,174 @@ where fn remove(&self, store: &mut dyn Storage, pk: &[u8], old_data: &T) -> StdResult<()>; } -pub struct MultiIndex<'a, T> { - index: fn(&T) -> Vec, - idx_map: Map<'a, (&'a [u8], &'a [u8]), u32>, - // note, we collapse the pk - combining everything under the namespace - even if it is composite - pk_map: Map<'a, &'a [u8], T>, +/// MultiIndex stores (namespace, index_name, idx_value, pk) -> b"pk_len". +/// Allows many values per index, and references pk. +/// The associated primary key value is stored in the main (pk_namespace) map, +/// which stores (namespace, pk_namespace, pk) -> value. +/// +/// The stored pk_len is used to recover the pk from the index namespace, and perform +/// the secondary load of the associated value from the main map. +/// +/// The MultiIndex definition must include a field for the pk. That is, the MultiIndex K value +/// is always a n-tuple (n >= 2) and its last element must be the pk. +/// The index function must therefore put the pk as last element, when generating the index. +pub struct MultiIndex<'a, K, T> { + index: fn(&T, Vec) -> K, + idx_namespace: &'a [u8], + idx_map: Map<'a, K, u32>, + pk_namespace: &'a [u8], } -impl<'a, T> MultiIndex<'a, T> { +impl<'a, K, T> MultiIndex<'a, K, T> +where + T: Serialize + DeserializeOwned + Clone, +{ // TODO: make this a const fn - pub fn new(idx_fn: fn(&T) -> Vec, pk_namespace: &'a str, idx_namespace: &'a str) -> Self { + pub fn new( + idx_fn: fn(&T, Vec) -> K, + pk_namespace: &'a str, + idx_namespace: &'a str, + ) -> Self { MultiIndex { index: idx_fn, - pk_map: Map::new(pk_namespace), + idx_namespace: idx_namespace.as_bytes(), idx_map: Map::new(idx_namespace), + pk_namespace: pk_namespace.as_bytes(), } } } -impl<'a, T> Index for MultiIndex<'a, T> +fn deserialize_multi_kv( + store: &dyn Storage, + pk_namespace: &[u8], + kv: KV, +) -> StdResult> { + let (key, pk_len) = kv; + + // Deserialize pk_len + let pk_len = from_slice::(pk_len.as_slice())?; + + // Recover pk from last part of k + let offset = key.len() - pk_len as usize; + let pk = &key[offset..]; + + let full_key = namespaces_with_key(&[pk_namespace], pk); + + let v = store + .get(&full_key) + .ok_or_else(|| StdError::generic_err("pk not found"))?; + let v = from_slice::(&v)?; + + Ok((pk.into(), v)) +} + +impl<'a, K, T> Index for MultiIndex<'a, K, T> where T: Serialize + DeserializeOwned + Clone, + K: PrimaryKey<'a>, { fn save(&self, store: &mut dyn Storage, pk: &[u8], data: &T) -> StdResult<()> { - let idx = (self.index)(data); - self.idx_map.save(store, (&idx, &pk), &MARKER) + let idx = (self.index)(data, pk.to_vec()); + self.idx_map.save(store, idx, &(pk.len() as u32)) } fn remove(&self, store: &mut dyn Storage, pk: &[u8], old_data: &T) -> StdResult<()> { - let idx = (self.index)(old_data); - self.idx_map.remove(store, (&idx, &pk)); + let idx = (self.index)(old_data, pk.to_vec()); + self.idx_map.remove(store, idx); Ok(()) } } -impl<'a, T> MultiIndex<'a, T> +impl<'a, K, T> MultiIndex<'a, K, T> where T: Serialize + DeserializeOwned + Clone, + K: PrimaryKey<'a>, { - pub fn pks<'c>( - &self, - store: &'c dyn Storage, - idx: &[u8], - min: Option, - max: Option, - order: Order, - ) -> Box> + 'c> { - let prefix = self.idx_map.prefix(idx); - let mapped = range_with_prefix(store, &prefix, min, max, order).map(|(k, _)| k); - Box::new(mapped) + pub fn prefix(&self, p: K::Prefix) -> Prefix { + Prefix::with_deserialization_function( + self.idx_namespace, + &p.prefix(), + self.pk_namespace, + deserialize_multi_kv, + ) } - /// returns all items that match this secondary index, always by pk Ascending - pub fn items<'c>( - &'c self, + pub fn sub_prefix(&self, p: K::SubPrefix) -> Prefix { + Prefix::with_deserialization_function( + self.idx_namespace, + &p.prefix(), + self.pk_namespace, + deserialize_multi_kv, + ) + } + + // FIXME?: Move to Prefix for ergonomics + pub fn pks<'c>( + &self, store: &'c dyn Storage, - idx: &[u8], + p: K::Prefix, min: Option, max: Option, order: Order, - ) -> Box>> + 'c> { - let mapped = self.pks(store, idx, min, max, order).map(move |pk| { - let v = self.pk_map.load(store, &pk)?; - Ok((pk, v)) + ) -> Box>> + 'c> + where + T: 'c, + { + let prefix = self.prefix(p); + let mapped = prefix.range(store, min, max, order).map(|res| { + let t = res?; + Ok(t.0) }); Box::new(mapped) } #[cfg(test)] - pub fn count<'c>(&self, store: &'c dyn Storage, idx: &[u8]) -> usize { - self.pks(store, idx, None, None, Order::Ascending).count() + pub fn count<'c>(&self, store: &'c dyn Storage, p: K::Prefix) -> usize { + self.pks(store, p, None, None, Order::Ascending).count() } #[cfg(test)] - pub fn all_pks<'c>(&self, store: &'c dyn Storage, idx: &[u8]) -> Vec> { - self.pks(store, idx, None, None, Order::Ascending).collect() + pub fn all_pks<'c>(&self, store: &'c dyn Storage, p: K::Prefix) -> Vec> { + self.pks(store, p, None, None, Order::Ascending) + .collect::>>>() + .unwrap() } #[cfg(test)] - pub fn all_items<'c>(&self, store: &'c dyn Storage, idx: &[u8]) -> StdResult>> { - self.items(store, idx, None, None, Order::Ascending) + pub fn all_items<'c>( + &self, + store: &'c dyn Storage, + prefix: K::Prefix, + ) -> StdResult>> { + self.prefix(prefix) + .range(store, None, None, Order::Ascending) .collect() } } +// short-cut for simple keys, rather than .prefix(()).range(...) +impl<'a, K, T> MultiIndex<'a, K, T> +where + T: Serialize + DeserializeOwned + Clone, + K: PrimaryKey<'a>, + K::Prefix: EmptyPrefix, +{ + // I would prefer not to copy code from Prefix, but no other way + // with lifetimes (create Prefix inside function and return ref = no no) + pub fn range<'c>( + &'c self, + store: &'c dyn Storage, + min: Option, + max: Option, + order: Order, + ) -> Box>> + 'c> + where + T: 'c, + { + self.prefix(K::Prefix::new()).range(store, min, max, order) + } +} + #[derive(Deserialize, Serialize)] pub(crate) struct UniqueRef { // note, we collapse the pk - combining everything under the namespace - even if it is composite @@ -129,6 +212,8 @@ pub(crate) struct UniqueRef { value: T, } +/// UniqueIndex stores (namespace, index_name, idx_value) -> {key, value} +/// Allows one value per index (i.e. unique) and copies pk and data pub struct UniqueIndex<'a, K, T> { index: fn(&T) -> K, idx_map: Map<'a, K, UniqueRef>, @@ -186,11 +271,15 @@ where K: PrimaryKey<'a>, { pub fn prefix(&self, p: K::Prefix) -> Prefix { - Prefix::new_de_fn(self.idx_namespace, &p.prefix(), deserialize_unique_kv) + Prefix::with_deserialization_function(self.idx_namespace, &p.prefix(), &[], |_, _, kv| { + deserialize_unique_kv(kv) + }) } pub fn sub_prefix(&self, p: K::SubPrefix) -> Prefix { - Prefix::new_de_fn(self.idx_namespace, &p.prefix(), deserialize_unique_kv) + Prefix::with_deserialization_function(self.idx_namespace, &p.prefix(), &[], |_, _, kv| { + deserialize_unique_kv(kv) + }) } /// returns all items that match this secondary index, always by pk Ascending @@ -217,7 +306,7 @@ where store: &'c dyn Storage, min: Option, max: Option, - order: cosmwasm_std::Order, + order: Order, ) -> Box>> + 'c> where T: 'c, diff --git a/packages/storage-plus/src/lib.rs b/packages/storage-plus/src/lib.rs index b012f4aed..a795edce7 100644 --- a/packages/storage-plus/src/lib.rs +++ b/packages/storage-plus/src/lib.rs @@ -14,7 +14,9 @@ pub use endian::Endian; #[cfg(feature = "iterator")] pub use indexed_map::{IndexList, IndexedMap}; #[cfg(feature = "iterator")] -pub use indexes::{index_string, index_string_tuple, Index, MultiIndex, UniqueIndex}; +pub use indexes::{ + index_string, index_string_tuple, index_triple, index_tuple, Index, MultiIndex, UniqueIndex, +}; pub use item::Item; pub use keys::{I128Key, I16Key, I32Key, I64Key, I8Key}; pub use keys::{PkOwned, Prefixer, PrimaryKey, U128Key, U16Key, U32Key, U64Key, U8Key}; diff --git a/packages/storage-plus/src/map.rs b/packages/storage-plus/src/map.rs index 4e4c81f0c..79e18fd19 100644 --- a/packages/storage-plus/src/map.rs +++ b/packages/storage-plus/src/map.rs @@ -252,8 +252,40 @@ mod test { assert_eq!(2, all.len()); assert_eq!( all, - vec![(b"jim".to_vec(), data2), (b"john".to_vec(), data)] + vec![ + (b"jim".to_vec(), data2.clone()), + (b"john".to_vec(), data.clone()) + ] ); + + // let's try to iterate over a range + let all: StdResult> = PEOPLE + .range( + &store, + Some(Bound::Inclusive(b"j".to_vec())), + None, + Order::Ascending, + ) + .collect(); + let all = all.unwrap(); + assert_eq!(2, all.len()); + assert_eq!( + all, + vec![(b"jim".to_vec(), data2), (b"john".to_vec(), data.clone())] + ); + + // let's try to iterate over a more restrictive range + let all: StdResult> = PEOPLE + .range( + &store, + Some(Bound::Inclusive(b"jo".to_vec())), + None, + Order::Ascending, + ) + .collect(); + let all = all.unwrap(); + assert_eq!(1, all.len()); + assert_eq!(all, vec![(b"john".to_vec(), data)]); } #[test] diff --git a/packages/storage-plus/src/prefix.rs b/packages/storage-plus/src/prefix.rs index 854d29f85..c544d89e6 100644 --- a/packages/storage-plus/src/prefix.rs +++ b/packages/storage-plus/src/prefix.rs @@ -42,7 +42,9 @@ impl Bound { } } -#[derive(Debug, Clone)] +type DeserializeFn = fn(&dyn Storage, &[u8], KV) -> StdResult>; + +#[derive(Clone)] pub struct Prefix where T: Serialize + DeserializeOwned, @@ -51,7 +53,8 @@ where storage_prefix: Vec, // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed data: PhantomData, - de_fn: fn(KV) -> StdResult>, + pk_name: Vec, + de_fn: DeserializeFn, } impl Deref for Prefix @@ -70,19 +73,23 @@ where T: Serialize + DeserializeOwned, { pub fn new(top_name: &[u8], sub_names: &[&[u8]]) -> Self { - Prefix::new_de_fn(top_name, sub_names, deserialize_kv) + Prefix::with_deserialization_function(top_name, sub_names, &[], |_, _, kv| { + deserialize_kv(kv) + }) } - pub fn new_de_fn( + pub fn with_deserialization_function( top_name: &[u8], sub_names: &[&[u8]], - de_fn: fn(KV) -> StdResult>, + pk_name: &[u8], + de_fn: DeserializeFn, ) -> Self { // FIXME: we can use a custom function here, probably make this cleaner let storage_prefix = nested_namespaces_with_key(&[top_name], sub_names, b""); Prefix { storage_prefix, data: PhantomData, + pk_name: pk_name.to_vec(), de_fn, } } @@ -97,8 +104,10 @@ where where T: 'a, { - let mapped = - range_with_prefix(store, &self.storage_prefix, min, max, order).map(self.de_fn); + let de_fn = self.de_fn; + let pk_name = self.pk_name.clone(); + let mapped = range_with_prefix(store, &self.storage_prefix, min, max, order) + .map(move |kv| (de_fn)(store, &*pk_name, kv)); Box::new(mapped) } } @@ -175,7 +184,8 @@ mod test { let prefix = Prefix { storage_prefix: b"foo".to_vec(), data: PhantomData::, - de_fn: deserialize_kv, + pk_name: vec![], + de_fn: |_, _, kv| deserialize_kv(kv), }; // set some data, we care about "foo" prefix