1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//! Database connection management and migration runner.
//!
//! [`open`] creates a connection with WAL mode and foreign keys enabled.
//! [`migrate`] runs pending migrations — call once at server startup.

use std::path::Path;

use rusqlite::Connection;
use rusqlite_migration::{M, Migrations};

/// The ordered set of schema migrations. Append-only — never edit
/// a migration that has already shipped.
static MIGRATIONS: std::sync::LazyLock<Migrations<'static>> = std::sync::LazyLock::new(|| {
    Migrations::new(vec![
        M::up(include_str!("../migrations/0001_initial.sql")),
        M::up(include_str!("../migrations/0002_sh_events.sql")),
        M::up(include_str!("../migrations/0003_ci_api.sql")),
        M::up(include_str!("../migrations/0004_bootstrap_api.sql")),
        M::up(include_str!("../migrations/0005_rename_run_token.sql")),
        M::up(include_str!("../migrations/0006_traceparent.sql")),
        M::up(include_str!("../migrations/0007_schema_cleanup.sql")),
        M::up(include_str!("../migrations/0008_rename_sh.sql")),
        M::up(include_str!("../migrations/0009_rename_ci_vocab.sql")),
        M::up(include_str!("../migrations/0010_outcome_schema.sql")),
    ])
});

/// Error from running migrations.
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
pub enum MigrationError {
    #[error(transparent)]
    Sqlite(#[from] rusqlite::Error),
    #[error("migration error: {0}")]
    Migration(#[from] rusqlite_migration::Error),
}

/// Open the database at `path`, enable WAL mode and foreign keys.
/// Creates the file if it doesn't exist.
///
/// Does not run migrations. Call [`migrate`] once at server startup.
pub fn open(path: &Path) -> Result<Connection, rusqlite::Error> {
    let conn = Connection::open(path)?;
    conn.execute_batch(
        "PRAGMA journal_mode = WAL;
         PRAGMA foreign_keys = ON;
         PRAGMA busy_timeout = 5000;",
    )?;
    Ok(conn)
}

/// Run any pending migrations on the given connection.
///
/// Call once at server startup, after [`open`].
pub fn migrate(conn: &mut Connection) -> Result<(), MigrationError> {
    MIGRATIONS.to_latest(conn)?;
    Ok(())
}

/// Open an in-memory database with migrations applied (for tests).
#[cfg(test)]
pub fn open_in_memory() -> Result<Connection, MigrationError> {
    let mut conn = Connection::open_in_memory()?;
    conn.execute_batch("PRAGMA foreign_keys = ON;")?;
    MIGRATIONS.to_latest(&mut conn)?;
    Ok(conn)
}