Skip to main content

matrix_sdk_sqlite/
event_cache_store.rs

1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! An SQLite-based backend for the [`EventCacheStore`].
16
17use std::{
18    collections::HashMap,
19    fmt,
20    iter::once,
21    ops::{Deref, Not},
22    path::{Path, PathBuf},
23    sync::Arc,
24};
25
26use async_trait::async_trait;
27use deadpool::managed::PoolConfig;
28use matrix_sdk_base::{
29    cross_process_lock::CrossProcessLockGeneration,
30    deserialized_responses::TimelineEvent,
31    event_cache::{
32        Event, Gap,
33        store::{EventCacheStore, extract_event_relation},
34        thread::ThreadInfo,
35    },
36    linked_chunk::{
37        ChunkContent, ChunkIdentifier, ChunkIdentifierGenerator, ChunkMetadata, LinkedChunkId,
38        Position, RawChunk, Update,
39    },
40    timer,
41};
42use matrix_sdk_store_encryption::StoreCipher;
43use ruma::{
44    EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, events::relation::RelationType,
45};
46use rusqlite::{
47    OptionalExtension, ToSql, Transaction, TransactionBehavior, params, params_from_iter,
48};
49use tokio::{
50    fs,
51    sync::{Mutex, OwnedMutexGuard},
52};
53use tracing::{debug, error, instrument, trace};
54
55use crate::{
56    OpenStoreError, RuntimeConfig, Secret, SqliteStoreConfig,
57    connection::{
58        self, Connection as SqliteAsyncConn, Pool as SqlitePool, SqliteConnections,
59        SqliteConnectionsInner,
60    },
61    error::{Error, Result},
62    utils::{
63        EncryptableStore, Key, SqliteAsyncConnExt, SqliteKeyValueStoreAsyncConnExt,
64        SqliteKeyValueStoreConnExt, SqliteTransactionExt, host_parameters,
65    },
66};
67
68mod keys {
69    // Tables
70    pub const LINKED_CHUNKS: &str = "linked_chunks";
71    pub const EVENTS: &str = "events";
72}
73
74/// The database name.
75pub const DATABASE_NAME: &str = "matrix-sdk-event-cache.sqlite3";
76
77/// The string used to identify a chunk of type events, in the `type` field in
78/// the database.
79const CHUNK_TYPE_EVENT_TYPE_STRING: &str = "E";
80/// The string used to identify a chunk of type gap, in the `type` field in the
81/// database.
82const CHUNK_TYPE_GAP_TYPE_STRING: &str = "G";
83
84/// Type to support (de)encryption of keys and values for the
85/// [`SqliteEventCacheStore`].
86///
87/// See the [`SqliteEventCacheStore::encryption`] field.
88struct Encryption {
89    cipher: Option<StoreCipher>,
90}
91
92impl Encryption {
93    fn encode_event(&self, event: &TimelineEvent) -> Result<EncodedEvent> {
94        let serialized = serde_json::to_vec(event)?;
95
96        // Extract the relationship info here.
97        let raw_event = event.raw();
98        let (relates_to, rel_type) = extract_event_relation(raw_event).unzip();
99
100        // An event is a state event if it has a `state_key`, even if it's
101        // empty. Empty state keys are encoded too, so they can be told apart
102        // from a missing one.
103        let state_key = raw_event
104            .get_field::<String>("state_key")
105            .ok()
106            .flatten()
107            .map(|state_key| self.encode_key(keys::EVENTS, state_key));
108
109        // The content may be encrypted.
110        let content = self.encode_value(serialized)?;
111
112        Ok(EncodedEvent {
113            content,
114            rel_type,
115            state_key,
116            relates_to: relates_to
117                .map(|relates_to| self.encode_event_id(keys::EVENTS, &relates_to)),
118        })
119    }
120
121    fn decode_event(&self, raw_encoded_event: &[u8]) -> Result<Event> {
122        Ok(serde_json::from_slice(&self.decode_value(raw_encoded_event)?)?)
123    }
124
125    /// Encode the event ID as a _key_: it cannot be decoded, but this is
126    /// stable.
127    fn encode_event_id(&self, table_name: &str, event_id: &EventId) -> Key {
128        self.encode_key(table_name, event_id)
129    }
130
131    /// Encode the room ID as a _key_: it cannot be decoded, but this is stable.
132    fn encode_room_id(&self, table_name: &str, room_id: &RoomId) -> Key {
133        self.encode_key(table_name, room_id)
134    }
135
136    /// Encode a [`LinkedChunkId`].
137    fn encode_linked_chunk(&self, table_name: &str, linked_chunk_id: &LinkedChunkId<'_>) -> Key {
138        self.encode_key(table_name, linked_chunk_id.storage_key())
139    }
140
141    /// Encode a thread ID (which is an [`EventId`]).
142    fn encode_thread_id(&self, thread_id: &EventId) -> Result<Vec<u8>> {
143        self.encode_value(String::from(thread_id.as_str()))
144    }
145
146    /// Decode a thread ID (which is an [`EventId`]).
147    #[cfg(test)]
148    fn decode_thread_id(&self, encoded_thread_id: &[u8]) -> Result<OwnedEventId> {
149        let as_slice = self.decode_value(encoded_thread_id)?;
150        let as_str = str::from_utf8(as_slice.as_ref())?;
151
152        Ok(EventId::parse(as_str)?)
153    }
154
155    /// Encode a [`ThreadInfo`]).
156    fn encode_thread_info(&self, thread_info: &ThreadInfo) -> Result<Vec<u8>> {
157        self.encode_value(serde_json::to_vec(thread_info)?)
158    }
159
160    /// Decode a [`ThreadInfo`].
161    fn decode_thread_info(&self, encoded_thread_info: &[u8]) -> Result<ThreadInfo> {
162        Ok(serde_json::from_slice(&self.decode_value(encoded_thread_info)?)?)
163    }
164}
165
166impl EncryptableStore for Encryption {
167    fn get_cypher(&self) -> Option<&StoreCipher> {
168        self.cipher.as_ref()
169    }
170}
171
172/// An SQLite-based event cache store.
173#[derive(Clone)]
174pub struct SqliteEventCacheStore {
175    /// Type to encrypt keys and values.
176    encryption: Arc<Encryption>,
177
178    /// `Some` when active, `None` when closed.
179    connections: Arc<SqliteConnections>,
180
181    /// Retained so we can rebuild the pool on reopen.
182    db_path: PathBuf,
183
184    /// Retained so we can rebuild the pool on reopen.
185    pool_config: PoolConfig,
186
187    /// Retained so we can re-apply runtime config on reopen.
188    runtime_config: RuntimeConfig,
189}
190
191#[cfg(not(tarpaulin_include))]
192impl fmt::Debug for SqliteEventCacheStore {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        f.debug_struct("SqliteEventCacheStore").finish_non_exhaustive()
195    }
196}
197
198impl SqliteEventCacheStore {
199    /// Open the SQLite-based event cache store at the given path using the
200    /// given passphrase to encrypt private data.
201    pub async fn open(
202        path: impl AsRef<Path>,
203        passphrase: Option<&str>,
204    ) -> Result<Self, OpenStoreError> {
205        Self::open_with_config(&SqliteStoreConfig::new(path).passphrase(passphrase)).await
206    }
207
208    /// Open the SQLite-based event cache store at the given path using the
209    /// given key to encrypt private data.
210    pub async fn open_with_key(
211        path: impl AsRef<Path>,
212        key: Option<&[u8]>,
213    ) -> Result<Self, OpenStoreError> {
214        Self::open_with_config(&SqliteStoreConfig::new(path).key(key)).await
215    }
216
217    /// Open the SQLite-based event cache store with the config open config.
218    #[instrument(skip(config), fields(path = ?config.path))]
219    pub async fn open_with_config(config: &SqliteStoreConfig) -> Result<Self, OpenStoreError> {
220        debug!(?config);
221
222        let _timer = timer!("open_with_config");
223
224        fs::create_dir_all(&config.path).await.map_err(OpenStoreError::CreateDir)?;
225
226        let db_path = config.path.join(DATABASE_NAME);
227        let pool_config = config.pool_config();
228        let runtime_config = config.runtime_config();
229
230        let pool = config.build_pool_of_connections(DATABASE_NAME)?;
231
232        let this =
233            Self::open_with_pool(pool, db_path, pool_config, runtime_config, config.secret.clone())
234                .await?;
235
236        // Apply runtime config on the write connection.
237        this.write().await?.apply_runtime_config(runtime_config).await?;
238
239        Ok(this)
240    }
241
242    /// Open an SQLite-based event cache store using the given SQLite database
243    /// pool. The given secret will be used to encrypt private data.
244    async fn open_with_pool(
245        pool: SqlitePool,
246        db_path: PathBuf,
247        pool_config: PoolConfig,
248        runtime_config: RuntimeConfig,
249        secret: Option<Secret>,
250    ) -> Result<Self, OpenStoreError> {
251        let conn = pool.get().await?;
252
253        let version = conn.db_version().await?;
254
255        run_migrations(&conn, version).await?;
256
257        conn.wal_checkpoint().await;
258
259        let cipher = match secret {
260            Some(s) => Some(conn.get_or_create_store_cipher(s).await?),
261            None => None,
262        };
263
264        let connections = SqliteConnectionsInner {
265            pool,
266            // Use `conn` as our selected write connection.
267            write_connection: Arc::new(Mutex::new(conn)),
268        };
269
270        Ok(Self {
271            encryption: Arc::new(Encryption { cipher }),
272            connections: Arc::new(SqliteConnections::new(connections).await),
273            db_path,
274            pool_config,
275            runtime_config,
276        })
277    }
278
279    /// Acquire a connection for executing read operations.
280    #[instrument(skip_all)]
281    async fn read(&self) -> Result<SqliteAsyncConn> {
282        let pool = {
283            let guard = self.connections.lock().await;
284            let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
285            conns.pool.clone()
286        };
287
288        let connection = pool.get().await?;
289
290        // Per https://www.sqlite.org/foreignkeys.html#fk_enable, foreign key
291        // support must be enabled on a per-connection basis. Execute it every
292        // time we try to get a connection, since we can't guarantee a previous
293        // connection did enable it before.
294        connection.execute_batch("PRAGMA foreign_keys = ON;").await?;
295
296        Ok(connection)
297    }
298
299    /// Acquire a connection for executing write operations.
300    #[instrument(skip_all)]
301    async fn write(&self) -> Result<OwnedMutexGuard<SqliteAsyncConn>> {
302        let write_connection = {
303            let guard = self.connections.lock().await;
304            let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
305            conns.write_connection.clone()
306        };
307
308        let connection = write_connection.lock_owned().await;
309
310        // Per https://www.sqlite.org/foreignkeys.html#fk_enable, foreign key
311        // support must be enabled on a per-connection basis. Execute it every
312        // time we try to get a connection, since we can't guarantee a previous
313        // connection did enable it before.
314        connection.execute_batch("PRAGMA foreign_keys = ON;").await?;
315
316        Ok(connection)
317    }
318
319    fn map_row_to_chunk(
320        row: &rusqlite::Row<'_>,
321    ) -> Result<(u64, Option<u64>, Option<u64>, String), rusqlite::Error> {
322        Ok((
323            row.get::<_, u64>(0)?,
324            row.get::<_, Option<u64>>(1)?,
325            row.get::<_, Option<u64>>(2)?,
326            row.get::<_, String>(3)?,
327        ))
328    }
329
330    pub async fn vacuum(&self) -> Result<()> {
331        let write_connection = {
332            let guard = self.connections.lock().await;
333            let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
334            conns.write_connection.clone()
335        };
336        write_connection.lock().await.vacuum().await
337    }
338
339    async fn get_db_size(&self) -> Result<Option<usize>> {
340        let pool = {
341            let guard = self.connections.lock().await;
342            let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
343            conns.pool.clone()
344        };
345        Ok(Some(pool.get().await?.get_db_size().await?))
346    }
347
348    pub async fn close(&self) -> Result<()> {
349        connection::close_connections(&self.connections, "Event cache store").await;
350        Ok(())
351    }
352
353    pub async fn reopen(&self) -> Result<()> {
354        connection::reopen_connections(
355            &self.connections,
356            self.db_path.clone(),
357            self.pool_config,
358            self.runtime_config,
359        )
360        .await?;
361        Ok(())
362    }
363
364    /// Returns the pool size status, for testing purposes.
365    #[cfg(test)]
366    async fn pool_max_size(&self) -> Option<usize> {
367        let guard = self.connections.lock().await;
368        guard.as_ref().map(|conns| conns.pool.status().max_size)
369    }
370}
371
372struct EncodedEvent {
373    content: Vec<u8>,
374    rel_type: Option<String>,
375    relates_to: Option<Key>,
376    /// The (possibly) hashed `state_key` of the event, if any. An empty
377    /// `state_key` is still `Some`.
378    state_key: Option<Key>,
379}
380
381trait TransactionExtForLinkedChunks {
382    fn rebuild_chunk(
383        &self,
384        encryption: &Encryption,
385        linked_chunk_id: &Key,
386        previous: Option<u64>,
387        index: u64,
388        next: Option<u64>,
389        chunk_type: &str,
390    ) -> Result<RawChunk<Event, Gap>>;
391
392    fn load_gap_content(
393        &self,
394        encryption: &Encryption,
395        linked_chunk_id: &Key,
396        chunk_id: ChunkIdentifier,
397    ) -> Result<Gap>;
398
399    fn load_events_content(
400        &self,
401        encryption: &Encryption,
402        linked_chunk_id: &Key,
403        chunk_id: ChunkIdentifier,
404    ) -> Result<Vec<Event>>;
405}
406
407impl TransactionExtForLinkedChunks for Transaction<'_> {
408    fn rebuild_chunk(
409        &self,
410        encryption: &Encryption,
411        linked_chunk_id: &Key,
412        previous: Option<u64>,
413        id: u64,
414        next: Option<u64>,
415        chunk_type: &str,
416    ) -> Result<RawChunk<Event, Gap>> {
417        let previous = previous.map(ChunkIdentifier::new);
418        let next = next.map(ChunkIdentifier::new);
419        let id = ChunkIdentifier::new(id);
420
421        match chunk_type {
422            CHUNK_TYPE_GAP_TYPE_STRING => {
423                // It's a gap!
424                let gap = self.load_gap_content(encryption, linked_chunk_id, id)?;
425                Ok(RawChunk { content: ChunkContent::Gap(gap), previous, identifier: id, next })
426            }
427
428            CHUNK_TYPE_EVENT_TYPE_STRING => {
429                // It's events!
430                let events = self.load_events_content(encryption, linked_chunk_id, id)?;
431                Ok(RawChunk {
432                    content: ChunkContent::Items(events),
433                    previous,
434                    identifier: id,
435                    next,
436                })
437            }
438
439            other => {
440                // It's an error!
441                Err(Error::InvalidData {
442                    details: format!("a linked chunk has an unknown type {other}"),
443                })
444            }
445        }
446    }
447
448    fn load_gap_content(
449        &self,
450        encryption: &Encryption,
451        linked_chunk_id: &Key,
452        chunk_id: ChunkIdentifier,
453    ) -> Result<Gap> {
454        // There's at most one row for it in the database, so a call to
455        // `query_one` is sufficient.
456        let encoded_prev_token: Vec<u8> = self.query_one(
457            "SELECT prev_token FROM gap_chunks WHERE chunk_id = ? AND linked_chunk_id = ?",
458            (chunk_id.index(), &linked_chunk_id),
459            |row| row.get(0),
460        )?;
461        let prev_token_bytes = encryption.decode_value(&encoded_prev_token)?;
462        let prev_token = String::from_utf8(prev_token_bytes.into_owned())?;
463        Ok(Gap { token: prev_token })
464    }
465
466    fn load_events_content(
467        &self,
468        encryption: &Encryption,
469        linked_chunk_id: &Key,
470        chunk_id: ChunkIdentifier,
471    ) -> Result<Vec<Event>> {
472        // Retrieve all the events from the database.
473        let mut events = Vec::new();
474
475        for event_data in self
476            .prepare(
477                "SELECT events.content \
478                    FROM event_chunks ec, events \
479                    WHERE events.event_id = ec.event_id AND ec.chunk_id = ? AND ec.linked_chunk_id = ? \
480                    ORDER BY ec.position ASC",
481            )?
482            .query_map((chunk_id.index(), &linked_chunk_id), |row| row.get::<_, Vec<u8>>(0))?
483        {
484            events.push(encryption.decode_event(&event_data?)?);
485        }
486
487        Ok(events)
488    }
489}
490
491/// Run migrations for the given version of the database.
492async fn run_migrations(conn: &SqliteAsyncConn, version: u8) -> Result<()> {
493    // Always enable foreign keys for the current connection.
494    conn.execute_batch("PRAGMA foreign_keys = ON;").await?;
495
496    if version < 1 {
497        debug!("Creating database");
498        // First turn on WAL mode, this can't be done in the transaction, it
499        // fails with the error message: "cannot change into wal mode from
500        // within a transaction".
501        conn.execute_batch("PRAGMA journal_mode = wal;").await?;
502        conn.with_transaction(|txn| {
503            txn.execute_batch(include_str!("../migrations/event_cache_store/001_init.sql"))?;
504            txn.set_db_version(1)
505        })
506        .await?;
507    }
508
509    if version < 2 {
510        debug!("Upgrading database to version 2");
511        conn.with_transaction(|txn| {
512            txn.execute_batch(include_str!("../migrations/event_cache_store/002_lease_locks.sql"))?;
513            txn.set_db_version(2)
514        })
515        .await?;
516    }
517
518    if version < 3 {
519        debug!("Upgrading database to version 3");
520        conn.with_transaction(|txn| {
521            txn.execute_batch(include_str!("../migrations/event_cache_store/003_events.sql"))?;
522            txn.set_db_version(3)
523        })
524        .await?;
525    }
526
527    if version < 4 {
528        debug!("Upgrading database to version 4");
529        conn.with_transaction(|txn| {
530            txn.execute_batch(include_str!(
531                "../migrations/event_cache_store/004_ignore_policy.sql"
532            ))?;
533            txn.set_db_version(4)
534        })
535        .await?;
536    }
537
538    if version < 5 {
539        debug!("Upgrading database to version 5");
540        conn.with_transaction(|txn| {
541            txn.execute_batch(include_str!(
542                "../migrations/event_cache_store/005_events_index_on_event_id.sql"
543            ))?;
544            txn.set_db_version(5)
545        })
546        .await?;
547    }
548
549    if version < 6 {
550        debug!("Upgrading database to version 6");
551        conn.with_transaction(|txn| {
552            txn.execute_batch(include_str!("../migrations/event_cache_store/006_events.sql"))?;
553            txn.set_db_version(6)
554        })
555        .await?;
556    }
557
558    if version < 7 {
559        debug!("Upgrading database to version 7");
560        conn.with_transaction(|txn| {
561            txn.execute_batch(include_str!(
562                "../migrations/event_cache_store/007_event_chunks.sql"
563            ))?;
564            txn.set_db_version(7)
565        })
566        .await?;
567    }
568
569    if version < 8 {
570        debug!("Upgrading database to version 8");
571        conn.with_transaction(|txn| {
572            txn.execute_batch(include_str!(
573                "../migrations/event_cache_store/008_linked_chunk_id.sql"
574            ))?;
575            txn.set_db_version(8)
576        })
577        .await?;
578    }
579
580    if version < 9 {
581        debug!("Upgrading database to version 9");
582        conn.with_transaction(|txn| {
583            txn.execute_batch(include_str!(
584                "../migrations/event_cache_store/009_related_event_index.sql"
585            ))?;
586            txn.set_db_version(9)
587        })
588        .await?;
589    }
590
591    if version < 10 {
592        debug!("Upgrading database to version 10");
593        conn.with_transaction(|txn| {
594            txn.execute_batch(include_str!("../migrations/event_cache_store/010_drop_media.sql"))?;
595            txn.set_db_version(10)
596        })
597        .await?;
598
599        if version >= 1 {
600            // Defragment the DB and optimize its size on the filesystem now
601            // that we removed the media cache.
602            conn.vacuum().await?;
603        }
604    }
605
606    if version < 11 {
607        debug!("Upgrading database to version 11");
608        conn.with_transaction(|txn| {
609            txn.execute_batch(include_str!(
610                "../migrations/event_cache_store/011_empty_event_cache.sql"
611            ))?;
612            txn.set_db_version(11)
613        })
614        .await?;
615    }
616
617    if version < 12 {
618        debug!("Upgrading database to version 12");
619        conn.with_transaction(|txn| {
620            txn.execute_batch(include_str!(
621                "../migrations/event_cache_store/012_store_event_type.sql"
622            ))?;
623            txn.set_db_version(12)
624        })
625        .await?;
626    }
627
628    if version < 13 {
629        debug!("Upgrading database to version 13");
630        conn.with_transaction(|txn| {
631            txn.execute_batch(include_str!(
632                "../migrations/event_cache_store/013_lease_locks_with_generation.sql"
633            ))?;
634            txn.set_db_version(13)
635        })
636        .await?;
637    }
638
639    if version < 14 {
640        debug!("Upgrading database to version 14");
641        conn.with_transaction(|txn| {
642            txn.execute_batch(include_str!(
643                "../migrations/event_cache_store/014_event_chunks_event_id_index.sql"
644            ))?;
645            txn.set_db_version(14)
646        })
647        .await?;
648    }
649
650    if version < 15 {
651        debug!("Upgrading database to version 15");
652        conn.with_transaction(|txn| {
653            txn.execute_batch(include_str!(
654                "../migrations/event_cache_store/015_event_ids_are_encoded.sql"
655            ))?;
656            txn.set_db_version(15)
657        })
658        .await?;
659    }
660
661    if version < 16 {
662        debug!("Upgrading database to version 16");
663        conn.with_transaction(|txn| {
664            txn.execute_batch(include_str!("../migrations/event_cache_store/016_threads.sql"))?;
665            txn.set_db_version(16)
666        })
667        .await?;
668    }
669
670    if version < 17 {
671        debug!("Upgrading database to version 17");
672        conn.with_transaction(|txn| {
673            txn.execute_batch(include_str!(
674                "../migrations/event_cache_store/017_threads_with_thread_infos.sql"
675            ))?;
676            txn.set_db_version(17)
677        })
678        .await?;
679    }
680
681    if version < 18 {
682        debug!("Upgrading database to version 18");
683        conn.with_transaction(|txn| {
684            txn.execute_batch(include_str!("../migrations/event_cache_store/018_event_chunks_unique_linked_chunk_id_event_id.sql"))?;
685            txn.set_db_version(18)
686        })
687        .await?;
688    }
689
690    if version < 19 {
691        debug!("Upgrading database to version 19");
692        conn.with_transaction(|txn| {
693            txn.execute_batch(include_str!(
694                "../migrations/event_cache_store/019_events_timestamp.sql"
695            ))?;
696            txn.set_db_version(19)
697        })
698        .await?;
699    }
700
701    if version < 20 {
702        debug!("Upgrading database to version 20");
703        conn.with_transaction(|txn| {
704            txn.execute_batch(include_str!(
705                "../migrations/event_cache_store/020_events_state_key.sql"
706            ))?;
707            txn.set_db_version(20)
708        })
709        .await?;
710    }
711
712    Ok(())
713}
714
715#[async_trait]
716impl EventCacheStore for SqliteEventCacheStore {
717    type Error = Error;
718
719    #[instrument(skip(self))]
720    async fn try_take_leased_lock(
721        &self,
722        lease_duration_ms: u32,
723        key: &str,
724        holder: &str,
725    ) -> Result<Option<CrossProcessLockGeneration>> {
726        let key = key.to_owned();
727        let holder = holder.to_owned();
728
729        let now: u64 = MilliSecondsSinceUnixEpoch::now().get().into();
730        let expiration = now + lease_duration_ms as u64;
731
732        // Learn about the `excluded` keyword in https://sqlite.org/lang_upsert.html.
733        let generation = self
734            .write()
735            .await?
736            .with_transaction(move |txn| {
737                txn.query_one(
738                    "INSERT INTO lease_locks (key, holder, expiration) \
739                    VALUES (?1, ?2, ?3) \
740                    ON CONFLICT (key) \
741                    DO \
742                        UPDATE SET \
743                            holder = excluded.holder, \
744                            expiration = excluded.expiration, \
745                            generation = \
746                                CASE holder \
747                                    WHEN excluded.holder THEN generation \
748                                    ELSE generation + 1 \
749                                END \
750                        WHERE \
751                            holder = excluded.holder \
752                            OR expiration < ?4 \
753                    RETURNING generation",
754                    (key, holder, expiration, now),
755                    |row| row.get(0),
756                )
757                .optional()
758            })
759            .await?;
760
761        Ok(generation)
762    }
763
764    #[instrument(skip(self, updates))]
765    async fn handle_linked_chunk_updates(
766        &self,
767        linked_chunk_id: LinkedChunkId<'_>,
768        updates: Vec<Update<Event, Gap>>,
769    ) -> Result<(), Self::Error> {
770        let _timer = timer!("method");
771
772        let hashed_linked_chunk_id =
773            self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
774        let hashed_room_id =
775            self.encryption.encode_room_id(keys::EVENTS, linked_chunk_id.room_id());
776        let encryption = self.encryption.clone();
777
778        // Use a single transaction throughout this function, so that either all
779        // updates work, or none is taken into account.
780        with_immediate_transaction(self, move |txn| {
781            for update in updates {
782                match update {
783                    Update::NewItemsChunk { previous, new, next } => {
784                        let previous = previous.as_ref().map(ChunkIdentifier::index);
785                        let new = new.index();
786                        let next = next.as_ref().map(ChunkIdentifier::index);
787
788                        trace!("new events chunk (prev={previous:?}, i={new}, next={next:?})");
789
790                        insert_chunk(
791                            txn,
792                            &hashed_linked_chunk_id,
793                            previous,
794                            new,
795                            next,
796                            CHUNK_TYPE_EVENT_TYPE_STRING,
797                        )?;
798                    }
799
800                    Update::NewGapChunk { previous, new, next, gap } => {
801                        let hashed_prev_token = encryption.encode_value(gap.token)?;
802
803                        let previous = previous.as_ref().map(ChunkIdentifier::index);
804                        let new = new.index();
805                        let next = next.as_ref().map(ChunkIdentifier::index);
806
807                        trace!("new gap chunk (prev={previous:?}, i={new}, next={next:?})");
808
809                        // Insert the chunk as a gap.
810                        insert_chunk(
811                            txn,
812                            &hashed_linked_chunk_id,
813                            previous,
814                            new,
815                            next,
816                            CHUNK_TYPE_GAP_TYPE_STRING,
817                        )?;
818
819                        // Insert the gap's value.
820                        txn.execute(
821                            r#"
822                            INSERT INTO gap_chunks(chunk_id, linked_chunk_id, prev_token)
823                            VALUES (?, ?, ?)
824                        "#,
825                            (new, &hashed_linked_chunk_id, hashed_prev_token),
826                        )?;
827                    }
828
829                    Update::RemoveChunk(chunk_identifier) => {
830                        let chunk_id = chunk_identifier.index();
831
832                        trace!("removing chunk @ {chunk_id}");
833
834                        // Find chunk to delete.
835                        let (previous, next): (Option<usize>, Option<usize>) = txn.query_one(
836                            "SELECT previous, next FROM linked_chunks WHERE id = ? AND linked_chunk_id = ?",
837                            (chunk_id, &hashed_linked_chunk_id),
838                            |row| Ok((row.get(0)?, row.get(1)?))
839                        )?;
840
841                        // Replace its previous' next to its own next.
842                        if let Some(previous) = previous {
843                            txn.execute("UPDATE linked_chunks SET next = ? WHERE id = ? AND linked_chunk_id = ?", (next, previous, &hashed_linked_chunk_id))?;
844                        }
845
846                        // Replace its next' previous to its own previous.
847                        if let Some(next) = next {
848                            txn.execute("UPDATE linked_chunks SET previous = ? WHERE id = ? AND linked_chunk_id = ?", (previous, next, &hashed_linked_chunk_id))?;
849                        }
850
851                        // Now delete it, and let cascading delete corresponding
852                        // entries in the other data tables.
853                        txn.execute("DELETE FROM linked_chunks WHERE id = ? AND linked_chunk_id = ?", (chunk_id, &hashed_linked_chunk_id))?;
854                    }
855
856                    Update::PushItems { at, items } => {
857                        if items.is_empty() {
858                            // Should never happens, but better be safe.
859                            continue;
860                        }
861
862                        let chunk_id = at.chunk_identifier().index();
863
864                        trace!("pushing {} items @ {chunk_id}", items.len());
865
866                        let mut chunk_statement = txn.prepare(
867                            "INSERT INTO event_chunks(chunk_id, linked_chunk_id, event_id, position) VALUES (?, ?, ?, ?)"
868                        )?;
869
870                        // Note: we use `OR REPLACE` here, because the event
871                        // might have been already inserted in the database.
872                        // This is the case when an event is deduplicated and
873                        // moved to another position; or because it was inserted
874                        // outside the context of a linked chunk (e.g. pinned
875                        // event).
876                        let mut content_statement = txn.prepare(
877                            "INSERT OR REPLACE INTO events(room_id, event_id, event_type, session_id, content, relates_to, rel_type, timestamp, state_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
878                        )?;
879
880                        let invalid_event = |event: TimelineEvent| {
881                            let Some(event_id) = event.event_id() else {
882                                error!("Trying to push an event with no ID");
883                                return None;
884                            };
885
886                            let Some(event_type) = event.kind.event_type() else {
887                                error!(%event_id, "Trying to save an event with no event type");
888                                return None;
889                            };
890
891                            Some((event_id.to_owned(), event_type, event))
892                        };
893
894                        for (i, (event_id, event_type, event)) in items.into_iter().filter_map(invalid_event).enumerate() {
895                            let hashed_event_id = encryption.encode_event_id(
896                                // For the event ID, we need a stable hash
897                                // between the `events` and `event_chunks`
898                                // tables. That's why we use `keys::EVENTS` even
899                                // if `hashed_event_id` is sometimes only used
900                                // in `events_chunks`.
901                                keys::EVENTS,
902                                &event_id,
903                            );
904
905                            // Table `event_chunks`.
906                            {
907                                let index = at.index() + i;
908
909                                chunk_statement.execute((chunk_id, &hashed_linked_chunk_id, &hashed_event_id, index))?;
910                            }
911
912                            // Table `events`.
913                            {
914                                let hashed_session_id = event.kind.session_id().map(|s| encryption.encode_key(keys::EVENTS, s));
915                                let hashed_event_type = encryption.encode_key(keys::EVENTS, event_type);
916                                let timestamp = event.timestamp.map(|ts| u64::from(ts.get()));
917                                let encoded_event = encryption.encode_event(&event)?;
918
919                                content_statement.execute((
920                                    &hashed_room_id,
921                                    &hashed_event_id,
922                                    hashed_event_type,
923                                    hashed_session_id,
924                                    encoded_event.content,
925                                    encoded_event.relates_to,
926                                    encoded_event.rel_type,
927                                    timestamp,
928                                    encoded_event.state_key,
929                                ))?;
930                            }
931                        }
932                    }
933
934                    Update::ReplaceItem { at, item: event } => {
935                        let chunk_id = at.chunk_identifier().index();
936                        let index = at.index();
937
938                        trace!("replacing item @ {chunk_id}:{index}");
939
940                        // The event ID should be the same, but just in case it changed…
941                        let Some(event_id) = event.event_id().map(|event_id| event_id.to_owned()) else {
942                            error!("Trying to replace an event with a new one that has no ID");
943                            continue;
944                        };
945
946                        let Some(event_type) = event.kind.event_type() else {
947                            error!(%event_id, "Trying to save an event with no event type");
948                            continue;
949                        };
950
951                        let hashed_event_id = encryption.encode_event_id(keys::EVENTS, &event_id);
952
953                        // Before updating the event in its chunk, we must
954                        // ensure the event exists: either we insert it, or we
955                        // update it. Note that it's possible to replace an
956                        // event by itself (with different encryption info for
957                        // example, or from UTD to decrypted, stuff like that).
958
959                        // Table `events`.
960                        {
961                            let hashed_session_id = event.kind.session_id().map(|s| encryption.encode_key(keys::EVENTS, s));
962                            let hashed_event_type = encryption.encode_key(keys::EVENTS, event_type);
963                            let encoded_event = encryption.encode_event(&event)?;
964
965                            let timestamp = event.timestamp.map(|ts| u64::from(ts.get()));
966                            txn.execute(
967                                "INSERT OR REPLACE INTO events(room_id, event_id, event_type, session_id, content, relates_to, rel_type, timestamp, state_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
968                                (
969                                    &hashed_room_id,
970                                    &hashed_event_id,
971                                    hashed_event_type,
972                                    hashed_session_id,
973                                    encoded_event.content,
974                                    encoded_event.relates_to,
975                                    encoded_event.rel_type,
976                                    timestamp,
977                                    encoded_event.state_key,
978                                ),
979                            )?;
980                        }
981
982                        // Table `event_chunks`.
983                        {
984                            // Replace the event at position `index` in chunk `chunk_id` by updating the event ID.
985                            txn.execute(
986                                r#"UPDATE event_chunks SET event_id = ? WHERE linked_chunk_id = ? AND chunk_id = ? AND position = ?"#,
987                                (&hashed_event_id, &hashed_linked_chunk_id, chunk_id, index)
988                            )?;
989                        }
990                    }
991
992                    Update::RemoveItem { at } => {
993                        let chunk_id = at.chunk_identifier().index();
994                        let index = at.index();
995
996                        trace!("removing item @ {chunk_id}:{index}");
997
998                        // Remove the entry in the chunk table.
999                        txn.execute("DELETE FROM event_chunks WHERE linked_chunk_id = ? AND chunk_id = ? AND position = ?", (&hashed_linked_chunk_id, chunk_id, index))?;
1000
1001                        // Decrement the index of each item after the one we are
1002                        // going to remove.
1003                        //
1004                        // Imagine we have the following events:
1005                        //
1006                        // | event_id | linked_chunk_id | chunk_id | position |
1007                        // |----------|-----------------|----------|----------|
1008                        // | $ev0     | !r0             | 42       | 0        |
1009                        // | $ev1     | !r0             | 42       | 1        |
1010                        // | $ev2     | !r0             | 42       | 2        |
1011                        // | $ev3     | !r0             | 42       | 3        |
1012                        // | $ev4     | !r0             | 42       | 4        |
1013                        //
1014                        // `$ev2` has been removed, then we end up in this
1015                        // state:
1016                        //
1017                        // | event_id | linked_chunk_id    | chunk_id | position |
1018                        // |----------|--------------------|----------|----------|
1019                        // | $ev0     | !r0                | 42       | 0        |
1020                        // | $ev1     | !r0                | 42       | 1        |
1021                        // |          |                    |          |          | <- no more `$ev2`
1022                        // | $ev3     | !r0                | 42       | 3        |
1023                        // | $ev4     | !r0                | 42       | 4        |
1024                        //
1025                        // We need to shift the `position` of `$ev3` and `$ev4`
1026                        // to `position - 1`, like so:
1027                        //
1028                        // | event_id | linked_chunk_id | chunk_id | position |
1029                        // |----------|-----------------|----------|----------|
1030                        // | $ev0     | !r0             | 42       | 0        |
1031                        // | $ev1     | !r0             | 42       | 1        |
1032                        // | $ev3     | !r0             | 42       | 2        |
1033                        // | $ev4     | !r0             | 42       | 3        |
1034                        //
1035                        // Usually, it boils down to run the following query:
1036                        //
1037                        // ```sql
1038                        // UPDATE event_chunks
1039                        // SET position = position - 1
1040                        // WHERE position > 2 AND …
1041                        // ```
1042                        //
1043                        // Okay. But `UPDATE` runs on rows in no particular
1044                        // order. It means that it can update `$ev4` before
1045                        // `$ev3` for example. What happens in this particular
1046                        // case? The `position` of `$ev4` becomes `3`, however
1047                        // `$ev3` already has `position = 3`. Because there is a
1048                        // `UNIQUE` constraint on
1049                        // `(linked_chunk_id, chunk_id, position)`, it will
1050                        // result in a constraint violation.
1051                        //
1052                        // There is **no way** to control the execution order of
1053                        // `UPDATE` in SQLite. To persuade yourself, try:
1054                        //
1055                        // ```sql
1056                        // UPDATE event_chunks
1057                        // SET position = position - 1
1058                        // FROM (
1059                        //     SELECT event_id
1060                        //     FROM event_chunks
1061                        //     WHERE position > 2 AND …
1062                        //     ORDER BY position ASC
1063                        // ) as ordered
1064                        // WHERE event_chunks.event_id = ordered.event_id
1065                        // ```
1066                        //
1067                        // It will fail the same way.
1068                        //
1069                        // Thus, we have 2 solutions:
1070                        //
1071                        // 1. Remove the `UNIQUE` constraint,
1072                        // 2. Be creative.
1073                        //
1074                        // The `UNIQUE` constraint is a safe belt. Normally, we
1075                        // have `event_cache::Deduplicator` that is responsible
1076                        // to ensure there is no duplicated event. However,
1077                        // relying on this is “fragile” in the sense it can
1078                        // contain bugs. Relying on the `UNIQUE` constraint from
1079                        // SQLite is more robust. It's “braces and belt” as we
1080                        // say here.
1081                        //
1082                        // So. We need to be creative.
1083                        //
1084                        // Many solutions exist. Amongst the most popular, we
1085                        // see _dropping and re-creating the index_, which is
1086                        // no-go for us, it's too expensive. I (@hywan) have
1087                        // adopted the following one:
1088                        //
1089                        // - Do `position = position - 1` but in the negative
1090                        //   space, so `position = -(position - 1)`. A position
1091                        //   cannot be negative; we are sure it is unique!
1092                        // - Once all candidate rows are updated, do
1093                        //   `position = -position` to move back to the positive
1094                        //   space.
1095                        //
1096                        // 'told you it's gonna be creative.
1097                        //
1098                        // This solution is a hack, **but** it is a small number
1099                        // of operations, and we can keep the `UNIQUE`
1100                        // constraint in place.
1101                        txn.execute(
1102                            r#"
1103                                UPDATE event_chunks
1104                                SET position = -(position - 1)
1105                                WHERE linked_chunk_id = ? AND chunk_id = ? AND position > ?
1106                            "#,
1107                            (&hashed_linked_chunk_id, chunk_id, index)
1108                        )?;
1109                        txn.execute(
1110                            r#"
1111                                UPDATE event_chunks
1112                                SET position = -position
1113                                WHERE position < 0 AND linked_chunk_id = ? AND chunk_id = ?
1114                            "#,
1115                            (&hashed_linked_chunk_id, chunk_id)
1116                        )?;
1117
1118                        // We don't remove the events from `events` purposely
1119                        // because they can be used by another `LinkedChunkId`.
1120                    }
1121
1122                    Update::DetachLastItems { at } => {
1123                        let chunk_id = at.chunk_identifier().index();
1124                        let index = at.index();
1125
1126                        trace!("truncating items >= {chunk_id}:{index}");
1127
1128                        // Remove these entries.
1129                        txn.execute("DELETE FROM event_chunks WHERE linked_chunk_id = ? AND chunk_id = ? AND position >= ?", (&hashed_linked_chunk_id, chunk_id, index))?;
1130
1131                        // We don't remove the events from `events` purposely
1132                        // because they can be used by another `LinkedChunkId`.
1133                    }
1134
1135                    Update::Clear => {
1136                        trace!("clearing items");
1137
1138                        // Remove chunks, and let cascading do its job.
1139                        txn.execute(
1140                            "DELETE FROM linked_chunks WHERE linked_chunk_id = ?",
1141                            (&hashed_linked_chunk_id,),
1142                        )?;
1143
1144                        // We don't remove the events from `events` purposely
1145                        // because they can be used by another `LinkedChunkId`.
1146                    }
1147
1148                    Update::StartReattachItems | Update::EndReattachItems => {
1149                        // Nothing.
1150                    }
1151                }
1152            }
1153
1154            Ok(())
1155        })
1156        .await?;
1157
1158        Ok(())
1159    }
1160
1161    #[instrument(skip(self))]
1162    async fn load_all_chunks(
1163        &self,
1164        linked_chunk_id: LinkedChunkId<'_>,
1165    ) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error> {
1166        let _timer = timer!("method");
1167
1168        let hashed_linked_chunk_id =
1169            self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1170        let encryption = self.encryption.clone();
1171
1172        let result = self
1173            .read()
1174            .await?
1175            .with_transaction(move |txn| -> Result<_> {
1176                let mut items = Vec::new();
1177
1178                // Use `ORDER BY id` to get a deterministic ordering for testing purposes.
1179                for data in txn
1180                    .prepare(
1181                        "SELECT id, previous, next, type FROM linked_chunks WHERE linked_chunk_id = ? ORDER BY id",
1182                    )?
1183                    .query_map((&hashed_linked_chunk_id,), Self::map_row_to_chunk)?
1184                {
1185                    let (id, previous, next, chunk_type) = data?;
1186                    let new = txn.rebuild_chunk(
1187                        &encryption,
1188                        &hashed_linked_chunk_id,
1189                        previous,
1190                        id,
1191                        next,
1192                        chunk_type.as_str(),
1193                    )?;
1194                    items.push(new);
1195                }
1196
1197                Ok(items)
1198            })
1199            .await?;
1200
1201        Ok(result)
1202    }
1203
1204    #[instrument(skip(self))]
1205    async fn load_all_chunks_metadata(
1206        &self,
1207        linked_chunk_id: LinkedChunkId<'_>,
1208    ) -> Result<Vec<ChunkMetadata>, Self::Error> {
1209        let _timer = timer!("method");
1210
1211        let hashed_linked_chunk_id =
1212            self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1213
1214        self.read()
1215            .await?
1216            .with_transaction(move |txn| -> Result<_> {
1217                // We want to collect the metadata about each chunk (id, next,
1218                // previous), and for event chunks, the number of events in it.
1219                // For gaps, the number of events is 0, by convention.
1220                //
1221                // We've tried different strategies over time:
1222                //
1223                // - use a `LEFT JOIN` + `COUNT`, which was extremely
1224                //   inefficient because it caused a full table traversal for
1225                //   each chunk, including for gaps which don't have any events.
1226                //   This happened in
1227                //   https://github.com/matrix-org/matrix-rust-sdk/pull/5225.
1228                // - use a `CASE` statement on the chunk's type: if it's an
1229                //   event chunk, run an additional `SELECT` query. It was an
1230                //   immense improvement, but still caused one select query per
1231                //   event chunk. This happened in
1232                //   https://github.com/matrix-org/matrix-rust-sdk/pull/5411.
1233                //
1234                // The current solution is to run two queries:
1235                //
1236                // - one to get each chunk and its number of events, by doing a
1237                //   single `SELECT` query over the `event_chunks` table,
1238                //   grouping by chunk ids. This gives us a list of `(chunk_id,
1239                //   num_events)` pairs, which can be transformed into a
1240                //   hashmap.
1241                // - one to get each chunk's metadata (id, previous, next, type)
1242                //   from the database with a `SELECT`, and then use the hashmap
1243                //   to get the number of events.
1244                //
1245                // This strategy minimizes the number of queries to the
1246                // database, and keeps them super simple, while doing a bit more
1247                // processing here, which is much faster.
1248
1249                let num_events_by_chunk_ids = txn
1250                    .prepare(
1251                        "SELECT ec.chunk_id, COUNT(ec.event_id) \
1252                        FROM event_chunks as ec \
1253                        WHERE ec.linked_chunk_id = ? \
1254                        GROUP BY ec.chunk_id",
1255                    )?
1256                    .query_map((&hashed_linked_chunk_id,), |row| {
1257                        Ok((row.get::<_, u64>(0)?, row.get::<_, usize>(1)?))
1258                    })?
1259                    .collect::<Result<HashMap<_, _>, _>>()?;
1260
1261                txn.prepare(
1262                    "SELECT \
1263                        lc.id, \
1264                        lc.previous, \
1265                        lc.next, \
1266                        lc.type \
1267                    FROM linked_chunks as lc \
1268                    WHERE lc.linked_chunk_id = ? \
1269                    ORDER BY lc.id",
1270                )?
1271                .query_map((&hashed_linked_chunk_id,), |row| {
1272                    Ok((
1273                        row.get::<_, u64>(0)?,
1274                        row.get::<_, Option<u64>>(1)?,
1275                        row.get::<_, Option<u64>>(2)?,
1276                        row.get::<_, String>(3)?,
1277                    ))
1278                })?
1279                .map(|data| -> Result<_> {
1280                    let (id, previous, next, chunk_type) = data?;
1281
1282                    // Note: since a gap has 0 events, an alternative could be
1283                    // to _not_ retrieve the chunk type, and just let the
1284                    // hashmap lookup fail for gaps. However, benchmarking shows
1285                    // that this is slightly slower than matching the chunk type
1286                    // (around 1%, so in the realm of noise), so we keep the
1287                    // explicit check instead.
1288                    let num_items = if chunk_type == CHUNK_TYPE_GAP_TYPE_STRING {
1289                        0
1290                    } else {
1291                        num_events_by_chunk_ids.get(&id).copied().unwrap_or(0)
1292                    };
1293
1294                    Ok(ChunkMetadata {
1295                        identifier: ChunkIdentifier::new(id),
1296                        previous: previous.map(ChunkIdentifier::new),
1297                        next: next.map(ChunkIdentifier::new),
1298                        num_items,
1299                    })
1300                })
1301                .collect::<Result<Vec<_>, _>>()
1302            })
1303            .await
1304    }
1305
1306    #[instrument(skip(self))]
1307    async fn load_last_chunk(
1308        &self,
1309        linked_chunk_id: LinkedChunkId<'_>,
1310    ) -> Result<(Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator), Self::Error> {
1311        let _timer = timer!("method");
1312
1313        let hashed_linked_chunk_id =
1314            self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1315        let encryption = self.encryption.clone();
1316
1317        self
1318            .read()
1319            .await?
1320            .with_transaction(move |txn| -> Result<_> {
1321                // Find the latest chunk identifier to generate a `ChunkIdentifierGenerator`, and count the number of chunks.
1322                let (observed_max_identifier, number_of_chunks) = txn
1323                    .prepare(
1324                        "SELECT MAX(id), COUNT(*) FROM linked_chunks WHERE linked_chunk_id = ?"
1325                    )?
1326                    .query_one(
1327                        (&hashed_linked_chunk_id,),
1328                        |row| {
1329                            Ok((
1330                                // Read the `MAX(id)` as an `Option<u64>`
1331                                // instead of `u64` in case the `SELECT` returns
1332                                // nothing. Indeed, if it returns no line, the
1333                                // `MAX(id)` is set to `Null`.
1334                                row.get::<_, Option<u64>>(0)?,
1335                                row.get::<_, u64>(1)?,
1336                            ))
1337                        }
1338                    )?;
1339
1340                let chunk_identifier_generator = match observed_max_identifier {
1341                    Some(max_observed_identifier) => {
1342                        ChunkIdentifierGenerator::new_from_previous_chunk_identifier(
1343                            ChunkIdentifier::new(max_observed_identifier)
1344                        )
1345                    },
1346                    None => ChunkIdentifierGenerator::new_from_scratch(),
1347                };
1348
1349                // Find the last chunk.
1350                let Some((chunk_identifier, previous_chunk, chunk_type)) = txn
1351                    .prepare(
1352                        "SELECT id, previous, type FROM linked_chunks WHERE linked_chunk_id = ? AND next IS NULL"
1353                    )?
1354                    .query_one(
1355                        (&hashed_linked_chunk_id,),
1356                        |row| {
1357                            Ok((
1358                                row.get::<_, u64>(0)?,
1359                                row.get::<_, Option<u64>>(1)?,
1360                                row.get::<_, String>(2)?,
1361                            ))
1362                        }
1363                    )
1364                    .optional()?
1365                else {
1366                    // Chunk is not found and there are zero chunks for this
1367                    // room, this is consistent, all good.
1368                    if number_of_chunks == 0 {
1369                        return Ok((None, chunk_identifier_generator));
1370                    }
1371                    // Chunk is not found **but** there are chunks for this
1372                    // room, this is inconsistent. The linked chunk is
1373                    // malformed.
1374                    //
1375                    // Returning `Ok((None, _))` would be invalid here: we must
1376                    // return an error.
1377                    else {
1378                        return Err(Error::InvalidData {
1379                            details:
1380                                "last chunk is not found but chunks exist: the linked chunk contains a cycle"
1381                                    .to_owned()
1382                            }
1383                        )
1384                    }
1385                };
1386
1387                // Build the chunk.
1388                let last_chunk = txn.rebuild_chunk(
1389                    &encryption,
1390                    &hashed_linked_chunk_id,
1391                    previous_chunk,
1392                    chunk_identifier,
1393                    None,
1394                    &chunk_type
1395                )?;
1396
1397                Ok((Some(last_chunk), chunk_identifier_generator))
1398            })
1399            .await
1400    }
1401
1402    #[instrument(skip(self))]
1403    async fn load_previous_chunk(
1404        &self,
1405        linked_chunk_id: LinkedChunkId<'_>,
1406        before_chunk_identifier: ChunkIdentifier,
1407    ) -> Result<Option<RawChunk<Event, Gap>>, Self::Error> {
1408        let _timer = timer!("method");
1409
1410        let hashed_linked_chunk_id =
1411            self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1412        let encryption = self.encryption.clone();
1413
1414        self
1415            .read()
1416            .await?
1417            .with_transaction(move |txn| -> Result<_> {
1418                // Find the chunk before the chunk identified by `before_chunk_identifier`.
1419                let Some((chunk_identifier, previous_chunk, next_chunk, chunk_type)) = txn
1420                    .prepare(
1421                        "SELECT id, previous, next, type FROM linked_chunks WHERE linked_chunk_id = ? AND next = ?"
1422                    )?
1423                    .query_one(
1424                        (&hashed_linked_chunk_id, before_chunk_identifier.index()),
1425                        |row| {
1426                            Ok((
1427                                row.get::<_, u64>(0)?,
1428                                row.get::<_, Option<u64>>(1)?,
1429                                row.get::<_, Option<u64>>(2)?,
1430                                row.get::<_, String>(3)?,
1431                            ))
1432                        }
1433                    )
1434                    .optional()?
1435                else {
1436                    // Chunk is not found.
1437                    return Ok(None);
1438                };
1439
1440                // Build the chunk.
1441                let last_chunk = txn.rebuild_chunk(
1442                    &encryption,
1443                    &hashed_linked_chunk_id,
1444                    previous_chunk,
1445                    chunk_identifier,
1446                    next_chunk,
1447                    &chunk_type
1448                )?;
1449
1450                Ok(Some(last_chunk))
1451            })
1452            .await
1453    }
1454
1455    async fn load_thread_info(
1456        &self,
1457        room_id: &RoomId,
1458        thread_id: &EventId,
1459        insert_default_if_missing: bool,
1460    ) -> Result<Option<ThreadInfo>, Self::Error> {
1461        let linked_chunk_id = LinkedChunkId::Thread(room_id, thread_id);
1462        let hashed_linked_chunk_id =
1463            self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1464        let encryption = self.encryption.clone();
1465
1466        // First off, try by selecting the thread info. It's the most common
1467        // case.
1468        //
1469        // We do that with 2 transactions.
1470        let maybe_thread_info = self
1471            .read()
1472            .await?
1473            .with_transaction(move |txn| {
1474                let maybe_encoded_thread_info = txn
1475                    .query_one(
1476                        "SELECT info FROM threads WHERE linked_chunk_id = ?",
1477                        (hashed_linked_chunk_id,),
1478                        |row| row.get::<_, Vec<u8>>(0),
1479                    )
1480                    .optional()?;
1481
1482                maybe_encoded_thread_info
1483                    .map(|encoded_thread_info| {
1484                        encryption.decode_thread_info(encoded_thread_info.as_slice())
1485                    })
1486                    .transpose()
1487            })
1488            .await?;
1489
1490        if let Some(thread_info) = maybe_thread_info {
1491            return Ok(Some(thread_info));
1492        } else if !insert_default_if_missing {
1493            // The thread info doesn't exist, but we don't want to create one.
1494            return Ok(None);
1495        }
1496
1497        // The thread info doesn't exist, and we want to create it!
1498
1499        let thread_info = ThreadInfo::default();
1500        let hashed_linked_chunk_id =
1501            self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1502        let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1503        let hashed_thread_id = self.encryption.encode_thread_id(thread_id)?;
1504        let encoded_thread_id = self.encryption.encode_thread_info(&thread_info)?;
1505
1506        self.write()
1507            .await?
1508            .with_transaction(move |txn| {
1509                txn.execute(
1510                    "INSERT INTO threads VALUES (?, ?, ?, ?)",
1511                    (hashed_linked_chunk_id, hashed_room_id, hashed_thread_id, encoded_thread_id),
1512                )?;
1513
1514                Ok::<(), Self::Error>(())
1515            })
1516            .await?;
1517
1518        Ok(Some(thread_info))
1519    }
1520
1521    async fn update_thread_info(
1522        &self,
1523        room_id: &RoomId,
1524        thread_id: &EventId,
1525        thread_info: &ThreadInfo,
1526    ) -> Result<(), Self::Error> {
1527        let hashed_linked_chunk_id = self
1528            .encryption
1529            .encode_linked_chunk(keys::LINKED_CHUNKS, &LinkedChunkId::Thread(room_id, thread_id));
1530        let encoded_thread_info = self.encryption.encode_thread_info(thread_info)?;
1531
1532        self.write()
1533            .await?
1534            .with_transaction(move |txn| {
1535                txn.execute(
1536                    "UPDATE threads SET info = ? WHERE linked_chunk_id = ?",
1537                    (encoded_thread_info, hashed_linked_chunk_id),
1538                )?;
1539
1540                Ok(())
1541            })
1542            .await
1543    }
1544
1545    #[instrument(skip(self))]
1546    async fn clear_all_events(&self, room_id: Option<&RoomId>) -> Result<(), Self::Error> {
1547        let _timer = timer!("method");
1548
1549        match room_id {
1550            // Clear all events.
1551            None => {
1552                self.write()
1553                    .await?
1554                    .with_transaction(move |txn| {
1555                        // Remove all the chunks, and let cascading do its job.
1556                        txn.execute("DELETE FROM linked_chunks", ())?;
1557
1558                        // Also clear all the events' contents, and let
1559                        // cascading do its job.
1560                        txn.execute("DELETE FROM events", ())?;
1561
1562                        Ok(())
1563                    })
1564                    .await
1565            }
1566
1567            // Clear events for specific room.
1568            Some(room_id) => {
1569                let encryption = self.encryption.clone();
1570                let room_id = room_id.to_owned();
1571
1572                self.write()
1573                    .await?
1574                    .with_transaction(move |txn| {
1575                        // Delete linked chunks for the room and pinned-events caches.
1576                        {
1577                            let mut delete =
1578                                txn.prepare("DELETE FROM linked_chunks WHERE linked_chunk_id = ?")?;
1579
1580                            for linked_chunk_id in
1581                                [LinkedChunkId::Room(&room_id), LinkedChunkId::PinnedEvents(&room_id)]
1582                            {
1583                                let linked_chunk_id = encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1584
1585                                // Remove all the chunks about the current
1586                                // `LinkedChunkId`, and let cascading do its
1587                                // job.
1588                                delete.execute((&linked_chunk_id,))?;
1589                            }
1590                        }
1591
1592                        let encoded_room_id = encryption.encode_room_id(keys::EVENTS, &room_id);
1593
1594                        // Delete linked chunks for the thread caches.
1595                        {
1596                            txn.execute(
1597                                "DELETE FROM linked_chunks WHERE linked_chunk_id IN (SELECT linked_chunk_id FROM threads WHERE room_id = ?)",
1598                                (&encoded_room_id,),
1599                            )?;
1600                        }
1601
1602                        // Also clear all the events' contents.
1603                        txn.execute(
1604                            "DELETE FROM events WHERE room_id = ?",
1605                            (encoded_room_id,),
1606                        )?;
1607
1608                        Ok(())
1609                    })
1610                    .await
1611            }
1612        }
1613    }
1614
1615    #[instrument(skip(self, event_ids))]
1616    async fn filter_duplicated_events(
1617        &self,
1618        linked_chunk_id: LinkedChunkId<'_>,
1619        event_ids: Vec<OwnedEventId>,
1620    ) -> Result<Vec<(OwnedEventId, Position)>, Self::Error> {
1621        let _timer = timer!("method");
1622
1623        // If there's no events for which we want to check duplicates, we can
1624        // return early. It's not only an optimization to do so: it's required,
1625        // otherwise the `host_parameters` call below will panic.
1626        if event_ids.is_empty() {
1627            return Ok(Vec::new());
1628        }
1629
1630        // Select all events that exist in the store, i.e. the duplicates.
1631        let hashed_linked_chunk_id =
1632            self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &linked_chunk_id);
1633        let event_ids_and_hashed_event_ids = event_ids
1634            .into_iter()
1635            .map(|event_id| {
1636                let hashed_event_id = self.encryption.encode_event_id(
1637                    // For the event ID, we need a stable hash between the
1638                    // `events` and `event_chunks` tables. That's why we use
1639                    // `keys::EVENTS` even if `hashed_event_id` is only used in
1640                    // `events_chunks`.
1641                    keys::EVENTS,
1642                    &event_id,
1643                );
1644
1645                (event_id, hashed_event_id)
1646            })
1647            .collect::<Vec<_>>();
1648
1649        self.read()
1650            .await?
1651            .with_transaction(move |txn| -> Result<_> {
1652                txn.chunk_large_query_over(
1653                    event_ids_and_hashed_event_ids,
1654                    None,
1655                    move |txn, event_ids_and_hashed_event_ids| {
1656                        let query = format!(
1657                            "SELECT event_id, chunk_id, position \
1658                            FROM event_chunks \
1659                            WHERE linked_chunk_id = ? AND event_id IN ({}) \
1660                            ORDER BY chunk_id ASC, position ASC",
1661                            event_ids_and_hashed_event_ids.host_parameters(),
1662                        );
1663
1664                        let parameters = params_from_iter(
1665                            // parameter for `linked_chunk_id = ?`
1666                            once(
1667                                hashed_linked_chunk_id
1668                                    .to_sql()
1669                                    // SAFETY: it cannot fail since `Key::to_sql` never fails
1670                                    .unwrap(),
1671                            )
1672                            // parameters for `event_id IN (…)`
1673                            .chain(
1674                                event_ids_and_hashed_event_ids.iter().map(
1675                                    |(_event_id, hashed_event_id)| {
1676                                        hashed_event_id
1677                                            .to_sql()
1678                                            // SAFETY: it cannot fail since
1679                                            // `Vec::<u8>::to_sql` never fails
1680                                            .unwrap()
1681                                    },
1682                                ),
1683                            ),
1684                        );
1685
1686                        let mut duplicated_events = Vec::new();
1687
1688                        for duplicated_event in
1689                            txn.prepare(&query)?.query_map(parameters, |row| {
1690                                Ok((
1691                                    row.get::<_, Vec<u8>>(0)?,
1692                                    row.get::<_, u64>(1)?,
1693                                    row.get::<_, usize>(2)?,
1694                                ))
1695                            })?
1696                        {
1697                            let (duplicated_hashed_event_id, chunk_identifier, index) =
1698                                duplicated_event?;
1699
1700                            // The event ID is encoded in the database. We can't
1701                            // decode it. However, we can find the original
1702                            // event ID with the `event_ids` parameter of this
1703                            // method by comparing the encoded event ID!
1704                            let Some(duplicated_event_id) = event_ids_and_hashed_event_ids
1705                                .iter()
1706                                .find_map(|(event_id, hashed_event_id)| {
1707                                    (hashed_event_id.deref() == duplicated_hashed_event_id)
1708                                        .then_some(event_id.clone())
1709                                })
1710                            else {
1711                                error!(
1712                                    "Unreachable: found a duplicated event that was not requested"
1713                                );
1714                                continue;
1715                            };
1716
1717                            duplicated_events.push((
1718                                duplicated_event_id,
1719                                Position::new(ChunkIdentifier::new(chunk_identifier), index),
1720                            ));
1721                        }
1722
1723                        Ok(duplicated_events)
1724                    },
1725                )
1726            })
1727            .await
1728    }
1729
1730    #[instrument(skip(self, event_id))]
1731    async fn find_event(
1732        &self,
1733        room_id: &RoomId,
1734        event_id: &EventId,
1735    ) -> Result<Option<Event>, Self::Error> {
1736        let _timer = timer!("method");
1737
1738        let encryption = self.encryption.clone();
1739
1740        let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1741        let hashed_event_id = self.encryption.encode_event_id(keys::EVENTS, event_id);
1742
1743        self.read()
1744            .await?
1745            .with_transaction(move |txn| -> Result<_> {
1746                let Some(event) = txn
1747                    .prepare("SELECT content FROM events WHERE event_id = ? AND room_id = ?")?
1748                    .query_one((hashed_event_id, hashed_room_id), |row| row.get::<_, Vec<u8>>(0))
1749                    .optional()?
1750                else {
1751                    // Event is not found.
1752                    return Ok(None);
1753                };
1754
1755                Ok(Some(encryption.decode_event(&event)?))
1756            })
1757            .await
1758    }
1759
1760    #[instrument(skip(self, event_id, filters))]
1761    async fn find_event_relations(
1762        &self,
1763        room_id: &RoomId,
1764        event_id: &EventId,
1765        filters: Option<&[RelationType]>,
1766    ) -> Result<Vec<(Event, Option<Position>)>, Self::Error> {
1767        let _timer = timer!("method");
1768
1769        let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1770        let hashed_linked_chunk_id =
1771            self.encryption.encode_linked_chunk(keys::LINKED_CHUNKS, &LinkedChunkId::Room(room_id));
1772        let hashed_event_id = self.encryption.encode_event_id(keys::EVENTS, event_id);
1773
1774        let filters = filters.map(ToOwned::to_owned);
1775        let encryption = self.encryption.clone();
1776
1777        self.read()
1778            .await?
1779            .with_transaction(move |txn| -> Result<_> {
1780                find_event_relations_transaction(
1781                    &encryption,
1782                    hashed_room_id,
1783                    hashed_linked_chunk_id,
1784                    hashed_event_id,
1785                    filters,
1786                    txn,
1787                )
1788            })
1789            .await
1790    }
1791
1792    #[instrument(skip(self))]
1793    async fn find_events_before_timestamp(
1794        &self,
1795        room_id: &RoomId,
1796        cutoff_ms: u64,
1797    ) -> Result<Vec<Event>, Self::Error> {
1798        let _timer = timer!("method");
1799
1800        let encryption = self.encryption.clone();
1801        let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1802
1803        self.read()
1804            .await?
1805            .with_transaction(move |txn| -> Result<_> {
1806                let mut results = Vec::new();
1807                let mut statement = txn.prepare(
1808                    "SELECT content \
1809                    FROM events \
1810                    WHERE room_id = ? AND timestamp < ? \
1811                    ORDER BY timestamp ASC",
1812                )?;
1813                let rows = statement
1814                    .query_map((&hashed_room_id, cutoff_ms), |row| row.get::<_, Vec<u8>>(0))?;
1815
1816                for row in rows {
1817                    let event = encryption.decode_event(&row?)?;
1818                    results.push(event);
1819                }
1820
1821                Ok(results)
1822            })
1823            .await
1824    }
1825
1826    #[instrument(skip(self))]
1827    async fn get_room_events(
1828        &self,
1829        room_id: &RoomId,
1830        event_type: Option<&str>,
1831        session_id: Option<&str>,
1832    ) -> Result<Vec<Event>, Self::Error> {
1833        let _timer = timer!("method");
1834
1835        let encryption = self.encryption.clone();
1836
1837        let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1838        let hashed_event_type = event_type.map(|e| self.encryption.encode_key(keys::EVENTS, e));
1839        let hashed_session_id = session_id.map(|s| self.encryption.encode_key(keys::EVENTS, s));
1840
1841        self.read()
1842            .await?
1843            .with_transaction(move |txn| -> Result<_> {
1844                // I'm not sure why clippy claims that the clones aren't
1845                // required. The compiler tells us that the lifetimes aren't
1846                // long enough if we remove them. Doesn't matter much so let's
1847                // silence things.
1848                #[allow(clippy::redundant_clone)]
1849                let (query, keys) = match (hashed_event_type, hashed_session_id) {
1850                    (None, None) => {
1851                        ("SELECT content FROM events WHERE room_id = ?", params![hashed_room_id])
1852                    }
1853                    (None, Some(session_id)) => (
1854                        "SELECT content FROM events WHERE room_id = ?1 AND session_id = ?2",
1855                        params![hashed_room_id, session_id.to_owned()],
1856                    ),
1857                    (Some(event_type), None) => (
1858                        "SELECT content FROM events WHERE room_id = ? AND event_type = ?",
1859                        params![hashed_room_id, event_type.to_owned()]
1860                    ),
1861                    (Some(event_type), Some(session_id)) => (
1862                        "SELECT content FROM events WHERE room_id = ?1 AND event_type = ?2 AND session_id = ?3",
1863                        params![hashed_room_id, event_type.to_owned(), session_id.to_owned()],
1864                    ),
1865                };
1866
1867                let mut statement = txn.prepare(query)?;
1868
1869                statement
1870                    .query_map(keys, |row| row.get::<_, Vec<u8>>(0))?
1871                    .map(|maybe_encoded_event| {
1872                        encryption.decode_event(&maybe_encoded_event?)
1873                    })
1874                    .collect::<Result<Vec<_>>>()
1875            })
1876            .await
1877    }
1878
1879    #[instrument(skip(self, event))]
1880    async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error> {
1881        let _timer = timer!("method");
1882
1883        let Some(event_id) = event.event_id() else {
1884            error!("Trying to save an event with no ID");
1885            return Ok(());
1886        };
1887
1888        let Some(event_type) = event.kind.event_type() else {
1889            error!(%event_id, "Trying to save an event with no event type");
1890            return Ok(());
1891        };
1892
1893        let hashed_event_type = self.encryption.encode_key(keys::EVENTS, event_type);
1894        let hashed_session_id =
1895            event.kind.session_id().map(|s| self.encryption.encode_key(keys::EVENTS, s));
1896
1897        let hashed_room_id = self.encryption.encode_room_id(keys::EVENTS, room_id);
1898        let hashed_event_id = self.encryption.encode_event_id(keys::EVENTS, event_id);
1899        let encoded_event = self.encryption.encode_event(&event)?;
1900
1901        self.write()
1902            .await?
1903            .with_transaction(move |txn| -> Result<_> {
1904                let timestamp = event.timestamp.map(|ts| u64::from(ts.get()));
1905                txn.execute(
1906                    "INSERT OR REPLACE INTO events(room_id, event_id, event_type, session_id, content, relates_to, rel_type, timestamp, state_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
1907                    (
1908                        &hashed_room_id,
1909                        hashed_event_id,
1910                        hashed_event_type,
1911                        hashed_session_id,
1912                        encoded_event.content,
1913                        encoded_event.relates_to,
1914                        encoded_event.rel_type,
1915                        timestamp,
1916                        encoded_event.state_key,
1917                    )
1918                )?;
1919
1920                Ok(())
1921            })
1922            .await
1923    }
1924
1925    async fn close(&self) -> Result<(), Self::Error> {
1926        SqliteEventCacheStore::close(self).await
1927    }
1928
1929    async fn reopen(&self) -> Result<(), Self::Error> {
1930        SqliteEventCacheStore::reopen(self).await
1931    }
1932
1933    async fn optimize(&self) -> Result<(), Self::Error> {
1934        Ok(self.vacuum().await?)
1935    }
1936
1937    async fn get_size(&self) -> Result<Option<usize>, Self::Error> {
1938        self.get_db_size().await
1939    }
1940}
1941
1942fn find_event_relations_transaction(
1943    encryption: &Encryption,
1944    hashed_room_id: Key,
1945    hashed_linked_chunk_id: Key,
1946    hashed_event_id: Key,
1947    filters: Option<Vec<RelationType>>,
1948    txn: &Transaction<'_>,
1949) -> Result<Vec<(Event, Option<Position>)>> {
1950    let get_rows = |row: &rusqlite::Row<'_>| {
1951        Ok((
1952            row.get::<_, Vec<u8>>(0)?,
1953            row.get::<_, Option<u64>>(1)?,
1954            row.get::<_, Option<usize>>(2)?,
1955        ))
1956    };
1957
1958    // Collect related events.
1959    let collect_results = |transaction| {
1960        let mut related = Vec::new();
1961
1962        for result in transaction {
1963            let (event, chunk_id, index): (Vec<u8>, Option<u64>, _) = result?;
1964            let event = encryption.decode_event(&event)?;
1965
1966            // Only build the position if both the chunk_id and position were
1967            // present; in theory, they should either be present at the same
1968            // time, or not at all.
1969            let pos = chunk_id
1970                .zip(index)
1971                .map(|(chunk_id, index)| Position::new(ChunkIdentifier::new(chunk_id), index));
1972
1973            related.push((event, pos));
1974        }
1975
1976        Ok(related)
1977    };
1978
1979    if let Some(filters) = filters
1980        && filters.is_empty().not()
1981    {
1982        let query = format!(
1983            "SELECT events.content, event_chunks.chunk_id, event_chunks.position \
1984            FROM events \
1985            LEFT JOIN event_chunks ON events.event_id = event_chunks.event_id AND event_chunks.linked_chunk_id = ? \
1986            WHERE events.relates_to = ? AND events.room_id = ? AND events.rel_type IN ({})",
1987            host_parameters(filters.len())
1988        );
1989
1990        // First the filters need to be stringified; because `.to_sql()` will
1991        // borrow from them, they also need to be stringified onto the stack, so
1992        // as to get a stable address (to avoid returning a temporary reference
1993        // in the map closure below).
1994        let filter_strings: Vec<_> = filters.iter().map(|f| f.to_string()).collect();
1995        let filters_params: Vec<_> = filter_strings
1996            .iter()
1997            .map(|f| f.to_sql().expect("converting a string to SQL should work"))
1998            .collect();
1999
2000        let parameters = params_from_iter(
2001            [
2002                hashed_linked_chunk_id.to_sql().expect(
2003                    "We should be able to convert a hashed linked chunk ID to a SQLite value",
2004                ),
2005                hashed_event_id
2006                    .to_sql()
2007                    .expect("We should be able to convert an event ID to a SQLite value"),
2008                hashed_room_id
2009                    .to_sql()
2010                    .expect("We should be able to convert a room ID to a SQLite value"),
2011            ]
2012            .into_iter()
2013            .chain(filters_params),
2014        );
2015
2016        let mut transaction = txn.prepare(&query)?;
2017        let transaction = transaction.query_map(parameters, get_rows)?;
2018
2019        collect_results(transaction)
2020    } else {
2021        let query = "SELECT events.content, event_chunks.chunk_id, event_chunks.position \
2022            FROM events \
2023            LEFT JOIN event_chunks ON events.event_id = event_chunks.event_id AND event_chunks.linked_chunk_id = ? \
2024            WHERE events.relates_to = ? AND events.room_id = ?";
2025        let parameters = (hashed_linked_chunk_id, hashed_event_id, hashed_room_id);
2026
2027        let mut transaction = txn.prepare(query)?;
2028        let transaction = transaction.query_map(parameters, get_rows)?;
2029
2030        collect_results(transaction)
2031    }
2032}
2033
2034/// Like `deadpool::managed::Object::with_transaction`, but starts the
2035/// transaction in immediate (write) mode from the beginning, precluding errors
2036/// of the kind SQLITE_BUSY from happening, for transactions that may involve
2037/// both reads and writes, and start with a write.
2038async fn with_immediate_transaction<
2039    T: Send + 'static,
2040    F: FnOnce(&Transaction<'_>) -> Result<T, Error> + Send + 'static,
2041>(
2042    this: &SqliteEventCacheStore,
2043    f: F,
2044) -> Result<T, Error> {
2045    this.write()
2046        .await?
2047        .interact(move |conn| -> Result<T, Error> {
2048            // Start the transaction in IMMEDIATE mode since all updates may
2049            // cause writes, to avoid read transactions upgrading to write mode
2050            // and causing SQLITE_BUSY errors. See also:
2051            // https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions
2052            conn.set_transaction_behavior(TransactionBehavior::Immediate);
2053
2054            let code = || -> Result<T, Error> {
2055                let txn = conn.transaction()?;
2056                let res = f(&txn)?;
2057                txn.commit()?;
2058                Ok(res)
2059            };
2060
2061            let res = code();
2062
2063            // Reset the transaction behavior to use Deferred, after this
2064            // transaction has been run, whether it was successful or not.
2065            conn.set_transaction_behavior(TransactionBehavior::Deferred);
2066
2067            res
2068        })
2069        .await
2070        // SAFETY: same logic as in [`deadpool::managed::Object::with_transaction`].`
2071        .unwrap()
2072}
2073
2074fn insert_chunk(
2075    txn: &Transaction<'_>,
2076    linked_chunk_id: &Key,
2077    previous: Option<u64>,
2078    new: u64,
2079    next: Option<u64>,
2080    type_str: &str,
2081) -> rusqlite::Result<()> {
2082    // First, insert the new chunk.
2083    txn.execute(
2084        r#"
2085            INSERT INTO linked_chunks(id, linked_chunk_id, previous, next, type)
2086            VALUES (?, ?, ?, ?, ?)
2087        "#,
2088        (new, linked_chunk_id, previous, next, type_str),
2089    )?;
2090
2091    // If this chunk has a previous one, update its `next` field.
2092    if let Some(previous) = previous {
2093        let updated = txn.execute(
2094            r#"
2095                UPDATE linked_chunks
2096                SET next = ?
2097                WHERE id = ? AND linked_chunk_id = ?
2098            "#,
2099            (new, previous, linked_chunk_id),
2100        )?;
2101        if updated < 1 {
2102            return Err(rusqlite::Error::QueryReturnedNoRows);
2103        }
2104        if updated > 1 {
2105            return Err(rusqlite::Error::QueryReturnedMoreThanOneRow);
2106        }
2107    }
2108
2109    // If this chunk has a next one, update its `previous` field.
2110    if let Some(next) = next {
2111        let updated = txn.execute(
2112            r#"
2113                UPDATE linked_chunks
2114                SET previous = ?
2115                WHERE id = ? AND linked_chunk_id = ?
2116            "#,
2117            (new, next, linked_chunk_id),
2118        )?;
2119        if updated < 1 {
2120            return Err(rusqlite::Error::QueryReturnedNoRows);
2121        }
2122        if updated > 1 {
2123            return Err(rusqlite::Error::QueryReturnedMoreThanOneRow);
2124        }
2125    }
2126
2127    Ok(())
2128}
2129
2130#[cfg(test)]
2131mod tests {
2132    use std::{
2133        path::PathBuf,
2134        sync::{
2135            LazyLock,
2136            atomic::{AtomicU32, Ordering::SeqCst},
2137        },
2138    };
2139
2140    use assert_matches::assert_matches;
2141    use matrix_sdk_base::{
2142        event_cache::store::{
2143            EventCacheStore, EventCacheStoreError, IntoEventCacheStore,
2144            integration_tests::EventCacheStoreIntegrationTests,
2145        },
2146        event_cache_store_integration_tests, event_cache_store_integration_tests_time,
2147        linked_chunk::{ChunkIdentifier, LinkedChunkId, Update},
2148    };
2149    use matrix_sdk_test::{TestRoom, User, async_test, event_factory::EventFactory};
2150    use ruma::{EventId, OwnedEventId, event_id, room_id};
2151    use tempfile::{TempDir, tempdir};
2152
2153    use super::{SqliteEventCacheStore, keys};
2154    use crate::{
2155        SqliteStoreConfig,
2156        utils::{EncryptableStore, SqliteAsyncConnExt},
2157    };
2158
2159    static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
2160    static NUM: AtomicU32 = AtomicU32::new(0);
2161
2162    fn new_event_cache_store_workspace() -> PathBuf {
2163        let name = NUM.fetch_add(1, SeqCst).to_string();
2164        TMP_DIR.path().join(name)
2165    }
2166
2167    async fn get_event_cache_store() -> Result<SqliteEventCacheStore, EventCacheStoreError> {
2168        let tmpdir_path = new_event_cache_store_workspace();
2169
2170        tracing::info!("using event cache store @ {}", tmpdir_path.to_str().unwrap());
2171
2172        Ok(SqliteEventCacheStore::open(tmpdir_path.to_str().unwrap(), None).await.unwrap())
2173    }
2174
2175    event_cache_store_integration_tests!();
2176    event_cache_store_integration_tests_time!();
2177
2178    #[async_test]
2179    async fn test_encryption_encode_decode_thread_id_roundtrip() {
2180        let store = get_event_cache_store().await.expect("creating cache store failed");
2181        let thread_id: OwnedEventId = event_id!("$event").to_owned();
2182
2183        let encoded_thread_id = store.encryption.encode_thread_id(&thread_id).unwrap();
2184        let decoded_thread_id: OwnedEventId =
2185            store.encryption.decode_thread_id(&encoded_thread_id).unwrap();
2186
2187        assert_eq!(thread_id, decoded_thread_id);
2188    }
2189
2190    #[async_test]
2191    async fn test_state_key_column() {
2192        let store = get_event_cache_store().await.expect("creating cache store failed");
2193
2194        let room_id = room_id!("!test:localhost");
2195        let f = EventFactory::new().room(room_id).sender(User::Alice);
2196
2197        let message_id = event_id!("$message");
2198        let empty_state_key_ev_id = event_id!("$empty_state_key");
2199        let state_key_ev_id = event_id!("$state_key");
2200        let state_key = User::Bob.as_str();
2201
2202        // A message-like event, without a `state_key`.
2203        store
2204            .save_event(room_id, f.text_msg("hello").event_id(message_id).into_event())
2205            .await
2206            .unwrap();
2207        // A state event with an empty `state_key`.
2208        store
2209            .save_event(
2210                room_id,
2211                f.room_topic("topic").state_key("").event_id(empty_state_key_ev_id).into_event(),
2212            )
2213            .await
2214            .unwrap();
2215        // A state event with a non-empty `state_key` (in this case, Bob's user
2216        // id).
2217        store
2218            .save_event(room_id, f.member(User::Bob).event_id(state_key_ev_id).into_event())
2219            .await
2220            .unwrap();
2221
2222        let stored_state_key = async |event_id: &EventId| -> Option<Vec<u8>> {
2223            let hashed_event_id = store.encryption.encode_event_id(keys::EVENTS, event_id);
2224            store
2225                .read()
2226                .await
2227                .unwrap()
2228                .with_transaction(move |txn| {
2229                    txn.query_one(
2230                        "SELECT state_key FROM events WHERE event_id = ?",
2231                        (hashed_event_id,),
2232                        |row| row.get::<_, Option<Vec<u8>>>(0),
2233                    )
2234                })
2235                .await
2236                .unwrap()
2237        };
2238        let encoded_state_key =
2239            |state_key: &str| store.encryption.encode_key(keys::EVENTS, state_key).to_vec();
2240
2241        assert_eq!(stored_state_key(message_id).await, None);
2242        assert_eq!(stored_state_key(empty_state_key_ev_id).await, Some(encoded_state_key("")));
2243        assert_eq!(stored_state_key(state_key_ev_id).await, Some(encoded_state_key(state_key)));
2244    }
2245
2246    #[async_test]
2247    async fn test_pool_size() {
2248        let tmpdir_path = new_event_cache_store_workspace();
2249        let store_open_config = SqliteStoreConfig::new(tmpdir_path).pool_max_size(42);
2250
2251        let store = SqliteEventCacheStore::open_with_config(&store_open_config).await.unwrap();
2252
2253        assert_eq!(store.pool_max_size().await, Some(42));
2254    }
2255
2256    #[async_test]
2257    async fn test_linked_chunk_remove_chunk() {
2258        let store = get_event_cache_store().await.expect("creating cache store failed");
2259
2260        // Run corresponding integration test
2261        store.clone().into_event_cache_store().test_linked_chunk_remove_chunk().await;
2262
2263        // Check that cascading worked. Yes, SQLite, I doubt you.
2264        let gaps = store
2265            .read()
2266            .await
2267            .unwrap()
2268            .with_transaction(|txn| -> rusqlite::Result<_> {
2269                let mut gaps = Vec::new();
2270                for data in txn
2271                    .prepare("SELECT chunk_id FROM gap_chunks ORDER BY chunk_id")?
2272                    .query_map((), |row| row.get::<_, u64>(0))?
2273                {
2274                    gaps.push(data?);
2275                }
2276                Ok(gaps)
2277            })
2278            .await
2279            .unwrap();
2280
2281        // Check that the gaps match those set up in the corresponding
2282        // integration test above
2283        assert_eq!(gaps, vec![42, 44]);
2284    }
2285
2286    #[async_test]
2287    async fn test_linked_chunk_remove_item() {
2288        let store = get_event_cache_store().await.expect("creating cache store failed");
2289
2290        // Run corresponding integration test
2291        store.clone().into_event_cache_store().test_linked_chunk_remove_item().await;
2292
2293        let hashed_linked_chunk_id = store.encryption.encode_linked_chunk(
2294            keys::LINKED_CHUNKS,
2295            &LinkedChunkId::Room(TestRoom::Default.into()),
2296        );
2297
2298        // Make sure the position have been updated for the remaining events.
2299        let num_rows: u64 = store
2300            .read()
2301            .await
2302            .unwrap()
2303            .with_transaction(move |txn| {
2304                txn.query_one(
2305                    "SELECT COUNT(*) FROM event_chunks WHERE chunk_id = 42 AND linked_chunk_id = ? AND position IN (2, 3, 4)",
2306                    (hashed_linked_chunk_id,),
2307                    |row| row.get(0),
2308                )
2309            })
2310            .await
2311            .unwrap();
2312        assert_eq!(num_rows, 3);
2313    }
2314
2315    #[async_test]
2316    async fn test_linked_chunk_clear() {
2317        let store = get_event_cache_store().await.expect("creating cache store failed");
2318
2319        // Run corresponding integration test
2320        store.clone().into_event_cache_store().test_linked_chunk_clear().await;
2321
2322        // Check that cascading worked. Yes, SQLite, I doubt you.
2323        store
2324            .read()
2325            .await
2326            .unwrap()
2327            .with_transaction(|txn| -> rusqlite::Result<_> {
2328                let num_gaps = txn
2329                    .prepare("SELECT COUNT(chunk_id) FROM gap_chunks ORDER BY chunk_id")?
2330                    .query_one((), |row| row.get::<_, u64>(0))?;
2331                assert_eq!(num_gaps, 0);
2332
2333                let num_events = txn
2334                    .prepare("SELECT COUNT(event_id) FROM event_chunks ORDER BY chunk_id")?
2335                    .query_one((), |row| row.get::<_, u64>(0))?;
2336                assert_eq!(num_events, 0);
2337
2338                Ok(())
2339            })
2340            .await
2341            .unwrap();
2342    }
2343
2344    #[async_test]
2345    async fn test_linked_chunk_update_is_a_transaction() {
2346        let store = get_event_cache_store().await.expect("creating cache store failed");
2347
2348        let linked_chunk_id = LinkedChunkId::Room(TestRoom::Default.into());
2349
2350        // Trigger a violation of the unique constraint on the (room id, chunk
2351        // id) couple.
2352        let err = store
2353            .handle_linked_chunk_updates(
2354                linked_chunk_id,
2355                vec![
2356                    Update::NewItemsChunk {
2357                        previous: None,
2358                        new: ChunkIdentifier::new(42),
2359                        next: None,
2360                    },
2361                    Update::NewItemsChunk {
2362                        previous: None,
2363                        new: ChunkIdentifier::new(42),
2364                        next: None,
2365                    },
2366                ],
2367            )
2368            .await
2369            .unwrap_err();
2370
2371        // The operation fails with a constraint violation error.
2372        assert_matches!(err, crate::error::Error::Sqlite(err) => {
2373            assert_matches!(err.sqlite_error_code(), Some(rusqlite::ErrorCode::ConstraintViolation));
2374        });
2375
2376        // If the updates have been handled transactionally, then no new chunks
2377        // should have been added; failure of the second update leads to the
2378        // first one being rolled back.
2379        let chunks = store.load_all_chunks(linked_chunk_id).await.unwrap();
2380        assert!(chunks.is_empty());
2381    }
2382}
2383
2384#[cfg(test)]
2385mod encrypted_tests {
2386    use std::sync::{
2387        LazyLock,
2388        atomic::{AtomicU32, Ordering::SeqCst},
2389    };
2390
2391    use matrix_sdk_base::{
2392        event_cache::store::{EventCacheStore, EventCacheStoreError},
2393        event_cache_store_integration_tests, event_cache_store_integration_tests_time,
2394    };
2395    use matrix_sdk_test::{async_test, event_factory::EventFactory};
2396    use ruma::{
2397        event_id,
2398        events::{relation::RelationType, room::message::RoomMessageEventContentWithoutRelation},
2399        room_id, user_id,
2400    };
2401    use tempfile::{TempDir, tempdir};
2402
2403    use super::SqliteEventCacheStore;
2404
2405    static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
2406    static NUM: AtomicU32 = AtomicU32::new(0);
2407
2408    async fn get_event_cache_store() -> Result<SqliteEventCacheStore, EventCacheStoreError> {
2409        let name = NUM.fetch_add(1, SeqCst).to_string();
2410        let tmpdir_path = TMP_DIR.path().join(name);
2411
2412        tracing::info!("using event cache store @ {}", tmpdir_path.to_str().unwrap());
2413
2414        Ok(SqliteEventCacheStore::open(
2415            tmpdir_path.to_str().unwrap(),
2416            Some("default_test_password"),
2417        )
2418        .await
2419        .unwrap())
2420    }
2421
2422    event_cache_store_integration_tests!();
2423    event_cache_store_integration_tests_time!();
2424
2425    #[async_test]
2426    async fn test_no_sqlite_injection_in_find_event_relations() {
2427        let room_id = room_id!("!test:localhost");
2428        let another_room_id = room_id!("!r1:matrix.org");
2429        let sender = user_id!("@alice:localhost");
2430
2431        let store = get_event_cache_store()
2432            .await
2433            .expect("We should be able to create a new, empty, event cache store");
2434
2435        let f = EventFactory::new().room(room_id).sender(sender);
2436
2437        // Create an event for the first room.
2438        let event_id = event_id!("$DO_NOT_FIND_ME:matrix.org");
2439        let event = f.text_msg("DO NOT FIND").event_id(event_id).into_event();
2440
2441        // Create a related event.
2442        let edit_id = event_id!("$find_me:matrix.org");
2443        let edit = f
2444            .text_msg("Find me")
2445            .event_id(edit_id)
2446            .edit(event_id, RoomMessageEventContentWithoutRelation::text_plain("jebote"))
2447            .into_event();
2448
2449        // Create an event for the second room.
2450        let f = f.room(another_room_id);
2451
2452        let another_event_id = event_id!("$DO_NOT_FIND_ME_EITHER:matrix.org");
2453        let another_event =
2454            f.text_msg("DO NOT FIND ME EITHER").event_id(another_event_id).into_event();
2455
2456        // Save the events in the DB.
2457        store.save_event(room_id, event).await.unwrap();
2458        store.save_event(room_id, edit).await.unwrap();
2459        store.save_event(another_room_id, another_event).await.unwrap();
2460
2461        // Craft a `RelationType` that will inject some SQL to be executed. The
2462        // `OR 1=1` ensures that all the previous parameters, the room ID and
2463        // event ID are ignored.
2464        let filter = Some(vec![RelationType::Replacement, "x\") OR 1=1; --".into()]);
2465
2466        // Attempt to find events in the first room.
2467        let results = store
2468            .find_event_relations(room_id, event_id, filter.as_deref())
2469            .await
2470            .expect("We should be able to attempt to find event relations");
2471
2472        // Ensure that we only got the single related event the first room
2473        // contains.
2474        similar_asserts::assert_eq!(
2475            results.len(),
2476            1,
2477            "We should only have loaded events for the first room {results:#?}"
2478        );
2479
2480        // The event needs to be the edit event, otherwise something is wrong.
2481        let (found_event, _) = &results[0];
2482        assert_eq!(
2483            found_event.event_id(),
2484            Some(edit_id),
2485            "The single event we found should be the edit event"
2486        );
2487    }
2488}
2489
2490#[cfg(test)]
2491mod close_reopen_tests {
2492    use std::sync::{
2493        LazyLock,
2494        atomic::{AtomicU32, Ordering::SeqCst},
2495    };
2496
2497    use matrix_sdk_base::{event_cache::store::EventCacheStore, linked_chunk::LinkedChunkId};
2498    use matrix_sdk_test::{TestRoom, async_test};
2499    use tempfile::{TempDir, tempdir};
2500
2501    use super::SqliteEventCacheStore;
2502
2503    static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
2504    static NUM: AtomicU32 = AtomicU32::new(0);
2505
2506    async fn new_store() -> SqliteEventCacheStore {
2507        let name = NUM.fetch_add(1, SeqCst).to_string();
2508        let tmpdir_path = TMP_DIR.path().join(name);
2509        SqliteEventCacheStore::open(tmpdir_path, None).await.unwrap()
2510    }
2511
2512    #[async_test]
2513    async fn test_close_completes_without_timeout() {
2514        let store = new_store().await;
2515
2516        // Close should complete quickly without hitting the 5s timeout.
2517        let start = std::time::Instant::now();
2518        store.close().await.unwrap();
2519        let elapsed = start.elapsed();
2520
2521        assert!(
2522            elapsed < std::time::Duration::from_secs(2),
2523            "close() took {elapsed:?}, expected < 2s (no timeout)"
2524        );
2525
2526        // Connections should be None after close.
2527        let guard = store.connections.lock().await;
2528        assert!(guard.is_none(), "connections should be None after close");
2529    }
2530
2531    #[async_test]
2532    async fn test_reopen_restores_connections() {
2533        let store = new_store().await;
2534
2535        store.close().await.unwrap();
2536
2537        {
2538            let guard = store.connections.lock().await;
2539            assert!(guard.is_none());
2540        }
2541
2542        store.reopen().await.unwrap();
2543
2544        {
2545            let guard = store.connections.lock().await;
2546            assert!(guard.is_some(), "connections should be Some after reopen");
2547        }
2548    }
2549
2550    #[async_test]
2551    async fn test_close_is_idempotent() {
2552        let store = new_store().await;
2553
2554        store.close().await.unwrap();
2555        // Second close should be a no-op.
2556        store.close().await.unwrap();
2557
2558        let guard = store.connections.lock().await;
2559        assert!(guard.is_none());
2560    }
2561
2562    #[async_test]
2563    async fn test_reopen_is_idempotent() {
2564        let store = new_store().await;
2565
2566        // Reopen on an active store should be a no-op.
2567        store.reopen().await.unwrap();
2568
2569        let guard = store.connections.lock().await;
2570        assert!(guard.is_some());
2571    }
2572
2573    #[async_test]
2574    async fn test_read_fails_when_closed() {
2575        let store = new_store().await;
2576        store.close().await.unwrap();
2577
2578        let err = store.load_all_chunks(LinkedChunkId::Room(TestRoom::Default.into())).await;
2579        assert!(err.is_err(), "read should fail when closed");
2580
2581        let err_msg = err.unwrap_err().to_string();
2582        assert!(err_msg.contains("closed"), "error should mention 'closed', got: {err_msg}");
2583    }
2584
2585    #[async_test]
2586    async fn test_write_fails_when_closed() {
2587        let store = new_store().await;
2588        store.close().await.unwrap();
2589
2590        let err = store.try_take_leased_lock(1000, "test_lock", "holder").await;
2591        assert!(err.is_err(), "write should fail when closed");
2592
2593        let err_msg = err.unwrap_err().to_string();
2594        assert!(err_msg.contains("closed"), "error should mention 'closed', got: {err_msg}");
2595    }
2596
2597    #[async_test]
2598    async fn test_data_persists_across_close_reopen() {
2599        let store = new_store().await;
2600
2601        // Take a lease lock — this is persisted in the database.
2602        let result = store.try_take_leased_lock(60_000, "test_lock", "holder").await.unwrap();
2603        assert!(result.is_some(), "should have acquired the lock");
2604
2605        // Close and reopen.
2606        store.close().await.unwrap();
2607        store.reopen().await.unwrap();
2608
2609        // The lock should still be held by the original holder after reopen.
2610        let result = store.try_take_leased_lock(60_000, "test_lock", "other_holder").await.unwrap();
2611        assert!(result.is_none(), "lock should still be held by the original holder after reopen");
2612    }
2613
2614    #[async_test]
2615    async fn test_multiple_close_reopen_cycles() {
2616        let store = new_store().await;
2617
2618        for _ in 0..5 {
2619            store.close().await.unwrap();
2620            store.reopen().await.unwrap();
2621
2622            // After each cycle, the store should be fully operational.
2623            let result = store.load_all_chunks(LinkedChunkId::Room(TestRoom::Default.into())).await;
2624            assert!(result.is_ok(), "store should work after close/reopen cycle");
2625        }
2626    }
2627
2628    #[async_test]
2629    async fn test_pool_is_fully_drained_after_close() {
2630        let store = new_store().await;
2631
2632        // Do a few reads to exercise the pool.
2633        let _ = store.load_all_chunks(LinkedChunkId::Room(TestRoom::Default.into())).await;
2634        let _ = store.load_all_chunks(LinkedChunkId::Room(TestRoom::Default.into())).await;
2635
2636        store.close().await.unwrap();
2637
2638        // After close, the connections field should be None (pool and write
2639        // connection have been fully torn down).
2640        let guard = store.connections.lock().await;
2641        assert!(guard.is_none(), "all connections should be released after close");
2642    }
2643
2644    #[async_test]
2645    async fn test_operations_work_immediately_after_reopen() {
2646        let store = new_store().await;
2647
2648        store.close().await.unwrap();
2649        store.reopen().await.unwrap();
2650
2651        // Read should work immediately after reopen.
2652        let result = store.load_all_chunks(LinkedChunkId::Room(TestRoom::Default.into())).await;
2653        assert!(result.is_ok(), "read should succeed immediately after reopen");
2654
2655        // Write should work immediately after reopen.
2656        let result = store.try_take_leased_lock(1000, "test_lock", "holder").await;
2657        assert!(result.is_ok(), "write should succeed immediately after reopen");
2658    }
2659
2660    #[async_test]
2661    async fn test_close_waits_for_held_read_connection_to_drain() {
2662        let store = new_store().await;
2663
2664        // Acquire a read connection and hold it, simulating an in-flight read.
2665        let held_conn = store.read().await.unwrap();
2666
2667        // Spawn close in a background task — it will close the pool and then
2668        // poll-wait for pool.status().size == 0 in the drain loop.
2669        let store_clone = store.clone();
2670        let close_handle = tokio::spawn(async move {
2671            store_clone.close().await.unwrap();
2672        });
2673
2674        // Give close() a moment to close the pool and enter the drain loop.
2675        tokio::time::sleep(std::time::Duration::from_millis(200)).await;
2676
2677        // The close task should still be running because we hold a connection.
2678        assert!(!close_handle.is_finished(), "close should be waiting for the held connection");
2679
2680        // Release the held connection — this lets pool.status().size drop to 0.
2681        drop(held_conn);
2682
2683        // Now close should complete promptly (well within the 5s timeout).
2684        let timeout = tokio::time::timeout(std::time::Duration::from_secs(3), close_handle).await;
2685        assert!(timeout.is_ok(), "close should complete after the held connection is released");
2686        timeout.unwrap().unwrap();
2687
2688        // Verify the store is fully closed.
2689        let guard = store.connections.lock().await;
2690        assert!(guard.is_none(), "connections should be None after close");
2691    }
2692}