matrix_sdk_base/event_cache/store/
traits.rs

1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{fmt, sync::Arc};
16
17use async_trait::async_trait;
18use matrix_sdk_common::{
19    AsyncTraitDeps,
20    linked_chunk::{
21        ChunkIdentifier, ChunkIdentifierGenerator, ChunkMetadata, LinkedChunkId, Position,
22        RawChunk, Update,
23    },
24};
25use ruma::{EventId, OwnedEventId, RoomId, events::relation::RelationType};
26
27use super::EventCacheStoreError;
28use crate::event_cache::{Event, Gap};
29
30/// A default capacity for linked chunks, when manipulating in conjunction with
31/// an `EventCacheStore` implementation.
32// TODO: move back?
33pub const DEFAULT_CHUNK_CAPACITY: usize = 128;
34
35/// An abstract trait that can be used to implement different store backends
36/// for the event cache of the SDK.
37#[cfg_attr(target_family = "wasm", async_trait(?Send))]
38#[cfg_attr(not(target_family = "wasm"), async_trait)]
39pub trait EventCacheStore: AsyncTraitDeps {
40    /// The error type used by this event cache store.
41    type Error: fmt::Debug + Into<EventCacheStoreError>;
42
43    /// Try to take a lock using the given store.
44    async fn try_take_leased_lock(
45        &self,
46        lease_duration_ms: u32,
47        key: &str,
48        holder: &str,
49    ) -> Result<bool, Self::Error>;
50
51    /// An [`Update`] reflects an operation that has happened inside a linked
52    /// chunk. The linked chunk is used by the event cache to store the events
53    /// in-memory. This method aims at forwarding this update inside this store.
54    async fn handle_linked_chunk_updates(
55        &self,
56        linked_chunk_id: LinkedChunkId<'_>,
57        updates: Vec<Update<Event, Gap>>,
58    ) -> Result<(), Self::Error>;
59
60    /// Remove all data tied to a given room from the cache.
61    async fn remove_room(&self, room_id: &RoomId) -> Result<(), Self::Error> {
62        // Right now, this means removing all the linked chunk. If implementations
63        // override this behavior, they should *also* include this code.
64        self.handle_linked_chunk_updates(LinkedChunkId::Room(room_id), vec![Update::Clear]).await
65    }
66
67    /// Return all the raw components of a linked chunk, so the caller may
68    /// reconstruct the linked chunk later.
69    #[doc(hidden)]
70    async fn load_all_chunks(
71        &self,
72        linked_chunk_id: LinkedChunkId<'_>,
73    ) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error>;
74
75    /// Load all of the chunks' metadata for the given [`LinkedChunkId`].
76    ///
77    /// Chunks are unordered, and there's no guarantee that the chunks would
78    /// form a valid linked chunk after reconstruction.
79    async fn load_all_chunks_metadata(
80        &self,
81        linked_chunk_id: LinkedChunkId<'_>,
82    ) -> Result<Vec<ChunkMetadata>, Self::Error>;
83
84    /// Load the last chunk of the `LinkedChunk` holding all events of the room
85    /// identified by `room_id`.
86    ///
87    /// This is used to iteratively load events for the `EventCache`.
88    async fn load_last_chunk(
89        &self,
90        linked_chunk_id: LinkedChunkId<'_>,
91    ) -> Result<(Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator), Self::Error>;
92
93    /// Load the chunk before the chunk identified by `before_chunk_identifier`
94    /// of the `LinkedChunk` holding all events of the room identified by
95    /// `room_id`
96    ///
97    /// This is used to iteratively load events for the `EventCache`.
98    async fn load_previous_chunk(
99        &self,
100        linked_chunk_id: LinkedChunkId<'_>,
101        before_chunk_identifier: ChunkIdentifier,
102    ) -> Result<Option<RawChunk<Event, Gap>>, Self::Error>;
103
104    /// Clear persisted events for all the rooms.
105    ///
106    /// This will empty and remove all the linked chunks stored previously,
107    /// using the above [`Self::handle_linked_chunk_updates`] methods. It
108    /// must *also* delete all the events' content, if they were stored in a
109    /// separate table.
110    ///
111    /// ⚠ This is meant only for super specific use cases, where there shouldn't
112    /// be any live in-memory linked chunks. In general, prefer using
113    /// `EventCache::clear_all_rooms()` from the common SDK crate.
114    async fn clear_all_linked_chunks(&self) -> Result<(), Self::Error>;
115
116    /// Given a set of event IDs, return the duplicated events along with their
117    /// position if there are any.
118    async fn filter_duplicated_events(
119        &self,
120        linked_chunk_id: LinkedChunkId<'_>,
121        events: Vec<OwnedEventId>,
122    ) -> Result<Vec<(OwnedEventId, Position)>, Self::Error>;
123
124    /// Find an event by its ID in a room.
125    ///
126    /// This method must return events saved either in any linked chunks, *or*
127    /// events saved "out-of-band" with the [`Self::save_event`] method.
128    async fn find_event(
129        &self,
130        room_id: &RoomId,
131        event_id: &EventId,
132    ) -> Result<Option<Event>, Self::Error>;
133
134    /// Find all the events (alongside their position in the room's linked
135    /// chunk, if available) that relate to a given event.
136    ///
137    /// The only events which don't have a position are those which have been
138    /// saved out-of-band using [`Self::save_event`].
139    ///
140    /// Note: it doesn't process relations recursively: for instance, if
141    /// requesting only thread events, it will NOT return the aggregated
142    /// events affecting the returned events. It is the responsibility of
143    /// the caller to do so, if needed.
144    ///
145    /// An additional filter can be provided to only retrieve related events for
146    /// a certain relationship.
147    ///
148    /// This method must return events saved either in any linked chunks, *or*
149    /// events saved "out-of-band" with the [`Self::save_event`] method.
150    async fn find_event_relations(
151        &self,
152        room_id: &RoomId,
153        event_id: &EventId,
154        filter: Option<&[RelationType]>,
155    ) -> Result<Vec<(Event, Option<Position>)>, Self::Error>;
156
157    /// Get all events in this room.
158    ///
159    /// This method must return events saved either in any linked chunks, *or*
160    /// events saved "out-of-band" with the [`Self::save_event`] method.
161    async fn get_room_events(&self, room_id: &RoomId) -> Result<Vec<Event>, Self::Error>;
162
163    /// Save an event, that might or might not be part of an existing linked
164    /// chunk.
165    ///
166    /// If the event has no event id, it will not be saved, and the function
167    /// must return an Ok result early.
168    ///
169    /// If the event was already stored with the same id, it must be replaced,
170    /// without causing an error.
171    async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error>;
172}
173
174#[repr(transparent)]
175struct EraseEventCacheStoreError<T>(T);
176
177#[cfg(not(tarpaulin_include))]
178impl<T: fmt::Debug> fmt::Debug for EraseEventCacheStoreError<T> {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        self.0.fmt(f)
181    }
182}
183
184#[cfg_attr(target_family = "wasm", async_trait(?Send))]
185#[cfg_attr(not(target_family = "wasm"), async_trait)]
186impl<T: EventCacheStore> EventCacheStore for EraseEventCacheStoreError<T> {
187    type Error = EventCacheStoreError;
188
189    async fn try_take_leased_lock(
190        &self,
191        lease_duration_ms: u32,
192        key: &str,
193        holder: &str,
194    ) -> Result<bool, Self::Error> {
195        self.0.try_take_leased_lock(lease_duration_ms, key, holder).await.map_err(Into::into)
196    }
197
198    async fn handle_linked_chunk_updates(
199        &self,
200        linked_chunk_id: LinkedChunkId<'_>,
201        updates: Vec<Update<Event, Gap>>,
202    ) -> Result<(), Self::Error> {
203        self.0.handle_linked_chunk_updates(linked_chunk_id, updates).await.map_err(Into::into)
204    }
205
206    async fn load_all_chunks(
207        &self,
208        linked_chunk_id: LinkedChunkId<'_>,
209    ) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error> {
210        self.0.load_all_chunks(linked_chunk_id).await.map_err(Into::into)
211    }
212
213    async fn load_all_chunks_metadata(
214        &self,
215        linked_chunk_id: LinkedChunkId<'_>,
216    ) -> Result<Vec<ChunkMetadata>, Self::Error> {
217        self.0.load_all_chunks_metadata(linked_chunk_id).await.map_err(Into::into)
218    }
219
220    async fn load_last_chunk(
221        &self,
222        linked_chunk_id: LinkedChunkId<'_>,
223    ) -> Result<(Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator), Self::Error> {
224        self.0.load_last_chunk(linked_chunk_id).await.map_err(Into::into)
225    }
226
227    async fn load_previous_chunk(
228        &self,
229        linked_chunk_id: LinkedChunkId<'_>,
230        before_chunk_identifier: ChunkIdentifier,
231    ) -> Result<Option<RawChunk<Event, Gap>>, Self::Error> {
232        self.0
233            .load_previous_chunk(linked_chunk_id, before_chunk_identifier)
234            .await
235            .map_err(Into::into)
236    }
237
238    async fn clear_all_linked_chunks(&self) -> Result<(), Self::Error> {
239        self.0.clear_all_linked_chunks().await.map_err(Into::into)
240    }
241
242    async fn filter_duplicated_events(
243        &self,
244        linked_chunk_id: LinkedChunkId<'_>,
245        events: Vec<OwnedEventId>,
246    ) -> Result<Vec<(OwnedEventId, Position)>, Self::Error> {
247        self.0.filter_duplicated_events(linked_chunk_id, events).await.map_err(Into::into)
248    }
249
250    async fn find_event(
251        &self,
252        room_id: &RoomId,
253        event_id: &EventId,
254    ) -> Result<Option<Event>, Self::Error> {
255        self.0.find_event(room_id, event_id).await.map_err(Into::into)
256    }
257
258    async fn find_event_relations(
259        &self,
260        room_id: &RoomId,
261        event_id: &EventId,
262        filter: Option<&[RelationType]>,
263    ) -> Result<Vec<(Event, Option<Position>)>, Self::Error> {
264        self.0.find_event_relations(room_id, event_id, filter).await.map_err(Into::into)
265    }
266
267    async fn get_room_events(&self, room_id: &RoomId) -> Result<Vec<Event>, Self::Error> {
268        self.0.get_room_events(room_id).await.map_err(Into::into)
269    }
270
271    async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error> {
272        self.0.save_event(room_id, event).await.map_err(Into::into)
273    }
274}
275
276/// A type-erased [`EventCacheStore`].
277pub type DynEventCacheStore = dyn EventCacheStore<Error = EventCacheStoreError>;
278
279/// A type that can be type-erased into `Arc<dyn EventCacheStore>`.
280///
281/// This trait is not meant to be implemented directly outside
282/// `matrix-sdk-base`, but it is automatically implemented for everything that
283/// implements `EventCacheStore`.
284pub trait IntoEventCacheStore {
285    #[doc(hidden)]
286    fn into_event_cache_store(self) -> Arc<DynEventCacheStore>;
287}
288
289impl IntoEventCacheStore for Arc<DynEventCacheStore> {
290    fn into_event_cache_store(self) -> Arc<DynEventCacheStore> {
291        self
292    }
293}
294
295impl<T> IntoEventCacheStore for T
296where
297    T: EventCacheStore + Sized + 'static,
298{
299    fn into_event_cache_store(self) -> Arc<DynEventCacheStore> {
300        Arc::new(EraseEventCacheStoreError(self))
301    }
302}
303
304// Turns a given `Arc<T>` into `Arc<DynEventCacheStore>` by attaching the
305// `EventCacheStore` impl vtable of `EraseEventCacheStoreError<T>`.
306impl<T> IntoEventCacheStore for Arc<T>
307where
308    T: EventCacheStore + 'static,
309{
310    fn into_event_cache_store(self) -> Arc<DynEventCacheStore> {
311        let ptr: *const T = Arc::into_raw(self);
312        let ptr_erased = ptr as *const EraseEventCacheStoreError<T>;
313        // SAFETY: EraseEventCacheStoreError is repr(transparent) so T and
314        //         EraseEventCacheStoreError<T> have the same layout and ABI
315        unsafe { Arc::from_raw(ptr_erased) }
316    }
317}