2026-03-25 08:45:22 +08:00
|
|
|
//! Database migrations module.
|
|
|
|
|
//!
|
|
|
|
|
//! SQL migrations are embedded at compile time and applied in order.
|
|
|
|
|
//! Each migration is applied atomically within a transaction.
|
|
|
|
|
|
2026-04-03 08:25:26 +08:00
|
|
|
use rusqlite::Connection;
|
|
|
|
|
use storage::StorageError;
|
|
|
|
|
|
|
|
|
|
use crate::errors::map_rusqlite_error;
|
2026-03-25 08:45:22 +08:00
|
|
|
|
|
|
|
|
/// Embeds and returns all migration SQL files in order.
|
|
|
|
|
pub fn get_migrations() -> Vec<(&'static str, &'static str)> {
|
2026-04-03 08:25:26 +08:00
|
|
|
vec![
|
|
|
|
|
(
|
|
|
|
|
"001_initial_schema",
|
|
|
|
|
include_str!("migrations/001_initial_schema.sql"),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"002_ratchet_state",
|
|
|
|
|
include_str!("migrations/002_ratchet_state.sql"),
|
|
|
|
|
),
|
|
|
|
|
]
|
2026-03-25 08:45:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Applies all migrations to the database.
|
|
|
|
|
///
|
|
|
|
|
/// Uses a simple version tracking table to avoid re-running migrations.
|
|
|
|
|
pub fn apply_migrations(conn: &mut Connection) -> Result<(), StorageError> {
|
|
|
|
|
// Create migrations tracking table if it doesn't exist
|
|
|
|
|
conn.execute_batch(
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS _migrations (
|
|
|
|
|
name TEXT PRIMARY KEY,
|
|
|
|
|
applied_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
|
|
|
|
);",
|
2026-04-03 08:25:26 +08:00
|
|
|
)
|
|
|
|
|
.map_err(map_rusqlite_error)?;
|
2026-03-25 08:45:22 +08:00
|
|
|
|
|
|
|
|
for (name, sql) in get_migrations() {
|
|
|
|
|
// Check if migration already applied
|
2026-04-03 08:25:26 +08:00
|
|
|
let already_applied: bool = conn
|
|
|
|
|
.query_row(
|
|
|
|
|
"SELECT EXISTS(SELECT 1 FROM _migrations WHERE name = ?1)",
|
|
|
|
|
[name],
|
|
|
|
|
|row| row.get(0),
|
|
|
|
|
)
|
|
|
|
|
.map_err(map_rusqlite_error)?;
|
2026-03-25 08:45:22 +08:00
|
|
|
|
|
|
|
|
if !already_applied {
|
|
|
|
|
// Apply migration and record it atomically in a transaction
|
2026-04-03 08:25:26 +08:00
|
|
|
let tx = conn.transaction().map_err(map_rusqlite_error)?;
|
|
|
|
|
tx.execute_batch(sql).map_err(map_rusqlite_error)?;
|
|
|
|
|
tx.execute("INSERT INTO _migrations (name) VALUES (?1)", [name])
|
|
|
|
|
.map_err(map_rusqlite_error)?;
|
|
|
|
|
tx.commit().map_err(map_rusqlite_error)?;
|
2026-03-25 08:45:22 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|