tga_core/db/
migrations.rs1use rusqlite::Connection;
13use tracing::{debug, info};
14
15use crate::errors::{Result, TgaError};
16
17pub struct Migration {
19 pub version: i64,
21 pub name: &'static str,
23 pub sql: &'static str,
25}
26
27pub const MIGRATIONS: &[Migration] = &[Migration {
29 version: 1,
30 name: "initial_schema",
31 sql: include_str!("sql/0001_initial_schema.sql"),
32}];
33
34fn ensure_migrations_table(conn: &Connection) -> Result<()> {
36 conn.execute_batch(
37 "CREATE TABLE IF NOT EXISTS schema_migrations ( \
38 version INTEGER PRIMARY KEY, \
39 name TEXT NOT NULL, \
40 applied_at TEXT NOT NULL \
41 );",
42 )?;
43 Ok(())
44}
45
46fn current_version(conn: &Connection) -> Result<i64> {
48 let v: Option<i64> = conn
49 .query_row(
50 "SELECT COALESCE(MAX(version), 0) FROM schema_migrations",
51 [],
52 |row| row.get(0),
53 )
54 .map_err(TgaError::from)?;
55 Ok(v.unwrap_or(0))
56}
57
58pub fn run(conn: &mut Connection) -> Result<()> {
67 ensure_migrations_table(conn)?;
68 let current = current_version(conn)?;
69 debug!(current_version = current, "running migrations");
70
71 for m in MIGRATIONS {
72 if m.version <= current {
73 continue;
74 }
75 info!(version = m.version, name = m.name, "applying migration");
76 let tx = conn.transaction().map_err(TgaError::from)?;
77 tx.execute_batch(m.sql).map_err(|e| {
78 TgaError::MigrationError(format!("migration {} ({}) failed: {e}", m.version, m.name))
79 })?;
80 tx.execute(
81 "INSERT INTO schema_migrations(version, name, applied_at) VALUES (?1, ?2, ?3)",
82 rusqlite::params![m.version, m.name, chrono::Utc::now().to_rfc3339()],
83 )
84 .map_err(TgaError::from)?;
85 tx.commit().map_err(TgaError::from)?;
86 }
87 Ok(())
88}