7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 1 | // Copyright 2022 The Matrix.org Foundation C.I.C. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 2 | // |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 4 | // you may not use this file except in compliance with the License. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 5 | // You may obtain a copy of the License at |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 6 | // |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 8 | // |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 9 | // Unless required by applicable law or agreed to in writing, software |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 12 | // See the License for the specific language governing permissions and |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 13 | // limitations under the License. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 14 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 15 | use core::fmt; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 16 | use std::{ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 17 | borrow::{Borrow, Cow}, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 18 | cmp::min, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 19 | iter, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 20 | ops::Deref, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 21 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 22 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 23 | use async_trait::async_trait; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 24 | use deadpool_sync::InteractError; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 25 | use itertools::Itertools; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 26 | use matrix_sdk_store_encryption::StoreCipher; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 27 | use ruma::{OwnedEventId, OwnedRoomId, serde::Raw, time::SystemTime}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 28 | use rusqlite::{OptionalExtension, Params, Row, Statement, Transaction, limits::Limit}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 29 | use serde::{Serialize, de::DeserializeOwned}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 30 | use tracing::{error, trace, warn}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 31 | use zeroize::Zeroize; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 32 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 33 | use crate::{ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 34 | OpenStoreError, RuntimeConfig, Secret, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 35 | connection::Connection as SqliteAsyncConn, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 36 | error::{Error, Result}, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 37 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 38 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 39 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 40 | pub(crate) enum Key { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 41 | Plain(Vec<u8>), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 42 | Hashed([u8; 32]), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 43 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 44 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 45 | impl Deref for Key { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 46 | type Target = [u8]; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 47 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 48 | fn deref(&self) -> &Self::Target { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 49 | match self { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 50 | Key::Plain(slice) => slice, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 51 | Key::Hashed(bytes) => bytes, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 52 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 53 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 54 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 55 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 56 | impl Borrow<[u8]> for Key { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 57 | fn borrow(&self) -> &[u8] { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 58 | self.deref() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 59 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 60 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 61 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 62 | impl rusqlite::ToSql for Key { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 63 | fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 64 | self.deref().to_sql() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 65 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 66 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 67 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 68 | #[async_trait] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 69 | pub(crate) trait SqliteAsyncConnExt { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 70 | async fn execute<P>( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 71 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 72 | sql: impl AsRef<str> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 73 | params: P, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 74 | ) -> rusqlite::Result<usize> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 75 | where |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 76 | P: Params + Send + 'static; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 77 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 78 | async fn execute_batch(&self, sql: impl AsRef<str> + Send + 'static) -> rusqlite::Result<()>; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 79 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 80 | async fn prepare<T, F>( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 81 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 82 | sql: impl AsRef<str> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 83 | f: F, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 84 | ) -> rusqlite::Result<T> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 85 | where |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 86 | T: Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 87 | F: FnOnce(Statement<'_>) -> rusqlite::Result<T> + Send + 'static; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 88 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 89 | async fn query_row<T, P, F>( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 90 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 91 | sql: impl AsRef<str> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 92 | params: P, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 93 | f: F, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 94 | ) -> rusqlite::Result<T> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 95 | where |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 96 | T: Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 97 | P: Params + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 98 | F: FnOnce(&Row<'_>) -> rusqlite::Result<T> + Send + 'static; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 99 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 100 | async fn query_many<T, P, F>( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 101 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 102 | sql: impl AsRef<str> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 103 | params: P, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 104 | f: F, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 105 | ) -> rusqlite::Result<Vec<T>> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 106 | where |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 107 | T: Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 108 | P: Params + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 109 | F: FnMut(&Row<'_>) -> rusqlite::Result<T> + Send + 'static; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 110 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 111 | async fn with_transaction<T, E, F>(&self, f: F) -> Result<T, E> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 112 | where |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 113 | T: Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 114 | E: From<rusqlite::Error> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 115 | F: FnOnce(&Transaction<'_>) -> Result<T, E> + Send + 'static; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 116 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 117 | async fn chunk_large_query_over<Query, Res>( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 118 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 119 | mut keys_to_chunk: Vec<Key>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 120 | result_capacity: Option<usize>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 121 | do_query: Query, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 122 | ) -> Result<Vec<Res>> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 123 | where |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 124 | Res: Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 125 | Query: Fn(&Transaction<'_>, Vec<Key>) -> Result<Vec<Res>> + Send + 'static; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 126 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 127 | /// Apply the [`RuntimeConfig`]. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 128 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 129 | /// It will call the `Self::optimize`, `Self::cache_size` or |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 130 | /// `Self::journal_size_limit` methods automatically based on the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 131 | /// `RuntimeConfig` values. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 132 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 133 | /// It is possible to call these methods individually though. This |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 134 | /// `apply_runtime_config` method allows to automate this process. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 135 | async fn apply_runtime_config(&self, runtime_config: RuntimeConfig) -> Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 136 | let RuntimeConfig { optimize, cache_size, journal_size_limit } = runtime_config; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 137 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 138 | if optimize { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 139 | self.optimize().await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 140 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 141 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 142 | self.cache_size(cache_size).await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 143 | self.journal_size_limit(journal_size_limit).await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 144 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 145 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 146 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 147 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 148 | /// Optimize the database. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 149 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 150 | /// The SQLite documentation recommends to run this regularly and after any |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 151 | /// schema change. The easiest is to do it consistently when the store is |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 152 | /// constructed, after eventual migrations. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 153 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 154 | /// See [`PRAGMA optimize`] to learn more. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 155 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 156 | /// [`PRAGMA cache_size`]: https://www.sqlite.org/pragma.html#pragma_optimize |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 157 | async fn optimize(&self) -> Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 158 | self.execute_batch("PRAGMA optimize = 0x10002;").await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 159 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 160 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 161 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 162 | /// Define the maximum size in **bytes** the SQLite cache can use. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 163 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 164 | /// See [`PRAGMA cache_size`] to learn more. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 165 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 166 | /// [`PRAGMA cache_size`]: https://www.sqlite.org/pragma.html#pragma_cache_size |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 167 | async fn cache_size(&self, cache_size: u32) -> Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 168 | // `N` in `PRAGMA cache_size = -N` is expressed in kibibytes. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 169 | // `cache_size` is expressed in bytes. Let's convert. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 170 | let n = cache_size / 1024; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 171 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 172 | self.execute_batch(format!("PRAGMA cache_size = -{n};")).await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 173 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 174 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 175 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 176 | /// Limit the size of the WAL file, in **bytes**. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 177 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 178 | /// By default, while the DB connections of the databases are open, [the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 179 | /// size of the WAL file can keep increasing][size_wal_file] depending on |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 180 | /// the size needed for the transactions. A critical case is `VACUUM` |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 181 | /// which basically writes the content of the DB file to the WAL file |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 182 | /// before writing it back to the DB file, so we end up taking twice the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 183 | /// size of the database. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 184 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 185 | /// By setting this limit, the WAL file is truncated after its content is |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 186 | /// written to the database, if it is bigger than the limit. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 187 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 188 | /// See [`PRAGMA journal_size_limit`] to learn more. The value `limit` |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 189 | /// corresponds to `N` in `PRAGMA journal_size_limit = N`. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 190 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 191 | /// [size_wal_file]: https://www.sqlite.org/wal.html#avoiding_excessively_large_wal_files |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 192 | /// [`PRAGMA journal_size_limit`]: https://www.sqlite.org/pragma.html#pragma_journal_size_limit |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 193 | async fn journal_size_limit(&self, limit: u32) -> Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 194 | self.execute_batch(format!("PRAGMA journal_size_limit = {limit};")).await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 195 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 196 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 197 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 198 | /// Defragment the database and free space on the filesystem. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 199 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 200 | /// Only returns an error in tests, otherwise the error is only logged. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 201 | async fn vacuum(&self) -> Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 202 | // Truncate the WAL file before vacuuming so it has room to grow. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 203 | self.wal_checkpoint().await; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 204 | if let Err(error) = self.execute_batch("VACUUM").await { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 205 | // Since this is an optimisation step, do not propagate the error |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 206 | // but log it. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 207 | #[cfg(not(any(test, debug_assertions)))] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 208 | tracing::warn!("Failed to vacuum database: {error}"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 209 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 210 | // We want to know if there is an error with this step during tests. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 211 | #[cfg(any(test, debug_assertions))] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 212 | return Err(error.into()); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 213 | } else { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 214 | trace!("VACUUM complete"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 215 | // Once vacuumed, truncate the WAL file again to purge the copied DB contents. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 216 | self.wal_checkpoint().await; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 217 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 218 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 219 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 220 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 221 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 222 | /// Adds a manual [WAL checkpoint] to copy back the contents of the WAL |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 223 | /// files into the actual database, resetting the write-ahead log. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 224 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 225 | /// [WAL checkpoint]: https://sqlite.org/c3ref/wal_checkpoint.html |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 226 | async fn wal_checkpoint(&self) { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 227 | match self.execute_batch("PRAGMA wal_checkpoint(TRUNCATE);").await { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 228 | Ok(_) => trace!("WAL checkpoint completed"), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 229 | Err(error) => error!(?error, "WAL checkpoint error"), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 230 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 231 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 232 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 233 | async fn get_db_size(&self) -> Result<usize> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 234 | let page_size = |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 235 | self.query_row("PRAGMA page_size;", (), |row| row.get::<_, usize>(0)).await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 236 | let total_pages = |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 237 | self.query_row("PRAGMA page_count;", (), |row| row.get::<_, usize>(0)).await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 238 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 239 | Ok(total_pages * page_size) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 240 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 241 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 242 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 243 | #[async_trait] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 244 | impl SqliteAsyncConnExt for SqliteAsyncConn { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 245 | async fn execute<P>( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 246 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 247 | sql: impl AsRef<str> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 248 | params: P, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 249 | ) -> rusqlite::Result<usize> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 250 | where |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 251 | P: Params + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 252 | { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 253 | self.interact(move |conn| conn.execute(sql.as_ref(), params)) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 254 | .await |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 255 | .map_err(map_interact_err)? |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 256 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 257 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 258 | async fn execute_batch(&self, sql: impl AsRef<str> + Send + 'static) -> rusqlite::Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 259 | self.interact(move |conn| conn.execute_batch(sql.as_ref())) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 260 | .await |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 261 | .map_err(map_interact_err)? |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 262 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 263 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 264 | async fn prepare<T, F>( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 265 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 266 | sql: impl AsRef<str> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 267 | f: F, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 268 | ) -> rusqlite::Result<T> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 269 | where |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 270 | T: Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 271 | F: FnOnce(Statement<'_>) -> rusqlite::Result<T> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 272 | { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 273 | self.interact(move |conn| f(conn.prepare(sql.as_ref())?)).await.map_err(map_interact_err)? |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 274 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 275 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 276 | async fn query_row<T, P, F>( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 277 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 278 | sql: impl AsRef<str> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 279 | params: P, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 280 | f: F, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 281 | ) -> rusqlite::Result<T> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 282 | where |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 283 | T: Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 284 | P: Params + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 285 | F: FnOnce(&Row<'_>) -> rusqlite::Result<T> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 286 | { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 287 | self.interact(move |conn| conn.query_row(sql.as_ref(), params, f)) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 288 | .await |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 289 | .map_err(map_interact_err)? |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 290 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 291 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 292 | async fn query_many<T, P, F>( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 293 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 294 | sql: impl AsRef<str> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 295 | params: P, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 296 | f: F, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 297 | ) -> rusqlite::Result<Vec<T>> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 298 | where |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 299 | T: Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 300 | P: Params + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 301 | F: FnMut(&Row<'_>) -> rusqlite::Result<T> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 302 | { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 303 | self.interact(move |conn| { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 304 | let mut stmt = conn.prepare(sql.as_ref())?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 305 | stmt.query_and_then(params, f)?.collect() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 306 | }) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 307 | .await |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 308 | .map_err(map_interact_err)? |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 309 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 310 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 311 | async fn with_transaction<T, E, F>(&self, f: F) -> Result<T, E> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 312 | where |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 313 | T: Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 314 | E: From<rusqlite::Error> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 315 | F: FnOnce(&Transaction<'_>) -> Result<T, E> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 316 | { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 317 | self.interact(move |conn| { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 318 | let txn = conn.transaction()?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 319 | let result = f(&txn)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 320 | txn.commit()?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 321 | Ok(result) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 322 | }) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 323 | .await |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 324 | .map_err(map_interact_err) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 325 | .map_err(E::from)? |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 326 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 327 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 328 | /// Chunk a large query over some keys. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 329 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 330 | /// Imagine there is a _dynamic_ query that runs potentially large number of |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 331 | /// parameters, so much that the maximum number of parameters can be hit. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 332 | /// Then, this helper is for you. It will execute the query on chunks of |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 333 | /// parameters. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 334 | async fn chunk_large_query_over<Query, Res>( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 335 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 336 | keys_to_chunk: Vec<Key>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 337 | result_capacity: Option<usize>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 338 | do_query: Query, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 339 | ) -> Result<Vec<Res>> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 340 | where |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 341 | Res: Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 342 | Query: Fn(&Transaction<'_>, Vec<Key>) -> Result<Vec<Res>> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 343 | { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 344 | self.with_transaction(move |txn| { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 345 | txn.chunk_large_query_over(keys_to_chunk, result_capacity, do_query) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 346 | }) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 347 | .await |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 348 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 349 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 350 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 351 | /// Map an [`InteractError`] into a [`rusqlite::Error`]. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 352 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 353 | /// An [`InteractError::Panic`] will panic. An [`InteractError::Cancelled`] will |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 354 | /// generate a [`rusqlite::Error::SqliteFailure`] with the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 355 | /// [`rusqlite::ffi::SQLITE_ABORT`] code. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 356 | fn map_interact_err(error: InteractError) -> rusqlite::Error { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 357 | match error { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 358 | InteractError::Panic(p) => panic!("{p:?}"), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 359 | InteractError::Cancelled => rusqlite::Error::SqliteFailure( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 360 | rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_ABORT), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 361 | None, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 362 | ), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 363 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 364 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 365 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 366 | pub(crate) trait SqliteTransactionExt { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 367 | fn chunk_large_query_over<Key, Query, Res>( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 368 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 369 | keys_to_chunk: Vec<Key>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 370 | result_capacity: Option<usize>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 371 | do_query: Query, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 372 | ) -> Result<Vec<Res>> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 373 | where |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 374 | Res: Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 375 | Query: Fn(&Transaction<'_>, Vec<Key>) -> Result<Vec<Res>> + Send + 'static; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 376 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 377 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 378 | impl SqliteTransactionExt for Transaction<'_> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 379 | fn chunk_large_query_over<Key, Query, Res>( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 380 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 381 | mut keys_to_chunk: Vec<Key>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 382 | result_capacity: Option<usize>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 383 | do_query: Query, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 384 | ) -> Result<Vec<Res>> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 385 | where |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 386 | Res: Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 387 | Query: Fn(&Transaction<'_>, Vec<Key>) -> Result<Vec<Res>> + Send + 'static, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 388 | { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 389 | // Divide by 2 to allow space for more static parameters (not part of |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 390 | // `keys_to_chunk`). |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 391 | let maximum_chunk_size = self.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER)? / 2; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 392 | let maximum_chunk_size: usize = maximum_chunk_size |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 393 | .try_into() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 394 | .map_err(|_| Error::SqliteMaximumVariableNumber(maximum_chunk_size))?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 395 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 396 | if keys_to_chunk.len() < maximum_chunk_size { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 397 | // Chunking isn't necessary. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 398 | let chunk = keys_to_chunk; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 399 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 400 | Ok(do_query(self, chunk)?) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 401 | } else { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 402 | // Chunking _is_ necessary. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 403 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 404 | // Define the accumulator. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 405 | let capacity = result_capacity.unwrap_or_default(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 406 | let mut all_results = Vec::with_capacity(capacity); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 407 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 408 | while !keys_to_chunk.is_empty() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 409 | // Chunk and run the query. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 410 | let tail = keys_to_chunk.split_off(min(keys_to_chunk.len(), maximum_chunk_size)); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 411 | let chunk = keys_to_chunk; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 412 | keys_to_chunk = tail; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 413 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 414 | all_results.extend(do_query(self, chunk)?); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 415 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 416 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 417 | Ok(all_results) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 418 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 419 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 420 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 421 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 422 | /// Extension trait for a [`rusqlite::Connection`] that contains a key-value |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 423 | /// table named `kv`. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 424 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 425 | /// The table should be created like this: |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 426 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 427 | /// ```sql |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 428 | /// CREATE TABLE "kv" ( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 429 | /// "key" TEXT PRIMARY KEY NOT NULL, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 430 | /// "value" BLOB NOT NULL |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 431 | /// ); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 432 | /// ``` |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 433 | pub(crate) trait SqliteKeyValueStoreConnExt { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 434 | /// Store the given value for the given key. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 435 | fn set_kv(&self, key: &str, value: &[u8]) -> rusqlite::Result<()>; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 436 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 437 | /// Store the given value for the given key by serializing it. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 438 | fn set_serialized_kv<T: Serialize + Send>(&self, key: &str, value: T) -> Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 439 | let serialized_value = rmp_serde::to_vec_named(&value)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 440 | self.set_kv(key, &serialized_value)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 441 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 442 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 443 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 444 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 445 | /// Removes the current key and value if exists. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 446 | fn clear_kv(&self, key: &str) -> rusqlite::Result<()>; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 447 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 448 | /// Set the version of the database. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 449 | fn set_db_version(&self, version: u8) -> rusqlite::Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 450 | self.set_kv("version", &[version]) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 451 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 452 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 453 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 454 | impl SqliteKeyValueStoreConnExt for rusqlite::Connection { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 455 | fn set_kv(&self, key: &str, value: &[u8]) -> rusqlite::Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 456 | self.execute( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 457 | "INSERT INTO kv VALUES (?1, ?2) ON CONFLICT (key) DO UPDATE SET value = ?2", |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 458 | (key, value), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 459 | )?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 460 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 461 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 462 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 463 | fn clear_kv(&self, key: &str) -> rusqlite::Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 464 | self.execute("DELETE FROM kv WHERE key = ?1", (key,))?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 465 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 466 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 467 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 468 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 469 | /// Extension trait for an [`SqliteAsyncConn`] that contains a key-value |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 470 | /// table named `kv`. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 471 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 472 | /// The table should be created like this: |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 473 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 474 | /// ```sql |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 475 | /// CREATE TABLE "kv" ( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 476 | /// "key" TEXT PRIMARY KEY NOT NULL, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 477 | /// "value" BLOB NOT NULL |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 478 | /// ); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 479 | /// ``` |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 480 | #[async_trait] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 481 | pub(crate) trait SqliteKeyValueStoreAsyncConnExt: SqliteAsyncConnExt { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 482 | /// Whether the `kv` table exists in this database. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 483 | async fn kv_table_exists(&self) -> rusqlite::Result<bool> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 484 | self.query_row( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 485 | "SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'kv')", |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 486 | (), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 487 | |row| row.get(0), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 488 | ) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 489 | .await |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 490 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 491 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 492 | /// Get the stored value for the given key. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 493 | async fn get_kv(&self, key: &str) -> rusqlite::Result<Option<Vec<u8>>> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 494 | let key = key.to_owned(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 495 | self.query_row("SELECT value FROM kv WHERE key = ?", (key,), |row| row.get(0)) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 496 | .await |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 497 | .optional() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 498 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 499 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 500 | /// Get the stored serialized value for the given key. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 501 | async fn get_serialized_kv<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 502 | let Some(bytes) = self.get_kv(key).await? else { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 503 | return Ok(None); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 504 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 505 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 506 | Ok(Some(rmp_serde::from_slice(&bytes)?)) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 507 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 508 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 509 | /// Store the given value for the given key. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 510 | async fn set_kv(&self, key: &str, value: Vec<u8>) -> rusqlite::Result<()>; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 511 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 512 | /// Store the given value for the given key by serializing it. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 513 | async fn set_serialized_kv<T: Serialize + Send + 'static>( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 514 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 515 | key: &str, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 516 | value: T, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 517 | ) -> Result<()>; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 518 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 519 | /// Clears the given value for the given key. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 520 | async fn clear_kv(&self, key: &str) -> rusqlite::Result<()>; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 521 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 522 | /// Get the version of the database. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 523 | async fn db_version(&self) -> Result<u8, OpenStoreError> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 524 | let kv_exists = self.kv_table_exists().await.map_err(OpenStoreError::LoadVersion)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 525 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 526 | if kv_exists { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 527 | match self.get_kv("version").await.map_err(OpenStoreError::LoadVersion)?.as_deref() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 528 | Some([v]) => Ok(*v), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 529 | Some(_) => Err(OpenStoreError::InvalidVersion), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 530 | None => Err(OpenStoreError::MissingVersion), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 531 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 532 | } else { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 533 | Ok(0) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 534 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 535 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 536 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 537 | /// Get the [`StoreCipher`] of the database or create it. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 538 | async fn get_or_create_store_cipher( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 539 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 540 | mut secret: Secret, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 541 | ) -> Result<StoreCipher, OpenStoreError> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 542 | let encrypted_cipher = self.get_kv("cipher").await.map_err(OpenStoreError::LoadCipher)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 543 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 544 | let cipher = if let Some(encrypted) = encrypted_cipher { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 545 | match secret { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 546 | Secret::PassPhrase(ref passphrase) => StoreCipher::import(passphrase, &encrypted)?, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 547 | Secret::Key(ref key) => StoreCipher::import_with_key(key, &encrypted)?, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 548 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 549 | } else { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 550 | let cipher = StoreCipher::new()?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 551 | let export = match secret { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 552 | Secret::PassPhrase(ref passphrase) => { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 553 | #[cfg(not(test))] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 554 | { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 555 | cipher.export(passphrase) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 556 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 557 | #[cfg(test)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 558 | { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 559 | cipher._insecure_export_fast_for_testing(passphrase) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 560 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 561 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 562 | Secret::Key(ref key) => cipher.export_with_key(key), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 563 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 564 | self.set_kv("cipher", export?).await.map_err(OpenStoreError::SaveCipher)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 565 | cipher |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 566 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 567 | secret.zeroize(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 568 | Ok(cipher) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 569 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 570 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 571 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 572 | #[async_trait] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 573 | impl SqliteKeyValueStoreAsyncConnExt for SqliteAsyncConn { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 574 | async fn set_kv(&self, key: &str, value: Vec<u8>) -> rusqlite::Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 575 | let key = key.to_owned(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 576 | self.interact(move |conn| conn.set_kv(&key, &value)).await.unwrap()?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 577 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 578 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 579 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 580 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 581 | async fn set_serialized_kv<T: Serialize + Send + 'static>( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 582 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 583 | key: &str, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 584 | value: T, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 585 | ) -> Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 586 | let key = key.to_owned(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 587 | self.interact(move |conn| conn.set_serialized_kv(&key, value)).await.unwrap()?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 588 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 589 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 590 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 591 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 592 | async fn clear_kv(&self, key: &str) -> rusqlite::Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 593 | let key = key.to_owned(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 594 | self.interact(move |conn| conn.clear_kv(&key)).await.unwrap()?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 595 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 596 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 597 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 598 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 599 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 600 | /// Repeat `?` n times, where n is defined by `count`. `?` are comma-separated. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 601 | pub(crate) fn repeat_vars(count: usize) -> impl fmt::Display { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 602 | assert_ne!(count, 0, "Can't generate zero repeated vars"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 603 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 604 | iter::repeat_n("?", count).format(",") |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 605 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 606 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 607 | /// Convert the given `SystemTime` to a timestamp, as the number of seconds |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 608 | /// since Unix Epoch. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 609 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 610 | /// Returns an `i64` as it is the numeric type used by SQLite. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 611 | pub(crate) fn time_to_timestamp(time: SystemTime) -> i64 { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 612 | time.duration_since(SystemTime::UNIX_EPOCH) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 613 | .ok() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 614 | .and_then(|d| d.as_secs().try_into().ok()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 615 | // It is unlikely to happen unless the time on the system is seriously wrong, but we always |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 616 | // need a value. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 617 | .unwrap_or(0) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 618 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 619 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 620 | /// Trait for a store that can encrypt its values, based on the presence of a |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 621 | /// cipher or not. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 622 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 623 | /// A single method must be implemented: `get_cypher`, which returns an optional |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 624 | /// cipher. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 625 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 626 | /// All the other methods come for free, based on the implementation of |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 627 | /// `get_cypher`. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 628 | pub(crate) trait EncryptableStore { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 629 | fn get_cypher(&self) -> Option<&StoreCipher>; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 630 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 631 | /// If the store is using encryption, this will hash the given key. This is |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 632 | /// useful when we need to do queries against a given key, but we don't |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 633 | /// need to store the key in plain text (i.e. it's not both a key and a |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 634 | /// value). |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 635 | fn encode_key(&self, table_name: &str, key: impl AsRef<[u8]>) -> Key { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 636 | let bytes = key.as_ref(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 637 | if let Some(store_cipher) = self.get_cypher() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 638 | Key::Hashed(store_cipher.hash_key(table_name, bytes)) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 639 | } else { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 640 | Key::Plain(bytes.to_owned()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 641 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 642 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 643 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 644 | fn encode_value(&self, value: Vec<u8>) -> Result<Vec<u8>> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 645 | if let Some(key) = self.get_cypher() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 646 | let encrypted = key.encrypt_value_data(value)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 647 | Ok(rmp_serde::to_vec_named(&encrypted)?) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 648 | } else { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 649 | Ok(value) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 650 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 651 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 652 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 653 | fn decode_value<'a>(&self, value: &'a [u8]) -> Result<Cow<'a, [u8]>> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 654 | if let Some(key) = self.get_cypher() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 655 | let encrypted = rmp_serde::from_slice(value)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 656 | let decrypted = key.decrypt_value_data(encrypted)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 657 | Ok(Cow::Owned(decrypted)) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 658 | } else { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 659 | Ok(Cow::Borrowed(value)) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 660 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 661 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 662 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 663 | fn serialize_value(&self, value: &impl Serialize) -> Result<Vec<u8>> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 664 | let serialized = rmp_serde::to_vec_named(value)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 665 | self.encode_value(serialized) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 666 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 667 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 668 | fn deserialize_value<T: DeserializeOwned>(&self, value: &[u8]) -> Result<T> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 669 | let decoded = self.decode_value(value)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 670 | Ok(rmp_serde::from_slice(&decoded)?) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 671 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 672 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 673 | fn serialize_json(&self, value: &impl Serialize) -> Result<Vec<u8>> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 674 | let serialized = serde_json::to_vec(value)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 675 | self.encode_value(serialized) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 676 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 677 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 678 | fn deserialize_json<T: DeserializeOwned>(&self, data: &[u8]) -> Result<T> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 679 | let decoded = self.decode_value(data)?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 680 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 681 | let json_deserializer = &mut serde_json::Deserializer::from_slice(&decoded); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 682 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 683 | serde_path_to_error::deserialize(json_deserializer).map_err(|err| { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 684 | let raw_json: Option<Raw<serde_json::Value>> = serde_json::from_slice(&decoded).ok(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 685 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 686 | let target_type = std::any::type_name::<T>(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 687 | let serde_path = err.path().to_string(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 688 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 689 | error!( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 690 | sentry = true, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 691 | %err, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 692 | "Failed to deserialize {target_type} in a store: {serde_path}", |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 693 | ); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 694 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 695 | if let Some(raw) = raw_json { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 696 | if let Some(room_id) = raw.get_field::<OwnedRoomId>("room_id").ok().flatten() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 697 | warn!("Found a room id in the source data to deserialize: {room_id}"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 698 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 699 | if let Some(event_id) = raw.get_field::<OwnedEventId>("event_id").ok().flatten() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 700 | warn!("Found an event id in the source data to deserialize: {event_id}"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 701 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 702 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 703 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 704 | err.into_inner().into() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 705 | }) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 706 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 707 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 708 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 709 | #[cfg(test)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 710 | mod unit_tests { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 711 | use std::time::Duration; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 712 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 713 | use super::*; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 714 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 715 | #[test] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 716 | fn can_generate_repeated_vars() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 717 | assert_eq!(repeat_vars(1).to_string(), "?"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 718 | assert_eq!(repeat_vars(2).to_string(), "?,?"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 719 | assert_eq!(repeat_vars(5).to_string(), "?,?,?,?,?"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 720 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 721 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 722 | #[test] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 723 | #[should_panic(expected = "Can't generate zero repeated vars")] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 724 | fn generating_zero_vars_panics() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 725 | repeat_vars(0); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 726 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 727 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 728 | #[test] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 729 | fn test_time_to_timestamp() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 730 | assert_eq!(time_to_timestamp(SystemTime::UNIX_EPOCH), 0); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 731 | assert_eq!(time_to_timestamp(SystemTime::UNIX_EPOCH + Duration::from_secs(60)), 60); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 732 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 733 | // Fallback value on overflow. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 734 | assert_eq!(time_to_timestamp(SystemTime::UNIX_EPOCH - Duration::from_secs(60)), 0); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 735 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 736 | } |