2021-01-31 02:38:35 +03:00
|
|
|
#![deny(clippy::all)]
|
|
|
|
|
2021-01-31 03:23:43 +03:00
|
|
|
mod config;
|
2021-02-05 01:37:25 +03:00
|
|
|
mod database;
|
2021-02-10 01:13:27 +03:00
|
|
|
mod error;
|
2021-02-08 23:42:13 +03:00
|
|
|
mod migration;
|
2021-01-31 02:54:23 +03:00
|
|
|
mod opts;
|
2021-02-05 01:37:25 +03:00
|
|
|
mod path;
|
2021-01-31 02:38:35 +03:00
|
|
|
|
2021-02-03 01:03:56 +03:00
|
|
|
use chrono::Local;
|
2021-01-31 03:23:43 +03:00
|
|
|
use config::Config;
|
2021-02-10 01:13:27 +03:00
|
|
|
use error::ErrorKind;
|
2021-02-03 01:03:56 +03:00
|
|
|
use opts::{AppOpt, ApplyCommandOpt, Command, MakeCommandOpt, StructOpt};
|
2021-02-05 01:37:25 +03:00
|
|
|
use path::PathBuilder;
|
2021-01-31 14:54:50 +03:00
|
|
|
use std::fs;
|
2021-01-31 02:38:35 +03:00
|
|
|
|
2021-02-10 01:13:27 +03:00
|
|
|
const EM_DASH: char = '—';
|
|
|
|
|
2021-01-31 03:23:43 +03:00
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
2021-01-31 02:38:35 +03:00
|
|
|
let opt = AppOpt::from_args();
|
2021-01-31 03:23:43 +03:00
|
|
|
|
2021-02-02 00:53:33 +03:00
|
|
|
match opt.command {
|
|
|
|
Command::Init => {
|
2021-02-08 07:28:35 +03:00
|
|
|
Config::initialize(opt.config)?;
|
2021-02-01 23:51:23 +03:00
|
|
|
}
|
2021-02-02 00:53:33 +03:00
|
|
|
Command::Apply(ApplyCommandOpt { file_name }) => {
|
|
|
|
let config = Config::read(opt.config)?;
|
2021-01-31 13:41:12 +03:00
|
|
|
|
2021-02-10 01:13:27 +03:00
|
|
|
let database_connection_string = &config.database_connection_string()?;
|
|
|
|
let mut client = database::connect(database_connection_string)?;
|
2021-01-31 13:41:12 +03:00
|
|
|
|
2021-02-02 23:50:42 +03:00
|
|
|
let file_path = PathBuilder::from(config.directory_path())
|
2021-01-31 14:54:50 +03:00
|
|
|
.append(file_name)
|
|
|
|
.default_extension("sql")
|
|
|
|
.build();
|
2021-01-31 13:41:12 +03:00
|
|
|
|
2021-01-31 14:54:50 +03:00
|
|
|
let content = fs::read_to_string(file_path)?;
|
2021-01-31 13:41:12 +03:00
|
|
|
|
2021-02-05 01:37:25 +03:00
|
|
|
match database::apply_sql(&mut client, &content) {
|
2021-01-31 13:41:12 +03:00
|
|
|
Ok(_) => {
|
|
|
|
println!("File was applied successfully")
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
println!("{}", err)
|
|
|
|
}
|
|
|
|
}
|
2021-01-31 02:38:35 +03:00
|
|
|
}
|
2021-02-03 01:03:56 +03:00
|
|
|
Command::Make(MakeCommandOpt { migration_name }) => {
|
|
|
|
let config = Config::read(opt.config)?;
|
|
|
|
|
|
|
|
let now = Local::now().format("%y%m%d%H%M%S");
|
|
|
|
|
|
|
|
let migration_name: String = migration_name
|
|
|
|
.to_lowercase()
|
|
|
|
.chars()
|
|
|
|
.map(|c| match c {
|
|
|
|
'0'..='9' | 'a'..='z' => c,
|
|
|
|
_ => '_',
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
2021-02-05 01:46:18 +03:00
|
|
|
let migration_dir_path = PathBuilder::from(config.migration_dir_path())
|
2021-02-03 01:03:56 +03:00
|
|
|
.append(format!("{}_{}", now, migration_name))
|
|
|
|
.build();
|
2021-02-03 10:18:02 +03:00
|
|
|
if !migration_dir_path.exists() {
|
2021-02-05 01:46:18 +03:00
|
|
|
fs::create_dir_all(&migration_dir_path)?;
|
2021-02-03 01:03:56 +03:00
|
|
|
}
|
|
|
|
|
2021-02-03 10:18:02 +03:00
|
|
|
let upgrade_migration_path = PathBuilder::from(&migration_dir_path)
|
|
|
|
.append("up.sql")
|
|
|
|
.build();
|
2021-02-03 01:03:56 +03:00
|
|
|
if !upgrade_migration_path.exists() {
|
|
|
|
fs::write(upgrade_migration_path, "-- Your SQL goes here\n\n")?;
|
|
|
|
}
|
|
|
|
|
2021-02-03 10:18:02 +03:00
|
|
|
let downgrade_migration_path = PathBuilder::from(&migration_dir_path)
|
|
|
|
.append("down.sql")
|
|
|
|
.build();
|
|
|
|
if !downgrade_migration_path.exists() {
|
2021-02-03 01:03:56 +03:00
|
|
|
fs::write(
|
2021-02-03 10:18:02 +03:00
|
|
|
downgrade_migration_path,
|
2021-02-03 01:03:56 +03:00
|
|
|
"-- This file should undo anything in `up.sql`\n\n",
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
}
|
2021-02-02 23:50:42 +03:00
|
|
|
Command::List => {
|
|
|
|
let config = Config::read(opt.config)?;
|
|
|
|
|
2021-02-10 01:13:27 +03:00
|
|
|
let applied_migrations = match config.database_connection_string() {
|
|
|
|
Ok(ref database_connection_string) => {
|
|
|
|
let mut client = database::connect(database_connection_string)?;
|
|
|
|
let applied_migrations = database::applied_migrations(&mut client)?;
|
|
|
|
|
|
|
|
println!("Applied migrations:");
|
|
|
|
if applied_migrations.is_empty() {
|
|
|
|
println!("{}", EM_DASH);
|
|
|
|
} else {
|
|
|
|
applied_migrations
|
|
|
|
.iter()
|
|
|
|
.rev()
|
|
|
|
.for_each(|name| println!("{}", name));
|
|
|
|
}
|
|
|
|
|
|
|
|
applied_migrations
|
|
|
|
}
|
|
|
|
Err(e) if *e.kind() == ErrorKind::MissedEnvVar(String::new()) => {
|
|
|
|
println!("{}", e.kind());
|
|
|
|
println!("No connection to database");
|
2021-02-06 01:22:00 +03:00
|
|
|
|
2021-02-10 01:13:27 +03:00
|
|
|
Vec::new()
|
|
|
|
}
|
|
|
|
Err(e) => panic!(e),
|
|
|
|
};
|
2021-02-06 01:22:00 +03:00
|
|
|
|
2021-02-06 01:39:12 +03:00
|
|
|
println!();
|
|
|
|
|
2021-02-08 07:01:31 +03:00
|
|
|
let pending_migrations = config
|
|
|
|
.migrations()?
|
2021-02-06 01:22:00 +03:00
|
|
|
.into_iter()
|
|
|
|
.filter(|m| !applied_migrations.contains(m.name()))
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
println!("Pending migrations:");
|
|
|
|
if pending_migrations.is_empty() {
|
2021-02-10 01:13:27 +03:00
|
|
|
println!("{}", EM_DASH);
|
2021-02-02 23:50:42 +03:00
|
|
|
} else {
|
2021-02-06 01:22:00 +03:00
|
|
|
pending_migrations.iter().for_each(|m| {
|
|
|
|
println!("{}", m.name());
|
2021-02-02 23:50:42 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2021-02-06 01:22:00 +03:00
|
|
|
Command::Upgrade => {
|
|
|
|
let config = Config::read(opt.config)?;
|
|
|
|
|
2021-02-10 01:13:27 +03:00
|
|
|
let database_connection_string = &config.database_connection_string()?;
|
|
|
|
let mut client = database::connect(database_connection_string)?;
|
2021-02-06 01:22:00 +03:00
|
|
|
|
|
|
|
let applied_migrations = database::applied_migrations(&mut client)?;
|
|
|
|
|
|
|
|
let migrations = config.migrations()?;
|
|
|
|
|
|
|
|
if migrations.is_empty()
|
|
|
|
|| migrations.last().map(|m| m.name()) == applied_migrations.first()
|
|
|
|
{
|
|
|
|
println!("Up to date");
|
|
|
|
} else {
|
|
|
|
for m in migrations
|
|
|
|
.iter()
|
|
|
|
.filter(|m| !applied_migrations.contains(m.name()))
|
|
|
|
{
|
2021-02-06 01:37:30 +03:00
|
|
|
println!("upgrade {}...", m.name());
|
2021-02-06 01:22:00 +03:00
|
|
|
m.upgrade(&mut client)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Command::Downgrade => {
|
2021-02-06 01:37:30 +03:00
|
|
|
let config = Config::read(opt.config)?;
|
|
|
|
|
2021-02-10 01:13:27 +03:00
|
|
|
let database_connection_string = &config.database_connection_string()?;
|
|
|
|
let mut client = database::connect(database_connection_string)?;
|
2021-02-06 01:37:30 +03:00
|
|
|
|
|
|
|
let applied_migrations = database::applied_migrations(&mut client)?;
|
|
|
|
let migrations = config.migrations()?;
|
|
|
|
|
|
|
|
if let Some(first_applied_migration) = applied_migrations.first() {
|
2021-02-08 07:01:31 +03:00
|
|
|
if let Some(migration) = migrations
|
|
|
|
.iter()
|
|
|
|
.find(|m| m.name() == first_applied_migration)
|
|
|
|
{
|
2021-02-06 01:37:30 +03:00
|
|
|
println!("downgrade {}...", migration.name());
|
|
|
|
migration.downgrade(&mut client)?;
|
|
|
|
}
|
|
|
|
}
|
2021-02-02 00:53:33 +03:00
|
|
|
}
|
2021-01-31 02:38:35 +03:00
|
|
|
}
|
2021-01-31 03:23:43 +03:00
|
|
|
|
|
|
|
Ok(())
|
2021-01-31 02:38:35 +03:00
|
|
|
}
|