diff --git a/benchmarks/benches/linked_chunk.rs b/benchmarks/benches/linked_chunk.rs index ab031fde9cc..40045c4361b 100644 --- a/benchmarks/benches/linked_chunk.rs +++ b/benchmarks/benches/linked_chunk.rs @@ -2,7 +2,7 @@ use std::{sync::Arc, time::Duration}; use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput}; use matrix_sdk::{ - linked_chunk::{lazy_loader, LinkedChunk, Update}, + linked_chunk::{lazy_loader, LinkedChunk, LinkedChunkId, Update}, SqliteEventCacheStore, }; use matrix_sdk_base::event_cache::{ @@ -29,6 +29,7 @@ fn writing(c: &mut Criterion) { .expect("Failed to create an asynchronous runtime"); let room_id = room_id!("!foo:bar.baz"); + let linked_chunk_id = LinkedChunkId::Room(room_id); let event_factory = EventFactory::new().room(room_id).sender(&ALICE); let mut group = c.benchmark_group("writing"); @@ -115,9 +116,9 @@ fn writing(c: &mut Criterion) { if let Some(store) = &store { let updates = linked_chunk.updates().unwrap().take(); - store.handle_linked_chunk_updates(room_id, updates).await.unwrap(); + store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap(); // Empty the store. - store.handle_linked_chunk_updates(room_id, vec![Update::Clear]).await.unwrap(); + store.handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear]).await.unwrap(); } }, @@ -145,6 +146,7 @@ fn reading(c: &mut Criterion) { .expect("Failed to create an asynchronous runtime"); let room_id = room_id!("!foo:bar.baz"); + let linked_chunk_id = LinkedChunkId::Room(room_id); let event_factory = EventFactory::new().room(room_id).sender(&ALICE); let mut group = c.benchmark_group("reading"); @@ -195,7 +197,9 @@ fn reading(c: &mut Criterion) { // Now persist the updates to recreate this full linked chunk. let updates = lc.updates().unwrap().take(); - runtime.block_on(store.handle_linked_chunk_updates(room_id, updates)).unwrap(); + runtime + .block_on(store.handle_linked_chunk_updates(linked_chunk_id, updates)) + .unwrap(); } // Define the throughput. @@ -206,7 +210,8 @@ fn reading(c: &mut Criterion) { // Bench the routine. bencher.to_async(&runtime).iter(|| async { // Load the last chunk first, - let (last_chunk, chunk_id_gen) = store.load_last_chunk(room_id).await.unwrap(); + let (last_chunk, chunk_id_gen) = + store.load_last_chunk(linked_chunk_id).await.unwrap(); let mut lc = lazy_loader::from_last_chunk::<128, _, _>(last_chunk, chunk_id_gen) @@ -216,7 +221,7 @@ fn reading(c: &mut Criterion) { // Then load until the start of the linked chunk. let mut cur_chunk_id = lc.chunks().next().unwrap().identifier(); while let Some(prev) = - store.load_previous_chunk(room_id, cur_chunk_id).await.unwrap() + store.load_previous_chunk(linked_chunk_id, cur_chunk_id).await.unwrap() { cur_chunk_id = prev.identifier; lazy_loader::insert_new_first_chunk(&mut lc, prev) diff --git a/benchmarks/benches/room_bench.rs b/benchmarks/benches/room_bench.rs index 51fb30a57dd..b60e07c9387 100644 --- a/benchmarks/benches/room_bench.rs +++ b/benchmarks/benches/room_bench.rs @@ -178,7 +178,7 @@ pub fn load_pinned_events_benchmark(c: &mut Criterion) { .lock() .await .unwrap() - .clear_all_rooms_chunks() + .clear_all_linked_chunks() .await .unwrap(); diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index 7983d3eb3ca..7caf42f7d8a 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -1444,7 +1444,7 @@ impl Client { .map_err(EventCacheError::from)?; // Clear all the room chunks. It's important to *not* call - // `EventCacheStore::clear_all_rooms_chunks` here, because there might be live + // `EventCacheStore::clear_all_linked_chunks` here, because there might be live // observers of the linked chunks, and that would cause some very bad state // mismatch. self.inner.event_cache().clear_all_rooms().await?; diff --git a/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs b/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs index f89dd7789d6..59b047c7395 100644 --- a/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs +++ b/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs @@ -22,7 +22,9 @@ use matrix_sdk_common::{ AlgorithmInfo, DecryptedRoomEvent, EncryptionInfo, TimelineEvent, TimelineEventKind, VerificationState, }, - linked_chunk::{lazy_loader, ChunkContent, ChunkIdentifier as CId, Position, Update}, + linked_chunk::{ + lazy_loader, ChunkContent, ChunkIdentifier as CId, LinkedChunkId, Position, Update, + }, }; use matrix_sdk_test::{event_factory::EventFactory, ALICE, DEFAULT_TEST_ROOM_ID}; use ruma::{ @@ -132,7 +134,7 @@ pub trait EventCacheStoreIntegrationTests { async fn test_rebuild_empty_linked_chunk(&self); /// Test that clear all the rooms' linked chunks works. - async fn test_clear_all_rooms_chunks(&self); + async fn test_clear_all_linked_chunks(&self); /// Test that removing a room from storage empties all associated data. async fn test_remove_room(&self); @@ -337,9 +339,10 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { async fn test_handle_updates_and_rebuild_linked_chunk(&self) { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = LinkedChunkId::Room(room_id); self.handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ // new chunk Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -371,10 +374,11 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { .unwrap(); // The linked chunk is correctly reloaded. - let lc = - lazy_loader::from_all_chunks::<3, _, _>(self.load_all_chunks(room_id).await.unwrap()) - .unwrap() - .unwrap(); + let lc = lazy_loader::from_all_chunks::<3, _, _>( + self.load_all_chunks(linked_chunk_id).await.unwrap(), + ) + .unwrap() + .unwrap(); let mut chunks = lc.chunks(); @@ -415,19 +419,20 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { async fn test_linked_chunk_incremental_loading(&self) { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = LinkedChunkId::Room(room_id); let event = |msg: &str| make_test_event(room_id, msg); // Load the last chunk, but none exists yet. { let (last_chunk, chunk_identifier_generator) = - self.load_last_chunk(room_id).await.unwrap(); + self.load_last_chunk(linked_chunk_id).await.unwrap(); assert!(last_chunk.is_none()); assert_eq!(chunk_identifier_generator.current(), 0); } self.handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ // new chunk for items Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -458,7 +463,7 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { // Load the last chunk. let mut linked_chunk = { let (last_chunk, chunk_identifier_generator) = - self.load_last_chunk(room_id).await.unwrap(); + self.load_last_chunk(linked_chunk_id).await.unwrap(); assert_eq!(chunk_identifier_generator.current(), 2); @@ -493,7 +498,7 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { { let first_chunk = linked_chunk.chunks().next().unwrap().identifier(); let previous_chunk = - self.load_previous_chunk(room_id, first_chunk).await.unwrap().unwrap(); + self.load_previous_chunk(linked_chunk_id, first_chunk).await.unwrap().unwrap(); lazy_loader::insert_new_first_chunk(&mut linked_chunk, previous_chunk).unwrap(); @@ -530,7 +535,7 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { { let first_chunk = linked_chunk.chunks().next().unwrap().identifier(); let previous_chunk = - self.load_previous_chunk(room_id, first_chunk).await.unwrap().unwrap(); + self.load_previous_chunk(linked_chunk_id, first_chunk).await.unwrap().unwrap(); lazy_loader::insert_new_first_chunk(&mut linked_chunk, previous_chunk).unwrap(); @@ -579,7 +584,8 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { // Load the previous chunk: there is none. { let first_chunk = linked_chunk.chunks().next().unwrap().identifier(); - let previous_chunk = self.load_previous_chunk(room_id, first_chunk).await.unwrap(); + let previous_chunk = + self.load_previous_chunk(linked_chunk_id, first_chunk).await.unwrap(); assert!(previous_chunk.is_none()); } @@ -631,19 +637,21 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { async fn test_rebuild_empty_linked_chunk(&self) { // When I rebuild a linked chunk from an empty store, it's empty. let linked_chunk = lazy_loader::from_all_chunks::<3, _, _>( - self.load_all_chunks(&DEFAULT_TEST_ROOM_ID).await.unwrap(), + self.load_all_chunks(LinkedChunkId::Room(&DEFAULT_TEST_ROOM_ID)).await.unwrap(), ) .unwrap(); assert!(linked_chunk.is_none()); } - async fn test_clear_all_rooms_chunks(&self) { + async fn test_clear_all_linked_chunks(&self) { let r0 = room_id!("!r0:matrix.org"); + let linked_chunk_id0 = LinkedChunkId::Room(r0); let r1 = room_id!("!r1:matrix.org"); + let linked_chunk_id1 = LinkedChunkId::Room(r1); // Add updates for the first room. self.handle_linked_chunk_updates( - r0, + linked_chunk_id0, vec![ // new chunk Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -659,7 +667,7 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { // Add updates for the second room. self.handle_linked_chunk_updates( - r1, + linked_chunk_id1, vec![ // Empty items chunk. Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -683,32 +691,42 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { .unwrap(); // Sanity check: both linked chunks can be reloaded. - assert!(lazy_loader::from_all_chunks::<3, _, _>(self.load_all_chunks(r0).await.unwrap()) - .unwrap() - .is_some()); - assert!(lazy_loader::from_all_chunks::<3, _, _>(self.load_all_chunks(r1).await.unwrap()) - .unwrap() - .is_some()); + assert!(lazy_loader::from_all_chunks::<3, _, _>( + self.load_all_chunks(linked_chunk_id0).await.unwrap() + ) + .unwrap() + .is_some()); + assert!(lazy_loader::from_all_chunks::<3, _, _>( + self.load_all_chunks(linked_chunk_id1).await.unwrap() + ) + .unwrap() + .is_some()); // Clear the chunks. - self.clear_all_rooms_chunks().await.unwrap(); + self.clear_all_linked_chunks().await.unwrap(); // Both rooms now have no linked chunk. - assert!(lazy_loader::from_all_chunks::<3, _, _>(self.load_all_chunks(r0).await.unwrap()) - .unwrap() - .is_none()); - assert!(lazy_loader::from_all_chunks::<3, _, _>(self.load_all_chunks(r1).await.unwrap()) - .unwrap() - .is_none()); + assert!(lazy_loader::from_all_chunks::<3, _, _>( + self.load_all_chunks(linked_chunk_id0).await.unwrap() + ) + .unwrap() + .is_none()); + assert!(lazy_loader::from_all_chunks::<3, _, _>( + self.load_all_chunks(linked_chunk_id1).await.unwrap() + ) + .unwrap() + .is_none()); } async fn test_remove_room(&self) { let r0 = room_id!("!r0:matrix.org"); + let linked_chunk_id0 = LinkedChunkId::Room(r0); let r1 = room_id!("!r1:matrix.org"); + let linked_chunk_id1 = LinkedChunkId::Room(r1); // Add updates to the first room. self.handle_linked_chunk_updates( - r0, + linked_chunk_id0, vec![ // new chunk Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -724,7 +742,7 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { // Add updates to the second room. self.handle_linked_chunk_updates( - r1, + linked_chunk_id1, vec![ // new chunk Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -742,17 +760,19 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { self.remove_room(r0).await.unwrap(); // Check that r0 doesn't have a linked chunk anymore. - let r0_linked_chunk = self.load_all_chunks(r0).await.unwrap(); + let r0_linked_chunk = self.load_all_chunks(linked_chunk_id0).await.unwrap(); assert!(r0_linked_chunk.is_empty()); // Check that r1 is unaffected. - let r1_linked_chunk = self.load_all_chunks(r1).await.unwrap(); + let r1_linked_chunk = self.load_all_chunks(linked_chunk_id1).await.unwrap(); assert!(!r1_linked_chunk.is_empty()); } async fn test_filter_duplicated_events(&self) { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = LinkedChunkId::Room(room_id); let another_room_id = room_id!("!r1:matrix.org"); + let another_linked_chunk_id = LinkedChunkId::Room(another_room_id); let event = |msg: &str| make_test_event(room_id, msg); let event_comte = event("comté"); @@ -764,7 +784,7 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { let event_mont_dor = event("mont d'or"); self.handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, Update::PushItems { @@ -790,7 +810,7 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { // Add other events in another room, to ensure filtering take the `room_id` into // account. self.handle_linked_chunk_updates( - another_room_id, + another_linked_chunk_id, vec![ Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, Update::PushItems { @@ -804,7 +824,7 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { let duplicated_events = self .filter_duplicated_events( - room_id, + linked_chunk_id, vec![ event_comte.event_id().unwrap().to_owned(), event_raclette.event_id().unwrap().to_owned(), @@ -835,6 +855,7 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { async fn test_find_event(&self) { let room_id = room_id!("!r0:matrix.org"); let another_room_id = room_id!("!r1:matrix.org"); + let another_linked_chunk_id = LinkedChunkId::Room(another_room_id); let event = |msg: &str| make_test_event(room_id, msg); let event_comte = event("comté"); @@ -842,7 +863,7 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { // Add one event in one room. self.handle_linked_chunk_updates( - room_id, + LinkedChunkId::Room(room_id), vec![ Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, Update::PushItems { @@ -856,7 +877,7 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { // Add another event in another room. self.handle_linked_chunk_updates( - another_room_id, + another_linked_chunk_id, vec![ Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, Update::PushItems { @@ -885,7 +906,7 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore { .is_none()); // Clearing the rooms also clears the event's storage. - self.clear_all_rooms_chunks().await.expect("failed to clear all rooms chunks"); + self.clear_all_linked_chunks().await.expect("failed to clear all rooms chunks"); assert!(self .find_event(room_id, event_comte.event_id().unwrap().as_ref()) .await @@ -1085,10 +1106,10 @@ macro_rules! event_cache_store_integration_tests { } #[async_test] - async fn test_clear_all_rooms_chunks() { + async fn test_clear_all_linked_chunks() { let event_cache_store = get_event_cache_store().await.unwrap().into_event_cache_store(); - event_cache_store.test_clear_all_rooms_chunks().await; + event_cache_store.test_clear_all_linked_chunks().await; } #[async_test] diff --git a/crates/matrix-sdk-base/src/event_cache/store/memory_store.rs b/crates/matrix-sdk-base/src/event_cache/store/memory_store.rs index 2a99eddb204..e252a9eae0a 100644 --- a/crates/matrix-sdk-base/src/event_cache/store/memory_store.rs +++ b/crates/matrix-sdk-base/src/event_cache/store/memory_store.rs @@ -21,8 +21,8 @@ use std::{ use async_trait::async_trait; use matrix_sdk_common::{ linked_chunk::{ - relational::RelationalLinkedChunk, ChunkIdentifier, ChunkIdentifierGenerator, Position, - RawChunk, Update, + relational::RelationalLinkedChunk, ChunkIdentifier, ChunkIdentifierGenerator, + LinkedChunkId, Position, RawChunk, Update, }, ring_buffer::RingBuffer, store_locks::memory_store_helper::try_take_leased_lock, @@ -128,57 +128,57 @@ impl EventCacheStore for MemoryStore { async fn handle_linked_chunk_updates( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, updates: Vec>, ) -> Result<(), Self::Error> { let mut inner = self.inner.write().unwrap(); - inner.events.apply_updates(room_id, updates); + inner.events.apply_updates(linked_chunk_id, updates); Ok(()) } async fn load_all_chunks( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, ) -> Result>, Self::Error> { let inner = self.inner.read().unwrap(); inner .events - .load_all_chunks(room_id) + .load_all_chunks(linked_chunk_id) .map_err(|err| EventCacheStoreError::InvalidData { details: err }) } async fn load_last_chunk( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, ) -> Result<(Option>, ChunkIdentifierGenerator), Self::Error> { let inner = self.inner.read().unwrap(); inner .events - .load_last_chunk(room_id) + .load_last_chunk(linked_chunk_id) .map_err(|err| EventCacheStoreError::InvalidData { details: err }) } async fn load_previous_chunk( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, before_chunk_identifier: ChunkIdentifier, ) -> Result>, Self::Error> { let inner = self.inner.read().unwrap(); inner .events - .load_previous_chunk(room_id, before_chunk_identifier) + .load_previous_chunk(linked_chunk_id, before_chunk_identifier) .map_err(|err| EventCacheStoreError::InvalidData { details: err }) } - async fn clear_all_rooms_chunks(&self) -> Result<(), Self::Error> { + async fn clear_all_linked_chunks(&self) -> Result<(), Self::Error> { self.inner.write().unwrap().events.clear(); Ok(()) } async fn filter_duplicated_events( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, mut events: Vec, ) -> Result, Self::Error> { // Collect all duplicated events. @@ -186,7 +186,7 @@ impl EventCacheStore for MemoryStore { let mut duplicated_events = Vec::new(); - for (event, position) in inner.events.unordered_room_items(room_id) { + for (event, position) in inner.events.unordered_linked_chunk_items(linked_chunk_id) { // If `events` is empty, we can short-circuit. if events.is_empty() { break; @@ -212,8 +212,9 @@ impl EventCacheStore for MemoryStore { ) -> Result, Self::Error> { let inner = self.inner.read().unwrap(); - let event = inner.events.items().find_map(|(event, this_room_id)| { - (room_id == this_room_id && event.event_id()? == event_id).then_some(event.clone()) + let event = inner.events.items().find_map(|(event, this_linked_chunk_id)| { + (room_id == this_linked_chunk_id.room_id() && event.event_id()? == event_id) + .then_some(event.clone()) }); Ok(event) @@ -232,9 +233,9 @@ impl EventCacheStore for MemoryStore { let related_events = inner .events .items() - .filter_map(|(event, this_room_id)| { + .filter_map(|(event, this_linked_chunk_id)| { // Must be in the same room. - if room_id != this_room_id { + if room_id != this_linked_chunk_id.room_id() { return None; } diff --git a/crates/matrix-sdk-base/src/event_cache/store/traits.rs b/crates/matrix-sdk-base/src/event_cache/store/traits.rs index 7a225ff7815..e799eab4795 100644 --- a/crates/matrix-sdk-base/src/event_cache/store/traits.rs +++ b/crates/matrix-sdk-base/src/event_cache/store/traits.rs @@ -16,7 +16,9 @@ use std::{fmt, sync::Arc}; use async_trait::async_trait; use matrix_sdk_common::{ - linked_chunk::{ChunkIdentifier, ChunkIdentifierGenerator, Position, RawChunk, Update}, + linked_chunk::{ + ChunkIdentifier, ChunkIdentifierGenerator, LinkedChunkId, Position, RawChunk, Update, + }, AsyncTraitDeps, }; use ruma::{events::relation::RelationType, EventId, MxcUri, OwnedEventId, RoomId}; @@ -56,7 +58,7 @@ pub trait EventCacheStore: AsyncTraitDeps { /// in-memory. This method aims at forwarding this update inside this store. async fn handle_linked_chunk_updates( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, updates: Vec>, ) -> Result<(), Self::Error>; @@ -64,7 +66,7 @@ pub trait EventCacheStore: AsyncTraitDeps { async fn remove_room(&self, room_id: &RoomId) -> Result<(), Self::Error> { // Right now, this means removing all the linked chunk. If implementations // override this behavior, they should *also* include this code. - self.handle_linked_chunk_updates(room_id, vec![Update::Clear]).await + self.handle_linked_chunk_updates(LinkedChunkId::Room(room_id), vec![Update::Clear]).await } /// Return all the raw components of a linked chunk, so the caller may @@ -72,7 +74,7 @@ pub trait EventCacheStore: AsyncTraitDeps { #[doc(hidden)] async fn load_all_chunks( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, ) -> Result>, Self::Error>; /// Load the last chunk of the `LinkedChunk` holding all events of the room @@ -81,7 +83,7 @@ pub trait EventCacheStore: AsyncTraitDeps { /// This is used to iteratively load events for the `EventCache`. async fn load_last_chunk( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, ) -> Result<(Option>, ChunkIdentifierGenerator), Self::Error>; /// Load the chunk before the chunk identified by `before_chunk_identifier` @@ -91,7 +93,7 @@ pub trait EventCacheStore: AsyncTraitDeps { /// This is used to iteratively load events for the `EventCache`. async fn load_previous_chunk( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, before_chunk_identifier: ChunkIdentifier, ) -> Result>, Self::Error>; @@ -105,17 +107,17 @@ pub trait EventCacheStore: AsyncTraitDeps { /// ⚠ This is meant only for super specific use cases, where there shouldn't /// be any live in-memory linked chunks. In general, prefer using /// `EventCache::clear_all_rooms()` from the common SDK crate. - async fn clear_all_rooms_chunks(&self) -> Result<(), Self::Error>; + async fn clear_all_linked_chunks(&self) -> Result<(), Self::Error>; /// Given a set of event IDs, return the duplicated events along with their /// position if there are any. async fn filter_duplicated_events( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, events: Vec, ) -> Result, Self::Error>; - /// Find an event by its ID. + /// Find an event by its ID in a room. async fn find_event( &self, room_id: &RoomId, @@ -298,44 +300,47 @@ impl EventCacheStore for EraseEventCacheStoreError { async fn handle_linked_chunk_updates( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, updates: Vec>, ) -> Result<(), Self::Error> { - self.0.handle_linked_chunk_updates(room_id, updates).await.map_err(Into::into) + self.0.handle_linked_chunk_updates(linked_chunk_id, updates).await.map_err(Into::into) } async fn load_all_chunks( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, ) -> Result>, Self::Error> { - self.0.load_all_chunks(room_id).await.map_err(Into::into) + self.0.load_all_chunks(linked_chunk_id).await.map_err(Into::into) } async fn load_last_chunk( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, ) -> Result<(Option>, ChunkIdentifierGenerator), Self::Error> { - self.0.load_last_chunk(room_id).await.map_err(Into::into) + self.0.load_last_chunk(linked_chunk_id).await.map_err(Into::into) } async fn load_previous_chunk( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, before_chunk_identifier: ChunkIdentifier, ) -> Result>, Self::Error> { - self.0.load_previous_chunk(room_id, before_chunk_identifier).await.map_err(Into::into) + self.0 + .load_previous_chunk(linked_chunk_id, before_chunk_identifier) + .await + .map_err(Into::into) } - async fn clear_all_rooms_chunks(&self) -> Result<(), Self::Error> { - self.0.clear_all_rooms_chunks().await.map_err(Into::into) + async fn clear_all_linked_chunks(&self) -> Result<(), Self::Error> { + self.0.clear_all_linked_chunks().await.map_err(Into::into) } async fn filter_duplicated_events( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, events: Vec, ) -> Result, Self::Error> { - self.0.filter_duplicated_events(room_id, events).await.map_err(Into::into) + self.0.filter_duplicated_events(linked_chunk_id, events).await.map_err(Into::into) } async fn find_event( diff --git a/crates/matrix-sdk-common/src/linked_chunk/mod.rs b/crates/matrix-sdk-common/src/linked_chunk/mod.rs index aa7d78eda7e..e5e7dfcb991 100644 --- a/crates/matrix-sdk-common/src/linked_chunk/mod.rs +++ b/crates/matrix-sdk-common/src/linked_chunk/mod.rs @@ -97,15 +97,88 @@ pub mod relational; mod updates; use std::{ - fmt, + fmt::{self, Display}, marker::PhantomData, ptr::NonNull, - sync::atomic::{AtomicU64, Ordering}, + sync::atomic::{self, AtomicU64}, }; pub use as_vector::*; +use ruma::{OwnedRoomId, RoomId}; pub use updates::*; +/// An identifier for a linked chunk; borrowed variant. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum LinkedChunkId<'a> { + Room(&'a RoomId), + // TODO(bnjbvr): Soon™. + // Thread(&'a RoomId, &'a EventId), +} + +impl LinkedChunkId<'_> { + pub fn storage_key(&self) -> impl '_ + AsRef<[u8]> { + match self { + LinkedChunkId::Room(room_id) => room_id, + } + } + + pub fn to_owned(&self) -> OwnedLinkedChunkId { + match self { + LinkedChunkId::Room(room_id) => OwnedLinkedChunkId::Room((*room_id).to_owned()), + } + } + + pub fn room_id(&self) -> &RoomId { + match self { + LinkedChunkId::Room(room_id) => room_id, + } + } +} + +impl PartialEq<&OwnedLinkedChunkId> for LinkedChunkId<'_> { + fn eq(&self, other: &&OwnedLinkedChunkId) -> bool { + match (self, other) { + (LinkedChunkId::Room(a), OwnedLinkedChunkId::Room(b)) => *a == b, + } + } +} + +impl PartialEq> for OwnedLinkedChunkId { + fn eq(&self, other: &LinkedChunkId<'_>) -> bool { + other.eq(&self) + } +} + +/// An identifier for a linked chunk; owned variant. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum OwnedLinkedChunkId { + Room(OwnedRoomId), + // TODO(bnjbvr): Soon™. + // Thread(OwnedRoomId, OwnedEventId), +} + +impl Display for OwnedLinkedChunkId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + OwnedLinkedChunkId::Room(room_id) => write!(f, "{room_id}"), + } + } +} + +impl OwnedLinkedChunkId { + fn as_ref(&self) -> LinkedChunkId<'_> { + match self { + OwnedLinkedChunkId::Room(room_id) => LinkedChunkId::Room(room_id.as_ref()), + } + } + + pub fn room_id(&self) -> &RoomId { + match self { + OwnedLinkedChunkId::Room(room_id) => room_id, + } + } +} + /// Errors of [`LinkedChunk`]. #[derive(thiserror::Error, Debug)] pub enum Error { @@ -1080,7 +1153,7 @@ impl ChunkIdentifierGenerator { /// Note that it can fail if there is no more unique identifier available. /// In this case, this method will panic. fn next(&self) -> ChunkIdentifier { - let previous = self.next.fetch_add(1, Ordering::Relaxed); + let previous = self.next.fetch_add(1, atomic::Ordering::Relaxed); // Check for overflows. // unlikely — TODO: call `std::intrinsics::unlikely` once it's stable. @@ -1096,7 +1169,7 @@ impl ChunkIdentifierGenerator { // This is hidden because it's used only in the tests. #[doc(hidden)] pub fn current(&self) -> ChunkIdentifier { - ChunkIdentifier(self.next.load(Ordering::Relaxed)) + ChunkIdentifier(self.next.load(atomic::Ordering::Relaxed)) } } diff --git a/crates/matrix-sdk-common/src/linked_chunk/relational.rs b/crates/matrix-sdk-common/src/linked_chunk/relational.rs index a94c1751f85..ae9b67b7bd9 100644 --- a/crates/matrix-sdk-common/src/linked_chunk/relational.rs +++ b/crates/matrix-sdk-common/src/linked_chunk/relational.rs @@ -17,18 +17,18 @@ use std::{collections::HashMap, hash::Hash}; -use ruma::{OwnedEventId, OwnedRoomId, RoomId}; +use ruma::{OwnedEventId, OwnedRoomId}; use super::{ChunkContent, ChunkIdentifierGenerator, RawChunk}; use crate::{ deserialized_responses::TimelineEvent, - linked_chunk::{ChunkIdentifier, Position, Update}, + linked_chunk::{ChunkIdentifier, LinkedChunkId, OwnedLinkedChunkId, Position, Update}, }; /// A row of the [`RelationalLinkedChunk::chunks`]. #[derive(Debug, PartialEq)] struct ChunkRow { - room_id: OwnedRoomId, + linked_chunk_id: OwnedLinkedChunkId, previous_chunk: Option, chunk: ChunkIdentifier, next_chunk: Option, @@ -37,7 +37,7 @@ struct ChunkRow { /// A row of the [`RelationalLinkedChunk::items`]. #[derive(Debug, PartialEq)] struct ItemRow { - room_id: OwnedRoomId, + linked_chunk_id: OwnedLinkedChunkId, position: Position, item: Either, } @@ -79,7 +79,7 @@ pub struct RelationalLinkedChunk { items_chunks: Vec>, /// The items' content themselves. - items: HashMap>, + items: HashMap>, } /// The [`IndexableItem`] trait is used to mark items that can be indexed into a @@ -119,32 +119,43 @@ where /// Apply [`Update`]s. That's the only way to write data inside this /// relational linked chunk. - pub fn apply_updates(&mut self, room_id: &RoomId, updates: Vec>) { + pub fn apply_updates( + &mut self, + linked_chunk_id: LinkedChunkId<'_>, + updates: Vec>, + ) { for update in updates { match update { Update::NewItemsChunk { previous, new, next } => { - insert_chunk(&mut self.chunks, room_id, previous, new, next); + insert_chunk(&mut self.chunks, linked_chunk_id, previous, new, next); } Update::NewGapChunk { previous, new, next, gap } => { - insert_chunk(&mut self.chunks, room_id, previous, new, next); + insert_chunk(&mut self.chunks, linked_chunk_id, previous, new, next); self.items_chunks.push(ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.to_owned(), position: Position::new(new, 0), item: Either::Gap(gap), }); } Update::RemoveChunk(chunk_identifier) => { - remove_chunk(&mut self.chunks, room_id, chunk_identifier); + remove_chunk(&mut self.chunks, linked_chunk_id, chunk_identifier); let indices_to_remove = self .items_chunks .iter() .enumerate() .filter_map( - |(nth, ItemRow { room_id: room_id_candidate, position, .. })| { - (room_id == room_id_candidate + |( + nth, + ItemRow { + linked_chunk_id: linked_chunk_id_candidate, + position, + .. + }, + )| { + (linked_chunk_id == linked_chunk_id_candidate && position.chunk_identifier() == chunk_identifier) .then_some(nth) }, @@ -160,11 +171,11 @@ where for item in items { let item_id = item.id(); self.items - .entry(room_id.to_owned()) + .entry(linked_chunk_id.to_owned()) .or_default() .insert(item_id.clone(), item); self.items_chunks.push(ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.to_owned(), position: at, item: Either::Item(item_id), }); @@ -183,18 +194,23 @@ where "trying to replace a gap with an item" ); let item_id = item.id(); - self.items.entry(room_id.to_owned()).or_default().insert(item_id.clone(), item); + self.items + .entry(linked_chunk_id.to_owned()) + .or_default() + .insert(item_id.clone(), item); existing.item = Either::Item(item_id); } Update::RemoveItem { at } => { let mut entry_to_remove = None; - for (nth, ItemRow { room_id: room_id_candidate, position, .. }) in - self.items_chunks.iter_mut().enumerate() + for ( + nth, + ItemRow { linked_chunk_id: linked_chunk_id_candidate, position, .. }, + ) in self.items_chunks.iter_mut().enumerate() { - // Filter by room ID. - if room_id != room_id_candidate { + // Filter by linked chunk id. + if linked_chunk_id != &*linked_chunk_id_candidate { continue; } @@ -223,8 +239,15 @@ where .iter() .enumerate() .filter_map( - |(nth, ItemRow { room_id: room_id_candidate, position, .. })| { - (room_id == room_id_candidate + |( + nth, + ItemRow { + linked_chunk_id: linked_chunk_id_candidate, + position, + .. + }, + )| { + (linked_chunk_id == linked_chunk_id_candidate && position.chunk_identifier() == at.chunk_identifier() && position.index() >= at.index()) .then_some(nth) @@ -240,8 +263,8 @@ where Update::StartReattachItems | Update::EndReattachItems => { /* nothing */ } Update::Clear => { - self.chunks.retain(|chunk| chunk.room_id != room_id); - self.items_chunks.retain(|chunk| chunk.room_id != room_id); + self.chunks.retain(|chunk| chunk.linked_chunk_id != linked_chunk_id); + self.items_chunks.retain(|chunk| chunk.linked_chunk_id != linked_chunk_id); // We deliberately leave the items intact. } } @@ -249,7 +272,7 @@ where fn insert_chunk( chunks: &mut Vec, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, previous: Option, new: ChunkIdentifier, next: Option, @@ -258,9 +281,11 @@ where if let Some(previous) = previous { let entry_for_previous_chunk = chunks .iter_mut() - .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| { - room_id == room_id_candidate && *chunk == previous - }) + .find( + |ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| { + linked_chunk_id == linked_chunk_id_candidate && *chunk == previous + }, + ) .expect("Previous chunk should be present"); // Link the chunk. @@ -271,9 +296,11 @@ where if let Some(next) = next { let entry_for_next_chunk = chunks .iter_mut() - .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| { - room_id == room_id_candidate && *chunk == next - }) + .find( + |ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| { + linked_chunk_id == linked_chunk_id_candidate && *chunk == next + }, + ) .expect("Next chunk should be present"); // Link the chunk. @@ -282,7 +309,7 @@ where // Insert the chunk. chunks.push(ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.to_owned(), previous_chunk: previous, chunk: new, next_chunk: next, @@ -291,27 +318,32 @@ where fn remove_chunk( chunks: &mut Vec, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, chunk_to_remove: ChunkIdentifier, ) { let entry_nth_to_remove = chunks .iter() .enumerate() - .find_map(|(nth, ChunkRow { room_id: room_id_candidate, chunk, .. })| { - (room_id == room_id_candidate && *chunk == chunk_to_remove).then_some(nth) - }) + .find_map( + |(nth, ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. })| { + (linked_chunk_id == linked_chunk_id_candidate && *chunk == chunk_to_remove) + .then_some(nth) + }, + ) .expect("Remove an unknown chunk"); - let ChunkRow { room_id, previous_chunk: previous, next_chunk: next, .. } = + let ChunkRow { linked_chunk_id, previous_chunk: previous, next_chunk: next, .. } = chunks.remove(entry_nth_to_remove); // Find the previous chunk, and update its next chunk. if let Some(previous) = previous { let entry_for_previous_chunk = chunks .iter_mut() - .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| { - &room_id == room_id_candidate && *chunk == previous - }) + .find( + |ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| { + &linked_chunk_id == linked_chunk_id_candidate && *chunk == previous + }, + ) .expect("Previous chunk should be present"); // Insert the chunk. @@ -322,9 +354,11 @@ where if let Some(next) = next { let entry_for_next_chunk = chunks .iter_mut() - .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| { - &room_id == room_id_candidate && *chunk == next - }) + .find( + |ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| { + &linked_chunk_id == linked_chunk_id_candidate && *chunk == next + }, + ) .expect("Next chunk should be present"); // Insert the chunk. @@ -333,17 +367,17 @@ where } } - /// Return an iterator that yields items of a particular room, in no + /// Return an iterator that yields items of a particular linked chunk, in no /// particular order. - pub fn unordered_room_items<'a>( + pub fn unordered_linked_chunk_items<'a>( &'a self, - room_id: &'a RoomId, + target: LinkedChunkId<'a>, ) -> impl Iterator { self.items_chunks.iter().filter_map(move |item_row| { - if item_row.room_id == room_id { + if item_row.linked_chunk_id == target { match &item_row.item { Either::Item(item_id) => { - Some((self.items.get(room_id)?.get(item_id)?, item_row.position)) + Some((self.items.get(&target.to_owned())?.get(item_id)?, item_row.position)) } Either::Gap(..) => None, } @@ -353,20 +387,21 @@ where }) } - /// Return an iterator over all items of all rooms, without their actual - /// positions. + /// Return an iterator over all items of all room linked chunks, without + /// their actual positions. /// /// This will include out-of-band items. - pub fn items(&self) -> impl Iterator { - self.items - .iter() - .flat_map(|(room_id, items)| items.values().map(|item| (item, room_id.as_ref()))) + pub fn items(&self) -> impl Iterator)> { + self.items.iter().flat_map(|(linked_chunk_id, items)| { + items.values().map(|item| (item, linked_chunk_id.as_ref())) + }) } /// Save a single item "out-of-band" in the relational linked chunk. pub fn save_item(&mut self, room_id: OwnedRoomId, item: Item) { let id = item.id(); - self.items.entry(room_id).or_default().insert(id, item); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id); + self.items.entry(linked_chunk_id).or_default().insert(id, item); } } @@ -381,23 +416,28 @@ where /// Return an error result if the data was malformed in the struct, with a /// string message explaining details about the error. #[doc(hidden)] - pub fn load_all_chunks(&self, room_id: &RoomId) -> Result>, String> { + pub fn load_all_chunks( + &self, + linked_chunk_id: LinkedChunkId<'_>, + ) -> Result>, String> { self.chunks .iter() - .filter(|chunk| chunk.room_id == room_id) - .map(|chunk_row| load_raw_chunk(self, chunk_row, room_id)) + .filter(|chunk| chunk.linked_chunk_id == linked_chunk_id) + .map(|chunk_row| load_raw_chunk(self, chunk_row, linked_chunk_id)) .collect::, String>>() } pub fn load_last_chunk( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, ) -> Result<(Option>, ChunkIdentifierGenerator), String> { // Find the latest chunk identifier to generate a `ChunkIdentifierGenerator`. let chunk_identifier_generator = match self .chunks .iter() - .filter_map(|chunk_row| (chunk_row.room_id == room_id).then_some(chunk_row.chunk)) + .filter_map(|chunk_row| { + (chunk_row.linked_chunk_id == linked_chunk_id).then_some(chunk_row.chunk) + }) .max() { Some(last_chunk_identifier) => { @@ -411,7 +451,7 @@ where let mut chunk_row = None; for chunk_row_candidate in &self.chunks { - if chunk_row_candidate.room_id == room_id { + if chunk_row_candidate.linked_chunk_id == linked_chunk_id { number_of_chunks += 1; if chunk_row_candidate.next_chunk.is_none() { @@ -445,25 +485,26 @@ where }; // Build the chunk. - load_raw_chunk(self, chunk_row, room_id) + load_raw_chunk(self, chunk_row, linked_chunk_id) .map(|raw_chunk| (Some(raw_chunk), chunk_identifier_generator)) } pub fn load_previous_chunk( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, before_chunk_identifier: ChunkIdentifier, ) -> Result>, String> { // Find the chunk before the chunk identified by `before_chunk_identifier`. let Some(chunk_row) = self.chunks.iter().find(|chunk_row| { - chunk_row.room_id == room_id && chunk_row.next_chunk == Some(before_chunk_identifier) + chunk_row.linked_chunk_id == linked_chunk_id + && chunk_row.next_chunk == Some(before_chunk_identifier) }) else { // Chunk is not found. return Ok(None); }; // Build the chunk. - load_raw_chunk(self, chunk_row, room_id).map(Some) + load_raw_chunk(self, chunk_row, linked_chunk_id).map(Some) } } @@ -480,7 +521,7 @@ where fn load_raw_chunk( relational_linked_chunk: &RelationalLinkedChunk, chunk_row: &ChunkRow, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, ) -> Result, String> where Item: Clone, @@ -492,7 +533,8 @@ where .items_chunks .iter() .filter(|item_row| { - item_row.room_id == room_id && item_row.position.chunk_identifier() == chunk_row.chunk + item_row.linked_chunk_id == linked_chunk_id + && item_row.position.chunk_identifier() == chunk_row.chunk }) .peekable(); @@ -535,7 +577,11 @@ where collected_items .into_iter() .filter_map(|(item_id, _index)| { - relational_linked_chunk.items.get(room_id)?.get(item_id).cloned() + relational_linked_chunk + .items + .get(&linked_chunk_id.to_owned())? + .get(item_id) + .cloned() }) .collect(), ), @@ -584,10 +630,12 @@ mod tests { #[test] fn test_new_items_chunk() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned()); + let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new(); relational_linked_chunk.apply_updates( - room_id, + linked_chunk_id.as_ref(), vec![ // 0 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -609,31 +657,32 @@ mod tests { relational_linked_chunk.chunks, &[ ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), previous_chunk: Some(CId::new(3)), chunk: CId::new(0), next_chunk: Some(CId::new(1)) }, ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), previous_chunk: Some(CId::new(0)), chunk: CId::new(1), next_chunk: None }, ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), previous_chunk: None, chunk: CId::new(2), next_chunk: Some(CId::new(3)) }, ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id, previous_chunk: Some(CId::new(2)), chunk: CId::new(3), next_chunk: Some(CId::new(0)) }, ], ); + // Items have not been modified. assert!(relational_linked_chunk.items_chunks.is_empty()); } @@ -641,10 +690,12 @@ mod tests { #[test] fn test_new_gap_chunk() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned()); + let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new(); relational_linked_chunk.apply_updates( - room_id, + linked_chunk_id.as_ref(), vec![ // 0 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -665,19 +716,19 @@ mod tests { relational_linked_chunk.chunks, &[ ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), previous_chunk: None, chunk: CId::new(0), next_chunk: Some(CId::new(1)) }, ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), previous_chunk: Some(CId::new(0)), chunk: CId::new(1), next_chunk: Some(CId::new(2)) }, ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), previous_chunk: Some(CId::new(1)), chunk: CId::new(2), next_chunk: None @@ -688,7 +739,7 @@ mod tests { assert_eq!( relational_linked_chunk.items_chunks, &[ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id, position: Position::new(CId::new(1), 0), item: Either::Gap(()) }], @@ -698,10 +749,12 @@ mod tests { #[test] fn test_remove_chunk() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned()); + let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new(); relational_linked_chunk.apply_updates( - room_id, + linked_chunk_id.as_ref(), vec![ // 0 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -724,19 +777,20 @@ mod tests { relational_linked_chunk.chunks, &[ ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), previous_chunk: None, chunk: CId::new(0), next_chunk: Some(CId::new(2)) }, ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id, previous_chunk: Some(CId::new(0)), chunk: CId::new(2), next_chunk: None }, ], ); + // Items no longer contains the gap. assert!(relational_linked_chunk.items_chunks.is_empty()); } @@ -744,10 +798,12 @@ mod tests { #[test] fn test_push_items() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned()); + let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new(); relational_linked_chunk.apply_updates( - room_id, + linked_chunk_id.as_ref(), vec![ // new chunk (this is not mandatory for this test, but let's try to be realistic) Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -767,13 +823,13 @@ mod tests { relational_linked_chunk.chunks, &[ ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), previous_chunk: None, chunk: CId::new(0), next_chunk: Some(CId::new(1)) }, ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), previous_chunk: Some(CId::new(0)), chunk: CId::new(1), next_chunk: None @@ -785,42 +841,42 @@ mod tests { relational_linked_chunk.items_chunks, &[ ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(0), 0), item: Either::Item('a') }, ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(0), 1), item: Either::Item('b') }, ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(0), 2), item: Either::Item('c') }, ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(1), 0), item: Either::Item('x') }, ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(1), 1), item: Either::Item('y') }, ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(1), 2), item: Either::Item('z') }, ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(0), 3), item: Either::Item('d') }, ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(0), 4), item: Either::Item('e') }, @@ -831,10 +887,12 @@ mod tests { #[test] fn test_remove_item() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned()); + let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new(); relational_linked_chunk.apply_updates( - room_id, + linked_chunk_id.as_ref(), vec![ // new chunk (this is not mandatory for this test, but let's try to be realistic) Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -854,7 +912,7 @@ mod tests { assert_eq!( relational_linked_chunk.chunks, &[ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), previous_chunk: None, chunk: CId::new(0), next_chunk: None @@ -865,17 +923,17 @@ mod tests { relational_linked_chunk.items_chunks, &[ ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(0), 0), item: Either::Item('b') }, ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(0), 1), item: Either::Item('c') }, ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(0), 2), item: Either::Item('e') }, @@ -886,10 +944,12 @@ mod tests { #[test] fn test_detach_last_items() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned()); + let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new(); relational_linked_chunk.apply_updates( - room_id, + linked_chunk_id.as_ref(), vec![ // new chunk Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -912,13 +972,13 @@ mod tests { relational_linked_chunk.chunks, &[ ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), previous_chunk: None, chunk: CId::new(0), next_chunk: Some(CId::new(1)) }, ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), previous_chunk: Some(CId::new(0)), chunk: CId::new(1), next_chunk: None @@ -930,27 +990,27 @@ mod tests { relational_linked_chunk.items_chunks, &[ ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(0), 0), item: Either::Item('a') }, ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(0), 1), item: Either::Item('b') }, ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(1), 0), item: Either::Item('x') }, ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(1), 1), item: Either::Item('y') }, ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(1), 2), item: Either::Item('z') }, @@ -961,10 +1021,14 @@ mod tests { #[test] fn test_start_and_end_reattach_items() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned()); + let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new(); - relational_linked_chunk - .apply_updates(room_id, vec![Update::StartReattachItems, Update::EndReattachItems]); + relational_linked_chunk.apply_updates( + linked_chunk_id.as_ref(), + vec![Update::StartReattachItems, Update::EndReattachItems], + ); // Nothing happened. assert!(relational_linked_chunk.chunks.is_empty()); @@ -974,11 +1038,15 @@ mod tests { #[test] fn test_clear() { let r0 = room_id!("!r0:matrix.org"); + let linked_chunk_id0 = OwnedLinkedChunkId::Room(r0.to_owned()); + let r1 = room_id!("!r1:matrix.org"); + let linked_chunk_id1 = OwnedLinkedChunkId::Room(r1.to_owned()); + let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new(); relational_linked_chunk.apply_updates( - r0, + linked_chunk_id0.as_ref(), vec![ // new chunk (this is not mandatory for this test, but let's try to be realistic) Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -988,7 +1056,7 @@ mod tests { ); relational_linked_chunk.apply_updates( - r1, + linked_chunk_id1.as_ref(), vec![ // new chunk (this is not mandatory for this test, but let's try to be realistic) Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -1002,13 +1070,13 @@ mod tests { relational_linked_chunk.chunks, &[ ChunkRow { - room_id: r0.to_owned(), + linked_chunk_id: linked_chunk_id0.to_owned(), previous_chunk: None, chunk: CId::new(0), next_chunk: None, }, ChunkRow { - room_id: r1.to_owned(), + linked_chunk_id: linked_chunk_id1.to_owned(), previous_chunk: None, chunk: CId::new(0), next_chunk: None, @@ -1021,22 +1089,22 @@ mod tests { relational_linked_chunk.items_chunks, &[ ItemRow { - room_id: r0.to_owned(), + linked_chunk_id: linked_chunk_id0.to_owned(), position: Position::new(CId::new(0), 0), item: Either::Item('a') }, ItemRow { - room_id: r0.to_owned(), + linked_chunk_id: linked_chunk_id0.to_owned(), position: Position::new(CId::new(0), 1), item: Either::Item('b') }, ItemRow { - room_id: r0.to_owned(), + linked_chunk_id: linked_chunk_id0.to_owned(), position: Position::new(CId::new(0), 2), item: Either::Item('c') }, ItemRow { - room_id: r1.to_owned(), + linked_chunk_id: linked_chunk_id1.to_owned(), position: Position::new(CId::new(0), 0), item: Either::Item('x') }, @@ -1044,13 +1112,13 @@ mod tests { ); // Now, time for a clean up. - relational_linked_chunk.apply_updates(r0, vec![Update::Clear]); + relational_linked_chunk.apply_updates(linked_chunk_id0.as_ref(), vec![Update::Clear]); // Only items from r1 remain. assert_eq!( relational_linked_chunk.chunks, &[ChunkRow { - room_id: r1.to_owned(), + linked_chunk_id: linked_chunk_id1.to_owned(), previous_chunk: None, chunk: CId::new(0), next_chunk: None, @@ -1060,7 +1128,7 @@ mod tests { assert_eq!( relational_linked_chunk.items_chunks, &[ItemRow { - room_id: r1.to_owned(), + linked_chunk_id: linked_chunk_id1.to_owned(), position: Position::new(CId::new(0), 0), item: Either::Item('x') },], @@ -1070,30 +1138,33 @@ mod tests { #[test] fn test_load_empty_linked_chunk() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned()); // When I reload the linked chunk components from an empty store, let relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new(); - let result = relational_linked_chunk.load_all_chunks(room_id).unwrap(); + let result = relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap(); assert!(result.is_empty()); } #[test] fn test_load_all_chunks_with_empty_items() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned()); let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new(); // When I store an empty items chunks, relational_linked_chunk.apply_updates( - room_id, + linked_chunk_id.as_ref(), vec![Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }], ); // It correctly gets reloaded as such. - let lc = - from_all_chunks::<3, _, _>(relational_linked_chunk.load_all_chunks(room_id).unwrap()) - .expect("building succeeds") - .expect("this leads to a non-empty linked chunk"); + let lc = from_all_chunks::<3, _, _>( + relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap(), + ) + .expect("building succeeds") + .expect("this leads to a non-empty linked chunk"); assert_items_eq!(lc, []); } @@ -1101,10 +1172,12 @@ mod tests { #[test] fn test_rebuild_linked_chunk() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned()); + let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new(); relational_linked_chunk.apply_updates( - room_id, + linked_chunk_id.as_ref(), vec![ // new chunk Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -1124,10 +1197,11 @@ mod tests { ], ); - let lc = - from_all_chunks::<3, _, _>(relational_linked_chunk.load_all_chunks(room_id).unwrap()) - .expect("building succeeds") - .expect("this leads to a non-empty linked chunk"); + let lc = from_all_chunks::<3, _, _>( + relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap(), + ) + .expect("building succeeds") + .expect("this leads to a non-empty linked chunk"); // The linked chunk is correctly reloaded. assert_items_eq!(lc, ['a', 'b', 'c'] [-] ['d', 'e', 'f']); @@ -1136,10 +1210,12 @@ mod tests { #[test] fn test_replace_item() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned()); + let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new(); relational_linked_chunk.apply_updates( - room_id, + linked_chunk_id.as_ref(), vec![ // new chunk (this is not mandatory for this test, but let's try to be realistic) Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, @@ -1154,7 +1230,7 @@ mod tests { assert_eq!( relational_linked_chunk.chunks, &[ChunkRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), previous_chunk: None, chunk: CId::new(0), next_chunk: None, @@ -1166,17 +1242,17 @@ mod tests { relational_linked_chunk.items_chunks, &[ ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(0), 0), item: Either::Item('a') }, ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id: linked_chunk_id.clone(), position: Position::new(CId::new(0), 1), item: Either::Item('B') }, ItemRow { - room_id: room_id.to_owned(), + linked_chunk_id, position: Position::new(CId::new(0), 2), item: Either::Item('c') }, @@ -1187,11 +1263,15 @@ mod tests { #[test] fn test_unordered_events() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned()); + let other_room_id = room_id!("!r1:matrix.org"); + let other_linked_chunk_id = OwnedLinkedChunkId::Room(other_room_id.to_owned()); + let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new(); relational_linked_chunk.apply_updates( - room_id, + linked_chunk_id.as_ref(), vec![ Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] }, @@ -1201,14 +1281,15 @@ mod tests { ); relational_linked_chunk.apply_updates( - other_room_id, + other_linked_chunk_id.as_ref(), vec![ Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x', 'y', 'z'] }, ], ); - let mut events = relational_linked_chunk.unordered_room_items(room_id); + let mut events = + relational_linked_chunk.unordered_linked_chunk_items(linked_chunk_id.as_ref()); assert_eq!(events.next().unwrap(), (&'a', Position::new(CId::new(0), 0))); assert_eq!(events.next().unwrap(), (&'b', Position::new(CId::new(0), 1))); @@ -1222,12 +1303,14 @@ mod tests { #[test] fn test_load_last_chunk() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned()); + let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new(); // Case #1: no last chunk. { let (last_chunk, chunk_identifier_generator) = - relational_linked_chunk.load_last_chunk(room_id).unwrap(); + relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap(); assert!(last_chunk.is_none()); assert_eq!(chunk_identifier_generator.current(), 0); @@ -1236,7 +1319,7 @@ mod tests { // Case #2: only one chunk is present. { relational_linked_chunk.apply_updates( - room_id, + linked_chunk_id.as_ref(), vec![ Update::NewItemsChunk { previous: None, new: CId::new(42), next: None }, Update::PushItems { at: Position::new(CId::new(42), 0), items: vec!['a', 'b'] }, @@ -1244,7 +1327,7 @@ mod tests { ); let (last_chunk, chunk_identifier_generator) = - relational_linked_chunk.load_last_chunk(room_id).unwrap(); + relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap(); assert_matches!(last_chunk, Some(last_chunk) => { assert_eq!(last_chunk.identifier, 42); @@ -1261,7 +1344,7 @@ mod tests { // Case #3: more chunks are present. { relational_linked_chunk.apply_updates( - room_id, + linked_chunk_id.as_ref(), vec![ Update::NewItemsChunk { previous: Some(CId::new(42)), @@ -1276,7 +1359,7 @@ mod tests { ); let (last_chunk, chunk_identifier_generator) = - relational_linked_chunk.load_last_chunk(room_id).unwrap(); + relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap(); assert_matches!(last_chunk, Some(last_chunk) => { assert_eq!(last_chunk.identifier, 7); @@ -1296,10 +1379,11 @@ mod tests { #[test] fn test_load_last_chunk_with_a_cycle() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned()); let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new(); relational_linked_chunk.apply_updates( - room_id, + linked_chunk_id.as_ref(), vec![ Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, Update::NewItemsChunk { @@ -1313,19 +1397,21 @@ mod tests { ], ); - relational_linked_chunk.load_last_chunk(room_id).unwrap_err(); + relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap_err(); } #[test] fn test_load_previous_chunk() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned()); let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new(); // Case #1: no chunk at all, equivalent to having an inexistent // `before_chunk_identifier`. { - let previous_chunk = - relational_linked_chunk.load_previous_chunk(room_id, CId::new(153)).unwrap(); + let previous_chunk = relational_linked_chunk + .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(153)) + .unwrap(); assert!(previous_chunk.is_none()); } @@ -1334,12 +1420,13 @@ mod tests { // one, it doesn't exist. { relational_linked_chunk.apply_updates( - room_id, + linked_chunk_id.as_ref(), vec![Update::NewItemsChunk { previous: None, new: CId::new(42), next: None }], ); - let previous_chunk = - relational_linked_chunk.load_previous_chunk(room_id, CId::new(42)).unwrap(); + let previous_chunk = relational_linked_chunk + .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(42)) + .unwrap(); assert!(previous_chunk.is_none()); } @@ -1347,7 +1434,7 @@ mod tests { // Case #3: there is two chunks. { relational_linked_chunk.apply_updates( - room_id, + linked_chunk_id.as_ref(), vec![ // new chunk before the one that exists. Update::NewItemsChunk { @@ -1362,8 +1449,9 @@ mod tests { ], ); - let previous_chunk = - relational_linked_chunk.load_previous_chunk(room_id, CId::new(42)).unwrap(); + let previous_chunk = relational_linked_chunk + .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(42)) + .unwrap(); assert_matches!(previous_chunk, Some(previous_chunk) => { assert_eq!(previous_chunk.identifier, 7); diff --git a/crates/matrix-sdk-sqlite/migrations/event_cache_store/008_linked_chunk_id.sql b/crates/matrix-sdk-sqlite/migrations/event_cache_store/008_linked_chunk_id.sql new file mode 100644 index 00000000000..b7e18f6a81f --- /dev/null +++ b/crates/matrix-sdk-sqlite/migrations/event_cache_store/008_linked_chunk_id.sql @@ -0,0 +1,71 @@ +-- We're changing the format of the linked chunk keys, and not migrating them over. +DELETE FROM "linked_chunks"; + +-- We're changing the name of `room_id` to `linked_chunk_id` in the linked chunks table, and it's +-- part of a primary key, so we'll recreate all the impacted tables. +DROP TABLE "event_chunks"; +DROP TABLE "linked_chunks"; +DROP TABLE "gap_chunks"; + +CREATE TABLE "linked_chunks" ( + -- Which linked chunk does this chunk belong to? (hashed key shared with the two other tables) + "linked_chunk_id" BLOB NOT NULL, + -- Identifier of the chunk, unique per room. Corresponds to a `ChunkIdentifier`. + "id" INTEGER NOT NULL, + + -- Previous chunk in the linked list. Corresponds to a `ChunkIdentifier`. + "previous" INTEGER, + -- Next chunk in the linked list. Corresponds to a `ChunkIdentifier`. + "next" INTEGER, + -- Type of underlying entries: E for events, G for gaps + "type" TEXT CHECK("type" IN ('E', 'G')) NOT NULL, + + -- Primary key is composed of the linked chunk ID and the chunk identifier. + -- Such pairs must be unique. + PRIMARY KEY (linked_chunk_id, id) +) +WITHOUT ROWID; + +-- Entries inside an event chunk. +CREATE TABLE "event_chunks" ( + -- Which linked chunk does this event belong to? (hashed key shared with linked_chunks) + "linked_chunk_id" BLOB NOT NULL, + -- Which chunk does this event refer to? Corresponds to a `ChunkIdentifier`. + "chunk_id" INTEGER NOT NULL, + + -- `OwnedEventId` for events. + "event_id" BLOB NOT NULL, + -- Position (index) in the chunk. + "position" INTEGER NOT NULL, + + -- Primary key is the event ID. + PRIMARY KEY (event_id), + + -- We need a uniqueness constraint over the linked chunk id, `chunk_id` and + -- `position` tuple because (i) they must be unique, (ii) it dramatically + -- improves the performance. + UNIQUE (linked_chunk_id, chunk_id, position), + + -- If the owning chunk gets deleted, delete the entry too. + FOREIGN KEY (linked_chunk_id, chunk_id) REFERENCES linked_chunks(linked_chunk_id, id) ON DELETE CASCADE +) +WITHOUT ROWID; + +-- Gaps! +CREATE TABLE "gap_chunks" ( + -- Which linked chunk does this event belong to? (hashed key shared with linked_chunks) + "linked_chunk_id" BLOB NOT NULL, + -- Which chunk does this gap refer to? Corresponds to a `ChunkIdentifier`. + "chunk_id" INTEGER NOT NULL, + + -- The previous batch token of a gap (encrypted value). + "prev_token" BLOB NOT NULL, + + -- Primary key is composed of the linked chunk ID and the chunk identifier. + -- Such pairs must be unique. + PRIMARY KEY (linked_chunk_id, chunk_id), + + -- If the owning chunk gets deleted, delete the entry too. + FOREIGN KEY (chunk_id, linked_chunk_id) REFERENCES linked_chunks(id, linked_chunk_id) ON DELETE CASCADE +) +WITHOUT ROWID; diff --git a/crates/matrix-sdk-sqlite/src/event_cache_store.rs b/crates/matrix-sdk-sqlite/src/event_cache_store.rs index 854c7b20688..d8be0dfce99 100644 --- a/crates/matrix-sdk-sqlite/src/event_cache_store.rs +++ b/crates/matrix-sdk-sqlite/src/event_cache_store.rs @@ -32,7 +32,8 @@ use matrix_sdk_base::{ Event, Gap, }, linked_chunk::{ - ChunkContent, ChunkIdentifier, ChunkIdentifierGenerator, Position, RawChunk, Update, + ChunkContent, ChunkIdentifier, ChunkIdentifierGenerator, LinkedChunkId, Position, RawChunk, + Update, }, media::{MediaRequestParameters, UniqueKey}, }; @@ -72,7 +73,7 @@ const DATABASE_NAME: &str = "matrix-sdk-event-cache.sqlite3"; /// This is used to figure whether the SQLite database requires a migration. /// Every new SQL migration should imply a bump of this number, and changes in /// the [`run_migrations`] function. -const DATABASE_VERSION: u8 = 7; +const DATABASE_VERSION: u8 = 8; /// The string used to identify a chunk of type events, in the `type` field in /// the database. @@ -235,7 +236,7 @@ trait TransactionExtForLinkedChunks { fn rebuild_chunk( &self, store: &SqliteEventCacheStore, - room_id: &Key, + linked_chunk_id: &Key, previous: Option, index: u64, next: Option, @@ -245,14 +246,14 @@ trait TransactionExtForLinkedChunks { fn load_gap_content( &self, store: &SqliteEventCacheStore, - room_id: &Key, + linked_chunk_id: &Key, chunk_id: ChunkIdentifier, ) -> Result; fn load_events_content( &self, store: &SqliteEventCacheStore, - room_id: &Key, + linked_chunk_id: &Key, chunk_id: ChunkIdentifier, ) -> Result>; } @@ -261,7 +262,7 @@ impl TransactionExtForLinkedChunks for Transaction<'_> { fn rebuild_chunk( &self, store: &SqliteEventCacheStore, - room_id: &Key, + linked_chunk_id: &Key, previous: Option, id: u64, next: Option, @@ -274,13 +275,13 @@ impl TransactionExtForLinkedChunks for Transaction<'_> { match chunk_type { CHUNK_TYPE_GAP_TYPE_STRING => { // It's a gap! - let gap = self.load_gap_content(store, room_id, id)?; + let gap = self.load_gap_content(store, linked_chunk_id, id)?; Ok(RawChunk { content: ChunkContent::Gap(gap), previous, identifier: id, next }) } CHUNK_TYPE_EVENT_TYPE_STRING => { // It's events! - let events = self.load_events_content(store, room_id, id)?; + let events = self.load_events_content(store, linked_chunk_id, id)?; Ok(RawChunk { content: ChunkContent::Items(events), previous, @@ -301,14 +302,14 @@ impl TransactionExtForLinkedChunks for Transaction<'_> { fn load_gap_content( &self, store: &SqliteEventCacheStore, - room_id: &Key, + linked_chunk_id: &Key, chunk_id: ChunkIdentifier, ) -> Result { // There's at most one row for it in the database, so a call to `query_row` is // sufficient. let encoded_prev_token: Vec = self.query_row( - "SELECT prev_token FROM gap_chunks WHERE chunk_id = ? AND room_id = ?", - (chunk_id.index(), &room_id), + "SELECT prev_token FROM gap_chunks WHERE chunk_id = ? AND linked_chunk_id = ?", + (chunk_id.index(), &linked_chunk_id), |row| row.get(0), )?; let prev_token_bytes = store.decode_value(&encoded_prev_token)?; @@ -319,7 +320,7 @@ impl TransactionExtForLinkedChunks for Transaction<'_> { fn load_events_content( &self, store: &SqliteEventCacheStore, - room_id: &Key, + linked_chunk_id: &Key, chunk_id: ChunkIdentifier, ) -> Result> { // Retrieve all the events from the database. @@ -329,13 +330,12 @@ impl TransactionExtForLinkedChunks for Transaction<'_> { .prepare( r#" SELECT events.content - FROM event_chunks ec - INNER JOIN events USING (event_id, room_id) - WHERE ec.chunk_id = ? AND ec.room_id = ? + FROM event_chunks ec, events + WHERE events.event_id = ec.event_id AND ec.chunk_id = ? AND ec.linked_chunk_id = ? ORDER BY ec.position ASC "#, )? - .query_map((chunk_id.index(), &room_id), |row| row.get::<_, Vec>(0))? + .query_map((chunk_id.index(), &linked_chunk_id), |row| row.get::<_, Vec>(0))? { let encoded_content = event_data?; let serialized_content = store.decode_value(&encoded_content)?; @@ -426,6 +426,16 @@ async fn run_migrations(conn: &SqliteAsyncConn, version: u8) -> Result<()> { .await?; } + if version < 8 { + conn.with_transaction(|txn| { + txn.execute_batch(include_str!( + "../migrations/event_cache_store/008_linked_chunk_id.sql" + ))?; + txn.set_db_version(8) + }) + .await?; + } + Ok(()) } @@ -468,13 +478,14 @@ impl EventCacheStore for SqliteEventCacheStore { async fn handle_linked_chunk_updates( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, updates: Vec>, ) -> Result<(), Self::Error> { // Use a single transaction throughout this function, so that either all updates // work, or none is taken into account. - let hashed_room_id = self.encode_key(keys::LINKED_CHUNKS, room_id); - let room_id = room_id.to_owned(); + let hashed_linked_chunk_id = + self.encode_key(keys::LINKED_CHUNKS, linked_chunk_id.storage_key()); + let linked_chunk_id = linked_chunk_id.to_owned(); let this = self.clone(); with_immediate_transaction(self.acquire().await?, move |txn| { @@ -486,13 +497,13 @@ impl EventCacheStore for SqliteEventCacheStore { let next = next.as_ref().map(ChunkIdentifier::index); trace!( - %room_id, + %linked_chunk_id, "new events chunk (prev={previous:?}, i={new}, next={next:?})", ); insert_chunk( txn, - &hashed_room_id, + &hashed_linked_chunk_id, previous, new, next, @@ -509,14 +520,14 @@ impl EventCacheStore for SqliteEventCacheStore { let next = next.as_ref().map(ChunkIdentifier::index); trace!( - %room_id, + %linked_chunk_id, "new gap chunk (prev={previous:?}, i={new}, next={next:?})", ); // Insert the chunk as a gap. insert_chunk( txn, - &hashed_room_id, + &hashed_linked_chunk_id, previous, new, next, @@ -526,38 +537,38 @@ impl EventCacheStore for SqliteEventCacheStore { // Insert the gap's value. txn.execute( r#" - INSERT INTO gap_chunks(chunk_id, room_id, prev_token) + INSERT INTO gap_chunks(chunk_id, linked_chunk_id, prev_token) VALUES (?, ?, ?) "#, - (new, &hashed_room_id, prev_token), + (new, &hashed_linked_chunk_id, prev_token), )?; } Update::RemoveChunk(chunk_identifier) => { let chunk_id = chunk_identifier.index(); - trace!(%room_id, "removing chunk @ {chunk_id}"); + trace!(%linked_chunk_id, "removing chunk @ {chunk_id}"); // Find chunk to delete. let (previous, next): (Option, Option) = txn.query_row( - "SELECT previous, next FROM linked_chunks WHERE id = ? AND room_id = ?", - (chunk_id, &hashed_room_id), + "SELECT previous, next FROM linked_chunks WHERE id = ? AND linked_chunk_id = ?", + (chunk_id, &hashed_linked_chunk_id), |row| Ok((row.get(0)?, row.get(1)?)) )?; // Replace its previous' next to its own next. if let Some(previous) = previous { - txn.execute("UPDATE linked_chunks SET next = ? WHERE id = ? AND room_id = ?", (next, previous, &hashed_room_id))?; + txn.execute("UPDATE linked_chunks SET next = ? WHERE id = ? AND linked_chunk_id = ?", (next, previous, &hashed_linked_chunk_id))?; } // Replace its next' previous to its own previous. if let Some(next) = next { - txn.execute("UPDATE linked_chunks SET previous = ? WHERE id = ? AND room_id = ?", (previous, next, &hashed_room_id))?; + txn.execute("UPDATE linked_chunks SET previous = ? WHERE id = ? AND linked_chunk_id = ?", (previous, next, &hashed_linked_chunk_id))?; } // Now delete it, and let cascading delete corresponding entries in the // other data tables. - txn.execute("DELETE FROM linked_chunks WHERE id = ? AND room_id = ?", (chunk_id, &hashed_room_id))?; + txn.execute("DELETE FROM linked_chunks WHERE id = ? AND linked_chunk_id = ?", (chunk_id, &hashed_linked_chunk_id))?; } Update::PushItems { at, items } => { @@ -568,10 +579,10 @@ impl EventCacheStore for SqliteEventCacheStore { let chunk_id = at.chunk_identifier().index(); - trace!(%room_id, "pushing {} items @ {chunk_id}", items.len()); + trace!(%linked_chunk_id, "pushing {} items @ {chunk_id}", items.len()); let mut chunk_statement = txn.prepare( - "INSERT INTO event_chunks(chunk_id, room_id, event_id, position) VALUES (?, ?, ?, ?)" + "INSERT INTO event_chunks(chunk_id, linked_chunk_id, event_id, position) VALUES (?, ?, ?, ?)" )?; // Note: we use `OR REPLACE` here, because the event might have been @@ -584,17 +595,20 @@ impl EventCacheStore for SqliteEventCacheStore { let invalid_event = |event: TimelineEvent| { let Some(event_id) = event.event_id() else { - error!(%room_id, "Trying to push an event with no ID"); + error!(%linked_chunk_id, "Trying to push an event with no ID"); return None; }; Some((event_id.to_string(), event)) }; + let room_id = linked_chunk_id.room_id(); + let hashed_room_id = this.encode_key(keys::LINKED_CHUNKS, room_id); + for (i, (event_id, event)) in items.into_iter().filter_map(invalid_event).enumerate() { // Insert the location information into the database. let index = at.index() + i; - chunk_statement.execute((chunk_id, &hashed_room_id, &event_id, index))?; + chunk_statement.execute((chunk_id, &hashed_linked_chunk_id, &event_id, index))?; // Now, insert the event content into the database. let encoded_event = this.encode_event(&event)?; @@ -607,11 +621,11 @@ impl EventCacheStore for SqliteEventCacheStore { let index = at.index(); - trace!(%room_id, "replacing item @ {chunk_id}:{index}"); + trace!(%linked_chunk_id, "replacing item @ {chunk_id}:{index}"); // The event id should be the same, but just in case it changed… let Some(event_id) = event.event_id().map(|event_id| event_id.to_string()) else { - error!(%room_id, "Trying to replace an event with a new one that has no ID"); + error!(%linked_chunk_id, "Trying to replace an event with a new one that has no ID"); continue; }; @@ -619,14 +633,16 @@ impl EventCacheStore for SqliteEventCacheStore { // event id changed, we are a bit lenient here and will allow an insertion // of the new event. let encoded_event = this.encode_event(&event)?; + let room_id = linked_chunk_id.room_id(); + let hashed_room_id = this.encode_key(keys::LINKED_CHUNKS, room_id); txn.execute( "INSERT OR REPLACE INTO events(room_id, event_id, content, relates_to, rel_type) VALUES (?, ?, ?, ?, ?)" , (&hashed_room_id, &event_id, encoded_event.content, encoded_event.relates_to, encoded_event.rel_type))?; // Replace the event id in the linked chunk, in case it changed. txn.execute( - r#"UPDATE event_chunks SET event_id = ? WHERE room_id = ? AND chunk_id = ? AND position = ?"#, - (event_id, &hashed_room_id, chunk_id, index) + r#"UPDATE event_chunks SET event_id = ? WHERE linked_chunk_id = ? AND chunk_id = ? AND position = ?"#, + (event_id, &hashed_linked_chunk_id, chunk_id, index) )?; } @@ -634,44 +650,44 @@ impl EventCacheStore for SqliteEventCacheStore { let chunk_id = at.chunk_identifier().index(); let index = at.index(); - trace!(%room_id, "removing item @ {chunk_id}:{index}"); + trace!(%linked_chunk_id, "removing item @ {chunk_id}:{index}"); // Remove the entry in the chunk table. - txn.execute("DELETE FROM event_chunks WHERE room_id = ? AND chunk_id = ? AND position = ?", (&hashed_room_id, chunk_id, index))?; + txn.execute("DELETE FROM event_chunks WHERE linked_chunk_id = ? AND chunk_id = ? AND position = ?", (&hashed_linked_chunk_id, chunk_id, index))?; // Decrement the index of each item after the one we are // going to remove. // // Imagine we have the following events: // - // | event_id | room_id | chunk_id | position | - // |----------|---------|----------|----------| - // | $ev0 | !r0 | 42 | 0 | - // | $ev1 | !r0 | 42 | 1 | - // | $ev2 | !r0 | 42 | 2 | - // | $ev3 | !r0 | 42 | 3 | - // | $ev4 | !r0 | 42 | 4 | + // | event_id | linked_chunk_id | chunk_id | position | + // |----------|-----------------|----------|----------| + // | $ev0 | !r0 | 42 | 0 | + // | $ev1 | !r0 | 42 | 1 | + // | $ev2 | !r0 | 42 | 2 | + // | $ev3 | !r0 | 42 | 3 | + // | $ev4 | !r0 | 42 | 4 | // // `$ev2` has been removed, then we end up in this // state: // - // | event_id | room_id | chunk_id | position | - // |----------|---------|----------|----------| - // | $ev0 | !r0 | 42 | 0 | - // | $ev1 | !r0 | 42 | 1 | - // | | | | | <- no more `$ev2` - // | $ev3 | !r0 | 42 | 3 | - // | $ev4 | !r0 | 42 | 4 | + // | event_id | linked_chunk_id | chunk_id | position | + // |----------|--------------------|----------|----------| + // | $ev0 | !r0 | 42 | 0 | + // | $ev1 | !r0 | 42 | 1 | + // | | | | | <- no more `$ev2` + // | $ev3 | !r0 | 42 | 3 | + // | $ev4 | !r0 | 42 | 4 | // // We need to shift the `position` of `$ev3` and `$ev4` // to `position - 1`, like so: // - // | event_id | room_id | chunk_id | position | - // |----------|---------|----------|----------| - // | $ev0 | !r0 | 42 | 0 | - // | $ev1 | !r0 | 42 | 1 | - // | $ev3 | !r0 | 42 | 2 | - // | $ev4 | !r0 | 42 | 3 | + // | event_id | linked_chunk_id | chunk_id | position | + // |----------|-----------------|----------|----------| + // | $ev0 | !r0 | 42 | 0 | + // | $ev1 | !r0 | 42 | 1 | + // | $ev3 | !r0 | 42 | 2 | + // | $ev4 | !r0 | 42 | 3 | // // Usually, it boils down to run the following query: // @@ -686,7 +702,7 @@ impl EventCacheStore for SqliteEventCacheStore { // `$ev3` for example. What happens in this particular // case? The `position` of `$ev4` becomes `3`, however // `$ev3` already has `position = 3`. Because there - // is a `UNIQUE` constraint on `(room_id, chunk_id, + // is a `UNIQUE` constraint on `(linked_chunk_id, chunk_id, // position)`, it will result in a constraint violation. // // There is **no way** to control the execution order of @@ -741,17 +757,17 @@ impl EventCacheStore for SqliteEventCacheStore { r#" UPDATE event_chunks SET position = -(position - 1) - WHERE room_id = ? AND chunk_id = ? AND position > ? + WHERE linked_chunk_id = ? AND chunk_id = ? AND position > ? "#, - (&hashed_room_id, chunk_id, index) + (&hashed_linked_chunk_id, chunk_id, index) )?; txn.execute( r#" UPDATE event_chunks SET position = -position - WHERE position < 0 AND room_id = ? AND chunk_id = ? + WHERE position < 0 AND linked_chunk_id = ? AND chunk_id = ? "#, - (&hashed_room_id, chunk_id) + (&hashed_linked_chunk_id, chunk_id) )?; } @@ -759,19 +775,19 @@ impl EventCacheStore for SqliteEventCacheStore { let chunk_id = at.chunk_identifier().index(); let index = at.index(); - trace!(%room_id, "truncating items >= {chunk_id}:{index}"); + trace!(%linked_chunk_id, "truncating items >= {chunk_id}:{index}"); // Remove these entries. - txn.execute("DELETE FROM event_chunks WHERE room_id = ? AND chunk_id = ? AND position >= ?", (&hashed_room_id, chunk_id, index))?; + txn.execute("DELETE FROM event_chunks WHERE linked_chunk_id = ? AND chunk_id = ? AND position >= ?", (&hashed_linked_chunk_id, chunk_id, index))?; } Update::Clear => { - trace!(%room_id, "clearing items"); + trace!(%linked_chunk_id, "clearing items"); // Remove chunks, and let cascading do its job. txn.execute( - "DELETE FROM linked_chunks WHERE room_id = ?", - (&hashed_room_id,), + "DELETE FROM linked_chunks WHERE linked_chunk_id = ?", + (&hashed_linked_chunk_id,), )?; } @@ -790,10 +806,10 @@ impl EventCacheStore for SqliteEventCacheStore { async fn load_all_chunks( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, ) -> Result>, Self::Error> { - let room_id = room_id.to_owned(); - let hashed_room_id = self.encode_key(keys::LINKED_CHUNKS, &room_id); + let hashed_linked_chunk_id = + self.encode_key(keys::LINKED_CHUNKS, linked_chunk_id.storage_key()); let this = self.clone(); @@ -806,14 +822,14 @@ impl EventCacheStore for SqliteEventCacheStore { // Use `ORDER BY id` to get a deterministic ordering for testing purposes. for data in txn .prepare( - "SELECT id, previous, next, type FROM linked_chunks WHERE room_id = ? ORDER BY id", + "SELECT id, previous, next, type FROM linked_chunks WHERE linked_chunk_id = ? ORDER BY id", )? - .query_map((&hashed_room_id,), Self::map_row_to_chunk)? + .query_map((&hashed_linked_chunk_id,), Self::map_row_to_chunk)? { let (id, previous, next, chunk_type) = data?; let new = txn.rebuild_chunk( &this, - &hashed_room_id, + &hashed_linked_chunk_id, previous, id, next, @@ -831,10 +847,10 @@ impl EventCacheStore for SqliteEventCacheStore { async fn load_last_chunk( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, ) -> Result<(Option>, ChunkIdentifierGenerator), Self::Error> { - let room_id = room_id.to_owned(); - let hashed_room_id = self.encode_key(keys::LINKED_CHUNKS, &room_id); + let hashed_linked_chunk_id = + self.encode_key(keys::LINKED_CHUNKS, linked_chunk_id.storage_key()); let this = self.clone(); @@ -845,10 +861,10 @@ impl EventCacheStore for SqliteEventCacheStore { // Find the latest chunk identifier to generate a `ChunkIdentifierGenerator`, and count the number of chunks. let (chunk_identifier_generator, number_of_chunks) = txn .prepare( - "SELECT MAX(id), COUNT(*) FROM linked_chunks WHERE room_id = ?" + "SELECT MAX(id), COUNT(*) FROM linked_chunks WHERE linked_chunk_id = ?" )? .query_row( - (&hashed_room_id,), + (&hashed_linked_chunk_id,), |row| { Ok(( // Read the `MAX(id)` as an `Option` instead @@ -873,10 +889,10 @@ impl EventCacheStore for SqliteEventCacheStore { // Find the last chunk. let Some((chunk_identifier, previous_chunk, chunk_type)) = txn .prepare( - "SELECT id, previous, type FROM linked_chunks WHERE room_id = ? AND next IS NULL" + "SELECT id, previous, type FROM linked_chunks WHERE linked_chunk_id = ? AND next IS NULL" )? .query_row( - (&hashed_room_id,), + (&hashed_linked_chunk_id,), |row| { Ok(( row.get::<_, u64>(0)?, @@ -909,7 +925,7 @@ impl EventCacheStore for SqliteEventCacheStore { // Build the chunk. let last_chunk = txn.rebuild_chunk( &this, - &hashed_room_id, + &hashed_linked_chunk_id, previous_chunk, chunk_identifier, None, @@ -923,11 +939,11 @@ impl EventCacheStore for SqliteEventCacheStore { async fn load_previous_chunk( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, before_chunk_identifier: ChunkIdentifier, ) -> Result>, Self::Error> { - let room_id = room_id.to_owned(); - let hashed_room_id = self.encode_key(keys::LINKED_CHUNKS, &room_id); + let hashed_linked_chunk_id = + self.encode_key(keys::LINKED_CHUNKS, linked_chunk_id.storage_key()); let this = self.clone(); @@ -938,10 +954,10 @@ impl EventCacheStore for SqliteEventCacheStore { // Find the chunk before the chunk identified by `before_chunk_identifier`. let Some((chunk_identifier, previous_chunk, next_chunk, chunk_type)) = txn .prepare( - "SELECT id, previous, next, type FROM linked_chunks WHERE room_id = ? AND next = ?" + "SELECT id, previous, next, type FROM linked_chunks WHERE linked_chunk_id = ? AND next = ?" )? .query_row( - (&hashed_room_id, before_chunk_identifier.index()), + (&hashed_linked_chunk_id, before_chunk_identifier.index()), |row| { Ok(( row.get::<_, u64>(0)?, @@ -960,7 +976,7 @@ impl EventCacheStore for SqliteEventCacheStore { // Build the chunk. let last_chunk = txn.rebuild_chunk( &this, - &hashed_room_id, + &hashed_linked_chunk_id, previous_chunk, chunk_identifier, next_chunk, @@ -972,7 +988,7 @@ impl EventCacheStore for SqliteEventCacheStore { .await } - async fn clear_all_rooms_chunks(&self) -> Result<(), Self::Error> { + async fn clear_all_linked_chunks(&self) -> Result<(), Self::Error> { self.acquire() .await? .with_transaction(move |txn| { @@ -987,7 +1003,7 @@ impl EventCacheStore for SqliteEventCacheStore { async fn filter_duplicated_events( &self, - room_id: &RoomId, + linked_chunk_id: LinkedChunkId<'_>, events: Vec, ) -> Result, Self::Error> { // If there's no events for which we want to check duplicates, we can return @@ -998,8 +1014,9 @@ impl EventCacheStore for SqliteEventCacheStore { } // Select all events that exist in the store, i.e. the duplicates. - let room_id = room_id.to_owned(); - let hashed_room_id = self.encode_key(keys::LINKED_CHUNKS, &room_id); + let hashed_linked_chunk_id = + self.encode_key(keys::LINKED_CHUNKS, linked_chunk_id.storage_key()); + let linked_chunk_id = linked_chunk_id.to_owned(); self.acquire() .await? @@ -1009,16 +1026,16 @@ impl EventCacheStore for SqliteEventCacheStore { r#" SELECT event_id, chunk_id, position FROM event_chunks - WHERE room_id = ? AND event_id IN ({}) + WHERE linked_chunk_id = ? AND event_id IN ({}) ORDER BY chunk_id ASC, position ASC "#, repeat_vars(events.len()), ); let parameters = params_from_iter( - // parameter for `room_id = ?` + // parameter for `linked_chunk_id = ?` once( - hashed_room_id + hashed_linked_chunk_id .to_sql() // SAFETY: it cannot fail since `Key::to_sql` never fails .unwrap(), @@ -1047,7 +1064,7 @@ impl EventCacheStore for SqliteEventCacheStore { let Ok(duplicated_event) = EventId::parse(duplicated_event.clone()) else { // Normally unreachable, but the event ID has been stored even if it is // malformed, let's skip it. - error!(%duplicated_event, %room_id, "Reading an malformed event ID"); + error!(%duplicated_event, %linked_chunk_id, "Reading an malformed event ID"); continue; }; @@ -1068,10 +1085,11 @@ impl EventCacheStore for SqliteEventCacheStore { room_id: &RoomId, event_id: &EventId, ) -> Result, Self::Error> { - let hashed_room_id = self.encode_key(keys::LINKED_CHUNKS, room_id); let event_id = event_id.to_owned(); let this = self.clone(); + let hashed_room_id = self.encode_key(keys::LINKED_CHUNKS, room_id); + self.acquire() .await? .with_transaction(move |txn| -> Result<_> { @@ -1098,6 +1116,7 @@ impl EventCacheStore for SqliteEventCacheStore { filters: Option<&[RelationType]>, ) -> Result, Self::Error> { let hashed_room_id = self.encode_key(keys::LINKED_CHUNKS, room_id); + let event_id = event_id.to_owned(); let filters = filters.map(ToOwned::to_owned); let this = self.clone(); @@ -1548,7 +1567,7 @@ async fn with_immediate_transaction< fn insert_chunk( txn: &Transaction<'_>, - room_id: &Key, + linked_chunk_id: &Key, previous: Option, new: u64, next: Option, @@ -1557,10 +1576,10 @@ fn insert_chunk( // First, insert the new chunk. txn.execute( r#" - INSERT INTO linked_chunks(id, room_id, previous, next, type) + INSERT INTO linked_chunks(id, linked_chunk_id, previous, next, type) VALUES (?, ?, ?, ?, ?) "#, - (new, room_id, previous, next, type_str), + (new, linked_chunk_id, previous, next, type_str), )?; // If this chunk has a previous one, update its `next` field. @@ -1569,9 +1588,9 @@ fn insert_chunk( r#" UPDATE linked_chunks SET next = ? - WHERE id = ? AND room_id = ? + WHERE id = ? AND linked_chunk_id = ? "#, - (new, previous, room_id), + (new, previous, linked_chunk_id), )?; } @@ -1581,9 +1600,9 @@ fn insert_chunk( r#" UPDATE linked_chunks SET previous = ? - WHERE id = ? AND room_id = ? + WHERE id = ? AND linked_chunk_id = ? "#, - (new, next, room_id), + (new, next, linked_chunk_id), )?; } @@ -1612,7 +1631,7 @@ mod tests { }, event_cache_store_integration_tests, event_cache_store_integration_tests_time, event_cache_store_media_integration_tests, - linked_chunk::{ChunkContent, ChunkIdentifier, Position, Update}, + linked_chunk::{ChunkContent, ChunkIdentifier, LinkedChunkId, Position, Update}, media::{MediaFormat, MediaRequestParameters, MediaThumbnailSettings}, }; use matrix_sdk_test::{async_test, DEFAULT_TEST_ROOM_ID}; @@ -1621,7 +1640,7 @@ mod tests { use tempfile::{tempdir, TempDir}; use super::SqliteEventCacheStore; - use crate::{utils::SqliteAsyncConnExt, SqliteStoreConfig}; + use crate::{event_cache_store::keys, utils::SqliteAsyncConnExt, SqliteStoreConfig}; static TMP_DIR: Lazy = Lazy::new(|| tempdir().unwrap()); static NUM: AtomicU32 = AtomicU32::new(0); @@ -1737,10 +1756,11 @@ mod tests { let store = get_event_cache_store().await.expect("creating cache store failed"); let room_id = &DEFAULT_TEST_ROOM_ID; + let linked_chunk_id = LinkedChunkId::Room(room_id); store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ Update::NewItemsChunk { previous: None, @@ -1763,7 +1783,7 @@ mod tests { .await .unwrap(); - let mut chunks = store.load_all_chunks(room_id).await.unwrap(); + let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap(); assert_eq!(chunks.len(), 3); @@ -1800,10 +1820,11 @@ mod tests { let store = get_event_cache_store().await.expect("creating cache store failed"); let room_id = &DEFAULT_TEST_ROOM_ID; + let linked_chunk_id = LinkedChunkId::Room(room_id); store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![Update::NewGapChunk { previous: None, new: ChunkIdentifier::new(42), @@ -1814,7 +1835,7 @@ mod tests { .await .unwrap(); - let mut chunks = store.load_all_chunks(room_id).await.unwrap(); + let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap(); assert_eq!(chunks.len(), 1); @@ -1833,11 +1854,12 @@ mod tests { let store = get_event_cache_store().await.expect("creating cache store failed"); let room_id = &DEFAULT_TEST_ROOM_ID; + let linked_chunk_id = LinkedChunkId::Room(room_id); let event_id = event_id!("$world"); store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ Update::NewItemsChunk { previous: None, @@ -1860,7 +1882,7 @@ mod tests { .await .unwrap(); - let mut chunks = store.load_all_chunks(room_id).await.unwrap(); + let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap(); assert_eq!(chunks.len(), 1); @@ -1880,10 +1902,11 @@ mod tests { let store = get_event_cache_store().await.expect("creating cache store failed"); let room_id = &DEFAULT_TEST_ROOM_ID; + let linked_chunk_id = LinkedChunkId::Room(room_id); store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ Update::NewGapChunk { previous: None, @@ -1909,7 +1932,7 @@ mod tests { .await .unwrap(); - let mut chunks = store.load_all_chunks(room_id).await.unwrap(); + let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap(); assert_eq!(chunks.len(), 2); @@ -1956,10 +1979,11 @@ mod tests { let store = get_event_cache_store().await.expect("creating cache store failed"); let room_id = &DEFAULT_TEST_ROOM_ID; + let linked_chunk_id = LinkedChunkId::Room(room_id); store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ Update::NewItemsChunk { previous: None, @@ -1982,7 +2006,7 @@ mod tests { .await .unwrap(); - let mut chunks = store.load_all_chunks(room_id).await.unwrap(); + let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap(); assert_eq!(chunks.len(), 1); @@ -2004,10 +2028,11 @@ mod tests { let store = get_event_cache_store().await.expect("creating cache store failed"); let room_id = *DEFAULT_TEST_ROOM_ID; + let linked_chunk_id = LinkedChunkId::Room(room_id); store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ Update::NewItemsChunk { previous: None, @@ -2033,7 +2058,7 @@ mod tests { .await .unwrap(); - let mut chunks = store.load_all_chunks(room_id).await.unwrap(); + let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap(); assert_eq!(chunks.len(), 1); @@ -2057,8 +2082,8 @@ mod tests { .unwrap() .with_transaction(move |txn| { txn.query_row( - "SELECT COUNT(*) FROM event_chunks WHERE chunk_id = 42 AND room_id = ? AND position IN (2, 3, 4)", - (room_id.as_bytes(),), + "SELECT COUNT(*) FROM event_chunks WHERE chunk_id = 42 AND linked_chunk_id = ? AND position IN (2, 3, 4)", + (store.encode_key(keys::LINKED_CHUNKS, linked_chunk_id.storage_key()),), |row| row.get(0), ) }) @@ -2072,10 +2097,11 @@ mod tests { let store = get_event_cache_store().await.expect("creating cache store failed"); let room_id = *DEFAULT_TEST_ROOM_ID; + let linked_chunk_id = LinkedChunkId::Room(room_id); store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ Update::NewItemsChunk { previous: None, @@ -2096,7 +2122,7 @@ mod tests { .await .unwrap(); - let mut chunks = store.load_all_chunks(room_id).await.unwrap(); + let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap(); assert_eq!(chunks.len(), 1); @@ -2115,13 +2141,14 @@ mod tests { let store = get_event_cache_store().await.expect("creating cache store failed"); let room_id = *DEFAULT_TEST_ROOM_ID; + let linked_chunk_id = LinkedChunkId::Room(room_id); // Same updates and checks as test_linked_chunk_push_items, but with extra // `StartReattachItems` and `EndReattachItems` updates, which must have no // effects. store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ Update::NewItemsChunk { previous: None, @@ -2143,7 +2170,7 @@ mod tests { .await .unwrap(); - let mut chunks = store.load_all_chunks(room_id).await.unwrap(); + let mut chunks = store.load_all_chunks(linked_chunk_id).await.unwrap(); assert_eq!(chunks.len(), 1); @@ -2164,13 +2191,14 @@ mod tests { let store = get_event_cache_store().await.expect("creating cache store failed"); let room_id = *DEFAULT_TEST_ROOM_ID; + let linked_chunk_id = LinkedChunkId::Room(room_id); let event_0 = make_test_event(room_id, "hello"); let event_1 = make_test_event(room_id, "world"); let event_2 = make_test_event(room_id, "howdy"); store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ Update::NewItemsChunk { previous: None, @@ -2193,7 +2221,7 @@ mod tests { .await .unwrap(); - let chunks = store.load_all_chunks(room_id).await.unwrap(); + let chunks = store.load_all_chunks(linked_chunk_id).await.unwrap(); assert!(chunks.is_empty()); // Check that cascading worked. Yes, SQLite, I doubt you. @@ -2220,7 +2248,7 @@ mod tests { // It's okay to re-insert a past event. store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ Update::NewItemsChunk { previous: None, @@ -2242,14 +2270,16 @@ mod tests { let store = get_event_cache_store().await.expect("creating cache store failed"); let room1 = room_id!("!realcheeselovers:raclette.fr"); + let linked_chunk_id1 = LinkedChunkId::Room(room1); let room2 = room_id!("!realcheeselovers:fondue.ch"); + let linked_chunk_id2 = LinkedChunkId::Room(room2); // Check that applying updates to one room doesn't affect the others. // Use the same chunk identifier in both rooms to battle-test search. store .handle_linked_chunk_updates( - room1, + linked_chunk_id1, vec![ Update::NewItemsChunk { previous: None, @@ -2270,7 +2300,7 @@ mod tests { store .handle_linked_chunk_updates( - room2, + linked_chunk_id2, vec![ Update::NewItemsChunk { previous: None, @@ -2287,7 +2317,7 @@ mod tests { .unwrap(); // Check chunks from room 1. - let mut chunks_room1 = store.load_all_chunks(room1).await.unwrap(); + let mut chunks_room1 = store.load_all_chunks(linked_chunk_id1).await.unwrap(); assert_eq!(chunks_room1.len(), 1); let c = chunks_room1.remove(0); @@ -2298,7 +2328,7 @@ mod tests { }); // Check chunks from room 2. - let mut chunks_room2 = store.load_all_chunks(room2).await.unwrap(); + let mut chunks_room2 = store.load_all_chunks(linked_chunk_id2).await.unwrap(); assert_eq!(chunks_room2.len(), 1); let c = chunks_room2.remove(0); @@ -2313,12 +2343,13 @@ mod tests { let store = get_event_cache_store().await.expect("creating cache store failed"); let room_id = *DEFAULT_TEST_ROOM_ID; + let linked_chunk_id = LinkedChunkId::Room(room_id); // Trigger a violation of the unique constraint on the (room id, chunk id) // couple. let err = store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ Update::NewItemsChunk { previous: None, @@ -2343,7 +2374,7 @@ mod tests { // If the updates have been handled transactionally, then no new chunks should // have been added; failure of the second update leads to the first one being // rolled back. - let chunks = store.load_all_chunks(room_id).await.unwrap(); + let chunks = store.load_all_chunks(linked_chunk_id).await.unwrap(); assert!(chunks.is_empty()); } @@ -2352,20 +2383,22 @@ mod tests { let store = get_event_cache_store().await.expect("creating cache store failed"); let room_id = *DEFAULT_TEST_ROOM_ID; - let duplicates = store.filter_duplicated_events(room_id, Vec::new()).await.unwrap(); + let linked_chunk_id = LinkedChunkId::Room(room_id); + let duplicates = store.filter_duplicated_events(linked_chunk_id, Vec::new()).await.unwrap(); assert!(duplicates.is_empty()); } #[async_test] async fn test_load_last_chunk() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = LinkedChunkId::Room(room_id); let event = |msg: &str| make_test_event(room_id, msg); let store = get_event_cache_store().await.expect("creating cache store failed"); // Case #1: no last chunk. { let (last_chunk, chunk_identifier_generator) = - store.load_last_chunk(room_id).await.unwrap(); + store.load_last_chunk(linked_chunk_id).await.unwrap(); assert!(last_chunk.is_none()); assert_eq!(chunk_identifier_generator.current(), 0); @@ -2375,7 +2408,7 @@ mod tests { { store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ Update::NewItemsChunk { previous: None, @@ -2392,7 +2425,7 @@ mod tests { .unwrap(); let (last_chunk, chunk_identifier_generator) = - store.load_last_chunk(room_id).await.unwrap(); + store.load_last_chunk(linked_chunk_id).await.unwrap(); assert_matches!(last_chunk, Some(last_chunk) => { assert_eq!(last_chunk.identifier, 42); @@ -2411,7 +2444,7 @@ mod tests { { store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ Update::NewItemsChunk { previous: Some(ChunkIdentifier::new(42)), @@ -2428,7 +2461,7 @@ mod tests { .unwrap(); let (last_chunk, chunk_identifier_generator) = - store.load_last_chunk(room_id).await.unwrap(); + store.load_last_chunk(linked_chunk_id).await.unwrap(); assert_matches!(last_chunk, Some(last_chunk) => { assert_eq!(last_chunk.identifier, 7); @@ -2450,11 +2483,12 @@ mod tests { #[async_test] async fn test_load_last_chunk_with_a_cycle() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = LinkedChunkId::Room(room_id); let store = get_event_cache_store().await.expect("creating cache store failed"); store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ Update::NewItemsChunk { previous: None, @@ -2474,20 +2508,23 @@ mod tests { .await .unwrap(); - store.load_last_chunk(room_id).await.unwrap_err(); + store.load_last_chunk(linked_chunk_id).await.unwrap_err(); } #[async_test] async fn test_load_previous_chunk() { let room_id = room_id!("!r0:matrix.org"); + let linked_chunk_id = LinkedChunkId::Room(room_id); let event = |msg: &str| make_test_event(room_id, msg); let store = get_event_cache_store().await.expect("creating cache store failed"); // Case #1: no chunk at all, equivalent to having an nonexistent // `before_chunk_identifier`. { - let previous_chunk = - store.load_previous_chunk(room_id, ChunkIdentifier::new(153)).await.unwrap(); + let previous_chunk = store + .load_previous_chunk(linked_chunk_id, ChunkIdentifier::new(153)) + .await + .unwrap(); assert!(previous_chunk.is_none()); } @@ -2497,7 +2534,7 @@ mod tests { { store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(42), @@ -2508,7 +2545,7 @@ mod tests { .unwrap(); let previous_chunk = - store.load_previous_chunk(room_id, ChunkIdentifier::new(42)).await.unwrap(); + store.load_previous_chunk(linked_chunk_id, ChunkIdentifier::new(42)).await.unwrap(); assert!(previous_chunk.is_none()); } @@ -2517,7 +2554,7 @@ mod tests { { store .handle_linked_chunk_updates( - room_id, + linked_chunk_id, vec![ // new chunk before the one that exists. Update::NewItemsChunk { @@ -2535,7 +2572,7 @@ mod tests { .unwrap(); let previous_chunk = - store.load_previous_chunk(room_id, ChunkIdentifier::new(42)).await.unwrap(); + store.load_previous_chunk(linked_chunk_id, ChunkIdentifier::new(42)).await.unwrap(); assert_matches!(previous_chunk, Some(previous_chunk) => { assert_eq!(previous_chunk.identifier, 7); diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/decryption.rs b/crates/matrix-sdk-ui/tests/integration/timeline/decryption.rs index f1b4de4a90b..feb92009e39 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/decryption.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/decryption.rs @@ -18,7 +18,7 @@ use assert_matches::assert_matches; use eyeball_im::VectorDiff; use matrix_sdk::{ assert_next_matches_with_timeout, - linked_chunk::{ChunkIdentifier, Position, Update}, + linked_chunk::{ChunkIdentifier, LinkedChunkId, Position, Update}, test_utils::mocks::MatrixMockServer, }; use matrix_sdk_test::{async_test, event_factory::EventFactory, BOB}; @@ -74,7 +74,7 @@ async fn test_an_utd_from_the_event_cache_as_an_initial_item_is_decrypted() { // be decrypted. Damn. We want to see if decryption will trigger automatically. event_cache_store .handle_linked_chunk_updates( - room_id, + LinkedChunkId::Room(room_id), vec![ // chunk #1 Update::NewItemsChunk { @@ -213,7 +213,7 @@ async fn test_an_utd_from_the_event_cache_as_a_paginated_item_is_decrypted() { // automatically. event_cache_store .handle_linked_chunk_updates( - room_id, + LinkedChunkId::Room(room_id), vec![ // chunk #1 Update::NewItemsChunk { diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs b/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs index 8ae26368777..284a5f16327 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs @@ -19,7 +19,7 @@ use assert_matches2::assert_let; use eyeball_im::VectorDiff; use futures_util::StreamExt; use matrix_sdk::{ - linked_chunk::{ChunkIdentifier, Position, Update}, + linked_chunk::{ChunkIdentifier, LinkedChunkId, Position, Update}, test_utils::mocks::MatrixMockServer, }; use matrix_sdk_test::{ @@ -724,7 +724,7 @@ async fn test_timeline_receives_a_limited_number_of_events_when_subscribing() { // The event cache contains 30 events. event_cache_store .handle_linked_chunk_updates( - room_id, + LinkedChunkId::Room(room_id), vec![ Update::NewItemsChunk { previous: None, diff --git a/crates/matrix-sdk/src/event_cache/deduplicator.rs b/crates/matrix-sdk/src/event_cache/deduplicator.rs index 7e5ddb24b07..3e346a884be 100644 --- a/crates/matrix-sdk/src/event_cache/deduplicator.rs +++ b/crates/matrix-sdk/src/event_cache/deduplicator.rs @@ -17,7 +17,10 @@ use std::collections::BTreeSet; -use matrix_sdk_base::{event_cache::store::EventCacheStoreLock, linked_chunk::Position}; +use matrix_sdk_base::{ + event_cache::store::EventCacheStoreLock, + linked_chunk::{LinkedChunkId, Position}, +}; use ruma::{OwnedEventId, OwnedRoomId}; use super::{ @@ -80,7 +83,7 @@ impl Deduplicator { // Let the store do its magic ✨ let duplicated_event_ids = store .filter_duplicated_events( - &self.room_id, + LinkedChunkId::Room(&self.room_id), events.iter().filter_map(|event| event.event_id()).collect(), ) .await?; @@ -187,7 +190,7 @@ mod tests { // Prefill the store with ev1 and ev2. event_cache_store .handle_linked_chunk_updates( - room_id, + LinkedChunkId::Room(room_id), vec![ Update::NewItemsChunk { previous: None, @@ -291,7 +294,7 @@ mod tests { // Prefill the store with ev1 and ev2. event_cache_store .handle_linked_chunk_updates( - room_id, + LinkedChunkId::Room(room_id), vec![ // Non empty items chunk. Update::NewItemsChunk { diff --git a/crates/matrix-sdk/src/event_cache/mod.rs b/crates/matrix-sdk/src/event_cache/mod.rs index 58d3fe6e995..8a6a21eb0c8 100644 --- a/crates/matrix-sdk/src/event_cache/mod.rs +++ b/crates/matrix-sdk/src/event_cache/mod.rs @@ -442,7 +442,7 @@ impl EventCacheInner { .await; // Clear the storage for all the rooms, using the storage facility. - self.store.lock().await?.clear_all_rooms_chunks().await?; + self.store.lock().await?.clear_all_linked_chunks().await?; // At this point, all the in-memory linked chunks are desynchronized from the // storage. Resynchronize them manually by calling reset(), and diff --git a/crates/matrix-sdk/src/event_cache/room/mod.rs b/crates/matrix-sdk/src/event_cache/room/mod.rs index 36d0fed4079..43b4b5a6b23 100644 --- a/crates/matrix-sdk/src/event_cache/room/mod.rs +++ b/crates/matrix-sdk/src/event_cache/room/mod.rs @@ -550,7 +550,9 @@ mod private { ThreadSummary, ThreadSummaryStatus, TimelineEvent, TimelineEventKind, }, event_cache::{store::EventCacheStoreLock, Event, Gap}, - linked_chunk::{lazy_loader, ChunkContent, ChunkIdentifierGenerator, Position, Update}, + linked_chunk::{ + lazy_loader, ChunkContent, ChunkIdentifierGenerator, LinkedChunkId, Position, Update, + }, serde_helpers::extract_thread_root, }; use matrix_sdk_common::executor::spawn; @@ -625,8 +627,9 @@ mod private { ) -> Result { let store_lock = store.lock().await?; + let linked_chunk_id = LinkedChunkId::Room(&room_id); let linked_chunk = match store_lock - .load_last_chunk(&room_id) + .load_last_chunk(linked_chunk_id) .await .map_err(EventCacheError::from) .and_then(|(last_chunk, chunk_identifier_generator)| { @@ -639,7 +642,9 @@ mod private { error!("error when reloading a linked chunk from memory: {err}"); // Clear storage for this room. - store_lock.handle_linked_chunk_updates(&room_id, vec![Update::Clear]).await?; + store_lock + .handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear]) + .await?; // Restart with an empty linked chunk. None @@ -746,28 +751,31 @@ mod private { let store = self.store.lock().await?; // The first chunk is not a gap, we can load its previous chunk. - let new_first_chunk = - match store.load_previous_chunk(&self.room, first_chunk_identifier).await { - Ok(Some(new_first_chunk)) => { - // All good, let's continue with this chunk. - new_first_chunk - } + let linked_chunk_id = LinkedChunkId::Room(&self.room); + let new_first_chunk = match store + .load_previous_chunk(linked_chunk_id, first_chunk_identifier) + .await + { + Ok(Some(new_first_chunk)) => { + // All good, let's continue with this chunk. + new_first_chunk + } - Ok(None) => { - // There's no previous chunk. The chunk is now fully-loaded. Conclude. - return Ok(self.conclude_load_more_for_fully_loaded_chunk()); - } + Ok(None) => { + // There's no previous chunk. The chunk is now fully-loaded. Conclude. + return Ok(self.conclude_load_more_for_fully_loaded_chunk()); + } - Err(err) => { - error!("error when loading the previous chunk of a linked chunk: {err}"); + Err(err) => { + error!("error when loading the previous chunk of a linked chunk: {err}"); - // Clear storage for this room. - store.handle_linked_chunk_updates(&self.room, vec![Update::Clear]).await?; + // Clear storage for this room. + store.handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear]).await?; - // Return the error. - return Err(err.into()); - } - }; + // Return the error. + return Err(err.into()); + } + }; let chunk_content = new_first_chunk.content.clone(); @@ -782,7 +790,7 @@ mod private { error!("error when inserting the previous chunk into its linked chunk: {err}"); // Clear storage for this room. - store.handle_linked_chunk_updates(&self.room, vec![Update::Clear]).await?; + store.handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear]).await?; // Return the error. return Err(err.into()); @@ -827,23 +835,24 @@ mod private { let store_lock = self.store.lock().await?; // Attempt to load the last chunk. - let (last_chunk, chunk_identifier_generator) = match store_lock - .load_last_chunk(&self.room) - .await - { - Ok(pair) => pair, + let linked_chunk_id = LinkedChunkId::Room(&self.room); + let (last_chunk, chunk_identifier_generator) = + match store_lock.load_last_chunk(linked_chunk_id).await { + Ok(pair) => pair, - Err(err) => { - // If loading the last chunk failed, clear the entire linked chunk. - error!("error when reloading a linked chunk from memory: {err}"); + Err(err) => { + // If loading the last chunk failed, clear the entire linked chunk. + error!("error when reloading a linked chunk from memory: {err}"); - // Clear storage for this room. - store_lock.handle_linked_chunk_updates(&self.room, vec![Update::Clear]).await?; + // Clear storage for this room. + store_lock + .handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear]) + .await?; - // Restart with an empty linked chunk. - (None, ChunkIdentifierGenerator::new_from_scratch()) - } - }; + // Restart with an empty linked chunk. + (None, ChunkIdentifierGenerator::new_from_scratch()) + } + }; debug!("unloading the linked chunk, and resetting it to its last chunk"); @@ -1026,7 +1035,8 @@ mod private { let store = store.lock().await?; trace!(?updates, "sending linked chunk updates to the store"); - store.handle_linked_chunk_updates(&room_id, updates).await?; + let linked_chunk_id = LinkedChunkId::Room(&room_id); + store.handle_linked_chunk_updates(linked_chunk_id, updates).await?; trace!("linked chunk updates applied"); super::Result::Ok(()) @@ -1678,7 +1688,8 @@ mod timed_tests { Gap, }, linked_chunk::{ - lazy_loader::from_all_chunks, ChunkContent, ChunkIdentifier, Position, Update, + lazy_loader::from_all_chunks, ChunkContent, ChunkIdentifier, LinkedChunkId, Position, + Update, }, store::StoreConfig, sync::{JoinedRoomUpdate, Timeline}, @@ -1734,10 +1745,11 @@ mod timed_tests { .await .unwrap(); - let linked_chunk = - from_all_chunks::<3, _, _>(event_cache_store.load_all_chunks(room_id).await.unwrap()) - .unwrap() - .unwrap(); + let linked_chunk = from_all_chunks::<3, _, _>( + event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(), + ) + .unwrap() + .unwrap(); assert_eq!(linked_chunk.chunks().count(), 2); @@ -1816,10 +1828,11 @@ mod timed_tests { } // The one in storage does not. - let linked_chunk = - from_all_chunks::<3, _, _>(event_cache_store.load_all_chunks(room_id).await.unwrap()) - .unwrap() - .unwrap(); + let linked_chunk = from_all_chunks::<3, _, _>( + event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(), + ) + .unwrap() + .unwrap(); assert_eq!(linked_chunk.chunks().count(), 1); @@ -1855,7 +1868,7 @@ mod timed_tests { // Prefill the store with some data. event_cache_store .handle_linked_chunk_updates( - room_id, + LinkedChunkId::Room(room_id), vec![ // An empty items chunk. Update::NewItemsChunk { @@ -1965,10 +1978,11 @@ mod timed_tests { assert!(items.is_empty()); // The event cache store too. - let linked_chunk = - from_all_chunks::<3, _, _>(event_cache_store.load_all_chunks(room_id).await.unwrap()) - .unwrap() - .unwrap(); + let linked_chunk = from_all_chunks::<3, _, _>( + event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(), + ) + .unwrap() + .unwrap(); // Note: while the event cache store could return `None` here, clearing it will // reset it to its initial form, maintaining the invariant that it @@ -1992,7 +2006,7 @@ mod timed_tests { // Prefill the store with some data. event_cache_store .handle_linked_chunk_updates( - room_id, + LinkedChunkId::Room(room_id), vec![ // An empty items chunk. Update::NewItemsChunk { @@ -2108,7 +2122,7 @@ mod timed_tests { // Prefill the store with invalid data: two chunks that form a cycle. event_cache_store .handle_linked_chunk_updates( - room_id, + LinkedChunkId::Room(room_id), vec![ Update::NewItemsChunk { previous: None, @@ -2154,7 +2168,8 @@ mod timed_tests { // Storage doesn't contain anything. It would also be valid that it contains a // single initial empty items chunk. - let raw_chunks = event_cache_store.load_all_chunks(room_id).await.unwrap(); + let raw_chunks = + event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(); assert!(raw_chunks.is_empty()); } @@ -2283,7 +2298,7 @@ mod timed_tests { let store = store.lock().await.unwrap(); store .handle_linked_chunk_updates( - room_id, + LinkedChunkId::Room(room_id), vec![ Update::NewItemsChunk { previous: None, @@ -2393,7 +2408,7 @@ mod timed_tests { let store = store.lock().await.unwrap(); store .handle_linked_chunk_updates( - room_id, + LinkedChunkId::Room(room_id), vec![ Update::NewItemsChunk { previous: None, diff --git a/crates/matrix-sdk/tests/integration/event_cache.rs b/crates/matrix-sdk/tests/integration/event_cache.rs index bf235f0996a..8d317008f5c 100644 --- a/crates/matrix-sdk/tests/integration/event_cache.rs +++ b/crates/matrix-sdk/tests/integration/event_cache.rs @@ -11,7 +11,7 @@ use matrix_sdk::{ event_cache::{ BackPaginationOutcome, EventCacheError, RoomEventCacheUpdate, RoomPaginationStatus, }, - linked_chunk::{ChunkIdentifier, Position, Update}, + linked_chunk::{ChunkIdentifier, LinkedChunkId, Position, Update}, store::StoreConfig, test_utils::{ assert_event_matches_msg, @@ -1468,7 +1468,7 @@ async fn test_apply_redaction_on_an_in_store_event() { // 2. a chunk of 1 item, the chunk that is going to be loaded. event_cache_store .handle_linked_chunk_updates( - room_id, + LinkedChunkId::Room(room_id), vec![ // chunk #1 Update::NewItemsChunk { @@ -1676,7 +1676,7 @@ async fn test_lazy_loading() { // 1. a chunk of 6 items event_cache_store .handle_linked_chunk_updates( - room_id, + LinkedChunkId::Room(room_id), vec![ // chunk #1 Update::NewItemsChunk { @@ -2040,7 +2040,7 @@ async fn test_deduplication() { // 1. a chunk of 3 items event_cache_store .handle_linked_chunk_updates( - room_id, + LinkedChunkId::Room(room_id), vec![ // chunk #0 Update::NewItemsChunk { @@ -2346,7 +2346,7 @@ async fn test_clear_all_rooms() { let cid = ChunkIdentifier::new(0); event_cache_store .handle_linked_chunk_updates( - sleeping_room_id, + LinkedChunkId::Room(sleeping_room_id), vec![ Update::NewItemsChunk { previous: None, new: cid, next: None }, Update::PushItems { at: Position::new(cid, 0), items: vec![ev0] }, @@ -2408,7 +2408,7 @@ async fn test_clear_all_rooms() { // The sleeping room should have been cleared too. let (maybe_last_chunk, _chunk_id_gen) = - event_cache_store.load_last_chunk(sleeping_room_id).await.unwrap(); + event_cache_store.load_last_chunk(LinkedChunkId::Room(sleeping_room_id)).await.unwrap(); assert!(maybe_last_chunk.is_none()); } diff --git a/crates/matrix-sdk/tests/integration/room/left.rs b/crates/matrix-sdk/tests/integration/room/left.rs index 0966b525776..a6e222a2a07 100644 --- a/crates/matrix-sdk/tests/integration/room/left.rs +++ b/crates/matrix-sdk/tests/integration/room/left.rs @@ -1,7 +1,7 @@ use std::time::Duration; use assert_matches2::assert_matches; -use matrix_sdk::config::SyncSettings; +use matrix_sdk::{config::SyncSettings, linked_chunk::LinkedChunkId}; use matrix_sdk_base::{RoomInfoNotableUpdateReasons, RoomState}; use matrix_sdk_test::{ async_test, test_json, GlobalAccountDataTestEvent, LeftRoomBuilder, SyncResponseBuilder, @@ -57,7 +57,10 @@ async fn test_forget_non_direct_room() { { // There is some data in the cache store. let event_cache_store = client.event_cache_store().lock().await.unwrap(); - let room_data = event_cache_store.load_all_chunks(&DEFAULT_TEST_ROOM_ID).await.unwrap(); + let room_data = event_cache_store + .load_all_chunks(LinkedChunkId::Room(&DEFAULT_TEST_ROOM_ID)) + .await + .unwrap(); assert!(!room_data.is_empty()); } @@ -71,7 +74,10 @@ async fn test_forget_non_direct_room() { { // Data in the event cache store has been removed. let event_cache_store = client.event_cache_store().lock().await.unwrap(); - let room_data = event_cache_store.load_all_chunks(&DEFAULT_TEST_ROOM_ID).await.unwrap(); + let room_data = event_cache_store + .load_all_chunks(LinkedChunkId::Room(&DEFAULT_TEST_ROOM_ID)) + .await + .unwrap(); assert!(room_data.is_empty()); } } @@ -113,7 +119,10 @@ async fn test_forget_banned_room() { { // There is some data in the cache store. let event_cache_store = client.event_cache_store().lock().await.unwrap(); - let room_data = event_cache_store.load_all_chunks(&DEFAULT_TEST_ROOM_ID).await.unwrap(); + let room_data = event_cache_store + .load_all_chunks(LinkedChunkId::Room(&DEFAULT_TEST_ROOM_ID)) + .await + .unwrap(); assert!(!room_data.is_empty()); } @@ -131,7 +140,10 @@ async fn test_forget_banned_room() { { // Data in the event cache store has been removed. let event_cache_store = client.event_cache_store().lock().await.unwrap(); - let room_data = event_cache_store.load_all_chunks(&DEFAULT_TEST_ROOM_ID).await.unwrap(); + let room_data = event_cache_store + .load_all_chunks(LinkedChunkId::Room(&DEFAULT_TEST_ROOM_ID)) + .await + .unwrap(); assert!(room_data.is_empty()); } }