| 1 | //! The events database as the migrations make it, for tests: SQLite (which |
| 2 | //! D1 is), each migration in a transaction of its own as D1 applies it. |
| 3 | |
| 4 | use rusqlite::Connection; |
| 5 | |
| 6 | /// The migrations, in order: each file's name and SQL. |
| 7 | pub(crate) fn migrations() -> Vec<(String, String)> { |
| 8 | let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("migrations"); |
| 9 | let mut names: Vec<String> = std::fs::read_dir(&dir) |
| 10 | .unwrap() |
| 11 | .map(|entry| entry.unwrap().file_name().into_string().unwrap()) |
| 12 | .filter(|name| name.ends_with(".sql")) |
| 13 | .collect(); |
| 14 | names.sort(); |
| 15 | names.into_iter().map(|name| (name.clone(), std::fs::read_to_string(dir.join(&name)).unwrap())).collect() |
| 16 | } |
| 17 | |
| 18 | /// Applies one migration as D1 does: in one transaction. |
| 19 | pub(crate) fn apply(db: &Connection, name: &str, sql: &str) { |
| 20 | db.execute_batch(&format!("BEGIN;\n{sql}\nCOMMIT;")).unwrap_or_else(|error| panic!("{name}: {error}")); |
| 21 | } |
| 22 | |
| 23 | /// An in-memory database with the migrations before `name` applied, for |
| 24 | /// filling with what that migration then moves. |
| 25 | pub(crate) fn database_before(name: &str) -> Connection { |
| 26 | let db = Connection::open_in_memory().unwrap(); |
| 27 | db.execute_batch("PRAGMA foreign_keys = ON;").unwrap(); |
| 28 | for (file, sql) in migrations() { |
| 29 | if file.as_str() >= name { |
| 30 | break; |
| 31 | } |
| 32 | apply(&db, &file, &sql); |
| 33 | } |
| 34 | db |
| 35 | } |
| 36 | |
| 37 | /// Applies the migrations from `name` on. |
| 38 | pub(crate) fn migrate_from(db: &Connection, name: &str) { |
| 39 | for (file, sql) in migrations() { |
| 40 | if file.as_str() >= name { |
| 41 | apply(db, &file, &sql); |
| 42 | } |
| 43 | } |
| 44 | } |