Skip to content

Commit 49db60d

Browse files
committed
feat: Allow events to be fetched by event type
1 parent 8f42673 commit 49db60d

File tree

7 files changed

+193
-16
lines changed

7 files changed

+193
-16
lines changed

crates/matrix-sdk-base/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ All notable changes to this project will be documented in this file.
1313

1414
### Features
1515

16+
- [**breaking**] The `EventCacheStore::get_room_events()` method has received
17+
two new arguments. This allows users to load only events of a certain event
18+
type and events that were encrypted using a certain room key identified by its
19+
session ID.
20+
([#5817](https://github.com/matrix-org/matrix-rust-sdk/pull/5817))
1621
- `ComposerDraft` can now store attachments alongside text messages.
1722
([#5794](https://github.com/matrix-org/matrix-rust-sdk/pull/5794))
1823

crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@
1414

1515
//! Trait and macro of integration tests for `EventCacheStore` implementations.
1616
17-
use std::{collections::BTreeMap, sync::Arc};
17+
use std::{
18+
collections::{BTreeMap, BTreeSet},
19+
sync::Arc,
20+
};
1821

1922
use assert_matches::assert_matches;
2023
use assert_matches2::assert_let;
2124
use matrix_sdk_common::{
2225
deserialized_responses::{
2326
AlgorithmInfo, DecryptedRoomEvent, EncryptionInfo, TimelineEvent, TimelineEventKind,
24-
VerificationState,
27+
UnableToDecryptInfo, UnableToDecryptReason, VerificationState,
2528
},
2629
linked_chunk::{
2730
ChunkContent, ChunkIdentifier as CId, LinkedChunkId, Position, Update, lazy_loader,
@@ -49,6 +52,24 @@ pub fn make_test_event(room_id: &RoomId, content: &str) -> TimelineEvent {
4952
make_test_event_with_event_id(room_id, content, None)
5053
}
5154

55+
/// Create a `m.room.encrypted` test event with all data filled, for testing
56+
/// that linked chunk correctly stores event data for encrypted events.
57+
pub fn make_encrypted_test_event(room_id: &RoomId, session_id: &str) -> TimelineEvent {
58+
let device_id = "DEVICEID";
59+
let builder = EventFactory::new()
60+
.encrypted("", "curve_key", device_id, session_id)
61+
.room(room_id)
62+
.sender(*ALICE);
63+
64+
let event = builder.into_raw();
65+
let utd_info = UnableToDecryptInfo {
66+
session_id: Some(session_id.to_owned()),
67+
reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
68+
};
69+
70+
TimelineEvent::from_utd(event, utd_info)
71+
}
72+
5273
/// Same as [`make_test_event`], with an extra event id.
5374
pub fn make_test_event_with_event_id(
5475
room_id: &RoomId,
@@ -144,6 +165,9 @@ pub trait EventCacheStoreIntegrationTests {
144165
/// Test that getting all events in a room works as expected.
145166
async fn test_get_room_events(&self);
146167

168+
/// Test that getting events in a room of a certain type works as expected.
169+
async fn test_get_room_events_filtered(&self);
170+
147171
/// Test that saving an event works as expected.
148172
async fn test_save_event(&self);
149173

@@ -962,7 +986,10 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore {
962986
.unwrap();
963987

964988
// Now let's find the events.
965-
let events = self.get_room_events(room_id).await.expect("failed to query for room events");
989+
let events = self
990+
.get_room_events(room_id, None, None)
991+
.await
992+
.expect("failed to query for room events");
966993

967994
assert_eq!(events.len(), 2);
968995

@@ -977,6 +1004,76 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore {
9771004
}
9781005
}
9791006

1007+
async fn test_get_room_events_filtered(&self) {
1008+
macro_rules! assert_expected_events {
1009+
($events:expr, [$($item:expr),* $(,)?]) => {{
1010+
let got_ids: BTreeSet<_> = $events.into_iter().map(|ev| ev.event_id().unwrap()).collect();
1011+
let expected_ids = BTreeSet::from([$($item.event_id().unwrap()),*]);
1012+
1013+
assert_eq!(got_ids, expected_ids);
1014+
}};
1015+
}
1016+
1017+
let room_id = room_id!("!r0:matrix.org");
1018+
let linked_chunk_id = LinkedChunkId::Room(room_id);
1019+
let another_room_id = room_id!("!r1:matrix.org");
1020+
let another_linked_chunk_id = LinkedChunkId::Room(another_room_id);
1021+
1022+
let event = |session_id: &str| make_encrypted_test_event(room_id, session_id);
1023+
1024+
let first_event = event("session_1");
1025+
let second_event = event("session_2");
1026+
let third_event = event("session_3");
1027+
let fourth_event = make_test_event(room_id, "It's a secret to everybody");
1028+
1029+
// Add one event in one room.
1030+
self.handle_linked_chunk_updates(
1031+
linked_chunk_id,
1032+
vec![
1033+
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1034+
Update::PushItems {
1035+
at: Position::new(CId::new(0), 0),
1036+
items: vec![first_event.clone(), second_event.clone(), fourth_event.clone()],
1037+
},
1038+
],
1039+
)
1040+
.await
1041+
.unwrap();
1042+
1043+
// Add an event in a different room.
1044+
self.handle_linked_chunk_updates(
1045+
another_linked_chunk_id,
1046+
vec![
1047+
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1048+
Update::PushItems {
1049+
at: Position::new(CId::new(0), 0),
1050+
items: vec![third_event.clone()],
1051+
},
1052+
],
1053+
)
1054+
.await
1055+
.unwrap();
1056+
1057+
// Now let's find all the encrypted events of the first room.
1058+
let events = self
1059+
.get_room_events(room_id, Some("m.room.encrypted"), None)
1060+
.await
1061+
.expect("failed to query for room events");
1062+
1063+
assert_eq!(events.len(), 2);
1064+
assert_expected_events!(events, [first_event, second_event]);
1065+
1066+
// Now let's find all the encrypted events which were encrypted using the first
1067+
// session ID.
1068+
let events = self
1069+
.get_room_events(room_id, Some("m.room.encrypted"), Some("session_1"))
1070+
.await
1071+
.expect("failed to query for room events");
1072+
1073+
assert_eq!(events.len(), 1);
1074+
assert_expected_events!(events, [first_event]);
1075+
}
1076+
9801077
async fn test_save_event(&self) {
9811078
let room_id = room_id!("!r0:matrix.org");
9821079
let another_room_id = room_id!("!r1:matrix.org");
@@ -1264,6 +1361,13 @@ macro_rules! event_cache_store_integration_tests {
12641361
event_cache_store.test_get_room_events().await;
12651362
}
12661363

1364+
#[async_test]
1365+
async fn test_get_room_events_filtered() {
1366+
let event_cache_store =
1367+
get_event_cache_store().await.unwrap().into_event_cache_store();
1368+
event_cache_store.test_get_room_events_filtered().await;
1369+
}
1370+
12671371
#[async_test]
12681372
async fn test_save_event() {
12691373
let event_cache_store =

crates/matrix-sdk-base/src/event_cache/store/memory_store.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,24 @@ impl EventCacheStore for MemoryStore {
217217
Ok(related_events)
218218
}
219219

220-
async fn get_room_events(&self, room_id: &RoomId) -> Result<Vec<Event>, Self::Error> {
220+
async fn get_room_events(
221+
&self,
222+
room_id: &RoomId,
223+
event_type: Option<&str>,
224+
session_id: Option<&str>,
225+
) -> Result<Vec<Event>, Self::Error> {
221226
let inner = self.inner.read().unwrap();
222227

223-
let event: Vec<_> =
224-
inner.events.items(room_id).map(|(event, _pos)| event.clone()).collect();
228+
let event: Vec<_> = inner
229+
.events
230+
.items(room_id)
231+
.map(|(event, _pos)| event.clone())
232+
.filter(|e| {
233+
event_type
234+
.is_none_or(|event_type| Some(event_type) == e.kind.event_type().as_deref())
235+
})
236+
.filter(|e| session_id.is_none_or(|s| Some(s) == e.kind.session_id()))
237+
.collect();
225238

226239
Ok(event)
227240
}

crates/matrix-sdk-base/src/event_cache/store/traits.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,12 @@ pub trait EventCacheStore: AsyncTraitDeps {
158158
///
159159
/// This method must return events saved either in any linked chunks, *or*
160160
/// 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>;
161+
async fn get_room_events(
162+
&self,
163+
room_id: &RoomId,
164+
event_type: Option<&str>,
165+
session_id: Option<&str>,
166+
) -> Result<Vec<Event>, Self::Error>;
162167

163168
/// Save an event, that might or might not be part of an existing linked
164169
/// chunk.
@@ -264,8 +269,13 @@ impl<T: EventCacheStore> EventCacheStore for EraseEventCacheStoreError<T> {
264269
self.0.find_event_relations(room_id, event_id, filter).await.map_err(Into::into)
265270
}
266271

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)
272+
async fn get_room_events(
273+
&self,
274+
room_id: &RoomId,
275+
event_type: Option<&str>,
276+
session_id: Option<&str>,
277+
) -> Result<Vec<Event>, Self::Error> {
278+
self.0.get_room_events(room_id, event_type, session_id).await.map_err(Into::into)
269279
}
270280

271281
async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error> {

crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,14 +479,29 @@ impl EventCacheStore for IndexeddbEventCacheStore {
479479
async fn get_room_events(
480480
&self,
481481
room_id: &RoomId,
482+
event_type: Option<&str>,
483+
session_id: Option<&str>,
482484
) -> Result<Vec<Event>, IndexeddbEventCacheStoreError> {
483485
let _timer = timer!("method");
484486

487+
// TODO: Make this more efficient so we don't load all events and filter them
488+
// here. We should instead only load the relevant events.
489+
485490
let transaction = self.transaction(&[keys::EVENTS], IdbTransactionMode::Readonly)?;
486491
transaction
487492
.get_room_events(room_id)
488493
.await
489-
.map(|vec| vec.into_iter().map(Into::into).collect())
494+
.map(|vec| {
495+
vec.into_iter()
496+
.map(Event::from)
497+
.filter(|e| {
498+
event_type.is_none_or(|event_type| {
499+
Some(event_type) == e.kind.event_type().as_deref()
500+
})
501+
})
502+
.filter(|e| session_id.is_none_or(|s| Some(s) == e.kind.session_id()))
503+
.collect()
504+
})
490505
.map_err(Into::into)
491506
}
492507

crates/matrix-sdk-sqlite/src/event_cache_store.rs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ use matrix_sdk_store_encryption::StoreCipher;
3434
use ruma::{
3535
events::relation::RelationType, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId,
3636
};
37-
use rusqlite::{params_from_iter, OptionalExtension, ToSql, Transaction, TransactionBehavior};
37+
use rusqlite::{
38+
params, params_from_iter, OptionalExtension, ToSql, Transaction, TransactionBehavior,
39+
};
3840
use tokio::{
3941
fs,
4042
sync::{Mutex, OwnedMutexGuard},
@@ -1329,19 +1331,47 @@ impl EventCacheStore for SqliteEventCacheStore {
13291331
}
13301332

13311333
#[instrument(skip(self))]
1332-
async fn get_room_events(&self, room_id: &RoomId) -> Result<Vec<Event>, Self::Error> {
1334+
async fn get_room_events(
1335+
&self,
1336+
room_id: &RoomId,
1337+
event_type: Option<&str>,
1338+
session_id: Option<&str>,
1339+
) -> Result<Vec<Event>, Self::Error> {
13331340
let _timer = timer!("method");
13341341

13351342
let this = self.clone();
13361343

13371344
let hashed_room_id = self.encode_key(keys::LINKED_CHUNKS, room_id);
1345+
let hashed_event_type = event_type.map(|e| self.encode_key(keys::EVENTS, e));
1346+
let hashed_session_id = session_id.map(|s| self.encode_key(keys::EVENTS, s));
13381347

13391348
self.read()
13401349
.await?
13411350
.with_transaction(move |txn| -> Result<_> {
1342-
let mut statement = txn.prepare("SELECT content FROM events WHERE room_id = ?")?;
1343-
let maybe_events =
1344-
statement.query_map((hashed_room_id,), |row| row.get::<_, Vec<u8>>(0))?;
1351+
// I'm not sure why clippy claims that the clones aren't required. The compiler
1352+
// tells us that the lifetimes aren't long enough if we remove them. Doesn't matter
1353+
// much so let's silence things.
1354+
#[allow(clippy::redundant_clone)]
1355+
let (query, keys) = match (hashed_event_type, hashed_session_id) {
1356+
(None, None) => {
1357+
("SELECT content FROM events WHERE room_id = ?", params![hashed_room_id])
1358+
}
1359+
(None, Some(session_id)) => (
1360+
"SELECT content FROM events WHERE room_id = ?1 AND session_id = ?2",
1361+
params![hashed_room_id, session_id.to_owned()],
1362+
),
1363+
(Some(event_type), None) => (
1364+
"SELECT content FROM events WHERE room_id = ? AND event_type = ?",
1365+
params![hashed_room_id, event_type.to_owned()]
1366+
),
1367+
(Some(event_type), Some(session_id)) => (
1368+
"SELECT content FROM events WHERE room_id = ?1 AND event_type = ?2 AND session_id = ?3",
1369+
params![hashed_room_id, event_type.to_owned(), session_id.to_owned()],
1370+
),
1371+
};
1372+
1373+
let mut statement = txn.prepare(query)?;
1374+
let maybe_events = statement.query_map(keys, |row| row.get::<_, Vec<u8>>(0))?;
13451375

13461376
let mut events = Vec::new();
13471377
for ev in maybe_events {

labs/multiverse/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ impl App {
394394

395395
let redaction_rules = room.clone_info().room_version_rules_or_default().redaction;
396396

397-
let maybe_timeline_events = store.get_room_events(room_id).await;
397+
let maybe_timeline_events = store.get_room_events(room_id, None, None).await;
398398
let Ok(timeline_events) = maybe_timeline_events else {
399399
warn!("Failed to get room's events: {maybe_timeline_events:?}");
400400
continue;

0 commit comments

Comments
 (0)