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 thread::ThreadInfo,
35 },
36 linked_chunk::{
37 ChunkContent, ChunkIdentifier, ChunkIdentifierGenerator, ChunkMetadata, LinkedChunkId,
38 Position, RawChunk, Update,
39 },
40 timer,
41};
42use matrix_sdk_store_encryption::StoreCipher;
43use ruma::{
44 EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, events::relation::RelationType,
45};
46use rusqlite::{
47 OptionalExtension, ToSql, Transaction, TransactionBehavior, params, params_from_iter,
48};
49use tokio::{
50 fs,
51 sync::{Mutex, OwnedMutexGuard},
52};
53use tracing::{debug, error, instrument, trace};
54
55use crate::{
56 OpenStoreError, RuntimeConfig, Secret, SqliteStoreConfig,
57 connection::{
58 self, Connection as SqliteAsyncConn, Pool as SqlitePool, SqliteConnections,
59 SqliteConnectionsInner,
60 },
61 error::{Error, Result},
62 utils::{
63 EncryptableStore, Key, SqliteAsyncConnExt, SqliteKeyValueStoreAsyncConnExt,
64 SqliteKeyValueStoreConnExt, SqliteTransactionExt, host_parameters,
65 },
66};
67
68mod keys {
69 pub const LINKED_CHUNKS: &str = "linked_chunks";
71 pub const EVENTS: &str = "events";
72}
73
74pub const DATABASE_NAME: &str = "matrix-sdk-event-cache.sqlite3";
76
77const CHUNK_TYPE_EVENT_TYPE_STRING: &str = "E";
80const CHUNK_TYPE_GAP_TYPE_STRING: &str = "G";
83
84struct Encryption {
89 cipher: Option<StoreCipher>,
90}
91
92impl Encryption {
93 fn encode_event(&self, event: &TimelineEvent) -> Result<EncodedEvent> {
94 let serialized = serde_json::to_vec(event)?;
95
96 let raw_event = event.raw();
98 let (relates_to, rel_type) = extract_event_relation(raw_event).unzip();
99
100 let state_key = raw_event
104 .get_field::<String>("state_key")
105 .ok()
106 .flatten()
107 .map(|state_key| self.encode_key(keys::EVENTS, state_key));
108
109 let content = self.encode_value(serialized)?;
111
112 Ok(EncodedEvent {
113 content,
114 rel_type,
115 state_key,
116 relates_to: relates_to
117 .map(|relates_to| self.encode_event_id(keys::EVENTS, &relates_to)),
118 })
119 }
120
121 fn decode_event(&self, raw_encoded_event: &[u8]) -> Result<Event> {
122 Ok(serde_json::from_slice(&self.decode_value(raw_encoded_event)?)?)
123 }
124
125 fn encode_event_id(&self, table_name: &str, event_id: &EventId) -> Key {
128 self.encode_key(table_name, event_id)
129 }
130
131 fn encode_room_id(&self, table_name: &str, room_id: &RoomId) -> Key {
133 self.encode_key(table_name, room_id)
134 }
135
136 fn encode_linked_chunk(&self, table_name: &str, linked_chunk_id: &LinkedChunkId<'_>) -> Key {
138 self.encode_key(table_name, linked_chunk_id.storage_key())
139 }
140
141 fn encode_thread_id(&self, thread_id: &EventId) -> Result<Vec<u8>> {
143 self.encode_value(String::from(thread_id.as_str()))
144 }
145
146 #[cfg(test)]
148 fn decode_thread_id(&self, encoded_thread_id: &[u8]) -> Result<OwnedEventId> {
149 let as_slice = self.decode_value(encoded_thread_id)?;
150 let as_str = str::from_utf8(as_slice.as_ref())?;
151
152 Ok(EventId::parse(as_str)?)
153 }
154
155 fn encode_thread_info(&self, thread_info: &ThreadInfo) -> Result<Vec<u8>> {
157 self.encode_value(serde_json::to_vec(thread_info)?)
158 }
159
160 fn decode_thread_info(&self, encoded_thread_info: &[u8]) -> Result<ThreadInfo> {
162 Ok(serde_json::from_slice(&self.decode_value(encoded_thread_info)?)?)
163 }
164}
165
166impl EncryptableStore for Encryption {
167 fn get_cypher(&self) -> Option<&StoreCipher> {
168 self.cipher.as_ref()
169 }
170}
171
172#[derive(Clone)]
174pub struct SqliteEventCacheStore {
175 encryption: Arc<Encryption>,
177
178 connections: Arc<SqliteConnections>,
180
181 db_path: PathBuf,
183
184 pool_config: PoolConfig,
186
187 runtime_config: RuntimeConfig,
189}
190
191#[cfg(not(tarpaulin_include))]
192impl fmt::Debug for SqliteEventCacheStore {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 f.debug_struct("SqliteEventCacheStore").finish_non_exhaustive()
195 }
196}
197
198impl SqliteEventCacheStore {
199 pub async fn open(
202 path: impl AsRef<Path>,
203 passphrase: Option<&str>,
204 ) -> Result<Self, OpenStoreError> {
205 Self::open_with_config(&SqliteStoreConfig::new(path).passphrase(passphrase)).await
206 }
207
208 pub async fn open_with_key(
211 path: impl AsRef<Path>,
212 key: Option<&[u8]>,
213 ) -> Result<Self, OpenStoreError> {
214 Self::open_with_config(&SqliteStoreConfig::new(path).key(key)).await
215 }
216
217 #[instrument(skip(config), fields(path = ?config.path))]
219 pub async fn open_with_config(config: &SqliteStoreConfig) -> Result<Self, OpenStoreError> {
220 debug!(?config);
221
222 let _timer = timer!("open_with_config");
223
224 fs::create_dir_all(&config.path).await.map_err(OpenStoreError::CreateDir)?;
225
226 let db_path = config.path.join(DATABASE_NAME);
227 let pool_config = config.pool_config();
228 let runtime_config = config.runtime_config();
229
230 let pool = config.build_pool_of_connections(DATABASE_NAME)?;
231
232 let this =
233 Self::open_with_pool(pool, db_path, pool_config, runtime_config, config.secret.clone())
234 .await?;
235
236 this.write().await?.apply_runtime_config(runtime_config).await?;
238
239 Ok(this)
240 }
241
242 async fn open_with_pool(
245 pool: SqlitePool,
246 db_path: PathBuf,
247 pool_config: PoolConfig,
248 runtime_config: RuntimeConfig,
249 secret: Option<Secret>,
250 ) -> Result<Self, OpenStoreError> {
251 let conn = pool.get().await?;
252
253 let version = conn.db_version().await?;
254
255 run_migrations(&conn, version).await?;
256
257 conn.wal_checkpoint().await;
258
259 let cipher = match secret {
260 Some(s) => Some(conn.get_or_create_store_cipher(s).await?),
261 None => None,
262 };
263
264 let connections = SqliteConnectionsInner {
265 pool,
266 write_connection: Arc::new(Mutex::new(conn)),
268 };
269
270 Ok(Self {
271 encryption: Arc::new(Encryption { cipher }),
272 connections: Arc::new(SqliteConnections::new(connections).await),
273 db_path,
274 pool_config,
275 runtime_config,
276 })
277 }
278
279 #[instrument(skip_all)]
281 async fn read(&self) -> Result<SqliteAsyncConn> {
282 let pool = {
283 let guard = self.connections.lock().await;
284 let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
285 conns.pool.clone()
286 };
287
288 let connection = pool.get().await?;
289
290 connection.execute_batch("PRAGMA foreign_keys = ON;").await?;
295
296 Ok(connection)
297 }
298
299 #[instrument(skip_all)]
301 async fn write(&self) -> Result<OwnedMutexGuard<SqliteAsyncConn>> {
302 let write_connection = {
303 let guard = self.connections.lock().await;
304 let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
305 conns.write_connection.clone()
306 };
307
308 let connection = write_connection.lock_owned().await;
309
310 connection.execute_batch("PRAGMA foreign_keys = ON;").await?;
315
316 Ok(connection)
317 }
318
319 fn map_row_to_chunk(
320 row: &rusqlite::Row<'_>,
321 ) -> Result<(u64, Option<u64>, Option<u64>, String), rusqlite::Error> {
322 Ok((
323 row.get::<_, u64>(0)?,
324 row.get::<_, Option<u64>>(1)?,
325 row.get::<_, Option<u64>>(2)?,
326 row.get::<_, String>(3)?,
327 ))
328 }
329
330 pub async fn vacuum(&self) -> Result<()> {
331 let write_connection = {
332 let guard = self.connections.lock().await;
333 let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
334 conns.write_connection.clone()
335 };
336 write_connection.lock().await.vacuum().await
337 }
338
339 async fn get_db_size(&self) -> Result<Option<usize>> {
340 let pool = {
341 let guard = self.connections.lock().await;
342 let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
343 conns.pool.clone()
344 };
345 Ok(Some(pool.get().await?.get_db_size().await?))
346 }
347
348 pub async fn close(&self) -> Result<()> {
349 connection::close_connections(&self.connections, "Event cache store").await;
350 Ok(())
351 }
352
353 pub async fn reopen(&self) -> Result<()> {
354 connection::reopen_connections(
355 &self.connections,
356 self.db_path.clone(),
357 self.pool_config,
358 self.runtime_config,
359 )
360 .await?;
361 Ok(())
362 }
363
364 #[cfg(test)]
366 async fn pool_max_size(&self) -> Option<usize> {
367 let guard = self.connections.lock().await;
368 guard.as_ref().map(|conns| conns.pool.status().max_size)
369 }
370}
371
372struct EncodedEvent {
373 content: Vec<u8>,
374 rel_type: Option<String>,
375 relates_to: Option<Key>,
376 state_key: Option<Key>,
379}
380
381trait TransactionExtForLinkedChunks {
382 fn rebuild_chunk(
383 &self,
384 encryption: &Encryption,
385 linked_chunk_id: &Key,
386 previous: Option<u64>,
387 index: u64,
388 next: Option<u64>,
389 chunk_type: &str,
390 ) -> Result<RawChunk<Event, Gap>>;
391
392 fn load_gap_content(
393 &self,
394 encryption: &Encryption,
395 linked_chunk_id: &Key,
396 chunk_id: ChunkIdentifier,
397 ) -> Result<Gap>;
398
399 fn load_events_content(
400 &self,
401 encryption: &Encryption,
402 linked_chunk_id: &Key,
403 chunk_id: ChunkIdentifier,
404 ) -> Result<Vec<Event>>;
405}
406
407impl TransactionExtForLinkedChunks for Transaction<'_> {
408 fn rebuild_chunk(
409 &self,
410 encryption: &Encryption,
411 linked_chunk_id: &Key,
412 previous: Option<u64>,
413 id: u64,
414 next: Option<u64>,
415 chunk_type: &str,
416 ) -> Result<RawChunk<Event, Gap>> {
417 let previous = previous.map(ChunkIdentifier::new);
418 let next = next.map(ChunkIdentifier::new);
419 let id = ChunkIdentifier::new(id);
420
421 match chunk_type {
422 CHUNK_TYPE_GAP_TYPE_STRING => {
423 let gap = self.load_gap_content(encryption, linked_chunk_id, id)?;
425 Ok(RawChunk { content: ChunkContent::Gap(gap), previous, identifier: id, next })
426 }
427
428 CHUNK_TYPE_EVENT_TYPE_STRING => {
429 let events = self.load_events_content(encryption, linked_chunk_id, id)?;
431 Ok(RawChunk {
432 content: ChunkContent::Items(events),
433 previous,
434 identifier: id,
435 next,
436 })
437 }
438
439 other => {
440 Err(Error::InvalidData {
442 details: format!("a linked chunk has an unknown type {other}"),
443 })
444 }
445 }
446 }
447
448 fn load_gap_content(
449 &self,
450 encryption: &Encryption,
451 linked_chunk_id: &Key,
452 chunk_id: ChunkIdentifier,
453 ) -> Result<Gap> {
454 let encoded_prev_token: Vec<u8> = self.query_one(
457 "SELECT prev_token FROM gap_chunks WHERE chunk_id = ? AND linked_chunk_id = ?",
458 (chunk_id.index(), &linked_chunk_id),
459 |row| row.get(0),
460 )?;
461 let prev_token_bytes = encryption.decode_value(&encoded_prev_token)?;
462 let prev_token = String::from_utf8(prev_token_bytes.into_owned())?;
463 Ok(Gap { token: prev_token })
464 }
465
466 fn load_events_content(
467 &self,
468 encryption: &Encryption,
469 linked_chunk_id: &Key,
470 chunk_id: ChunkIdentifier,
471 ) -> Result<Vec<Event>> {
472 let mut events = Vec::new();
474
475 for event_data in self
476 .prepare(
477 "SELECT events.content \
478 FROM event_chunks ec, events \
479 WHERE events.event_id = ec.event_id AND ec.chunk_id = ? AND ec.linked_chunk_id = ? \
480 ORDER BY ec.position ASC",
481 )?
482 .query_map((chunk_id.index(), &linked_chunk_id), |row| row.get::<_, Vec<u8>>(0))?
483 {
484 events.push(encryption.decode_event(&event_data?)?);
485 }
486
487 Ok(events)
488 }
489}
490
491async fn run_migrations(conn: &SqliteAsyncConn, version: u8) -> Result<()> {
493 conn.execute_batch("PRAGMA foreign_keys = ON;").await?;
495
496 if version < 1 {
497 debug!("Creating database");
498 conn.execute_batch("PRAGMA journal_mode = wal;").await?;
502 conn.with_transaction(|txn| {
503 txn.execute_batch(include_str!("../migrations/event_cache_store/001_init.sql"))?;
504 txn.set_db_version(1)
505 })
506 .await?;
507 }
508
509 if version < 2 {
510 debug!("Upgrading database to version 2");
511 conn.with_transaction(|txn| {
512 txn.execute_batch(include_str!("../migrations/event_cache_store/002_lease_locks.sql"))?;
513 txn.set_db_version(2)
514 })
515 .await?;
516 }
517
518 if version < 3 {
519 debug!("Upgrading database to version 3");
520 conn.with_transaction(|txn| {
521 txn.execute_batch(include_str!("../migrations/event_cache_store/003_events.sql"))?;
522 txn.set_db_version(3)
523 })
524 .await?;
525 }
526
527 if version < 4 {
528 debug!("Upgrading database to version 4");
529 conn.with_transaction(|txn| {
530 txn.execute_batch(include_str!(
531 "../migrations/event_cache_store/004_ignore_policy.sql"
532 ))?;
533 txn.set_db_version(4)
534 })
535 .await?;
536 }
537
538 if version < 5 {
539 debug!("Upgrading database to version 5");
540 conn.with_transaction(|txn| {
541 txn.execute_batch(include_str!(
542 "../migrations/event_cache_store/005_events_index_on_event_id.sql"
543 ))?;
544 txn.set_db_version(5)
545 })
546 .await?;
547 }
548
549 if version < 6 {
550 debug!("Upgrading database to version 6");
551 conn.with_transaction(|txn| {
552 txn.execute_batch(include_str!("../migrations/event_cache_store/006_events.sql"))?;
553 txn.set_db_version(6)
554 })
555 .await?;
556 }
557
558 if version < 7 {
559 debug!("Upgrading database to version 7");
560 conn.with_transaction(|txn| {
561 txn.execute_batch(include_str!(
562 "../migrations/event_cache_store/007_event_chunks.sql"
563 ))?;
564 txn.set_db_version(7)
565 })
566 .await?;
567 }
568
569 if version < 8 {
570 debug!("Upgrading database to version 8");
571 conn.with_transaction(|txn| {
572 txn.execute_batch(include_str!(
573 "../migrations/event_cache_store/008_linked_chunk_id.sql"
574 ))?;
575 txn.set_db_version(8)
576 })
577 .await?;
578 }
579
580 if version < 9 {
581 debug!("Upgrading database to version 9");
582 conn.with_transaction(|txn| {
583 txn.execute_batch(include_str!(
584 "../migrations/event_cache_store/009_related_event_index.sql"
585 ))?;
586 txn.set_db_version(9)
587 })
588 .await?;
589 }
590
591 if version < 10 {
592 debug!("Upgrading database to version 10");
593 conn.with_transaction(|txn| {
594 txn.execute_batch(include_str!("../migrations/event_cache_store/010_drop_media.sql"))?;
595 txn.set_db_version(10)
596 })
597 .await?;
598
599 if version >= 1 {
600 conn.vacuum().await?;
603 }
604 }
605
606 if version < 11 {
607 debug!("Upgrading database to version 11");
608 conn.with_transaction(|txn| {
609 txn.execute_batch(include_str!(
610 "../migrations/event_cache_store/011_empty_event_cache.sql"
611 ))?;
612 txn.set_db_version(11)
613 })
614 .await?;
615 }
616
617 if version < 12 {
618 debug!("Upgrading database to version 12");
619 conn.with_transaction(|txn| {
620 txn.execute_batch(include_str!(
621 "../migrations/event_cache_store/012_store_event_type.sql"
622 ))?;
623 txn.set_db_version(12)
624 })
625 .await?;
626 }
627
628 if version < 13 {
629 debug!("Upgrading database to version 13");
630 conn.with_transaction(|txn| {
631 txn.execute_batch(include_str!(
632 "../migrations/event_cache_store/013_lease_locks_with_generation.sql"
633 ))?;
634 txn.set_db_version(13)
635 })
636 .await?;
637 }
638
639 if version < 14 {
640 debug!("Upgrading database to version 14");
641 conn.with_transaction(|txn| {
642 txn.execute_batch(include_str!(
643 "../migrations/event_cache_store/014_event_chunks_event_id_index.sql"
644 ))?;
645 txn.set_db_version(14)
646 })
647 .await?;
648 }
649
650 if version < 15 {
651 debug!("Upgrading database to version 15");
652 conn.with_transaction(|txn| {
653 txn.execute_batch(include_str!(
654 "../migrations/event_cache_store/015_event_ids_are_encoded.sql"
655 ))?;
656 txn.set_db_version(15)
657 })
658 .await?;
659 }
660
661 if version < 16 {
662 debug!("Upgrading database to version 16");
663 conn.with_transaction(|txn| {
664 txn.execute_batch(include_str!("../migrations/event_cache_store/016_threads.sql"))?;
665 txn.set_db_version(16)
666 })
667 .await?;
668 }
669
670 if version < 17 {
671 debug!("Upgrading database to version 17");
672 conn.with_transaction(|txn| {
673 txn.execute_batch(include_str!(
674 "../migrations/event_cache_store/017_threads_with_thread_infos.sql"
675 ))?;
676 txn.set_db_version(17)
677 })
678 .await?;
679 }
680
681 if version < 18 {
682 debug!("Upgrading database to version 18");
683 conn.with_transaction(|txn| {
684 txn.execute_batch(include_str!("../migrations/event_cache_store/018_event_chunks_unique_linked_chunk_id_event_id.sql"))?;
685 txn.set_db_version(18)
686 })
687 .await?;
688 }
689
690 if version < 19 {
691 debug!("Upgrading database to version 19");
692 conn.with_transaction(|txn| {
693 txn.execute_batch(include_str!(
694 "../migrations/event_cache_store/019_events_timestamp.sql"
695 ))?;
696 txn.set_db_version(19)
697 })
698 .await?;
699 }
700
701 if version < 20 {
702 debug!("Upgrading database to version 20");
703 conn.with_transaction(|txn| {
704 txn.execute_batch(include_str!(
705 "../migrations/event_cache_store/020_events_state_key.sql"
706 ))?;
707 txn.set_db_version(20)
708 })
709 .await?;
710 }
711
712 Ok(())
713}
714
715#[async_trait]
716impl EventCacheStore for SqliteEventCacheStore {
717 type Error = Error;
718
719 #[instrument(skip(self))]
720 async fn try_take_leased_lock(
721 &self,
722 lease_duration_ms: u32,
723 key: &str,
724 holder: &str,
725 ) -> Result<Option<CrossProcessLockGeneration>> {
726 let key = key.to_owned();
727 let holder = holder.to_owned();
728
729 let now: u64 = MilliSecondsSinceUnixEpoch::now().get().into();
730 let expiration = now + lease_duration_ms as u64;
731
732 let generation = self
734 .write()
735 .await?
736 .with_transaction(move |txn| {
737 txn.query_one(
738 "INSERT INTO lease_locks (key, holder, expiration) \
739 VALUES (?1, ?2, ?3) \
740 ON CONFLICT (key) \
741 DO \
742 UPDATE SET \
743 holder = excluded.holder, \
744 expiration = excluded.expiration, \
745 generation = \
746 CASE holder \
747 WHEN excluded.holder THEN generation \
748 ELSE generation + 1 \
749 END \
750 WHERE \
751 holder = excluded.holder \
752 OR expiration < ?4 \
753 RETURNING generation",
754 (key, holder, expiration, now),
755 |row| row.get(0),
756 )
757 .optional()
758 })
759 .await?;
760
761 Ok(generation)
762 }
763
764 #[instrument(skip(self, updates))]
765 async fn handle_linked_chunk_updates(
766 &self,
767 linked_chunk_id: LinkedChunkId<'_>,
768 updates: Vec<Update<Event, Gap>>,
769 ) -> Result<(), Self::Error> {
770 let _timer = timer!("method");
771
772 let hashed_linked_chunk_id =
773 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
774 let hashed_room_id =
775 self.encryption.encode_room_id(keys::EVENTS, linked_chunk_id.room_id());
776 let encryption = self.encryption.clone();
777
778 with_immediate_transaction(self, move |txn| {
781 for update in updates {
782 match update {
783 Update::NewItemsChunk { previous, new, next } => {
784 let previous = previous.as_ref().map(ChunkIdentifier::index);
785 let new = new.index();
786 let next = next.as_ref().map(ChunkIdentifier::index);
787
788 trace!("new events chunk (prev={previous:?}, i={new}, next={next:?})");
789
790 insert_chunk(
791 txn,
792 &hashed_linked_chunk_id,
793 previous,
794 new,
795 next,
796 CHUNK_TYPE_EVENT_TYPE_STRING,
797 )?;
798 }
799
800 Update::NewGapChunk { previous, new, next, gap } => {
801 let hashed_prev_token = encryption.encode_value(gap.token)?;
802
803 let previous = previous.as_ref().map(ChunkIdentifier::index);
804 let new = new.index();
805 let next = next.as_ref().map(ChunkIdentifier::index);
806
807 trace!("new gap chunk (prev={previous:?}, i={new}, next={next:?})");
808
809 insert_chunk(
811 txn,
812 &hashed_linked_chunk_id,
813 previous,
814 new,
815 next,
816 CHUNK_TYPE_GAP_TYPE_STRING,
817 )?;
818
819 txn.execute(
821 r#"
822 INSERT INTO gap_chunks(chunk_id, linked_chunk_id, prev_token)
823 VALUES (?, ?, ?)
824 "#,
825 (new, &hashed_linked_chunk_id, hashed_prev_token),
826 )?;
827 }
828
829 Update::RemoveChunk(chunk_identifier) => {
830 let chunk_id = chunk_identifier.index();
831
832 trace!("removing chunk @ {chunk_id}");
833
834 let (previous, next): (Option<usize>, Option<usize>) = txn.query_one(
836 "SELECT previous, next FROM linked_chunks WHERE id = ? AND linked_chunk_id = ?",
837 (chunk_id, &hashed_linked_chunk_id),
838 |row| Ok((row.get(0)?, row.get(1)?))
839 )?;
840
841 if let Some(previous) = previous {
843 txn.execute("UPDATE linked_chunks SET next = ? WHERE id = ? AND linked_chunk_id = ?", (next, previous, &hashed_linked_chunk_id))?;
844 }
845
846 if let Some(next) = next {
848 txn.execute("UPDATE linked_chunks SET previous = ? WHERE id = ? AND linked_chunk_id = ?", (previous, next, &hashed_linked_chunk_id))?;
849 }
850
851 txn.execute("DELETE FROM linked_chunks WHERE id = ? AND linked_chunk_id = ?", (chunk_id, &hashed_linked_chunk_id))?;
854 }
855
856 Update::PushItems { at, items } => {
857 if items.is_empty() {
858 continue;
860 }
861
862 let chunk_id = at.chunk_identifier().index();
863
864 trace!("pushing {} items @ {chunk_id}", items.len());
865
866 let mut chunk_statement = txn.prepare(
867 "INSERT INTO event_chunks(chunk_id, linked_chunk_id, event_id, position) VALUES (?, ?, ?, ?)"
868 )?;
869
870 let mut content_statement = txn.prepare(
877 "INSERT OR REPLACE INTO events(room_id, event_id, event_type, session_id, content, relates_to, rel_type, timestamp, state_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
878 )?;
879
880 let invalid_event = |event: TimelineEvent| {
881 let Some(event_id) = event.event_id() else {
882 error!("Trying to push an event with no ID");
883 return None;
884 };
885
886 let Some(event_type) = event.kind.event_type() else {
887 error!(%event_id, "Trying to save an event with no event type");
888 return None;
889 };
890
891 Some((event_id.to_owned(), event_type, event))
892 };
893
894 for (i, (event_id, event_type, event)) in items.into_iter().filter_map(invalid_event).enumerate() {
895 let hashed_event_id = encryption.encode_event_id(
896 keys::EVENTS,
902 &event_id,
903 );
904
905 {
907 let index = at.index() + i;
908
909 chunk_statement.execute((chunk_id, &hashed_linked_chunk_id, &hashed_event_id, index))?;
910 }
911
912 {
914 let hashed_session_id = event.kind.session_id().map(|s| encryption.encode_key(keys::EVENTS, s));
915 let hashed_event_type = encryption.encode_key(keys::EVENTS, event_type);
916 let timestamp = event.timestamp.map(|ts| u64::from(ts.get()));
917 let encoded_event = encryption.encode_event(&event)?;
918
919 content_statement.execute((
920 &hashed_room_id,
921 &hashed_event_id,
922 hashed_event_type,
923 hashed_session_id,
924 encoded_event.content,
925 encoded_event.relates_to,
926 encoded_event.rel_type,
927 timestamp,
928 encoded_event.state_key,
929 ))?;
930 }
931 }
932 }
933
934 Update::ReplaceItem { at, item: event } => {
935 let chunk_id = at.chunk_identifier().index();
936 let index = at.index();
937
938 trace!("replacing item @ {chunk_id}:{index}");
939
940 let Some(event_id) = event.event_id().map(|event_id| event_id.to_owned()) else {
942 error!("Trying to replace an event with a new one that has no ID");
943 continue;
944 };
945
946 let Some(event_type) = event.kind.event_type() else {
947 error!(%event_id, "Trying to save an event with no event type");
948 continue;
949 };
950
951 let hashed_event_id = encryption.encode_event_id(keys::EVENTS, &event_id);
952
953 {
961 let hashed_session_id = event.kind.session_id().map(|s| encryption.encode_key(keys::EVENTS, s));
962 let hashed_event_type = encryption.encode_key(keys::EVENTS, event_type);
963 let encoded_event = encryption.encode_event(&event)?;
964
965 let timestamp = event.timestamp.map(|ts| u64::from(ts.get()));
966 txn.execute(
967 "INSERT OR REPLACE INTO events(room_id, event_id, event_type, session_id, content, relates_to, rel_type, timestamp, state_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
968 (
969 &hashed_room_id,
970 &hashed_event_id,
971 hashed_event_type,
972 hashed_session_id,
973 encoded_event.content,
974 encoded_event.relates_to,
975 encoded_event.rel_type,
976 timestamp,
977 encoded_event.state_key,
978 ),
979 )?;
980 }
981
982 {
984 txn.execute(
986 r#"UPDATE event_chunks SET event_id = ? WHERE linked_chunk_id = ? AND chunk_id = ? AND position = ?"#,
987 (&hashed_event_id, &hashed_linked_chunk_id, chunk_id, index)
988 )?;
989 }
990 }
991
992 Update::RemoveItem { at } => {
993 let chunk_id = at.chunk_identifier().index();
994 let index = at.index();
995
996 trace!("removing item @ {chunk_id}:{index}");
997
998 txn.execute("DELETE FROM event_chunks WHERE linked_chunk_id = ? AND chunk_id = ? AND position = ?", (&hashed_linked_chunk_id, chunk_id, index))?;
1000
1001 txn.execute(
1102 r#"
1103 UPDATE event_chunks
1104 SET position = -(position - 1)
1105 WHERE linked_chunk_id = ? AND chunk_id = ? AND position > ?
1106 "#,
1107 (&hashed_linked_chunk_id, chunk_id, index)
1108 )?;
1109 txn.execute(
1110 r#"
1111 UPDATE event_chunks
1112 SET position = -position
1113 WHERE position < 0 AND linked_chunk_id = ? AND chunk_id = ?
1114 "#,
1115 (&hashed_linked_chunk_id, chunk_id)
1116 )?;
1117
1118 }
1121
1122 Update::DetachLastItems { at } => {
1123 let chunk_id = at.chunk_identifier().index();
1124 let index = at.index();
1125
1126 trace!("truncating items >= {chunk_id}:{index}");
1127
1128 txn.execute("DELETE FROM event_chunks WHERE linked_chunk_id = ? AND chunk_id = ? AND position >= ?", (&hashed_linked_chunk_id, chunk_id, index))?;
1130
1131 }
1134
1135 Update::Clear => {
1136 trace!("clearing items");
1137
1138 txn.execute(
1140 "DELETE FROM linked_chunks WHERE linked_chunk_id = ?",
1141 (&hashed_linked_chunk_id,),
1142 )?;
1143
1144 }
1147
1148 Update::StartReattachItems | Update::EndReattachItems => {
1149 }
1151 }
1152 }
1153
1154 Ok(())
1155 })
1156 .await?;
1157
1158 Ok(())
1159 }
1160
1161 #[instrument(skip(self))]
1162 async fn load_all_chunks(
1163 &self,
1164 linked_chunk_id: LinkedChunkId<'_>,
1165 ) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error> {
1166 let _timer = timer!("method");
1167
1168 let hashed_linked_chunk_id =
1169 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1170 let encryption = self.encryption.clone();
1171
1172 let result = self
1173 .read()
1174 .await?
1175 .with_transaction(move |txn| -> Result<_> {
1176 let mut items = Vec::new();
1177
1178 for data in txn
1180 .prepare(
1181 "SELECT id, previous, next, type FROM linked_chunks WHERE linked_chunk_id = ? ORDER BY id",
1182 )?
1183 .query_map((&hashed_linked_chunk_id,), Self::map_row_to_chunk)?
1184 {
1185 let (id, previous, next, chunk_type) = data?;
1186 let new = txn.rebuild_chunk(
1187 &encryption,
1188 &hashed_linked_chunk_id,
1189 previous,
1190 id,
1191 next,
1192 chunk_type.as_str(),
1193 )?;
1194 items.push(new);
1195 }
1196
1197 Ok(items)
1198 })
1199 .await?;
1200
1201 Ok(result)
1202 }
1203
1204 #[instrument(skip(self))]
1205 async fn load_all_chunks_metadata(
1206 &self,
1207 linked_chunk_id: LinkedChunkId<'_>,
1208 ) -> Result<Vec<ChunkMetadata>, Self::Error> {
1209 let _timer = timer!("method");
1210
1211 let hashed_linked_chunk_id =
1212 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1213
1214 self.read()
1215 .await?
1216 .with_transaction(move |txn| -> Result<_> {
1217 let num_events_by_chunk_ids = txn
1250 .prepare(
1251 "SELECT ec.chunk_id, COUNT(ec.event_id) \
1252 FROM event_chunks as ec \
1253 WHERE ec.linked_chunk_id = ? \
1254 GROUP BY ec.chunk_id",
1255 )?
1256 .query_map((&hashed_linked_chunk_id,), |row| {
1257 Ok((row.get::<_, u64>(0)?, row.get::<_, usize>(1)?))
1258 })?
1259 .collect::<Result<HashMap<_, _>, _>>()?;
1260
1261 txn.prepare(
1262 "SELECT \
1263 lc.id, \
1264 lc.previous, \
1265 lc.next, \
1266 lc.type \
1267 FROM linked_chunks as lc \
1268 WHERE lc.linked_chunk_id = ? \
1269 ORDER BY lc.id",
1270 )?
1271 .query_map((&hashed_linked_chunk_id,), |row| {
1272 Ok((
1273 row.get::<_, u64>(0)?,
1274 row.get::<_, Option<u64>>(1)?,
1275 row.get::<_, Option<u64>>(2)?,
1276 row.get::<_, String>(3)?,
1277 ))
1278 })?
1279 .map(|data| -> Result<_> {
1280 let (id, previous, next, chunk_type) = data?;
1281
1282 let num_items = if chunk_type == CHUNK_TYPE_GAP_TYPE_STRING {
1289 0
1290 } else {
1291 num_events_by_chunk_ids.get(&id).copied().unwrap_or(0)
1292 };
1293
1294 Ok(ChunkMetadata {
1295 identifier: ChunkIdentifier::new(id),
1296 previous: previous.map(ChunkIdentifier::new),
1297 next: next.map(ChunkIdentifier::new),
1298 num_items,
1299 })
1300 })
1301 .collect::<Result<Vec<_>, _>>()
1302 })
1303 .await
1304 }
1305
1306 #[instrument(skip(self))]
1307 async fn load_last_chunk(
1308 &self,
1309 linked_chunk_id: LinkedChunkId<'_>,
1310 ) -> Result<(Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator), Self::Error> {
1311 let _timer = timer!("method");
1312
1313 let hashed_linked_chunk_id =
1314 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1315 let encryption = self.encryption.clone();
1316
1317 self
1318 .read()
1319 .await?
1320 .with_transaction(move |txn| -> Result<_> {
1321 let (observed_max_identifier, number_of_chunks) = txn
1323 .prepare(
1324 "SELECT MAX(id), COUNT(*) FROM linked_chunks WHERE linked_chunk_id = ?"
1325 )?
1326 .query_one(
1327 (&hashed_linked_chunk_id,),
1328 |row| {
1329 Ok((
1330 row.get::<_, Option<u64>>(0)?,
1335 row.get::<_, u64>(1)?,
1336 ))
1337 }
1338 )?;
1339
1340 let chunk_identifier_generator = match observed_max_identifier {
1341 Some(max_observed_identifier) => {
1342 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(
1343 ChunkIdentifier::new(max_observed_identifier)
1344 )
1345 },
1346 None => ChunkIdentifierGenerator::new_from_scratch(),
1347 };
1348
1349 let Some((chunk_identifier, previous_chunk, chunk_type)) = txn
1351 .prepare(
1352 "SELECT id, previous, type FROM linked_chunks WHERE linked_chunk_id = ? AND next IS NULL"
1353 )?
1354 .query_one(
1355 (&hashed_linked_chunk_id,),
1356 |row| {
1357 Ok((
1358 row.get::<_, u64>(0)?,
1359 row.get::<_, Option<u64>>(1)?,
1360 row.get::<_, String>(2)?,
1361 ))
1362 }
1363 )
1364 .optional()?
1365 else {
1366 if number_of_chunks == 0 {
1369 return Ok((None, chunk_identifier_generator));
1370 }
1371 else {
1378 return Err(Error::InvalidData {
1379 details:
1380 "last chunk is not found but chunks exist: the linked chunk contains a cycle"
1381 .to_owned()
1382 }
1383 )
1384 }
1385 };
1386
1387 let last_chunk = txn.rebuild_chunk(
1389 &encryption,
1390 &hashed_linked_chunk_id,
1391 previous_chunk,
1392 chunk_identifier,
1393 None,
1394 &chunk_type
1395 )?;
1396
1397 Ok((Some(last_chunk), chunk_identifier_generator))
1398 })
1399 .await
1400 }
1401
1402 #[instrument(skip(self))]
1403 async fn load_previous_chunk(
1404 &self,
1405 linked_chunk_id: LinkedChunkId<'_>,
1406 before_chunk_identifier: ChunkIdentifier,
1407 ) -> Result<Option<RawChunk<Event, Gap>>, Self::Error> {
1408 let _timer = timer!("method");
1409
1410 let hashed_linked_chunk_id =
1411 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1412 let encryption = self.encryption.clone();
1413
1414 self
1415 .read()
1416 .await?
1417 .with_transaction(move |txn| -> Result<_> {
1418 let Some((chunk_identifier, previous_chunk, next_chunk, chunk_type)) = txn
1420 .prepare(
1421 "SELECT id, previous, next, type FROM linked_chunks WHERE linked_chunk_id = ? AND next = ?"
1422 )?
1423 .query_one(
1424 (&hashed_linked_chunk_id, before_chunk_identifier.index()),
1425 |row| {
1426 Ok((
1427 row.get::<_, u64>(0)?,
1428 row.get::<_, Option<u64>>(1)?,
1429 row.get::<_, Option<u64>>(2)?,
1430 row.get::<_, String>(3)?,
1431 ))
1432 }
1433 )
1434 .optional()?
1435 else {
1436 return Ok(None);
1438 };
1439
1440 let last_chunk = txn.rebuild_chunk(
1442 &encryption,
1443 &hashed_linked_chunk_id,
1444 previous_chunk,
1445 chunk_identifier,
1446 next_chunk,
1447 &chunk_type
1448 )?;
1449
1450 Ok(Some(last_chunk))
1451 })
1452 .await
1453 }
1454
1455 async fn load_thread_info(
1456 &self,
1457 room_id: &RoomId,
1458 thread_id: &EventId,
1459 insert_default_if_missing: bool,
1460 ) -> Result<Option<ThreadInfo>, Self::Error> {
1461 let linked_chunk_id = LinkedChunkId::Thread(room_id, thread_id);
1462 let hashed_linked_chunk_id =
1463 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1464 let encryption = self.encryption.clone();
1465
1466 let maybe_thread_info = self
1471 .read()
1472 .await?
1473 .with_transaction(move |txn| {
1474 let maybe_encoded_thread_info = txn
1475 .query_one(
1476 "SELECT info FROM threads WHERE linked_chunk_id = ?",
1477 (hashed_linked_chunk_id,),
1478 |row| row.get::<_, Vec<u8>>(0),
1479 )
1480 .optional()?;
1481
1482 maybe_encoded_thread_info
1483 .map(|encoded_thread_info| {
1484 encryption.decode_thread_info(encoded_thread_info.as_slice())
1485 })
1486 .transpose()
1487 })
1488 .await?;
1489
1490 if let Some(thread_info) = maybe_thread_info {
1491 return Ok(Some(thread_info));
1492 } else if !insert_default_if_missing {
1493 return Ok(None);
1495 }
1496
1497 let thread_info = ThreadInfo::default();
1500 let hashed_linked_chunk_id =
1501 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1502 let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1503 let hashed_thread_id = self.encryption.encode_thread_id(thread_id)?;
1504 let encoded_thread_id = self.encryption.encode_thread_info(&thread_info)?;
1505
1506 self.write()
1507 .await?
1508 .with_transaction(move |txn| {
1509 txn.execute(
1510 "INSERT INTO threads VALUES (?, ?, ?, ?)",
1511 (hashed_linked_chunk_id, hashed_room_id, hashed_thread_id, encoded_thread_id),
1512 )?;
1513
1514 Ok::<(), Self::Error>(())
1515 })
1516 .await?;
1517
1518 Ok(Some(thread_info))
1519 }
1520
1521 async fn update_thread_info(
1522 &self,
1523 room_id: &RoomId,
1524 thread_id: &EventId,
1525 thread_info: &ThreadInfo,
1526 ) -> Result<(), Self::Error> {
1527 let hashed_linked_chunk_id = self
1528 .encryption
1529 .encode_linked_chunk(keys::LINKED_CHUNKS, &LinkedChunkId::Thread(room_id, thread_id));
1530 let encoded_thread_info = self.encryption.encode_thread_info(thread_info)?;
1531
1532 self.write()
1533 .await?
1534 .with_transaction(move |txn| {
1535 txn.execute(
1536 "UPDATE threads SET info = ? WHERE linked_chunk_id = ?",
1537 (encoded_thread_info, hashed_linked_chunk_id),
1538 )?;
1539
1540 Ok(())
1541 })
1542 .await
1543 }
1544
1545 #[instrument(skip(self))]
1546 async fn clear_all_events(&self, room_id: Option<&RoomId>) -> Result<(), Self::Error> {
1547 let _timer = timer!("method");
1548
1549 match room_id {
1550 None => {
1552 self.write()
1553 .await?
1554 .with_transaction(move |txn| {
1555 txn.execute("DELETE FROM linked_chunks", ())?;
1557
1558 txn.execute("DELETE FROM events", ())?;
1561
1562 Ok(())
1563 })
1564 .await
1565 }
1566
1567 Some(room_id) => {
1569 let encryption = self.encryption.clone();
1570 let room_id = room_id.to_owned();
1571
1572 self.write()
1573 .await?
1574 .with_transaction(move |txn| {
1575 {
1577 let mut delete =
1578 txn.prepare("DELETE FROM linked_chunks WHERE linked_chunk_id = ?")?;
1579
1580 for linked_chunk_id in
1581 [LinkedChunkId::Room(&room_id), LinkedChunkId::PinnedEvents(&room_id)]
1582 {
1583 let linked_chunk_id = encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1584
1585 delete.execute((&linked_chunk_id,))?;
1589 }
1590 }
1591
1592 let encoded_room_id = encryption.encode_room_id(keys::EVENTS, &room_id);
1593
1594 {
1596 txn.execute(
1597 "DELETE FROM linked_chunks WHERE linked_chunk_id IN (SELECT linked_chunk_id FROM threads WHERE room_id = ?)",
1598 (&encoded_room_id,),
1599 )?;
1600 }
1601
1602 txn.execute(
1604 "DELETE FROM events WHERE room_id = ?",
1605 (encoded_room_id,),
1606 )?;
1607
1608 Ok(())
1609 })
1610 .await
1611 }
1612 }
1613 }
1614
1615 #[instrument(skip(self, event_ids))]
1616 async fn filter_duplicated_events(
1617 &self,
1618 linked_chunk_id: LinkedChunkId<'_>,
1619 event_ids: Vec<OwnedEventId>,
1620 ) -> Result<Vec<(OwnedEventId, Position)>, Self::Error> {
1621 let _timer = timer!("method");
1622
1623 if event_ids.is_empty() {
1627 return Ok(Vec::new());
1628 }
1629
1630 let hashed_linked_chunk_id =
1632 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1633 let event_ids_and_hashed_event_ids = event_ids
1634 .into_iter()
1635 .map(|event_id| {
1636 let hashed_event_id = self.encryption.encode_event_id(
1637 keys::EVENTS,
1642 &event_id,
1643 );
1644
1645 (event_id, hashed_event_id)
1646 })
1647 .collect::<Vec<_>>();
1648
1649 self.read()
1650 .await?
1651 .with_transaction(move |txn| -> Result<_> {
1652 txn.chunk_large_query_over(
1653 event_ids_and_hashed_event_ids,
1654 None,
1655 move |txn, event_ids_and_hashed_event_ids| {
1656 let query = format!(
1657 "SELECT event_id, chunk_id, position \
1658 FROM event_chunks \
1659 WHERE linked_chunk_id = ? AND event_id IN ({}) \
1660 ORDER BY chunk_id ASC, position ASC",
1661 event_ids_and_hashed_event_ids.host_parameters(),
1662 );
1663
1664 let parameters = params_from_iter(
1665 once(
1667 hashed_linked_chunk_id
1668 .to_sql()
1669 .unwrap(),
1671 )
1672 .chain(
1674 event_ids_and_hashed_event_ids.iter().map(
1675 |(_event_id, hashed_event_id)| {
1676 hashed_event_id
1677 .to_sql()
1678 .unwrap()
1681 },
1682 ),
1683 ),
1684 );
1685
1686 let mut duplicated_events = Vec::new();
1687
1688 for duplicated_event in
1689 txn.prepare(&query)?.query_map(parameters, |row| {
1690 Ok((
1691 row.get::<_, Vec<u8>>(0)?,
1692 row.get::<_, u64>(1)?,
1693 row.get::<_, usize>(2)?,
1694 ))
1695 })?
1696 {
1697 let (duplicated_hashed_event_id, chunk_identifier, index) =
1698 duplicated_event?;
1699
1700 let Some(duplicated_event_id) = event_ids_and_hashed_event_ids
1705 .iter()
1706 .find_map(|(event_id, hashed_event_id)| {
1707 (hashed_event_id.deref() == duplicated_hashed_event_id)
1708 .then_some(event_id.clone())
1709 })
1710 else {
1711 error!(
1712 "Unreachable: found a duplicated event that was not requested"
1713 );
1714 continue;
1715 };
1716
1717 duplicated_events.push((
1718 duplicated_event_id,
1719 Position::new(ChunkIdentifier::new(chunk_identifier), index),
1720 ));
1721 }
1722
1723 Ok(duplicated_events)
1724 },
1725 )
1726 })
1727 .await
1728 }
1729
1730 #[instrument(skip(self, event_id))]
1731 async fn find_event(
1732 &self,
1733 room_id: &RoomId,
1734 event_id: &EventId,
1735 ) -> Result<Option<Event>, Self::Error> {
1736 let _timer = timer!("method");
1737
1738 let encryption = self.encryption.clone();
1739
1740 let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1741 let hashed_event_id = self.encryption.encode_event_id(keys::EVENTS, event_id);
1742
1743 self.read()
1744 .await?
1745 .with_transaction(move |txn| -> Result<_> {
1746 let Some(event) = txn
1747 .prepare("SELECT content FROM events WHERE event_id = ? AND room_id = ?")?
1748 .query_one((hashed_event_id, hashed_room_id), |row| row.get::<_, Vec<u8>>(0))
1749 .optional()?
1750 else {
1751 return Ok(None);
1753 };
1754
1755 Ok(Some(encryption.decode_event(&event)?))
1756 })
1757 .await
1758 }
1759
1760 #[instrument(skip(self, event_id, filters))]
1761 async fn find_event_relations(
1762 &self,
1763 room_id: &RoomId,
1764 event_id: &EventId,
1765 filters: Option<&[RelationType]>,
1766 ) -> Result<Vec<(Event, Option<Position>)>, Self::Error> {
1767 let _timer = timer!("method");
1768
1769 let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1770 let hashed_linked_chunk_id =
1771 self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &LinkedChunkId::Room(room_id));
1772 let hashed_event_id = self.encryption.encode_event_id(keys::EVENTS, event_id);
1773
1774 let filters = filters.map(ToOwned::to_owned);
1775 let encryption = self.encryption.clone();
1776
1777 self.read()
1778 .await?
1779 .with_transaction(move |txn| -> Result<_> {
1780 find_event_relations_transaction(
1781 &encryption,
1782 hashed_room_id,
1783 hashed_linked_chunk_id,
1784 hashed_event_id,
1785 filters,
1786 txn,
1787 )
1788 })
1789 .await
1790 }
1791
1792 #[instrument(skip(self))]
1793 async fn find_events_before_timestamp(
1794 &self,
1795 room_id: &RoomId,
1796 cutoff_ms: u64,
1797 ) -> Result<Vec<Event>, Self::Error> {
1798 let _timer = timer!("method");
1799
1800 let encryption = self.encryption.clone();
1801 let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1802
1803 self.read()
1804 .await?
1805 .with_transaction(move |txn| -> Result<_> {
1806 let mut results = Vec::new();
1807 let mut statement = txn.prepare(
1808 "SELECT content \
1809 FROM events \
1810 WHERE room_id = ? AND timestamp < ? \
1811 ORDER BY timestamp ASC",
1812 )?;
1813 let rows = statement
1814 .query_map((&hashed_room_id, cutoff_ms), |row| row.get::<_, Vec<u8>>(0))?;
1815
1816 for row in rows {
1817 let event = encryption.decode_event(&row?)?;
1818 results.push(event);
1819 }
1820
1821 Ok(results)
1822 })
1823 .await
1824 }
1825
1826 #[instrument(skip(self))]
1827 async fn get_room_events(
1828 &self,
1829 room_id: &RoomId,
1830 event_type: Option<&str>,
1831 session_id: Option<&str>,
1832 ) -> Result<Vec<Event>, Self::Error> {
1833 let _timer = timer!("method");
1834
1835 let encryption = self.encryption.clone();
1836
1837 let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1838 let hashed_event_type = event_type.map(|e| self.encryption.encode_key(keys::EVENTS, e));
1839 let hashed_session_id = session_id.map(|s| self.encryption.encode_key(keys::EVENTS, s));
1840
1841 self.read()
1842 .await?
1843 .with_transaction(move |txn| -> Result<_> {
1844 #[allow(clippy::redundant_clone)]
1849 let (query, keys) = match (hashed_event_type, hashed_session_id) {
1850 (None, None) => {
1851 ("SELECT content FROM events WHERE room_id = ?", params![hashed_room_id])
1852 }
1853 (None, Some(session_id)) => (
1854 "SELECT content FROM events WHERE room_id = ?1 AND session_id = ?2",
1855 params![hashed_room_id, session_id.to_owned()],
1856 ),
1857 (Some(event_type), None) => (
1858 "SELECT content FROM events WHERE room_id = ? AND event_type = ?",
1859 params![hashed_room_id, event_type.to_owned()]
1860 ),
1861 (Some(event_type), Some(session_id)) => (
1862 "SELECT content FROM events WHERE room_id = ?1 AND event_type = ?2 AND session_id = ?3",
1863 params![hashed_room_id, event_type.to_owned(), session_id.to_owned()],
1864 ),
1865 };
1866
1867 let mut statement = txn.prepare(query)?;
1868
1869 statement
1870 .query_map(keys, |row| row.get::<_, Vec<u8>>(0))?
1871 .map(|maybe_encoded_event| {
1872 encryption.decode_event(&maybe_encoded_event?)
1873 })
1874 .collect::<Result<Vec<_>>>()
1875 })
1876 .await
1877 }
1878
1879 #[instrument(skip(self, event))]
1880 async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error> {
1881 let _timer = timer!("method");
1882
1883 let Some(event_id) = event.event_id() else {
1884 error!("Trying to save an event with no ID");
1885 return Ok(());
1886 };
1887
1888 let Some(event_type) = event.kind.event_type() else {
1889 error!(%event_id, "Trying to save an event with no event type");
1890 return Ok(());
1891 };
1892
1893 let hashed_event_type = self.encryption.encode_key(keys::EVENTS, event_type);
1894 let hashed_session_id =
1895 event.kind.session_id().map(|s| self.encryption.encode_key(keys::EVENTS, s));
1896
1897 let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1898 let hashed_event_id = self.encryption.encode_event_id(keys::EVENTS, event_id);
1899 let encoded_event = self.encryption.encode_event(&event)?;
1900
1901 self.write()
1902 .await?
1903 .with_transaction(move |txn| -> Result<_> {
1904 let timestamp = event.timestamp.map(|ts| u64::from(ts.get()));
1905 txn.execute(
1906 "INSERT OR REPLACE INTO events(room_id, event_id, event_type, session_id, content, relates_to, rel_type, timestamp, state_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
1907 (
1908 &hashed_room_id,
1909 hashed_event_id,
1910 hashed_event_type,
1911 hashed_session_id,
1912 encoded_event.content,
1913 encoded_event.relates_to,
1914 encoded_event.rel_type,
1915 timestamp,
1916 encoded_event.state_key,
1917 )
1918 )?;
1919
1920 Ok(())
1921 })
1922 .await
1923 }
1924
1925 async fn close(&self) -> Result<(), Self::Error> {
1926 SqliteEventCacheStore::close(self).await
1927 }
1928
1929 async fn reopen(&self) -> Result<(), Self::Error> {
1930 SqliteEventCacheStore::reopen(self).await
1931 }
1932
1933 async fn optimize(&self) -> Result<(), Self::Error> {
1934 Ok(self.vacuum().await?)
1935 }
1936
1937 async fn get_size(&self) -> Result<Option<usize>, Self::Error> {
1938 self.get_db_size().await
1939 }
1940}
1941
1942fn find_event_relations_transaction(
1943 encryption: &Encryption,
1944 hashed_room_id: Key,
1945 hashed_linked_chunk_id: Key,
1946 hashed_event_id: Key,
1947 filters: Option<Vec<RelationType>>,
1948 txn: &Transaction<'_>,
1949) -> Result<Vec<(Event, Option<Position>)>> {
1950 let get_rows = |row: &rusqlite::Row<'_>| {
1951 Ok((
1952 row.get::<_, Vec<u8>>(0)?,
1953 row.get::<_, Option<u64>>(1)?,
1954 row.get::<_, Option<usize>>(2)?,
1955 ))
1956 };
1957
1958 let collect_results = |transaction| {
1960 let mut related = Vec::new();
1961
1962 for result in transaction {
1963 let (event, chunk_id, index): (Vec<u8>, Option<u64>, _) = result?;
1964 let event = encryption.decode_event(&event)?;
1965
1966 let pos = chunk_id
1970 .zip(index)
1971 .map(|(chunk_id, index)| Position::new(ChunkIdentifier::new(chunk_id), index));
1972
1973 related.push((event, pos));
1974 }
1975
1976 Ok(related)
1977 };
1978
1979 if let Some(filters) = filters
1980 && filters.is_empty().not()
1981 {
1982 let query = format!(
1983 "SELECT events.content, event_chunks.chunk_id, event_chunks.position \
1984 FROM events \
1985 LEFT JOIN event_chunks ON events.event_id = event_chunks.event_id AND event_chunks.linked_chunk_id = ? \
1986 WHERE events.relates_to = ? AND events.room_id = ? AND events.rel_type IN ({})",
1987 host_parameters(filters.len())
1988 );
1989
1990 let filter_strings: Vec<_> = filters.iter().map(|f| f.to_string()).collect();
1995 let filters_params: Vec<_> = filter_strings
1996 .iter()
1997 .map(|f| f.to_sql().expect("converting a string to SQL should work"))
1998 .collect();
1999
2000 let parameters = params_from_iter(
2001 [
2002 hashed_linked_chunk_id.to_sql().expect(
2003 "We should be able to convert a hashed linked chunk ID to a SQLite value",
2004 ),
2005 hashed_event_id
2006 .to_sql()
2007 .expect("We should be able to convert an event ID to a SQLite value"),
2008 hashed_room_id
2009 .to_sql()
2010 .expect("We should be able to convert a room ID to a SQLite value"),
2011 ]
2012 .into_iter()
2013 .chain(filters_params),
2014 );
2015
2016 let mut transaction = txn.prepare(&query)?;
2017 let transaction = transaction.query_map(parameters, get_rows)?;
2018
2019 collect_results(transaction)
2020 } else {
2021 let query = "SELECT events.content, event_chunks.chunk_id, event_chunks.position \
2022 FROM events \
2023 LEFT JOIN event_chunks ON events.event_id = event_chunks.event_id AND event_chunks.linked_chunk_id = ? \
2024 WHERE events.relates_to = ? AND events.room_id = ?";
2025 let parameters = (hashed_linked_chunk_id, hashed_event_id, hashed_room_id);
2026
2027 let mut transaction = txn.prepare(query)?;
2028 let transaction = transaction.query_map(parameters, get_rows)?;
2029
2030 collect_results(transaction)
2031 }
2032}
2033
2034async fn with_immediate_transaction<
2039 T: Send + 'static,
2040 F: FnOnce(&Transaction<'_>) -> Result<T, Error> + Send + 'static,
2041>(
2042 this: &SqliteEventCacheStore,
2043 f: F,
2044) -> Result<T, Error> {
2045 this.write()
2046 .await?
2047 .interact(move |conn| -> Result<T, Error> {
2048 conn.set_transaction_behavior(TransactionBehavior::Immediate);
2053
2054 let code = || -> Result<T, Error> {
2055 let txn = conn.transaction()?;
2056 let res = f(&txn)?;
2057 txn.commit()?;
2058 Ok(res)
2059 };
2060
2061 let res = code();
2062
2063 conn.set_transaction_behavior(TransactionBehavior::Deferred);
2066
2067 res
2068 })
2069 .await
2070 .unwrap()
2072}
2073
2074fn insert_chunk(
2075 txn: &Transaction<'_>,
2076 linked_chunk_id: &Key,
2077 previous: Option<u64>,
2078 new: u64,
2079 next: Option<u64>,
2080 type_str: &str,
2081) -> rusqlite::Result<()> {
2082 txn.execute(
2084 r#"
2085 INSERT INTO linked_chunks(id, linked_chunk_id, previous, next, type)
2086 VALUES (?, ?, ?, ?, ?)
2087 "#,
2088 (new, linked_chunk_id, previous, next, type_str),
2089 )?;
2090
2091 if let Some(previous) = previous {
2093 let updated = txn.execute(
2094 r#"
2095 UPDATE linked_chunks
2096 SET next = ?
2097 WHERE id = ? AND linked_chunk_id = ?
2098 "#,
2099 (new, previous, linked_chunk_id),
2100 )?;
2101 if updated < 1 {
2102 return Err(rusqlite::Error::QueryReturnedNoRows);
2103 }
2104 if updated > 1 {
2105 return Err(rusqlite::Error::QueryReturnedMoreThanOneRow);
2106 }
2107 }
2108
2109 if let Some(next) = next {
2111 let updated = txn.execute(
2112 r#"
2113 UPDATE linked_chunks
2114 SET previous = ?
2115 WHERE id = ? AND linked_chunk_id = ?
2116 "#,
2117 (new, next, linked_chunk_id),
2118 )?;
2119 if updated < 1 {
2120 return Err(rusqlite::Error::QueryReturnedNoRows);
2121 }
2122 if updated > 1 {
2123 return Err(rusqlite::Error::QueryReturnedMoreThanOneRow);
2124 }
2125 }
2126
2127 Ok(())
2128}
2129
2130#[cfg(test)]
2131mod tests {
2132 use std::{
2133 path::PathBuf,
2134 sync::{
2135 LazyLock,
2136 atomic::{AtomicU32, Ordering::SeqCst},
2137 },
2138 };
2139
2140 use assert_matches::assert_matches;
2141 use matrix_sdk_base::{
2142 event_cache::store::{
2143 EventCacheStore, EventCacheStoreError, IntoEventCacheStore,
2144 integration_tests::EventCacheStoreIntegrationTests,
2145 },
2146 event_cache_store_integration_tests, event_cache_store_integration_tests_time,
2147 linked_chunk::{ChunkIdentifier, LinkedChunkId, Update},
2148 };
2149 use matrix_sdk_test::{TestRoom, User, async_test, event_factory::EventFactory};
2150 use ruma::{EventId, OwnedEventId, event_id, room_id};
2151 use tempfile::{TempDir, tempdir};
2152
2153 use super::{SqliteEventCacheStore, keys};
2154 use crate::{
2155 SqliteStoreConfig,
2156 utils::{EncryptableStore, SqliteAsyncConnExt},
2157 };
2158
2159 static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
2160 static NUM: AtomicU32 = AtomicU32::new(0);
2161
2162 fn new_event_cache_store_workspace() -> PathBuf {
2163 let name = NUM.fetch_add(1, SeqCst).to_string();
2164 TMP_DIR.path().join(name)
2165 }
2166
2167 async fn get_event_cache_store() -> Result<SqliteEventCacheStore, EventCacheStoreError> {
2168 let tmpdir_path = new_event_cache_store_workspace();
2169
2170 tracing::info!("using event cache store @ {}", tmpdir_path.to_str().unwrap());
2171
2172 Ok(SqliteEventCacheStore::open(tmpdir_path.to_str().unwrap(), None).await.unwrap())
2173 }
2174
2175 event_cache_store_integration_tests!();
2176 event_cache_store_integration_tests_time!();
2177
2178 #[async_test]
2179 async fn test_encryption_encode_decode_thread_id_roundtrip() {
2180 let store = get_event_cache_store().await.expect("creating cache store failed");
2181 let thread_id: OwnedEventId = event_id!("$event").to_owned();
2182
2183 let encoded_thread_id = store.encryption.encode_thread_id(&thread_id).unwrap();
2184 let decoded_thread_id: OwnedEventId =
2185 store.encryption.decode_thread_id(&encoded_thread_id).unwrap();
2186
2187 assert_eq!(thread_id, decoded_thread_id);
2188 }
2189
2190 #[async_test]
2191 async fn test_state_key_column() {
2192 let store = get_event_cache_store().await.expect("creating cache store failed");
2193
2194 let room_id = room_id!("!test:localhost");
2195 let f = EventFactory::new().room(room_id).sender(User::Alice);
2196
2197 let message_id = event_id!("$message");
2198 let empty_state_key_ev_id = event_id!("$empty_state_key");
2199 let state_key_ev_id = event_id!("$state_key");
2200 let state_key = User::Bob.as_str();
2201
2202 store
2204 .save_event(room_id, f.text_msg("hello").event_id(message_id).into_event())
2205 .await
2206 .unwrap();
2207 store
2209 .save_event(
2210 room_id,
2211 f.room_topic("topic").state_key("").event_id(empty_state_key_ev_id).into_event(),
2212 )
2213 .await
2214 .unwrap();
2215 store
2218 .save_event(room_id, f.member(User::Bob).event_id(state_key_ev_id).into_event())
2219 .await
2220 .unwrap();
2221
2222 let stored_state_key = async |event_id: &EventId| -> Option<Vec<u8>> {
2223 let hashed_event_id = store.encryption.encode_event_id(keys::EVENTS, event_id);
2224 store
2225 .read()
2226 .await
2227 .unwrap()
2228 .with_transaction(move |txn| {
2229 txn.query_one(
2230 "SELECT state_key FROM events WHERE event_id = ?",
2231 (hashed_event_id,),
2232 |row| row.get::<_, Option<Vec<u8>>>(0),
2233 )
2234 })
2235 .await
2236 .unwrap()
2237 };
2238 let encoded_state_key =
2239 |state_key: &str| store.encryption.encode_key(keys::EVENTS, state_key).to_vec();
2240
2241 assert_eq!(stored_state_key(message_id).await, None);
2242 assert_eq!(stored_state_key(empty_state_key_ev_id).await, Some(encoded_state_key("")));
2243 assert_eq!(stored_state_key(state_key_ev_id).await, Some(encoded_state_key(state_key)));
2244 }
2245
2246 #[async_test]
2247 async fn test_pool_size() {
2248 let tmpdir_path = new_event_cache_store_workspace();
2249 let store_open_config = SqliteStoreConfig::new(tmpdir_path).pool_max_size(42);
2250
2251 let store = SqliteEventCacheStore::open_with_config(&store_open_config).await.unwrap();
2252
2253 assert_eq!(store.pool_max_size().await, Some(42));
2254 }
2255
2256 #[async_test]
2257 async fn test_linked_chunk_remove_chunk() {
2258 let store = get_event_cache_store().await.expect("creating cache store failed");
2259
2260 store.clone().into_event_cache_store().test_linked_chunk_remove_chunk().await;
2262
2263 let gaps = store
2265 .read()
2266 .await
2267 .unwrap()
2268 .with_transaction(|txn| -> rusqlite::Result<_> {
2269 let mut gaps = Vec::new();
2270 for data in txn
2271 .prepare("SELECT chunk_id FROM gap_chunks ORDER BY chunk_id")?
2272 .query_map((), |row| row.get::<_, u64>(0))?
2273 {
2274 gaps.push(data?);
2275 }
2276 Ok(gaps)
2277 })
2278 .await
2279 .unwrap();
2280
2281 assert_eq!(gaps, vec![42, 44]);
2284 }
2285
2286 #[async_test]
2287 async fn test_linked_chunk_remove_item() {
2288 let store = get_event_cache_store().await.expect("creating cache store failed");
2289
2290 store.clone().into_event_cache_store().test_linked_chunk_remove_item().await;
2292
2293 let hashed_linked_chunk_id = store.encryption.encode_linked_chunk(
2294 keys::LINKED_CHUNKS,
2295 &LinkedChunkId::Room(TestRoom::Default.into()),
2296 );
2297
2298 let num_rows: u64 = store
2300 .read()
2301 .await
2302 .unwrap()
2303 .with_transaction(move |txn| {
2304 txn.query_one(
2305 "SELECT COUNT(*) FROM event_chunks WHERE chunk_id = 42 AND linked_chunk_id = ? AND position IN (2, 3, 4)",
2306 (hashed_linked_chunk_id,),
2307 |row| row.get(0),
2308 )
2309 })
2310 .await
2311 .unwrap();
2312 assert_eq!(num_rows, 3);
2313 }
2314
2315 #[async_test]
2316 async fn test_linked_chunk_clear() {
2317 let store = get_event_cache_store().await.expect("creating cache store failed");
2318
2319 store.clone().into_event_cache_store().test_linked_chunk_clear().await;
2321
2322 store
2324 .read()
2325 .await
2326 .unwrap()
2327 .with_transaction(|txn| -> rusqlite::Result<_> {
2328 let num_gaps = txn
2329 .prepare("SELECT COUNT(chunk_id) FROM gap_chunks ORDER BY chunk_id")?
2330 .query_one((), |row| row.get::<_, u64>(0))?;
2331 assert_eq!(num_gaps, 0);
2332
2333 let num_events = txn
2334 .prepare("SELECT COUNT(event_id) FROM event_chunks ORDER BY chunk_id")?
2335 .query_one((), |row| row.get::<_, u64>(0))?;
2336 assert_eq!(num_events, 0);
2337
2338 Ok(())
2339 })
2340 .await
2341 .unwrap();
2342 }
2343
2344 #[async_test]
2345 async fn test_linked_chunk_update_is_a_transaction() {
2346 let store = get_event_cache_store().await.expect("creating cache store failed");
2347
2348 let linked_chunk_id = LinkedChunkId::Room(TestRoom::Default.into());
2349
2350 let err = store
2353 .handle_linked_chunk_updates(
2354 linked_chunk_id,
2355 vec![
2356 Update::NewItemsChunk {
2357 previous: None,
2358 new: ChunkIdentifier::new(42),
2359 next: None,
2360 },
2361 Update::NewItemsChunk {
2362 previous: None,
2363 new: ChunkIdentifier::new(42),
2364 next: None,
2365 },
2366 ],
2367 )
2368 .await
2369 .unwrap_err();
2370
2371 assert_matches!(err, crate::error::Error::Sqlite(err) => {
2373 assert_matches!(err.sqlite_error_code(), Some(rusqlite::ErrorCode::ConstraintViolation));
2374 });
2375
2376 let chunks = store.load_all_chunks(linked_chunk_id).await.unwrap();
2380 assert!(chunks.is_empty());
2381 }
2382}
2383
2384#[cfg(test)]
2385mod encrypted_tests {
2386 use std::sync::{
2387 LazyLock,
2388 atomic::{AtomicU32, Ordering::SeqCst},
2389 };
2390
2391 use matrix_sdk_base::{
2392 event_cache::store::{EventCacheStore, EventCacheStoreError},
2393 event_cache_store_integration_tests, event_cache_store_integration_tests_time,
2394 };
2395 use matrix_sdk_test::{async_test, event_factory::EventFactory};
2396 use ruma::{
2397 event_id,
2398 events::{relation::RelationType, room::message::RoomMessageEventContentWithoutRelation},
2399 room_id, user_id,
2400 };
2401 use tempfile::{TempDir, tempdir};
2402
2403 use super::SqliteEventCacheStore;
2404
2405 static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
2406 static NUM: AtomicU32 = AtomicU32::new(0);
2407
2408 async fn get_event_cache_store() -> Result<SqliteEventCacheStore, EventCacheStoreError> {
2409 let name = NUM.fetch_add(1, SeqCst).to_string();
2410 let tmpdir_path = TMP_DIR.path().join(name);
2411
2412 tracing::info!("using event cache store @ {}", tmpdir_path.to_str().unwrap());
2413
2414 Ok(SqliteEventCacheStore::open(
2415 tmpdir_path.to_str().unwrap(),
2416 Some("default_test_password"),
2417 )
2418 .await
2419 .unwrap())
2420 }
2421
2422 event_cache_store_integration_tests!();
2423 event_cache_store_integration_tests_time!();
2424
2425 #[async_test]
2426 async fn test_no_sqlite_injection_in_find_event_relations() {
2427 let room_id = room_id!("!test:localhost");
2428 let another_room_id = room_id!("!r1:matrix.org");
2429 let sender = user_id!("@alice:localhost");
2430
2431 let store = get_event_cache_store()
2432 .await
2433 .expect("We should be able to create a new, empty, event cache store");
2434
2435 let f = EventFactory::new().room(room_id).sender(sender);
2436
2437 let event_id = event_id!("$DO_NOT_FIND_ME:matrix.org");
2439 let event = f.text_msg("DO NOT FIND").event_id(event_id).into_event();
2440
2441 let edit_id = event_id!("$find_me:matrix.org");
2443 let edit = f
2444 .text_msg("Find me")
2445 .event_id(edit_id)
2446 .edit(event_id, RoomMessageEventContentWithoutRelation::text_plain("jebote"))
2447 .into_event();
2448
2449 let f = f.room(another_room_id);
2451
2452 let another_event_id = event_id!("$DO_NOT_FIND_ME_EITHER:matrix.org");
2453 let another_event =
2454 f.text_msg("DO NOT FIND ME EITHER").event_id(another_event_id).into_event();
2455
2456 store.save_event(room_id, event).await.unwrap();
2458 store.save_event(room_id, edit).await.unwrap();
2459 store.save_event(another_room_id, another_event).await.unwrap();
2460
2461 let filter = Some(vec![RelationType::Replacement, "x\") OR 1=1; --".into()]);
2465
2466 let results = store
2468 .find_event_relations(room_id, event_id, filter.as_deref())
2469 .await
2470 .expect("We should be able to attempt to find event relations");
2471
2472 similar_asserts::assert_eq!(
2475 results.len(),
2476 1,
2477 "We should only have loaded events for the first room {results:#?}"
2478 );
2479
2480 let (found_event, _) = &results[0];
2482 assert_eq!(
2483 found_event.event_id(),
2484 Some(edit_id),
2485 "The single event we found should be the edit event"
2486 );
2487 }
2488}
2489
2490#[cfg(test)]
2491mod close_reopen_tests {
2492 use std::sync::{
2493 LazyLock,
2494 atomic::{AtomicU32, Ordering::SeqCst},
2495 };
2496
2497 use matrix_sdk_base::{event_cache::store::EventCacheStore, linked_chunk::LinkedChunkId};
2498 use matrix_sdk_test::{TestRoom, async_test};
2499 use tempfile::{TempDir, tempdir};
2500
2501 use super::SqliteEventCacheStore;
2502
2503 static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
2504 static NUM: AtomicU32 = AtomicU32::new(0);
2505
2506 async fn new_store() -> SqliteEventCacheStore {
2507 let name = NUM.fetch_add(1, SeqCst).to_string();
2508 let tmpdir_path = TMP_DIR.path().join(name);
2509 SqliteEventCacheStore::open(tmpdir_path, None).await.unwrap()
2510 }
2511
2512 #[async_test]
2513 async fn test_close_completes_without_timeout() {
2514 let store = new_store().await;
2515
2516 let start = std::time::Instant::now();
2518 store.close().await.unwrap();
2519 let elapsed = start.elapsed();
2520
2521 assert!(
2522 elapsed < std::time::Duration::from_secs(2),
2523 "close() took {elapsed:?}, expected < 2s (no timeout)"
2524 );
2525
2526 let guard = store.connections.lock().await;
2528 assert!(guard.is_none(), "connections should be None after close");
2529 }
2530
2531 #[async_test]
2532 async fn test_reopen_restores_connections() {
2533 let store = new_store().await;
2534
2535 store.close().await.unwrap();
2536
2537 {
2538 let guard = store.connections.lock().await;
2539 assert!(guard.is_none());
2540 }
2541
2542 store.reopen().await.unwrap();
2543
2544 {
2545 let guard = store.connections.lock().await;
2546 assert!(guard.is_some(), "connections should be Some after reopen");
2547 }
2548 }
2549
2550 #[async_test]
2551 async fn test_close_is_idempotent() {
2552 let store = new_store().await;
2553
2554 store.close().await.unwrap();
2555 store.close().await.unwrap();
2557
2558 let guard = store.connections.lock().await;
2559 assert!(guard.is_none());
2560 }
2561
2562 #[async_test]
2563 async fn test_reopen_is_idempotent() {
2564 let store = new_store().await;
2565
2566 store.reopen().await.unwrap();
2568
2569 let guard = store.connections.lock().await;
2570 assert!(guard.is_some());
2571 }
2572
2573 #[async_test]
2574 async fn test_read_fails_when_closed() {
2575 let store = new_store().await;
2576 store.close().await.unwrap();
2577
2578 let err = store.load_all_chunks(LinkedChunkId::Room(TestRoom::Default.into())).await;
2579 assert!(err.is_err(), "read should fail when closed");
2580
2581 let err_msg = err.unwrap_err().to_string();
2582 assert!(err_msg.contains("closed"), "error should mention 'closed', got: {err_msg}");
2583 }
2584
2585 #[async_test]
2586 async fn test_write_fails_when_closed() {
2587 let store = new_store().await;
2588 store.close().await.unwrap();
2589
2590 let err = store.try_take_leased_lock(1000, "test_lock", "holder").await;
2591 assert!(err.is_err(), "write should fail when closed");
2592
2593 let err_msg = err.unwrap_err().to_string();
2594 assert!(err_msg.contains("closed"), "error should mention 'closed', got: {err_msg}");
2595 }
2596
2597 #[async_test]
2598 async fn test_data_persists_across_close_reopen() {
2599 let store = new_store().await;
2600
2601 let result = store.try_take_leased_lock(60_000, "test_lock", "holder").await.unwrap();
2603 assert!(result.is_some(), "should have acquired the lock");
2604
2605 store.close().await.unwrap();
2607 store.reopen().await.unwrap();
2608
2609 let result = store.try_take_leased_lock(60_000, "test_lock", "other_holder").await.unwrap();
2611 assert!(result.is_none(), "lock should still be held by the original holder after reopen");
2612 }
2613
2614 #[async_test]
2615 async fn test_multiple_close_reopen_cycles() {
2616 let store = new_store().await;
2617
2618 for _ in 0..5 {
2619 store.close().await.unwrap();
2620 store.reopen().await.unwrap();
2621
2622 let result = store.load_all_chunks(LinkedChunkId::Room(TestRoom::Default.into())).await;
2624 assert!(result.is_ok(), "store should work after close/reopen cycle");
2625 }
2626 }
2627
2628 #[async_test]
2629 async fn test_pool_is_fully_drained_after_close() {
2630 let store = new_store().await;
2631
2632 let _ = store.load_all_chunks(LinkedChunkId::Room(TestRoom::Default.into())).await;
2634 let _ = store.load_all_chunks(LinkedChunkId::Room(TestRoom::Default.into())).await;
2635
2636 store.close().await.unwrap();
2637
2638 let guard = store.connections.lock().await;
2641 assert!(guard.is_none(), "all connections should be released after close");
2642 }
2643
2644 #[async_test]
2645 async fn test_operations_work_immediately_after_reopen() {
2646 let store = new_store().await;
2647
2648 store.close().await.unwrap();
2649 store.reopen().await.unwrap();
2650
2651 let result = store.load_all_chunks(LinkedChunkId::Room(TestRoom::Default.into())).await;
2653 assert!(result.is_ok(), "read should succeed immediately after reopen");
2654
2655 let result = store.try_take_leased_lock(1000, "test_lock", "holder").await;
2657 assert!(result.is_ok(), "write should succeed immediately after reopen");
2658 }
2659
2660 #[async_test]
2661 async fn test_close_waits_for_held_read_connection_to_drain() {
2662 let store = new_store().await;
2663
2664 let held_conn = store.read().await.unwrap();
2666
2667 let store_clone = store.clone();
2670 let close_handle = tokio::spawn(async move {
2671 store_clone.close().await.unwrap();
2672 });
2673
2674 tokio::time::sleep(std::time::Duration::from_millis(200)).await;
2676
2677 assert!(!close_handle.is_finished(), "close should be waiting for the held connection");
2679
2680 drop(held_conn);
2682
2683 let timeout = tokio::time::timeout(std::time::Duration::from_secs(3), close_handle).await;
2685 assert!(timeout.is_ok(), "close should complete after the held connection is released");
2686 timeout.unwrap().unwrap();
2687
2688 let guard = store.connections.lock().await;
2690 assert!(guard.is_none(), "connections should be None after close");
2691 }
2692}