Skip to content

Commit

Permalink
Frame Add translate_next
Browse files Browse the repository at this point in the history
This works similarly to to `translate` but only translate a single entry.
This function will be useful in the context of multi-block migration.
  • Loading branch information
pgherveou committed Apr 28, 2023
1 parent e94cb0d commit b878662
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 1 deletion.
37 changes: 37 additions & 0 deletions frame/support/src/storage/generator/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,43 @@ where
}
}
}

fn translate_next<O: Decode, F: FnMut(K, O) -> Option<V>>(
previous_key: Option<Vec<u8>>,
mut f: F,
) -> (Option<Vec<u8>>, Result<(), ()>) {
let prefix = G::prefix_hash();
let previous_key = previous_key.unwrap_or_else(|| prefix.clone());

match sp_io::storage::next_key(&previous_key).filter(|n| n.starts_with(&prefix)) {
Some(current_key) => {
let value = match unhashed::get::<O>(&current_key) {
Some(value) => value,
None => {
log::error!("Invalid translate: fail to decode old value");
return (Some(current_key), Err(()))
},
};

let mut key_material = G::Hasher::reverse(&current_key[prefix.len()..]);
let key = match K::decode(&mut key_material) {
Ok(key) => key,
Err(_) => {
log::error!("Invalid translate: fail to decode key");
return (Some(current_key), Err(()))
},
};

match f(key, value) {
Some(new) => unhashed::put::<V>(&current_key, &new),
None => unhashed::kill(&current_key),
}

(Some(current_key), Ok(()))
},
None => (None, Ok(())),
}
}
}

impl<K: FullEncode, V: FullCodec, G: StorageMap<K, V>> storage::StorageMap<K, V> for G {
Expand Down
11 changes: 11 additions & 0 deletions frame/support/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,17 @@ pub trait IterableStorageMap<K: FullEncode, V: FullCodec>: StorageMap<K, V> {
///
/// NOTE: If a value fail to decode because storage is corrupted then it is skipped.
fn translate<O: Decode, F: FnMut(K, O) -> Option<V>>(f: F);

/// Translate the next entry following `previous_key` by a function `f`.
/// By returning `None` from `f` for an element, you'll remove it from the map.
///
/// Returns the next key to iterate from in lexicographical order of the encoded key.
/// and whether or not the translation failed because either the old key or value was
/// corrupted.
fn translate_next<O: Decode, F: FnMut(K, O) -> Option<V>>(
previous_key: Option<Vec<u8>>,
f: F,
) -> (Option<Vec<u8>>, Result<(), ()>);
}

/// A strongly-typed double map in storage whose secondary keys and values can be iterated over.
Expand Down
11 changes: 10 additions & 1 deletion frame/support/src/storage/types/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ mod test {
use crate::{
hash::*,
metadata_ir::{StorageEntryModifierIR, StorageEntryTypeIR, StorageHasherIR},
storage::types::ValueQuery,
storage::{types::ValueQuery, IterableStorageMap},
};
use sp_io::{hashing::twox_128, TestExternalities};

Expand Down Expand Up @@ -700,6 +700,15 @@ mod test {
A::translate::<u8, _>(|k, v| Some((k * v as u16).into()));
assert_eq!(A::iter().collect::<Vec<_>>(), vec![(4, 40), (3, 30)]);

let translate_next = |k: u16, v: u8| Some((v as u16 / k).into());
let (k, _) = A::translate_next::<u8, _>(None, translate_next);
let (k, _) = A::translate_next::<u8, _>(k, translate_next);
assert_eq!((None, Ok(())), A::translate_next::<u8, _>(k, translate_next));
assert_eq!(A::iter().collect::<Vec<_>>(), vec![(4, 10), (3, 10)]);

let _ = A::translate_next::<u8, _>(None, |_, _| None);
assert_eq!(A::iter().collect::<Vec<_>>(), vec![(3, 10)]);

let mut entries = vec![];
A::build_metadata(vec![], &mut entries);
AValueQueryWithAnOnEmpty::build_metadata(vec![], &mut entries);
Expand Down

0 comments on commit b878662

Please sign in to comment.