diff --git a/migra-cli/src/commands/apply.rs b/migra-cli/src/commands/apply.rs index 1b22da6..0a78448 100644 --- a/migra-cli/src/commands/apply.rs +++ b/migra-cli/src/commands/apply.rs @@ -10,7 +10,7 @@ pub(crate) fn apply_sql(app: &App, cmd_opts: ApplyCommandOpt) -> StdResult<()> { let mut connection_manager = DatabaseConnectionManager::connect(&config.database)?; let conn = connection_manager.connection(); - let migration_manager = MigrationManager::new(); + let migration_manager = MigrationManager::from(&config); let file_contents = cmd_opts .file_paths diff --git a/migra-cli/src/commands/downgrade.rs b/migra-cli/src/commands/downgrade.rs index 63ee043..4b9c697 100644 --- a/migra-cli/src/commands/downgrade.rs +++ b/migra-cli/src/commands/downgrade.rs @@ -10,7 +10,7 @@ pub(crate) fn rollback_applied_migrations(app: &App, opts: DowngradeCommandOpt) let config = app.config()?; let mut connection_manager = DatabaseConnectionManager::connect(&config.database)?; let conn = connection_manager.connection(); - let migration_manager = MigrationManager::new(); + let migration_manager = MigrationManager::from(&config); let applied_migrations = migration_manager.applied_migration_names(conn)?; let migrations = config.migrations()?; diff --git a/migra-cli/src/commands/list.rs b/migra-cli/src/commands/list.rs index 0b45101..880feb6 100644 --- a/migra-cli/src/commands/list.rs +++ b/migra-cli/src/commands/list.rs @@ -16,7 +16,7 @@ pub(crate) fn print_migration_lists(app: &App) -> StdResult<()> { )?; let conn = connection_manager.connection(); - let migration_manager = MigrationManager::new(); + let migration_manager = MigrationManager::from(&config); let applied_migration_names = migration_manager.applied_migration_names(conn)?; show_applied_migrations(&applied_migration_names); diff --git a/migra-cli/src/commands/make.rs b/migra-cli/src/commands/make.rs index ec3eed6..96f3ca6 100644 --- a/migra-cli/src/commands/make.rs +++ b/migra-cli/src/commands/make.rs @@ -6,7 +6,8 @@ use std::fs; pub(crate) fn make_migration(app: &App, opts: MakeCommandOpt) -> StdResult<()> { let config = app.config()?; - let now = Local::now().format("%y%m%d%H%M%S"); + let date_format = config.migrations.date_format(); + let formatted_current_timestamp = Local::now().format(&date_format); let migration_name: String = opts .migration_name @@ -18,9 +19,10 @@ pub(crate) fn make_migration(app: &App, opts: MakeCommandOpt) -> StdResult<()> { }) .collect(); - let migration_dir_path = config - .migration_dir_path() - .join(format!("{}_{}", now, migration_name)); + let migration_dir_path = config.migration_dir_path().join(format!( + "{}_{}", + formatted_current_timestamp, migration_name + )); if !migration_dir_path.exists() { fs::create_dir_all(&migration_dir_path)?; } diff --git a/migra-cli/src/commands/upgrade.rs b/migra-cli/src/commands/upgrade.rs index abbf088..b107c9d 100644 --- a/migra-cli/src/commands/upgrade.rs +++ b/migra-cli/src/commands/upgrade.rs @@ -10,7 +10,7 @@ pub(crate) fn upgrade_pending_migrations(app: &App, opts: UpgradeCommandOpt) -> let mut connection_manager = DatabaseConnectionManager::connect(&config.database)?; let conn = connection_manager.connection(); - let migration_manager = MigrationManager::new(); + let migration_manager = MigrationManager::from(&config); let applied_migration_names = migration_manager.applied_migration_names(conn)?; let migrations = config.migrations()?; diff --git a/migra-cli/src/config.rs b/migra-cli/src/config.rs index c5e534d..6be608d 100644 --- a/migra-cli/src/config.rs +++ b/migra-cli/src/config.rs @@ -4,11 +4,25 @@ use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; use std::{env, fs, io}; -pub(crate) const MIGRA_TOML_FILENAME: &str = "Migra.toml"; -pub(crate) const DEFAULT_DATABASE_CONNECTION_ENV: &str = "$DATABASE_URL"; +//===========================================================================// +// Internal Config Utils / Macros // +//===========================================================================// -fn default_database_connection_env() -> String { - DEFAULT_DATABASE_CONNECTION_ENV.to_owned() +fn search_for_directory_containing_file(path: &Path, file_name: &str) -> MigraResult { + let file_path = path.join(file_name); + if file_path.is_file() { + Ok(path.to_owned()) + } else { + path.parent() + .ok_or(Error::RootNotFound) + .and_then(|p| search_for_directory_containing_file(p, file_name)) + } +} + +fn recursive_find_project_root() -> MigraResult { + let current_dir = std::env::current_dir()?; + + search_for_directory_containing_file(¤t_dir, MIGRA_TOML_FILENAME) } #[cfg(any(not(feature = "postgres"), not(feature = "mysql")))] @@ -24,15 +38,12 @@ cargo install migra-cli --features ${database_name}"#, }; } -#[derive(Debug, Serialize, Deserialize)] -pub(crate) struct Config { - #[serde(skip)] - manifest_root: PathBuf, +//===========================================================================// +// Database config // +//===========================================================================// - root: PathBuf, - - #[serde(default)] - pub database: DatabaseConfig, +fn default_database_connection_env() -> String { + String::from("$DATABASE_URL") } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -56,7 +67,7 @@ impl Default for SupportedDatabaseClient { } } -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct DatabaseConfig { pub client: Option, @@ -64,6 +75,15 @@ pub(crate) struct DatabaseConfig { pub connection: String, } +impl Default for DatabaseConfig { + fn default() -> Self { + DatabaseConfig { + connection: default_database_connection_env(), + client: None, + } + } +} + impl DatabaseConfig { pub fn client(&self) -> SupportedDatabaseClient { self.client.clone().unwrap_or_else(|| { @@ -103,36 +123,108 @@ impl DatabaseConfig { } } +//===========================================================================// +// Migrations config // +//===========================================================================// + +fn default_migrations_directory() -> String { + String::from("migrations") +} + +fn default_migrations_table_name() -> String { + String::from("migrations") +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct MigrationsConfig { + #[serde(rename = "directory", default = "default_migrations_directory")] + directory: String, + + #[serde(default = "default_migrations_table_name")] + table_name: String, + + date_format: Option, +} + +impl Default for MigrationsConfig { + fn default() -> Self { + MigrationsConfig { + directory: default_migrations_directory(), + table_name: default_migrations_table_name(), + date_format: None, + } + } +} + +impl MigrationsConfig { + pub fn directory(&self) -> String { + if let Some(directory_env) = self.directory.strip_prefix("$") { + env::var(directory_env).unwrap_or_else(|_| { + println!( + "WARN: Cannot read {} variable and use {} directory by default", + directory_env, + default_migrations_directory() + ); + default_migrations_directory() + }) + } else { + self.directory.clone() + } + } + + pub fn table_name(&self) -> String { + if let Some(table_name_env) = self.table_name.strip_prefix("$") { + env::var(table_name_env).unwrap_or_else(|_| { + println!( + "WARN: Cannot read {} variable and use {} table_name by default", + table_name_env, + default_migrations_table_name() + ); + default_migrations_table_name() + }) + } else { + self.table_name.clone() + } + } + + pub fn date_format(&self) -> String { + self.date_format + .clone() + .unwrap_or_else(|| String::from("%y%m%d%H%M%S")) + } +} + +//===========================================================================// +// Main config // +//===========================================================================// + +pub(crate) const MIGRA_TOML_FILENAME: &str = "Migra.toml"; + +#[derive(Debug, Serialize, Deserialize)] +pub(crate) struct Config { + #[serde(skip)] + manifest_root: PathBuf, + + root: PathBuf, + + #[serde(default)] + pub(crate) database: DatabaseConfig, + + #[serde(default)] + pub(crate) migrations: MigrationsConfig, +} + impl Default for Config { fn default() -> Config { Config { manifest_root: PathBuf::default(), root: PathBuf::from("database"), - database: DatabaseConfig { - connection: default_database_connection_env(), - ..Default::default() - }, + database: DatabaseConfig::default(), + migrations: MigrationsConfig::default(), } } } -fn search_for_directory_containing_file(path: &Path, file_name: &str) -> MigraResult { - let file_path = path.join(file_name); - if file_path.is_file() { - Ok(path.to_owned()) - } else { - path.parent() - .ok_or(Error::RootNotFound) - .and_then(|p| search_for_directory_containing_file(p, file_name)) - } -} - -fn recursive_find_project_root() -> MigraResult { - let current_dir = std::env::current_dir()?; - - search_for_directory_containing_file(¤t_dir, MIGRA_TOML_FILENAME) -} - impl Config { pub fn read(config_path: Option<&PathBuf>) -> MigraResult { let config_path = match config_path { @@ -160,15 +252,13 @@ impl Config { } } } -} -impl Config { pub fn directory_path(&self) -> PathBuf { self.manifest_root.join(&self.root) } pub fn migration_dir_path(&self) -> PathBuf { - self.directory_path().join("migrations") + self.directory_path().join(self.migrations.directory()) } pub fn migrations(&self) -> MigraResult> { diff --git a/migra-cli/src/database/clients/mysql.rs b/migra-cli/src/database/clients/mysql.rs index 2f53aa5..6453230 100644 --- a/migra-cli/src/database/clients/mysql.rs +++ b/migra-cli/src/database/clients/mysql.rs @@ -17,11 +17,14 @@ impl OpenDatabaseConnection for MySqlConnection { } impl DatabaseStatements for MySqlConnection { - fn create_migration_table_stmt(&self) -> &'static str { - r#"CREATE TABLE IF NOT EXISTS migrations ( - id int AUTO_INCREMENT PRIMARY KEY, - name varchar(256) NOT NULL UNIQUE - )"# + fn create_migration_table_stmt(&self, migrations_table_name: &str) -> String { + format!( + r#"CREATE TABLE IF NOT EXISTS {} ( + id int AUTO_INCREMENT PRIMARY KEY, + name varchar(256) NOT NULL UNIQUE + )"#, + migrations_table_name + ) } } diff --git a/migra-cli/src/database/clients/postgres.rs b/migra-cli/src/database/clients/postgres.rs index 23b53a4..fb4a16c 100644 --- a/migra-cli/src/database/clients/postgres.rs +++ b/migra-cli/src/database/clients/postgres.rs @@ -15,11 +15,14 @@ impl OpenDatabaseConnection for PostgresConnection { } impl DatabaseStatements for PostgresConnection { - fn create_migration_table_stmt(&self) -> &'static str { - r#"CREATE TABLE IF NOT EXISTS migrations ( - id serial PRIMARY KEY, - name text NOT NULL UNIQUE - )"# + fn create_migration_table_stmt(&self, migrations_table_name: &str) -> String { + format!( + r#"CREATE TABLE IF NOT EXISTS {} ( + id serial PRIMARY KEY, + name text NOT NULL UNIQUE + )"#, + migrations_table_name + ) } } diff --git a/migra-cli/src/database/connection.rs b/migra-cli/src/database/connection.rs index 6fa68e4..76e1260 100644 --- a/migra-cli/src/database/connection.rs +++ b/migra-cli/src/database/connection.rs @@ -10,7 +10,7 @@ pub trait OpenDatabaseConnection: Sized { } pub trait DatabaseStatements { - fn create_migration_table_stmt(&self) -> &'static str; + fn create_migration_table_stmt(&self, migrations_table_name: &str) -> String; } pub trait SupportsTransactionalDdl { diff --git a/migra-cli/src/database/migration.rs b/migra-cli/src/database/migration.rs index e6b1657..c5a0a6b 100644 --- a/migra-cli/src/database/migration.rs +++ b/migra-cli/src/database/migration.rs @@ -1,4 +1,5 @@ use super::connection::AnyConnection; +use crate::Config; use crate::StdResult; use std::fs; use std::path::{Path, PathBuf}; @@ -50,11 +51,21 @@ impl Migration { } #[derive(Debug)] -pub struct MigrationManager; +pub struct MigrationManager { + migrations_table_name: String, +} impl MigrationManager { - pub fn new() -> Self { - MigrationManager + fn new(migrations_table_name: &str) -> Self { + MigrationManager { + migrations_table_name: migrations_table_name.to_owned(), + } + } +} + +impl From<&Config> for MigrationManager { + fn from(config: &Config) -> Self { + MigrationManager::new(&config.migrations.table_name()) } } @@ -109,21 +120,39 @@ impl ManageMigration for MigrationManager { } fn create_migrations_table(&self, conn: &mut AnyConnection) -> StdResult<()> { - let stmt = conn.create_migration_table_stmt(); + let stmt = conn.create_migration_table_stmt(&self.migrations_table_name); conn.batch_execute(&stmt) } fn insert_migration_info(&self, conn: &mut AnyConnection, name: &str) -> StdResult { - conn.execute("INSERT INTO migrations (name) VALUES ($1)", &[&name]) + conn.execute( + &format!( + "INSERT INTO {} (name) VALUES ($1)", + &self.migrations_table_name + ), + &[&name], + ) } fn delete_migration_info(&self, conn: &mut AnyConnection, name: &str) -> StdResult { - conn.execute("DELETE FROM migrations WHERE name = $1", &[&name]) + conn.execute( + &format!( + "DELETE FROM {} WHERE name = $1", + &self.migrations_table_name + ), + &[&name], + ) } fn applied_migration_names(&self, conn: &mut AnyConnection) -> StdResult> { let res = conn - .query("SELECT name FROM migrations ORDER BY id DESC", &[]) + .query( + &format!( + "SELECT name FROM {} ORDER BY id DESC", + &self.migrations_table_name + ), + &[], + ) .or_else(|e| { if is_migrations_table_not_found(&e) { Ok(Vec::new()) diff --git a/migra-cli/tests/commands.rs b/migra-cli/tests/commands.rs index e9bed51..92ddcab 100644 --- a/migra-cli/tests/commands.rs +++ b/migra-cli/tests/commands.rs @@ -44,6 +44,8 @@ mod init { fn init_manifest_with_default_config() -> TestResult { let manifest_path = "Migra.toml"; + fs::remove_file(&manifest_path).ok(); + Command::cargo_bin("migra")? .arg("init") .assert() @@ -58,6 +60,10 @@ mod init { [database] connection = "$DATABASE_URL" + +[migrations] +directory = "migrations" +table_name = "migrations" "# ); @@ -70,6 +76,8 @@ connection = "$DATABASE_URL" fn init_manifest_in_custom_path() -> TestResult { let manifest_path = path_to_file("Migra.toml"); + fs::remove_file(&manifest_path).ok(); + Command::cargo_bin("migra")? .arg("-c") .arg(&manifest_path) @@ -86,6 +94,10 @@ connection = "$DATABASE_URL" [database] connection = "$DATABASE_URL" + +[migrations] +directory = "migrations" +table_name = "migrations" "# );