use clap::{Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(name = "migratio-runner")]
#[command(about = "Database migration runner")]
pub struct CliArgs {
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
Status,
Upgrade {
#[arg(long)]
to: Option<u32>,
},
Downgrade {
#[arg(long)]
to: u32,
},
History,
Preview,
List,
}
#[cfg(feature = "sqlite")]
pub use sqlite::run_sqlite;
#[cfg(feature = "sqlite")]
mod sqlite {
use super::{CliArgs, Commands};
use migratio::sqlite::SqliteMigrator;
use rusqlite::Connection;
pub fn run_sqlite(
migrator: SqliteMigrator,
database_url: &str,
args: CliArgs,
) -> Result<(), Box<dyn std::error::Error>> {
if !std::path::Path::new(database_url).exists() {
return Err(format!(
"Database file not found: {}\n\nTo create a new database, first create the file manually or use your application's initialization logic.",
database_url
).into());
}
let mut conn = Connection::open(database_url)?;
match args.command {
Commands::Status => {
let version = migrator.get_current_version(&mut conn)?;
let pending = migrator.preview_upgrade(&mut conn)?;
println!("Current version: {}", version);
println!("Pending migrations: {}", pending.len());
for m in pending {
println!(" - {} (v{})", m.name(), m.version());
}
}
Commands::Upgrade { to } => {
let report = match to {
Some(target) => migrator.upgrade_to(&mut conn, target)?,
None => migrator.upgrade(&mut conn)?,
};
if report.migrations_run.is_empty() {
println!("No migrations to run.");
} else {
println!("Migrations run: {:?}", report.migrations_run);
}
}
Commands::Downgrade { to } => {
let report = migrator.downgrade(&mut conn, to)?;
if report.migrations_run.is_empty() {
println!("No migrations to roll back.");
} else {
println!("Migrations rolled back: {:?}", report.migrations_run);
}
}
Commands::History => {
let history = migrator.get_migration_history(&mut conn)?;
if history.is_empty() {
println!("No migrations have been applied yet.");
} else {
println!("Migration history:");
for entry in history {
println!(
" v{}: {} [{}] (applied {})",
entry.version, entry.name, entry.migration_type, entry.applied_at
);
}
}
}
Commands::Preview => {
let pending = migrator.preview_upgrade(&mut conn)?;
if pending.is_empty() {
println!("No pending migrations.");
} else {
println!("Pending migrations:");
for m in pending {
println!(" - {} (v{})", m.name(), m.version());
if let Some(desc) = m.description() {
println!(" {}", desc);
}
}
}
}
Commands::List => {
unreachable!("List command should be handled before run_sqlite");
}
}
Ok(())
}
}
#[cfg(feature = "mysql")]
pub use mysql_support::run_mysql;
#[cfg(feature = "postgres")]
pub use postgres_support::run_postgres;
#[cfg(feature = "mysql")]
mod mysql_support {
use super::{CliArgs, Commands};
use migratio::mysql::MysqlMigrator;
use mysql::{Conn, Opts};
pub fn run_mysql(
migrator: MysqlMigrator,
database_url: &str,
args: CliArgs,
) -> Result<(), Box<dyn std::error::Error>> {
let opts = Opts::from_url(database_url)?;
let mut conn = Conn::new(opts)?;
match args.command {
Commands::Status => {
let version = migrator.get_current_version(&mut conn)?;
let pending = migrator.preview_upgrade(&mut conn)?;
println!("Current version: {}", version);
println!("Pending migrations: {}", pending.len());
for m in pending {
println!(" - {} (v{})", m.name(), m.version());
}
}
Commands::Upgrade { to } => {
let report = match to {
Some(target) => migrator.upgrade_to(&mut conn, target)?,
None => migrator.upgrade(&mut conn)?,
};
if report.migrations_run.is_empty() {
println!("No migrations to run.");
} else {
println!("Migrations run: {:?}", report.migrations_run);
}
}
Commands::Downgrade { to } => {
let report = migrator.downgrade(&mut conn, to)?;
if report.migrations_run.is_empty() {
println!("No migrations to roll back.");
} else {
println!("Migrations rolled back: {:?}", report.migrations_run);
}
}
Commands::History => {
let history = migrator.get_migration_history(&mut conn)?;
if history.is_empty() {
println!("No migrations have been applied yet.");
} else {
println!("Migration history:");
for entry in history {
println!(
" v{}: {} [{}] (applied {})",
entry.version, entry.name, entry.migration_type, entry.applied_at
);
}
}
}
Commands::Preview => {
let pending = migrator.preview_upgrade(&mut conn)?;
if pending.is_empty() {
println!("No pending migrations.");
} else {
println!("Pending migrations:");
for m in pending {
println!(" - {} (v{})", m.name(), m.version());
if let Some(desc) = m.description() {
println!(" {}", desc);
}
}
}
}
Commands::List => {
unreachable!("List command should be handled before run_mysql");
}
}
Ok(())
}
}
#[cfg(feature = "postgres")]
mod postgres_support {
use super::{CliArgs, Commands};
use migratio::postgres::PostgresMigrator;
use postgres::{Client, NoTls};
pub fn run_postgres(
migrator: PostgresMigrator,
database_url: &str,
args: CliArgs,
) -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::connect(database_url, NoTls)?;
match args.command {
Commands::Status => {
let version = migrator.get_current_version(&mut client)?;
let pending = migrator.preview_upgrade(&mut client)?;
println!("Current version: {}", version);
println!("Pending migrations: {}", pending.len());
for m in pending {
println!(" - {} (v{})", m.name(), m.version());
}
}
Commands::Upgrade { to } => {
let report = match to {
Some(target) => migrator.upgrade_to(&mut client, target)?,
None => migrator.upgrade(&mut client)?,
};
if report.migrations_run.is_empty() {
println!("No migrations to run.");
} else {
println!("Migrations run: {:?}", report.migrations_run);
}
}
Commands::Downgrade { to } => {
let report = migrator.downgrade(&mut client, to)?;
if report.migrations_run.is_empty() {
println!("No migrations to roll back.");
} else {
println!("Migrations rolled back: {:?}", report.migrations_run);
}
}
Commands::History => {
let history = migrator.get_migration_history(&mut client)?;
if history.is_empty() {
println!("No migrations have been applied yet.");
} else {
println!("Migration history:");
for entry in history {
println!(
" v{}: {} [{}] (applied {})",
entry.version, entry.name, entry.migration_type, entry.applied_at
);
}
}
}
Commands::Preview => {
let pending = migrator.preview_upgrade(&mut client)?;
if pending.is_empty() {
println!("No pending migrations.");
} else {
println!("Pending migrations:");
for m in pending {
println!(" - {} (v{})", m.name(), m.version());
if let Some(desc) = m.description() {
println!(" {}", desc);
}
}
}
}
Commands::List => {
unreachable!("List command should be handled before run_postgres");
}
}
Ok(())
}
}