Skip to main content

max / goingson

3.1 KB · 91 lines History Blame Raw
1 //! SQLite database layer for GoingsOn.
2 //!
3 //! Provides SQLite implementations of all repository traits defined in `goingson-core`.
4 //! Uses `sqlx` for async database operations with connection pooling.
5 //!
6 //! # Usage
7 //!
8 //! ```ignore
9 //! let pool = goingson_db_sqlite::init_pool(Some("./data.db")).await?;
10 //! goingson_db_sqlite::run_migrations(&pool).await?;
11 //! let task_repo = SqliteTaskRepository::new(pool.clone());
12 //! ```
13
14 pub mod migrations;
15 pub mod repository;
16 pub mod utils;
17
18 use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions};
19 use sqlx::SqlitePool;
20 use std::str::FromStr;
21 use std::time::Duration;
22
23 /// Initialize the SQLite database connection pool
24 /// If database_path is None, uses an in-memory database
25 pub async fn init_pool(database_path: Option<&str>) -> Result<SqlitePool, sqlx::Error> {
26 let path = database_path.unwrap_or("goingson.db");
27
28 // Build options from a real filesystem path via `.filename()` so a path
29 // containing URI syntax (`?vfs=`, `?mode=memory`, `#frag`) is treated as a
30 // literal file, never parsed as connection options. `:memory:` still needs
31 // the URL form so sqlx sets its `in_memory` flag and the 5-connection pool
32 // shares one database (tests rely on this).
33 let base = if path == ":memory:" {
34 SqliteConnectOptions::from_str("sqlite::memory:")?
35 } else {
36 SqliteConnectOptions::new().filename(path)
37 };
38 let options = base
39 .create_if_missing(true)
40 .foreign_keys(true)
41 .journal_mode(SqliteJournalMode::Wal)
42 // WAL allows concurrent readers + one writer; a second writer (e.g. a
43 // backup tick racing a sync pull) would otherwise get SQLITE_BUSY
44 // immediately. Wait briefly for the lock instead. acquire_timeout only
45 // bounds pool checkout, not lock contention inside a held connection.
46 .busy_timeout(Duration::from_secs(5));
47
48 SqlitePoolOptions::new()
49 .max_connections(5)
50 .acquire_timeout(Duration::from_secs(3))
51 .connect_with(options)
52 .await
53 }
54
55 /// Run database migrations
56 pub async fn run_migrations(pool: &SqlitePool) -> Result<(), sqlx::migrate::MigrateError> {
57 sqlx::migrate!("../../migrations/sqlite")
58 .run(pool)
59 .await?;
60
61 // Ensure foreign keys are enabled (safety net for interrupted migrations)
62 sqlx::query("PRAGMA foreign_keys = ON")
63 .execute(pool)
64 .await
65 .map_err(sqlx::migrate::MigrateError::Execute)?;
66
67 Ok(())
68 }
69
70 pub use repository::{
71 SqliteAttachmentRepository,
72 SqliteBackupSettingsRepository,
73 SqliteContactRepository,
74 SqliteDailyNoteRepository,
75 SqliteProjectRepository,
76 SqliteTaskRepository,
77 SqliteEventRepository,
78 SqliteEmailRepository,
79 SqliteUserRepository,
80 SqliteEmailAccountRepository,
81 SqliteStatsRepository,
82 SqliteSearchRepository,
83 SqliteMilestoneRepository,
84 SqliteMonthlyReviewRepository,
85 SqliteSavedViewRepository,
86 SqliteSyncAccountRepository,
87 SqliteWeeklyReviewRepository,
88 };
89
90 pub use repository::restore::{restore_all, BACKUP_TABLES, EXCLUDED_TABLES};
91