Skip to main content

linera_views/views/
register_view.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use allocative::Allocative;
5#[cfg(with_metrics)]
6use linera_base::prometheus_util::MeasureLatency as _;
7use serde::{de::DeserializeOwned, Serialize};
8
9use crate::{
10    batch::Batch,
11    common::{from_bytes_option_or_default, HasherOutput},
12    context::Context,
13    hashable_wrapper::WrappedHashableContainerView,
14    views::{ClonableView, HashableView, Hasher, ReplaceContext, View},
15    ViewError,
16};
17
18#[cfg(with_metrics)]
19pub(crate) mod metrics {
20    use linera_base::prometheus_util::{exponential_bucket_latencies, register_histogram_vec};
21    use prometheus::HistogramVec;
22
23    linera_base::declare_metrics! {
24        /// The runtime of hash computation
25        pub static REGISTER_VIEW_HASH_RUNTIME: HistogramVec =
26            register_histogram_vec(
27                "register_view_hash_runtime",
28                "RegisterView hash runtime",
29                &[],
30                exponential_bucket_latencies(5.0),
31            );
32    }
33}
34
35/// A view that supports modifying a single value of type `T`.
36#[derive(Debug, Allocative)]
37#[allocative(bound = "C, T: Allocative")]
38pub struct RegisterView<C, T> {
39    /// Whether to clear storage before applying updates.
40    delete_storage_first: bool,
41    /// The view context.
42    #[allocative(skip)]
43    context: C,
44    /// The value persisted in storage.
45    stored_value: Box<T>,
46    /// Pending update not yet persisted to storage.
47    update: Option<Box<T>>,
48}
49
50impl<C, T, C2> ReplaceContext<C2> for RegisterView<C, T>
51where
52    C: Context,
53    C2: Context,
54    T: Default + Send + Sync + Serialize + DeserializeOwned + Clone,
55{
56    type Target = RegisterView<C2, T>;
57
58    async fn with_context(
59        &mut self,
60        ctx: impl FnOnce(&Self::Context) -> C2 + Clone,
61    ) -> Self::Target {
62        RegisterView {
63            delete_storage_first: self.delete_storage_first,
64            context: ctx(&self.context),
65            stored_value: self.stored_value.clone(),
66            update: self.update.clone(),
67        }
68    }
69}
70
71impl<C, T> View for RegisterView<C, T>
72where
73    C: Context,
74    T: Default + Send + Sync + Serialize + DeserializeOwned,
75{
76    const NUM_INIT_KEYS: usize = 1;
77
78    type Context = C;
79
80    fn context(&self) -> C {
81        self.context.clone()
82    }
83
84    fn pre_load(context: &C) -> Result<Vec<Vec<u8>>, ViewError> {
85        Ok(vec![context.base_key().bytes.clone()])
86    }
87
88    fn post_load(context: C, values: &[Option<Vec<u8>>]) -> Result<Self, ViewError> {
89        let value =
90            from_bytes_option_or_default(values.first().ok_or(ViewError::PostLoadValuesError)?)?;
91        let stored_value = Box::new(value);
92        Ok(Self {
93            delete_storage_first: false,
94            context,
95            stored_value,
96            update: None,
97        })
98    }
99
100    fn rollback(&mut self) {
101        self.delete_storage_first = false;
102        self.update = None;
103    }
104
105    async fn has_pending_changes(&self) -> bool {
106        if self.delete_storage_first {
107            return true;
108        }
109        self.update.is_some()
110    }
111
112    fn pre_save(&self, batch: &mut Batch) -> Result<bool, ViewError> {
113        let mut delete_view = false;
114        if self.delete_storage_first {
115            batch.delete_key(self.context.base_key().bytes.clone());
116            delete_view = true;
117        } else if let Some(value) = &self.update {
118            let key = self.context.base_key().bytes.clone();
119            batch.put_key_value(key, value)?;
120        }
121        Ok(delete_view)
122    }
123
124    fn post_save(&mut self) {
125        if self.delete_storage_first {
126            *self.stored_value = Default::default();
127        } else if let Some(value) = self.update.take() {
128            self.stored_value = value;
129        }
130        self.delete_storage_first = false;
131        self.update = None;
132    }
133
134    fn clear(&mut self) {
135        self.delete_storage_first = true;
136        self.update = Some(Box::default());
137    }
138}
139
140impl<C, T> ClonableView for RegisterView<C, T>
141where
142    C: Context,
143    T: Clone + Default + Send + Sync + Serialize + DeserializeOwned,
144{
145    fn clone_unchecked(&mut self) -> Result<Self, ViewError> {
146        Ok(RegisterView {
147            delete_storage_first: self.delete_storage_first,
148            context: self.context.clone(),
149            stored_value: self.stored_value.clone(),
150            update: self.update.clone(),
151        })
152    }
153}
154
155impl<C, T> RegisterView<C, T>
156where
157    C: Context,
158{
159    /// Access the current value in the register.
160    /// ```rust
161    /// # tokio_test::block_on(async {
162    /// # use linera_views::context::MemoryContext;
163    /// # use linera_views::register_view::RegisterView;
164    /// # use linera_views::views::View;
165    /// # let context = MemoryContext::new_for_testing(());
166    /// let mut register = RegisterView::<_, u32>::load(context).await.unwrap();
167    /// let value = register.get();
168    /// assert_eq!(*value, 0);
169    /// # })
170    /// ```
171    pub fn get(&self) -> &T {
172        match &self.update {
173            None => &self.stored_value,
174            Some(value) => value,
175        }
176    }
177
178    /// Sets the value in the register.
179    /// ```rust
180    /// # tokio_test::block_on(async {
181    /// # use linera_views::context::MemoryContext;
182    /// # use linera_views::register_view::RegisterView;
183    /// # use linera_views::views::View;
184    /// # let context = MemoryContext::new_for_testing(());
185    /// let mut register = RegisterView::load(context).await.unwrap();
186    /// register.set(5);
187    /// let value = register.get();
188    /// assert_eq!(*value, 5);
189    /// # })
190    /// ```
191    pub fn set(&mut self, value: T) {
192        self.delete_storage_first = false;
193        self.update = Some(Box::new(value));
194    }
195
196    /// Obtains the extra data.
197    pub fn extra(&self) -> &C::Extra {
198        self.context.extra()
199    }
200}
201
202impl<C, T> RegisterView<C, T>
203where
204    C: Context,
205    T: Clone + Serialize,
206{
207    /// Obtains a mutable reference to the value in the register.
208    /// ```rust
209    /// # tokio_test::block_on(async {
210    /// # use linera_views::context::MemoryContext;
211    /// # use linera_views::register_view::RegisterView;
212    /// # use linera_views::views::View;
213    /// # let context = MemoryContext::new_for_testing(());
214    /// let mut register: RegisterView<_, u32> = RegisterView::load(context).await.unwrap();
215    /// let value = register.get_mut();
216    /// assert_eq!(*value, 0);
217    /// # })
218    /// ```
219    pub fn get_mut(&mut self) -> &mut T {
220        self.delete_storage_first = false;
221        self.update.get_or_insert_with(|| self.stored_value.clone())
222    }
223
224    fn compute_hash(&self) -> Result<<sha3::Sha3_256 as Hasher>::Output, ViewError> {
225        #[cfg(with_metrics)]
226        let _hash_latency = metrics::REGISTER_VIEW_HASH_RUNTIME.measure_latency();
227        let mut hasher = sha3::Sha3_256::default();
228        hasher.update_with_bcs_bytes(self.get())?;
229        Ok(hasher.finalize())
230    }
231}
232
233impl<C, T> HashableView for RegisterView<C, T>
234where
235    C: Context,
236    T: Clone + Default + Send + Sync + Serialize + DeserializeOwned,
237{
238    type Hasher = sha3::Sha3_256;
239
240    async fn hash_mut(&mut self) -> Result<<Self::Hasher as Hasher>::Output, ViewError> {
241        self.compute_hash()
242    }
243
244    async fn hash(&self) -> Result<<Self::Hasher as Hasher>::Output, ViewError> {
245        self.compute_hash()
246    }
247}
248
249/// Type wrapping `RegisterView` while memoizing the hash.
250pub type HashedRegisterView<C, T> =
251    WrappedHashableContainerView<C, RegisterView<C, T>, HasherOutput>;
252
253#[cfg(with_graphql)]
254mod graphql {
255    use std::borrow::Cow;
256
257    use super::RegisterView;
258    use crate::context::Context;
259
260    impl<C, T> async_graphql::OutputType for RegisterView<C, T>
261    where
262        C: Context,
263        T: async_graphql::OutputType + Send + Sync,
264    {
265        fn type_name() -> Cow<'static, str> {
266            T::type_name()
267        }
268
269        fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
270            T::create_type_info(registry)
271        }
272
273        async fn resolve(
274            &self,
275            ctx: &async_graphql::ContextSelectionSet<'_>,
276            field: &async_graphql::Positioned<async_graphql::parser::types::Field>,
277        ) -> async_graphql::ServerResult<async_graphql::Value> {
278            self.get().resolve(ctx, field).await
279        }
280    }
281}