1use std::{
18 collections::HashMap,
19 fmt,
20 iter::once,
21 ops::{Deref, Not},
22 path::{Path, PathBuf},
23 sync::Arc,
24};
25
26use async_trait::async_trait;
27use deadpool::managed::PoolConfig;
28use matrix_sdk_base::{
29 cross_process_lock::CrossProcessLockGeneration,
30 deserialized_responses::TimelineEvent,
31 event_cache::{
32 Event, Gap,
33 store::{EventCacheStore, extract_event_relation},
34 },
35 linked_chunk::{
36 ChunkContent, ChunkIdentifier, ChunkIdentifierGenerator, ChunkMetadata, LinkedChunkId,
37 Position, RawChunk, Update,
38 },
39 timer,
40};
41use matrix_sdk_store_encryption::StoreCipher;
42use ruma::{
43 EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, events::relation::RelationType,
44};
45use rusqlite::{
46 OptionalExtension, ToSql, Transaction, TransactionBehavior, params, params_from_iter,
47};
48use tokio::{
49 fs,
50 sync::{Mutex, OwnedMutexGuard},
51};
52use tracing::{debug, error, instrument, trace};
53
54use crate::{
55 OpenStoreError, RuntimeConfig, Secret, SqliteStoreConfig,
56 connection::{self, Connection as SqliteAsyncConn, Pool as SqlitePool, SqliteConnections},
57 error::{Error, Result},
58 utils::{
59 EncryptableStore, Key, SqliteAsyncConnExt, SqliteKeyValueStoreAsyncConnExt,
60 SqliteKeyValueStoreConnExt, SqliteTransactionExt, repeat_vars,
61 },
62};
63
64mod keys {
65 pub const LINKED_CHUNKS: &str = "linked_chunks";
67 pub const EVENTS: &str = "events";
68}
69
70const DATABASE_NAME: &str = "matrix-sdk-event-cache.sqlite3";
72
73const CHUNK_TYPE_EVENT_TYPE_STRING: &str = "E";
76const CHUNK_TYPE_GAP_TYPE_STRING: &str = "G";
79
80struct Encryption {
85 cipher: Option<StoreCipher>,
86}
87
88impl Encryption {
89 fn encode_event(&self, event: &TimelineEvent) -> Result<EncodedEvent> {
90 let serialized = serde_json::to_vec(event)?;
91
92 let raw_event = event.raw();
94 let (relates_to, rel_type) = extract_event_relation(raw_event).unzip();
95
96 let content = self.encode_value(serialized)?;
98
99 Ok(EncodedEvent {
100 content,
101 rel_type,
102 relates_to: relates_to
103 .map(|relates_to| self.encode_event_id(keys::EVENTS, &relates_to)),
104 })
105 }
106
107 fn decode_event(&self, raw_encoded_event: &[u8]) -> Result<Event> {
108 Ok(serde_json::from_slice(&self.decode_value(raw_encoded_event)?)?)
109 }
110
111 fn encode_event_id(&self, table_name: &str, event_id: &EventId) -> Key {
114 self.encode_key(table_name, event_id)
115 }
116
117 fn encode_room_id(&self, table_name: &str, room_id: &RoomId) -> Key {
120 self.encode_key(table_name, room_id)
121 }
122
123 fn encode_linked_chunk(&self, table_name: &str, linked_chunk_id: &LinkedChunkId<'_>) -> Key {
125 self.encode_key(table_name, linked_chunk_id.storage_key())
126 }
127
128 fn encode_thread_id(&self, thread_id: &EventId) -> Result<Vec<u8>> {
130 self.encode_value(String::from(thread_id.as_str()))
131 }
132
133 #[cfg(test)]
135 fn decode_thread_id(&self, encoded_thread_id: &[u8]) -> Result<OwnedEventId> {
136 let as_slice = self.decode_value(encoded_thread_id)?;
137 let as_str = str::from_utf8(as_slice.as_ref())?;
138
139 Ok(EventId::parse(as_str)?)
140 }
141}
142
143impl EncryptableStore for Encryption {
144 fn get_cypher(&self) -> Option<&StoreCipher> {
145 self.cipher.as_ref()
146 }
147}
148
149#[derive(Clone)]
151pub struct SqliteEventCacheStore {
152 encryption: Arc<Encryption>,
154
155 connections: Arc<Mutex<Option<SqliteConnections>>>,
157
158 db_path: PathBuf,
160
161 pool_config: PoolConfig,
163
164 runtime_config: RuntimeConfig,
166}
167
168#[cfg(not(tarpaulin_include))]
169impl fmt::Debug for SqliteEventCacheStore {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 f.debug_struct("SqliteEventCacheStore").finish_non_exhaustive()
172 }
173}
174
175impl SqliteEventCacheStore {
176 pub async fn open(
179 path: impl AsRef<Path>,
180 passphrase: Option<&str>,
181 ) -> Result<Self, OpenStoreError> {
182 Self::open_with_config(&SqliteStoreConfig::new(path).passphrase(passphrase)).await
183 }
184
185 pub async fn open_with_key(
188 path: impl AsRef<Path>,
189 key: Option<&[u8; 32]>,
190 ) -> Result<Self, OpenStoreError> {
191 Self::open_with_config(&SqliteStoreConfig::new(path).key(key)).await
192 }
193
194 #[instrument(skip(config), fields(path = ?config.path))]
196 pub async fn open_with_config(config: &SqliteStoreConfig) -> Result<Self, OpenStoreError> {
197 debug!(?config);
198
199 let _timer = timer!("open_with_config");
200
201 fs::create_dir_all(&config.path).await.map_err(OpenStoreError::CreateDir)?;
202
203 let db_path = config.path.join(DATABASE_NAME);
204 let pool_config = config.pool_config();
205 let runtime_config = config.runtime_config();
206
207 let pool = config.build_pool_of_connections(DATABASE_NAME)?;
208
209 let this =
210 Self::open_with_pool(pool, db_path, pool_config, runtime_config, config.secret.clone())
211 .await?;
212
213 this.write().await?.apply_runtime_config(runtime_config).await?;
215
216 Ok(this)
217 }
218
219 async fn open_with_pool(
222 pool: SqlitePool,
223 db_path: PathBuf,
224 pool_config: PoolConfig,
225 runtime_config: RuntimeConfig,
226 secret: Option<Secret>,
227 ) -> Result<Self, OpenStoreError> {
228 let conn = pool.get().await?;
229
230 let version = conn.db_version().await?;
231
232 run_migrations(&conn, version).await?;
233
234 conn.wal_checkpoint().await;
235
236 let cipher = match secret {
237 Some(s) => Some(conn.get_or_create_store_cipher(s).await?),
238 None => None,
239 };
240
241 let connections = SqliteConnections {
242 pool,
243 write_connection: Arc::new(Mutex::new(conn)),
245 };
246
247 Ok(Self {
248 encryption: Arc::new(Encryption { cipher }),
249 connections: Arc::new(Mutex::new(Some(connections))),
250 db_path,
251 pool_config,
252 runtime_config,
253 })
254 }
255
256 #[instrument(skip_all)]
258 async fn read(&self) -> Result<SqliteAsyncConn> {
259 let pool = {
260 let guard = self.connections.lock().await;
261 let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
262 conns.pool.clone()
263 };
264
265 let connection = pool.get().await?;
266
267 connection.execute_batch("PRAGMA foreign_keys = ON;").await?;
272
273 Ok(connection)
274 }
275
276 #[instrument(skip_all)]
278 async fn write(&self) -> Result<OwnedMutexGuard<SqliteAsyncConn>> {
279 let write_connection = {
280 let guard = self.connections.lock().await;
281 let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
282 conns.write_connection.clone()
283 };
284
285 let connection = write_connection.lock_owned().await;
286
287 connection.execute_batch("PRAGMA foreign_keys = ON;").await?;
292
293 Ok(connection)
294 }
295
296 fn map_row_to_chunk(
297 row: &rusqlite::Row<'_>,
298 ) -> Result<(u64, Option<u64>, Option<u64>, String), rusqlite::Error> {
299 Ok((
300 row.get::<_, u64>(0)?,
301 row.get::<_, Option<u64>>(1)?,
302 row.get::<_, Option<u64>>(2)?,
303 row.get::<_, String>(3)?,
304 ))
305 }
306
307 pub async fn vacuum(&self) -> Result<()> {
308 let write_connection = {
309 let guard = self.connections.lock().await;
310 let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
311 conns.write_connection.clone()
312 };
313 write_connection.lock().await.vacuum().await
314 }
315
316 async fn get_db_size(&self) -> Result<Option<usize>> {
317 let pool = {
318 let guard = self.connections.lock().await;
319 let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
320 conns.pool.clone()
321 };
322 Ok(Some(pool.get().await?.get_db_size().await?))
323 }
324
325 pub async fn close(&self) -> Result<()> {
326 connection::close_connections(&self.connections, "Event cache store").await;
327 Ok(())
328 }
329
330 pub async fn reopen(&self) -> Result<()> {
331 connection::reopen_connections(
332 &self.connections,
333 self.db_path.clone(),
334 self.pool_config,
335 self.runtime_config,
336 )
337 .await?;
338 Ok(())
339 }
340
341 #[cfg(test)]
343 async fn pool_max_size(&self) -> Option<usize> {
344 let guard = self.connections.lock().await;
345 guard.as_ref().map(|conns| conns.pool.status().max_size)
346 }
347}
348
349struct EncodedEvent {
350 content: Vec<u8>,
351 rel_type: Option<String>,
352 relates_to: Option<Key>,
353}
354
355trait TransactionExtForLinkedChunks {
356 fn rebuild_chunk(
357 &self,
358 encryption: &Encryption,
359 linked_chunk_id: &Key,
360 previous: Option<u64>,
361 index: u64,
362 next: Option<u64>,
363 chunk_type: &str,
364 ) -> Result<RawChunk<Event, Gap>>;
365
366 fn load_gap_content(
367 &self,
368 encryption: &Encryption,
369 linked_chunk_id: &Key,
370 chunk_id: ChunkIdentifier,
371 ) -> Result<Gap>;
372
373 fn load_events_content(
374 &self,
375 encryption: &Encryption,
376 linked_chunk_id: &Key,
377 chunk_id: ChunkIdentifier,
378 ) -> Result<Vec<Event>>;
379}
380
381impl TransactionExtForLinkedChunks for Transaction<'_> {
382 fn rebuild_chunk(
383 &self,
384 encryption: &Encryption,
385 linked_chunk_id: &Key,
386 previous: Option<u64>,
387 id: u64,
388 next: Option<u64>,
389 chunk_type: &str,
390 ) -> Result<RawChunk<Event, Gap>> {
391 let previous = previous.map(ChunkIdentifier::new);
392 let next = next.map(ChunkIdentifier::new);
393 let id = ChunkIdentifier::new(id);
394
395 match chunk_type {
396 CHUNK_TYPE_GAP_TYPE_STRING => {
397 let gap = self.load_gap_content(encryption, linked_chunk_id, id)?;
399 Ok(RawChunk { content: ChunkContent::Gap(gap), previous, identifier: id, next })
400 }
401
402 CHUNK_TYPE_EVENT_TYPE_STRING => {
403 let events = self.load_events_content(encryption, linked_chunk_id, id)?;
405 Ok(RawChunk {
406 content: ChunkContent::Items(events),
407 previous,
408 identifier: id,
409 next,
410 })
411 }
412
413 other => {
414 Err(Error::InvalidData {
416 details: format!("a linked chunk has an unknown type {other}"),
417 })
418 }
419 }
420 }
421
422 fn load_gap_content(
423 &self,
424 encryption: &Encryption,
425 linked_chunk_id: &Key,
426 chunk_id: ChunkIdentifier,
427 ) -> Result<Gap> {
428 let encoded_prev_token: Vec<u8> = self.query_row(
431 "SELECT prev_token FROM gap_chunks WHERE chunk_id = ? AND linked_chunk_id = ?",
432 (chunk_id.index(), &linked_chunk_id),
433 |row| row.get(0),
434 )?;
435 let prev_token_bytes = encryption.decode_value(&encoded_prev_token)?;
436 let prev_token = String::from_utf8(prev_token_bytes.into_owned())?;
437 Ok(Gap { token: prev_token })
438 }
439
440 fn load_events_content(
441 &self,
442 encryption: &Encryption,
443 linked_chunk_id: &Key,
444 chunk_id: ChunkIdentifier,
445 ) -> Result<Vec<Event>> {
446 let mut events = Vec::new();
448
449 for event_data in self
450 .prepare(
451 "SELECT events.content \
452 FROM event_chunks ec, events \
453 WHERE events.event_id = ec.event_id AND ec.chunk_id = ? AND ec.linked_chunk_id = ? \
454 ORDER BY ec.position ASC",
455 )?
456 .query_map((chunk_id.index(), &linked_chunk_id), |row| row.get::<_, Vec<u8>>(0))?
457 {
458 events.push(encryption.decode_event(&event_data?)?);
459 }
460
461 Ok(events)
462 }
463}
464
465async fn run_migrations(conn: &SqliteAsyncConn, version: u8) -> Result<()> {
467 conn.execute_batch("PRAGMA foreign_keys = ON;").await?;
469
470 if version < 1 {
471 debug!("Creating database");
472 conn.execute_batch("PRAGMA journal_mode = wal;").await?;
475 conn.with_transaction(|txn| {
476 txn.execute_batch(include_str!("../migrations/event_cache_store/001_init.sql"))?;
477 txn.set_db_version(1)
478 })
479 .await?;
480 }
481
482 if version < 2 {
483 debug!("Upgrading database to version 2");
484 conn.with_transaction(|txn| {
485 txn.execute_batch(include_str!("../migrations/event_cache_store/002_lease_locks.sql"))?;
486 txn.set_db_version(2)
487 })
488 .await?;
489 }
490
491 if version < 3 {
492 debug!("Upgrading database to version 3");
493 conn.with_transaction(|txn| {
494 txn.execute_batch(include_str!("../migrations/event_cache_store/003_events.sql"))?;
495 txn.set_db_version(3)
496 })
497 .await?;
498 }
499
500 if version < 4 {
501 debug!("Upgrading database to version 4");
502 conn.with_transaction(|txn| {
503 txn.execute_batch(include_str!(
504 "../migrations/event_cache_store/004_ignore_policy.sql"
505 ))?;
506 txn.set_db_version(4)
507 })
508 .await?;
509 }
510
511 if version < 5 {
512 debug!("Upgrading database to version 5");
513 conn.with_transaction(|txn| {
514 txn.execute_batch(include_str!(
515 "../migrations/event_cache_store/005_events_index_on_event_id.sql"
516 ))?;
517 txn.set_db_version(5)
518 })
519 .await?;
520 }
521
522 if version < 6 {
523 debug!("Upgrading database to version 6");
524 conn.with_transaction(|txn| {
525 txn.execute_batch(include_str!("../migrations/event_cache_store/006_events.sql"))?;
526 txn.set_db_version(6)
527 })
528 .await?;
529 }
530
531 if version < 7 {
532 debug!("Upgrading database to version 7");
533 conn.with_transaction(|txn| {
534 txn.execute_batch(include_str!(
535 "../migrations/event_cache_store/007_event_chunks.sql"
536 ))?;
537 txn.set_db_version(7)
538 })
539 .await?;
540 }
541
542 if version < 8 {
543 debug!("Upgrading database to version 8");
544 conn.with_transaction(|txn| {
545 txn.execute_batch(include_str!(
546 "../migrations/event_cache_store/008_linked_chunk_id.sql"
547 ))?;
548 txn.set_db_version(8)
549 })
550 .await?;
551 }
552
553 if version < 9 {
554 debug!("Upgrading database to version 9");
555 conn.with_transaction(|txn| {
556 txn.execute_batch(include_str!(
557 "../migrations/event_cache_store/009_related_event_index.sql"
558 ))?;
559 txn.set_db_version(9)
560 })
561 .await?;
562 }
563
564 if version < 10 {
565 debug!("Upgrading database to version 10");
566 conn.with_transaction(|txn| {
567 txn.execute_batch(include_str!("../migrations/event_cache_store/010_drop_media.sql"))?;
568 txn.set_db_version(10)
569 })
570 .await?;
571
572 if version >= 1 {
573 conn.vacuum().await?;
576 }
577 }
578
579 if version < 11 {
580 debug!("Upgrading database to version 11");
581 conn.with_transaction(|txn| {
582 txn.execute_batch(include_str!(
583 "../migrations/event_cache_store/011_empty_event_cache.sql"
584 ))?;
585 txn.set_db_version(11)
586 })
587 .await?;
588 }
589
590 if version < 12 {
591 debug!("Upgrading database to version 12");
592 conn.with_transaction(|txn| {
593 txn.execute_batch(include_str!(
594 "../migrations/event_cache_store/012_store_event_type.sql"
595 ))?;
596 txn.set_db_version(12)
597 })
598 .await?;
599 }
600
601 if version < 13 {
602 debug!("Upgrading database to version 13");
603 conn.with_transaction(|txn| {
604 txn.execute_batch(include_str!(
605 "../migrations/event_cache_store/013_lease_locks_with_generation.sql"
606 ))?;
607 txn.set_db_version(13)
608 })
609 .await?;
610 }
611
612 if version < 14 {
613 debug!("Upgrading database to version 14");
614 conn.with_transaction(|txn| {
615 txn.execute_batch(include_str!(
616 "../migrations/event_cache_store/014_event_chunks_event_id_index.sql"
617 ))?;
618 txn.set_db_version(14)
619 })
620 .await?;
621 }
622
623 if version < 15 {
624 debug!("Upgrading database to version 15");
625 conn.with_transaction(|txn| {
626 txn.execute_batch(include_str!(
627 "../migrations/event_cache_store/015_event_ids_are_encoded.sql"
628 ))?;
629 txn.set_db_version(15)
630 })
631 .await?;
632 }
633
634 if version < 16 {
635 debug!("Upgrading database to version 16");
636 conn.with_transaction(|txn| {
637 txn.execute_batch(include_str!("../migrations/event_cache_store/016_threads.sql"))?;
638 txn.set_db_version(16)
639 })
640 .await?;
641 }
642
643 Ok(())
644}
645
646#[async_trait]
647impl EventCacheStore for SqliteEventCacheStore {
648 type Error = Error;
649
650 #[instrument(skip(self))]
651 async fn try_take_leased_lock(
652 &self,
653 lease_duration_ms: u32,
654 key: &str,
655 holder: &str,
656 ) -> Result<Option<CrossProcessLockGeneration>> {
657 let key = key.to_owned();
658 let holder = holder.to_owned();
659
660 let now: u64 = MilliSecondsSinceUnixEpoch::now().get().into();
661 let expiration = now + lease_duration_ms as u64;
662
663 let generation = self
665 .write()
666 .await?
667 .with_transaction(move |txn| {
668 txn.query_row(
669 "INSERT INTO lease_locks (key, holder, expiration) \
670 VALUES (?1, ?2, ?3) \
671 ON CONFLICT (key) \
672 DO \
673 UPDATE SET \
674 holder = excluded.holder, \
675 expiration = excluded.expiration, \
676 generation = \
677 CASE holder \
678 WHEN excluded.holder THEN generation \
679 ELSE generation + 1 \
680 END \
681 WHERE \
682 holder = excluded.holder \
683 OR expiration < ?4 \
684 RETURNING generation",
685 (key, holder, expiration, now),
686 |row| row.get(0),
687 )
688 .optional()
689 })
690 .await?;
691
692 Ok(generation)
693 }
694
695 #[instrument(skip(self, updates))]
696 async fn handle_linked_chunk_updates(
697 &self,
698 linked_chunk_id: LinkedChunkId<'_>,
699 updates: Vec<Update<Event, Gap>>,
700 ) -> Result<(), Self::Error> {
701 let _timer = timer!("method");
702
703 let hashed_linked_chunk_id =
704 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
705 let hashed_room_id =
706 self.encryption.encode_room_id(keys::EVENTS, linked_chunk_id.room_id());
707 let encryption = self.encryption.clone();
708
709 with_immediate_transaction(self, move |txn| {
712 for update in updates {
713 match update {
714 Update::NewItemsChunk { previous, new, next } => {
715 let previous = previous.as_ref().map(ChunkIdentifier::index);
716 let new = new.index();
717 let next = next.as_ref().map(ChunkIdentifier::index);
718
719 trace!("new events chunk (prev={previous:?}, i={new}, next={next:?})");
720
721 insert_chunk(
722 txn,
723 &hashed_linked_chunk_id,
724 previous,
725 new,
726 next,
727 CHUNK_TYPE_EVENT_TYPE_STRING,
728 )?;
729 }
730
731 Update::NewGapChunk { previous, new, next, gap } => {
732 let hashed_prev_token = encryption.encode_value(gap.token)?;
733
734 let previous = previous.as_ref().map(ChunkIdentifier::index);
735 let new = new.index();
736 let next = next.as_ref().map(ChunkIdentifier::index);
737
738 trace!("new gap chunk (prev={previous:?}, i={new}, next={next:?})");
739
740 insert_chunk(
742 txn,
743 &hashed_linked_chunk_id,
744 previous,
745 new,
746 next,
747 CHUNK_TYPE_GAP_TYPE_STRING,
748 )?;
749
750 txn.execute(
752 r#"
753 INSERT INTO gap_chunks(chunk_id, linked_chunk_id, prev_token)
754 VALUES (?, ?, ?)
755 "#,
756 (new, &hashed_linked_chunk_id, hashed_prev_token),
757 )?;
758 }
759
760 Update::RemoveChunk(chunk_identifier) => {
761 let chunk_id = chunk_identifier.index();
762
763 trace!("removing chunk @ {chunk_id}");
764
765 let (previous, next): (Option<usize>, Option<usize>) = txn.query_row(
767 "SELECT previous, next FROM linked_chunks WHERE id = ? AND linked_chunk_id = ?",
768 (chunk_id, &hashed_linked_chunk_id),
769 |row| Ok((row.get(0)?, row.get(1)?))
770 )?;
771
772 if let Some(previous) = previous {
774 txn.execute("UPDATE linked_chunks SET next = ? WHERE id = ? AND linked_chunk_id = ?", (next, previous, &hashed_linked_chunk_id))?;
775 }
776
777 if let Some(next) = next {
779 txn.execute("UPDATE linked_chunks SET previous = ? WHERE id = ? AND linked_chunk_id = ?", (previous, next, &hashed_linked_chunk_id))?;
780 }
781
782 txn.execute("DELETE FROM linked_chunks WHERE id = ? AND linked_chunk_id = ?", (chunk_id, &hashed_linked_chunk_id))?;
785 }
786
787 Update::PushItems { at, items } => {
788 if items.is_empty() {
789 continue;
791 }
792
793 let chunk_id = at.chunk_identifier().index();
794
795 trace!("pushing {} items @ {chunk_id}", items.len());
796
797 let mut chunk_statement = txn.prepare(
798 "INSERT INTO event_chunks(chunk_id, linked_chunk_id, event_id, position) VALUES (?, ?, ?, ?)"
799 )?;
800
801 let mut content_statement = txn.prepare(
806 "INSERT OR REPLACE INTO events(room_id, event_id, event_type, session_id, content, relates_to, rel_type) VALUES (?, ?, ?, ?, ?, ?, ?)"
807 )?;
808
809 let invalid_event = |event: TimelineEvent| {
810 let Some(event_id) = event.event_id() else {
811 error!("Trying to push an event with no ID");
812 return None;
813 };
814
815 let Some(event_type) = event.kind.event_type() else {
816 error!(%event_id, "Trying to save an event with no event type");
817 return None;
818 };
819
820 Some((event_id.to_owned(), event_type, event))
821 };
822
823 for (i, (event_id, event_type, event)) in items.into_iter().filter_map(invalid_event).enumerate() {
824 let hashed_event_id = encryption.encode_event_id(
825 keys::EVENTS,
830 &event_id,
831 );
832
833 {
835 let index = at.index() + i;
836
837 chunk_statement.execute((chunk_id, &hashed_linked_chunk_id, &hashed_event_id, index))?;
838 }
839
840 {
842 let hashed_session_id = event.kind.session_id().map(|s| encryption.encode_key(keys::EVENTS, s));
843 let hashed_event_type = encryption.encode_key(keys::EVENTS, event_type);
844 let encoded_event = encryption.encode_event(&event)?;
845
846 content_statement.execute((
847 &hashed_room_id,
848 &hashed_event_id,
849 hashed_event_type,
850 hashed_session_id,
851 encoded_event.content,
852 encoded_event.relates_to,
853 encoded_event.rel_type
854 ))?;
855 }
856 }
857 }
858
859 Update::ReplaceItem { at, item: event } => {
860 let chunk_id = at.chunk_identifier().index();
861 let index = at.index();
862
863 trace!("replacing item @ {chunk_id}:{index}");
864
865 let Some(event_id) = event.event_id().map(|event_id| event_id.to_owned()) else {
867 error!("Trying to replace an event with a new one that has no ID");
868 continue;
869 };
870
871 let Some(event_type) = event.kind.event_type() else {
872 error!(%event_id, "Trying to save an event with no event type");
873 continue;
874 };
875
876 let hashed_event_id = encryption.encode_event_id(keys::EVENTS, &event_id);
877
878 {
885 let hashed_session_id = event.kind.session_id().map(|s| encryption.encode_key(keys::EVENTS, s));
886 let hashed_event_type = encryption.encode_key(keys::EVENTS, event_type);
887 let encoded_event = encryption.encode_event(&event)?;
888
889 txn.execute(
890 "INSERT OR REPLACE INTO events(room_id, event_id, event_type, session_id, content, relates_to, rel_type) VALUES (?, ?, ?, ?, ?, ?, ?)",
891 (
892 &hashed_room_id,
893 &hashed_event_id,
894 hashed_event_type,
895 hashed_session_id,
896 encoded_event.content,
897 encoded_event.relates_to,
898 encoded_event.rel_type
899 ),
900 )?;
901 }
902
903 {
905 txn.execute(
907 r#"UPDATE event_chunks SET event_id = ? WHERE linked_chunk_id = ? AND chunk_id = ? AND position = ?"#,
908 (&hashed_event_id, &hashed_linked_chunk_id, chunk_id, index)
909 )?;
910 }
911 }
912
913 Update::RemoveItem { at } => {
914 let chunk_id = at.chunk_identifier().index();
915 let index = at.index();
916
917 trace!("removing item @ {chunk_id}:{index}");
918
919 txn.execute("DELETE FROM event_chunks WHERE linked_chunk_id = ? AND chunk_id = ? AND position = ?", (&hashed_linked_chunk_id, chunk_id, index))?;
921
922 txn.execute(
1021 r#"
1022 UPDATE event_chunks
1023 SET position = -(position - 1)
1024 WHERE linked_chunk_id = ? AND chunk_id = ? AND position > ?
1025 "#,
1026 (&hashed_linked_chunk_id, chunk_id, index)
1027 )?;
1028 txn.execute(
1029 r#"
1030 UPDATE event_chunks
1031 SET position = -position
1032 WHERE position < 0 AND linked_chunk_id = ? AND chunk_id = ?
1033 "#,
1034 (&hashed_linked_chunk_id, chunk_id)
1035 )?;
1036
1037 }
1040
1041 Update::DetachLastItems { at } => {
1042 let chunk_id = at.chunk_identifier().index();
1043 let index = at.index();
1044
1045 trace!("truncating items >= {chunk_id}:{index}");
1046
1047 txn.execute("DELETE FROM event_chunks WHERE linked_chunk_id = ? AND chunk_id = ? AND position >= ?", (&hashed_linked_chunk_id, chunk_id, index))?;
1049
1050 }
1053
1054 Update::Clear => {
1055 trace!("clearing items");
1056
1057 txn.execute(
1059 "DELETE FROM linked_chunks WHERE linked_chunk_id = ?",
1060 (&hashed_linked_chunk_id,),
1061 )?;
1062
1063 }
1066
1067 Update::StartReattachItems | Update::EndReattachItems => {
1068 }
1070 }
1071 }
1072
1073 Ok(())
1074 })
1075 .await?;
1076
1077 Ok(())
1078 }
1079
1080 #[instrument(skip(self))]
1081 async fn load_all_chunks(
1082 &self,
1083 linked_chunk_id: LinkedChunkId<'_>,
1084 ) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error> {
1085 let _timer = timer!("method");
1086
1087 let hashed_linked_chunk_id =
1088 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1089 let encryption = self.encryption.clone();
1090
1091 let result = self
1092 .read()
1093 .await?
1094 .with_transaction(move |txn| -> Result<_> {
1095 let mut items = Vec::new();
1096
1097 for data in txn
1099 .prepare(
1100 "SELECT id, previous, next, type FROM linked_chunks WHERE linked_chunk_id = ? ORDER BY id",
1101 )?
1102 .query_map((&hashed_linked_chunk_id,), Self::map_row_to_chunk)?
1103 {
1104 let (id, previous, next, chunk_type) = data?;
1105 let new = txn.rebuild_chunk(
1106 &encryption,
1107 &hashed_linked_chunk_id,
1108 previous,
1109 id,
1110 next,
1111 chunk_type.as_str(),
1112 )?;
1113 items.push(new);
1114 }
1115
1116 Ok(items)
1117 })
1118 .await?;
1119
1120 Ok(result)
1121 }
1122
1123 #[instrument(skip(self))]
1124 async fn load_all_chunks_metadata(
1125 &self,
1126 linked_chunk_id: LinkedChunkId<'_>,
1127 ) -> Result<Vec<ChunkMetadata>, Self::Error> {
1128 let _timer = timer!("method");
1129
1130 let hashed_linked_chunk_id =
1131 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1132
1133 self.read()
1134 .await?
1135 .with_transaction(move |txn| -> Result<_> {
1136 let num_events_by_chunk_ids = txn
1163 .prepare(
1164 "SELECT ec.chunk_id, COUNT(ec.event_id) \
1165 FROM event_chunks as ec \
1166 WHERE ec.linked_chunk_id = ? \
1167 GROUP BY ec.chunk_id",
1168 )?
1169 .query_map((&hashed_linked_chunk_id,), |row| {
1170 Ok((row.get::<_, u64>(0)?, row.get::<_, usize>(1)?))
1171 })?
1172 .collect::<Result<HashMap<_, _>, _>>()?;
1173
1174 txn.prepare(
1175 "SELECT \
1176 lc.id, \
1177 lc.previous, \
1178 lc.next, \
1179 lc.type \
1180 FROM linked_chunks as lc \
1181 WHERE lc.linked_chunk_id = ? \
1182 ORDER BY lc.id",
1183 )?
1184 .query_map((&hashed_linked_chunk_id,), |row| {
1185 Ok((
1186 row.get::<_, u64>(0)?,
1187 row.get::<_, Option<u64>>(1)?,
1188 row.get::<_, Option<u64>>(2)?,
1189 row.get::<_, String>(3)?,
1190 ))
1191 })?
1192 .map(|data| -> Result<_> {
1193 let (id, previous, next, chunk_type) = data?;
1194
1195 let num_items = if chunk_type == CHUNK_TYPE_GAP_TYPE_STRING {
1201 0
1202 } else {
1203 num_events_by_chunk_ids.get(&id).copied().unwrap_or(0)
1204 };
1205
1206 Ok(ChunkMetadata {
1207 identifier: ChunkIdentifier::new(id),
1208 previous: previous.map(ChunkIdentifier::new),
1209 next: next.map(ChunkIdentifier::new),
1210 num_items,
1211 })
1212 })
1213 .collect::<Result<Vec<_>, _>>()
1214 })
1215 .await
1216 }
1217
1218 #[instrument(skip(self))]
1219 async fn load_last_chunk(
1220 &self,
1221 linked_chunk_id: LinkedChunkId<'_>,
1222 ) -> Result<(Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator), Self::Error> {
1223 let _timer = timer!("method");
1224
1225 let hashed_linked_chunk_id =
1226 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1227 let encryption = self.encryption.clone();
1228
1229 self
1230 .read()
1231 .await?
1232 .with_transaction(move |txn| -> Result<_> {
1233 let (observed_max_identifier, number_of_chunks) = txn
1235 .prepare(
1236 "SELECT MAX(id), COUNT(*) FROM linked_chunks WHERE linked_chunk_id = ?"
1237 )?
1238 .query_row(
1239 (&hashed_linked_chunk_id,),
1240 |row| {
1241 Ok((
1242 row.get::<_, Option<u64>>(0)?,
1247 row.get::<_, u64>(1)?,
1248 ))
1249 }
1250 )?;
1251
1252 let chunk_identifier_generator = match observed_max_identifier {
1253 Some(max_observed_identifier) => {
1254 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(
1255 ChunkIdentifier::new(max_observed_identifier)
1256 )
1257 },
1258 None => ChunkIdentifierGenerator::new_from_scratch(),
1259 };
1260
1261 let Some((chunk_identifier, previous_chunk, chunk_type)) = txn
1263 .prepare(
1264 "SELECT id, previous, type FROM linked_chunks WHERE linked_chunk_id = ? AND next IS NULL"
1265 )?
1266 .query_row(
1267 (&hashed_linked_chunk_id,),
1268 |row| {
1269 Ok((
1270 row.get::<_, u64>(0)?,
1271 row.get::<_, Option<u64>>(1)?,
1272 row.get::<_, String>(2)?,
1273 ))
1274 }
1275 )
1276 .optional()?
1277 else {
1278 if number_of_chunks == 0 {
1281 return Ok((None, chunk_identifier_generator));
1282 }
1283 else {
1288 return Err(Error::InvalidData {
1289 details:
1290 "last chunk is not found but chunks exist: the linked chunk contains a cycle"
1291 .to_owned()
1292 }
1293 )
1294 }
1295 };
1296
1297 let last_chunk = txn.rebuild_chunk(
1299 &encryption,
1300 &hashed_linked_chunk_id,
1301 previous_chunk,
1302 chunk_identifier,
1303 None,
1304 &chunk_type
1305 )?;
1306
1307 Ok((Some(last_chunk), chunk_identifier_generator))
1308 })
1309 .await
1310 }
1311
1312 #[instrument(skip(self))]
1313 async fn load_previous_chunk(
1314 &self,
1315 linked_chunk_id: LinkedChunkId<'_>,
1316 before_chunk_identifier: ChunkIdentifier,
1317 ) -> Result<Option<RawChunk<Event, Gap>>, Self::Error> {
1318 let _timer = timer!("method");
1319
1320 let hashed_linked_chunk_id =
1321 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1322 let encryption = self.encryption.clone();
1323
1324 self
1325 .read()
1326 .await?
1327 .with_transaction(move |txn| -> Result<_> {
1328 let Some((chunk_identifier, previous_chunk, next_chunk, chunk_type)) = txn
1330 .prepare(
1331 "SELECT id, previous, next, type FROM linked_chunks WHERE linked_chunk_id = ? AND next = ?"
1332 )?
1333 .query_row(
1334 (&hashed_linked_chunk_id, before_chunk_identifier.index()),
1335 |row| {
1336 Ok((
1337 row.get::<_, u64>(0)?,
1338 row.get::<_, Option<u64>>(1)?,
1339 row.get::<_, Option<u64>>(2)?,
1340 row.get::<_, String>(3)?,
1341 ))
1342 }
1343 )
1344 .optional()?
1345 else {
1346 return Ok(None);
1348 };
1349
1350 let last_chunk = txn.rebuild_chunk(
1352 &encryption,
1353 &hashed_linked_chunk_id,
1354 previous_chunk,
1355 chunk_identifier,
1356 next_chunk,
1357 &chunk_type
1358 )?;
1359
1360 Ok(Some(last_chunk))
1361 })
1362 .await
1363 }
1364
1365 async fn remember_thread(
1366 &self,
1367 room_id: &RoomId,
1368 thread_id: &EventId,
1369 ) -> Result<(), Self::Error> {
1370 let hashed_linked_chunk_id = self
1371 .encryption
1372 .encode_linked_chunk(keys::LINKED_CHUNKS, &LinkedChunkId::Thread(room_id, thread_id));
1373 let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1374 let hashed_thread_id = self.encryption.encode_thread_id(thread_id)?;
1375
1376 self.write()
1377 .await?
1378 .with_transaction(move |txn| {
1379 txn.execute(
1380 "INSERT OR IGNORE INTO threads VALUES (?, ?, ?)",
1381 (hashed_linked_chunk_id, hashed_room_id, hashed_thread_id),
1382 )?;
1383
1384 Ok(())
1385 })
1386 .await
1387 }
1388
1389 #[instrument(skip(self))]
1390 async fn clear_all_events(&self, room_id: Option<&RoomId>) -> Result<(), Self::Error> {
1391 let _timer = timer!("method");
1392
1393 match room_id {
1394 None => {
1396 self.write()
1397 .await?
1398 .with_transaction(move |txn| {
1399 txn.execute("DELETE FROM linked_chunks", ())?;
1401
1402 txn.execute("DELETE FROM events", ())?;
1404
1405 Ok(())
1406 })
1407 .await
1408 }
1409
1410 Some(room_id) => {
1412 let encryption = self.encryption.clone();
1413 let room_id = room_id.to_owned();
1414
1415 self.write()
1416 .await?
1417 .with_transaction(move |txn| {
1418 {
1420 let mut delete =
1421 txn.prepare("DELETE FROM linked_chunks WHERE linked_chunk_id = ?")?;
1422
1423 for linked_chunk_id in
1424 [LinkedChunkId::Room(&room_id), LinkedChunkId::PinnedEvents(&room_id)]
1425 {
1426 let linked_chunk_id = encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1427
1428 delete.execute((&linked_chunk_id,))?;
1431 }
1432 }
1433
1434 let encoded_room_id = encryption.encode_room_id(keys::EVENTS, &room_id);
1435
1436 {
1438 txn.execute(
1439 "DELETE FROM linked_chunks WHERE linked_chunk_id IN (SELECT linked_chunk_id FROM threads WHERE room_id = ?)",
1440 (&encoded_room_id,),
1441 )?;
1442 }
1443
1444 txn.execute(
1446 "DELETE FROM events WHERE room_id = ?",
1447 (encoded_room_id,),
1448 )?;
1449
1450 Ok(())
1451 })
1452 .await
1453 }
1454 }
1455 }
1456
1457 #[instrument(skip(self, event_ids))]
1458 async fn filter_duplicated_events(
1459 &self,
1460 linked_chunk_id: LinkedChunkId<'_>,
1461 event_ids: Vec<OwnedEventId>,
1462 ) -> Result<Vec<(OwnedEventId, Position)>, Self::Error> {
1463 let _timer = timer!("method");
1464
1465 if event_ids.is_empty() {
1469 return Ok(Vec::new());
1470 }
1471
1472 let hashed_linked_chunk_id =
1474 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1475 let event_ids_and_hashed_event_ids = event_ids
1476 .into_iter()
1477 .map(|event_id| {
1478 let hashed_event_id = self.encryption.encode_event_id(
1479 keys::EVENTS,
1484 &event_id,
1485 );
1486
1487 (event_id, hashed_event_id)
1488 })
1489 .collect::<Vec<_>>();
1490
1491 self.read()
1492 .await?
1493 .with_transaction(move |txn| -> Result<_> {
1494 txn.chunk_large_query_over(
1495 event_ids_and_hashed_event_ids,
1496 None,
1497 move |txn, event_ids_and_hashed_event_ids| {
1498 let query = format!(
1499 "SELECT event_id, chunk_id, position \
1500 FROM event_chunks \
1501 WHERE linked_chunk_id = ? AND event_id IN ({}) \
1502 ORDER BY chunk_id ASC, position ASC",
1503 repeat_vars(event_ids_and_hashed_event_ids.len()),
1504 );
1505
1506 let parameters = params_from_iter(
1507 once(
1509 hashed_linked_chunk_id
1510 .to_sql()
1511 .unwrap(),
1513 )
1514 .chain(
1516 event_ids_and_hashed_event_ids.iter().map(
1517 |(_event_id, hashed_event_id)| {
1518 hashed_event_id
1519 .to_sql()
1520 .unwrap()
1523 },
1524 ),
1525 ),
1526 );
1527
1528 let mut duplicated_events = Vec::new();
1529
1530 for duplicated_event in
1531 txn.prepare(&query)?.query_map(parameters, |row| {
1532 Ok((
1533 row.get::<_, Vec<u8>>(0)?,
1534 row.get::<_, u64>(1)?,
1535 row.get::<_, usize>(2)?,
1536 ))
1537 })?
1538 {
1539 let (duplicated_hashed_event_id, chunk_identifier, index) =
1540 duplicated_event?;
1541
1542 let Some(duplicated_event_id) = event_ids_and_hashed_event_ids
1546 .iter()
1547 .find_map(|(event_id, hashed_event_id)| {
1548 (hashed_event_id.deref() == duplicated_hashed_event_id)
1549 .then_some(event_id.clone())
1550 })
1551 else {
1552 error!(
1553 "Unreachable: found a duplicated event that was not requested"
1554 );
1555 continue;
1556 };
1557
1558 duplicated_events.push((
1559 duplicated_event_id,
1560 Position::new(ChunkIdentifier::new(chunk_identifier), index),
1561 ));
1562 }
1563
1564 Ok(duplicated_events)
1565 },
1566 )
1567 })
1568 .await
1569 }
1570
1571 #[instrument(skip(self, event_id))]
1572 async fn find_event(
1573 &self,
1574 room_id: &RoomId,
1575 event_id: &EventId,
1576 ) -> Result<Option<Event>, Self::Error> {
1577 let _timer = timer!("method");
1578
1579 let encryption = self.encryption.clone();
1580
1581 let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1582 let hashed_event_id = self.encryption.encode_event_id(keys::EVENTS, event_id);
1583
1584 self.read()
1585 .await?
1586 .with_transaction(move |txn| -> Result<_> {
1587 let Some(event) = txn
1588 .prepare("SELECT content FROM events WHERE event_id = ? AND room_id = ?")?
1589 .query_row((hashed_event_id, hashed_room_id), |row| row.get::<_, Vec<u8>>(0))
1590 .optional()?
1591 else {
1592 return Ok(None);
1594 };
1595
1596 Ok(Some(encryption.decode_event(&event)?))
1597 })
1598 .await
1599 }
1600
1601 #[instrument(skip(self, event_id, filters))]
1602 async fn find_event_relations(
1603 &self,
1604 room_id: &RoomId,
1605 event_id: &EventId,
1606 filters: Option<&[RelationType]>,
1607 ) -> Result<Vec<(Event, Option<Position>)>, Self::Error> {
1608 let _timer = timer!("method");
1609
1610 let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1611 let hashed_linked_chunk_id =
1612 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &LinkedChunkId::Room(room_id));
1613 let hashed_event_id = self.encryption.encode_event_id(keys::EVENTS, event_id);
1614
1615 let filters = filters.map(ToOwned::to_owned);
1616 let encryption = self.encryption.clone();
1617
1618 self.read()
1619 .await?
1620 .with_transaction(move |txn| -> Result<_> {
1621 find_event_relations_transaction(
1622 &encryption,
1623 hashed_room_id,
1624 hashed_linked_chunk_id,
1625 hashed_event_id,
1626 filters,
1627 txn,
1628 )
1629 })
1630 .await
1631 }
1632
1633 #[instrument(skip(self))]
1634 async fn get_room_events(
1635 &self,
1636 room_id: &RoomId,
1637 event_type: Option<&str>,
1638 session_id: Option<&str>,
1639 ) -> Result<Vec<Event>, Self::Error> {
1640 let _timer = timer!("method");
1641
1642 let encryption = self.encryption.clone();
1643
1644 let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1645 let hashed_event_type = event_type.map(|e| self.encryption.encode_key(keys::EVENTS, e));
1646 let hashed_session_id = session_id.map(|s| self.encryption.encode_key(keys::EVENTS, s));
1647
1648 self.read()
1649 .await?
1650 .with_transaction(move |txn| -> Result<_> {
1651 #[allow(clippy::redundant_clone)]
1655 let (query, keys) = match (hashed_event_type, hashed_session_id) {
1656 (None, None) => {
1657 ("SELECT content FROM events WHERE room_id = ?", params![hashed_room_id])
1658 }
1659 (None, Some(session_id)) => (
1660 "SELECT content FROM events WHERE room_id = ?1 AND session_id = ?2",
1661 params![hashed_room_id, session_id.to_owned()],
1662 ),
1663 (Some(event_type), None) => (
1664 "SELECT content FROM events WHERE room_id = ? AND event_type = ?",
1665 params![hashed_room_id, event_type.to_owned()]
1666 ),
1667 (Some(event_type), Some(session_id)) => (
1668 "SELECT content FROM events WHERE room_id = ?1 AND event_type = ?2 AND session_id = ?3",
1669 params![hashed_room_id, event_type.to_owned(), session_id.to_owned()],
1670 ),
1671 };
1672
1673 let mut statement = txn.prepare(query)?;
1674
1675 statement
1676 .query_map(keys, |row| row.get::<_, Vec<u8>>(0))?
1677 .map(|maybe_encoded_event| {
1678 encryption.decode_event(&maybe_encoded_event?)
1679 })
1680 .collect::<Result<Vec<_>>>()
1681 })
1682 .await
1683 }
1684
1685 #[instrument(skip(self, event))]
1686 async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error> {
1687 let _timer = timer!("method");
1688
1689 let Some(event_id) = event.event_id() else {
1690 error!("Trying to save an event with no ID");
1691 return Ok(());
1692 };
1693
1694 let Some(event_type) = event.kind.event_type() else {
1695 error!(%event_id, "Trying to save an event with no event type");
1696 return Ok(());
1697 };
1698
1699 let hashed_event_type = self.encryption.encode_key(keys::EVENTS, event_type);
1700 let hashed_session_id =
1701 event.kind.session_id().map(|s| self.encryption.encode_key(keys::EVENTS, s));
1702
1703 let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1704 let hashed_event_id = self.encryption.encode_event_id(keys::EVENTS, event_id);
1705 let encoded_event = self.encryption.encode_event(&event)?;
1706
1707 self.write()
1708 .await?
1709 .with_transaction(move |txn| -> Result<_> {
1710 txn.execute(
1711 "INSERT OR REPLACE INTO events(room_id, event_id, event_type, session_id, content, relates_to, rel_type) VALUES (?, ?, ?, ?, ?, ?, ?)",
1712 (
1713 &hashed_room_id,
1714 hashed_event_id,
1715 hashed_event_type,
1716 hashed_session_id,
1717 encoded_event.content,
1718 encoded_event.relates_to,
1719 encoded_event.rel_type
1720 )
1721 )?;
1722
1723 Ok(())
1724 })
1725 .await
1726 }
1727
1728 async fn close(&self) -> Result<(), Self::Error> {
1729 SqliteEventCacheStore::close(self).await
1730 }
1731
1732 async fn reopen(&self) -> Result<(), Self::Error> {
1733 SqliteEventCacheStore::reopen(self).await
1734 }
1735
1736 async fn optimize(&self) -> Result<(), Self::Error> {
1737 Ok(self.vacuum().await?)
1738 }
1739
1740 async fn get_size(&self) -> Result<Option<usize>, Self::Error> {
1741 self.get_db_size().await
1742 }
1743}
1744
1745fn find_event_relations_transaction(
1746 encryption: &Encryption,
1747 hashed_room_id: Key,
1748 hashed_linked_chunk_id: Key,
1749 hashed_event_id: Key,
1750 filters: Option<Vec<RelationType>>,
1751 txn: &Transaction<'_>,
1752) -> Result<Vec<(Event, Option<Position>)>> {
1753 let get_rows = |row: &rusqlite::Row<'_>| {
1754 Ok((
1755 row.get::<_, Vec<u8>>(0)?,
1756 row.get::<_, Option<u64>>(1)?,
1757 row.get::<_, Option<usize>>(2)?,
1758 ))
1759 };
1760
1761 let collect_results = |transaction| {
1763 let mut related = Vec::new();
1764
1765 for result in transaction {
1766 let (event, chunk_id, index): (Vec<u8>, Option<u64>, _) = result?;
1767 let event = encryption.decode_event(&event)?;
1768
1769 let pos = chunk_id
1772 .zip(index)
1773 .map(|(chunk_id, index)| Position::new(ChunkIdentifier::new(chunk_id), index));
1774
1775 related.push((event, pos));
1776 }
1777
1778 Ok(related)
1779 };
1780
1781 if let Some(filters) = filters
1782 && filters.is_empty().not()
1783 {
1784 let query = format!(
1785 "SELECT events.content, event_chunks.chunk_id, event_chunks.position \
1786 FROM events \
1787 LEFT JOIN event_chunks ON events.event_id = event_chunks.event_id AND event_chunks.linked_chunk_id = ? \
1788 WHERE events.relates_to = ? AND events.room_id = ? AND events.rel_type IN ({})",
1789 repeat_vars(filters.len())
1790 );
1791
1792 let filter_strings: Vec<_> = filters.iter().map(|f| f.to_string()).collect();
1797 let filters_params: Vec<_> = filter_strings
1798 .iter()
1799 .map(|f| f.to_sql().expect("converting a string to SQL should work"))
1800 .collect();
1801
1802 let parameters = params_from_iter(
1803 [
1804 hashed_linked_chunk_id.to_sql().expect(
1805 "We should be able to convert a hashed linked chunk ID to a SQLite value",
1806 ),
1807 hashed_event_id
1808 .to_sql()
1809 .expect("We should be able to convert an event ID to a SQLite value"),
1810 hashed_room_id
1811 .to_sql()
1812 .expect("We should be able to convert a room ID to a SQLite value"),
1813 ]
1814 .into_iter()
1815 .chain(filters_params),
1816 );
1817
1818 let mut transaction = txn.prepare(&query)?;
1819 let transaction = transaction.query_map(parameters, get_rows)?;
1820
1821 collect_results(transaction)
1822 } else {
1823 let query = "SELECT events.content, event_chunks.chunk_id, event_chunks.position \
1824 FROM events \
1825 LEFT JOIN event_chunks ON events.event_id = event_chunks.event_id AND event_chunks.linked_chunk_id = ? \
1826 WHERE events.relates_to = ? AND events.room_id = ?";
1827 let parameters = (hashed_linked_chunk_id, hashed_event_id, hashed_room_id);
1828
1829 let mut transaction = txn.prepare(query)?;
1830 let transaction = transaction.query_map(parameters, get_rows)?;
1831
1832 collect_results(transaction)
1833 }
1834}
1835
1836async fn with_immediate_transaction<
1841 T: Send + 'static,
1842 F: FnOnce(&Transaction<'_>) -> Result<T, Error> + Send + 'static,
1843>(
1844 this: &SqliteEventCacheStore,
1845 f: F,
1846) -> Result<T, Error> {
1847 this.write()
1848 .await?
1849 .interact(move |conn| -> Result<T, Error> {
1850 conn.set_transaction_behavior(TransactionBehavior::Immediate);
1854
1855 let code = || -> Result<T, Error> {
1856 let txn = conn.transaction()?;
1857 let res = f(&txn)?;
1858 txn.commit()?;
1859 Ok(res)
1860 };
1861
1862 let res = code();
1863
1864 conn.set_transaction_behavior(TransactionBehavior::Deferred);
1867
1868 res
1869 })
1870 .await
1871 .unwrap()
1873}
1874
1875fn insert_chunk(
1876 txn: &Transaction<'_>,
1877 linked_chunk_id: &Key,
1878 previous: Option<u64>,
1879 new: u64,
1880 next: Option<u64>,
1881 type_str: &str,
1882) -> rusqlite::Result<()> {
1883 txn.execute(
1885 r#"
1886 INSERT INTO linked_chunks(id, linked_chunk_id, previous, next, type)
1887 VALUES (?, ?, ?, ?, ?)
1888 "#,
1889 (new, linked_chunk_id, previous, next, type_str),
1890 )?;
1891
1892 if let Some(previous) = previous {
1894 let updated = txn.execute(
1895 r#"
1896 UPDATE linked_chunks
1897 SET next = ?
1898 WHERE id = ? AND linked_chunk_id = ?
1899 "#,
1900 (new, previous, linked_chunk_id),
1901 )?;
1902 if updated < 1 {
1903 return Err(rusqlite::Error::QueryReturnedNoRows);
1904 }
1905 if updated > 1 {
1906 return Err(rusqlite::Error::QueryReturnedMoreThanOneRow);
1907 }
1908 }
1909
1910 if let Some(next) = next {
1912 let updated = txn.execute(
1913 r#"
1914 UPDATE linked_chunks
1915 SET previous = ?
1916 WHERE id = ? AND linked_chunk_id = ?
1917 "#,
1918 (new, next, linked_chunk_id),
1919 )?;
1920 if updated < 1 {
1921 return Err(rusqlite::Error::QueryReturnedNoRows);
1922 }
1923 if updated > 1 {
1924 return Err(rusqlite::Error::QueryReturnedMoreThanOneRow);
1925 }
1926 }
1927
1928 Ok(())
1929}
1930
1931#[cfg(test)]
1932mod tests {
1933 use std::{
1934 path::PathBuf,
1935 sync::{
1936 LazyLock,
1937 atomic::{AtomicU32, Ordering::SeqCst},
1938 },
1939 };
1940
1941 use assert_matches::assert_matches;
1942 use matrix_sdk_base::{
1943 event_cache::store::{
1944 EventCacheStore, EventCacheStoreError, IntoEventCacheStore,
1945 integration_tests::EventCacheStoreIntegrationTests,
1946 },
1947 event_cache_store_integration_tests, event_cache_store_integration_tests_time,
1948 linked_chunk::{ChunkIdentifier, LinkedChunkId, Update},
1949 };
1950 use matrix_sdk_test::{DEFAULT_TEST_ROOM_ID, async_test};
1951 use ruma::{OwnedEventId, event_id};
1952 use tempfile::{TempDir, tempdir};
1953
1954 use super::{SqliteEventCacheStore, keys};
1955 use crate::{SqliteStoreConfig, utils::SqliteAsyncConnExt};
1956
1957 static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
1958 static NUM: AtomicU32 = AtomicU32::new(0);
1959
1960 fn new_event_cache_store_workspace() -> PathBuf {
1961 let name = NUM.fetch_add(1, SeqCst).to_string();
1962 TMP_DIR.path().join(name)
1963 }
1964
1965 async fn get_event_cache_store() -> Result<SqliteEventCacheStore, EventCacheStoreError> {
1966 let tmpdir_path = new_event_cache_store_workspace();
1967
1968 tracing::info!("using event cache store @ {}", tmpdir_path.to_str().unwrap());
1969
1970 Ok(SqliteEventCacheStore::open(tmpdir_path.to_str().unwrap(), None).await.unwrap())
1971 }
1972
1973 event_cache_store_integration_tests!();
1974 event_cache_store_integration_tests_time!();
1975
1976 #[async_test]
1977 async fn test_encryption_encode_decode_thread_id_roundtrip() {
1978 let store = get_event_cache_store().await.expect("creating cache store failed");
1979 let thread_id: OwnedEventId = event_id!("$event").to_owned();
1980
1981 let encoded_thread_id = store.encryption.encode_thread_id(&thread_id).unwrap();
1982 let decoded_thread_id: OwnedEventId =
1983 store.encryption.decode_thread_id(&encoded_thread_id).unwrap();
1984
1985 assert_eq!(thread_id, decoded_thread_id);
1986 }
1987
1988 #[async_test]
1989 async fn test_pool_size() {
1990 let tmpdir_path = new_event_cache_store_workspace();
1991 let store_open_config = SqliteStoreConfig::new(tmpdir_path).pool_max_size(42);
1992
1993 let store = SqliteEventCacheStore::open_with_config(&store_open_config).await.unwrap();
1994
1995 assert_eq!(store.pool_max_size().await, Some(42));
1996 }
1997
1998 #[async_test]
1999 async fn test_linked_chunk_remove_chunk() {
2000 let store = get_event_cache_store().await.expect("creating cache store failed");
2001
2002 store.clone().into_event_cache_store().test_linked_chunk_remove_chunk().await;
2004
2005 let gaps = store
2007 .read()
2008 .await
2009 .unwrap()
2010 .with_transaction(|txn| -> rusqlite::Result<_> {
2011 let mut gaps = Vec::new();
2012 for data in txn
2013 .prepare("SELECT chunk_id FROM gap_chunks ORDER BY chunk_id")?
2014 .query_map((), |row| row.get::<_, u64>(0))?
2015 {
2016 gaps.push(data?);
2017 }
2018 Ok(gaps)
2019 })
2020 .await
2021 .unwrap();
2022
2023 assert_eq!(gaps, vec![42, 44]);
2026 }
2027
2028 #[async_test]
2029 async fn test_linked_chunk_remove_item() {
2030 let store = get_event_cache_store().await.expect("creating cache store failed");
2031
2032 store.clone().into_event_cache_store().test_linked_chunk_remove_item().await;
2034
2035 let room_id = *DEFAULT_TEST_ROOM_ID;
2036 let hashed_linked_chunk_id = store
2037 .encryption
2038 .encode_linked_chunk(keys::LINKED_CHUNKS, &LinkedChunkId::Room(room_id));
2039
2040 let num_rows: u64 = store
2042 .read()
2043 .await
2044 .unwrap()
2045 .with_transaction(move |txn| {
2046 txn.query_row(
2047 "SELECT COUNT(*) FROM event_chunks WHERE chunk_id = 42 AND linked_chunk_id = ? AND position IN (2, 3, 4)",
2048 (hashed_linked_chunk_id,),
2049 |row| row.get(0),
2050 )
2051 })
2052 .await
2053 .unwrap();
2054 assert_eq!(num_rows, 3);
2055 }
2056
2057 #[async_test]
2058 async fn test_linked_chunk_clear() {
2059 let store = get_event_cache_store().await.expect("creating cache store failed");
2060
2061 store.clone().into_event_cache_store().test_linked_chunk_clear().await;
2063
2064 store
2066 .read()
2067 .await
2068 .unwrap()
2069 .with_transaction(|txn| -> rusqlite::Result<_> {
2070 let num_gaps = txn
2071 .prepare("SELECT COUNT(chunk_id) FROM gap_chunks ORDER BY chunk_id")?
2072 .query_row((), |row| row.get::<_, u64>(0))?;
2073 assert_eq!(num_gaps, 0);
2074
2075 let num_events = txn
2076 .prepare("SELECT COUNT(event_id) FROM event_chunks ORDER BY chunk_id")?
2077 .query_row((), |row| row.get::<_, u64>(0))?;
2078 assert_eq!(num_events, 0);
2079
2080 Ok(())
2081 })
2082 .await
2083 .unwrap();
2084 }
2085
2086 #[async_test]
2087 async fn test_linked_chunk_update_is_a_transaction() {
2088 let store = get_event_cache_store().await.expect("creating cache store failed");
2089
2090 let room_id = *DEFAULT_TEST_ROOM_ID;
2091 let linked_chunk_id = LinkedChunkId::Room(room_id);
2092
2093 let err = store
2096 .handle_linked_chunk_updates(
2097 linked_chunk_id,
2098 vec![
2099 Update::NewItemsChunk {
2100 previous: None,
2101 new: ChunkIdentifier::new(42),
2102 next: None,
2103 },
2104 Update::NewItemsChunk {
2105 previous: None,
2106 new: ChunkIdentifier::new(42),
2107 next: None,
2108 },
2109 ],
2110 )
2111 .await
2112 .unwrap_err();
2113
2114 assert_matches!(err, crate::error::Error::Sqlite(err) => {
2116 assert_matches!(err.sqlite_error_code(), Some(rusqlite::ErrorCode::ConstraintViolation));
2117 });
2118
2119 let chunks = store.load_all_chunks(linked_chunk_id).await.unwrap();
2123 assert!(chunks.is_empty());
2124 }
2125}
2126
2127#[cfg(test)]
2128mod encrypted_tests {
2129 use std::sync::{
2130 LazyLock,
2131 atomic::{AtomicU32, Ordering::SeqCst},
2132 };
2133
2134 use matrix_sdk_base::{
2135 event_cache::store::{EventCacheStore, EventCacheStoreError},
2136 event_cache_store_integration_tests, event_cache_store_integration_tests_time,
2137 };
2138 use matrix_sdk_test::{async_test, event_factory::EventFactory};
2139 use ruma::{
2140 event_id,
2141 events::{relation::RelationType, room::message::RoomMessageEventContentWithoutRelation},
2142 room_id, user_id,
2143 };
2144 use tempfile::{TempDir, tempdir};
2145
2146 use super::SqliteEventCacheStore;
2147
2148 static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
2149 static NUM: AtomicU32 = AtomicU32::new(0);
2150
2151 async fn get_event_cache_store() -> Result<SqliteEventCacheStore, EventCacheStoreError> {
2152 let name = NUM.fetch_add(1, SeqCst).to_string();
2153 let tmpdir_path = TMP_DIR.path().join(name);
2154
2155 tracing::info!("using event cache store @ {}", tmpdir_path.to_str().unwrap());
2156
2157 Ok(SqliteEventCacheStore::open(
2158 tmpdir_path.to_str().unwrap(),
2159 Some("default_test_password"),
2160 )
2161 .await
2162 .unwrap())
2163 }
2164
2165 event_cache_store_integration_tests!();
2166 event_cache_store_integration_tests_time!();
2167
2168 #[async_test]
2169 async fn test_no_sqlite_injection_in_find_event_relations() {
2170 let room_id = room_id!("!test:localhost");
2171 let another_room_id = room_id!("!r1:matrix.org");
2172 let sender = user_id!("@alice:localhost");
2173
2174 let store = get_event_cache_store()
2175 .await
2176 .expect("We should be able to create a new, empty, event cache store");
2177
2178 let f = EventFactory::new().room(room_id).sender(sender);
2179
2180 let event_id = event_id!("$DO_NOT_FIND_ME:matrix.org");
2182 let event = f.text_msg("DO NOT FIND").event_id(event_id).into_event();
2183
2184 let edit_id = event_id!("$find_me:matrix.org");
2186 let edit = f
2187 .text_msg("Find me")
2188 .event_id(edit_id)
2189 .edit(event_id, RoomMessageEventContentWithoutRelation::text_plain("jebote"))
2190 .into_event();
2191
2192 let f = f.room(another_room_id);
2194
2195 let another_event_id = event_id!("$DO_NOT_FIND_ME_EITHER:matrix.org");
2196 let another_event =
2197 f.text_msg("DO NOT FIND ME EITHER").event_id(another_event_id).into_event();
2198
2199 store.save_event(room_id, event).await.unwrap();
2201 store.save_event(room_id, edit).await.unwrap();
2202 store.save_event(another_room_id, another_event).await.unwrap();
2203
2204 let filter = Some(vec![RelationType::Replacement, "x\") OR 1=1; --".into()]);
2208
2209 let results = store
2211 .find_event_relations(room_id, event_id, filter.as_deref())
2212 .await
2213 .expect("We should be able to attempt to find event relations");
2214
2215 similar_asserts::assert_eq!(
2217 results.len(),
2218 1,
2219 "We should only have loaded events for the first room {results:#?}"
2220 );
2221
2222 let (found_event, _) = &results[0];
2224 assert_eq!(
2225 found_event.event_id(),
2226 Some(edit_id),
2227 "The single event we found should be the edit event"
2228 );
2229 }
2230}
2231
2232#[cfg(test)]
2233mod close_reopen_tests {
2234 use std::sync::{
2235 LazyLock,
2236 atomic::{AtomicU32, Ordering::SeqCst},
2237 };
2238
2239 use matrix_sdk_base::{event_cache::store::EventCacheStore, linked_chunk::LinkedChunkId};
2240 use matrix_sdk_test::{DEFAULT_TEST_ROOM_ID, async_test};
2241 use tempfile::{TempDir, tempdir};
2242
2243 use super::SqliteEventCacheStore;
2244
2245 static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
2246 static NUM: AtomicU32 = AtomicU32::new(0);
2247
2248 async fn new_store() -> SqliteEventCacheStore {
2249 let name = NUM.fetch_add(1, SeqCst).to_string();
2250 let tmpdir_path = TMP_DIR.path().join(name);
2251 SqliteEventCacheStore::open(tmpdir_path, None).await.unwrap()
2252 }
2253
2254 #[async_test]
2255 async fn test_close_completes_without_timeout() {
2256 let store = new_store().await;
2257
2258 let start = std::time::Instant::now();
2260 store.close().await.unwrap();
2261 let elapsed = start.elapsed();
2262
2263 assert!(
2264 elapsed < std::time::Duration::from_secs(2),
2265 "close() took {elapsed:?}, expected < 2s (no timeout)"
2266 );
2267
2268 let guard = store.connections.lock().await;
2270 assert!(guard.is_none(), "connections should be None after close");
2271 }
2272
2273 #[async_test]
2274 async fn test_reopen_restores_connections() {
2275 let store = new_store().await;
2276
2277 store.close().await.unwrap();
2278
2279 {
2280 let guard = store.connections.lock().await;
2281 assert!(guard.is_none());
2282 }
2283
2284 store.reopen().await.unwrap();
2285
2286 {
2287 let guard = store.connections.lock().await;
2288 assert!(guard.is_some(), "connections should be Some after reopen");
2289 }
2290 }
2291
2292 #[async_test]
2293 async fn test_close_is_idempotent() {
2294 let store = new_store().await;
2295
2296 store.close().await.unwrap();
2297 store.close().await.unwrap();
2299
2300 let guard = store.connections.lock().await;
2301 assert!(guard.is_none());
2302 }
2303
2304 #[async_test]
2305 async fn test_reopen_is_idempotent() {
2306 let store = new_store().await;
2307
2308 store.reopen().await.unwrap();
2310
2311 let guard = store.connections.lock().await;
2312 assert!(guard.is_some());
2313 }
2314
2315 #[async_test]
2316 async fn test_read_fails_when_closed() {
2317 let store = new_store().await;
2318 store.close().await.unwrap();
2319
2320 let err = store.load_all_chunks(LinkedChunkId::Room(*DEFAULT_TEST_ROOM_ID)).await;
2321 assert!(err.is_err(), "read should fail when closed");
2322
2323 let err_msg = err.unwrap_err().to_string();
2324 assert!(err_msg.contains("closed"), "error should mention 'closed', got: {err_msg}");
2325 }
2326
2327 #[async_test]
2328 async fn test_write_fails_when_closed() {
2329 let store = new_store().await;
2330 store.close().await.unwrap();
2331
2332 let err = store.try_take_leased_lock(1000, "test_lock", "holder").await;
2333 assert!(err.is_err(), "write should fail when closed");
2334
2335 let err_msg = err.unwrap_err().to_string();
2336 assert!(err_msg.contains("closed"), "error should mention 'closed', got: {err_msg}");
2337 }
2338
2339 #[async_test]
2340 async fn test_data_persists_across_close_reopen() {
2341 let store = new_store().await;
2342
2343 let result = store.try_take_leased_lock(60_000, "test_lock", "holder").await.unwrap();
2345 assert!(result.is_some(), "should have acquired the lock");
2346
2347 store.close().await.unwrap();
2349 store.reopen().await.unwrap();
2350
2351 let result = store.try_take_leased_lock(60_000, "test_lock", "other_holder").await.unwrap();
2353 assert!(result.is_none(), "lock should still be held by the original holder after reopen");
2354 }
2355
2356 #[async_test]
2357 async fn test_multiple_close_reopen_cycles() {
2358 let store = new_store().await;
2359
2360 for _ in 0..5 {
2361 store.close().await.unwrap();
2362 store.reopen().await.unwrap();
2363
2364 let result = store.load_all_chunks(LinkedChunkId::Room(*DEFAULT_TEST_ROOM_ID)).await;
2366 assert!(result.is_ok(), "store should work after close/reopen cycle");
2367 }
2368 }
2369
2370 #[async_test]
2371 async fn test_pool_is_fully_drained_after_close() {
2372 let store = new_store().await;
2373
2374 let _ = store.load_all_chunks(LinkedChunkId::Room(*DEFAULT_TEST_ROOM_ID)).await;
2376 let _ = store.load_all_chunks(LinkedChunkId::Room(*DEFAULT_TEST_ROOM_ID)).await;
2377
2378 store.close().await.unwrap();
2379
2380 let guard = store.connections.lock().await;
2383 assert!(guard.is_none(), "all connections should be released after close");
2384 }
2385
2386 #[async_test]
2387 async fn test_operations_work_immediately_after_reopen() {
2388 let store = new_store().await;
2389
2390 store.close().await.unwrap();
2391 store.reopen().await.unwrap();
2392
2393 let result = store.load_all_chunks(LinkedChunkId::Room(*DEFAULT_TEST_ROOM_ID)).await;
2395 assert!(result.is_ok(), "read should succeed immediately after reopen");
2396
2397 let result = store.try_take_leased_lock(1000, "test_lock", "holder").await;
2399 assert!(result.is_ok(), "write should succeed immediately after reopen");
2400 }
2401
2402 #[async_test]
2403 async fn test_close_waits_for_held_read_connection_to_drain() {
2404 let store = new_store().await;
2405
2406 let held_conn = store.read().await.unwrap();
2408
2409 let store_clone = store.clone();
2412 let close_handle = tokio::spawn(async move {
2413 store_clone.close().await.unwrap();
2414 });
2415
2416 tokio::time::sleep(std::time::Duration::from_millis(200)).await;
2418
2419 assert!(!close_handle.is_finished(), "close should be waiting for the held connection");
2421
2422 drop(held_conn);
2424
2425 let timeout = tokio::time::timeout(std::time::Duration::from_secs(3), close_handle).await;
2427 assert!(timeout.is_ok(), "close should complete after the held connection is released");
2428 timeout.unwrap().unwrap();
2429
2430 let guard = store.connections.lock().await;
2432 assert!(guard.is_none(), "connections should be None after close");
2433 }
2434}