lr2-oxytabler 0.10.1

Table manager for Lunatic Rave 2
Documentation
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
        );
    }
}