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