Merge pull request #9 from pleshevskiy/extend-manifest
Extend migra manifest
This commit is contained in:
commit
885abd0871
11 changed files with 202 additions and 63 deletions
|
@ -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
|
||||
|
|
|
@ -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()?;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)?;
|
||||
}
|
||||
|
|
|
@ -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()?;
|
||||
|
|
|
@ -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<PathBuf> {
|
||||
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<PathBuf> {
|
||||
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<SupportedDatabaseClient>,
|
||||
|
||||
|
@ -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<String>,
|
||||
}
|
||||
|
||||
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<PathBuf> {
|
||||
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<PathBuf> {
|
||||
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<Config> {
|
||||
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<Vec<Migration>> {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<u64> {
|
||||
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<u64> {
|
||||
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<Vec<String>> {
|
||||
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())
|
||||
|
|
|
@ -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"
|
||||
"#
|
||||
);
|
||||
|
||||
|
|
Reference in a new issue