Skip to main content

matrix_sdk_sqlite/
state_store.rs

1use std::{
2    borrow::Cow,
3    collections::{BTreeMap, BTreeSet, HashMap},
4    fmt, iter,
5    path::{Path, PathBuf},
6    str::FromStr as _,
7    sync::Arc,
8};
9
10use async_trait::async_trait;
11use deadpool::managed::PoolConfig;
12use matrix_sdk_base::{
13    MinimalRoomMemberEvent, ROOM_VERSION_FALLBACK, ROOM_VERSION_RULES_FALLBACK, RoomInfo,
14    RoomMemberships, RoomState, StateChanges, StateStore, StateStoreDataKey, StateStoreDataValue,
15    deserialized_responses::{DisplayName, RawAnySyncOrStrippedState, SyncOrStrippedState},
16    store::{
17        ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind, QueueWedgeError,
18        QueuedRequest, QueuedRequestKind, RoomLoadSettings, SentRequestKey,
19        StoredThreadSubscription, ThreadSubscriptionStatus, migration_helpers::RoomInfoV1,
20    },
21};
22use matrix_sdk_store_encryption::StoreCipher;
23use ruma::{
24    CanonicalJsonObject, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId,
25    OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UInt, UserId,
26    canonical_json::{RedactedBecause, redact},
27    events::{
28        AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent,
29        GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
30        presence::PresenceEvent,
31        receipt::{Receipt, ReceiptThread, ReceiptType},
32        room::{
33            create::RoomCreateEventContent,
34            member::{StrippedRoomMemberEvent, SyncRoomMemberEvent},
35        },
36    },
37    profile::UserProfile,
38    serde::Raw,
39};
40use rusqlite::{OptionalExtension, Transaction};
41use serde::{Deserialize, Serialize};
42use tokio::{
43    fs,
44    sync::{Mutex, OwnedMutexGuard},
45};
46use tracing::{debug, instrument, warn};
47
48use crate::{
49    OpenStoreError, RuntimeConfig, Secret, SqliteStoreConfig,
50    connection::{self, Connection as SqliteAsyncConn, Pool as SqlitePool, SqliteConnections},
51    error::{Error, Result},
52    utils::{
53        EncryptableStore, Key, SqliteAsyncConnExt, SqliteKeyValueStoreAsyncConnExt,
54        SqliteKeyValueStoreConnExt, repeat_vars,
55    },
56};
57
58mod keys {
59    // Tables
60    pub const KV_BLOB: &str = "kv_blob";
61    pub const ROOM_INFO: &str = "room_info";
62    pub const STATE_EVENT: &str = "state_event";
63    pub const GLOBAL_ACCOUNT_DATA: &str = "global_account_data";
64    pub const ROOM_ACCOUNT_DATA: &str = "room_account_data";
65    pub const MEMBER: &str = "member";
66    pub const PROFILE: &str = "profile";
67    pub const RECEIPT: &str = "receipt";
68    pub const DISPLAY_NAME: &str = "display_name";
69    pub const SEND_QUEUE: &str = "send_queue_events";
70    pub const DEPENDENTS_SEND_QUEUE: &str = "dependent_send_queue_events";
71    pub const THREAD_SUBSCRIPTIONS: &str = "thread_subscriptions";
72    pub const GLOBAL_PROFILES: &str = "global_profiles";
73}
74
75/// The filename used for the SQLITE database file used by the state store.
76pub const DATABASE_NAME: &str = "matrix-sdk-state.sqlite3";
77
78/// An SQLite-based state store.
79#[derive(Clone)]
80pub struct SqliteStateStore {
81    store_cipher: Option<Arc<StoreCipher>>,
82
83    /// `Some` when active, `None` when closed.
84    connections: Arc<Mutex<Option<SqliteConnections>>>,
85
86    /// Retained so we can rebuild the pool on reopen.
87    db_path: PathBuf,
88
89    /// Retained so we can rebuild the pool on reopen.
90    pool_config: PoolConfig,
91
92    /// Retained so we can re-apply runtime config on reopen.
93    runtime_config: RuntimeConfig,
94}
95
96#[cfg(not(tarpaulin_include))]
97impl fmt::Debug for SqliteStateStore {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        f.debug_struct("SqliteStateStore").finish_non_exhaustive()
100    }
101}
102
103impl SqliteStateStore {
104    /// Open the SQLite-based state store at the given path using the given
105    /// given passphrase to encrypt private data.
106    pub async fn open(
107        path: impl AsRef<Path>,
108        passphrase: Option<&str>,
109    ) -> Result<Self, OpenStoreError> {
110        Self::open_with_config(&SqliteStoreConfig::new(path).passphrase(passphrase)).await
111    }
112
113    /// Open the SQLite-based state store at the given path using the given
114    /// key to encrypt private data.
115    pub async fn open_with_key(
116        path: impl AsRef<Path>,
117        key: Option<&[u8; 32]>,
118    ) -> Result<Self, OpenStoreError> {
119        Self::open_with_config(&SqliteStoreConfig::new(path).key(key)).await
120    }
121
122    /// Open the SQLite-based state store with the config open config.
123    pub async fn open_with_config(config: &SqliteStoreConfig) -> Result<Self, OpenStoreError> {
124        fs::create_dir_all(&config.path).await.map_err(OpenStoreError::CreateDir)?;
125
126        let pool = config.build_pool_of_connections(DATABASE_NAME)?;
127        let pool_config = config.pool_config;
128        let runtime_config = config.runtime_config;
129
130        let this =
131            Self::open_with_pool(pool, config.secret.clone(), pool_config, runtime_config).await?;
132        this.read().await?.apply_runtime_config(runtime_config).await?;
133
134        Ok(this)
135    }
136
137    /// Create an SQLite-based state store using the given SQLite database pool.
138    /// The given secret will be used to encrypt private data.
139    pub(crate) async fn open_with_pool(
140        pool: SqlitePool,
141        secret: Option<Secret>,
142        pool_config: PoolConfig,
143        runtime_config: RuntimeConfig,
144    ) -> Result<Self, OpenStoreError> {
145        let db_path = pool.manager().database_path.clone();
146        let conn = pool.get().await?;
147
148        let mut version = conn.db_version().await?;
149
150        if version == 0 {
151            init(&conn).await?;
152            version = 1;
153        }
154
155        let store_cipher = match secret {
156            Some(s) => Some(Arc::new(conn.get_or_create_store_cipher(s).await?)),
157            None => None,
158        };
159        let this = Self {
160            store_cipher,
161            connections: Arc::new(Mutex::new(Some(SqliteConnections {
162                pool,
163                write_connection: Arc::new(Mutex::new(conn)),
164            }))),
165            db_path,
166            pool_config,
167            runtime_config,
168        };
169        this.run_migrations(version, None).await?;
170
171        this.read().await?.wal_checkpoint().await;
172
173        Ok(this)
174    }
175
176    /// Run database migrations from the given `from` version to the given `to`
177    /// version
178    ///
179    /// If `to` is `None`, the current database version will be used.
180    async fn run_migrations(&self, from: u8, to: Option<u8>) -> Result<()> {
181        if to == Some(1) {
182            return Ok(());
183        }
184
185        let conn = self.write().await?;
186
187        if from < 2 {
188            debug!("Upgrading database to version 2");
189            let this = self.clone();
190            conn.with_transaction(move |txn| {
191                // Create new table.
192                txn.execute_batch(include_str!(
193                    "../migrations/state_store/002_a_create_new_room_info.sql"
194                ))?;
195
196                // Migrate data to new table.
197                for data in txn
198                    .prepare("SELECT data FROM room_info")?
199                    .query_map((), |row| row.get::<_, Vec<u8>>(0))?
200                {
201                    let data = data?;
202                    let room_info: RoomInfoV1 = this.deserialize_json(&data)?;
203
204                    let room_id = this.encode_key(keys::ROOM_INFO, room_info.room_id());
205                    let state = this
206                        .encode_key(keys::ROOM_INFO, serde_json::to_string(&room_info.state())?);
207                    txn.prepare_cached(
208                        "INSERT OR REPLACE INTO new_room_info (room_id, state, data)
209                         VALUES (?, ?, ?)",
210                    )?
211                    .execute((room_id, state, data))?;
212                }
213
214                // Replace old table.
215                txn.execute_batch(include_str!(
216                    "../migrations/state_store/002_b_replace_room_info.sql"
217                ))?;
218
219                txn.set_db_version(2)?;
220                Result::<_, Error>::Ok(())
221            })
222            .await?;
223        }
224
225        if to == Some(2) {
226            return Ok(());
227        }
228
229        // Migration to v3: RoomInfo format has changed.
230        if from < 3 {
231            debug!("Upgrading database to version 3");
232            let this = self.clone();
233            conn.with_transaction(move |txn| {
234                // Migrate data .
235                for data in txn
236                    .prepare("SELECT data FROM room_info")?
237                    .query_map((), |row| row.get::<_, Vec<u8>>(0))?
238                {
239                    let data = data?;
240                    let room_info_v1: RoomInfoV1 = this.deserialize_json(&data)?;
241
242                    // Get the `m.room.create` event from the room state.
243                    let room_id = this.encode_key(keys::STATE_EVENT, room_info_v1.room_id());
244                    let event_type =
245                        this.encode_key(keys::STATE_EVENT, StateEventType::RoomCreate.to_string());
246                    let create_res = txn
247                        .prepare(
248                            "SELECT stripped, data FROM state_event
249                             WHERE room_id = ? AND event_type = ?",
250                        )?
251                        .query_row([room_id, event_type], |row| {
252                            Ok((row.get::<_, bool>(0)?, row.get::<_, Vec<u8>>(1)?))
253                        })
254                        .optional()?;
255
256                    let create = create_res.and_then(|(stripped, data)| {
257                        let create = if stripped {
258                            SyncOrStrippedState::<RoomCreateEventContent>::Stripped(
259                                this.deserialize_json(&data).ok()?,
260                            )
261                        } else {
262                            SyncOrStrippedState::Sync(this.deserialize_json(&data).ok()?)
263                        };
264                        Some(create)
265                    });
266
267                    let migrated_room_info = room_info_v1.migrate(create.as_ref());
268
269                    let data = this.serialize_json(&migrated_room_info)?;
270                    let room_id = this.encode_key(keys::ROOM_INFO, migrated_room_info.room_id());
271                    txn.prepare_cached("UPDATE room_info SET data = ? WHERE room_id = ?")?
272                        .execute((data, room_id))?;
273                }
274
275                txn.set_db_version(3)?;
276                Result::<_, Error>::Ok(())
277            })
278            .await?;
279        }
280
281        if to == Some(3) {
282            return Ok(());
283        }
284
285        if from < 4 {
286            debug!("Upgrading database to version 4");
287            conn.with_transaction(move |txn| {
288                // Create new table.
289                txn.execute_batch(include_str!("../migrations/state_store/003_send_queue.sql"))?;
290                txn.set_db_version(4)
291            })
292            .await?;
293        }
294
295        if to == Some(4) {
296            return Ok(());
297        }
298
299        if from < 5 {
300            debug!("Upgrading database to version 5");
301            conn.with_transaction(move |txn| {
302                // Create new table.
303                txn.execute_batch(include_str!(
304                    "../migrations/state_store/004_send_queue_with_roomid_value.sql"
305                ))?;
306                txn.set_db_version(4)
307            })
308            .await?;
309        }
310
311        if to == Some(5) {
312            return Ok(());
313        }
314
315        if from < 6 {
316            debug!("Upgrading database to version 6");
317            conn.with_transaction(move |txn| {
318                // Create new table.
319                txn.execute_batch(include_str!(
320                    "../migrations/state_store/005_send_queue_dependent_events.sql"
321                ))?;
322                txn.set_db_version(6)
323            })
324            .await?;
325        }
326
327        if to == Some(6) {
328            return Ok(());
329        }
330
331        if from < 7 {
332            debug!("Upgrading database to version 7");
333            conn.with_transaction(move |txn| {
334                // Drop media table.
335                txn.execute_batch(include_str!("../migrations/state_store/006_drop_media.sql"))?;
336                txn.set_db_version(7)
337            })
338            .await?;
339        }
340
341        if to == Some(7) {
342            return Ok(());
343        }
344
345        if from < 8 {
346            debug!("Upgrading database to version 8");
347            // Replace all existing wedged events with a generic error.
348            let error = QueueWedgeError::GenericApiError {
349                msg: "local echo failed to send in a previous session".into(),
350            };
351            let default_err = self.serialize_value(&error)?;
352
353            conn.with_transaction(move |txn| {
354                // Update send queue table to persist the wedge reason if any.
355                txn.execute_batch(include_str!("../migrations/state_store/007_a_send_queue_wedge_reason.sql"))?;
356
357                // Migrate the data, add a generic error for currently wedged events
358
359                for wedged_entries in txn
360                    .prepare("SELECT room_id, transaction_id FROM send_queue_events WHERE wedged = 1")?
361                    .query_map((), |row| {
362                        Ok(
363                            (row.get::<_, Vec<u8>>(0)?,row.get::<_, String>(1)?)
364                        )
365                    })? {
366
367                    let (room_id, transaction_id) = wedged_entries?;
368
369                    txn.prepare_cached("UPDATE send_queue_events SET wedge_reason = ? WHERE room_id = ? AND transaction_id = ?")?
370                        .execute((default_err.clone(), room_id, transaction_id))?;
371                }
372
373
374                // Clean up the table now that data is migrated
375                txn.execute_batch(include_str!("../migrations/state_store/007_b_send_queue_clean.sql"))?;
376
377                txn.set_db_version(8)
378            })
379                .await?;
380        }
381
382        if to == Some(8) {
383            return Ok(());
384        }
385
386        if from < 9 {
387            debug!("Upgrading database to version 9");
388            conn.with_transaction(move |txn| {
389                // Run the migration.
390                txn.execute_batch(include_str!("../migrations/state_store/008_send_queue.sql"))?;
391                txn.set_db_version(9)
392            })
393            .await?;
394        }
395
396        if to == Some(9) {
397            return Ok(());
398        }
399
400        if from < 10 {
401            debug!("Upgrading database to version 10");
402            conn.with_transaction(move |txn| {
403                // Run the migration.
404                txn.execute_batch(include_str!(
405                    "../migrations/state_store/009_send_queue_priority.sql"
406                ))?;
407                txn.set_db_version(10)
408            })
409            .await?;
410        }
411
412        if to == Some(10) {
413            return Ok(());
414        }
415
416        if from < 11 {
417            debug!("Upgrading database to version 11");
418            conn.with_transaction(move |txn| {
419                // Run the migration.
420                txn.execute_batch(include_str!(
421                    "../migrations/state_store/010_send_queue_enqueue_time.sql"
422                ))?;
423                txn.set_db_version(11)
424            })
425            .await?;
426        }
427
428        if to == Some(11) {
429            return Ok(());
430        }
431
432        if from < 12 {
433            debug!("Upgrading database to version 12");
434            // Defragment the DB and optimize its size on the filesystem.
435            // This should have been run in the migration for version 7, to reduce the size
436            // of the DB as we removed the media cache.
437            conn.vacuum().await?;
438            conn.set_kv("version", vec![12]).await?;
439        }
440
441        if to == Some(12) {
442            return Ok(());
443        }
444
445        if from < 13 {
446            debug!("Upgrading database to version 13");
447            conn.with_transaction(move |txn| {
448                // Run the migration.
449                txn.execute_batch(include_str!(
450                    "../migrations/state_store/011_thread_subscriptions.sql"
451                ))?;
452                txn.set_db_version(13)
453            })
454            .await?;
455        }
456
457        if to == Some(13) {
458            return Ok(());
459        }
460
461        if from < 14 {
462            debug!("Upgrading database to version 14");
463            conn.with_transaction(move |txn| {
464                // Run the migration.
465                txn.execute_batch(include_str!(
466                    "../migrations/state_store/012_thread_subscriptions_bumpstamp.sql"
467                ))?;
468                txn.set_db_version(14)
469            })
470            .await?;
471        }
472
473        if to == Some(14) {
474            return Ok(());
475        }
476
477        if from < 15 {
478            debug!("Upgrading database to version 15");
479            conn.with_transaction(move |txn| {
480                // Run the migration.
481                txn.execute_batch(include_str!(
482                    "../migrations/state_store/013_send_queue_new_parent_key_format.sql"
483                ))?;
484                txn.set_db_version(15)
485            })
486            .await?;
487        }
488
489        if to == Some(15) {
490            return Ok(());
491        }
492
493        if from < 16 {
494            debug!("Upgrading database to version 16");
495            conn.with_transaction(move |txn| {
496                // Run the migration.
497                txn.execute_batch(include_str!(
498                    "../migrations/state_store/014_global_profiles.sql"
499                ))?;
500                txn.set_db_version(16)
501            })
502            .await?;
503        }
504
505        if to == Some(16) {
506            return Ok(());
507        }
508
509        Ok(())
510    }
511
512    fn encode_state_store_data_key(&self, key: StateStoreDataKey<'_>) -> Key {
513        let key_s = match key {
514            StateStoreDataKey::SyncToken => Cow::Borrowed(StateStoreDataKey::SYNC_TOKEN),
515            StateStoreDataKey::SupportedVersions => {
516                Cow::Borrowed(StateStoreDataKey::SUPPORTED_VERSIONS)
517            }
518            StateStoreDataKey::WellKnown => Cow::Borrowed(StateStoreDataKey::WELL_KNOWN),
519            StateStoreDataKey::Filter(f) => {
520                Cow::Owned(format!("{}:{f}", StateStoreDataKey::FILTER))
521            }
522            StateStoreDataKey::UserAvatarUrl(u) => {
523                Cow::Owned(format!("{}:{u}", StateStoreDataKey::USER_AVATAR_URL))
524            }
525            StateStoreDataKey::RecentlyVisitedRooms(b) => {
526                Cow::Owned(format!("{}:{b}", StateStoreDataKey::RECENTLY_VISITED_ROOMS))
527            }
528            StateStoreDataKey::UtdHookManagerData => {
529                Cow::Borrowed(StateStoreDataKey::UTD_HOOK_MANAGER_DATA)
530            }
531            StateStoreDataKey::OneTimeKeyAlreadyUploaded => {
532                Cow::Borrowed(StateStoreDataKey::ONE_TIME_KEY_ALREADY_UPLOADED)
533            }
534            StateStoreDataKey::ComposerDraft(room_id, thread_root) => {
535                if let Some(thread_root) = thread_root {
536                    Cow::Owned(format!(
537                        "{}:{room_id}:{thread_root}",
538                        StateStoreDataKey::COMPOSER_DRAFT
539                    ))
540                } else {
541                    Cow::Owned(format!("{}:{room_id}", StateStoreDataKey::COMPOSER_DRAFT))
542                }
543            }
544            StateStoreDataKey::SeenKnockRequests(room_id) => {
545                Cow::Owned(format!("{}:{room_id}", StateStoreDataKey::SEEN_KNOCK_REQUESTS))
546            }
547            StateStoreDataKey::ThreadSubscriptionsCatchupTokens => {
548                Cow::Borrowed(StateStoreDataKey::THREAD_SUBSCRIPTIONS_CATCHUP_TOKENS)
549            }
550            StateStoreDataKey::HomeserverCapabilities => {
551                Cow::Borrowed(StateStoreDataKey::HOMESERVER_CAPABILITIES)
552            }
553        };
554
555        self.encode_key(keys::KV_BLOB, &*key_s)
556    }
557
558    fn encode_presence_key(&self, user_id: &UserId) -> Key {
559        self.encode_key(keys::KV_BLOB, format!("presence:{user_id}"))
560    }
561
562    fn encode_custom_key(&self, key: &[u8]) -> Key {
563        let mut full_key = b"custom:".to_vec();
564        full_key.extend(key);
565        self.encode_key(keys::KV_BLOB, full_key)
566    }
567
568    /// Acquire a connection for executing read operations.
569    /// Returns `StoreClosed` if closed.
570    #[instrument(skip_all)]
571    async fn read(&self) -> Result<SqliteAsyncConn> {
572        let pool = {
573            let guard = self.connections.lock().await;
574            let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
575            conns.pool.clone()
576        };
577        Ok(pool.get().await?)
578    }
579
580    /// Acquire a connection for executing write operations.
581    /// Returns `StoreClosed` if closed.
582    #[instrument(skip_all)]
583    async fn write(&self) -> Result<OwnedMutexGuard<SqliteAsyncConn>> {
584        let write_conn = {
585            let guard = self.connections.lock().await;
586            let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
587            conns.write_connection.clone()
588        };
589        Ok(write_conn.lock_owned().await)
590    }
591
592    fn remove_maybe_stripped_room_data(
593        &self,
594        txn: &Transaction<'_>,
595        room_id: &RoomId,
596        stripped: bool,
597    ) -> rusqlite::Result<()> {
598        let state_event_room_id = self.encode_key(keys::STATE_EVENT, room_id);
599        txn.remove_room_state_events(&state_event_room_id, Some(stripped))?;
600
601        let member_room_id = self.encode_key(keys::MEMBER, room_id);
602        txn.remove_room_members(&member_room_id, Some(stripped))
603    }
604
605    pub async fn vacuum(&self) -> Result<()> {
606        self.write().await?.vacuum().await
607    }
608
609    pub async fn get_db_size(&self) -> Result<Option<usize>> {
610        let read_conn = self.read().await?;
611        Ok(Some(read_conn.get_db_size().await?))
612    }
613}
614
615impl EncryptableStore for SqliteStateStore {
616    fn get_cypher(&self) -> Option<&StoreCipher> {
617        self.store_cipher.as_deref()
618    }
619}
620
621/// Initialize the database.
622async fn init(conn: &SqliteAsyncConn) -> Result<()> {
623    // First turn on WAL mode, this can't be done in the transaction, it fails with
624    // the error message: "cannot change into wal mode from within a transaction".
625    conn.execute_batch("PRAGMA journal_mode = wal;").await?;
626    conn.with_transaction(|txn| {
627        txn.execute_batch(include_str!("../migrations/state_store/001_init.sql"))?;
628        txn.set_db_version(1)?;
629
630        Ok(())
631    })
632    .await
633}
634
635trait SqliteConnectionStateStoreExt {
636    fn set_kv_blob(&self, key: &[u8], value: &[u8]) -> rusqlite::Result<()>;
637
638    fn set_global_account_data(&self, event_type: &[u8], data: &[u8]) -> rusqlite::Result<()>;
639
640    fn set_room_account_data(
641        &self,
642        room_id: &[u8],
643        event_type: &[u8],
644        data: &[u8],
645    ) -> rusqlite::Result<()>;
646    fn remove_room_account_data(&self, room_id: &[u8]) -> rusqlite::Result<()>;
647
648    fn set_room_info(&self, room_id: &[u8], state: &[u8], data: &[u8]) -> rusqlite::Result<()>;
649    fn get_room_info(&self, room_id: &[u8]) -> rusqlite::Result<Option<Vec<u8>>>;
650    fn remove_room_info(&self, room_id: &[u8]) -> rusqlite::Result<()>;
651
652    fn set_state_event(
653        &self,
654        room_id: &[u8],
655        event_type: &[u8],
656        state_key: &[u8],
657        stripped: bool,
658        event_id: Option<&[u8]>,
659        data: &[u8],
660    ) -> rusqlite::Result<()>;
661    fn get_state_event_by_id(
662        &self,
663        room_id: &[u8],
664        event_id: &[u8],
665    ) -> rusqlite::Result<Option<Vec<u8>>>;
666    fn remove_room_state_events(
667        &self,
668        room_id: &[u8],
669        stripped: Option<bool>,
670    ) -> rusqlite::Result<()>;
671
672    fn set_member(
673        &self,
674        room_id: &[u8],
675        user_id: &[u8],
676        membership: &[u8],
677        stripped: bool,
678        data: &[u8],
679    ) -> rusqlite::Result<()>;
680    fn remove_room_members(&self, room_id: &[u8], stripped: Option<bool>) -> rusqlite::Result<()>;
681
682    fn set_profile(&self, room_id: &[u8], user_id: &[u8], data: &[u8]) -> rusqlite::Result<()>;
683    fn remove_room_profiles(&self, room_id: &[u8]) -> rusqlite::Result<()>;
684    fn remove_room_profile(&self, room_id: &[u8], user_id: &[u8]) -> rusqlite::Result<()>;
685
686    fn set_receipt(
687        &self,
688        room_id: &[u8],
689        user_id: &[u8],
690        receipt_type: &[u8],
691        thread_id: &[u8],
692        event_id: &[u8],
693        data: &[u8],
694    ) -> rusqlite::Result<()>;
695    fn remove_room_receipts(&self, room_id: &[u8]) -> rusqlite::Result<()>;
696
697    fn set_display_name(&self, room_id: &[u8], name: &[u8], data: &[u8]) -> rusqlite::Result<()>;
698    fn remove_display_name(&self, room_id: &[u8], name: &[u8]) -> rusqlite::Result<()>;
699    fn remove_room_display_names(&self, room_id: &[u8]) -> rusqlite::Result<()>;
700    fn remove_room_send_queue(&self, room_id: &[u8]) -> rusqlite::Result<()>;
701    fn remove_room_dependent_send_queue(&self, room_id: &[u8]) -> rusqlite::Result<()>;
702}
703
704impl SqliteConnectionStateStoreExt for rusqlite::Connection {
705    fn set_kv_blob(&self, key: &[u8], value: &[u8]) -> rusqlite::Result<()> {
706        self.execute("INSERT OR REPLACE INTO kv_blob VALUES (?, ?)", (key, value))?;
707        Ok(())
708    }
709
710    fn set_global_account_data(&self, event_type: &[u8], data: &[u8]) -> rusqlite::Result<()> {
711        self.prepare_cached(
712            "INSERT OR REPLACE INTO global_account_data (event_type, data)
713             VALUES (?, ?)",
714        )?
715        .execute((event_type, data))?;
716        Ok(())
717    }
718
719    fn set_room_account_data(
720        &self,
721        room_id: &[u8],
722        event_type: &[u8],
723        data: &[u8],
724    ) -> rusqlite::Result<()> {
725        self.prepare_cached(
726            "INSERT OR REPLACE INTO room_account_data (room_id, event_type, data)
727             VALUES (?, ?, ?)",
728        )?
729        .execute((room_id, event_type, data))?;
730        Ok(())
731    }
732
733    fn remove_room_account_data(&self, room_id: &[u8]) -> rusqlite::Result<()> {
734        self.prepare(
735            "DELETE FROM room_account_data
736             WHERE room_id = ?",
737        )?
738        .execute((room_id,))?;
739        Ok(())
740    }
741
742    fn set_room_info(&self, room_id: &[u8], state: &[u8], data: &[u8]) -> rusqlite::Result<()> {
743        self.prepare_cached(
744            "INSERT OR REPLACE INTO room_info (room_id, state, data)
745             VALUES (?, ?, ?)",
746        )?
747        .execute((room_id, state, data))?;
748        Ok(())
749    }
750
751    fn get_room_info(&self, room_id: &[u8]) -> rusqlite::Result<Option<Vec<u8>>> {
752        self.query_row("SELECT data FROM room_info WHERE room_id = ?", (room_id,), |row| row.get(0))
753            .optional()
754    }
755
756    /// Remove the room info for the given room.
757    fn remove_room_info(&self, room_id: &[u8]) -> rusqlite::Result<()> {
758        self.prepare_cached("DELETE FROM room_info WHERE room_id = ?")?.execute((room_id,))?;
759        Ok(())
760    }
761
762    fn set_state_event(
763        &self,
764        room_id: &[u8],
765        event_type: &[u8],
766        state_key: &[u8],
767        stripped: bool,
768        event_id: Option<&[u8]>,
769        data: &[u8],
770    ) -> rusqlite::Result<()> {
771        self.prepare_cached(
772            "INSERT OR REPLACE
773             INTO state_event (room_id, event_type, state_key, stripped, event_id, data)
774             VALUES (?, ?, ?, ?, ?, ?)",
775        )?
776        .execute((room_id, event_type, state_key, stripped, event_id, data))?;
777        Ok(())
778    }
779
780    fn get_state_event_by_id(
781        &self,
782        room_id: &[u8],
783        event_id: &[u8],
784    ) -> rusqlite::Result<Option<Vec<u8>>> {
785        self.query_row(
786            "SELECT data FROM state_event WHERE room_id = ? AND event_id = ?",
787            (room_id, event_id),
788            |row| row.get(0),
789        )
790        .optional()
791    }
792
793    /// Remove state events for the given room.
794    ///
795    /// If `stripped` is `Some()`, only removes state events for the given
796    /// stripped state. Otherwise, state events are removed regardless of the
797    /// stripped state.
798    fn remove_room_state_events(
799        &self,
800        room_id: &[u8],
801        stripped: Option<bool>,
802    ) -> rusqlite::Result<()> {
803        if let Some(stripped) = stripped {
804            self.prepare_cached("DELETE FROM state_event WHERE room_id = ? AND stripped = ?")?
805                .execute((room_id, stripped))?;
806        } else {
807            self.prepare_cached("DELETE FROM state_event WHERE room_id = ?")?
808                .execute((room_id,))?;
809        }
810        Ok(())
811    }
812
813    fn set_member(
814        &self,
815        room_id: &[u8],
816        user_id: &[u8],
817        membership: &[u8],
818        stripped: bool,
819        data: &[u8],
820    ) -> rusqlite::Result<()> {
821        self.prepare_cached(
822            "INSERT OR REPLACE
823             INTO member (room_id, user_id, membership, stripped, data)
824             VALUES (?, ?, ?, ?, ?)",
825        )?
826        .execute((room_id, user_id, membership, stripped, data))?;
827        Ok(())
828    }
829
830    /// Remove members for the given room.
831    ///
832    /// If `stripped` is `Some()`, only removes members for the given stripped
833    /// state. Otherwise, members are removed regardless of the stripped state.
834    fn remove_room_members(&self, room_id: &[u8], stripped: Option<bool>) -> rusqlite::Result<()> {
835        if let Some(stripped) = stripped {
836            self.prepare_cached("DELETE FROM member WHERE room_id = ? AND stripped = ?")?
837                .execute((room_id, stripped))?;
838        } else {
839            self.prepare_cached("DELETE FROM member WHERE room_id = ?")?.execute((room_id,))?;
840        }
841        Ok(())
842    }
843
844    fn set_profile(&self, room_id: &[u8], user_id: &[u8], data: &[u8]) -> rusqlite::Result<()> {
845        self.prepare_cached(
846            "INSERT OR REPLACE
847             INTO profile (room_id, user_id, data)
848             VALUES (?, ?, ?)",
849        )?
850        .execute((room_id, user_id, data))?;
851        Ok(())
852    }
853
854    fn remove_room_profiles(&self, room_id: &[u8]) -> rusqlite::Result<()> {
855        self.prepare("DELETE FROM profile WHERE room_id = ?")?.execute((room_id,))?;
856        Ok(())
857    }
858
859    fn remove_room_profile(&self, room_id: &[u8], user_id: &[u8]) -> rusqlite::Result<()> {
860        self.prepare("DELETE FROM profile WHERE room_id = ? AND user_id = ?")?
861            .execute((room_id, user_id))?;
862        Ok(())
863    }
864
865    fn set_receipt(
866        &self,
867        room_id: &[u8],
868        user_id: &[u8],
869        receipt_type: &[u8],
870        thread: &[u8],
871        event_id: &[u8],
872        data: &[u8],
873    ) -> rusqlite::Result<()> {
874        self.prepare_cached(
875            "INSERT OR REPLACE
876             INTO receipt (room_id, user_id, receipt_type, thread, event_id, data)
877             VALUES (?, ?, ?, ?, ?, ?)",
878        )?
879        .execute((room_id, user_id, receipt_type, thread, event_id, data))?;
880        Ok(())
881    }
882
883    fn remove_room_receipts(&self, room_id: &[u8]) -> rusqlite::Result<()> {
884        self.prepare("DELETE FROM receipt WHERE room_id = ?")?.execute((room_id,))?;
885        Ok(())
886    }
887
888    fn set_display_name(&self, room_id: &[u8], name: &[u8], data: &[u8]) -> rusqlite::Result<()> {
889        self.prepare_cached(
890            "INSERT OR REPLACE
891             INTO display_name (room_id, name, data)
892             VALUES (?, ?, ?)",
893        )?
894        .execute((room_id, name, data))?;
895        Ok(())
896    }
897
898    fn remove_display_name(&self, room_id: &[u8], name: &[u8]) -> rusqlite::Result<()> {
899        self.prepare("DELETE FROM display_name WHERE room_id = ? AND name = ?")?
900            .execute((room_id, name))?;
901        Ok(())
902    }
903
904    fn remove_room_display_names(&self, room_id: &[u8]) -> rusqlite::Result<()> {
905        self.prepare("DELETE FROM display_name WHERE room_id = ?")?.execute((room_id,))?;
906        Ok(())
907    }
908
909    fn remove_room_send_queue(&self, room_id: &[u8]) -> rusqlite::Result<()> {
910        self.prepare("DELETE FROM send_queue_events WHERE room_id = ?")?.execute((room_id,))?;
911        Ok(())
912    }
913
914    fn remove_room_dependent_send_queue(&self, room_id: &[u8]) -> rusqlite::Result<()> {
915        self.prepare("DELETE FROM dependent_send_queue_events WHERE room_id = ?")?
916            .execute((room_id,))?;
917        Ok(())
918    }
919}
920
921#[async_trait]
922trait SqliteObjectStateStoreExt: SqliteAsyncConnExt {
923    async fn get_kv_blob(&self, key: Key) -> Result<Option<Vec<u8>>> {
924        Ok(self
925            .query_row("SELECT value FROM kv_blob WHERE key = ?", (key,), |row| row.get(0))
926            .await
927            .optional()?)
928    }
929
930    async fn get_kv_blobs(&self, keys: Vec<Key>) -> Result<Vec<Vec<u8>>> {
931        let keys_length = keys.len();
932
933        self.chunk_large_query_over(keys, Some(keys_length), |txn, keys| {
934            let sql_params = repeat_vars(keys.len());
935            let sql = format!("SELECT value FROM kv_blob WHERE key IN ({sql_params})");
936
937            let params = rusqlite::params_from_iter(keys);
938
939            Ok(txn
940                .prepare(&sql)?
941                .query(params)?
942                .mapped(|row| row.get(0))
943                .collect::<Result<_, _>>()?)
944        })
945        .await
946    }
947
948    async fn set_kv_blob(&self, key: Key, value: Vec<u8>) -> Result<()>;
949
950    async fn delete_kv_blob(&self, key: Key) -> Result<()> {
951        self.execute("DELETE FROM kv_blob WHERE key = ?", (key,)).await?;
952        Ok(())
953    }
954
955    async fn get_room_infos(&self, room_id: Option<Key>) -> Result<Vec<Vec<u8>>> {
956        Ok(match room_id {
957            None => {
958                self.prepare("SELECT data FROM room_info", move |mut stmt| {
959                    stmt.query_map((), |row| row.get(0))?.collect()
960                })
961                .await?
962            }
963
964            Some(room_id) => {
965                self.prepare("SELECT data FROM room_info WHERE room_id = ?", move |mut stmt| {
966                    stmt.query((room_id,))?.mapped(|row| row.get(0)).collect()
967                })
968                .await?
969            }
970        })
971    }
972
973    async fn get_maybe_stripped_state_events_for_keys(
974        &self,
975        room_id: Key,
976        event_type: Key,
977        state_keys: Vec<Key>,
978    ) -> Result<Vec<(bool, Vec<u8>)>> {
979        self.chunk_large_query_over(state_keys, None, move |txn, state_keys: Vec<Key>| {
980            let sql_params = repeat_vars(state_keys.len());
981            let sql = format!(
982                "SELECT stripped, data FROM state_event
983                 WHERE room_id = ? AND event_type = ? AND state_key IN ({sql_params})"
984            );
985
986            let params = rusqlite::params_from_iter(
987                [room_id.clone(), event_type.clone()].into_iter().chain(state_keys),
988            );
989
990            Ok(txn
991                .prepare(&sql)?
992                .query(params)?
993                .mapped(|row| Ok((row.get(0)?, row.get(1)?)))
994                .collect::<Result<_, _>>()?)
995        })
996        .await
997    }
998
999    async fn get_maybe_stripped_state_events(
1000        &self,
1001        room_id: Key,
1002        event_type: Key,
1003    ) -> Result<Vec<(bool, Vec<u8>)>> {
1004        Ok(self
1005            .prepare(
1006                "SELECT stripped, data FROM state_event
1007                 WHERE room_id = ? AND event_type = ?",
1008                |mut stmt| {
1009                    stmt.query((room_id, event_type))?
1010                        .mapped(|row| Ok((row.get(0)?, row.get(1)?)))
1011                        .collect()
1012                },
1013            )
1014            .await?)
1015    }
1016
1017    async fn get_profiles(
1018        &self,
1019        room_id: Key,
1020        user_ids: Vec<Key>,
1021    ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
1022        let user_ids_length = user_ids.len();
1023
1024        self.chunk_large_query_over(user_ids, Some(user_ids_length), move |txn, user_ids| {
1025            let sql_params = repeat_vars(user_ids_length);
1026            let sql = format!(
1027                "SELECT user_id, data FROM profile WHERE room_id = ? AND user_id IN ({sql_params})"
1028            );
1029
1030            let params = rusqlite::params_from_iter(iter::once(room_id.clone()).chain(user_ids));
1031
1032            Ok(txn
1033                .prepare(&sql)?
1034                .query(params)?
1035                .mapped(|row| Ok((row.get(0)?, row.get(1)?)))
1036                .collect::<Result<_, _>>()?)
1037        })
1038        .await
1039    }
1040
1041    async fn get_global_profiles(&self, user_ids: Vec<Key>) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
1042        let user_ids_length = user_ids.len();
1043
1044        self.chunk_large_query_over(user_ids, Some(user_ids_length), move |txn, user_ids| {
1045            let sql_params = repeat_vars(user_ids_length);
1046            let sql = format!(
1047                "SELECT user_id, profile_data FROM global_profiles WHERE user_id IN ({sql_params})"
1048            );
1049
1050            let params = rusqlite::params_from_iter(user_ids);
1051
1052            Ok(txn
1053                .prepare(&sql)?
1054                .query(params)?
1055                .mapped(|row| Ok((row.get(0)?, row.get(1)?)))
1056                .collect::<Result<_, _>>()?)
1057        })
1058        .await
1059    }
1060
1061    async fn get_user_ids(&self, room_id: Key, memberships: Vec<Key>) -> Result<Vec<Vec<u8>>> {
1062        let res = if memberships.is_empty() {
1063            self.prepare("SELECT data FROM member WHERE room_id = ?", |mut stmt| {
1064                stmt.query((room_id,))?.mapped(|row| row.get(0)).collect()
1065            })
1066            .await?
1067        } else {
1068            self.chunk_large_query_over(memberships, None, move |txn, memberships| {
1069                let sql_params = repeat_vars(memberships.len());
1070                let sql = format!(
1071                    "SELECT data FROM member WHERE room_id = ? AND membership IN ({sql_params})"
1072                );
1073
1074                let params =
1075                    rusqlite::params_from_iter(iter::once(room_id.clone()).chain(memberships));
1076
1077                Ok(txn
1078                    .prepare(&sql)?
1079                    .query(params)?
1080                    .mapped(|row| row.get(0))
1081                    .collect::<Result<_, _>>()?)
1082            })
1083            .await?
1084        };
1085
1086        Ok(res)
1087    }
1088
1089    async fn get_global_account_data(&self, event_type: Key) -> Result<Option<Vec<u8>>> {
1090        Ok(self
1091            .query_row(
1092                "SELECT data FROM global_account_data WHERE event_type = ?",
1093                (event_type,),
1094                |row| row.get(0),
1095            )
1096            .await
1097            .optional()?)
1098    }
1099
1100    async fn get_room_account_data(
1101        &self,
1102        room_id: Key,
1103        event_type: Key,
1104    ) -> Result<Option<Vec<u8>>> {
1105        Ok(self
1106            .query_row(
1107                "SELECT data FROM room_account_data WHERE room_id = ? AND event_type = ?",
1108                (room_id, event_type),
1109                |row| row.get(0),
1110            )
1111            .await
1112            .optional()?)
1113    }
1114
1115    async fn get_display_names(
1116        &self,
1117        room_id: Key,
1118        names: Vec<Key>,
1119    ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
1120        let names_length = names.len();
1121
1122        self.chunk_large_query_over(names, Some(names_length), move |txn, names| {
1123            let sql_params = repeat_vars(names.len());
1124            let sql = format!(
1125                "SELECT name, data FROM display_name WHERE room_id = ? AND name IN ({sql_params})"
1126            );
1127
1128            let params = rusqlite::params_from_iter(iter::once(room_id.clone()).chain(names));
1129
1130            Ok(txn
1131                .prepare(&sql)?
1132                .query(params)?
1133                .mapped(|row| Ok((row.get(0)?, row.get(1)?)))
1134                .collect::<Result<_, _>>()?)
1135        })
1136        .await
1137    }
1138
1139    async fn get_user_receipt(
1140        &self,
1141        room_id: Key,
1142        receipt_type: Key,
1143        thread: Key,
1144        user_id: Key,
1145    ) -> Result<Option<Vec<u8>>> {
1146        Ok(self
1147            .query_row(
1148                "SELECT data FROM receipt
1149                 WHERE room_id = ? AND receipt_type = ? AND thread = ? and user_id = ?",
1150                (room_id, receipt_type, thread, user_id),
1151                |row| row.get(0),
1152            )
1153            .await
1154            .optional()?)
1155    }
1156
1157    async fn get_event_receipts(
1158        &self,
1159        room_id: Key,
1160        receipt_type: Key,
1161        thread: Key,
1162        event_id: Key,
1163    ) -> Result<Vec<Vec<u8>>> {
1164        Ok(self
1165            .prepare(
1166                "SELECT data FROM receipt
1167                 WHERE room_id = ? AND receipt_type = ? AND thread = ? and event_id = ?",
1168                |mut stmt| {
1169                    stmt.query((room_id, receipt_type, thread, event_id))?
1170                        .mapped(|row| row.get(0))
1171                        .collect()
1172                },
1173            )
1174            .await?)
1175    }
1176}
1177
1178#[async_trait]
1179impl SqliteObjectStateStoreExt for SqliteAsyncConn {
1180    async fn set_kv_blob(&self, key: Key, value: Vec<u8>) -> Result<()> {
1181        Ok(self.interact(move |conn| conn.set_kv_blob(&key, &value)).await.unwrap()?)
1182    }
1183}
1184
1185#[async_trait]
1186impl StateStore for SqliteStateStore {
1187    type Error = Error;
1188
1189    async fn get_kv_data(&self, key: StateStoreDataKey<'_>) -> Result<Option<StateStoreDataValue>> {
1190        self.read()
1191            .await?
1192            .get_kv_blob(self.encode_state_store_data_key(key))
1193            .await?
1194            .map(|data| {
1195                Ok(match key {
1196                    StateStoreDataKey::SyncToken => {
1197                        StateStoreDataValue::SyncToken(self.deserialize_value(&data)?)
1198                    }
1199                    StateStoreDataKey::SupportedVersions => {
1200                        StateStoreDataValue::SupportedVersions(self.deserialize_value(&data)?)
1201                    }
1202                    StateStoreDataKey::WellKnown => {
1203                        StateStoreDataValue::WellKnown(self.deserialize_value(&data)?)
1204                    }
1205                    StateStoreDataKey::Filter(_) => {
1206                        StateStoreDataValue::Filter(self.deserialize_value(&data)?)
1207                    }
1208                    StateStoreDataKey::UserAvatarUrl(_) => {
1209                        StateStoreDataValue::UserAvatarUrl(self.deserialize_value(&data)?)
1210                    }
1211                    StateStoreDataKey::RecentlyVisitedRooms(_) => {
1212                        StateStoreDataValue::RecentlyVisitedRooms(self.deserialize_value(&data)?)
1213                    }
1214                    StateStoreDataKey::UtdHookManagerData => {
1215                        StateStoreDataValue::UtdHookManagerData(self.deserialize_value(&data)?)
1216                    }
1217                    StateStoreDataKey::OneTimeKeyAlreadyUploaded => {
1218                        StateStoreDataValue::OneTimeKeyAlreadyUploaded
1219                    }
1220                    StateStoreDataKey::ComposerDraft(_, _) => {
1221                        StateStoreDataValue::ComposerDraft(self.deserialize_value(&data)?)
1222                    }
1223                    StateStoreDataKey::SeenKnockRequests(_) => {
1224                        StateStoreDataValue::SeenKnockRequests(self.deserialize_value(&data)?)
1225                    }
1226                    StateStoreDataKey::ThreadSubscriptionsCatchupTokens => {
1227                        StateStoreDataValue::ThreadSubscriptionsCatchupTokens(
1228                            self.deserialize_value(&data)?,
1229                        )
1230                    }
1231                    StateStoreDataKey::HomeserverCapabilities => {
1232                        StateStoreDataValue::HomeserverCapabilities(self.deserialize_value(&data)?)
1233                    }
1234                })
1235            })
1236            .transpose()
1237    }
1238
1239    async fn set_kv_data(
1240        &self,
1241        key: StateStoreDataKey<'_>,
1242        value: StateStoreDataValue,
1243    ) -> Result<()> {
1244        let serialized_value = match key {
1245            StateStoreDataKey::SyncToken => self.serialize_value(
1246                &value.into_sync_token().expect("Session data not a sync token"),
1247            )?,
1248            StateStoreDataKey::SupportedVersions => self.serialize_value(
1249                &value
1250                    .into_supported_versions()
1251                    .expect("Session data not containing supported versions"),
1252            )?,
1253            StateStoreDataKey::WellKnown => self.serialize_value(
1254                &value.into_well_known().expect("Session data not containing well-known"),
1255            )?,
1256            StateStoreDataKey::Filter(_) => {
1257                self.serialize_value(&value.into_filter().expect("Session data not a filter"))?
1258            }
1259            StateStoreDataKey::UserAvatarUrl(_) => self.serialize_value(
1260                &value.into_user_avatar_url().expect("Session data not an user avatar url"),
1261            )?,
1262            StateStoreDataKey::RecentlyVisitedRooms(_) => self.serialize_value(
1263                &value.into_recently_visited_rooms().expect("Session data not breadcrumbs"),
1264            )?,
1265            StateStoreDataKey::UtdHookManagerData => self.serialize_value(
1266                &value.into_utd_hook_manager_data().expect("Session data not UtdHookManagerData"),
1267            )?,
1268            StateStoreDataKey::OneTimeKeyAlreadyUploaded => {
1269                self.serialize_value(&true).expect("We should be able to serialize a boolean")
1270            }
1271            StateStoreDataKey::ComposerDraft(_, _) => self.serialize_value(
1272                &value.into_composer_draft().expect("Session data not a composer draft"),
1273            )?,
1274            StateStoreDataKey::SeenKnockRequests(_) => self.serialize_value(
1275                &value
1276                    .into_seen_knock_requests()
1277                    .expect("Session data is not a set of seen knock request ids"),
1278            )?,
1279            StateStoreDataKey::ThreadSubscriptionsCatchupTokens => self.serialize_value(
1280                &value
1281                    .into_thread_subscriptions_catchup_tokens()
1282                    .expect("Session data is not a list of thread subscription catchup tokens"),
1283            )?,
1284            StateStoreDataKey::HomeserverCapabilities => self.serialize_value(
1285                &value
1286                    .into_homeserver_capabilities()
1287                    .expect("Session data is not the homeserver capabilities"),
1288            )?,
1289        };
1290
1291        self.write()
1292            .await?
1293            .set_kv_blob(self.encode_state_store_data_key(key), serialized_value)
1294            .await
1295    }
1296
1297    async fn remove_kv_data(&self, key: StateStoreDataKey<'_>) -> Result<()> {
1298        self.write().await?.delete_kv_blob(self.encode_state_store_data_key(key)).await
1299    }
1300
1301    async fn save_changes(&self, changes: &StateChanges) -> Result<()> {
1302        let changes = changes.to_owned();
1303        let this = self.clone();
1304        self.write()
1305            .await?
1306            .with_transaction(move |txn| {
1307                let StateChanges {
1308                    sync_token,
1309                    account_data,
1310                    presence,
1311                    profiles,
1312                    profiles_to_delete,
1313                    state,
1314                    room_account_data,
1315                    room_infos,
1316                    receipts,
1317                    redactions,
1318                    stripped_state,
1319                    ambiguity_maps,
1320                    global_profiles,
1321                } = changes;
1322
1323                if let Some(sync_token) = sync_token {
1324                    let key = this.encode_state_store_data_key(StateStoreDataKey::SyncToken);
1325                    let value = this.serialize_value(&sync_token)?;
1326                    txn.set_kv_blob(&key, &value)?;
1327                }
1328
1329                for (event_type, event) in account_data {
1330                    let event_type =
1331                        this.encode_key(keys::GLOBAL_ACCOUNT_DATA, event_type.to_string());
1332                    let data = this.serialize_json(&event)?;
1333                    txn.set_global_account_data(&event_type, &data)?;
1334                }
1335
1336                for (room_id, events) in room_account_data {
1337                    let room_id = this.encode_key(keys::ROOM_ACCOUNT_DATA, room_id);
1338                    for (event_type, event) in events {
1339                        let event_type =
1340                            this.encode_key(keys::ROOM_ACCOUNT_DATA, event_type.to_string());
1341                        let data = this.serialize_json(&event)?;
1342                        txn.set_room_account_data(&room_id, &event_type, &data)?;
1343                    }
1344                }
1345
1346                for (user_id, event) in presence {
1347                    let key = this.encode_presence_key(&user_id);
1348                    let value = this.serialize_json(&event)?;
1349                    txn.set_kv_blob(&key, &value)?;
1350                }
1351
1352                for (room_id, room_info) in room_infos {
1353                    let stripped = room_info.state() == RoomState::Invited;
1354                    // Remove non-stripped data for stripped rooms and vice-versa.
1355                    this.remove_maybe_stripped_room_data(txn, &room_id, !stripped)?;
1356
1357                    let room_id = this.encode_key(keys::ROOM_INFO, room_id);
1358                    let state = this
1359                        .encode_key(keys::ROOM_INFO, serde_json::to_string(&room_info.state())?);
1360                    let data = this.serialize_json(&room_info)?;
1361                    txn.set_room_info(&room_id, &state, &data)?;
1362                }
1363
1364                for (room_id, user_ids) in profiles_to_delete {
1365                    let room_id = this.encode_key(keys::PROFILE, room_id);
1366                    for user_id in user_ids {
1367                        let user_id = this.encode_key(keys::PROFILE, user_id);
1368                        txn.remove_room_profile(&room_id, &user_id)?;
1369                    }
1370                }
1371
1372                for (room_id, state_event_types) in state {
1373                    let profiles = profiles.get(&room_id);
1374                    let encoded_room_id = this.encode_key(keys::STATE_EVENT, &room_id);
1375
1376                    for (event_type, state_events) in state_event_types {
1377                        let encoded_event_type =
1378                            this.encode_key(keys::STATE_EVENT, event_type.to_string());
1379
1380                        for (state_key, raw_state_event) in state_events {
1381                            let encoded_state_key = this.encode_key(keys::STATE_EVENT, &state_key);
1382                            let data = this.serialize_json(&raw_state_event)?;
1383
1384                            let event_id: Option<String> =
1385                                raw_state_event.get_field("event_id").ok().flatten();
1386                            let encoded_event_id =
1387                                event_id.as_ref().map(|e| this.encode_key(keys::STATE_EVENT, e));
1388
1389                            txn.set_state_event(
1390                                &encoded_room_id,
1391                                &encoded_event_type,
1392                                &encoded_state_key,
1393                                false,
1394                                encoded_event_id.as_deref(),
1395                                &data,
1396                            )?;
1397
1398                            if event_type == StateEventType::RoomMember {
1399                                let member_event = match raw_state_event
1400                                    .deserialize_as_unchecked::<SyncRoomMemberEvent>()
1401                                {
1402                                    Ok(ev) => ev,
1403                                    Err(e) => {
1404                                        debug!(event_id, "Failed to deserialize member event: {e}");
1405                                        continue;
1406                                    }
1407                                };
1408
1409                                let encoded_room_id = this.encode_key(keys::MEMBER, &room_id);
1410                                let user_id = this.encode_key(keys::MEMBER, &state_key);
1411                                let membership = this
1412                                    .encode_key(keys::MEMBER, member_event.membership().as_str());
1413                                let data = this.serialize_value(&state_key)?;
1414
1415                                txn.set_member(
1416                                    &encoded_room_id,
1417                                    &user_id,
1418                                    &membership,
1419                                    false,
1420                                    &data,
1421                                )?;
1422
1423                                if let Some(profile) =
1424                                    profiles.and_then(|p| p.get(member_event.state_key()))
1425                                {
1426                                    let room_id = this.encode_key(keys::PROFILE, &room_id);
1427                                    let user_id = this.encode_key(keys::PROFILE, &state_key);
1428                                    let data = this.serialize_json(&profile)?;
1429                                    txn.set_profile(&room_id, &user_id, &data)?;
1430                                }
1431                            }
1432                        }
1433                    }
1434                }
1435
1436                for (room_id, stripped_state_event_types) in stripped_state {
1437                    let encoded_room_id = this.encode_key(keys::STATE_EVENT, &room_id);
1438
1439                    for (event_type, stripped_state_events) in stripped_state_event_types {
1440                        let encoded_event_type =
1441                            this.encode_key(keys::STATE_EVENT, event_type.to_string());
1442
1443                        for (state_key, raw_stripped_state_event) in stripped_state_events {
1444                            let encoded_state_key = this.encode_key(keys::STATE_EVENT, &state_key);
1445                            let data = this.serialize_json(&raw_stripped_state_event)?;
1446                            txn.set_state_event(
1447                                &encoded_room_id,
1448                                &encoded_event_type,
1449                                &encoded_state_key,
1450                                true,
1451                                None,
1452                                &data,
1453                            )?;
1454
1455                            if event_type == StateEventType::RoomMember {
1456                                let member_event = match raw_stripped_state_event
1457                                    .deserialize_as_unchecked::<StrippedRoomMemberEvent>(
1458                                ) {
1459                                    Ok(ev) => ev,
1460                                    Err(e) => {
1461                                        debug!("Failed to deserialize stripped member event: {e}");
1462                                        continue;
1463                                    }
1464                                };
1465
1466                                let room_id = this.encode_key(keys::MEMBER, &room_id);
1467                                let user_id = this.encode_key(keys::MEMBER, &state_key);
1468                                let membership = this.encode_key(
1469                                    keys::MEMBER,
1470                                    member_event.content.membership.as_str(),
1471                                );
1472                                let data = this.serialize_value(&state_key)?;
1473
1474                                txn.set_member(&room_id, &user_id, &membership, true, &data)?;
1475                            }
1476                        }
1477                    }
1478                }
1479
1480                for (room_id, receipt_event) in receipts {
1481                    let room_id = this.encode_key(keys::RECEIPT, room_id);
1482
1483                    for (event_id, receipt_types) in receipt_event {
1484                        let encoded_event_id = this.encode_key(keys::RECEIPT, &event_id);
1485
1486                        for (receipt_type, receipt_users) in receipt_types {
1487                            let receipt_type =
1488                                this.encode_key(keys::RECEIPT, receipt_type.as_str());
1489
1490                            for (user_id, receipt) in receipt_users {
1491                                let encoded_user_id = this.encode_key(keys::RECEIPT, &user_id);
1492                                // We cannot have a NULL primary key so we rely on serialization
1493                                // instead of the string representation.
1494                                let thread = this.encode_key(
1495                                    keys::RECEIPT,
1496                                    rmp_serde::to_vec_named(&receipt.thread)?,
1497                                );
1498                                let data = this.serialize_json(&ReceiptData {
1499                                    receipt,
1500                                    event_id: event_id.clone(),
1501                                    user_id,
1502                                })?;
1503
1504                                txn.set_receipt(
1505                                    &room_id,
1506                                    &encoded_user_id,
1507                                    &receipt_type,
1508                                    &thread,
1509                                    &encoded_event_id,
1510                                    &data,
1511                                )?;
1512                            }
1513                        }
1514                    }
1515                }
1516
1517                for (room_id, redactions) in redactions {
1518                    let make_redaction_rules = || {
1519                        let encoded_room_id = this.encode_key(keys::ROOM_INFO, &room_id);
1520                        txn.get_room_info(&encoded_room_id)
1521                            .ok()
1522                            .flatten()
1523                            .and_then(|v| this.deserialize_json::<RoomInfo>(&v).ok())
1524                            .map(|info| info.room_version_rules_or_default())
1525                            .unwrap_or_else(|| {
1526                                warn!(
1527                                    ?room_id,
1528                                    "Unable to get the room version rules, defaulting to rules for room version {ROOM_VERSION_FALLBACK}"
1529                                );
1530                                ROOM_VERSION_RULES_FALLBACK
1531                            }).redaction
1532                    };
1533
1534                    let encoded_room_id = this.encode_key(keys::STATE_EVENT, &room_id);
1535                    let mut redaction_rules = None;
1536
1537                    for (event_id, redaction) in redactions {
1538                        let event_id = this.encode_key(keys::STATE_EVENT, event_id);
1539
1540                        if let Some(Ok(raw_event)) = txn
1541                            .get_state_event_by_id(&encoded_room_id, &event_id)?
1542                            .map(|value| this.deserialize_json::<Raw<AnySyncStateEvent>>(&value))
1543                        {
1544                            let event = raw_event.deserialize()?;
1545                            let redacted = redact(
1546                                raw_event.deserialize_as::<CanonicalJsonObject>()?,
1547                                redaction_rules.get_or_insert_with(make_redaction_rules),
1548                                Some(RedactedBecause::from_raw_event(&redaction)?),
1549                            )
1550                            .map_err(Error::Redaction)?;
1551                            let data = this.serialize_json(&redacted)?;
1552
1553                            let event_type =
1554                                this.encode_key(keys::STATE_EVENT, event.event_type().to_string());
1555                            let state_key = this.encode_key(keys::STATE_EVENT, event.state_key());
1556
1557                            txn.set_state_event(
1558                                &encoded_room_id,
1559                                &event_type,
1560                                &state_key,
1561                                false,
1562                                Some(&event_id),
1563                                &data,
1564                            )?;
1565                        }
1566                    }
1567                }
1568
1569                for (room_id, display_names) in ambiguity_maps {
1570                    let room_id = this.encode_key(keys::DISPLAY_NAME, room_id);
1571
1572                    for (name, user_ids) in display_names {
1573                        let encoded_name = this.encode_key(
1574                            keys::DISPLAY_NAME,
1575                            name.as_normalized_str().unwrap_or_else(|| name.as_raw_str()),
1576                        );
1577                        let data = this.serialize_json(&user_ids)?;
1578
1579                        if user_ids.is_empty() {
1580                            txn.remove_display_name(&room_id, &encoded_name)?;
1581
1582                            // We can't do a migration to merge the previously distinct buckets of
1583                            // user IDs since the display names themselves are hashed before they
1584                            // are persisted in the store. So the store will always retain two
1585                            // buckets: one for raw display names and one for normalised ones.
1586                            //
1587                            // We therefore do the next best thing, which is a sort of a soft
1588                            // migration: we fetch both the raw and normalised buckets, then merge
1589                            // the user IDs contained in them into a separate, temporary merged
1590                            // bucket. The SDK then operates on the merged buckets exclusively. See
1591                            // the comment in `get_users_with_display_names` for details.
1592                            //
1593                            // If the merged bucket is empty, that must mean that both the raw and
1594                            // normalised buckets were also empty, so we can remove both from the
1595                            // store.
1596                            let raw_name = this.encode_key(keys::DISPLAY_NAME, name.as_raw_str());
1597                            txn.remove_display_name(&room_id, &raw_name)?;
1598                        } else {
1599                            // We only create new buckets with the normalized display name.
1600                            txn.set_display_name(&room_id, &encoded_name, &data)?;
1601                        }
1602                    }
1603                }
1604
1605                if !global_profiles.is_empty() {
1606                    let mut select_stmt = txn.prepare_cached(
1607                        "SELECT profile_data FROM global_profiles WHERE user_id = ?",
1608                    )?;
1609                    let mut insert_stmt = txn.prepare_cached(
1610                        "INSERT OR REPLACE INTO global_profiles (user_id, profile_data) VALUES (?, ?)",
1611                    )?;
1612
1613                    for (user_id, profile_update) in global_profiles {
1614                        let user_id = this.encode_key(keys::GLOBAL_PROFILES, &user_id);
1615                        let existing_data: Option<Vec<u8>> =
1616                            select_stmt.query_row([&user_id], |row| row.get(0)).optional()?;
1617
1618                        let mut profile: UserProfile = existing_data
1619                            .map(|data| this.deserialize_json(&data))
1620                            .transpose()?
1621                            .unwrap_or_default();
1622                        profile.merge(profile_update);
1623
1624                        let serialized = this.serialize_json(&profile)?;
1625                        insert_stmt.execute((&user_id, serialized))?;
1626                    }
1627                }
1628
1629                Ok::<_, Error>(())
1630            })
1631            .await?;
1632
1633        Ok(())
1634    }
1635
1636    async fn get_presence_event(&self, user_id: &UserId) -> Result<Option<Raw<PresenceEvent>>> {
1637        self.read()
1638            .await?
1639            .get_kv_blob(self.encode_presence_key(user_id))
1640            .await?
1641            .map(|data| self.deserialize_json(&data))
1642            .transpose()
1643    }
1644
1645    async fn get_presence_events(
1646        &self,
1647        user_ids: &[OwnedUserId],
1648    ) -> Result<Vec<Raw<PresenceEvent>>> {
1649        if user_ids.is_empty() {
1650            return Ok(Vec::new());
1651        }
1652
1653        let user_ids = user_ids.iter().map(|u| self.encode_presence_key(u)).collect();
1654        self.read()
1655            .await?
1656            .get_kv_blobs(user_ids)
1657            .await?
1658            .into_iter()
1659            .map(|data| self.deserialize_json(&data))
1660            .collect()
1661    }
1662
1663    async fn get_state_event(
1664        &self,
1665        room_id: &RoomId,
1666        event_type: StateEventType,
1667        state_key: &str,
1668    ) -> Result<Option<RawAnySyncOrStrippedState>> {
1669        Ok(self
1670            .get_state_events_for_keys(room_id, event_type, &[state_key])
1671            .await?
1672            .into_iter()
1673            .next())
1674    }
1675
1676    async fn get_state_events(
1677        &self,
1678        room_id: &RoomId,
1679        event_type: StateEventType,
1680    ) -> Result<Vec<RawAnySyncOrStrippedState>> {
1681        let room_id = self.encode_key(keys::STATE_EVENT, room_id);
1682        let event_type = self.encode_key(keys::STATE_EVENT, event_type.to_string());
1683        self.read()
1684            .await?
1685            .get_maybe_stripped_state_events(room_id, event_type)
1686            .await?
1687            .into_iter()
1688            .map(|(stripped, data)| {
1689                let ev = if stripped {
1690                    RawAnySyncOrStrippedState::Stripped(self.deserialize_json(&data)?)
1691                } else {
1692                    RawAnySyncOrStrippedState::Sync(self.deserialize_json(&data)?)
1693                };
1694
1695                Ok(ev)
1696            })
1697            .collect()
1698    }
1699
1700    async fn get_state_events_for_keys(
1701        &self,
1702        room_id: &RoomId,
1703        event_type: StateEventType,
1704        state_keys: &[&str],
1705    ) -> Result<Vec<RawAnySyncOrStrippedState>, Self::Error> {
1706        if state_keys.is_empty() {
1707            return Ok(Vec::new());
1708        }
1709
1710        let room_id = self.encode_key(keys::STATE_EVENT, room_id);
1711        let event_type = self.encode_key(keys::STATE_EVENT, event_type.to_string());
1712        let state_keys = state_keys.iter().map(|k| self.encode_key(keys::STATE_EVENT, k)).collect();
1713        self.read()
1714            .await?
1715            .get_maybe_stripped_state_events_for_keys(room_id, event_type, state_keys)
1716            .await?
1717            .into_iter()
1718            .map(|(stripped, data)| {
1719                let ev = if stripped {
1720                    RawAnySyncOrStrippedState::Stripped(self.deserialize_json(&data)?)
1721                } else {
1722                    RawAnySyncOrStrippedState::Sync(self.deserialize_json(&data)?)
1723                };
1724
1725                Ok(ev)
1726            })
1727            .collect()
1728    }
1729
1730    async fn get_profile(
1731        &self,
1732        room_id: &RoomId,
1733        user_id: &UserId,
1734    ) -> Result<Option<MinimalRoomMemberEvent>> {
1735        let room_id = self.encode_key(keys::PROFILE, room_id);
1736        let user_ids = vec![self.encode_key(keys::PROFILE, user_id)];
1737
1738        self.read()
1739            .await?
1740            .get_profiles(room_id, user_ids)
1741            .await?
1742            .into_iter()
1743            .next()
1744            .map(|(_, data)| self.deserialize_json(&data))
1745            .transpose()
1746    }
1747
1748    async fn get_profiles<'a>(
1749        &self,
1750        room_id: &RoomId,
1751        user_ids: &'a [OwnedUserId],
1752    ) -> Result<BTreeMap<&'a UserId, MinimalRoomMemberEvent>> {
1753        if user_ids.is_empty() {
1754            return Ok(BTreeMap::new());
1755        }
1756
1757        let room_id = self.encode_key(keys::PROFILE, room_id);
1758        let mut user_ids_map = user_ids
1759            .iter()
1760            .map(|u| (self.encode_key(keys::PROFILE, u), u.as_ref()))
1761            .collect::<BTreeMap<_, _>>();
1762        let user_ids = user_ids_map.keys().cloned().collect();
1763
1764        self.read()
1765            .await?
1766            .get_profiles(room_id, user_ids)
1767            .await?
1768            .into_iter()
1769            .map(|(user_id, data)| {
1770                Ok((
1771                    user_ids_map
1772                        .remove(user_id.as_slice())
1773                        .expect("returned user IDs were requested"),
1774                    self.deserialize_json(&data)?,
1775                ))
1776            })
1777            .collect()
1778    }
1779
1780    async fn get_user_ids(
1781        &self,
1782        room_id: &RoomId,
1783        membership: RoomMemberships,
1784    ) -> Result<Vec<OwnedUserId>> {
1785        let room_id = self.encode_key(keys::MEMBER, room_id);
1786        let memberships = membership
1787            .as_vec()
1788            .into_iter()
1789            .map(|m| self.encode_key(keys::MEMBER, m.as_str()))
1790            .collect();
1791        self.read()
1792            .await?
1793            .get_user_ids(room_id, memberships)
1794            .await?
1795            .iter()
1796            .map(|data| self.deserialize_value(data))
1797            .collect()
1798    }
1799
1800    async fn get_room_infos(&self, room_load_settings: &RoomLoadSettings) -> Result<Vec<RoomInfo>> {
1801        self.read()
1802            .await?
1803            .get_room_infos(match room_load_settings {
1804                RoomLoadSettings::All => None,
1805                RoomLoadSettings::One(room_id) => Some(self.encode_key(keys::ROOM_INFO, room_id)),
1806            })
1807            .await?
1808            .into_iter()
1809            .map(|data| self.deserialize_json(&data))
1810            .collect()
1811    }
1812
1813    async fn get_users_with_display_name(
1814        &self,
1815        room_id: &RoomId,
1816        display_name: &DisplayName,
1817    ) -> Result<BTreeSet<OwnedUserId>> {
1818        let room_id = self.encode_key(keys::DISPLAY_NAME, room_id);
1819        let names = vec![self.encode_key(
1820            keys::DISPLAY_NAME,
1821            display_name.as_normalized_str().unwrap_or_else(|| display_name.as_raw_str()),
1822        )];
1823
1824        Ok(self
1825            .read()
1826            .await?
1827            .get_display_names(room_id, names)
1828            .await?
1829            .into_iter()
1830            .next()
1831            .map(|(_, data)| self.deserialize_json(&data))
1832            .transpose()?
1833            .unwrap_or_default())
1834    }
1835
1836    async fn get_users_with_display_names<'a>(
1837        &self,
1838        room_id: &RoomId,
1839        display_names: &'a [DisplayName],
1840    ) -> Result<HashMap<&'a DisplayName, BTreeSet<OwnedUserId>>> {
1841        let mut result = HashMap::new();
1842
1843        if display_names.is_empty() {
1844            return Ok(result);
1845        }
1846
1847        let room_id = self.encode_key(keys::DISPLAY_NAME, room_id);
1848        let mut names_map = display_names
1849            .iter()
1850            .flat_map(|display_name| {
1851                // We encode the display name as the `raw_str()` and the normalized string.
1852                //
1853                // This is for compatibility reasons since:
1854                //  1. Previously "Alice" and "alice" were considered to be distinct display
1855                //     names, while we now consider them to be the same so we need to merge the
1856                //     previously distinct buckets of user IDs.
1857                //  2. We can't do a migration to merge the previously distinct buckets of user
1858                //     IDs since the display names itself are hashed before they are persisted
1859                //     in the store.
1860                let raw =
1861                    (self.encode_key(keys::DISPLAY_NAME, display_name.as_raw_str()), display_name);
1862                let normalized = display_name.as_normalized_str().map(|normalized| {
1863                    (self.encode_key(keys::DISPLAY_NAME, normalized), display_name)
1864                });
1865
1866                iter::once(raw).chain(normalized)
1867            })
1868            .collect::<BTreeMap<_, _>>();
1869        let names = names_map.keys().cloned().collect();
1870
1871        for (name, data) in self.read().await?.get_display_names(room_id, names).await?.into_iter()
1872        {
1873            let display_name =
1874                names_map.remove(name.as_slice()).expect("returned display names were requested");
1875            let user_ids: BTreeSet<_> = self.deserialize_json(&data)?;
1876
1877            result.entry(display_name).or_insert_with(BTreeSet::new).extend(user_ids);
1878        }
1879
1880        Ok(result)
1881    }
1882
1883    async fn get_account_data_event(
1884        &self,
1885        event_type: GlobalAccountDataEventType,
1886    ) -> Result<Option<Raw<AnyGlobalAccountDataEvent>>> {
1887        let event_type = self.encode_key(keys::GLOBAL_ACCOUNT_DATA, event_type.to_string());
1888        self.read()
1889            .await?
1890            .get_global_account_data(event_type)
1891            .await?
1892            .map(|value| self.deserialize_json(&value))
1893            .transpose()
1894    }
1895
1896    async fn get_room_account_data_event(
1897        &self,
1898        room_id: &RoomId,
1899        event_type: RoomAccountDataEventType,
1900    ) -> Result<Option<Raw<AnyRoomAccountDataEvent>>> {
1901        let room_id = self.encode_key(keys::ROOM_ACCOUNT_DATA, room_id);
1902        let event_type = self.encode_key(keys::ROOM_ACCOUNT_DATA, event_type.to_string());
1903        self.read()
1904            .await?
1905            .get_room_account_data(room_id, event_type)
1906            .await?
1907            .map(|value| self.deserialize_json(&value))
1908            .transpose()
1909    }
1910
1911    async fn get_user_room_receipt_event(
1912        &self,
1913        room_id: &RoomId,
1914        receipt_type: ReceiptType,
1915        thread: ReceiptThread,
1916        user_id: &UserId,
1917    ) -> Result<Option<(OwnedEventId, Receipt)>> {
1918        let room_id = self.encode_key(keys::RECEIPT, room_id);
1919        let receipt_type = self.encode_key(keys::RECEIPT, receipt_type.to_string());
1920        // We cannot have a NULL primary key so we rely on serialization instead of the
1921        // string representation.
1922        let thread = self.encode_key(keys::RECEIPT, rmp_serde::to_vec_named(&thread)?);
1923        let user_id = self.encode_key(keys::RECEIPT, user_id);
1924
1925        self.read()
1926            .await?
1927            .get_user_receipt(room_id, receipt_type, thread, user_id)
1928            .await?
1929            .map(|value| {
1930                self.deserialize_json::<ReceiptData>(&value).map(|d| (d.event_id, d.receipt))
1931            })
1932            .transpose()
1933    }
1934
1935    async fn get_event_room_receipt_events(
1936        &self,
1937        room_id: &RoomId,
1938        receipt_type: ReceiptType,
1939        thread: ReceiptThread,
1940        event_id: &EventId,
1941    ) -> Result<Vec<(OwnedUserId, Receipt)>> {
1942        let room_id = self.encode_key(keys::RECEIPT, room_id);
1943        let receipt_type = self.encode_key(keys::RECEIPT, receipt_type.to_string());
1944        // We cannot have a NULL primary key so we rely on serialization instead of the
1945        // string representation.
1946        let thread = self.encode_key(keys::RECEIPT, rmp_serde::to_vec_named(&thread)?);
1947        let event_id = self.encode_key(keys::RECEIPT, event_id);
1948
1949        self.read()
1950            .await?
1951            .get_event_receipts(room_id, receipt_type, thread, event_id)
1952            .await?
1953            .iter()
1954            .map(|value| {
1955                self.deserialize_json::<ReceiptData>(value).map(|d| (d.user_id, d.receipt))
1956            })
1957            .collect()
1958    }
1959
1960    async fn get_custom_value(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
1961        self.read().await?.get_kv_blob(self.encode_custom_key(key)).await
1962    }
1963
1964    async fn set_custom_value_no_read(&self, key: &[u8], value: Vec<u8>) -> Result<()> {
1965        let conn = self.write().await?;
1966        let key = self.encode_custom_key(key);
1967        conn.set_kv_blob(key, value).await?;
1968        Ok(())
1969    }
1970
1971    async fn set_custom_value(&self, key: &[u8], value: Vec<u8>) -> Result<Option<Vec<u8>>> {
1972        let conn = self.write().await?;
1973        let key = self.encode_custom_key(key);
1974        let previous = conn.get_kv_blob(key.clone()).await?;
1975        conn.set_kv_blob(key, value).await?;
1976        Ok(previous)
1977    }
1978
1979    async fn remove_custom_value(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
1980        let conn = self.write().await?;
1981        let key = self.encode_custom_key(key);
1982        let previous = conn.get_kv_blob(key.clone()).await?;
1983        if previous.is_some() {
1984            conn.delete_kv_blob(key).await?;
1985        }
1986        Ok(previous)
1987    }
1988
1989    async fn remove_room(&self, room_id: &RoomId) -> Result<()> {
1990        let this = self.clone();
1991        let room_id = room_id.to_owned();
1992
1993        let conn = self.write().await?;
1994
1995        conn.with_transaction(move |txn| -> Result<()> {
1996            let room_info_room_id = this.encode_key(keys::ROOM_INFO, &room_id);
1997            txn.remove_room_info(&room_info_room_id)?;
1998
1999            let state_event_room_id = this.encode_key(keys::STATE_EVENT, &room_id);
2000            txn.remove_room_state_events(&state_event_room_id, None)?;
2001
2002            let member_room_id = this.encode_key(keys::MEMBER, &room_id);
2003            txn.remove_room_members(&member_room_id, None)?;
2004
2005            let profile_room_id = this.encode_key(keys::PROFILE, &room_id);
2006            txn.remove_room_profiles(&profile_room_id)?;
2007
2008            let room_account_data_room_id = this.encode_key(keys::ROOM_ACCOUNT_DATA, &room_id);
2009            txn.remove_room_account_data(&room_account_data_room_id)?;
2010
2011            let receipt_room_id = this.encode_key(keys::RECEIPT, &room_id);
2012            txn.remove_room_receipts(&receipt_room_id)?;
2013
2014            let display_name_room_id = this.encode_key(keys::DISPLAY_NAME, &room_id);
2015            txn.remove_room_display_names(&display_name_room_id)?;
2016
2017            let send_queue_room_id = this.encode_key(keys::SEND_QUEUE, &room_id);
2018            txn.remove_room_send_queue(&send_queue_room_id)?;
2019
2020            let dependent_send_queue_room_id =
2021                this.encode_key(keys::DEPENDENTS_SEND_QUEUE, &room_id);
2022            txn.remove_room_dependent_send_queue(&dependent_send_queue_room_id)?;
2023
2024            let thread_subscriptions_room_id =
2025                this.encode_key(keys::THREAD_SUBSCRIPTIONS, &room_id);
2026            txn.execute(
2027                "DELETE FROM thread_subscriptions WHERE room_id = ?",
2028                (thread_subscriptions_room_id,),
2029            )?;
2030
2031            Ok(())
2032        })
2033        .await?;
2034
2035        conn.vacuum().await
2036    }
2037
2038    async fn save_send_queue_request(
2039        &self,
2040        room_id: &RoomId,
2041        transaction_id: OwnedTransactionId,
2042        created_at: MilliSecondsSinceUnixEpoch,
2043        content: QueuedRequestKind,
2044        priority: usize,
2045    ) -> Result<(), Self::Error> {
2046        let room_id_key = self.encode_key(keys::SEND_QUEUE, room_id);
2047        let room_id_value = self.serialize_value(&room_id.to_owned())?;
2048
2049        let content = self.serialize_json(&content)?;
2050        // The transaction id is used both as a key (in remove/update) and a value (as
2051        // it's useful for the callers), so we keep it as is, and neither hash
2052        // it (with encode_key) or encrypt it (through serialize_value). After
2053        // all, it carries no personal information, so this is considered fine.
2054
2055        let created_at_ts: u64 = created_at.0.into();
2056        self.write()
2057            .await?
2058            .with_transaction(move |txn| {
2059                txn.prepare_cached("INSERT INTO send_queue_events (room_id, room_id_val, transaction_id, content, priority, created_at) VALUES (?, ?, ?, ?, ?, ?)")?.execute((room_id_key, room_id_value, transaction_id.to_string(), content, priority, created_at_ts))?;
2060                Ok(())
2061            })
2062            .await
2063    }
2064
2065    async fn update_send_queue_request(
2066        &self,
2067        room_id: &RoomId,
2068        transaction_id: &TransactionId,
2069        content: QueuedRequestKind,
2070    ) -> Result<bool, Self::Error> {
2071        let room_id = self.encode_key(keys::SEND_QUEUE, room_id);
2072
2073        let content = self.serialize_json(&content)?;
2074        // See comment in [`Self::save_send_queue_request`] to understand why the
2075        // transaction id is neither encrypted or hashed.
2076        let transaction_id = transaction_id.to_string();
2077
2078        let num_updated = self.write()
2079            .await?
2080            .with_transaction(move |txn| {
2081                txn.prepare_cached("UPDATE send_queue_events SET wedge_reason = NULL, content = ? WHERE room_id = ? AND transaction_id = ?")?.execute((content, room_id, transaction_id))
2082            })
2083            .await?;
2084
2085        Ok(num_updated > 0)
2086    }
2087
2088    async fn remove_send_queue_request(
2089        &self,
2090        room_id: &RoomId,
2091        transaction_id: &TransactionId,
2092    ) -> Result<bool, Self::Error> {
2093        let room_id = self.encode_key(keys::SEND_QUEUE, room_id);
2094
2095        // See comment in `save_send_queue_request`.
2096        let transaction_id = transaction_id.to_string();
2097
2098        let num_deleted = self
2099            .write()
2100            .await?
2101            .with_transaction(move |txn| {
2102                txn.prepare_cached(
2103                    "DELETE FROM send_queue_events WHERE room_id = ? AND transaction_id = ?",
2104                )?
2105                .execute((room_id, &transaction_id))
2106            })
2107            .await?;
2108
2109        Ok(num_deleted > 0)
2110    }
2111
2112    async fn load_send_queue_requests(
2113        &self,
2114        room_id: &RoomId,
2115    ) -> Result<Vec<QueuedRequest>, Self::Error> {
2116        let room_id = self.encode_key(keys::SEND_QUEUE, room_id);
2117
2118        // Note: ROWID is always present and is an auto-incremented integer counter. We
2119        // want to maintain the insertion order, so we can sort using it.
2120        // Note 2: transaction_id is not encoded, see why in `save_send_queue_request`.
2121        let res: Vec<(String, Vec<u8>, Option<Vec<u8>>, usize, Option<u64>)> = self
2122            .read()
2123            .await?
2124            .prepare(
2125                "SELECT transaction_id, content, wedge_reason, priority, created_at FROM send_queue_events WHERE room_id = ? ORDER BY priority DESC, ROWID",
2126                |mut stmt| {
2127                    stmt.query((room_id,))?
2128                        .mapped(|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?)))
2129                        .collect()
2130                },
2131            )
2132            .await?;
2133
2134        let mut requests = Vec::with_capacity(res.len());
2135
2136        for entry in res {
2137            let created_at = entry
2138                .4
2139                .and_then(UInt::new)
2140                .map_or_else(MilliSecondsSinceUnixEpoch::now, MilliSecondsSinceUnixEpoch);
2141
2142            requests.push(QueuedRequest {
2143                transaction_id: entry.0.into(),
2144                kind: self.deserialize_json(&entry.1)?,
2145                error: entry.2.map(|v| self.deserialize_value(&v)).transpose()?,
2146                priority: entry.3,
2147                created_at,
2148            });
2149        }
2150
2151        Ok(requests)
2152    }
2153
2154    async fn update_send_queue_request_status(
2155        &self,
2156        room_id: &RoomId,
2157        transaction_id: &TransactionId,
2158        error: Option<QueueWedgeError>,
2159    ) -> Result<(), Self::Error> {
2160        let room_id = self.encode_key(keys::SEND_QUEUE, room_id);
2161
2162        // See comment in `save_send_queue_request`.
2163        let transaction_id = transaction_id.to_string();
2164
2165        // Serialize the error to json bytes (encrypted if option is enabled) if set.
2166        let error_value = error.map(|e| self.serialize_value(&e)).transpose()?;
2167
2168        self.write()
2169            .await?
2170            .with_transaction(move |txn| {
2171                txn.prepare_cached("UPDATE send_queue_events SET wedge_reason = ? WHERE room_id = ? AND transaction_id = ?")?.execute((error_value, room_id, transaction_id))?;
2172                Ok(())
2173            })
2174            .await
2175    }
2176
2177    async fn load_rooms_with_unsent_requests(&self) -> Result<Vec<OwnedRoomId>, Self::Error> {
2178        // If the values were not encrypted, we could use `SELECT DISTINCT` here, but we
2179        // have to manually do the deduplication: indeed, for all X, encrypt(X)
2180        // != encrypted(X), since we use a nonce in the encryption process.
2181
2182        let res: Vec<Vec<u8>> = self
2183            .read()
2184            .await?
2185            .prepare("SELECT room_id_val FROM send_queue_events", |mut stmt| {
2186                stmt.query(())?.mapped(|row| row.get(0)).collect()
2187            })
2188            .await?;
2189
2190        // So we collect the results into a `BTreeSet` to perform the deduplication, and
2191        // then rejigger that into a vector.
2192        Ok(res
2193            .into_iter()
2194            .map(|entry| self.deserialize_value(&entry))
2195            .collect::<Result<BTreeSet<OwnedRoomId>, _>>()?
2196            .into_iter()
2197            .collect())
2198    }
2199
2200    async fn save_dependent_queued_request(
2201        &self,
2202        room_id: &RoomId,
2203        parent_txn_id: &TransactionId,
2204        own_txn_id: ChildTransactionId,
2205        created_at: MilliSecondsSinceUnixEpoch,
2206        content: DependentQueuedRequestKind,
2207    ) -> Result<()> {
2208        let room_id = self.encode_key(keys::DEPENDENTS_SEND_QUEUE, room_id);
2209        let content = self.serialize_json(&content)?;
2210
2211        // See comment in `save_send_queue_request`.
2212        let parent_txn_id = parent_txn_id.to_string();
2213        let own_txn_id = own_txn_id.to_string();
2214
2215        let created_at_ts: u64 = created_at.0.into();
2216        self.write()
2217            .await?
2218            .with_transaction(move |txn| {
2219                txn.prepare_cached(
2220                    r#"INSERT INTO dependent_send_queue_events
2221                         (room_id, parent_transaction_id, own_transaction_id, content, created_at)
2222                       VALUES (?, ?, ?, ?, ?)"#,
2223                )?
2224                .execute((
2225                    room_id,
2226                    parent_txn_id,
2227                    own_txn_id,
2228                    content,
2229                    created_at_ts,
2230                ))?;
2231                Ok(())
2232            })
2233            .await
2234    }
2235
2236    async fn update_dependent_queued_request(
2237        &self,
2238        room_id: &RoomId,
2239        own_transaction_id: &ChildTransactionId,
2240        new_content: DependentQueuedRequestKind,
2241    ) -> Result<bool> {
2242        let room_id = self.encode_key(keys::DEPENDENTS_SEND_QUEUE, room_id);
2243        let content = self.serialize_json(&new_content)?;
2244
2245        // See comment in `save_send_queue_request`.
2246        let own_txn_id = own_transaction_id.to_string();
2247
2248        let num_updated = self
2249            .write()
2250            .await?
2251            .with_transaction(move |txn| {
2252                txn.prepare_cached(
2253                    r#"UPDATE dependent_send_queue_events
2254                       SET content = ?
2255                       WHERE own_transaction_id = ?
2256                       AND room_id = ?"#,
2257                )?
2258                .execute((content, own_txn_id, room_id))
2259            })
2260            .await?;
2261
2262        if num_updated > 1 {
2263            return Err(Error::InconsistentUpdate);
2264        }
2265
2266        Ok(num_updated == 1)
2267    }
2268
2269    async fn mark_dependent_queued_requests_as_ready(
2270        &self,
2271        room_id: &RoomId,
2272        parent_txn_id: &TransactionId,
2273        parent_key: SentRequestKey,
2274    ) -> Result<usize> {
2275        let room_id = self.encode_key(keys::DEPENDENTS_SEND_QUEUE, room_id);
2276        let parent_key = self.serialize_json(&parent_key)?;
2277
2278        // See comment in `save_send_queue_request`.
2279        let parent_txn_id = parent_txn_id.to_string();
2280
2281        self.write()
2282            .await?
2283            .with_transaction(move |txn| {
2284                Ok(txn.prepare_cached(
2285                    "UPDATE dependent_send_queue_events SET parent_key = ? WHERE parent_transaction_id = ? and room_id = ?",
2286                )?
2287                .execute((parent_key, parent_txn_id, room_id))?)
2288            })
2289            .await
2290    }
2291
2292    async fn remove_dependent_queued_request(
2293        &self,
2294        room_id: &RoomId,
2295        txn_id: &ChildTransactionId,
2296    ) -> Result<bool> {
2297        let room_id = self.encode_key(keys::DEPENDENTS_SEND_QUEUE, room_id);
2298
2299        // See comment in `save_send_queue_request`.
2300        let txn_id = txn_id.to_string();
2301
2302        let num_deleted = self
2303            .write()
2304            .await?
2305            .with_transaction(move |txn| {
2306                txn.prepare_cached(
2307                    "DELETE FROM dependent_send_queue_events WHERE own_transaction_id = ? AND room_id = ?",
2308                )?
2309                .execute((txn_id, room_id))
2310            })
2311            .await?;
2312
2313        Ok(num_deleted > 0)
2314    }
2315
2316    async fn load_dependent_queued_requests(
2317        &self,
2318        room_id: &RoomId,
2319    ) -> Result<Vec<DependentQueuedRequest>> {
2320        let room_id = self.encode_key(keys::DEPENDENTS_SEND_QUEUE, room_id);
2321
2322        // Note: transaction_id is not encoded, see why in `save_send_queue_request`.
2323        let res: Vec<(String, String, Option<Vec<u8>>, Vec<u8>, Option<u64>)> = self
2324            .read()
2325            .await?
2326            .prepare(
2327                "SELECT own_transaction_id, parent_transaction_id, parent_key, content, created_at FROM dependent_send_queue_events WHERE room_id = ? ORDER BY ROWID",
2328                |mut stmt| {
2329                    stmt.query((room_id,))?
2330                        .mapped(|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?)))
2331                        .collect()
2332                },
2333            )
2334            .await?;
2335
2336        let mut dependent_events = Vec::with_capacity(res.len());
2337
2338        for entry in res {
2339            let created_at = entry
2340                .4
2341                .and_then(UInt::new)
2342                .map_or_else(MilliSecondsSinceUnixEpoch::now, MilliSecondsSinceUnixEpoch);
2343
2344            dependent_events.push(DependentQueuedRequest {
2345                own_transaction_id: entry.0.into(),
2346                parent_transaction_id: entry.1.into(),
2347                parent_key: entry.2.map(|json| self.deserialize_json(&json)).transpose()?,
2348                kind: self.deserialize_json(&entry.3)?,
2349                created_at,
2350            });
2351        }
2352
2353        Ok(dependent_events)
2354    }
2355
2356    async fn upsert_thread_subscriptions(
2357        &self,
2358        updates: Vec<(&RoomId, &EventId, StoredThreadSubscription)>,
2359    ) -> Result<(), Self::Error> {
2360        let values: Vec<_> = updates
2361            .into_iter()
2362            .map(|(room_id, thread_id, subscription)| {
2363                (
2364                    self.encode_key(keys::THREAD_SUBSCRIPTIONS, room_id),
2365                    self.encode_key(keys::THREAD_SUBSCRIPTIONS, thread_id),
2366                    subscription.status.as_str(),
2367                    subscription.bump_stamp,
2368                )
2369            })
2370            .collect();
2371
2372        self.write()
2373            .await?
2374            .with_transaction(move |txn| {
2375                let mut txn = txn.prepare_cached(
2376                    "INSERT INTO thread_subscriptions (room_id, event_id, status, bump_stamp)
2377                    VALUES (?, ?, ?, ?)
2378                    ON CONFLICT (room_id, event_id) DO UPDATE
2379                    SET
2380                        status =
2381                            CASE
2382                                WHEN thread_subscriptions.bump_stamp IS NULL THEN EXCLUDED.status
2383                                WHEN EXCLUDED.bump_stamp IS NULL THEN EXCLUDED.status
2384                                WHEN thread_subscriptions.bump_stamp < EXCLUDED.bump_stamp THEN EXCLUDED.status
2385                                ELSE thread_subscriptions.status
2386                            END,
2387                        bump_stamp =
2388                            CASE
2389                                WHEN thread_subscriptions.bump_stamp IS NULL THEN EXCLUDED.bump_stamp
2390                                WHEN EXCLUDED.bump_stamp IS NULL THEN thread_subscriptions.bump_stamp
2391                                WHEN thread_subscriptions.bump_stamp < EXCLUDED.bump_stamp THEN EXCLUDED.bump_stamp
2392                                ELSE thread_subscriptions.bump_stamp
2393                            END",
2394                )?;
2395
2396                for value in values {
2397                    txn.execute(value)?;
2398                }
2399
2400                Result::<_, Error>::Ok(())
2401            })
2402            .await?;
2403
2404        Ok(())
2405    }
2406
2407    async fn load_thread_subscription(
2408        &self,
2409        room_id: &RoomId,
2410        thread_id: &EventId,
2411    ) -> Result<Option<StoredThreadSubscription>, Self::Error> {
2412        let room_id = self.encode_key(keys::THREAD_SUBSCRIPTIONS, room_id);
2413        let thread_id = self.encode_key(keys::THREAD_SUBSCRIPTIONS, thread_id);
2414
2415        Ok(self
2416            .read()
2417            .await?
2418            .query_row(
2419                "SELECT status, bump_stamp FROM thread_subscriptions WHERE room_id = ? AND event_id = ?",
2420                (room_id, thread_id),
2421                |row| Ok((row.get::<_, String>(0)?, row.get::<_, Option<u64>>(1)?))
2422            )
2423            .await
2424            .optional()?
2425            .map(|(status, bump_stamp)| -> Result<_, Self::Error> {
2426                let status = ThreadSubscriptionStatus::from_str(&status).map_err(|_| {
2427                    Error::InvalidData { details: format!("Invalid thread status: {status}") }
2428                })?;
2429                Ok(StoredThreadSubscription { status, bump_stamp })
2430            })
2431            .transpose()?)
2432    }
2433
2434    async fn remove_thread_subscription(
2435        &self,
2436        room_id: &RoomId,
2437        thread_id: &EventId,
2438    ) -> Result<(), Self::Error> {
2439        let room_id = self.encode_key(keys::THREAD_SUBSCRIPTIONS, room_id);
2440        let thread_id = self.encode_key(keys::THREAD_SUBSCRIPTIONS, thread_id);
2441
2442        self.write()
2443            .await?
2444            .execute(
2445                "DELETE FROM thread_subscriptions WHERE room_id = ? AND event_id = ?",
2446                (room_id, thread_id),
2447            )
2448            .await?;
2449
2450        Ok(())
2451    }
2452
2453    async fn get_global_profile(
2454        &self,
2455        user_id: &UserId,
2456    ) -> Result<Option<UserProfile>, Self::Error> {
2457        self.read()
2458            .await?
2459            .get_global_profiles(vec![self.encode_key(keys::GLOBAL_PROFILES, user_id)])
2460            .await?
2461            .into_iter()
2462            .next()
2463            .map(|(_, data)| self.deserialize_json(&data))
2464            .transpose()
2465    }
2466
2467    async fn get_global_profiles<'a>(
2468        &self,
2469        user_ids: &'a [OwnedUserId],
2470    ) -> Result<BTreeMap<&'a UserId, UserProfile>, Self::Error> {
2471        if user_ids.is_empty() {
2472            return Ok(BTreeMap::new());
2473        }
2474
2475        let mut user_ids_map = user_ids
2476            .iter()
2477            .map(|u| (self.encode_key(keys::GLOBAL_PROFILES, u), u.as_ref()))
2478            .collect::<BTreeMap<_, _>>();
2479        let user_ids = user_ids_map.keys().cloned().collect();
2480
2481        self.read()
2482            .await?
2483            .get_global_profiles(user_ids)
2484            .await?
2485            .into_iter()
2486            .map(|(user_id, data)| {
2487                Ok((
2488                    user_ids_map
2489                        .remove(user_id.as_slice())
2490                        .expect("returned user IDs were requested"),
2491                    self.deserialize_json(&data)?,
2492                ))
2493            })
2494            .collect()
2495    }
2496
2497    async fn optimize(&self) -> Result<(), Self::Error> {
2498        Ok(self.vacuum().await?)
2499    }
2500
2501    async fn get_size(&self) -> Result<Option<usize>, Self::Error> {
2502        self.get_db_size().await
2503    }
2504
2505    async fn close(&self) -> Result<(), Self::Error> {
2506        connection::close_connections(&self.connections, "State store").await;
2507        Ok(())
2508    }
2509
2510    async fn reopen(&self) -> Result<(), Self::Error> {
2511        connection::reopen_connections(
2512            &self.connections,
2513            self.db_path.clone(),
2514            self.pool_config,
2515            self.runtime_config,
2516        )
2517        .await?;
2518        Ok(())
2519    }
2520}
2521
2522#[derive(Debug, Clone, Serialize, Deserialize)]
2523struct ReceiptData {
2524    receipt: Receipt,
2525    event_id: OwnedEventId,
2526    user_id: OwnedUserId,
2527}
2528
2529#[cfg(test)]
2530mod tests {
2531    use std::sync::{
2532        LazyLock,
2533        atomic::{AtomicU32, Ordering::SeqCst},
2534    };
2535
2536    use matrix_sdk_base::{StateStore, StoreError, statestore_integration_tests};
2537    use tempfile::{TempDir, tempdir};
2538
2539    use super::SqliteStateStore;
2540
2541    static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
2542    static NUM: AtomicU32 = AtomicU32::new(0);
2543
2544    async fn get_store() -> Result<impl StateStore, StoreError> {
2545        let name = NUM.fetch_add(1, SeqCst).to_string();
2546        let tmpdir_path = TMP_DIR.path().join(name);
2547
2548        tracing::info!("using store @ {}", tmpdir_path.to_str().unwrap());
2549
2550        Ok(SqliteStateStore::open(tmpdir_path.to_str().unwrap(), None).await.unwrap())
2551    }
2552
2553    statestore_integration_tests!();
2554}
2555
2556#[cfg(test)]
2557mod encrypted_tests {
2558    use std::{
2559        path::PathBuf,
2560        sync::{
2561            LazyLock,
2562            atomic::{AtomicU32, Ordering::SeqCst},
2563        },
2564    };
2565
2566    use matrix_sdk_base::{StateStore, StoreError, statestore_integration_tests};
2567    use matrix_sdk_test::async_test;
2568    use tempfile::{TempDir, tempdir};
2569
2570    use super::SqliteStateStore;
2571    use crate::{SqliteStoreConfig, utils::SqliteAsyncConnExt};
2572
2573    static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
2574    static NUM: AtomicU32 = AtomicU32::new(0);
2575
2576    fn new_state_store_workspace() -> PathBuf {
2577        let name = NUM.fetch_add(1, SeqCst).to_string();
2578        TMP_DIR.path().join(name)
2579    }
2580
2581    async fn get_store() -> Result<impl StateStore, StoreError> {
2582        let tmpdir_path = new_state_store_workspace();
2583
2584        tracing::info!("using store @ {}", tmpdir_path.to_str().unwrap());
2585
2586        Ok(SqliteStateStore::open(tmpdir_path.to_str().unwrap(), Some("default_test_password"))
2587            .await
2588            .unwrap())
2589    }
2590
2591    #[async_test]
2592    async fn test_pool_size() {
2593        let tmpdir_path = new_state_store_workspace();
2594        let store_open_config = SqliteStoreConfig::new(tmpdir_path).pool_max_size(42);
2595
2596        let store = SqliteStateStore::open_with_config(&store_open_config).await.unwrap();
2597
2598        let guard = store.connections.lock().await;
2599        assert_eq!(guard.as_ref().unwrap().pool.status().max_size, 42);
2600    }
2601
2602    #[async_test]
2603    async fn test_cache_size() {
2604        let tmpdir_path = new_state_store_workspace();
2605        let store_open_config = SqliteStoreConfig::new(tmpdir_path).cache_size(1500);
2606
2607        let store = SqliteStateStore::open_with_config(&store_open_config).await.unwrap();
2608
2609        let conn = store.read().await.unwrap();
2610        let cache_size =
2611            conn.query_row("PRAGMA cache_size", (), |row| row.get::<_, i32>(0)).await.unwrap();
2612
2613        // The value passed to `SqliteStoreConfig` is in bytes. Check it is
2614        // converted to kibibytes. Also, it must be a negative value because it
2615        // _is_ the size in kibibytes, not in page size.
2616        assert_eq!(cache_size, -(1500 / 1024));
2617    }
2618
2619    #[async_test]
2620    async fn test_journal_size_limit() {
2621        let tmpdir_path = new_state_store_workspace();
2622        let store_open_config = SqliteStoreConfig::new(tmpdir_path).journal_size_limit(1500);
2623
2624        let store = SqliteStateStore::open_with_config(&store_open_config).await.unwrap();
2625
2626        let conn = store.read().await.unwrap();
2627        let journal_size_limit = conn
2628            .query_row("PRAGMA journal_size_limit", (), |row| row.get::<_, u32>(0))
2629            .await
2630            .unwrap();
2631
2632        // The value passed to `SqliteStoreConfig` is in bytes. It stays in
2633        // bytes in SQLite.
2634        assert_eq!(journal_size_limit, 1500);
2635    }
2636
2637    statestore_integration_tests!();
2638}
2639
2640#[cfg(test)]
2641mod migration_tests {
2642    use std::{
2643        path::{Path, PathBuf},
2644        sync::{
2645            Arc, LazyLock,
2646            atomic::{AtomicU32, Ordering::SeqCst},
2647        },
2648    };
2649
2650    use as_variant::as_variant;
2651    use matrix_sdk_base::{
2652        RoomState, StateStore,
2653        media::{MediaFormat, MediaRequestParameters},
2654        store::{
2655            ChildTransactionId, DependentQueuedRequestKind, RoomLoadSettings,
2656            SerializableEventContent,
2657        },
2658        sync::UnreadNotificationsCount,
2659    };
2660    use matrix_sdk_test::async_test;
2661    use ruma::{
2662        EventId, MilliSecondsSinceUnixEpoch, OwnedTransactionId, RoomId, TransactionId, UserId,
2663        events::{
2664            StateEventType,
2665            room::{MediaSource, create::RoomCreateEventContent, message::RoomMessageEventContent},
2666        },
2667        room_id, server_name, user_id,
2668    };
2669    use rusqlite::Transaction;
2670    use serde::{Deserialize, Serialize};
2671    use serde_json::json;
2672    use tempfile::{TempDir, tempdir};
2673    use tokio::{fs, sync::Mutex};
2674    use zeroize::Zeroizing;
2675
2676    use super::{DATABASE_NAME, SqliteStateStore, init, keys};
2677    use crate::{
2678        OpenStoreError, Secret, SqliteStoreConfig, connection,
2679        error::{Error, Result},
2680        utils::{EncryptableStore as _, SqliteAsyncConnExt, SqliteKeyValueStoreAsyncConnExt},
2681    };
2682
2683    static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
2684    static NUM: AtomicU32 = AtomicU32::new(0);
2685    const SECRET: &str = "secret";
2686
2687    fn new_path() -> PathBuf {
2688        let name = NUM.fetch_add(1, SeqCst).to_string();
2689        TMP_DIR.path().join(name)
2690    }
2691
2692    async fn create_fake_db(path: &Path, version: u8) -> Result<SqliteStateStore> {
2693        let config = SqliteStoreConfig::new(path);
2694
2695        fs::create_dir_all(&config.path).await.map_err(OpenStoreError::CreateDir).unwrap();
2696
2697        let pool = config.build_pool_of_connections(DATABASE_NAME).unwrap();
2698        let db_path = pool.manager().database_path.clone();
2699        let conn = pool.get().await?;
2700
2701        init(&conn).await?;
2702
2703        let store_cipher = Some(Arc::new(
2704            conn.get_or_create_store_cipher(Secret::PassPhrase(Zeroizing::new(SECRET.to_owned())))
2705                .await
2706                .unwrap(),
2707        ));
2708        let this = SqliteStateStore {
2709            store_cipher,
2710            connections: Arc::new(Mutex::new(Some(connection::SqliteConnections {
2711                pool,
2712                write_connection: Arc::new(Mutex::new(conn)),
2713            }))),
2714            db_path,
2715            pool_config: deadpool::managed::PoolConfig::default(),
2716            runtime_config: crate::RuntimeConfig::default(),
2717        };
2718        this.run_migrations(1, Some(version)).await?;
2719
2720        Ok(this)
2721    }
2722
2723    fn room_info_v1_json(
2724        room_id: &RoomId,
2725        state: RoomState,
2726        name: Option<&str>,
2727        creator: Option<&UserId>,
2728    ) -> serde_json::Value {
2729        // Test with name set or not.
2730        let name_content = match name {
2731            Some(name) => json!({ "name": name }),
2732            None => json!({ "name": null }),
2733        };
2734        // Test with creator set or not.
2735        let create_content = match creator {
2736            Some(creator) => RoomCreateEventContent::new_v1(creator.to_owned()),
2737            None => RoomCreateEventContent::new_v11(),
2738        };
2739
2740        json!({
2741            "room_id": room_id,
2742            "room_type": state,
2743            "notification_counts": UnreadNotificationsCount::default(),
2744            "summary": {
2745                "heroes": [],
2746                "joined_member_count": 0,
2747                "invited_member_count": 0,
2748            },
2749            "members_synced": false,
2750            "base_info": {
2751                "dm_targets": [],
2752                "max_power_level": 100,
2753                "name": {
2754                    "Original": {
2755                        "content": name_content,
2756                    },
2757                },
2758                "create": {
2759                    "Original": {
2760                        "content": create_content,
2761                    }
2762                }
2763            },
2764        })
2765    }
2766
2767    #[async_test]
2768    pub async fn test_migrating_v1_to_v2() {
2769        let path = new_path();
2770        // Create and populate db.
2771        {
2772            let db = create_fake_db(&path, 1).await.unwrap();
2773            let conn = db.read().await.unwrap();
2774
2775            let this = db.clone();
2776            conn.with_transaction(move |txn| {
2777                for i in 0..5 {
2778                    let room_id = RoomId::parse(format!("!room_{i}:localhost")).unwrap();
2779                    let (state, stripped) =
2780                        if i < 3 { (RoomState::Joined, false) } else { (RoomState::Invited, true) };
2781                    let info = room_info_v1_json(&room_id, state, None, None);
2782
2783                    let room_id = this.encode_key(keys::ROOM_INFO, room_id);
2784                    let data = this.serialize_json(&info)?;
2785
2786                    txn.prepare_cached(
2787                        "INSERT INTO room_info (room_id, stripped, data)
2788                         VALUES (?, ?, ?)",
2789                    )?
2790                    .execute((room_id, stripped, data))?;
2791                }
2792
2793                Result::<_, Error>::Ok(())
2794            })
2795            .await
2796            .unwrap();
2797        }
2798
2799        // This transparently migrates to the latest version.
2800        let store = SqliteStateStore::open(path, Some(SECRET)).await.unwrap();
2801
2802        // Check all room infos are there.
2803        assert_eq!(store.get_room_infos(&RoomLoadSettings::default()).await.unwrap().len(), 5);
2804    }
2805
2806    // Add a room in version 2 format of the state store.
2807    fn add_room_v2(
2808        this: &SqliteStateStore,
2809        txn: &Transaction<'_>,
2810        room_id: &RoomId,
2811        name: Option<&str>,
2812        create_creator: Option<&UserId>,
2813        create_sender: Option<&UserId>,
2814    ) -> Result<(), Error> {
2815        let room_info_json = room_info_v1_json(room_id, RoomState::Joined, name, create_creator);
2816
2817        let encoded_room_id = this.encode_key(keys::ROOM_INFO, room_id);
2818        let encoded_state =
2819            this.encode_key(keys::ROOM_INFO, serde_json::to_string(&RoomState::Joined)?);
2820        let data = this.serialize_json(&room_info_json)?;
2821
2822        txn.prepare_cached(
2823            "INSERT INTO room_info (room_id, state, data)
2824             VALUES (?, ?, ?)",
2825        )?
2826        .execute((encoded_room_id, encoded_state, data))?;
2827
2828        // Test with or without `m.room.create` event in the room state.
2829        let Some(create_sender) = create_sender else {
2830            return Ok(());
2831        };
2832
2833        let create_content = match create_creator {
2834            Some(creator) => RoomCreateEventContent::new_v1(creator.to_owned()),
2835            None => RoomCreateEventContent::new_v11(),
2836        };
2837
2838        let event_id = EventId::new_v1(server_name!("dummy.local"));
2839        let create_event = json!({
2840            "content": create_content,
2841            "event_id": event_id,
2842            "sender": create_sender.to_owned(),
2843            "origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
2844            "state_key": "",
2845            "type": "m.room.create",
2846            "unsigned": {},
2847        });
2848
2849        let encoded_room_id = this.encode_key(keys::STATE_EVENT, room_id);
2850        let encoded_event_type =
2851            this.encode_key(keys::STATE_EVENT, StateEventType::RoomCreate.to_string());
2852        let encoded_state_key = this.encode_key(keys::STATE_EVENT, "");
2853        let stripped = false;
2854        let encoded_event_id = this.encode_key(keys::STATE_EVENT, event_id);
2855        let data = this.serialize_json(&create_event)?;
2856
2857        txn.prepare_cached(
2858            "INSERT
2859             INTO state_event (room_id, event_type, state_key, stripped, event_id, data)
2860             VALUES (?, ?, ?, ?, ?, ?)",
2861        )?
2862        .execute((
2863            encoded_room_id,
2864            encoded_event_type,
2865            encoded_state_key,
2866            stripped,
2867            encoded_event_id,
2868            data,
2869        ))?;
2870
2871        Ok(())
2872    }
2873
2874    #[async_test]
2875    pub async fn test_migrating_v2_to_v3() {
2876        let path = new_path();
2877
2878        // Room A: with name, creator and sender.
2879        let room_a_id = room_id!("!room_a:dummy.local");
2880        let room_a_name = "Room A";
2881        let room_a_creator = user_id!("@creator:dummy.local");
2882        // Use a different sender to check that sender is used over creator in
2883        // migration.
2884        let room_a_create_sender = user_id!("@sender:dummy.local");
2885
2886        // Room B: without name, creator and sender.
2887        let room_b_id = room_id!("!room_b:dummy.local");
2888
2889        // Room C: only with sender.
2890        let room_c_id = room_id!("!room_c:dummy.local");
2891        let room_c_create_sender = user_id!("@creator:dummy.local");
2892
2893        // Create and populate db.
2894        {
2895            let db = create_fake_db(&path, 2).await.unwrap();
2896            let conn = db.read().await.unwrap();
2897
2898            let this = db.clone();
2899            conn.with_transaction(move |txn| {
2900                add_room_v2(
2901                    &this,
2902                    txn,
2903                    room_a_id,
2904                    Some(room_a_name),
2905                    Some(room_a_creator),
2906                    Some(room_a_create_sender),
2907                )?;
2908                add_room_v2(&this, txn, room_b_id, None, None, None)?;
2909                add_room_v2(&this, txn, room_c_id, None, None, Some(room_c_create_sender))?;
2910
2911                Result::<_, Error>::Ok(())
2912            })
2913            .await
2914            .unwrap();
2915        }
2916
2917        // This transparently migrates to the latest version.
2918        let store = SqliteStateStore::open(path, Some(SECRET)).await.unwrap();
2919
2920        // Check all room infos are there.
2921        let room_infos = store.get_room_infos(&RoomLoadSettings::default()).await.unwrap();
2922        assert_eq!(room_infos.len(), 3);
2923
2924        let room_a = room_infos.iter().find(|r| r.room_id() == room_a_id).unwrap();
2925        assert_eq!(room_a.name(), Some(room_a_name));
2926        assert_eq!(room_a.creators(), Some(vec![room_a_create_sender.to_owned()]));
2927
2928        let room_b = room_infos.iter().find(|r| r.room_id() == room_b_id).unwrap();
2929        assert_eq!(room_b.name(), None);
2930        assert_eq!(room_b.creators(), None);
2931
2932        let room_c = room_infos.iter().find(|r| r.room_id() == room_c_id).unwrap();
2933        assert_eq!(room_c.name(), None);
2934        assert_eq!(room_c.creators(), Some(vec![room_c_create_sender.to_owned()]));
2935    }
2936
2937    #[async_test]
2938    pub async fn test_migrating_v7_to_v9() {
2939        let path = new_path();
2940
2941        let room_id = room_id!("!room_a:dummy.local");
2942        let wedged_event_transaction_id = TransactionId::new();
2943        let local_event_transaction_id = TransactionId::new();
2944
2945        // Create and populate db.
2946        {
2947            let db = create_fake_db(&path, 7).await.unwrap();
2948            let conn = db.read().await.unwrap();
2949
2950            let wedge_tx = wedged_event_transaction_id.clone();
2951            let local_tx = local_event_transaction_id.clone();
2952
2953            conn.with_transaction(move |txn| {
2954                add_dependent_send_queue_event_v7(
2955                    &db,
2956                    txn,
2957                    room_id,
2958                    &local_tx,
2959                    ChildTransactionId::new(),
2960                    DependentQueuedRequestKind::RedactEvent,
2961                )?;
2962                add_send_queue_event_v7(&db, txn, &wedge_tx, room_id, true)?;
2963                add_send_queue_event_v7(&db, txn, &local_tx, room_id, false)?;
2964                Result::<_, Error>::Ok(())
2965            })
2966            .await
2967            .unwrap();
2968        }
2969
2970        // This transparently migrates to the latest version, which clears up all
2971        // requests and dependent requests.
2972        let store = SqliteStateStore::open(path, Some(SECRET)).await.unwrap();
2973
2974        let requests = store.load_send_queue_requests(room_id).await.unwrap();
2975        assert!(requests.is_empty());
2976
2977        let dependent_requests = store.load_dependent_queued_requests(room_id).await.unwrap();
2978        assert!(dependent_requests.is_empty());
2979    }
2980
2981    fn add_send_queue_event_v7(
2982        this: &SqliteStateStore,
2983        txn: &Transaction<'_>,
2984        transaction_id: &TransactionId,
2985        room_id: &RoomId,
2986        is_wedged: bool,
2987    ) -> Result<(), Error> {
2988        let content =
2989            SerializableEventContent::new(&RoomMessageEventContent::text_plain("Hello").into())?;
2990
2991        let room_id_key = this.encode_key(keys::SEND_QUEUE, room_id);
2992        let room_id_value = this.serialize_value(&room_id.to_owned())?;
2993
2994        let content = this.serialize_json(&content)?;
2995
2996        txn.prepare_cached("INSERT INTO send_queue_events (room_id, room_id_val, transaction_id, content, wedged) VALUES (?, ?, ?, ?, ?)")?
2997            .execute((room_id_key, room_id_value, transaction_id.to_string(), content, is_wedged))?;
2998
2999        Ok(())
3000    }
3001
3002    fn add_dependent_send_queue_event_v7(
3003        this: &SqliteStateStore,
3004        txn: &Transaction<'_>,
3005        room_id: &RoomId,
3006        parent_txn_id: &TransactionId,
3007        own_txn_id: ChildTransactionId,
3008        content: DependentQueuedRequestKind,
3009    ) -> Result<(), Error> {
3010        let room_id_value = this.serialize_value(&room_id.to_owned())?;
3011
3012        let parent_txn_id = parent_txn_id.to_string();
3013        let own_txn_id = own_txn_id.to_string();
3014        let content = this.serialize_json(&content)?;
3015
3016        txn.prepare_cached(
3017            "INSERT INTO dependent_send_queue_events
3018                         (room_id, parent_transaction_id, own_transaction_id, content)
3019                       VALUES (?, ?, ?, ?)",
3020        )?
3021        .execute((room_id_value, parent_txn_id, own_txn_id, content))?;
3022
3023        Ok(())
3024    }
3025
3026    #[derive(Clone, Debug, Serialize, Deserialize)]
3027    pub enum LegacyDependentQueuedRequestKind {
3028        UploadFileWithThumbnail {
3029            content_type: String,
3030            cache_key: MediaRequestParameters,
3031            related_to: OwnedTransactionId,
3032        },
3033    }
3034
3035    #[async_test]
3036    pub async fn test_dependent_queued_request_variant_renaming() {
3037        let path = new_path();
3038        let db = create_fake_db(&path, 7).await.unwrap();
3039
3040        let cache_key = MediaRequestParameters {
3041            format: MediaFormat::File,
3042            source: MediaSource::Plain("https://server.local/foobar".into()),
3043        };
3044        let related_to = TransactionId::new();
3045        let request = LegacyDependentQueuedRequestKind::UploadFileWithThumbnail {
3046            content_type: "image/png".to_owned(),
3047            cache_key,
3048            related_to: related_to.clone(),
3049        };
3050
3051        let data = db
3052            .serialize_json(&request)
3053            .expect("should be able to serialize legacy dependent request");
3054        let deserialized: DependentQueuedRequestKind = db.deserialize_json(&data).expect(
3055            "should be able to deserialize dependent request from legacy dependent request",
3056        );
3057
3058        as_variant!(deserialized, DependentQueuedRequestKind::UploadFileOrThumbnail { related_to: de_related_to, .. } => {
3059            assert_eq!(de_related_to, related_to);
3060        });
3061    }
3062}
3063
3064#[cfg(test)]
3065mod close_reopen_tests {
3066    use std::sync::{
3067        LazyLock,
3068        atomic::{AtomicU32, Ordering::SeqCst},
3069    };
3070
3071    use matrix_sdk_base::StateStore;
3072    use matrix_sdk_test::async_test;
3073    use tempfile::{TempDir, tempdir};
3074
3075    use super::SqliteStateStore;
3076
3077    static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
3078    static NUM: AtomicU32 = AtomicU32::new(0);
3079
3080    async fn new_store() -> SqliteStateStore {
3081        let name = NUM.fetch_add(1, SeqCst).to_string();
3082        let tmpdir_path = TMP_DIR.path().join(name);
3083        SqliteStateStore::open(tmpdir_path.to_str().unwrap(), None).await.unwrap()
3084    }
3085
3086    #[async_test]
3087    async fn test_close_completes_without_timeout() {
3088        let store = new_store().await;
3089
3090        // Close should complete quickly without hitting the 5s timeout.
3091        let start = std::time::Instant::now();
3092        store.close().await.unwrap();
3093        let elapsed = start.elapsed();
3094
3095        assert!(
3096            elapsed < std::time::Duration::from_secs(2),
3097            "close() took {elapsed:?}, expected < 2s (no timeout)"
3098        );
3099
3100        // Connections should be None after close.
3101        let guard = store.connections.lock().await;
3102        assert!(guard.is_none(), "connections should be None after close");
3103    }
3104
3105    #[async_test]
3106    async fn test_reopen_restores_connections() {
3107        let store = new_store().await;
3108
3109        store.close().await.unwrap();
3110
3111        // Connections should be None after close.
3112        {
3113            let guard = store.connections.lock().await;
3114            assert!(guard.is_none());
3115        }
3116
3117        store.reopen().await.unwrap();
3118
3119        // Connections should be Some after reopen.
3120        {
3121            let guard = store.connections.lock().await;
3122            assert!(guard.is_some(), "connections should be Some after reopen");
3123        }
3124    }
3125
3126    #[async_test]
3127    async fn test_close_is_idempotent() {
3128        let store = new_store().await;
3129
3130        // First close.
3131        store.close().await.unwrap();
3132        // Second close should also succeed (no-op).
3133        store.close().await.unwrap();
3134
3135        let guard = store.connections.lock().await;
3136        assert!(guard.is_none());
3137    }
3138
3139    #[async_test]
3140    async fn test_reopen_is_idempotent() {
3141        let store = new_store().await;
3142
3143        // Reopen on an active store should be a no-op.
3144        store.reopen().await.unwrap();
3145
3146        // Connections should still be Some.
3147        let guard = store.connections.lock().await;
3148        assert!(guard.is_some());
3149    }
3150
3151    #[async_test]
3152    async fn test_read_fails_when_closed() {
3153        let store = new_store().await;
3154        store.close().await.unwrap();
3155
3156        let err = store.get_custom_value(b"some_key").await;
3157        assert!(err.is_err(), "read should fail when closed");
3158
3159        let err_msg = err.unwrap_err().to_string();
3160        assert!(err_msg.contains("closed"), "error should mention 'closed', got: {err_msg}");
3161    }
3162
3163    #[async_test]
3164    async fn test_write_fails_when_closed() {
3165        let store = new_store().await;
3166        store.close().await.unwrap();
3167
3168        let err = store.set_custom_value(b"key", b"value".to_vec()).await;
3169        assert!(err.is_err(), "write should fail when closed");
3170
3171        let err_msg = err.unwrap_err().to_string();
3172        assert!(err_msg.contains("closed"), "error should mention 'closed', got: {err_msg}");
3173    }
3174
3175    #[async_test]
3176    async fn test_data_persists_across_close_reopen() {
3177        let store = new_store().await;
3178
3179        // Write some data.
3180        store.set_custom_value(b"test_key", b"test_value".to_vec()).await.unwrap();
3181
3182        // Verify it's there.
3183        let value = store.get_custom_value(b"test_key").await.unwrap();
3184        assert_eq!(value.as_deref(), Some(b"test_value".as_slice()));
3185
3186        // Close and reopen.
3187        store.close().await.unwrap();
3188        store.reopen().await.unwrap();
3189
3190        // Data should still be there after reopen.
3191        let value = store.get_custom_value(b"test_key").await.unwrap();
3192        assert_eq!(
3193            value.as_deref(),
3194            Some(b"test_value".as_slice()),
3195            "data should persist across close/reopen"
3196        );
3197    }
3198
3199    #[async_test]
3200    async fn test_multiple_close_reopen_cycles() {
3201        let store = new_store().await;
3202
3203        for i in 0..3 {
3204            let key = format!("key_{i}");
3205            let value = format!("value_{i}");
3206
3207            store.set_custom_value(key.as_bytes(), value.as_bytes().to_vec()).await.unwrap();
3208
3209            store.close().await.unwrap();
3210            store.reopen().await.unwrap();
3211
3212            // Verify all previously written data is still accessible.
3213            for j in 0..=i {
3214                let k = format!("key_{j}");
3215                let v = format!("value_{j}");
3216                let retrieved = store.get_custom_value(k.as_bytes()).await.unwrap();
3217                assert_eq!(
3218                    retrieved.as_deref(),
3219                    Some(v.as_bytes()),
3220                    "data for key_{j} should persist after cycle {i}"
3221                );
3222            }
3223        }
3224    }
3225
3226    #[async_test]
3227    async fn test_pool_is_fully_drained_after_close() {
3228        let store = new_store().await;
3229
3230        // Do a few reads to exercise the pool.
3231        let _ = store.get_custom_value(b"key1").await;
3232        let _ = store.get_custom_value(b"key2").await;
3233
3234        store.close().await.unwrap();
3235
3236        // After close, the connections field should be None (pool and write
3237        // connection have been fully torn down).
3238        let guard = store.connections.lock().await;
3239        assert!(guard.is_none(), "all connections should be released after close");
3240    }
3241
3242    #[async_test]
3243    async fn test_operations_work_immediately_after_reopen() {
3244        let store = new_store().await;
3245
3246        store.close().await.unwrap();
3247        store.reopen().await.unwrap();
3248
3249        // Write should work immediately.
3250        store.set_custom_value(b"after_reopen", b"works".to_vec()).await.unwrap();
3251
3252        // Read should work immediately.
3253        let value = store.get_custom_value(b"after_reopen").await.unwrap();
3254        assert_eq!(value.as_deref(), Some(b"works".as_slice()));
3255    }
3256
3257    #[async_test]
3258    async fn test_close_waits_for_held_read_connection_to_drain() {
3259        let store = new_store().await;
3260
3261        // Acquire a read connection and hold it, simulating an in-flight read.
3262        let held_conn = store.read().await.unwrap();
3263
3264        // Spawn close in a background task — it will close the pool and then
3265        // poll-wait for pool.status().size == 0 in the drain loop.
3266        let store_clone = store.clone();
3267        let close_handle = tokio::spawn(async move {
3268            store_clone.close().await.unwrap();
3269        });
3270
3271        // Give close() a moment to close the pool and enter the drain loop.
3272        tokio::time::sleep(std::time::Duration::from_millis(200)).await;
3273
3274        // The close task should still be running because we hold a connection.
3275        assert!(!close_handle.is_finished(), "close should be waiting for the held connection");
3276
3277        // Release the held connection — this lets pool.status().size drop to 0.
3278        drop(held_conn);
3279
3280        // Now close should complete promptly (well within the 5s timeout).
3281        let timeout = tokio::time::timeout(std::time::Duration::from_secs(3), close_handle).await;
3282        assert!(timeout.is_ok(), "close should complete after the held connection is released");
3283        timeout.unwrap().unwrap();
3284
3285        // Verify the store is fully closed.
3286        let guard = store.connections.lock().await;
3287        assert!(guard.is_none(), "connections should be None after close");
3288    }
3289}