From 950c42742df656f4554772e66cd84d0635bcbcc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 22 Sep 2025 08:40:31 +0200 Subject: [PATCH] refactor(sqlite): Save the event type of an event in the SQLite event cache --- .../012_store_event_type.sql | 57 +++++++++++++++++++ .../src/event_cache_store.rs | 53 ++++++++++++++--- 2 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 crates/matrix-sdk-sqlite/migrations/event_cache_store/012_store_event_type.sql diff --git a/crates/matrix-sdk-sqlite/migrations/event_cache_store/012_store_event_type.sql b/crates/matrix-sdk-sqlite/migrations/event_cache_store/012_store_event_type.sql new file mode 100644 index 000000000..c0aeb40b5 --- /dev/null +++ b/crates/matrix-sdk-sqlite/migrations/event_cache_store/012_store_event_type.sql @@ -0,0 +1,57 @@ +-- For the event decryption to happen in the event cache we need the ability to +-- fetch only `m.room.encrypted` events out of the store. +-- +-- To accomplish that, we are emptying the event cache. New events inserted with +-- the event type and the session ID of the room key as separate columns. + +DELETE from linked_chunks; +DELETE from event_chunks; -- should be done by cascading +DELETE from gap_chunks; -- should be done by cascading +DELETE from events; + +DROP TABLE events; + +-- Events and their content. +CREATE TABLE "events" ( + -- The room in which the event is located. + "room_id" BLOB NOT NULL, + + -- The `OwnedEventId` of this event. + "event_id" BLOB NOT NULL, + + -- The event type of this event. + "event_type" BLOB NOT NULL, + + -- The ID of the session that was used to encrypt this event, may be null if + -- the event wasn't encrypted. + "session_id" BLOB NULL, + + -- JSON serialized `TimelineEvent` (encrypted value). + "content" BLOB NOT NULL, + + -- If this event is an aggregation (related event), the event id of the event it relates to. + -- Can be null if this event isn't an aggregation. + "relates_to" BLOB, + + -- If this event is an aggregation (related event), the kind of relation it has to the event it + -- relates to. + -- Can be null if this event isn't an aggregation. + "rel_type" BLOB, + + -- Primary key is the event ID. + PRIMARY KEY (event_id) +) +WITHOUT ROWID; + +-- Add an index to speed up queries that look for related events in a room. +CREATE INDEX "relates_to_idx" + ON "events" ("room_id", "relates_to"); + +-- Add an index to speed up queries that look for related events in a room, with an additional +-- filter. +CREATE INDEX "relates_to_rel_type_idx" + ON "events" ("room_id", "relates_to", "rel_type"); + +-- Add an index to speed up queries that look for related events in a room. +CREATE INDEX "event_type_index" + ON "events" ("room_id", "event_type", "session_id"); diff --git a/crates/matrix-sdk-sqlite/src/event_cache_store.rs b/crates/matrix-sdk-sqlite/src/event_cache_store.rs index f27112166..cc6e4cff9 100644 --- a/crates/matrix-sdk-sqlite/src/event_cache_store.rs +++ b/crates/matrix-sdk-sqlite/src/event_cache_store.rs @@ -53,6 +53,7 @@ use crate::{ mod keys { // Tables pub const LINKED_CHUNKS: &str = "linked_chunks"; + pub const EVENTS: &str = "events"; } /// The database name. @@ -63,7 +64,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 = 11; +const DATABASE_VERSION: u8 = 12; /// The string used to identify a chunk of type events, in the `type` field in /// the database. @@ -472,6 +473,16 @@ async fn run_migrations(conn: &SqliteAsyncConn, version: u8) -> Result<()> { .await?; } + if version < 12 { + conn.with_transaction(|txn| { + txn.execute_batch(include_str!( + "../migrations/event_cache_store/012_store_event_type.sql" + ))?; + txn.set_db_version(12) + }) + .await?; + } + Ok(()) } @@ -632,7 +643,7 @@ impl EventCacheStore for SqliteEventCacheStore { // deduplicated and moved to another position; or because it was inserted // outside the context of a linked chunk (e.g. pinned event). let mut content_statement = txn.prepare( - "INSERT OR REPLACE INTO events(room_id, event_id, content, relates_to, rel_type) VALUES (?, ?, ?, ?, ?)" + "INSERT OR REPLACE INTO events(room_id, event_id, event_type, session_id, content, relates_to, rel_type) VALUES (?, ?, ?, ?, ?, ?, ?)" )?; let invalid_event = |event: TimelineEvent| { @@ -641,20 +652,28 @@ impl EventCacheStore for SqliteEventCacheStore { return None; }; - Some((event_id.to_string(), event)) + let Some(event_type) = event.kind.event_type() else { + error!(%event_id, "Trying to save an event with no event type"); + return None; + }; + + Some((event_id.to_string(), event_type, 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() { + for (i, (event_id, event_type, 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_linked_chunk_id, &event_id, index))?; + let session_id = event.kind.session_id().map(|s| this.encode_key(keys::EVENTS, s)); + let event_type = this.encode_key(keys::EVENTS, event_type); + // Now, insert the event content into the database. let encoded_event = this.encode_event(&event)?; - content_statement.execute((&hashed_room_id, event_id, encoded_event.content, encoded_event.relates_to, encoded_event.rel_type))?; + content_statement.execute((&hashed_room_id, event_id, event_type, session_id, encoded_event.content, encoded_event.relates_to, encoded_event.rel_type))?; } } @@ -671,6 +690,14 @@ impl EventCacheStore for SqliteEventCacheStore { continue; }; + let Some(event_type) = event.kind.event_type() else { + error!(%event_id, "Trying to save an event with no event type"); + continue; + }; + + let session_id = event.kind.session_id().map(|s| this.encode_key(keys::EVENTS, s)); + let event_type = this.encode_key(keys::EVENTS, event_type); + // Replace the event's content. Really we'd like to update, but in case the // event id changed, we are a bit lenient here and will allow an insertion // of the new event. @@ -678,8 +705,8 @@ impl EventCacheStore for SqliteEventCacheStore { 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))?; + "INSERT OR REPLACE INTO events(room_id, event_id, event_type, session_id, content, relates_to, rel_type) VALUES (?, ?, ?, ?, ?, ?, ?)", + (&hashed_room_id, &event_id, event_type, session_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( @@ -1336,6 +1363,14 @@ impl EventCacheStore for SqliteEventCacheStore { return Ok(()); }; + let Some(event_type) = event.kind.event_type() else { + error!(%event_id, "Trying to save an event with no event type"); + return Ok(()); + }; + + let event_type = self.encode_key(keys::EVENTS, event_type); + let session_id = event.kind.session_id().map(|s| self.encode_key(keys::EVENTS, s)); + let hashed_room_id = self.encode_key(keys::LINKED_CHUNKS, room_id); let event_id = event_id.to_string(); let encoded_event = self.encode_event(&event)?; @@ -1344,8 +1379,8 @@ impl EventCacheStore for SqliteEventCacheStore { .await? .with_transaction(move |txn| -> Result<_> { 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))?; + "INSERT OR REPLACE INTO events(room_id, event_id, event_type, session_id, content, relates_to, rel_type) VALUES (?, ?, ?, ?, ?, ?, ?)", + (&hashed_room_id, &event_id, event_type, session_id, encoded_event.content, encoded_event.relates_to, encoded_event.rel_type))?; Ok(()) })