From 0e7399c939ae51e4e6c8dec96ecca1e0d280e7b7 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 22 Dec 2020 15:40:23 +0100 Subject: [PATCH 01/13] Generalize UniqueIndex keys --- packages/storage-plus/src/indexed_map.rs | 15 ++++++----- packages/storage-plus/src/indexes.rs | 33 ++++++++++++++---------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/packages/storage-plus/src/indexed_map.rs b/packages/storage-plus/src/indexed_map.rs index 7d08d0111..4867c7608 100644 --- a/packages/storage-plus/src/indexed_map.rs +++ b/packages/storage-plus/src/indexed_map.rs @@ -157,6 +157,7 @@ mod test { use super::*; use crate::indexes::{index_int, index_string, MultiIndex, UniqueIndex}; + use crate::U32Key; use cosmwasm_std::testing::MockStorage; use cosmwasm_std::{MemoryStorage, Order}; use serde::{Deserialize, Serialize}; @@ -169,7 +170,7 @@ mod test { struct DataIndexes<'a> { pub name: MultiIndex<'a, Data>, - pub age: UniqueIndex<'a, Data>, + pub age: UniqueIndex<'a, U32Key, Data>, } // Future Note: this can likely be macro-derived @@ -184,7 +185,7 @@ mod test { 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"), - age: UniqueIndex::new(|d| index_int(d.age), "data__age"), + age: UniqueIndex::new(|d| index_int(d.age as u32), "data__age"), }; IndexedMap::new("data", indexes) } @@ -257,13 +258,13 @@ mod test { // match on proper age let proper = index_int(42); - let aged = map.idx.age.item(&store, &proper).unwrap().unwrap(); + let aged = map.idx.age.item(&store, proper).unwrap().unwrap(); assert_eq!(pk.to_vec(), aged.0); assert_eq!(data, aged.1); // no match on wrong age let too_old = index_int(43); - let aged = map.idx.age.item(&store, &too_old).unwrap(); + let aged = map.idx.age.item(&store, too_old).unwrap(); assert_eq!(None, aged); } @@ -300,14 +301,14 @@ mod test { // query by unique key // match on proper age let age42 = index_int(42); - let (k, v) = map.idx.age.item(&store, &age42).unwrap().unwrap(); + let (k, v) = map.idx.age.item(&store, age42.clone()).unwrap().unwrap(); assert_eq!(k.as_slice(), pk1); assert_eq!(&v.name, "Maria"); assert_eq!(v.age, 42); // match on other age let age23 = index_int(23); - let (k, v) = map.idx.age.item(&store, &age23).unwrap().unwrap(); + let (k, v) = map.idx.age.item(&store, age23).unwrap().unwrap(); assert_eq!(k.as_slice(), pk2); assert_eq!(&v.name, "Maria"); assert_eq!(v.age, 23); @@ -316,7 +317,7 @@ mod test { map.remove(&mut store, pk1).unwrap(); map.save(&mut store, pk3, &data3).unwrap(); // now 42 is the new owner - let (k, v) = map.idx.age.item(&store, &age42).unwrap().unwrap(); + let (k, v) = map.idx.age.item(&store, age42).unwrap().unwrap(); assert_eq!(k.as_slice(), pk3); assert_eq!(&v.name, "Marta"); assert_eq!(v.age, 42); diff --git a/packages/storage-plus/src/indexes.rs b/packages/storage-plus/src/indexes.rs index a27cc4974..6452f3d3c 100644 --- a/packages/storage-plus/src/indexes.rs +++ b/packages/storage-plus/src/indexes.rs @@ -6,9 +6,10 @@ use serde::{Deserialize, Serialize}; use cosmwasm_std::{Binary, Order, StdError, StdResult, Storage, KV}; +use crate::keys::IntKey; use crate::map::Map; use crate::prefix::range_with_prefix; -use crate::{Bound, Endian}; +use crate::{Bound, Endian, PrimaryKey}; /// MARKER is stored in the multi-index as value, but we only look at the key (which is pk) const MARKER: u32 = 1; @@ -19,8 +20,8 @@ pub fn index_string(data: &str) -> Vec { // Look at https://docs.rs/endiannezz/0.4.1/endiannezz/trait.Primitive.html // if you want to make this generic over all ints -pub fn index_int(data: T) -> Vec { - data.to_be_bytes().into() +pub fn index_int(data: T) -> IntKey { + IntKey::::new(data) } // 2 main variants: @@ -130,14 +131,16 @@ pub(crate) struct UniqueRef { value: T, } -pub struct UniqueIndex<'a, T> { - index: fn(&T) -> Vec, - idx_map: Map<'a, &'a [u8], UniqueRef>, +pub struct UniqueIndex<'a, K, T> { + // index: fn(&T) -> Vec, + index: fn(&T) -> K, + // idx_map: Map<'a, &'a [u8], UniqueRef>, + idx_map: Map<'a, K, UniqueRef>, } -impl<'a, T> UniqueIndex<'a, T> { +impl<'a, K, T> UniqueIndex<'a, K, T> { // TODO: make this a const fn - pub fn new(idx_fn: fn(&T) -> Vec, idx_namespace: &'a str) -> Self { + pub fn new(idx_fn: fn(&T) -> K, idx_namespace: &'a str) -> Self { UniqueIndex { index: idx_fn, idx_map: Map::new(idx_namespace), @@ -145,15 +148,16 @@ impl<'a, T> UniqueIndex<'a, T> { } } -impl<'a, T> Index for UniqueIndex<'a, T> +impl<'a, K, T> Index for UniqueIndex<'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); // error if this is already set self.idx_map - .update(store, &idx, |existing| -> StdResult<_> { + .update(store, idx, |existing| -> StdResult<_> { match existing { Some(_) => Err(StdError::generic_err("Violates unique constraint on index")), None => Ok(UniqueRef:: { @@ -167,20 +171,21 @@ where 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); + self.idx_map.remove(store, idx); Ok(()) } } -impl<'a, T> UniqueIndex<'a, T> +impl<'a, K, T> UniqueIndex<'a, K, T> where T: Serialize + DeserializeOwned + Clone, + K: PrimaryKey<'a>, { /// returns all items that match this secondary index, always by pk Ascending - pub fn item(&self, store: &dyn Storage, idx: &[u8]) -> StdResult>> { + pub fn item(&self, store: &dyn Storage, idx: K) -> StdResult>> { let data = self .idx_map - .may_load(store, &idx)? + .may_load(store, idx)? .map(|i| (i.pk.into(), i.value)); Ok(data) } From 7eefe9bdfd1882db7adea8bddf30a1420c8e76dd Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 22 Dec 2020 15:57:02 +0100 Subject: [PATCH 02/13] Add U8Key for completeness --- packages/storage-plus/src/keys.rs | 1 + packages/storage-plus/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/storage-plus/src/keys.rs b/packages/storage-plus/src/keys.rs index f42a69aa7..d38be12e4 100644 --- a/packages/storage-plus/src/keys.rs +++ b/packages/storage-plus/src/keys.rs @@ -156,6 +156,7 @@ impl<'a, T: AsRef> Prefixer<'a> for T { } } +pub type U8Key = IntKey; pub type U16Key = IntKey; pub type U32Key = IntKey; pub type U64Key = IntKey; diff --git a/packages/storage-plus/src/lib.rs b/packages/storage-plus/src/lib.rs index 9af4d96cf..7771fd770 100644 --- a/packages/storage-plus/src/lib.rs +++ b/packages/storage-plus/src/lib.rs @@ -16,7 +16,7 @@ pub use indexed_map::{IndexList, IndexedMap}; #[cfg(feature = "iterator")] pub use indexes::{index_int, index_string, Index, MultiIndex, UniqueIndex}; pub use item::Item; -pub use keys::{PkOwned, Prefixer, PrimaryKey, U128Key, U16Key, U32Key, U64Key}; +pub use keys::{PkOwned, Prefixer, PrimaryKey, U128Key, U16Key, U32Key, U64Key, U8Key}; pub use map::Map; pub use path::Path; #[cfg(feature = "iterator")] From 20a8fc1f2c79484f1bff46358681af070728285e Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 22 Dec 2020 16:00:44 +0100 Subject: [PATCH 03/13] Add signed int key type aliases for completeness --- packages/storage-plus/src/keys.rs | 6 ++++++ packages/storage-plus/src/lib.rs | 1 + 2 files changed, 7 insertions(+) diff --git a/packages/storage-plus/src/keys.rs b/packages/storage-plus/src/keys.rs index d38be12e4..7a1d1cd47 100644 --- a/packages/storage-plus/src/keys.rs +++ b/packages/storage-plus/src/keys.rs @@ -162,6 +162,12 @@ pub type U32Key = IntKey; pub type U64Key = IntKey; pub type U128Key = IntKey; +pub type I8Key = IntKey; +pub type I16Key = IntKey; +pub type I32Key = IntKey; +pub type I64Key = IntKey; +pub type I128Key = IntKey; + /// It will cast one-particular int type into a Key via PkOwned, ensuring you don't mix up u32 and u64 /// You can use new or the from/into pair to build a key from an int: /// diff --git a/packages/storage-plus/src/lib.rs b/packages/storage-plus/src/lib.rs index 7771fd770..64157de6b 100644 --- a/packages/storage-plus/src/lib.rs +++ b/packages/storage-plus/src/lib.rs @@ -16,6 +16,7 @@ pub use indexed_map::{IndexList, IndexedMap}; #[cfg(feature = "iterator")] pub use indexes::{index_int, index_string, 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}; pub use map::Map; pub use path::Path; From e39c8bf5949932a8193d4f6fd36efecf7d09a985 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 22 Dec 2020 16:02:22 +0100 Subject: [PATCH 04/13] Use unsigned for age --- packages/storage-plus/src/indexed_map.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/storage-plus/src/indexed_map.rs b/packages/storage-plus/src/indexed_map.rs index 4867c7608..38445e58a 100644 --- a/packages/storage-plus/src/indexed_map.rs +++ b/packages/storage-plus/src/indexed_map.rs @@ -165,7 +165,7 @@ mod test { #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] struct Data { pub name: String, - pub age: i32, + pub age: u32, } struct DataIndexes<'a> { @@ -185,7 +185,7 @@ mod test { 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"), - age: UniqueIndex::new(|d| index_int(d.age as u32), "data__age"), + age: UniqueIndex::new(|d| index_int(d.age), "data__age"), }; IndexedMap::new("data", indexes) } From 96edc1833f04c19285b285a60fe57335fa5f4924 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 23 Dec 2020 00:16:36 +0100 Subject: [PATCH 05/13] Remove index_int helper Fix comments Remove commented code --- packages/storage-plus/src/indexed_map.rs | 12 ++++++------ packages/storage-plus/src/indexes.rs | 15 +++------------ packages/storage-plus/src/lib.rs | 2 +- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/packages/storage-plus/src/indexed_map.rs b/packages/storage-plus/src/indexed_map.rs index 38445e58a..fb8859788 100644 --- a/packages/storage-plus/src/indexed_map.rs +++ b/packages/storage-plus/src/indexed_map.rs @@ -156,7 +156,7 @@ where mod test { use super::*; - use crate::indexes::{index_int, index_string, MultiIndex, UniqueIndex}; + use crate::indexes::{index_string, MultiIndex, UniqueIndex}; use crate::U32Key; use cosmwasm_std::testing::MockStorage; use cosmwasm_std::{MemoryStorage, Order}; @@ -185,7 +185,7 @@ mod test { 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"), - age: UniqueIndex::new(|d| index_int(d.age), "data__age"), + age: UniqueIndex::new(|d| U32Key::new(d.age), "data__age"), }; IndexedMap::new("data", indexes) } @@ -257,13 +257,13 @@ mod test { assert_eq!(0, count); // match on proper age - let proper = index_int(42); + let proper = U32Key::new(42); let aged = map.idx.age.item(&store, proper).unwrap().unwrap(); assert_eq!(pk.to_vec(), aged.0); assert_eq!(data, aged.1); // no match on wrong age - let too_old = index_int(43); + let too_old = U32Key::new(43); let aged = map.idx.age.item(&store, too_old).unwrap(); assert_eq!(None, aged); } @@ -300,14 +300,14 @@ mod test { // query by unique key // match on proper age - let age42 = index_int(42); + let age42 = U32Key::new(42); let (k, v) = map.idx.age.item(&store, age42.clone()).unwrap().unwrap(); assert_eq!(k.as_slice(), pk1); assert_eq!(&v.name, "Maria"); assert_eq!(v.age, 42); // match on other age - let age23 = index_int(23); + let age23 = U32Key::new(23); let (k, v) = map.idx.age.item(&store, age23).unwrap().unwrap(); assert_eq!(k.as_slice(), pk2); assert_eq!(&v.name, "Maria"); diff --git a/packages/storage-plus/src/indexes.rs b/packages/storage-plus/src/indexes.rs index 6452f3d3c..852954185 100644 --- a/packages/storage-plus/src/indexes.rs +++ b/packages/storage-plus/src/indexes.rs @@ -6,10 +6,9 @@ use serde::{Deserialize, Serialize}; use cosmwasm_std::{Binary, Order, StdError, StdResult, Storage, KV}; -use crate::keys::IntKey; use crate::map::Map; use crate::prefix::range_with_prefix; -use crate::{Bound, Endian, PrimaryKey}; +use crate::{Bound, PrimaryKey}; /// MARKER is stored in the multi-index as value, but we only look at the key (which is pk) const MARKER: u32 = 1; @@ -18,12 +17,6 @@ pub fn index_string(data: &str) -> Vec { data.as_bytes().to_vec() } -// Look at https://docs.rs/endiannezz/0.4.1/endiannezz/trait.Primitive.html -// if you want to make this generic over all ints -pub fn index_int(data: T) -> IntKey { - IntKey::::new(data) -} - // 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 @@ -43,7 +36,7 @@ where pub struct MultiIndex<'a, T> { index: fn(&T) -> Vec, idx_map: Map<'a, (&'a [u8], &'a [u8]), u32>, - // note, we collapse the pubkey - combining everything under the namespace - even if it is composite + // note, we collapse the pk - combining everything under the namespace - even if it is composite pk_map: Map<'a, &'a [u8], T>, } @@ -126,15 +119,13 @@ where #[derive(Deserialize, Serialize)] pub(crate) struct UniqueRef { - // note, we collapse the pubkey - combining everything under the namespace - even if it is composite + // note, we collapse the pk - combining everything under the namespace - even if it is composite pk: Binary, value: T, } pub struct UniqueIndex<'a, K, T> { - // index: fn(&T) -> Vec, index: fn(&T) -> K, - // idx_map: Map<'a, &'a [u8], UniqueRef>, idx_map: Map<'a, K, UniqueRef>, } diff --git a/packages/storage-plus/src/lib.rs b/packages/storage-plus/src/lib.rs index 64157de6b..e2fb5a380 100644 --- a/packages/storage-plus/src/lib.rs +++ b/packages/storage-plus/src/lib.rs @@ -14,7 +14,7 @@ pub use endian::Endian; #[cfg(feature = "iterator")] pub use indexed_map::{IndexList, IndexedMap}; #[cfg(feature = "iterator")] -pub use indexes::{index_int, index_string, Index, MultiIndex, UniqueIndex}; +pub use indexes::{index_string, 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}; From 0c3717b77eacec891ac298520b9dac317b48c71f Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 23 Dec 2020 09:46:23 +0100 Subject: [PATCH 06/13] Add unique index composite key test / example --- packages/storage-plus/src/indexed_map.rs | 64 ++++++++++++++++++++++-- packages/storage-plus/src/indexes.rs | 6 ++- packages/storage-plus/src/lib.rs | 2 +- 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/packages/storage-plus/src/indexed_map.rs b/packages/storage-plus/src/indexed_map.rs index fb8859788..bde2cb7df 100644 --- a/packages/storage-plus/src/indexed_map.rs +++ b/packages/storage-plus/src/indexed_map.rs @@ -156,8 +156,8 @@ where mod test { use super::*; - use crate::indexes::{index_string, MultiIndex, UniqueIndex}; - use crate::U32Key; + use crate::indexes::{index_string, index_string_tuple, MultiIndex, UniqueIndex}; + use crate::{PkOwned, U32Key}; use cosmwasm_std::testing::MockStorage; use cosmwasm_std::{MemoryStorage, Order}; use serde::{Deserialize, Serialize}; @@ -165,18 +165,20 @@ mod test { #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] struct Data { pub name: String, + pub last_name: String, pub age: u32, } struct DataIndexes<'a> { pub name: MultiIndex<'a, Data>, pub age: UniqueIndex<'a, U32Key, Data>, + pub name_lastname: UniqueIndex<'a, (PkOwned, PkOwned), Data>, } // Future Note: this can likely be macro-derived impl<'a> IndexList for DataIndexes<'a> { fn get_indexes(&'_ self) -> Box> + '_> { - let v: Vec<&dyn Index> = vec![&self.name, &self.age]; + let v: Vec<&dyn Index> = vec![&self.name, &self.age, &self.name_lastname]; Box::new(v.into_iter()) } } @@ -186,6 +188,10 @@ mod test { let indexes = DataIndexes { name: MultiIndex::new(|d| index_string(&d.name), "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), + "data__name_lastname", + ), }; IndexedMap::new("data", indexes) } @@ -198,6 +204,7 @@ mod test { // save data let data = Data { name: "Maria".to_string(), + last_name: "".to_string(), age: 42, }; let pk: &[u8] = b"5627"; @@ -276,14 +283,16 @@ mod test { // first data let data1 = Data { name: "Maria".to_string(), + last_name: "Doe".to_string(), age: 42, }; let pk1: &[u8] = b"5627"; map.save(&mut store, pk1, &data1).unwrap(); - // same name (multi-index), different age => ok + // same name (multi-index), different last name, different age => ok let data2 = Data { name: "Maria".to_string(), + last_name: "Williams".to_string(), age: 23, }; let pk2: &[u8] = b"7326"; @@ -292,6 +301,7 @@ mod test { // different name, same age => error let data3 = Data { name: "Marta".to_string(), + last_name: "Williams".to_string(), age: 42, }; let pk3: &[u8] = b"8263"; @@ -323,6 +333,49 @@ mod test { assert_eq!(v.age, 42); } + #[test] + fn unique_index_enforced_composite_key() { + let mut store = MockStorage::new(); + let mut map = build_map(); + + // first data + let data1 = Data { + name: "John".to_string(), + last_name: "Doe".to_string(), + age: 1, + }; + let pk1: &[u8] = b"1"; + map.save(&mut store, pk1, &data1).unwrap(); + + // same name, different lastname => ok + let data2 = Data { + name: "John".to_string(), + last_name: "Wayne".to_string(), + age: 2, + }; + let pk2: &[u8] = b"2"; + map.save(&mut store, pk2, &data2).unwrap(); + + // different name, same last name => ok + let data3 = Data { + name: "Maria".to_string(), + last_name: "Doe".to_string(), + age: 3, + }; + let pk3: &[u8] = b"3"; + map.save(&mut store, pk3, &data3).unwrap(); + + // same name, same lastname => error + let data4 = Data { + name: "John".to_string(), + last_name: "Doe".to_string(), + age: 4, + }; + let pk4: &[u8] = b"4"; + // enforce this returns some error + map.save(&mut store, pk4, &data4).unwrap_err(); + } + #[test] fn remove_and_update_reflected_on_indexes() { let mut store = MockStorage::new(); @@ -341,18 +394,21 @@ mod test { // set up some data let data1 = Data { name: "John".to_string(), + last_name: "Doe".to_string(), age: 22, }; let pk1: &[u8] = b"john"; map.save(&mut store, pk1, &data1).unwrap(); let data2 = Data { name: "John".to_string(), + last_name: "Wayne".to_string(), age: 25, }; let pk2: &[u8] = b"john2"; map.save(&mut store, pk2, &data2).unwrap(); let data3 = Data { name: "Fred".to_string(), + last_name: "Astaire".to_string(), age: 33, }; let pk3: &[u8] = b"fred"; diff --git a/packages/storage-plus/src/indexes.rs b/packages/storage-plus/src/indexes.rs index 852954185..c8ddce4d1 100644 --- a/packages/storage-plus/src/indexes.rs +++ b/packages/storage-plus/src/indexes.rs @@ -8,7 +8,7 @@ use cosmwasm_std::{Binary, Order, StdError, StdResult, Storage, KV}; use crate::map::Map; use crate::prefix::range_with_prefix; -use crate::{Bound, PrimaryKey}; +use crate::{Bound, PkOwned, PrimaryKey}; /// MARKER is stored in the multi-index as value, but we only look at the key (which is pk) const MARKER: u32 = 1; @@ -17,6 +17,10 @@ pub fn index_string(data: &str) -> Vec { data.as_bytes().to_vec() } +pub fn index_string_tuple(data1: &str, data2: &str) -> (PkOwned, PkOwned) { + (PkOwned(index_string(data1)), PkOwned(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 diff --git a/packages/storage-plus/src/lib.rs b/packages/storage-plus/src/lib.rs index e2fb5a380..b012f4aed 100644 --- a/packages/storage-plus/src/lib.rs +++ b/packages/storage-plus/src/lib.rs @@ -14,7 +14,7 @@ pub use endian::Endian; #[cfg(feature = "iterator")] pub use indexed_map::{IndexList, IndexedMap}; #[cfg(feature = "iterator")] -pub use indexes::{index_string, Index, MultiIndex, UniqueIndex}; +pub use indexes::{index_string, index_string_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}; From 5e7479778ccf4c776ca59e3b6fbbfba073f144a0 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 23 Dec 2020 11:53:08 +0100 Subject: [PATCH 07/13] Add (canonical) prefix() / range() to UniqueIndex --- packages/storage-plus/src/indexes.rs | 33 ++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/storage-plus/src/indexes.rs b/packages/storage-plus/src/indexes.rs index c8ddce4d1..fd94a72a3 100644 --- a/packages/storage-plus/src/indexes.rs +++ b/packages/storage-plus/src/indexes.rs @@ -6,9 +6,10 @@ use serde::{Deserialize, Serialize}; use cosmwasm_std::{Binary, Order, StdError, StdResult, Storage, KV}; +use crate::keys::EmptyPrefix; use crate::map::Map; use crate::prefix::range_with_prefix; -use crate::{Bound, PkOwned, PrimaryKey}; +use crate::{Bound, PkOwned, Prefix, PrimaryKey}; /// MARKER is stored in the multi-index as value, but we only look at the key (which is pk) const MARKER: u32 = 1; @@ -122,7 +123,7 @@ where } #[derive(Deserialize, Serialize)] -pub(crate) struct UniqueRef { +pub struct UniqueRef { // note, we collapse the pk - combining everything under the namespace - even if it is composite pk: Binary, value: T, @@ -176,6 +177,11 @@ where T: Serialize + DeserializeOwned + Clone, K: PrimaryKey<'a>, { + pub fn prefix(&self, p: K::Prefix) -> Prefix> { + // Prefix::::new(self.idx_namespace, &p.prefix()) + self.idx_map.prefix(p) + } + /// returns all items that match this secondary index, always by pk Ascending pub fn item(&self, store: &dyn Storage, idx: K) -> StdResult>> { let data = self @@ -185,3 +191,26 @@ where Ok(data) } } + +// short-cut for simple keys, rather than .prefix(()).range(...) +impl<'a, K, T> UniqueIndex<'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>( + &self, + store: &'c dyn Storage, + min: Option, + max: Option, + order: cosmwasm_std::Order, + ) -> Box>>> + 'c> + where + T: 'c, + { + self.prefix(K::Prefix::new()).range(store, min, max, order) + } +} From 557baf235c2bf877dc56c5697cd23d5c9712e3a8 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 23 Dec 2020 12:21:09 +0100 Subject: [PATCH 08/13] Add UniqueIndex simple key range() test --- packages/storage-plus/src/indexed_map.rs | 67 ++++++++++++++++++++++++ packages/storage-plus/src/indexes.rs | 4 +- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/packages/storage-plus/src/indexed_map.rs b/packages/storage-plus/src/indexed_map.rs index bde2cb7df..01841c4e6 100644 --- a/packages/storage-plus/src/indexed_map.rs +++ b/packages/storage-plus/src/indexed_map.rs @@ -435,4 +435,71 @@ mod test { assert_eq!(name_count(&map, &store, "Fred"), 0); assert_eq!(name_count(&map, &store, "Mary"), 1); } + + #[test] + fn unique_index_simple_key_range() { + let mut store = MockStorage::new(); + let mut 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: "Young".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: "Rodriguez".to_string(), + age: 12, + }; + let pk: &[u8] = b"5630"; + map.save(&mut store, pk, &data4).unwrap(); + + let res: StdResult> = map + .idx + .age + .range(&store, None, None, Order::Ascending) + .collect(); + let ages = res.unwrap(); + + let count = ages.len(); + assert_eq!(4, count); + + // The (index) keys are the (unique, encoded) ages, in ascending order + assert_eq!(12u32.to_be_bytes(), ages[0].0.as_slice()); + assert_eq!(13u32.to_be_bytes(), ages[1].0.as_slice()); + assert_eq!(24u32.to_be_bytes(), ages[2].0.as_slice()); + assert_eq!(42u32.to_be_bytes(), ages[3].0.as_slice()); + + // The pks are in the (UniqueRef) values + assert_eq!(b"5630".to_vec(), ages[0].1.pk); + assert_eq!(b"5628".to_vec(), ages[1].1.pk); + assert_eq!(b"5629".to_vec(), ages[2].1.pk); + assert_eq!(b"5627".to_vec(), ages[3].1.pk); + + // The associated data is in the (UniqueRef) values + assert_eq!(data4, ages[0].1.value); + assert_eq!(data2, ages[1].1.value); + assert_eq!(data3, ages[2].1.value); + assert_eq!(data1, ages[3].1.value); + } } diff --git a/packages/storage-plus/src/indexes.rs b/packages/storage-plus/src/indexes.rs index fd94a72a3..89ca17efb 100644 --- a/packages/storage-plus/src/indexes.rs +++ b/packages/storage-plus/src/indexes.rs @@ -125,8 +125,8 @@ where #[derive(Deserialize, Serialize)] pub struct UniqueRef { // note, we collapse the pk - combining everything under the namespace - even if it is composite - pk: Binary, - value: T, + pub pk: Binary, + pub value: T, } pub struct UniqueIndex<'a, K, T> { From ae26c67642bbad53199eca7e417fffad6c678fd0 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 23 Dec 2020 12:42:23 +0100 Subject: [PATCH 09/13] Add UniqueIndex composite range() test --- packages/storage-plus/src/indexed_map.rs | 63 ++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/packages/storage-plus/src/indexed_map.rs b/packages/storage-plus/src/indexed_map.rs index 01841c4e6..760706fdb 100644 --- a/packages/storage-plus/src/indexed_map.rs +++ b/packages/storage-plus/src/indexed_map.rs @@ -502,4 +502,67 @@ mod test { assert_eq!(data3, ages[2].1.value); assert_eq!(data1, ages[3].1.value); } + + #[test] + fn unique_index_composite_key_range() { + let mut store = MockStorage::new(); + let mut 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: "Young".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: "Rodriguez".to_string(), + age: 12, + }; + let pk: &[u8] = b"5630"; + map.save(&mut store, pk, &data4).unwrap(); + + let res: StdResult> = map + .idx + .name_lastname + .prefix(PkOwned(b"Maria".to_vec())) + .range(&store, None, None, Order::Ascending) + .collect(); + let marias = res.unwrap(); + + // Only two people are called "Maria" + let count = marias.len(); + assert_eq!(2, count); + + // The (index) keys are the (encoded) last names, in ascending order + assert_eq!(b"", marias[0].0.as_slice()); + assert_eq!(b"Young", marias[1].0.as_slice()); + + // The pks are in the (UniqueRef) values + assert_eq!(b"5627".to_vec(), marias[0].1.pk); + assert_eq!(b"5629".to_vec(), marias[1].1.pk); + + // The associated data is in the (UniqueRef) values + assert_eq!(data1, marias[0].1.value); + assert_eq!(data3, marias[1].1.value); + } } From 5ab3103a1c1453aa50ba72f3ec62bd4bf0a05927 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 23 Dec 2020 21:59:25 +0100 Subject: [PATCH 10/13] Add custom deserialization to Prefix --- packages/storage-plus/src/prefix.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/storage-plus/src/prefix.rs b/packages/storage-plus/src/prefix.rs index 34fc73ac0..854d29f85 100644 --- a/packages/storage-plus/src/prefix.rs +++ b/packages/storage-plus/src/prefix.rs @@ -51,6 +51,7 @@ 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>, } impl Deref for Prefix @@ -69,11 +70,20 @@ where T: Serialize + DeserializeOwned, { pub fn new(top_name: &[u8], sub_names: &[&[u8]]) -> Self { + Prefix::new_de_fn(top_name, sub_names, deserialize_kv) + } + + pub fn new_de_fn( + top_name: &[u8], + sub_names: &[&[u8]], + de_fn: fn(KV) -> StdResult>, + ) -> 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, + de_fn, } } @@ -87,8 +97,8 @@ where where T: 'a, { - let mapped = range_with_prefix(store, &self.storage_prefix, min, max, order) - .map(deserialize_kv::); + let mapped = + range_with_prefix(store, &self.storage_prefix, min, max, order).map(self.de_fn); Box::new(mapped) } } @@ -165,6 +175,7 @@ mod test { let prefix = Prefix { storage_prefix: b"foo".to_vec(), data: PhantomData::, + de_fn: deserialize_kv, }; // set some data, we care about "foo" prefix From 5c9c82c23fadfe6da8503b322dae60f6063df9c0 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 23 Dec 2020 22:09:18 +0100 Subject: [PATCH 11/13] UniqueIndex range() returns KV iterator --- packages/storage-plus/src/indexed_map.rs | 44 +++++++++--------------- packages/storage-plus/src/indexes.rs | 25 +++++++++----- 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/packages/storage-plus/src/indexed_map.rs b/packages/storage-plus/src/indexed_map.rs index 760706fdb..8b982827b 100644 --- a/packages/storage-plus/src/indexed_map.rs +++ b/packages/storage-plus/src/indexed_map.rs @@ -484,23 +484,17 @@ mod test { let count = ages.len(); assert_eq!(4, count); - // The (index) keys are the (unique, encoded) ages, in ascending order - assert_eq!(12u32.to_be_bytes(), ages[0].0.as_slice()); - assert_eq!(13u32.to_be_bytes(), ages[1].0.as_slice()); - assert_eq!(24u32.to_be_bytes(), ages[2].0.as_slice()); - assert_eq!(42u32.to_be_bytes(), ages[3].0.as_slice()); - - // The pks are in the (UniqueRef) values - assert_eq!(b"5630".to_vec(), ages[0].1.pk); - assert_eq!(b"5628".to_vec(), ages[1].1.pk); - assert_eq!(b"5629".to_vec(), ages[2].1.pk); - assert_eq!(b"5627".to_vec(), ages[3].1.pk); - - // The associated data is in the (UniqueRef) values - assert_eq!(data4, ages[0].1.value); - assert_eq!(data2, ages[1].1.value); - assert_eq!(data3, ages[2].1.value); - assert_eq!(data1, ages[3].1.value); + // The pks + assert_eq!(b"5630".to_vec(), ages[0].0); + assert_eq!(b"5628".to_vec(), ages[1].0); + assert_eq!(b"5629".to_vec(), ages[2].0); + assert_eq!(b"5627".to_vec(), ages[3].0); + + // The associated data + assert_eq!(data4, ages[0].1); + assert_eq!(data2, ages[1].1); + assert_eq!(data3, ages[2].1); + assert_eq!(data1, ages[3].1); } #[test] @@ -553,16 +547,12 @@ mod test { let count = marias.len(); assert_eq!(2, count); - // The (index) keys are the (encoded) last names, in ascending order - assert_eq!(b"", marias[0].0.as_slice()); - assert_eq!(b"Young", marias[1].0.as_slice()); + // The pks + assert_eq!(b"5627".to_vec(), marias[0].0); + assert_eq!(b"5629".to_vec(), marias[1].0); - // The pks are in the (UniqueRef) values - assert_eq!(b"5627".to_vec(), marias[0].1.pk); - assert_eq!(b"5629".to_vec(), marias[1].1.pk); - - // The associated data is in the (UniqueRef) values - assert_eq!(data1, marias[0].1.value); - assert_eq!(data3, marias[1].1.value); + // The associated data + assert_eq!(data1, marias[0].1); + assert_eq!(data3, marias[1].1); } } diff --git a/packages/storage-plus/src/indexes.rs b/packages/storage-plus/src/indexes.rs index 89ca17efb..59c9f3eef 100644 --- a/packages/storage-plus/src/indexes.rs +++ b/packages/storage-plus/src/indexes.rs @@ -4,12 +4,12 @@ use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Binary, Order, StdError, StdResult, Storage, KV}; +use cosmwasm_std::{from_slice, Binary, Order, StdError, StdResult, Storage, KV}; use crate::keys::EmptyPrefix; use crate::map::Map; use crate::prefix::range_with_prefix; -use crate::{Bound, PkOwned, Prefix, PrimaryKey}; +use crate::{Bound, PkOwned, Prefix, Prefixer, PrimaryKey}; /// MARKER is stored in the multi-index as value, but we only look at the key (which is pk) const MARKER: u32 = 1; @@ -123,15 +123,16 @@ where } #[derive(Deserialize, Serialize)] -pub struct UniqueRef { +pub(crate) struct UniqueRef { // note, we collapse the pk - combining everything under the namespace - even if it is composite - pub pk: Binary, - pub value: T, + pk: Binary, + value: T, } pub struct UniqueIndex<'a, K, T> { index: fn(&T) -> K, idx_map: Map<'a, K, UniqueRef>, + idx_namespace: &'a [u8], } impl<'a, K, T> UniqueIndex<'a, K, T> { @@ -140,6 +141,7 @@ impl<'a, K, T> UniqueIndex<'a, K, T> { UniqueIndex { index: idx_fn, idx_map: Map::new(idx_namespace), + idx_namespace: idx_namespace.as_bytes(), } } } @@ -172,14 +174,19 @@ where } } +pub(crate) fn deserialize_unique_kv(kv: KV) -> StdResult> { + let (_, v) = kv; + let t = from_slice::>(&v)?; + Ok((t.pk.into(), t.value)) +} + impl<'a, K, T> UniqueIndex<'a, K, T> where T: Serialize + DeserializeOwned + Clone, K: PrimaryKey<'a>, { - pub fn prefix(&self, p: K::Prefix) -> Prefix> { - // Prefix::::new(self.idx_namespace, &p.prefix()) - self.idx_map.prefix(p) + pub fn prefix(&self, p: K::Prefix) -> Prefix { + Prefix::new_de_fn(self.idx_namespace, &p.prefix(), deserialize_unique_kv) } /// returns all items that match this secondary index, always by pk Ascending @@ -207,7 +214,7 @@ where min: Option, max: Option, order: cosmwasm_std::Order, - ) -> Box>>> + 'c> + ) -> Box>> + 'c> where T: 'c, { From ee9e14744a1faa9cdfa3d53621cf1ec27ea6a6f8 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Thu, 24 Dec 2020 20:59:41 +0100 Subject: [PATCH 12/13] Change deserialize_unique_kv access to private --- packages/storage-plus/src/indexes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/storage-plus/src/indexes.rs b/packages/storage-plus/src/indexes.rs index 59c9f3eef..12a721b32 100644 --- a/packages/storage-plus/src/indexes.rs +++ b/packages/storage-plus/src/indexes.rs @@ -174,7 +174,7 @@ where } } -pub(crate) fn deserialize_unique_kv(kv: KV) -> StdResult> { +fn deserialize_unique_kv(kv: KV) -> StdResult> { let (_, v) = kv; let t = from_slice::>(&v)?; Ok((t.pk.into(), t.value)) From fcc1d7c04efa9a5e7e02025acd1110375314d19c Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Sun, 27 Dec 2020 18:01:37 +0100 Subject: [PATCH 13/13] Unify test data loading --- packages/storage-plus/src/indexed_map.rs | 300 +++++++++-------------- 1 file changed, 116 insertions(+), 184 deletions(-) diff --git a/packages/storage-plus/src/indexed_map.rs b/packages/storage-plus/src/indexed_map.rs index 8b982827b..246403230 100644 --- a/packages/storage-plus/src/indexed_map.rs +++ b/packages/storage-plus/src/indexed_map.rs @@ -196,23 +196,70 @@ mod test { IndexedMap::new("data", indexes) } + fn save_data<'a>( + store: &mut MockStorage, + map: &mut IndexedMap<'a, &'a [u8], Data, DataIndexes<'a>>, + ) -> (Vec<&'a [u8]>, Vec) { + let mut pks = vec![]; + let mut datas = vec![]; + let data = Data { + name: "Maria".to_string(), + last_name: "Doe".to_string(), + age: 42, + }; + let pk: &[u8] = b"1"; + map.save(store, pk, &data).unwrap(); + pks.push(pk); + datas.push(data); + + // same name (multi-index), different last name, different age => ok + let data = Data { + name: "Maria".to_string(), + last_name: "Williams".to_string(), + age: 23, + }; + let pk: &[u8] = b"2"; + map.save(store, pk, &data).unwrap(); + pks.push(pk); + datas.push(data); + + // different name, different last name, different age => ok + let data = Data { + name: "John".to_string(), + last_name: "Wayne".to_string(), + age: 32, + }; + let pk: &[u8] = b"3"; + map.save(store, pk, &data).unwrap(); + pks.push(pk); + datas.push(data); + + let data = Data { + name: "Maria Luisa".to_string(), + last_name: "Rodriguez".to_string(), + age: 12, + }; + let pk: &[u8] = b"4"; + map.save(store, pk, &data).unwrap(); + pks.push(pk); + datas.push(data); + + (pks, datas) + } + #[test] fn store_and_load_by_index() { let mut store = MockStorage::new(); let mut map = build_map(); // save data - let data = Data { - name: "Maria".to_string(), - last_name: "".to_string(), - age: 42, - }; - let pk: &[u8] = b"5627"; - map.save(&mut store, pk, &data).unwrap(); + let (pks, datas) = save_data(&mut store, &mut map); + let pk = pks[0]; + let data = &datas[0]; // load it properly let loaded = map.load(&store, pk).unwrap(); - assert_eq!(data, loaded); + assert_eq!(*data, loaded); let count = map .idx @@ -220,7 +267,7 @@ mod test { .all_items(&store, &index_string("Maria")) .unwrap() .len(); - assert_eq!(1, count); + assert_eq!(2, count); // TODO: we load by wrong keys - get full storage key! @@ -231,10 +278,10 @@ mod test { .name .all_items(&store, &index_string("Maria")) .unwrap(); - assert_eq!(1, marias.len()); + assert_eq!(2, marias.len()); let (k, v) = &marias[0]; assert_eq!(pk, k.as_slice()); - assert_eq!(&data, v); + assert_eq!(data, v); // other index doesn't match (1 byte after) let count = map @@ -267,7 +314,7 @@ mod test { let proper = U32Key::new(42); let aged = map.idx.age.item(&store, proper).unwrap().unwrap(); assert_eq!(pk.to_vec(), aged.0); - assert_eq!(data, aged.1); + assert_eq!(*data, aged.1); // no match on wrong age let too_old = U32Key::new(43); @@ -280,57 +327,43 @@ mod test { let mut store = MockStorage::new(); let mut map = build_map(); - // first data - let data1 = Data { - name: "Maria".to_string(), - last_name: "Doe".to_string(), - age: 42, - }; - let pk1: &[u8] = b"5627"; - map.save(&mut store, pk1, &data1).unwrap(); - - // same name (multi-index), different last name, different age => ok - let data2 = Data { - name: "Maria".to_string(), - last_name: "Williams".to_string(), - age: 23, - }; - let pk2: &[u8] = b"7326"; - map.save(&mut store, pk2, &data2).unwrap(); + // save data + let (pks, datas) = save_data(&mut store, &mut map); - // different name, same age => error - let data3 = Data { + // different name, different last name, same age => error + let data5 = Data { name: "Marta".to_string(), - last_name: "Williams".to_string(), + last_name: "Laurens".to_string(), age: 42, }; - let pk3: &[u8] = b"8263"; + let pk5: &[u8] = b"4"; + // enforce this returns some error - map.save(&mut store, pk3, &data3).unwrap_err(); + map.save(&mut store, pk5, &data5).unwrap_err(); // query by unique key // match on proper age let age42 = U32Key::new(42); let (k, v) = map.idx.age.item(&store, age42.clone()).unwrap().unwrap(); - assert_eq!(k.as_slice(), pk1); - assert_eq!(&v.name, "Maria"); - assert_eq!(v.age, 42); + assert_eq!(k.as_slice(), pks[0]); + assert_eq!(v.name, datas[0].name); + assert_eq!(v.age, datas[0].age); // match on other age let age23 = U32Key::new(23); let (k, v) = map.idx.age.item(&store, age23).unwrap().unwrap(); - assert_eq!(k.as_slice(), pk2); - assert_eq!(&v.name, "Maria"); - assert_eq!(v.age, 23); + assert_eq!(k.as_slice(), pks[1]); + assert_eq!(v.name, datas[1].name); + assert_eq!(v.age, datas[1].age); // if we delete the first one, we can add the blocked one - map.remove(&mut store, pk1).unwrap(); - map.save(&mut store, pk3, &data3).unwrap(); + map.remove(&mut store, pks[0]).unwrap(); + map.save(&mut store, pk5, &data5).unwrap(); // now 42 is the new owner let (k, v) = map.idx.age.item(&store, age42).unwrap().unwrap(); - assert_eq!(k.as_slice(), pk3); - assert_eq!(&v.name, "Marta"); - assert_eq!(v.age, 42); + assert_eq!(k.as_slice(), pk5); + assert_eq!(v.name, data5.name); + assert_eq!(v.age, data5.age); } #[test] @@ -338,42 +371,18 @@ mod test { let mut store = MockStorage::new(); let mut map = build_map(); - // first data - let data1 = Data { - name: "John".to_string(), - last_name: "Doe".to_string(), - age: 1, - }; - let pk1: &[u8] = b"1"; - map.save(&mut store, pk1, &data1).unwrap(); - - // same name, different lastname => ok - let data2 = Data { - name: "John".to_string(), - last_name: "Wayne".to_string(), - age: 2, - }; - let pk2: &[u8] = b"2"; - map.save(&mut store, pk2, &data2).unwrap(); - - // different name, same last name => ok - let data3 = Data { - name: "Maria".to_string(), - last_name: "Doe".to_string(), - age: 3, - }; - let pk3: &[u8] = b"3"; - map.save(&mut store, pk3, &data3).unwrap(); + // save data + save_data(&mut store, &mut map); // same name, same lastname => error - let data4 = Data { - name: "John".to_string(), + let data5 = Data { + name: "Maria".to_string(), last_name: "Doe".to_string(), - age: 4, + age: 24, }; - let pk4: &[u8] = b"4"; + let pk5: &[u8] = b"5"; // enforce this returns some error - map.save(&mut store, pk4, &data4).unwrap_err(); + map.save(&mut store, pk5, &data5).unwrap_err(); } #[test] @@ -391,48 +400,31 @@ mod test { .count() }; - // set up some data - let data1 = Data { - name: "John".to_string(), - last_name: "Doe".to_string(), - age: 22, - }; - let pk1: &[u8] = b"john"; - map.save(&mut store, pk1, &data1).unwrap(); - let data2 = Data { - name: "John".to_string(), - last_name: "Wayne".to_string(), - age: 25, - }; - let pk2: &[u8] = b"john2"; - map.save(&mut store, pk2, &data2).unwrap(); - let data3 = Data { - name: "Fred".to_string(), - last_name: "Astaire".to_string(), - age: 33, - }; - let pk3: &[u8] = b"fred"; - map.save(&mut store, pk3, &data3).unwrap(); + // save data + let (pks, _) = save_data(&mut store, &mut map); - // find 2 Johns, 1 Fred, and no Mary - assert_eq!(name_count(&map, &store, "John"), 2); - assert_eq!(name_count(&map, &store, "Fred"), 1); + // find 2 Marias, 1 John, and no Mary + assert_eq!(name_count(&map, &store, "Maria"), 2); + assert_eq!(name_count(&map, &store, "John"), 1); + assert_eq!(name_count(&map, &store, "Maria Luisa"), 1); assert_eq!(name_count(&map, &store, "Mary"), 0); - // remove john 2 - map.remove(&mut store, pk2).unwrap(); - // change fred to mary - map.update(&mut store, pk3, |d| -> StdResult<_> { + // remove maria 2 + map.remove(&mut store, pks[1]).unwrap(); + + // change john to mary + map.update(&mut store, pks[2], |d| -> StdResult<_> { let mut x = d.unwrap(); - assert_eq!(&x.name, "Fred"); + assert_eq!(&x.name, "John"); x.name = "Mary".to_string(); Ok(x) }) .unwrap(); - // find 1 Johns, no Fred, and 1 Mary - assert_eq!(name_count(&map, &store, "John"), 1); - assert_eq!(name_count(&map, &store, "Fred"), 0); + // find 1 maria, 1 maria luisa, no john, and 1 mary + assert_eq!(name_count(&map, &store, "Maria"), 1); + assert_eq!(name_count(&map, &store, "Maria Luisa"), 1); + assert_eq!(name_count(&map, &store, "John"), 0); assert_eq!(name_count(&map, &store, "Mary"), 1); } @@ -442,37 +434,7 @@ mod test { let mut 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: "Young".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: "Rodriguez".to_string(), - age: 12, - }; - let pk: &[u8] = b"5630"; - map.save(&mut store, pk, &data4).unwrap(); + let (pks, datas) = save_data(&mut store, &mut map); let res: StdResult> = map .idx @@ -484,17 +446,17 @@ mod test { let count = ages.len(); assert_eq!(4, count); - // The pks - assert_eq!(b"5630".to_vec(), ages[0].0); - assert_eq!(b"5628".to_vec(), ages[1].0); - assert_eq!(b"5629".to_vec(), ages[2].0); - assert_eq!(b"5627".to_vec(), ages[3].0); + // The pks, sorted by age ascending + assert_eq!(pks[3].to_vec(), ages[0].0); + assert_eq!(pks[1].to_vec(), ages[1].0); + assert_eq!(pks[2].to_vec(), ages[2].0); + assert_eq!(pks[0].to_vec(), ages[3].0); // The associated data - assert_eq!(data4, ages[0].1); - assert_eq!(data2, ages[1].1); - assert_eq!(data3, ages[2].1); - assert_eq!(data1, ages[3].1); + assert_eq!(datas[3], ages[0].1); + assert_eq!(datas[1], ages[1].1); + assert_eq!(datas[2], ages[2].1); + assert_eq!(datas[0], ages[3].1); } #[test] @@ -503,37 +465,7 @@ mod test { let mut 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: "Young".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: "Rodriguez".to_string(), - age: 12, - }; - let pk: &[u8] = b"5630"; - map.save(&mut store, pk, &data4).unwrap(); + let (pks, datas) = save_data(&mut store, &mut map); let res: StdResult> = map .idx @@ -548,11 +480,11 @@ mod test { assert_eq!(2, count); // The pks - assert_eq!(b"5627".to_vec(), marias[0].0); - assert_eq!(b"5629".to_vec(), marias[1].0); + assert_eq!(pks[0].to_vec(), marias[0].0); + assert_eq!(pks[1].to_vec(), marias[1].0); // The associated data - assert_eq!(data1, marias[0].1); - assert_eq!(data3, marias[1].1); + assert_eq!(datas[0], marias[0].1); + assert_eq!(datas[1], marias[1].1); } }