OXYGENHUB
# 8f0efa0729b0 oxygenhub / crates / hub / src / db / mod.rs
2.8 KiB · Rust · last changed Cancel runs from oh, the run page, and newer pushes 1 month ago source blame history raw
1
//! SQLite through `sqlx` (hub spec §3.1, §17.2): WAL, foreign keys on,
2
//! migrations embedded and applied at start. One module per table group.
3
4
pub mod accounts;
5
pub mod activity;
6
pub mod flow;
7
pub mod hooks;
8
pub mod issues;
9
pub mod notify;
10
pub mod projects;
11
pub mod proposals;
12
pub mod releases;
13
pub mod repos;
14
pub mod runners;
15
16
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous};
17
use sqlx::SqlitePool;
18
use std::path::Path;
19
20
pub type Pool = SqlitePool;
21
22
/// Open (creating if needed) and migrate the database at `path`.
23
/// How long a statement waits for SQLite's write lock. Most writers are
24
/// background clients (runners appending logs, webhook deliveries), and a
25
/// commit can stall for seconds while a build saturates the host's disk, so
26
/// waiting beats failing.
27
const BUSY_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
28
29
pub async fn open(path: &Path) -> anyhow::Result<Pool> {
30
    let options = SqliteConnectOptions::new()
31
        .filename(path)
32
        .create_if_missing(true)
33
        .journal_mode(SqliteJournalMode::Wal)
34
        .synchronous(SqliteSynchronous::Normal)
35
        .foreign_keys(true)
36
        .busy_timeout(BUSY_TIMEOUT);
37
    migrate(options.clone()).await?;
38
    let pool = SqlitePoolOptions::new().max_connections(8).connect_with(options).await?;
39
    Ok(pool)
40
}
41
42
/// Apply the migrations on a connection of their own with foreign keys
43
/// off, as SQLite's way to change a table (rebuild it, drop the old one,
44
/// rename) needs: with them on, dropping a table its children reference
45
/// deletes the children too. Every reference must hold afterwards.
46
async fn migrate(options: SqliteConnectOptions) -> anyhow::Result<()> {
47
    use sqlx::Connection;
48
    let mut conn = sqlx::SqliteConnection::connect_with(&options.foreign_keys(false)).await?;
49
    sqlx::migrate!("./migrations").run(&mut conn).await?;
50
    let broken: Vec<(String,)> = sqlx::query_as("SELECT \"table\" FROM pragma_foreign_key_check").fetch_all(&mut conn).await?;
51
    if let Some((table,)) = broken.first() {
52
        anyhow::bail!("after the migrations, {} rows break a foreign key (the first in {table})", broken.len());
53
    }
54
    conn.close().await?;
55
    Ok(())
56
}
57
58
/// Milliseconds since the epoch, the hub's timestamp unit (as `oxy`'s).
59
pub fn now_ms() -> i64 {
60
    std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_millis() as i64).unwrap_or(0)
61
}
62
63
/// A write transaction that takes SQLite's write lock at `BEGIN`
64
/// (`BEGIN IMMEDIATE`). A deferred transaction that reads and then writes
65
/// fails at once with "database is locked" when another writer holds the
66
/// lock, whatever the busy timeout; an immediate one waits its turn.
67
pub async fn begin_write(pool: &Pool) -> sqlx::Result<sqlx::Transaction<'static, sqlx::Sqlite>> {
68
    pool.begin_with("BEGIN IMMEDIATE").await
69
}