diff --git a/crates/matrix-sdk-sqlite/migrations/event_cache_store/003_events.sql b/crates/matrix-sdk-sqlite/migrations/event_cache_store/003_events.sql index a372a842a..1de6495cc 100644 --- a/crates/matrix-sdk-sqlite/migrations/event_cache_store/003_events.sql +++ b/crates/matrix-sdk-sqlite/migrations/event_cache_store/003_events.sql @@ -1,21 +1,21 @@ CREATE TABLE "linked_chunks" ( - -- Identifier of the chunk, unique per room. + -- Identifier of the chunk, unique per room. Corresponds to a `ChunkIdentifier`. "id" INTEGER, -- Which room does this chunk belong to? (hashed key shared with the two other tables) "room_id" BLOB NOT NULL, - -- Previous chunk in the linked list. + -- Previous chunk in the linked list. Corresponds to a `ChunkIdentifier`. "previous" INTEGER, - -- Next chunk in the linked list. + -- Next chunk in the linked list. Corresponds to a `ChunkIdentifier`. "next" INTEGER, - -- Type of underlying entries: E for event, G for gaps + -- Type of underlying entries: E for events, G for gaps "type" TEXT CHECK("type" IN ('E', 'G')) NOT NULL ); CREATE UNIQUE INDEX "linked_chunks_id_and_room_id" ON linked_chunks (id, room_id); CREATE TABLE "gaps" ( - -- Which chunk does this gap refer to? + -- Which chunk does this gap refer to? Corresponds to a `ChunkIdentifier`. "chunk_id" INTEGER NOT NULL, -- Which room does this event belong to? (hashed key shared with linked_chunks) "room_id" BLOB NOT NULL, @@ -29,7 +29,7 @@ CREATE TABLE "gaps" ( -- Items for an event chunk. CREATE TABLE "events" ( - -- Which chunk does this event refer to? + -- Which chunk does this event refer to? Corresponds to a `ChunkIdentifier`. "chunk_id" INTEGER NOT NULL, -- Which room does this event belong to? (hashed key shared with linked_chunks) "room_id" BLOB NOT NULL, diff --git a/crates/matrix-sdk-sqlite/src/event_cache_store.rs b/crates/matrix-sdk-sqlite/src/event_cache_store.rs index 75179889d..baea282e7 100644 --- a/crates/matrix-sdk-sqlite/src/event_cache_store.rs +++ b/crates/matrix-sdk-sqlite/src/event_cache_store.rs @@ -50,6 +50,13 @@ mod keys { /// the [`run_migrations`] function. const DATABASE_VERSION: u8 = 3; +/// The string used to identify a chunk of type events, in the `type` field in +/// the database. +const CHUNK_TYPE_EVENT_TYPE_STRING: &str = "E"; +/// The string used to identify a chunk of type gap, in the `type` field in the +/// database. +const CHUNK_TYPE_GAP_TYPE_STRING: &str = "G"; + /// A SQLite-based event cache store. #[derive(Clone)] pub struct SqliteEventCacheStore { @@ -149,6 +156,7 @@ impl SqliteEventCacheStore { .with_transaction(move |txn| -> Result<_> { let mut items = Vec::new(); + // 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", @@ -238,14 +246,14 @@ impl TransactionExtForLinkedChunks for Transaction<'_> { let id = ChunkIdentifier::new(id); match chunk_type { - "G" => { + CHUNK_TYPE_GAP_TYPE_STRING => { // It's a gap! There's at most one row for it in the database, so a // call to `query_row` is sufficient. let gap = self.load_gap_content(store, room_id, id)?; Ok(RawLinkedChunk { content: ChunkContent::Gap(gap), previous, id, next }) } - "E" => { + CHUNK_TYPE_EVENT_TYPE_STRING => { // It's events! let events = self.load_events_content(store, room_id, id)?; Ok(RawLinkedChunk { content: ChunkContent::Items(events), previous, id, next }) @@ -421,7 +429,14 @@ impl EventCacheStore for SqliteEventCacheStore { self.acquire() .await? .with_transaction(move |txn| { - insert_chunk(txn, &hashed_room_id, previous, new, next, "E") + insert_chunk( + txn, + &hashed_room_id, + previous, + new, + next, + CHUNK_TYPE_EVENT_TYPE_STRING, + ) }) .await?; } @@ -445,11 +460,16 @@ impl EventCacheStore for SqliteEventCacheStore { .await? .with_transaction(move |txn| -> rusqlite::Result<()> { // Insert the chunk as a gap. - insert_chunk(txn, &hashed_room_id, previous, new, next, "G")?; + insert_chunk( + txn, + &hashed_room_id, + previous, + new, + next, + CHUNK_TYPE_GAP_TYPE_STRING, + )?; // Insert the gap's value. - // XXX(bnjbvr): might as well inline in the linked_chunks table? better - // for flexibility to use another table though. txn.execute( r#" INSERT INTO gaps(chunk_id, room_id, prev_token) @@ -967,27 +987,64 @@ mod tests { next: None, gap: Gap { prev_token: "raclette".to_owned() }, }, - Update::RemoveChunk(ChunkIdentifier::new(42)), + Update::NewGapChunk { + previous: Some(ChunkIdentifier::new(42)), + new: ChunkIdentifier::new(43), + next: None, + gap: Gap { prev_token: "fondue".to_owned() }, + }, + Update::NewGapChunk { + previous: Some(ChunkIdentifier::new(43)), + new: ChunkIdentifier::new(44), + next: None, + gap: Gap { prev_token: "tartiflette".to_owned() }, + }, + Update::RemoveChunk(ChunkIdentifier::new(43)), ], ) .await .unwrap(); - let chunks = store.load_chunks(room_id).await.unwrap(); - assert!(chunks.is_empty()); + let mut chunks = store.load_chunks(room_id).await.unwrap(); + + assert_eq!(chunks.len(), 2); + + // Chunks are ordered from smaller to bigger IDs. + let c = chunks.remove(0); + assert_eq!(c.id, ChunkIdentifier::new(42)); + assert_eq!(c.previous, None); + assert_eq!(c.next, Some(ChunkIdentifier::new(44))); + assert_matches!(c.content, ChunkContent::Gap(gap) => { + assert_eq!(gap.prev_token, "raclette"); + }); + + let c = chunks.remove(0); + assert_eq!(c.id, ChunkIdentifier::new(44)); + assert_eq!(c.previous, Some(ChunkIdentifier::new(42))); + assert_eq!(c.next, None); + assert_matches!(c.content, ChunkContent::Gap(gap) => { + assert_eq!(gap.prev_token, "tartiflette"); + }); // Check that cascading worked. Yes, sqlite, I doubt you. - let num_gaps: u64 = store + let gaps = store .acquire() .await .unwrap() - .with_transaction(|txn| { - txn.query_row("SELECT COUNT(*) FROM gaps", (), |row| row.get(0)) + .with_transaction(|txn| -> rusqlite::Result<_> { + let mut gaps = Vec::new(); + for data in txn + .prepare("SELECT chunk_id FROM gaps ORDER BY chunk_id")? + .query_map((), |row| row.get::<_, u64>(0))? + { + gaps.push(data?); + } + Ok(gaps) }) .await .unwrap(); - assert_eq!(num_gaps, 0); + assert_eq!(gaps, vec![42, 44]); } fn make_test_event(content: &str) -> SyncTimelineEvent {