linera_views/views/
register_view.rs

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