feat(pg-migration): migration tree validation
This commit is contained in:
@@ -81,40 +81,60 @@ fn check_psql_installed() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn apply_migrations(client: &mut Client, migration_dir: &str, db_url: &str, _force: bool) {
|
fn apply_migrations(client: &mut Client, migration_dir: &str, db_url: &str, _force: bool) {
|
||||||
let mut entries: Vec<_> = fs::read_dir(migration_dir)
|
// Get the list of new migrations from disk
|
||||||
|
let mut fs_entries: Vec<_> = fs::read_dir(migration_dir)
|
||||||
.expect("Reading migration directory failed")
|
.expect("Reading migration directory failed")
|
||||||
.filter_map(|e| e.ok())
|
.filter_map(|e| e.ok())
|
||||||
.filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("sql"))
|
.filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("sql"))
|
||||||
.collect();
|
.collect();
|
||||||
entries.sort_by_key(|e| e.path());
|
fs_entries.sort_by_key(|e| e.path());
|
||||||
|
let fs_migrations: Vec<String> = fs_entries
|
||||||
|
.iter()
|
||||||
|
.map(|e| e.path().file_name().unwrap().to_string_lossy().into_owned())
|
||||||
|
.collect();
|
||||||
|
|
||||||
// (Migration tree validation omitted)
|
// Get the list of already applied migrations from DB
|
||||||
|
let rows = client
|
||||||
|
.query("SELECT name FROM hectic.migration ORDER BY name ASC", &[])
|
||||||
|
.expect("Query failed");
|
||||||
|
let db_migrations: Vec<String> = rows.iter().map(|row| row.get(0)).collect();
|
||||||
|
|
||||||
for entry in entries {
|
// Check if the DB migrations form a proper prefix of disk migrations
|
||||||
let file_path = entry.path();
|
// (meaning all DB-applied migration filenames should appear in the same order at the start).
|
||||||
let file_name = file_path.file_name().unwrap().to_string_lossy();
|
for (i, db_mig) in db_migrations.iter().enumerate() {
|
||||||
|
if i >= fs_migrations.len() || fs_migrations[i] != *db_mig {
|
||||||
|
// The DB has migrations that are not found in the same position on disk -> unrelated tree
|
||||||
|
if !force {
|
||||||
|
eprintln!("Unrelated migration tree detected. Use --force to proceed.");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
eprintln!("Unrelated migration tree forced. Proceeding...");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if client
|
for fs_mig in fs_migrations {
|
||||||
.query_opt("SELECT 1 FROM hectic.migration WHERE name = $1", &[&file_name])
|
// Skip if already applied
|
||||||
.expect("Query failed")
|
if db_migrations.contains(&fs_mig) {
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let status = ProcessCommand::new("psql")
|
let status = std::process::Command::new("psql")
|
||||||
.arg("-d")
|
.arg("-d")
|
||||||
.arg(db_url)
|
.arg(db_url)
|
||||||
.arg("-f")
|
.arg("-f")
|
||||||
.arg(file_path.to_str().unwrap())
|
.arg(Path::new(migration_dir).join(&fs_mig).to_str().unwrap())
|
||||||
.status()
|
.status()
|
||||||
.expect("psql execution failed");
|
.expect("psql execution failed");
|
||||||
|
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
eprintln!("Migration failed: {}", file_name);
|
eprintln!("Migration failed: {}", fs_mig);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
client
|
client
|
||||||
.execute("INSERT INTO hectic.migration (name) VALUES ($1)", &[&file_name])
|
.execute("INSERT INTO hectic.migration (name) VALUES ($1)", &[&fs_mig])
|
||||||
.expect("Recording migration failed");
|
.expect("Recording migration failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,4 +161,3 @@ fn generate_migration_name() -> String {
|
|||||||
let noun = nouns[rng.random_range(0..nouns.len())];
|
let noun = nouns[rng.random_range(0..nouns.len())];
|
||||||
format!("{}_{}", adj, noun)
|
format!("{}_{}", adj, noun)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user