use anyhow::Result;
pub struct Migration<'a> {
pub id: u32,
pub description: &'a str,
pub sql: &'a str,
}
pub fn maybe_apply_migration(tx: &rusqlite::Connection, migration: &Migration) -> Result<()> {
tx.execute(
"CREATE TABLE IF NOT EXISTS lr2oxytabler_migrations (
id BIGINT PRIMARY KEY NOT NULL,
description TEXT NOT NULL,
timestamp BIGINT NOT NULL,
UNIQUE (id)
);",
[],
)?;
if tx
.prepare_cached("SELECT 1 FROM lr2oxytabler_migrations WHERE id = ?")?
.query(rusqlite::params![migration.id])?
.next()?
.is_some()
{
log::debug!("Migration {} has already been applied", migration.id);
return Ok(());
}
tx.execute_batch(migration.sql)?;
tx.prepare_cached(
"INSERT INTO
lr2oxytabler_migrations (id, description, timestamp)
VALUES
(?, ?, unixepoch ())",
)?
.execute(rusqlite::params![migration.id, migration.description])?;
Ok(())
}
#[cfg(test)]
mod tests {
use test_log::test;
#[test]
fn migrations_work() {
use super::{Migration, maybe_apply_migration};
let migration = Migration {
#[expect(clippy::unreadable_literal, reason = "date")]
id: 20250517,
description: "test",
sql: "CREATE TABLE test(id BIGINT); INSERT INTO test VALUES(1); INSERT INTO test VALUES(2);",
};
let mut db = rusqlite::Connection::open_in_memory().unwrap();
let tx = db.transaction().unwrap();
maybe_apply_migration(&tx, &migration).unwrap();
maybe_apply_migration(&tx, &migration).unwrap();
assert_eq!(
tx.query_row("SELECT count(1) FROM test;", [], |row| {
let v: u32 = row.get_unwrap(0);
Ok(v)
})
.unwrap(),
2u32
);
}
}