From 879a46541289802d22319cf4b1415a5fd9bd4afc Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Tue, 15 Jul 2025 11:40:03 -0400 Subject: [PATCH 1/7] feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::filter_duplicated_events Signed-off-by: Michael Goldenberg --- .../event_cache_store/integration_tests.rs | 7 ++++++ .../src/event_cache_store/mod.rs | 22 +++++++++++++++---- .../src/event_cache_store/transaction.rs | 11 ++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs index fd278cb73f4..cdf7e041b37 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs @@ -748,6 +748,13 @@ macro_rules! event_cache_store_integration_tests { get_event_cache_store().await.unwrap().into_event_cache_store(); event_cache_store.test_remove_room().await; } + + #[async_test] + async fn test_filter_duplicated_events() { + let event_cache_store = + get_event_cache_store().await.unwrap().into_event_cache_store(); + event_cache_store.test_filter_duplicated_events().await; + } } }; } diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs index 15139ff1a2e..a5df15c700e 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs @@ -435,10 +435,24 @@ impl_event_cache_store! { events: Vec, ) -> Result, IndexeddbEventCacheStoreError> { let _timer = timer!("method"); - self.memory_store - .filter_duplicated_events(linked_chunk_id, events) - .await - .map_err(IndexeddbEventCacheStoreError::MemoryStore) + + if events.is_empty() { + return Ok(Vec::new()); + } + + let linked_chunk_id = linked_chunk_id.to_owned(); + let room_id = linked_chunk_id.room_id(); + let transaction = + self.transaction(&[keys::EVENTS], IdbTransactionMode::Readonly)?; + let mut duplicated = Vec::new(); + for event_id in events { + if let Some(types::Event::InBand(event)) = + transaction.get_event_by_id(room_id, &event_id).await? + { + duplicated.push((event_id, event.position.into())); + } + } + Ok(duplicated) } #[instrument(skip(self, event_id))] diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/transaction.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/transaction.rs index 15435d4a5d6..284fd5aed75 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/transaction.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/transaction.rs @@ -570,6 +570,17 @@ impl<'a> IndexeddbEventCacheStoreTransaction<'a> { self.delete_items_in_room::(room_id).await } + /// Query IndexedDB for events that match the given event id in the given + /// room. If more than one item is found, an error is returned. + pub async fn get_event_by_id( + &self, + room_id: &RoomId, + event_id: &OwnedEventId, + ) -> Result, IndexeddbEventCacheStoreTransactionError> { + let key = self.serializer.encode_key(room_id, event_id); + self.get_item_by_key::(room_id, key).await + } + /// Query IndexedDB for events in the given position range in the given /// room. pub async fn get_events_by_position( From f5e11fae6efb11fcc253c284faf5a6e46257a1d7 Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Tue, 15 Jul 2025 11:45:36 -0400 Subject: [PATCH 2/7] feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::find_event Signed-off-by: Michael Goldenberg --- .../src/event_cache_store/integration_tests.rs | 7 +++++++ .../matrix-sdk-indexeddb/src/event_cache_store/mod.rs | 10 +++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs index cdf7e041b37..68c54ba2b61 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs @@ -755,6 +755,13 @@ macro_rules! event_cache_store_integration_tests { get_event_cache_store().await.unwrap().into_event_cache_store(); event_cache_store.test_filter_duplicated_events().await; } + + #[async_test] + async fn test_find_event() { + let event_cache_store = + get_event_cache_store().await.unwrap().into_event_cache_store(); + event_cache_store.test_find_event().await; + } } }; } diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs index a5df15c700e..66a9dcff569 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs @@ -462,10 +462,14 @@ impl_event_cache_store! { event_id: &EventId, ) -> Result, IndexeddbEventCacheStoreError> { let _timer = timer!("method"); - self.memory_store - .find_event(room_id, event_id) + + let transaction = + self.transaction(&[keys::EVENTS], IdbTransactionMode::Readonly)?; + transaction + .get_event_by_id(room_id, &event_id.to_owned()) .await - .map_err(IndexeddbEventCacheStoreError::MemoryStore) + .map(|ok| ok.map(Into::into)) + .map_err(Into::into) } #[instrument(skip(self, event_id, filters))] From 07b5d025d435ba03d87e8172ac9cbeb359783d0f Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Tue, 15 Jul 2025 12:26:36 -0400 Subject: [PATCH 3/7] feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::save_event Signed-off-by: Michael Goldenberg --- .../event_cache_store/integration_tests.rs | 7 ++++++ .../src/event_cache_store/mod.rs | 22 ++++++++++++++----- .../src/event_cache_store/types.rs | 9 ++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs index 68c54ba2b61..873f3f41f54 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs @@ -762,6 +762,13 @@ macro_rules! event_cache_store_integration_tests { get_event_cache_store().await.unwrap().into_event_cache_store(); event_cache_store.test_find_event().await; } + + #[async_test] + async fn test_save_event() { + let event_cache_store = + get_event_cache_store().await.unwrap().into_event_cache_store(); + event_cache_store.test_save_event().await; + } } }; } diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs index 66a9dcff569..0f102b369a0 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs @@ -31,14 +31,14 @@ use matrix_sdk_base::{ timer, }; use ruma::{events::relation::RelationType, EventId, MxcUri, OwnedEventId, RoomId}; -use tracing::{instrument, trace}; +use tracing::{error, instrument, trace}; use web_sys::IdbTransactionMode; use crate::event_cache_store::{ migrations::current::keys, serializer::IndexeddbEventCacheStoreSerializer, transaction::{IndexeddbEventCacheStoreTransaction, IndexeddbEventCacheStoreTransactionError}, - types::{ChunkType, InBandEvent}, + types::{ChunkType, InBandEvent, OutOfBandEvent}, }; mod builder; @@ -493,10 +493,20 @@ impl_event_cache_store! { event: Event, ) -> Result<(), IndexeddbEventCacheStoreError> { let _timer = timer!("method"); - self.memory_store - .save_event(room_id, event) - .await - .map_err(IndexeddbEventCacheStoreError::MemoryStore) + + let Some(event_id) = event.event_id() else { + error!(%room_id, "Trying to save an event with no ID"); + return Ok(()); + }; + let transaction = + self.transaction(&[keys::EVENTS], IdbTransactionMode::Readwrite)?; + let event = match transaction.get_event_by_id(room_id, &event_id).await? { + Some(mut inner) => inner.set_content(event), + None => types::Event::OutOfBand(OutOfBandEvent { content: event, position: () }), + }; + transaction.put_event(room_id, &event).await?; + transaction.commit().await?; + Ok(()) } #[instrument(skip_all)] diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/types.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/types.rs index d2f90f76cea..d56fe596fad 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/types.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/types.rs @@ -94,6 +94,15 @@ impl Event { Event::OutOfBand(e) => e.relation(), } } + + /// Sets the content of the underlying [`GenericEvent`] + pub fn set_content(mut self, content: TimelineEvent) -> Self { + match self { + Event::InBand(ref mut i) => i.content = content, + Event::OutOfBand(ref mut o) => o.content = content, + } + self + } } /// A generic representation of an From 14659d10b366cf20e481548d56cc561071493a4d Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Tue, 15 Jul 2025 12:28:22 -0400 Subject: [PATCH 4/7] fix(indexeddb): Updates::PushItems performs an update if any provided item already exists Signed-off-by: Michael Goldenberg --- crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs index 0f102b369a0..6854ba619ac 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs @@ -204,7 +204,7 @@ impl_event_cache_store! { for (i, item) in items.into_iter().enumerate() { transaction - .add_item( + .put_item( room_id, &types::Event::InBand(InBandEvent { content: item, From 783019e0f1a02b35bd0c3b90cb468359473b11cb Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Tue, 15 Jul 2025 12:30:45 -0400 Subject: [PATCH 5/7] feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::find_event_relations Signed-off-by: Michael Goldenberg --- .../event_cache_store/integration_tests.rs | 7 +++++ .../src/event_cache_store/mod.rs | 30 ++++++++++++++++--- .../src/event_cache_store/serializer/types.rs | 17 +++++++++++ .../src/event_cache_store/transaction.rs | 26 ++++++++++++++++ 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs index 873f3f41f54..1acea02313a 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/integration_tests.rs @@ -763,6 +763,13 @@ macro_rules! event_cache_store_integration_tests { event_cache_store.test_find_event().await; } + #[async_test] + async fn test_find_event_relations() { + let event_cache_store = + get_event_cache_store().await.unwrap().into_event_cache_store(); + event_cache_store.test_find_event_relations().await; + } + #[async_test] async fn test_save_event() { let event_cache_store = diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs index 6854ba619ac..49dd2de4fbd 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs @@ -480,10 +480,32 @@ impl_event_cache_store! { filters: Option<&[RelationType]>, ) -> Result)>, IndexeddbEventCacheStoreError> { let _timer = timer!("method"); - self.memory_store - .find_event_relations(room_id, event_id, filters) - .await - .map_err(IndexeddbEventCacheStoreError::MemoryStore) + + let transaction = + self.transaction(&[keys::EVENTS], IdbTransactionMode::Readonly)?; + + let mut related_events = Vec::new(); + match filters { + Some(relation_types) if !relation_types.is_empty() => { + for relation_type in relation_types { + let relation = (event_id.to_owned(), relation_type.clone()); + let events = transaction.get_events_by_relation(room_id, &relation).await?; + for event in events { + let position = event.position().map(Into::into); + related_events.push((event.into(), position)); + } + } + } + _ => { + for event in + transaction.get_events_by_related_event(room_id, &event_id.to_owned()).await? + { + let position = event.position().map(Into::into); + related_events.push((event.into(), position)); + } + } + } + Ok(related_events) } #[instrument(skip(self, event))] diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/serializer/types.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/serializer/types.rs index 6ca4f7c62d3..ac2e0284daa 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/serializer/types.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/serializer/types.rs @@ -417,6 +417,23 @@ pub type IndexedEventPositionIndex = usize; #[derive(Debug, Serialize, Deserialize)] pub struct IndexedEventRelationKey(IndexedRoomId, IndexedEventId, IndexedRelationType); +impl IndexedEventRelationKey { + /// Sets the related event field of this key. This is helpful + /// when searching for all events which are related to the given + /// event. + pub fn set_related_event_id( + &self, + related_event_id: &OwnedEventId, + serializer: &IndexeddbSerializer, + ) -> Self { + let room_id = self.0.clone(); + let related_event_id = + serializer.encode_key_as_string(keys::EVENTS_RELATION_RELATED_EVENTS, related_event_id); + let relation_type = self.2.clone(); + Self(room_id, related_event_id, relation_type) + } +} + impl IndexedKey for IndexedEventRelationKey { const INDEX: Option<&'static str> = Some(keys::EVENTS_RELATION); diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/transaction.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/transaction.rs index 284fd5aed75..a12d284c472 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/transaction.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/transaction.rs @@ -631,6 +631,32 @@ impl<'a> IndexeddbEventCacheStoreTransaction<'a> { self.get_events_count_by_position(room_id, range).await } + /// Query IndexedDB for events that match the given relation range in the + /// given room. + pub async fn get_events_by_relation( + &self, + room_id: &RoomId, + range: impl Into>, + ) -> Result, IndexeddbEventCacheStoreTransactionError> { + let range = range.into().encoded(room_id, self.serializer.inner()); + self.get_items_by_key::(room_id, range).await + } + + /// Query IndexedDB for events that are related to the given event in the + /// given room. + pub async fn get_events_by_related_event( + &self, + room_id: &RoomId, + related_event_id: &OwnedEventId, + ) -> Result, IndexeddbEventCacheStoreTransactionError> { + let lower = IndexedEventRelationKey::lower_key(room_id, self.serializer.inner()) + .set_related_event_id(related_event_id, self.serializer.inner()); + let upper = IndexedEventRelationKey::upper_key(room_id, self.serializer.inner()) + .set_related_event_id(related_event_id, self.serializer.inner()); + let range = IndexedKeyRange::Bound(lower, upper); + self.get_items_by_key::(room_id, range).await + } + /// Puts an event in the given room. If an event with the same key already /// exists, it will be overwritten. pub async fn put_event( From d534857b69cd3d6dba364e4db512cdd35ef3d509 Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Tue, 22 Jul 2025 13:05:08 -0400 Subject: [PATCH 6/7] fixup! feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::save_event Signed-off-by: Michael Goldenberg --- crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs | 2 +- crates/matrix-sdk-indexeddb/src/event_cache_store/types.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs index 49dd2de4fbd..e098176c5ff 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs @@ -523,7 +523,7 @@ impl_event_cache_store! { let transaction = self.transaction(&[keys::EVENTS], IdbTransactionMode::Readwrite)?; let event = match transaction.get_event_by_id(room_id, &event_id).await? { - Some(mut inner) => inner.set_content(event), + Some(mut inner) => inner.with_content(event), None => types::Event::OutOfBand(OutOfBandEvent { content: event, position: () }), }; transaction.put_event(room_id, &event).await?; diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/types.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/types.rs index d56fe596fad..3ef36eccc6c 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/types.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/types.rs @@ -95,8 +95,9 @@ impl Event { } } - /// Sets the content of the underlying [`GenericEvent`] - pub fn set_content(mut self, content: TimelineEvent) -> Self { + /// Sets the content of the underlying [`GenericEvent`] and returns + /// the mutated [`Event`] + pub fn with_content(mut self, content: TimelineEvent) -> Self { match self { Event::InBand(ref mut i) => i.content = content, Event::OutOfBand(ref mut o) => o.content = content, From 4799ce1a34bd2725cf75e52ff3ac6f66258d52d4 Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Tue, 22 Jul 2025 13:08:32 -0400 Subject: [PATCH 7/7] fixup! feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::find_event_relations Signed-off-by: Michael Goldenberg --- .../src/event_cache_store/serializer/types.rs | 8 ++++---- .../src/event_cache_store/transaction.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/serializer/types.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/serializer/types.rs index ac2e0284daa..e2a4f8c4090 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/serializer/types.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/serializer/types.rs @@ -418,10 +418,10 @@ pub type IndexedEventPositionIndex = usize; pub struct IndexedEventRelationKey(IndexedRoomId, IndexedEventId, IndexedRelationType); impl IndexedEventRelationKey { - /// Sets the related event field of this key. This is helpful - /// when searching for all events which are related to the given - /// event. - pub fn set_related_event_id( + /// Returns an identical key, but with the related event field updated to + /// the given related event. This is helpful when searching for all + /// events which are related to the given event. + pub fn with_related_event_id( &self, related_event_id: &OwnedEventId, serializer: &IndexeddbSerializer, diff --git a/crates/matrix-sdk-indexeddb/src/event_cache_store/transaction.rs b/crates/matrix-sdk-indexeddb/src/event_cache_store/transaction.rs index a12d284c472..6ecce3417ba 100644 --- a/crates/matrix-sdk-indexeddb/src/event_cache_store/transaction.rs +++ b/crates/matrix-sdk-indexeddb/src/event_cache_store/transaction.rs @@ -650,9 +650,9 @@ impl<'a> IndexeddbEventCacheStoreTransaction<'a> { related_event_id: &OwnedEventId, ) -> Result, IndexeddbEventCacheStoreTransactionError> { let lower = IndexedEventRelationKey::lower_key(room_id, self.serializer.inner()) - .set_related_event_id(related_event_id, self.serializer.inner()); + .with_related_event_id(related_event_id, self.serializer.inner()); let upper = IndexedEventRelationKey::upper_key(room_id, self.serializer.inner()) - .set_related_event_id(related_event_id, self.serializer.inner()); + .with_related_event_id(related_event_id, self.serializer.inner()); let range = IndexedKeyRange::Bound(lower, upper); self.get_items_by_key::(room_id, range).await }