simple-web-app

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

migrations.rs (3045B)


      1 use rusqlite::Connection;
      2 
      3 const MIGRATIONS: &[&str] = &[
      4     include_str!("../migrations/0001_initial_schema.sql"),
      5     include_str!("../migrations/0002_email_verification.sql"),
      6     include_str!("../migrations/0003_strict_tables.sql"),
      7     include_str!("../migrations/0004_sessions.sql"),
      8     include_str!("../migrations/0005_epoch_timestamps.sql"),
      9     include_str!("../migrations/0006_fts_vocab.sql"),
     10     include_str!("../migrations/0007_structured_pks.sql"),
     11 ];
     12 
     13 pub fn run(conn: &Connection) -> Result<(), String> {
     14     conn.execute_batch(
     15         "CREATE TABLE IF NOT EXISTS migration_version (version INTEGER NOT NULL);",
     16     )
     17     .map_err(|e| format!("Failed to create migration_version table: {e}"))?;
     18 
     19     // Seed version row if missing
     20     let count: i64 = conn
     21         .query_row("SELECT COUNT(*) FROM migration_version", [], |row| row.get(0))
     22         .map_err(|e| format!("Failed to read migration_version: {e}"))?;
     23     if count == 0 {
     24         conn.execute("INSERT INTO migration_version (version) VALUES (0)", [])
     25             .map_err(|e| format!("Failed to seed migration_version: {e}"))?;
     26     }
     27 
     28     let current_version: i64 = conn
     29         .query_row("SELECT version FROM migration_version", [], |row| row.get(0))
     30         .map_err(|e| format!("Failed to read version: {e}"))?;
     31 
     32     let total = MIGRATIONS.len() as i64;
     33     if current_version >= total {
     34         return Ok(());
     35     }
     36 
     37     eprintln!(
     38         "Running migrations {} through {} (current version: {})",
     39         current_version,
     40         total - 1,
     41         current_version
     42     );
     43 
     44     for i in current_version..total {
     45         eprintln!("  Applying migration {}...", i);
     46         conn.execute_batch(MIGRATIONS[i as usize])
     47             .map_err(|e| format!("Migration {} failed: {}", i, e))?;
     48     }
     49 
     50     eprintln!("Migrations complete. Database is now at version {}", total);
     51     Ok(())
     52 }
     53 
     54 #[cfg(test)]
     55 mod tests {
     56     use super::*;
     57 
     58     #[test]
     59     fn each_migration_bumps_version() {
     60         let conn = Connection::open_in_memory().unwrap();
     61         run(&conn).unwrap();
     62 
     63         let version: i64 = conn
     64             .query_row("SELECT version FROM migration_version", [], |row| row.get(0))
     65             .unwrap();
     66 
     67         assert_eq!(version, MIGRATIONS.len() as i64);
     68     }
     69 
     70     #[test]
     71     fn migrations_are_idempotent() {
     72         let conn = Connection::open_in_memory().unwrap();
     73         run(&conn).unwrap();
     74         run(&conn).unwrap();
     75 
     76         let version: i64 = conn
     77             .query_row("SELECT version FROM migration_version", [], |row| row.get(0))
     78             .unwrap();
     79 
     80         assert_eq!(version, MIGRATIONS.len() as i64);
     81     }
     82 
     83     #[test]
     84     fn foreign_keys_on_after_migrations() {
     85         let conn = Connection::open_in_memory().unwrap();
     86         conn.execute_batch("PRAGMA foreign_keys=ON;").unwrap();
     87 
     88         run(&conn).unwrap();
     89 
     90         let fk_enabled: i64 = conn
     91             .query_row("PRAGMA foreign_keys", [], |row| row.get(0))
     92             .unwrap();
     93 
     94         assert_eq!(fk_enabled, 1, "foreign_keys must be ON after migrations");
     95     }
     96 }