List migrations

This commit is contained in:
Jesper Axelsson 2020-04-18 14:47:15 +02:00 committed by Ryan Leckey
parent 70654ff2db
commit f491198891
5 changed files with 114 additions and 4 deletions

View file

@ -24,6 +24,8 @@ chrono = "0.4"
anyhow = "1.0"
url = { version = "2.1.1", default-features = false }
async-trait = "0.1.30"
console = "0.10.0"
dialoguer = "0.5.0"
[features]
default = [ "postgres", "sqlite", "mysql" ]

View file

@ -28,5 +28,6 @@ pub trait DatabaseMigrator {
// Migration
async fn create_migration_table(&self) -> Result<()>;
async fn get_migrations(&self) -> Result<Vec<String>>;
async fn begin_migration(&self) -> Result<Box<dyn MigrationTransaction>>;
}

View file

@ -9,6 +9,7 @@ use dotenv::dotenv;
use structopt::StructOpt;
use anyhow::{anyhow, Context, Result};
use console::style;
mod database_migrator;
mod postgres;
@ -40,6 +41,9 @@ enum MigrationCommand {
/// Run all migrations
Run,
/// List all migrations
List,
}
/// Create or drops database depending on your connection string. Alias: db
@ -97,6 +101,7 @@ async fn run_command(migrator: &dyn DatabaseMigrator) -> Result<()> {
Opt::Migrate(command) => match command {
MigrationCommand::Add { name } => add_migration_file(&name)?,
MigrationCommand::Run => run_migrations(migrator).await?,
MigrationCommand::List => list_migrations(migrator).await?,
},
Opt::Database(command) => match command {
DatabaseCommand::Create => run_create_database(migrator).await?,
@ -241,7 +246,7 @@ fn load_migrations() -> Result<Vec<Migration>> {
async fn run_migrations(migrator: &dyn DatabaseMigrator) -> Result<()> {
if !migrator.can_migrate_database() {
return Err(anyhow!(
"Database migrations not implemented for {}",
"Database migrations not supported for {}",
migrator.database_type()
));
}
@ -272,3 +277,76 @@ async fn run_migrations(migrator: &dyn DatabaseMigrator) -> Result<()> {
Ok(())
}
async fn list_migrations(migrator: &dyn DatabaseMigrator) -> Result<()> {
if !migrator.can_migrate_database() {
return Err(anyhow!(
"Database migrations not supported for {}",
migrator.database_type()
));
}
let file_migrations = load_migrations()?;
if migrator
.check_if_database_exists(&migrator.get_database_name()?)
.await?
{
let applied_migrations = migrator.get_migrations().await.unwrap_or_else(|_| {
println!("Could not retrive data from migration table");
Vec::new()
});
let mut width = 0;
for mig in file_migrations.iter() {
width = std::cmp::max(width, mig.name.len());
}
for mig in file_migrations.iter() {
let status = if applied_migrations
.iter()
.find(|&m| mig.name == *m)
.is_some()
{
style("Applied").green()
} else {
style("Not Applied").yellow()
};
println!("{:width$}\t{}", mig.name, status, width = width);
}
let orphans = check_for_orphans(file_migrations, applied_migrations);
if let Some(orphans) = orphans {
println!("\nFound migrations applied in the database that does not have a corresponding migration file:");
for name in orphans {
println!("{:width$}\t{}", name, style("Orphan").red(), width = width);
}
}
} else {
println!("No database found, listing migrations");
for mig in file_migrations {
println!("{}", mig.name);
}
}
Ok(())
}
fn check_for_orphans(
file_migrations: Vec<Migration>,
applied_migrations: Vec<String>,
) -> Option<Vec<String>> {
let orphans: Vec<String> = applied_migrations
.iter()
.filter(|m| !file_migrations.iter().any(|fm| fm.name == **m))
.cloned()
.collect();
if orphans.len() > 0 {
Some(orphans)
} else {
None
}
}

View file

@ -130,6 +130,22 @@ impl DatabaseMigrator for Postgres {
Ok(())
}
async fn get_migrations(&self) -> Result<Vec<String>> {
let mut conn = PgConnection::connect(&self.db_url).await?;
let result = sqlx::query(
r#"
select migration from __migrations order by created
"#,
)
.try_map(|row: PgRow| row.try_get(0))
.fetch_all(&mut conn)
.await
.context("Failed to create migration table")?;
Ok(result)
}
async fn begin_migration(&self) -> Result<Box<dyn MigrationTransaction>> {
let pool = PgPool::new(&self.db_url)
.await

View file

@ -69,8 +69,6 @@ impl DatabaseMigrator for Sqlite {
}
async fn create_database(&self, _db_name: &str) -> Result<()> {
use std::fs::OpenOptions;
println!("DB {}", self.path);
// Opening a connection to sqlite creates the database.
@ -102,7 +100,22 @@ impl DatabaseMigrator for Sqlite {
Ok(())
}
//
async fn get_migrations(&self) -> Result<Vec<String>> {
let mut conn = SqliteConnection::connect(&self.db_url).await?;
let result = sqlx::query(
r#"
select migration from __migrations order by created
"#,
)
.try_map(|row: SqliteRow| row.try_get(0))
.fetch_all(&mut conn)
.await
.context("Failed to create migration table")?;
Ok(result)
}
async fn begin_migration(&self) -> Result<Box<dyn MigrationTransaction>> {
// let pool = SqlitePool::new(&self.db_url)
// .await