-
Notifications
You must be signed in to change notification settings - Fork 356
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
storage-plus: Need better docs and examples for IndexedMap #327
Comments
Yes, this is quite confusing. I need to look at examples to remember how to create them myself. |
The composite key secondary indexes are the most complex use-case we have. I would start with a single-key composite index. It would be much easier to comment on code in a PR. Ideally we have some simple examples as test cases which serve as documentation (and always guaranteed to be up-to-date). Maybe @maurolacy can help on this? |
I will link here to some specific examples, and document them a bit. |
In cosmwasm-plus, there's currently one example of Let's discuss this piece by piece: These are the index definitions. Here there's only one index, called We see that the So, to recap, the Then, it will be indexed by token The important thing here is that the key (and its components, in the case of a combined key) must implement the We can now see how it all works taking a look at the remaining code: This implements the Here During index creation, we must supply an index function per index , which is the one that will take the value and the primary key (always in Besides the index function, we must also supply the namespace of the pk, and the one for the new index. After that, we just create (and return) the Here of course, the namespace of the pk must match the one used during index(es) creation. And, we pass our So, |
Added a detailed description for an The other index type is |
That's an awesome comment. Did you add it to some docs in the repo as well? |
Thanks! No, but I'll do it. |
Great documentation thanks! |
Yes, those are a little more complex. I'll do the same, and document them with some code walk-through. |
Composite indexed map exampleImagine the following situation: we have a number of batches, each stored by its (numeric) batch id, than can change status from The batches also have an associated expiration, after which they must be automatically promoted. Now imagine that we want to process all the pending batches at any given time. Of course, we are only interested in the pending ones that have already expired (so that we can promote them). So, we can build an index over the batches, with a composite key composed of the batch status, and their expiration timestamp. Using the composite key, we'll be discarding both, the already promoted batches, and the pending but not yet expired ones. So, we build the index, generate the composite key, and iterate over all pending batches that have an expiration timestamp that is less than the current time. Here's a code example on how to do this:
/// A Batch is a group of members who got voted in together. We need this to
/// calculate moving from *Paid, Pending Voter* to *Voter*
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
pub struct Batch {
/// Timestamp (seconds) when all members are no longer pending
pub grace_ends_at: u64,
/// How many must still pay in their escrow before the batch is early authorized
pub waiting_escrow: u32,
/// All paid members promoted. We do this once when grace ends or waiting escrow hits 0.
/// Store this one done so we don't loop through that anymore.
pub batch_promoted: bool,
/// List of all members that are part of this batch (look up ESCROWS with these keys)
pub members: Vec<Addr>,
}
// We need a secondary index for batches, such that we can look up batches that have
// not been promoted, ordered by expiration (ascending) from now.
// Index: (U8Key/bool: batch_promoted, U64Key: grace_ends_at) -> U64Key: pk
pub struct BatchIndexes<'a> {
pub promotion_time: MultiIndex<'a, (U8Key, U64Key, U64Key), Batch>,
}
impl<'a> IndexList<Batch> for BatchIndexes<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<Batch>> + '_> {
let v: Vec<&dyn Index<Batch>> = vec![&self.promotion_time];
Box::new(v.into_iter())
}
}
pub fn batches<'a>() -> IndexedMap<'a, U64Key, Batch, BatchIndexes<'a>> {
let indexes = BatchIndexes {
promotion_time: MultiIndex::new(
|b: &Batch, pk: Vec<u8>| {
let promoted = if b.batch_promoted { 1u8 } else { 0u8 };
(promoted.into(), b.grace_ends_at.into(), pk.into())
},
"batch",
"batch__promotion",
),
};
IndexedMap::new("batch", indexes)
} This example is similar to the previous one, above. The only differences are:
Now, here's how to use the indexed data: let batch_map = batches();
// Limit to batches that have not yet been promoted (0), using sub_prefix.
// Iterate which have expired at or less than the current time (now), using a bound.
// These are all eligible for timeout-based promotion
let now = block.time.nanos() / 1_000_000_000;
// as we want to keep the last item (pk) unbounded, we increment time by 1 and use exclusive (below the next tick)
let max_key = (U64Key::from(now + 1), U64Key::from(0)).joined_key();
let bound = Bound::Exclusive(max_key);
let ready = batch_map
.idx
.promotion_time
.sub_prefix(0u8.into())
.range(storage, None, Some(bound), Order::Ascending)
.collect::<StdResult<Vec<_>>>()?; A couple of comments:
That's it. After that, we can iterate ovet the results and change their status from |
Great documentation! |
Did this last (amazing) comment make it into a markdown file? |
|
I will. |
Ah, already done, thanks @orkunkl. |
I am testing out
IndexedMap
as workshop and article content.I found
Index
and prefixes under documented and not enough tests to understand as a noobie.For example here is a sample code I written to show an CompositeKey example.
Tokens are secondary indexed by (admin, ticker) composite key.
Here are some tests to show what I want to achieve.
Can you help me understand this case with few examples maybe?
The text was updated successfully, but these errors were encountered: