cw_paginate_storage/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use cosmwasm_std::{Deps, Order, StdResult};
4
5#[allow(unused_imports)]
6use cw_storage_plus::{Bound, Bounder, KeyDeserialize, Map, SnapshotMap, Strategy};
7
8/// Generic function for paginating a list of (K, V) pairs in a
9/// CosmWasm Map.
10pub fn paginate_map<'a, 'b, K, V, R: 'static>(
11    deps: Deps,
12    map: &Map<'a, K, V>,
13    start_after: Option<K>,
14    limit: Option<u32>,
15    order: Order,
16) -> StdResult<Vec<(R, V)>>
17where
18    K: Bounder<'a> + KeyDeserialize<Output = R> + 'b,
19    V: serde::de::DeserializeOwned + serde::Serialize,
20{
21    let (range_min, range_max) = match order {
22        Order::Ascending => (start_after.map(Bound::exclusive), None),
23        Order::Descending => (None, start_after.map(Bound::exclusive)),
24    };
25
26    let items = map.range(deps.storage, range_min, range_max, order);
27    match limit {
28        Some(limit) => Ok(items
29            .take(limit.try_into().unwrap())
30            .collect::<StdResult<_>>()?),
31        None => Ok(items.collect::<StdResult<_>>()?),
32    }
33}
34
35/// Same as `paginate_map` but only returns the keys.
36pub fn paginate_map_keys<'a, 'b, K, V, R: 'static>(
37    deps: Deps,
38    map: &Map<'a, K, V>,
39    start_after: Option<K>,
40    limit: Option<u32>,
41    order: Order,
42) -> StdResult<Vec<R>>
43where
44    K: Bounder<'a> + KeyDeserialize<Output = R> + 'b,
45    V: serde::de::DeserializeOwned + serde::Serialize,
46{
47    let (range_min, range_max) = match order {
48        Order::Ascending => (start_after.map(Bound::exclusive), None),
49        Order::Descending => (None, start_after.map(Bound::exclusive)),
50    };
51
52    let items = map.keys(deps.storage, range_min, range_max, order);
53    match limit {
54        Some(limit) => Ok(items
55            .take(limit.try_into().unwrap())
56            .collect::<StdResult<_>>()?),
57        None => Ok(items.collect::<StdResult<_>>()?),
58    }
59}
60
61/// Same as `paginate_map` but for use with `SnapshotMap`.
62pub fn paginate_snapshot_map<'a, 'b, K, V, R: 'static>(
63    deps: Deps,
64    map: &SnapshotMap<'a, K, V>,
65    start_after: Option<K>,
66    limit: Option<u32>,
67    order: Order,
68) -> StdResult<Vec<(R, V)>>
69where
70    K: Bounder<'a> + KeyDeserialize<Output = R> + 'b,
71    V: serde::de::DeserializeOwned + serde::Serialize,
72{
73    let (range_min, range_max) = match order {
74        Order::Ascending => (start_after.map(Bound::exclusive), None),
75        Order::Descending => (None, start_after.map(Bound::exclusive)),
76    };
77
78    let items = map.range(deps.storage, range_min, range_max, order);
79    match limit {
80        Some(limit) => Ok(items
81            .take(limit.try_into().unwrap())
82            .collect::<StdResult<_>>()?),
83        None => Ok(items.collect::<StdResult<_>>()?),
84    }
85}
86
87/// Same as `paginate_map` but only returns the values.
88pub fn paginate_map_values<'a, K, V>(
89    deps: Deps,
90    map: &Map<'a, K, V>,
91    start_after: Option<K>,
92    limit: Option<u32>,
93    order: Order,
94) -> StdResult<Vec<V>>
95where
96    K: Bounder<'a> + KeyDeserialize<Output = K> + 'static,
97    V: serde::de::DeserializeOwned + serde::Serialize,
98{
99    let (range_min, range_max) = match order {
100        Order::Ascending => (start_after.map(Bound::exclusive), None),
101        Order::Descending => (None, start_after.map(Bound::exclusive)),
102    };
103
104    let items = map
105        .range(deps.storage, range_min, range_max, order)
106        .map(|kv| Ok(kv?.1));
107
108    match limit {
109        Some(limit) => Ok(items
110            .take(limit.try_into().unwrap())
111            .collect::<StdResult<_>>()?),
112        None => Ok(items.collect::<StdResult<_>>()?),
113    }
114}
115
116/// Same as `paginate_map` but only returns the keys. For use with
117/// `SnaphotMap`.
118pub fn paginate_snapshot_map_keys<'a, 'b, K, V, R: 'static>(
119    deps: Deps,
120    map: &SnapshotMap<'a, K, V>,
121    start_after: Option<K>,
122    limit: Option<u32>,
123    order: Order,
124) -> StdResult<Vec<R>>
125where
126    K: Bounder<'a> + KeyDeserialize<Output = R> + 'b,
127    V: serde::de::DeserializeOwned + serde::Serialize,
128{
129    let (range_min, range_max) = match order {
130        Order::Ascending => (start_after.map(Bound::exclusive), None),
131        Order::Descending => (None, start_after.map(Bound::exclusive)),
132    };
133
134    let items = map.keys(deps.storage, range_min, range_max, order);
135    match limit {
136        Some(limit) => Ok(items
137            .take(limit.try_into().unwrap())
138            .collect::<StdResult<_>>()?),
139        None => Ok(items.collect::<StdResult<_>>()?),
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use cosmwasm_std::testing::{mock_dependencies, mock_env};
147    use cosmwasm_std::{Addr, Uint128};
148
149    #[test]
150    fn pagination() {
151        let mut deps = mock_dependencies();
152        let map: Map<String, String> = Map::new("items");
153
154        for num in 1..3 {
155            map.save(&mut deps.storage, num.to_string(), &(num * 2).to_string())
156                .unwrap();
157        }
158
159        let items = paginate_map(deps.as_ref(), &map, None, None, Order::Descending).unwrap();
160        assert_eq!(
161            items,
162            vec![
163                ("2".to_string(), "4".to_string()),
164                ("1".to_string(), "2".to_string())
165            ]
166        );
167
168        let items = paginate_map(deps.as_ref(), &map, None, None, Order::Ascending).unwrap();
169        assert_eq!(
170            items,
171            vec![
172                ("1".to_string(), "2".to_string()),
173                ("2".to_string(), "4".to_string())
174            ]
175        );
176
177        let items = paginate_map(
178            deps.as_ref(),
179            &map,
180            Some("1".to_string()),
181            None,
182            Order::Ascending,
183        )
184        .unwrap();
185        assert_eq!(items, vec![("2".to_string(), "4".to_string())]);
186
187        let items = paginate_map(deps.as_ref(), &map, None, Some(1), Order::Ascending).unwrap();
188        assert_eq!(items, vec![("1".to_string(), "2".to_string())]);
189    }
190
191    #[test]
192    fn key_pagination() {
193        let mut deps = mock_dependencies();
194        let map: Map<String, String> = Map::new("items");
195
196        for num in 1..3 {
197            map.save(&mut deps.storage, num.to_string(), &(num * 2).to_string())
198                .unwrap();
199        }
200
201        let items = paginate_map_keys(deps.as_ref(), &map, None, None, Order::Descending).unwrap();
202        assert_eq!(items, vec!["2".to_string(), "1".to_string()]);
203
204        let items = paginate_map_keys(deps.as_ref(), &map, None, None, Order::Ascending).unwrap();
205        assert_eq!(items, vec!["1".to_string(), "2".to_string()]);
206
207        let items = paginate_map_keys(
208            deps.as_ref(),
209            &map,
210            Some("1".to_string()),
211            None,
212            Order::Ascending,
213        )
214        .unwrap();
215        assert_eq!(items, vec!["2"]);
216
217        let items =
218            paginate_map_keys(deps.as_ref(), &map, None, Some(1), Order::Ascending).unwrap();
219        assert_eq!(items, vec!["1".to_string()]);
220    }
221
222    // this test will double check the descending keys with the rewrite
223    #[test]
224    fn key_pagination_test2() {
225        let mut deps = mock_dependencies();
226        let map: Map<u32, String> = Map::new("items");
227
228        for num in 1u32..=10 {
229            map.save(&mut deps.storage, num, &(num * 2).to_string())
230                .unwrap();
231        }
232
233        let items = paginate_map_keys(deps.as_ref(), &map, None, None, Order::Descending).unwrap();
234        assert_eq!(items, vec![10, 9, 8, 7, 6, 5, 4, 3, 2, 1]);
235
236        let items = paginate_map_keys(deps.as_ref(), &map, None, None, Order::Ascending).unwrap();
237        assert_eq!(items, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
238
239        let items =
240            paginate_map_keys(deps.as_ref(), &map, Some(3), Some(3), Order::Ascending).unwrap();
241        assert_eq!(items, vec![4, 5, 6]);
242
243        let items =
244            paginate_map_keys(deps.as_ref(), &map, Some(7), Some(4), Order::Descending).unwrap();
245        assert_eq!(items, vec![6, 5, 4, 3]);
246    }
247
248    #[test]
249    fn snapshot_pagination() {
250        let mut deps = mock_dependencies();
251        let env = mock_env();
252
253        let map: SnapshotMap<&Addr, Uint128> = SnapshotMap::new(
254            "items",
255            "items__checkpoints",
256            "items__changelog",
257            Strategy::EveryBlock,
258        );
259
260        for ctr in 1..100 {
261            let addr = Addr::unchecked(format!("test_addr{:0>3}", ctr.clone()));
262            map.save(
263                &mut deps.storage,
264                &addr,
265                &Uint128::new(ctr),
266                env.block.height,
267            )
268            .unwrap();
269        }
270
271        // grab first 10 items
272        let items =
273            paginate_snapshot_map(deps.as_ref(), &map, None, Some(10), Order::Ascending).unwrap();
274
275        assert_eq!(items.len(), 10);
276
277        let mut test_vec: Vec<(Addr, Uint128)> = vec![];
278        for ctr in 1..=10 {
279            let addr = Addr::unchecked(format!("test_addr{:0>3}", ctr.clone()));
280
281            test_vec.push((addr, Uint128::new(ctr)));
282        }
283        assert_eq!(items, test_vec);
284
285        // using the last result of the last item (10), grab the next one
286        let items = paginate_snapshot_map(
287            deps.as_ref(),
288            &map,
289            Some(&items[items.len() - 1].0),
290            Some(10),
291            Order::Ascending,
292        )
293        .unwrap();
294
295        // should be the 11th item
296        assert_eq!(items[0].0, Addr::unchecked("test_addr011".to_string()));
297        assert_eq!(items[0].1, Uint128::new(11));
298
299        let items =
300            paginate_snapshot_map(deps.as_ref(), &map, None, None, Order::Descending).unwrap();
301
302        // 20th item (19 index) should be 80
303        assert_eq!(items[19].0, Addr::unchecked("test_addr080".to_string()));
304        assert_eq!(items[19].1, Uint128::new(80));
305    }
306
307    // this test will encapsulate the generic changes for &Addr
308    #[test]
309    fn snapshot_pagination_keys_new_generic() {
310        let mut deps = mock_dependencies();
311        let env = mock_env();
312
313        let map: SnapshotMap<&Addr, Uint128> = SnapshotMap::new(
314            "items",
315            "items__checkpoints",
316            "items__changelog",
317            Strategy::EveryBlock,
318        );
319
320        for ctr in 1..100 {
321            let addr = Addr::unchecked(format!("test_addr{:0>3}", ctr.clone()));
322            map.save(
323                &mut deps.storage,
324                &addr,
325                &Uint128::new(ctr),
326                env.block.height,
327            )
328            .unwrap();
329        }
330
331        // grab first 10 items
332        let items =
333            paginate_snapshot_map_keys(deps.as_ref(), &map, None, Some(10), Order::Ascending)
334                .unwrap();
335
336        assert_eq!(items.len(), 10);
337
338        let mut test_vec: Vec<Addr> = vec![];
339        for ctr in 1..=10 {
340            let addr = Addr::unchecked(format!("test_addr{:0>3}", ctr.clone()));
341
342            test_vec.push(addr);
343        }
344        assert_eq!(items, test_vec);
345
346        // max item from before was the 10th, so it'll go backwards from 9->1
347        let items = paginate_snapshot_map_keys(
348            deps.as_ref(),
349            &map,
350            Some(&items[items.len() - 1]),
351            None,
352            Order::Descending,
353        )
354        .unwrap();
355
356        // 3rd item in vec should be 006
357        assert_eq!(items[3], Addr::unchecked("test_addr006".to_string()));
358
359        let items =
360            paginate_snapshot_map_keys(deps.as_ref(), &map, None, None, Order::Descending).unwrap();
361
362        // 20th item (19 index) should be 80
363        assert_eq!(items[19], Addr::unchecked("test_addr080".to_string()));
364    }
365
366    #[test]
367    fn snapshot_pagination_keys() {
368        let mut deps = mock_dependencies();
369        let env = mock_env();
370
371        let map: SnapshotMap<u32, Uint128> = SnapshotMap::new(
372            "items",
373            "items__checkpoints",
374            "items__changelog",
375            Strategy::EveryBlock,
376        );
377
378        for ctr in 1..=100 {
379            map.save(
380                &mut deps.storage,
381                ctr,
382                &Uint128::new(<u32 as std::convert::Into<u128>>::into(ctr)),
383                env.block.height,
384            )
385            .unwrap();
386        }
387
388        // grab first 10 items
389        let items =
390            paginate_snapshot_map_keys(deps.as_ref(), &map, None, Some(10), Order::Ascending)
391                .unwrap();
392
393        assert_eq!(items.len(), 10);
394        assert_eq!(items, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
395
396        let items =
397            paginate_snapshot_map_keys(deps.as_ref(), &map, Some(50), Some(10), Order::Ascending)
398                .unwrap();
399
400        assert_eq!(items, vec![51, 52, 53, 54, 55, 56, 57, 58, 59, 60]);
401
402        let items =
403            paginate_snapshot_map_keys(deps.as_ref(), &map, Some(50), Some(10), Order::Descending)
404                .unwrap();
405
406        assert_eq!(items, vec![49, 48, 47, 46, 45, 44, 43, 42, 41, 40]);
407    }
408
409    #[test]
410    fn pagination_order_desc_tests() {
411        let mut deps = mock_dependencies();
412        let map: Map<u32, u32> = Map::new("items");
413
414        map.save(&mut deps.storage, 1, &40).unwrap();
415        map.save(&mut deps.storage, 2, &22).unwrap();
416        map.save(&mut deps.storage, 3, &77).unwrap();
417        map.save(&mut deps.storage, 4, &66).unwrap();
418        map.save(&mut deps.storage, 5, &0).unwrap();
419
420        let items = paginate_map(deps.as_ref(), &map, None, None, Order::Descending).unwrap();
421        assert_eq!(items, vec![(5, 0), (4, 66), (3, 77), (2, 22), (1, 40)]);
422
423        let items = paginate_map(deps.as_ref(), &map, Some(3), None, Order::Descending).unwrap();
424        assert_eq!(items, vec![(2, 22), (1, 40)]);
425
426        let items = paginate_map(deps.as_ref(), &map, Some(1), None, Order::Descending).unwrap();
427        assert_eq!(items, vec![]);
428    }
429
430    /// testing reworked paginate_map and paginate_map_keys.
431    /// pay particular attention to the values added. this is to ensure
432    /// that the values arent being assessed
433    #[test]
434    fn pagination_keys_refs() {
435        let mut deps = mock_dependencies();
436        let map: Map<&Addr, u32> = Map::new("items");
437
438        map.save(
439            &mut deps.storage,
440            &Addr::unchecked(format!("test_addr{:0>3}", 1)),
441            &40,
442        )
443        .unwrap();
444        map.save(
445            &mut deps.storage,
446            &Addr::unchecked(format!("test_addr{:0>3}", 2)),
447            &22,
448        )
449        .unwrap();
450        map.save(
451            &mut deps.storage,
452            &Addr::unchecked(format!("test_addr{:0>3}", 3)),
453            &77,
454        )
455        .unwrap();
456        map.save(
457            &mut deps.storage,
458            &Addr::unchecked(format!("test_addr{:0>3}", 4)),
459            &66,
460        )
461        .unwrap();
462        map.save(
463            &mut deps.storage,
464            &Addr::unchecked(format!("test_addr{:0>3}", 5)),
465            &0,
466        )
467        .unwrap();
468
469        let items = paginate_map_keys(deps.as_ref(), &map, None, None, Order::Descending).unwrap();
470        assert_eq!(items[1], Addr::unchecked(format!("test_addr{:0>3}", 4)));
471        assert_eq!(items[4], Addr::unchecked(format!("test_addr{:0>3}", 1)));
472
473        let addr: Addr = Addr::unchecked(format!("test_addr{:0>3}", 3));
474        let items =
475            paginate_map_keys(deps.as_ref(), &map, Some(&addr), None, Order::Ascending).unwrap();
476        assert_eq!(items[0], Addr::unchecked(format!("test_addr{:0>3}", 4)));
477    }
478
479    /// testing reworked paginate_map and paginate_map_keys.
480    /// pay particular attention to the values added. this is to ensure
481    /// that the values arent being assessed
482    #[test]
483    fn pagination_refs() {
484        let mut deps = mock_dependencies();
485        let map: Map<&Addr, u32> = Map::new("items");
486
487        map.save(
488            &mut deps.storage,
489            &Addr::unchecked(format!("test_addr{:0>3}", 1)),
490            &0,
491        )
492        .unwrap();
493        map.save(
494            &mut deps.storage,
495            &Addr::unchecked(format!("test_addr{:0>3}", 2)),
496            &22,
497        )
498        .unwrap();
499        map.save(
500            &mut deps.storage,
501            &Addr::unchecked(format!("test_addr{:0>3}", 3)),
502            &77,
503        )
504        .unwrap();
505        map.save(
506            &mut deps.storage,
507            &Addr::unchecked(format!("test_addr{:0>3}", 4)),
508            &66,
509        )
510        .unwrap();
511        map.save(
512            &mut deps.storage,
513            &Addr::unchecked(format!("test_addr{:0>3}", 6)),
514            &0,
515        )
516        .unwrap();
517
518        let items = paginate_map(deps.as_ref(), &map, None, None, Order::Descending).unwrap();
519        assert_eq!(
520            items[1],
521            (Addr::unchecked(format!("test_addr{:0>3}", 4)), 66)
522        );
523        assert_eq!(
524            items[4],
525            (Addr::unchecked(format!("test_addr{:0>3}", 1)), 0)
526        );
527
528        let addr: Addr = Addr::unchecked(format!("test_addr{:0>3}", 3));
529        let items =
530            paginate_map(deps.as_ref(), &map, Some(&addr), Some(2), Order::Ascending).unwrap();
531        let test_vec: Vec<(Addr, u32)> = vec![
532            (Addr::unchecked(format!("test_addr{:0>3}", 4)), 66),
533            (Addr::unchecked(format!("test_addr{:0>3}", 6)), 0),
534        ];
535        assert_eq!(items, test_vec);
536    }
537}