From c608dbbb2f66310a28fd467e51ee6d6ef03bd6c2 Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Mon, 25 Nov 2024 13:00:02 +0100 Subject: [PATCH] storey::Map: document bounded and composite iteration --- src/pages/storey/containers/map.mdx | 119 ++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 17 deletions(-) diff --git a/src/pages/storey/containers/map.mdx b/src/pages/storey/containers/map.mdx index e7fdf3ab..57f08dc9 100644 --- a/src/pages/storey/containers/map.mdx +++ b/src/pages/storey/containers/map.mdx @@ -22,9 +22,9 @@ Let's say you want to keep track of the balances of each user. You can do this w use cw_storey::containers::{Item, Map}; use cw_storey::CwStorage; -const BALANCES: u8 = 0; +const BALANCES_IX: u8 = 0; -let balances: Map> = Map::new(BALANCES); +let balances: Map> = Map::new(BALANCES_IX); let mut cw_storage = CwStorage(&mut storage); let mut access = balances.access(&mut cw_storage); @@ -37,17 +37,17 @@ assert_eq!(access.entry("alice").get().unwrap(), Some(Uint128::new(1000))); - _line 6:_ Here we construct the `Map` facade. The constructor takes a key, which is the prefix of the keys in the underlying storage backend. -- _line 8:_ The `access` method returns a `MapAccess` entity, which allows manipulating the map. +- _line 8:_ The [`access`] method returns a [`MapAccess`] entity, which allows manipulating the map. - _line 10:_ Here we try to access the balance of `alice`. Since she doesn't have one yet, it - returns `None`. The `entry` method returns an `ItemAccess` entity, which allows manipulating the + returns `None`. The [`entry`] method returns an [`ItemAccess`] entity, which allows manipulating the item stored under the key `alice`. - _line 12:_ Here we set Alice's balance to `1000`. - _line 14:_ We check that the balance is now `1000`. #### Iterating over the balances -Iterating over the balances is pretty straightforward. The `keys` method returns an iterator over -the keys, the `values` method returns an iterator over the values, and the `pairs` method returns an +Iterating over the balances is pretty straightforward. The [`keys`] method returns an iterator over +the keys, the [`values`] method returns an iterator over the values, and the [`pairs`] method returns an iterator over both. ```rust template="storage" showLineNumbers {4, 17, 19} @@ -56,9 +56,9 @@ use cw_storey::CwStorage; use storey::containers::IterableAccessor as _; -const BALANCES: u8 = 1; +const BALANCES_IX: u8 = 1; -let balances: Map> = Map::new(BALANCES); +let balances: Map> = Map::new(BALANCES_IX); let mut cw_storage = CwStorage(&mut storage); let mut access = balances.access(&mut cw_storage); @@ -86,12 +86,44 @@ assert_eq!( ); ``` -- _line 4:_ Here we import the `IterableAccessor` trait. This trait provides unbounded iteration. +- _line 4:_ Here we import the [`IterableAccessor`] trait. This trait provides unbounded iteration. - _line 17:_ The `pairs` method returns an iterator over the key-value pairs. - _line 19:_ Notice the key type is `(String, ())`. This is likely to become just `String` in the future. For now, consider this a quirk of the design. This will make more sense once you get to composite maps. +#### Bounded iteration + +Bounded iteration is also supported in many cases. + +```rust template="storage" showLineNumbers {17} +use cw_storey::containers::{Item, Map}; +use cw_storey::CwStorage; + +use storey::containers::BoundedIterableAccessor as _; + +const BALANCES_IX: u8 = 1; + +let balances: Map> = Map::new(BALANCES_IX); +let mut cw_storage = CwStorage(&mut storage); +let mut access = balances.access(&mut cw_storage); + +access.entry_mut("bob").set(&Uint128::new(500)).unwrap(); +access.entry_mut("carol").set(&Uint128::new(1500)).unwrap(); +access.entry_mut("dave").set(&Uint128::new(2000)).unwrap(); + +assert_eq!( + access.bounded_pairs(Some("bob"), Some("dave")).collect::, _>>().unwrap(), + vec![(("bob".to_string(), ()), Uint128::new(500)), (("carol".to_string(), ()), Uint128::new(1500))] +); +``` + +Here we used the [`bounded_pairs`] method to iterate over some key-value pairs. Other bounded +methods are also available: [`bounded_keys`] and [`bounded_values`]. + +The bounds are provided as arguments to the methods. Currently, the bounds are inclusive on the lower +bound and exclusive on the upper bound. In a future release (soon!) this will be configurable. + ### Keeping balances with composition Alright, let's say this time you'd also like to keep track of the balances of each user, but each @@ -101,9 +133,9 @@ can have multiple different tokens. This is where composition comes in. use cw_storey::containers::{Item, Map}; use cw_storey::CwStorage; -const BALANCES: u8 = 0; +const BALANCES_IX: u8 = 0; -let balances: Map>> = Map::new(BALANCES); +let balances: Map>> = Map::new(BALANCES_IX); let mut cw_storage = CwStorage(&mut storage); let mut access = balances.access(&mut cw_storage); @@ -117,9 +149,9 @@ assert_eq!(access.entry("alice").entry("OSMO").get().unwrap(), Some(Uint128::new This example is similar to the previous one, but this time we have an extra level of nesting. First of all, our type is `Map>>`. The outer map maps user -addresses to an inner map. The inner map maps token denominations to actual balances. +addresses to inner maps. The inner map maps token denominations to actual balances. -When we access the stuff, the first `entry`/`entry_mut` call accesses a record in the outer map, and +When we access the stuff, the first [`entry`]/[`entry_mut`] call accesses a record in the outer map, and the second one accesses a record in the inner map. @@ -130,20 +162,73 @@ the second one accesses a record in the inner map. #### Iterating over the balances -TODO +Let's take a look at what iteration looks like with a composite map. + +```rust template="storage" showLineNumbers {18, 27} +use cw_storey::containers::{Item, Map}; +use cw_storey::CwStorage; +use cosmwasm_std::Order; + +use storey::containers::IterableAccessor as _; + +const BALANCES_IX: u8 = 1; + +let balances: Map>> = Map::new(BALANCES_IX); +let mut cw_storage = CwStorage(&mut storage); +let mut access = balances.access(&mut cw_storage); + +access.entry_mut("alice").entry_mut("USDT").set(&1000).unwrap(); +access.entry_mut("alice").entry_mut("OSMO").set(&2000).unwrap(); +access.entry_mut("bob").entry_mut("USDT").set(&1500).unwrap(); + +assert_eq!( + access.pairs().collect::, _>>().unwrap(), + vec![ + (("bob".into(), ("USDT".into(), ())), 1500), + (("alice".into(), ("OSMO".into(), ())), 2000), + (("alice".into(), ("USDT".into(), ())), 1000), + ] +); + +assert_eq!( + access.entry("alice").pairs().collect::, _>>().unwrap(), + vec![(("OSMO".into(), ()), 2000), (("USDT".into(), ()), 1000)] +); +``` + +Here we iterated twice, but each time we got a different view of the data. Each iteration was at a different level. + +- _line 18:_ We call `pairs` on the outer map, which gives us all the entries. +- _line 27:_ We call `pairs` on the inner map under the key `alice`, which gives us all of Alice's + balances. + +We can of course do the same with the `keys` and `values` methods. When iterating over the entries in a `Map` using the `pairs`, `keys`, and `values` methods, the **order of the keys is not guaranteed to be sensible** (though it is deterministic). If you need a - sensible order, try using the bounded iterators. If they do not exist (traits like - `BoundedIterableAccessor` are not implemented for the accessor), sensibly ordered iteration is not + sensible order, try using the bounded iterators. If they do not exist (`BoundedIterableAccessor` is not implemented for the accessor), sensibly ordered iteration is not possible. -Sensibly ordered iteration is not possible when both of the following conditions are met: +Bounded or sensibly ordered iteration is not possible when both of the following conditions are met: - The key is dynamically sized (e.g. `String`, `Vec`, etc.). - The value type is a collection (`Map`, `Column`, etc.) rather than something like `Item`. +This is why, in the example above, bounded iteration (and a sensible order) is only possible for +the inner map. [`cw-storage-plus`]: /cw-storage-plus/containers/map +[`bounded_pairs`]: https://docs.rs/storey/latest/storey/containers/trait.BoundedIterableAccessor.html#method.bounded_pairs +[`bounded_keys`]: https://docs.rs/storey/latest/storey/containers/trait.BoundedIterableAccessor.html#method.bounded_keys +[`bounded_values`]: https://docs.rs/storey/latest/storey/containers/trait.BoundedIterableAccessor.html#method.bounded_values +[`access`]: https://docs.rs/storey/latest/storey/containers/map/struct.Map.html#method.access +[`entry`]: https://docs.rs/storey/latest/storey/containers/map/struct.MapAccess.html#method.entry +[`entry_mut`]: https://docs.rs/storey/latest/storey/containers/map/struct.MapAccess.html#method.entry_mut +[`keys`]: https://docs.rs/storey/latest/storey/containers/trait.IterableAccessor.html#method.keys +[`values`]: https://docs.rs/storey/latest/storey/containers/trait.IterableAccessor.html#method.values +[`pairs`]: https://docs.rs/storey/latest/storey/containers/trait.IterableAccessor.html#method.pairs +[`ItemAccess`]: https://docs.rs/storey/latest/storey/containers/struct.ItemAccess.html +[`MapAccess`]: https://docs.rs/storey/latest/storey/containers/map/struct.MapAccess.html +[`IterableAccessor`]: https://docs.rs/storey/latest/storey/containers/trait.IterableAccessor.html \ No newline at end of file