Add translate API for storage values (#3947)

* Add translate item.

* fix

* doc

* fix doc

* A test added.

* Apply suggestions from code review

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* address suggestion
This commit is contained in:
Gavin Wood
2019-10-31 09:24:23 +01:00
committed by Bastian Köcher
parent a73334f769
commit 073040a053
3 changed files with 90 additions and 2 deletions
@@ -30,3 +30,52 @@ pub use linked_map::{StorageLinkedMap, Enumerator, Linkage};
pub use map::StorageMap;
pub use double_map::StorageDoubleMap;
pub use value::StorageValue;
#[cfg(test)]
#[allow(dead_code)]
mod tests {
use runtime_io::TestExternalities;
use codec::Encode;
use crate::storage::{unhashed, generator::StorageValue};
struct Runtime {}
pub trait Trait {
type Origin;
type BlockNumber;
}
impl Trait for Runtime {
type Origin = u32;
type BlockNumber = u32;
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {}
}
crate::decl_storage! {
trait Store for Module<T: Trait> as Runtime {
Value get(fn value) config(): (u64, u64);
}
}
#[test]
fn value_translate_works() {
let t = GenesisConfig::default().build_storage().unwrap();
TestExternalities::new(t).execute_with(|| {
// put the old value `1111u32` in the storage.
let key = Value::storage_value_final_key();
unhashed::put_raw(&key, &1111u32.encode());
// translate
let translate_fn = |old: Option<u32>| -> Option<(u64, u64)> {
old.map(|o| (o.into(), (o*2).into()))
};
let _ = Value::translate(translate_fn);
// new storage should be `(1111, 1111 * 2)`
assert_eq!(Value::get(), (1111, 2222));
})
}
}
@@ -16,7 +16,7 @@
#[cfg(not(feature = "std"))]
use rstd::prelude::*;
use codec::{FullCodec, Encode, EncodeAppend, EncodeLike};
use codec::{FullCodec, Encode, EncodeAppend, EncodeLike, Decode};
use crate::{storage::{self, unhashed}, hash::{Twox128, StorageHasher}, traits::Len};
/// Generator for `StorageValue` used by `decl_storage`.
@@ -60,6 +60,23 @@ impl<T: FullCodec, G: StorageValue<T>> storage::StorageValue<T> for G {
G::from_optional_value_to_query(value)
}
fn translate<O: Decode, F: FnOnce(Option<O>) -> Option<T>>(f: F) -> Result<Option<T>, ()> {
let key = Self::storage_value_final_key();
// attempt to get the length directly.
let maybe_old = match unhashed::get_raw(&key) {
Some(old_data) => Some(O::decode(&mut &old_data[..]).map_err(|_| ())?),
None => None,
};
let maybe_new = f(maybe_old);
if let Some(new) = maybe_new.as_ref() {
new.using_encoded(|d| unhashed::put_raw(&key, d));
} else {
unhashed::kill(&key);
}
Ok(maybe_new)
}
fn put<Arg: EncodeLike<T>>(val: Arg) {
unhashed::put(&Self::storage_value_final_key(), &val)
}
+23 -1
View File
@@ -17,7 +17,7 @@
//! Stuff to do with the runtime's storage.
use rstd::prelude::*;
use codec::{FullCodec, FullEncode, Encode, EncodeAppend, EncodeLike};
use codec::{FullCodec, FullEncode, Encode, EncodeAppend, EncodeLike, Decode};
use crate::traits::Len;
#[macro_use]
@@ -41,6 +41,28 @@ pub trait StorageValue<T: FullCodec> {
/// Load the value from the provided storage instance.
fn get() -> Self::Query;
/// Translate a value from some previous type (`O`) to the current type.
///
/// `f: F` is the translation function.
///
/// Returns `Err` if the storage item could not be interpreted as the old type, and Ok, along
/// with the new value if it could.
///
/// NOTE: This operates from and to `Option<_>` types; no effort is made to respect the default
/// value of the original type.
///
/// # Warning
///
/// This function must be used with care, before being updated the storage still contains the
/// old type, thus other calls (such as `get`) will fail at decoding it.
///
/// # Usage
///
/// This would typically be called inside the module implementation of on_initialize, while
/// ensuring **no usage of this storage are made before the call to `on_initialize`**. (More
/// precisely prior initialized modules doesn't make use of this storage).
fn translate<O: Decode, F: FnOnce(Option<O>) -> Option<T>>(f: F) -> Result<Option<T>, ()>;
/// Store a value under this key into the provided storage instance.
fn put<Arg: EncodeLike<T>>(val: Arg);