From 11f4b70da32e1ef30a04edc67a7b9ab5d33fff24 Mon Sep 17 00:00:00 2001 From: Mathieu Dutour Sikiric Date: Fri, 19 Sep 2025 07:44:43 +0200 Subject: [PATCH 01/13] Some update to the scheme. --- linera-views/src/common.rs | 5 +- linera-views/src/views/collection_view.rs | 68 ++++++++++++++--------- 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/linera-views/src/common.rs b/linera-views/src/common.rs index 593f8c730741..314a8dd800c1 100644 --- a/linera-views/src/common.rs +++ b/linera-views/src/common.rs @@ -20,8 +20,11 @@ type HasherOutputSize = ::Output pub type HasherOutput = generic_array::GenericArray; #[derive(Clone, Debug)] -pub(crate) enum Update { +/// An update, for example to a view. +pub enum Update { + /// The entry is removed. Removed, + /// The entry is set to the following. Set(T), } diff --git a/linera-views/src/views/collection_view.rs b/linera-views/src/views/collection_view.rs index 1cffe662a8ca..fce1e681e796 100644 --- a/linera-views/src/views/collection_view.rs +++ b/linera-views/src/views/collection_view.rs @@ -9,7 +9,7 @@ use std::{ mem, }; -use async_lock::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use async_lock::{RwLock, RwLockReadGuard}; #[cfg(with_metrics)] use linera_base::prometheus_util::MeasureLatency as _; use serde::{de::DeserializeOwned, Serialize}; @@ -51,19 +51,39 @@ pub struct ByteCollectionView { } /// A read-only accessor for a particular subview in a [`CollectionView`]. -pub struct ReadGuardedView<'a, W> { - guard: RwLockReadGuard<'a, BTreeMap, Update>>, - short_key: Vec, +pub enum ReadGuardedView<'a, W> { + /// The view is loaded in the updates + Loaded { + /// The guard for the updates. + updates: RwLockReadGuard<'a, BTreeMap, Update>>, + /// The key in question. + short_key: Vec, + }, + /// The view is not loaded in the updates + NotLoaded { + /// The guard for the updates. It is needed so that it prevents + /// opening the view as write separately. + _updates: RwLockReadGuard<'a, BTreeMap, Update>>, + /// The view obtained from the storage + view: W, + }, } impl std::ops::Deref for ReadGuardedView<'_, W> { type Target = W; fn deref(&self) -> &W { - let Update::Set(view) = self.guard.get(&self.short_key).unwrap() else { - unreachable!(); - }; - view + match self { + ReadGuardedView::Loaded { updates, short_key } => { + let Update::Set(view) = updates.get(short_key).unwrap() else { + unreachable!(); + }; + view + } + ReadGuardedView::NotLoaded { _updates, view } => { + view + } + } } } @@ -269,25 +289,23 @@ impl ByteCollectionView { &self, short_key: &[u8], ) -> Result>, ViewError> { - let mut updates = self + let updates = self .updates - .try_write() - .ok_or(ViewError::CannotAcquireCollectionEntry)?; - match updates.entry(short_key.to_vec()) { - btree_map::Entry::Occupied(entry) => { - let entry = entry.into_mut(); - match entry { - Update::Set(_) => { - let guard = RwLockWriteGuard::downgrade(updates); - Ok(Some(ReadGuardedView { - guard, + .read() + .await; + match updates.get(short_key) { + Some(update) => { + match update { + Update::Removed => Ok(None), + _ => { + Ok(Some(ReadGuardedView::Loaded { + updates, short_key: short_key.to_vec(), })) } - Update::Removed => Ok(None), } } - btree_map::Entry::Vacant(entry) => { + None => { let key_index = self .context .base_key() @@ -301,11 +319,9 @@ impl ByteCollectionView { .base_tag_index(KeyTag::Subview as u8, short_key); let context = self.context.clone_with_base_key(key); let view = W::load(context).await?; - entry.insert(Update::Set(view)); - let guard = RwLockWriteGuard::downgrade(updates); - Ok(Some(ReadGuardedView { - guard, - short_key: short_key.to_vec(), + Ok(Some(ReadGuardedView::NotLoaded { + _updates: updates, + view, })) } else { Ok(None) From c0aa3753124de6947c05e36c3a0020183b67a32c Mon Sep 17 00:00:00 2001 From: Mathieu Dutour Sikiric Date: Fri, 19 Sep 2025 08:27:35 +0200 Subject: [PATCH 02/13] Insertion of some initial code for the try_load_entries. --- linera-views/src/views/collection_view.rs | 163 ++++++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/linera-views/src/views/collection_view.rs b/linera-views/src/views/collection_view.rs index fce1e681e796..66f66de677bc 100644 --- a/linera-views/src/views/collection_view.rs +++ b/linera-views/src/views/collection_view.rs @@ -330,6 +330,99 @@ impl ByteCollectionView { } } + /// Load multiple entries for reading at once. + /// The entries in `short_keys` have to be all distinct. + /// ```rust + /// # tokio_test::block_on(async { + /// # use linera_views::context::MemoryContext; + /// # use linera_views::collection_view::ByteCollectionView; + /// # use linera_views::register_view::RegisterView; + /// # use linera_views::views::View; + /// # let context = MemoryContext::new_for_testing(()); + /// let mut view: ByteCollectionView<_, RegisterView<_, String>> = + /// ByteCollectionView::load(context).await.unwrap(); + /// { + /// let _subview = view.load_entry_or_insert(&[0, 1]).await.unwrap(); + /// } + /// let short_keys = vec![vec![0, 1], vec![2, 3]]; + /// let subviews = view.try_load_entries(short_keys).await.unwrap(); + /// let value0 = subviews[0].as_ref().unwrap().get(); + /// assert_eq!(*value0, String::default()); + /// # }) + /// ``` + pub async fn try_load_entries( + &self, + short_keys: Vec>, + ) -> Result>>, ViewError> { + let mut results = Vec::with_capacity(short_keys.len()); + let mut keys_to_check = Vec::new(); + let mut keys_to_check_metadata = Vec::new(); + let updates = self.updates.read().await; + + { + for (position, short_key) in short_keys.into_iter().enumerate() { + match updates.get(&short_key) { + Some(update) => { + match update { + Update::Removed => { + results.push(None); + } + _ => { + let updates = self.updates.read().await; + results.push(Some(ReadGuardedView::Loaded { + updates, + short_key: short_key.clone(), + })); + } + } + } + None => { + results.push(None); // Placeholder, may be updated later + if !self.delete_storage_first { + let key = self + .context + .base_key() + .base_tag_index(KeyTag::Index as u8, &short_key); + let subview_context = self.context.clone_with_base_key(key.clone()); + keys_to_check.push(key); + keys_to_check_metadata.push((position, subview_context)); + } + } + } + } + } + + let found_keys = self.context.store().contains_keys(keys_to_check).await?; + let entries_to_load = keys_to_check_metadata + .into_iter() + .zip(found_keys) + .filter_map(|(metadata, found)| found.then_some(metadata)) + .collect::>(); + + let mut keys_to_load = Vec::with_capacity(entries_to_load.len() * W::NUM_INIT_KEYS); + for (_, context) in &entries_to_load { + keys_to_load.extend(W::pre_load(context)?); + } + let values = self + .context + .store() + .read_multi_values_bytes(keys_to_load) + .await?; + + for (loaded_values, (position, context)) in + values.chunks_exact(W::NUM_INIT_KEYS).zip(entries_to_load) + { + let view = W::post_load(context, loaded_values)?; + let updates = self.updates.read().await; + results[position] = Some(ReadGuardedView::NotLoaded { + _updates: updates, + view, + }); + } + + Ok(results) + } + /// Resets an entry to the default value. /// ```rust /// # tokio_test::block_on(async { @@ -847,6 +940,41 @@ impl CollectionView { self.collection.try_load_entry(&short_key).await } + /// Load multiple entries for reading at once. + /// The entries in indices have to be all distinct. + /// ```rust + /// # tokio_test::block_on(async { + /// # use linera_views::context::MemoryContext; + /// # use linera_views::collection_view::CollectionView; + /// # use linera_views::register_view::RegisterView; + /// # use linera_views::views::View; + /// # let context = MemoryContext::new_for_testing(()); + /// let mut view: CollectionView<_, u64, RegisterView<_, String>> = + /// CollectionView::load(context).await.unwrap(); + /// { + /// let _subview = view.load_entry_or_insert(&23).await.unwrap(); + /// } + /// let indices = vec![23, 24]; + /// let subviews = view.try_load_entries(&indices).await.unwrap(); + /// let value0 = subviews[0].as_ref().unwrap().get(); + /// assert_eq!(*value0, String::default()); + /// # }) + /// ``` + pub async fn try_load_entries<'a, Q>( + &self, + indices: impl IntoIterator, + ) -> Result>>, ViewError> + where + I: Borrow, + Q: Serialize + 'a, + { + let short_keys = indices + .into_iter() + .map(|index| BaseKey::derive_short_key(index)) + .collect::>()?; + self.collection.try_load_entries(short_keys).await + } + /// Resets an entry to the default value. /// ```rust /// # tokio_test::block_on(async { @@ -1197,6 +1325,41 @@ impl CustomCollectionView { self.collection.try_load_entry(&short_key).await } + /// Load multiple entries for reading at once. + /// The entries in indices have to be all distinct. + /// ```rust + /// # tokio_test::block_on(async { + /// # use linera_views::context::MemoryContext; + /// # use linera_views::collection_view::CustomCollectionView; + /// # use linera_views::register_view::RegisterView; + /// # use linera_views::views::View; + /// # let context = MemoryContext::new_for_testing(()); + /// let mut view: CustomCollectionView<_, u128, RegisterView<_, String>> = + /// CustomCollectionView::load(context).await.unwrap(); + /// { + /// let _subview = view.load_entry_or_insert(&23).await.unwrap(); + /// } + /// let indices = vec![23, 24]; + /// let subviews = view.try_load_entries(indices).await.unwrap(); + /// let value0 = subviews[0].as_ref().unwrap().get(); + /// assert_eq!(*value0, String::default()); + /// # }) + /// ``` + pub async fn try_load_entries( + &self, + indices: impl IntoIterator, + ) -> Result>>, ViewError> + where + I: Borrow, + Q: CustomSerialize, + { + let short_keys = indices + .into_iter() + .map(|index| index.to_custom_bytes()) + .collect::>()?; + self.collection.try_load_entries(short_keys).await + } + /// Marks the entry so that it is removed in the next flush. /// ```rust /// # tokio_test::block_on(async { From a0283da0915d9ac8059bc64b2382955811f47335 Mon Sep 17 00:00:00 2001 From: Mathieu Dutour Sikiric Date: Fri, 19 Sep 2025 08:47:20 +0200 Subject: [PATCH 03/13] Some update. --- linera-views/src/views/collection_view.rs | 71 +++++++++---------- .../src/views/reentrant_collection_view.rs | 42 +++++------ 2 files changed, 57 insertions(+), 56 deletions(-) diff --git a/linera-views/src/views/collection_view.rs b/linera-views/src/views/collection_view.rs index 66f66de677bc..eaf357e662be 100644 --- a/linera-views/src/views/collection_view.rs +++ b/linera-views/src/views/collection_view.rs @@ -359,36 +359,34 @@ impl ByteCollectionView { let mut keys_to_check_metadata = Vec::new(); let updates = self.updates.read().await; - { - for (position, short_key) in short_keys.into_iter().enumerate() { - match updates.get(&short_key) { - Some(update) => { - match update { - Update::Removed => { - results.push(None); - } - _ => { - let updates = self.updates.read().await; - results.push(Some(ReadGuardedView::Loaded { - updates, - short_key: short_key.clone(), - })); - } + for (position, short_key) in short_keys.into_iter().enumerate() { + match updates.get(&short_key) { + Some(update) => { + match update { + Update::Removed => { + results.push(None); } - } - None => { - results.push(None); // Placeholder, may be updated later - if !self.delete_storage_first { - let key = self - .context - .base_key() - .base_tag_index(KeyTag::Index as u8, &short_key); - let subview_context = self.context.clone_with_base_key(key.clone()); - keys_to_check.push(key); - keys_to_check_metadata.push((position, subview_context)); + _ => { + let updates = self.updates.read().await; + results.push(Some(ReadGuardedView::Loaded { + updates, + short_key: short_key.clone(), + })); } } } + None => { + results.push(None); // Placeholder, may be updated later + if !self.delete_storage_first { + let key = self + .context + .base_key() + .base_tag_index(KeyTag::Index as u8, &short_key); + let subview_context = self.context.clone_with_base_key(key.clone()); + keys_to_check.push(key); + keys_to_check_metadata.push((position, subview_context)); + } + } } } @@ -1656,16 +1654,17 @@ mod graphql { self.indices().await? }; - let mut values = vec![]; - for key in keys { - let value = self - .try_load_entry(&key) - .await? - .ok_or_else(|| missing_key_error(&key))?; - values.push(Entry { value, key }) - } - - Ok(values) + let values = self.try_load_entries(&keys).await?; + values + .into_iter() + .zip(keys) + .map(|(value, key)| + match value { + None => Err(missing_key_error(&key)), + Some(value) => Ok(Entry { value, key }) + } + ) + .collect() } } diff --git a/linera-views/src/views/reentrant_collection_view.rs b/linera-views/src/views/reentrant_collection_view.rs index fb34e8c5a096..761533a785ca 100644 --- a/linera-views/src/views/reentrant_collection_view.rs +++ b/linera-views/src/views/reentrant_collection_view.rs @@ -2225,16 +2225,17 @@ mod graphql { self.indices().await? }; - let mut values = vec![]; - for key in keys { - let value = self - .try_load_entry(&key) - .await? - .ok_or_else(|| missing_key_error(&key))?; - values.push(Entry { value, key }) - } - - Ok(values) + let values = self.try_load_entries(&keys).await?; + values + .into_iter() + .zip(keys) + .map(|(value, key)| + match value { + None => Err(missing_key_error(&key)), + Some(value) => Ok(Entry { value, key }) + } + ) + .collect() } } @@ -2293,16 +2294,17 @@ mod graphql { self.indices().await? }; - let mut values = vec![]; - for key in keys { - let value = self - .try_load_entry(&key) - .await? - .ok_or_else(|| missing_key_error(&key))?; - values.push(Entry { value, key }) - } - - Ok(values) + let values = self.try_load_entries(keys.clone()).await?; + values + .into_iter() + .zip(keys) + .map(|(value, key)| + match value { + None => Err(missing_key_error(&key)), + Some(value) => Ok(Entry { value, key }) + } + ) + .collect() } } } From a63565d25984d182fb04142d3ddeda45f9ad0c10 Mon Sep 17 00:00:00 2001 From: Mathieu Dutour Sikiric Date: Fri, 19 Sep 2025 08:59:08 +0200 Subject: [PATCH 04/13] Clarify the Clone stuff. --- linera-views/src/views/collection_view.rs | 32 +++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/linera-views/src/views/collection_view.rs b/linera-views/src/views/collection_view.rs index eaf357e662be..ddaba4c1ea2b 100644 --- a/linera-views/src/views/collection_view.rs +++ b/linera-views/src/views/collection_view.rs @@ -1037,7 +1037,7 @@ impl CollectionView { impl CollectionView where - I: Sync + Clone + Send + Serialize + DeserializeOwned, + I: Sync + Send + Serialize + DeserializeOwned, { /// Returns the list of indices in the collection in the order determined by /// the serialization. @@ -1343,13 +1343,13 @@ impl CustomCollectionView { /// assert_eq!(*value0, String::default()); /// # }) /// ``` - pub async fn try_load_entries( + pub async fn try_load_entries<'a, Q>( &self, - indices: impl IntoIterator, + indices: impl IntoIterator, ) -> Result>>, ViewError> where I: Borrow, - Q: CustomSerialize, + Q: CustomSerialize + 'a, { let short_keys = indices .into_iter() @@ -1620,8 +1620,7 @@ mod graphql { + async_graphql::OutputType + serde::ser::Serialize + serde::de::DeserializeOwned - + std::fmt::Debug - + Clone, + + std::fmt::Debug, V: View + async_graphql::OutputType, MapInput: async_graphql::InputType, MapFilters: async_graphql::InputType, @@ -1721,16 +1720,17 @@ mod graphql { self.indices().await? }; - let mut values = vec![]; - for key in keys { - let value = self - .try_load_entry(&key) - .await? - .ok_or_else(|| missing_key_error(&key))?; - values.push(Entry { value, key }) - } - - Ok(values) + let values = self.try_load_entries(&keys).await?; + values + .into_iter() + .zip(keys) + .map(|(value, key)| + match value { + None => Err(missing_key_error(&key)), + Some(value) => Ok(Entry { value, key }) + } + ) + .collect() } } } From f0667a9dd2211bf81b133f81eaef813063a62027 Mon Sep 17 00:00:00 2001 From: Mathieu Dutour Sikiric Date: Fri, 19 Sep 2025 09:02:42 +0200 Subject: [PATCH 05/13] Some clone requirements removed. --- .../src/views/reentrant_collection_view.rs | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/linera-views/src/views/reentrant_collection_view.rs b/linera-views/src/views/reentrant_collection_view.rs index 761533a785ca..c7dec9574480 100644 --- a/linera-views/src/views/reentrant_collection_view.rs +++ b/linera-views/src/views/reentrant_collection_view.rs @@ -1186,7 +1186,7 @@ where impl ReentrantCollectionView where W: View, - I: Sync + Clone + Send + Serialize + DeserializeOwned, + I: Sync + Send + Serialize + DeserializeOwned, { /// Loads a subview for the data at the given index in the collection. If an entry /// is absent then a default entry is put on the collection. The obtained view can @@ -1339,7 +1339,7 @@ where impl ReentrantCollectionView where W: View, - I: Sync + Clone + Send + Serialize + DeserializeOwned, + I: Sync + Send + Serialize + DeserializeOwned, { /// Load multiple entries for writing at once. /// The entries in indices have to be all distinct. @@ -1485,7 +1485,7 @@ where impl ReentrantCollectionView where W: View, - I: Sync + Clone + Send + Serialize + DeserializeOwned, + I: Sync + Send + Serialize + DeserializeOwned, { /// Returns the list of indices in the collection in an order determined /// by serialization. @@ -1693,7 +1693,7 @@ where impl ReentrantCustomCollectionView where W: View, - I: Sync + Clone + Send + CustomSerialize, + I: Sync + Send + CustomSerialize, { /// Loads a subview for the data at the given index in the collection. If an entry /// is absent then a default entry is put in the collection on this index. @@ -1846,7 +1846,7 @@ where impl ReentrantCustomCollectionView where - I: Sync + Clone + Send + CustomSerialize, + I: Sync + Send + CustomSerialize, { /// Load multiple entries for writing at once. /// The entries in indices have to be all distinct. @@ -1867,13 +1867,13 @@ where /// assert_eq!(*value2, String::default()); /// # }) /// ``` - pub async fn try_load_entries_mut( + pub async fn try_load_entries_mut<'a, Q>( &mut self, - indices: impl IntoIterator, + indices: impl IntoIterator, ) -> Result>, ViewError> where I: Borrow, - Q: CustomSerialize, + Q: CustomSerialize + 'a, { let short_keys = indices .into_iter() @@ -1903,13 +1903,13 @@ where /// assert_eq!(*value0, String::default()); /// # }) /// ``` - pub async fn try_load_entries( + pub async fn try_load_entries<'a, Q>( &self, - indices: impl IntoIterator, + indices: impl IntoIterator, ) -> Result>>, ViewError> where I: Borrow, - Q: CustomSerialize, + Q: CustomSerialize + 'a, { let short_keys = indices .into_iter() @@ -1990,7 +1990,7 @@ where impl ReentrantCustomCollectionView where W: View, - I: Sync + Clone + Send + CustomSerialize, + I: Sync + Send + CustomSerialize, { /// Returns the list of indices in the collection. The order is determined by /// the custom serialization. @@ -2191,8 +2191,7 @@ mod graphql { + async_graphql::OutputType + serde::ser::Serialize + serde::de::DeserializeOwned - + std::fmt::Debug - + Clone, + + std::fmt::Debug, V: View + async_graphql::OutputType, MapInput: async_graphql::InputType, MapFilters: async_graphql::InputType, @@ -2260,8 +2259,7 @@ mod graphql { K: async_graphql::InputType + async_graphql::OutputType + crate::common::CustomSerialize - + std::fmt::Debug - + Clone, + + std::fmt::Debug, V: View + async_graphql::OutputType, MapInput: async_graphql::InputType, MapFilters: async_graphql::InputType, @@ -2294,7 +2292,7 @@ mod graphql { self.indices().await? }; - let values = self.try_load_entries(keys.clone()).await?; + let values = self.try_load_entries(&keys).await?; values .into_iter() .zip(keys) From ae214d520ef3b6336a5534d45d254d096436674f Mon Sep 17 00:00:00 2001 From: Mathieu Dutour Sikiric Date: Fri, 19 Sep 2025 09:03:57 +0200 Subject: [PATCH 06/13] Reformatting. --- linera-views/src/views/collection_view.rs | 73 ++++++++----------- .../src/views/reentrant_collection_view.rs | 20 ++--- 2 files changed, 37 insertions(+), 56 deletions(-) diff --git a/linera-views/src/views/collection_view.rs b/linera-views/src/views/collection_view.rs index ddaba4c1ea2b..51e078190005 100644 --- a/linera-views/src/views/collection_view.rs +++ b/linera-views/src/views/collection_view.rs @@ -80,9 +80,7 @@ impl std::ops::Deref for ReadGuardedView<'_, W> { }; view } - ReadGuardedView::NotLoaded { _updates, view } => { - view - } + ReadGuardedView::NotLoaded { _updates, view } => view, } } } @@ -289,22 +287,15 @@ impl ByteCollectionView { &self, short_key: &[u8], ) -> Result>, ViewError> { - let updates = self - .updates - .read() - .await; + let updates = self.updates.read().await; match updates.get(short_key) { - Some(update) => { - match update { - Update::Removed => Ok(None), - _ => { - Ok(Some(ReadGuardedView::Loaded { - updates, - short_key: short_key.to_vec(), - })) - } - } - } + Some(update) => match update { + Update::Removed => Ok(None), + _ => Ok(Some(ReadGuardedView::Loaded { + updates, + short_key: short_key.to_vec(), + })), + }, None => { let key_index = self .context @@ -361,20 +352,18 @@ impl ByteCollectionView { for (position, short_key) in short_keys.into_iter().enumerate() { match updates.get(&short_key) { - Some(update) => { - match update { - Update::Removed => { - results.push(None); - } - _ => { - let updates = self.updates.read().await; - results.push(Some(ReadGuardedView::Loaded { - updates, - short_key: short_key.clone(), - })); - } + Some(update) => match update { + Update::Removed => { + results.push(None); } - } + _ => { + let updates = self.updates.read().await; + results.push(Some(ReadGuardedView::Loaded { + updates, + short_key: short_key.clone(), + })); + } + }, None => { results.push(None); // Placeholder, may be updated later if !self.delete_storage_first { @@ -403,7 +392,7 @@ impl ByteCollectionView { } let values = self .context - .store() + .store() .read_multi_values_bytes(keys_to_load) .await?; @@ -1657,12 +1646,10 @@ mod graphql { values .into_iter() .zip(keys) - .map(|(value, key)| - match value { - None => Err(missing_key_error(&key)), - Some(value) => Ok(Entry { value, key }) - } - ) + .map(|(value, key)| match value { + None => Err(missing_key_error(&key)), + Some(value) => Ok(Entry { value, key }), + }) .collect() } } @@ -1724,12 +1711,10 @@ mod graphql { values .into_iter() .zip(keys) - .map(|(value, key)| - match value { - None => Err(missing_key_error(&key)), - Some(value) => Ok(Entry { value, key }) - } - ) + .map(|(value, key)| match value { + None => Err(missing_key_error(&key)), + Some(value) => Ok(Entry { value, key }), + }) .collect() } } diff --git a/linera-views/src/views/reentrant_collection_view.rs b/linera-views/src/views/reentrant_collection_view.rs index c7dec9574480..591f703db3e7 100644 --- a/linera-views/src/views/reentrant_collection_view.rs +++ b/linera-views/src/views/reentrant_collection_view.rs @@ -2228,12 +2228,10 @@ mod graphql { values .into_iter() .zip(keys) - .map(|(value, key)| - match value { - None => Err(missing_key_error(&key)), - Some(value) => Ok(Entry { value, key }) - } - ) + .map(|(value, key)| match value { + None => Err(missing_key_error(&key)), + Some(value) => Ok(Entry { value, key }), + }) .collect() } } @@ -2296,12 +2294,10 @@ mod graphql { values .into_iter() .zip(keys) - .map(|(value, key)| - match value { - None => Err(missing_key_error(&key)), - Some(value) => Ok(Entry { value, key }) - } - ) + .map(|(value, key)| match value { + None => Err(missing_key_error(&key)), + Some(value) => Ok(Entry { value, key }), + }) .collect() } } From 420b46a02a0f1ee66c4202b9c639791a3941e4a9 Mon Sep 17 00:00:00 2001 From: Mathieu Dutour Sikiric Date: Fri, 19 Sep 2025 09:13:19 +0200 Subject: [PATCH 07/13] Make the unit tests pass. --- linera-views/src/views/collection_view.rs | 3 +-- linera-views/src/views/reentrant_collection_view.rs | 6 ++---- linera-views/tests/views_tests.rs | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/linera-views/src/views/collection_view.rs b/linera-views/src/views/collection_view.rs index 51e078190005..178ae8e1a46d 100644 --- a/linera-views/src/views/collection_view.rs +++ b/linera-views/src/views/collection_view.rs @@ -1326,8 +1326,7 @@ impl CustomCollectionView { /// { /// let _subview = view.load_entry_or_insert(&23).await.unwrap(); /// } - /// let indices = vec![23, 24]; - /// let subviews = view.try_load_entries(indices).await.unwrap(); + /// let subviews = view.try_load_entries(&[23, 42]).await.unwrap(); /// let value0 = subviews[0].as_ref().unwrap().get(); /// assert_eq!(*value0, String::default()); /// # }) diff --git a/linera-views/src/views/reentrant_collection_view.rs b/linera-views/src/views/reentrant_collection_view.rs index 591f703db3e7..4d155199fe14 100644 --- a/linera-views/src/views/reentrant_collection_view.rs +++ b/linera-views/src/views/reentrant_collection_view.rs @@ -1859,8 +1859,7 @@ where /// # let context = MemoryContext::new_for_testing(()); /// let mut view: ReentrantCustomCollectionView<_, u128, RegisterView<_, String>> = /// ReentrantCustomCollectionView::load(context).await.unwrap(); - /// let indices = vec![23, 42]; - /// let subviews = view.try_load_entries_mut(indices).await.unwrap(); + /// let subviews = view.try_load_entries_mut(&[23, 42]).await.unwrap(); /// let value1 = subviews[0].get(); /// let value2 = subviews[1].get(); /// assert_eq!(*value1, String::default()); @@ -1896,8 +1895,7 @@ where /// { /// let _subview = view.try_load_entry_mut(&23).await.unwrap(); /// } - /// let indices = vec![23, 42]; - /// let subviews = view.try_load_entries(indices).await.unwrap(); + /// let subviews = view.try_load_entries(&[23, 42]).await.unwrap(); /// assert!(subviews[1].is_none()); /// let value0 = subviews[0].as_ref().unwrap().get(); /// assert_eq!(*value0, String::default()); diff --git a/linera-views/tests/views_tests.rs b/linera-views/tests/views_tests.rs index d0f3126fb014..28141534e930 100644 --- a/linera-views/tests/views_tests.rs +++ b/linera-views/tests/views_tests.rs @@ -518,7 +518,7 @@ where if config.with_collection { let subview = view.collection2.load_entry_mut("ciao").await?; let subsubview = subview.try_load_entry("!").await?.unwrap(); - assert!(subview.try_load_entry("!").await.is_err()); + let _subsubview = subview.try_load_entry("!").await?.unwrap(); assert_eq!(subsubview.get(), &3); assert_eq!(view.collection.indices().await?, vec!["hola".to_string()]); view.collection.remove_entry("hola")?; From 91403f2d905715856a10551bb6e7ad6e76c7dc5d Mon Sep 17 00:00:00 2001 From: Mathieu Dutour Sikiric Date: Fri, 19 Sep 2025 09:15:45 +0200 Subject: [PATCH 08/13] Remove one error scenario. --- linera-views/src/error.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/linera-views/src/error.rs b/linera-views/src/error.rs index 85c59656da05..d2ffecd3edd6 100644 --- a/linera-views/src/error.rs +++ b/linera-views/src/error.rs @@ -8,10 +8,6 @@ pub enum ViewError { #[error(transparent)] BcsError(#[from] bcs::Error), - /// We failed to acquire an entry in a `CollectionView`. - #[error("trying to access a collection view while some entries are still being accessed")] - CannotAcquireCollectionEntry, - /// Input output error. #[error("I/O error")] IoError(#[from] std::io::Error), From 6c4fdd17e09fbe679b8a6357378fa7a343d37e50 Mon Sep 17 00:00:00 2001 From: Mathieu Dutour Sikiric Date: Sun, 21 Sep 2025 17:41:49 +0200 Subject: [PATCH 09/13] Some update. --- linera-views/tests/views_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linera-views/tests/views_tests.rs b/linera-views/tests/views_tests.rs index 28141534e930..160031e34645 100644 --- a/linera-views/tests/views_tests.rs +++ b/linera-views/tests/views_tests.rs @@ -518,7 +518,7 @@ where if config.with_collection { let subview = view.collection2.load_entry_mut("ciao").await?; let subsubview = subview.try_load_entry("!").await?.unwrap(); - let _subsubview = subview.try_load_entry("!").await?.unwrap(); + subview.try_load_entry("!").await?.unwrap(); assert_eq!(subsubview.get(), &3); assert_eq!(view.collection.indices().await?, vec!["hola".to_string()]); view.collection.remove_entry("hola")?; From 204a713a3e2bee4eee6108405588c8b4d6554c4d Mon Sep 17 00:00:00 2001 From: Mathieu Dutour Sikiric Date: Sun, 21 Sep 2025 17:44:19 +0200 Subject: [PATCH 10/13] Reintroduce the error so that the PR can be backported to the testnet_conway. --- linera-views/src/error.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/linera-views/src/error.rs b/linera-views/src/error.rs index d2ffecd3edd6..85c59656da05 100644 --- a/linera-views/src/error.rs +++ b/linera-views/src/error.rs @@ -8,6 +8,10 @@ pub enum ViewError { #[error(transparent)] BcsError(#[from] bcs::Error), + /// We failed to acquire an entry in a `CollectionView`. + #[error("trying to access a collection view while some entries are still being accessed")] + CannotAcquireCollectionEntry, + /// Input output error. #[error("I/O error")] IoError(#[from] std::io::Error), From ca1d0b52b736ca7178b596392cb4a087d50dc701 Mon Sep 17 00:00:00 2001 From: Mathieu Dutour Sikiric Date: Mon, 22 Sep 2025 11:54:36 +0200 Subject: [PATCH 11/13] Some correction from reviewing process. --- linera-views/src/views/collection_view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linera-views/src/views/collection_view.rs b/linera-views/src/views/collection_view.rs index 178ae8e1a46d..b921cddf3dd2 100644 --- a/linera-views/src/views/collection_view.rs +++ b/linera-views/src/views/collection_view.rs @@ -291,7 +291,7 @@ impl ByteCollectionView { match updates.get(short_key) { Some(update) => match update { Update::Removed => Ok(None), - _ => Ok(Some(ReadGuardedView::Loaded { + Update::Set(_) => Ok(Some(ReadGuardedView::Loaded { updates, short_key: short_key.to_vec(), })), From 2f0b0253796db1705711b803678820a63f2a217e Mon Sep 17 00:00:00 2001 From: Mathieu Dutour Sikiric Date: Mon, 22 Sep 2025 13:31:10 +0200 Subject: [PATCH 12/13] Remove some trait requirements which are not needed. --- linera-views/src/views/collection_view.rs | 6 +----- linera-views/src/views/reentrant_collection_view.rs | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/linera-views/src/views/collection_view.rs b/linera-views/src/views/collection_view.rs index b921cddf3dd2..527eec487721 100644 --- a/linera-views/src/views/collection_view.rs +++ b/linera-views/src/views/collection_view.rs @@ -1565,7 +1565,7 @@ mod graphql { use super::{CollectionView, CustomCollectionView, ReadGuardedView}; use crate::{ - graphql::{hash_name, mangle, missing_key_error, Entry, MapFilters, MapInput}, + graphql::{hash_name, mangle, missing_key_error, Entry, MapInput}, views::View, }; @@ -1610,8 +1610,6 @@ mod graphql { + serde::de::DeserializeOwned + std::fmt::Debug, V: View + async_graphql::OutputType, - MapInput: async_graphql::InputType, - MapFilters: async_graphql::InputType, { async fn keys(&self) -> Result, async_graphql::Error> { Ok(self.indices().await?) @@ -1675,8 +1673,6 @@ mod graphql { + crate::common::CustomSerialize + std::fmt::Debug, V: View + async_graphql::OutputType, - MapInput: async_graphql::InputType, - MapFilters: async_graphql::InputType, { async fn keys(&self) -> Result, async_graphql::Error> { Ok(self.indices().await?) diff --git a/linera-views/src/views/reentrant_collection_view.rs b/linera-views/src/views/reentrant_collection_view.rs index 4d155199fe14..465511bf8568 100644 --- a/linera-views/src/views/reentrant_collection_view.rs +++ b/linera-views/src/views/reentrant_collection_view.rs @@ -2146,7 +2146,7 @@ mod graphql { use super::{ReadGuardedView, ReentrantCollectionView}; use crate::{ - graphql::{hash_name, mangle, missing_key_error, Entry, MapFilters, MapInput}, + graphql::{hash_name, mangle, missing_key_error, Entry, MapInput}, views::View, }; @@ -2191,8 +2191,6 @@ mod graphql { + serde::de::DeserializeOwned + std::fmt::Debug, V: View + async_graphql::OutputType, - MapInput: async_graphql::InputType, - MapFilters: async_graphql::InputType, { async fn keys(&self) -> Result, async_graphql::Error> { Ok(self.indices().await?) @@ -2257,8 +2255,6 @@ mod graphql { + crate::common::CustomSerialize + std::fmt::Debug, V: View + async_graphql::OutputType, - MapInput: async_graphql::InputType, - MapFilters: async_graphql::InputType, { async fn keys(&self) -> Result, async_graphql::Error> { Ok(self.indices().await?) From bfe15d493c1c715e74917eed79e58c517d18b2f6 Mon Sep 17 00:00:00 2001 From: Mathieu Dutour Sikiric Date: Mon, 22 Sep 2025 15:58:54 +0200 Subject: [PATCH 13/13] Change the API of "fn entries" so that now if keys are missing, nothing is returned on output. --- linera-views/src/views/collection_view.rs | 20 +++++++------------ .../src/views/reentrant_collection_view.rs | 18 ++++++----------- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/linera-views/src/views/collection_view.rs b/linera-views/src/views/collection_view.rs index 527eec487721..b17c1db3229b 100644 --- a/linera-views/src/views/collection_view.rs +++ b/linera-views/src/views/collection_view.rs @@ -356,7 +356,7 @@ impl ByteCollectionView { Update::Removed => { results.push(None); } - _ => { + Update::Set(_) => { let updates = self.updates.read().await; results.push(Some(ReadGuardedView::Loaded { updates, @@ -1640,14 +1640,11 @@ mod graphql { }; let values = self.try_load_entries(&keys).await?; - values + Ok(values .into_iter() .zip(keys) - .map(|(value, key)| match value { - None => Err(missing_key_error(&key)), - Some(value) => Ok(Entry { value, key }), - }) - .collect() + .filter_map(|(value, key)| value.map(|value| Entry { value, key })) + .collect()) } } @@ -1703,14 +1700,11 @@ mod graphql { }; let values = self.try_load_entries(&keys).await?; - values + Ok(values .into_iter() .zip(keys) - .map(|(value, key)| match value { - None => Err(missing_key_error(&key)), - Some(value) => Ok(Entry { value, key }), - }) - .collect() + .filter_map(|(value, key)| value.map(|value| Entry { value, key })) + .collect()) } } } diff --git a/linera-views/src/views/reentrant_collection_view.rs b/linera-views/src/views/reentrant_collection_view.rs index 465511bf8568..f2e8f6e82ad3 100644 --- a/linera-views/src/views/reentrant_collection_view.rs +++ b/linera-views/src/views/reentrant_collection_view.rs @@ -2221,14 +2221,11 @@ mod graphql { }; let values = self.try_load_entries(&keys).await?; - values + Ok(values .into_iter() .zip(keys) - .map(|(value, key)| match value { - None => Err(missing_key_error(&key)), - Some(value) => Ok(Entry { value, key }), - }) - .collect() + .filter_map(|(value, key)| value.map(|value| Entry { value, key })) + .collect()) } } @@ -2285,14 +2282,11 @@ mod graphql { }; let values = self.try_load_entries(&keys).await?; - values + Ok(values .into_iter() .zip(keys) - .map(|(value, key)| match value { - None => Err(missing_key_error(&key)), - Some(value) => Ok(Entry { value, key }), - }) - .collect() + .filter_map(|(value, key)| value.map(|value| Entry { value, key })) + .collect()) } } }