1#[macro_use]
2mod macros;
3
4mod connection;
5mod id;
6mod migrations;
7
8pub use id::DatabaseId;
9pub use migrations::SCHEMA_VERSION;
10
11use tracing::Span;
12
13use deadlock::ExpectShortLifetime;
14use ref_cast::RefCast;
15use sqlx::{
16 ConnectOptions, Row, SqlitePool, TransactionManager,
17 sqlite::{
18 Sqlite, SqliteAutoVacuum, SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions,
19 SqliteSynchronous, SqliteTransactionManager,
20 },
21};
22use std::{
23 fmt, io,
24 ops::{Deref, DerefMut},
25 panic::Location,
26 path::{Path, PathBuf},
27 time::Duration,
28};
29#[cfg(test)]
30use tempfile::TempDir;
31use thiserror::Error;
32use tokio::{fs, sync::Semaphore, task};
33
34const ACQUIRE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
35const IDLE_TIMEOUT: Duration = Duration::from_secs(60);
36const WARN_AFTER_CONNECTION_LIFETIME: Duration = Duration::from_secs(30);
37
38pub use self::connection::Connection;
39
40#[derive(Clone)]
42pub struct Pool {
43 path: PathBuf,
45 reads: SqlitePool,
47 write: SqlitePool,
49}
50
51impl Pool {
52 async fn create(conn_options: SqliteConnectOptions) -> Result<Self, sqlx::Error> {
53 let path = conn_options.get_filename().to_path_buf();
54
55 if fs::try_exists(&path).await.unwrap_or(false) {
56 if let Err(error) = enable_auto_vacuum(conn_options.get_filename()).await {
62 tracing::warn!(?error, "failed to enable auto vacuum");
63 }
64 }
65
66 let conn_options = conn_options
67 .journal_mode(SqliteJournalMode::Wal)
68 .synchronous(SqliteSynchronous::Normal)
69 .pragma("recursive_triggers", "ON");
70
71 let pool_options = SqlitePoolOptions::new()
72 .test_before_acquire(false)
74 .idle_timeout(IDLE_TIMEOUT)
76 .acquire_timeout(ACQUIRE_TIMEOUT);
77
78 let write = pool_options
79 .clone()
80 .max_connections(1)
81 .connect_with(
82 conn_options
83 .clone()
84 .auto_vacuum(SqliteAutoVacuum::Full)
85 .optimize_on_close(true, Some(1000)),
86 )
87 .await?;
88
89 let reads = pool_options
90 .max_connections(8)
91 .connect_with(conn_options.read_only(true))
92 .await?;
93
94 Ok(Self { path, reads, write })
95 }
96
97 pub fn store_path(&self) -> &Path {
99 self.path.as_ref()
100 }
101
102 #[track_caller]
104 pub fn acquire(&self) -> impl Future<Output = Result<PoolConnection, sqlx::Error>> + '_ {
105 PoolConnection::acquire(&self.reads, Location::caller())
106 }
107
108 #[track_caller]
110 pub fn begin_read(&self) -> impl Future<Output = Result<ReadTransaction, sqlx::Error>> + '_ {
111 ReadTransaction::begin(&self.reads, Location::caller())
112 }
113
114 #[track_caller]
116 pub fn begin_write(&self) -> impl Future<Output = Result<WriteTransaction, sqlx::Error>> + '_ {
117 let location = Location::caller();
118
119 async move {
120 Ok(WriteTransaction {
121 inner: ReadTransaction::begin(&self.write, location).await?,
122 })
123 }
124 }
125
126 pub(crate) async fn close(&self) -> Result<(), sqlx::Error> {
127 self.reads.close().await;
131 self.write.close().await;
132
133 Ok(())
134 }
135}
136
137pub struct PoolConnection {
139 inner: sqlx::pool::PoolConnection<Sqlite>,
140 _track_lifetime: ExpectShortLifetime,
141}
142
143impl PoolConnection {
144 async fn acquire(
146 pool: &SqlitePool,
147 location: &'static Location<'static>,
148 ) -> Result<Self, sqlx::Error> {
149 let inner = pool.acquire().await?;
150 let track_lifetime = ExpectShortLifetime::new_in(WARN_AFTER_CONNECTION_LIFETIME, location);
151
152 Ok(Self {
153 inner,
154 _track_lifetime: track_lifetime,
155 })
156 }
157}
158
159impl Deref for PoolConnection {
160 type Target = Connection;
161
162 fn deref(&self) -> &Self::Target {
163 Connection::ref_cast(self.inner.deref())
164 }
165}
166
167impl DerefMut for PoolConnection {
168 fn deref_mut(&mut self) -> &mut Self::Target {
169 Connection::ref_cast_mut(self.inner.deref_mut())
170 }
171}
172
173pub struct ReadTransaction {
181 inner: PoolConnection,
182 closed: bool,
183}
184
185impl ReadTransaction {
186 async fn begin(
188 pool: &SqlitePool,
189 location: &'static Location<'static>,
190 ) -> Result<Self, sqlx::Error> {
191 let mut inner = PoolConnection::acquire(pool, location).await?;
192 SqliteTransactionManager::begin(&mut inner.inner, None).await?;
193
194 Ok(Self {
195 inner,
196 closed: false,
197 })
198 }
199
200 async fn commit(mut self) -> Result<Committed, sqlx::Error> {
202 SqliteTransactionManager::commit(&mut self.inner.inner).await?;
203 self.closed = true;
204 Ok(Committed(self))
205 }
206}
207
208impl Deref for ReadTransaction {
209 type Target = Connection;
210
211 fn deref(&self) -> &Self::Target {
212 self.inner.deref()
213 }
214}
215
216impl DerefMut for ReadTransaction {
217 fn deref_mut(&mut self) -> &mut Self::Target {
218 self.inner.deref_mut()
219 }
220}
221
222impl fmt::Debug for ReadTransaction {
223 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
224 f.debug_struct("ReadTransaction").finish_non_exhaustive()
225 }
226}
227
228impl_executor_by_deref!(ReadTransaction);
229
230impl Drop for ReadTransaction {
231 fn drop(&mut self) {
232 if !self.closed {
233 SqliteTransactionManager::start_rollback(&mut self.inner.inner);
234 }
235 }
236}
237
238struct Committed(#[allow(dead_code)] ReadTransaction);
241
242pub struct WriteTransaction {
250 inner: ReadTransaction,
251}
252
253impl WriteTransaction {
254 pub async fn commit(self) -> Result<(), sqlx::Error> {
262 self.inner.commit().await?;
263 Ok(())
264 }
265
266 pub async fn commit_and_then<F, R>(self, f: F) -> Result<R, sqlx::Error>
305 where
306 F: FnOnce() -> R + Send + 'static,
307 R: Send + 'static,
308 {
309 let span = Span::current();
310
311 task::spawn(async move {
312 let _committed = self.inner.commit().await?;
314 let result = span.in_scope(f);
315 Ok(result)
316 })
317 .await
318 .unwrap()
319 }
320}
321
322impl Deref for WriteTransaction {
323 type Target = ReadTransaction;
324
325 fn deref(&self) -> &Self::Target {
326 &self.inner
327 }
328}
329
330impl DerefMut for WriteTransaction {
331 fn deref_mut(&mut self) -> &mut Self::Target {
332 &mut self.inner
333 }
334}
335
336impl std::fmt::Debug for WriteTransaction {
337 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
338 write!(f, "WriteTransaction{{ inner:{:?} }}", self.inner)
339 }
340}
341
342impl_executor_by_deref!(WriteTransaction);
343
344#[derive(Debug, Error)]
345pub enum Error {
346 #[error("failed to create database directory")]
347 CreateDirectory(#[source] io::Error),
348 #[error("database already exists")]
349 Exists,
350 #[error("failed to open database")]
351 Open(#[source] sqlx::Error),
352 #[error("failed to execute database query")]
353 Query(#[from] sqlx::Error),
354}
355
356pub(crate) async fn create(path: impl AsRef<Path>) -> Result<Pool, Error> {
358 let path = path.as_ref();
359
360 if fs::metadata(path).await.is_ok() {
361 return Err(Error::Exists);
362 }
363
364 create_directory(path).await?;
365
366 let connect_options = SqliteConnectOptions::new()
367 .filename(path)
368 .create_if_missing(true);
369
370 let pool = Pool::create(connect_options).await.map_err(Error::Open)?;
371
372 migrations::run(&pool).await?;
373
374 Ok(pool)
375}
376
377#[cfg(test)]
379pub(crate) async fn create_temp() -> Result<(TempDir, Pool), Error> {
380 let temp_dir = TempDir::new().map_err(Error::CreateDirectory)?;
381 let pool = create(temp_dir.path().join("temp.db")).await?;
382
383 Ok((temp_dir, pool))
384}
385
386pub(crate) async fn open(path: impl AsRef<Path>) -> Result<Pool, Error> {
388 let connect_options = SqliteConnectOptions::new().filename(path);
389 let pool = Pool::create(connect_options).await.map_err(Error::Open)?;
390
391 migrations::run(&pool).await?;
392
393 Ok(pool)
394}
395
396pub async fn open_without_migrations(path: impl AsRef<Path>) -> Result<Pool, Error> {
398 let connect_options = SqliteConnectOptions::new().filename(path);
399 let pool = Pool::create(connect_options).await.map_err(Error::Open)?;
400
401 Ok(pool)
402}
403
404async fn create_directory(path: &Path) -> Result<(), Error> {
405 if let Some(dir) = path.parent() {
406 fs::create_dir_all(dir)
407 .await
408 .map_err(Error::CreateDirectory)?
409 }
410
411 Ok(())
412}
413
414pub(crate) const fn decode_u64(i: i64) -> u64 {
417 i as u64
418}
419
420pub(crate) const fn encode_u64(u: u64) -> i64 {
423 u as i64
424}
425
426async fn enable_auto_vacuum(db_path: &Path) -> Result<(), Error> {
428 let mut conn = SqliteConnectOptions::new()
429 .filename(db_path)
430 .connect()
431 .await?;
432
433 let auto_vacuum: u32 = sqlx::query("PRAGMA auto_vacuum")
434 .fetch_one(&mut conn)
435 .await?
436 .get(0);
437
438 if auto_vacuum != 0 {
439 return Ok(());
440 }
441
442 static SEMAPHORE: Semaphore = Semaphore::const_new(1);
447 let _permit = SEMAPHORE.acquire().await.unwrap();
448
449 sqlx::query("PRAGMA auto_vacuum=FULL")
450 .execute(&mut conn)
451 .await?;
452
453 sqlx::query("VACUUM").execute(&mut conn).await?;
455
456 Ok(())
457}
458
459async fn get_pragma(conn: &mut Connection, name: &str) -> Result<u32, Error> {
460 Ok(sqlx::query(&format!("PRAGMA {name}"))
461 .fetch_one(&mut *conn)
462 .await?
463 .get(0))
464}
465
466async fn set_pragma(conn: &mut Connection, name: &str, value: u32) -> Result<(), Error> {
467 sqlx::query(&format!("PRAGMA {name} = {value}"))
469 .execute(&mut *conn)
470 .await?;
471
472 Ok(())
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478
479 #[test]
482 fn decode_u64_sanity_check() {
483 assert_eq!(decode_u64(0), 0);
487 assert_eq!(decode_u64(1), 1);
488 assert_eq!(decode_u64(-1), u64::MAX);
489 assert_eq!(decode_u64(i64::MIN), u64::MAX / 2 + 1);
490 assert_eq!(decode_u64(i64::MAX), u64::MAX / 2);
491 }
492
493 #[test]
494 fn encode_u64_sanity_check() {
495 assert_eq!(encode_u64(0), 0);
496 assert_eq!(encode_u64(1), 1);
497 assert_eq!(encode_u64(u64::MAX / 2), i64::MAX);
498 assert_eq!(encode_u64(u64::MAX / 2 + 1), i64::MIN);
499 assert_eq!(encode_u64(u64::MAX), -1);
500 }
501}