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 mut connection_manager = DatabaseConnectionManager::connect(&config.database)?;
|
||||||
let conn = connection_manager.connection();
|
let conn = connection_manager.connection();
|
||||||
|
|
||||||
let migration_manager = MigrationManager::new();
|
let migration_manager = MigrationManager::from(&config);
|
||||||
|
|
||||||
let file_contents = cmd_opts
|
let file_contents = cmd_opts
|
||||||
.file_paths
|
.file_paths
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub(crate) fn rollback_applied_migrations(app: &App, opts: DowngradeCommandOpt)
|
||||||
let config = app.config()?;
|
let config = app.config()?;
|
||||||
let mut connection_manager = DatabaseConnectionManager::connect(&config.database)?;
|
let mut connection_manager = DatabaseConnectionManager::connect(&config.database)?;
|
||||||
let conn = connection_manager.connection();
|
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 applied_migrations = migration_manager.applied_migration_names(conn)?;
|
||||||
let migrations = config.migrations()?;
|
let migrations = config.migrations()?;
|
||||||
|
|
|
@ -16,7 +16,7 @@ pub(crate) fn print_migration_lists(app: &App) -> StdResult<()> {
|
||||||
)?;
|
)?;
|
||||||
let conn = connection_manager.connection();
|
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 applied_migration_names = migration_manager.applied_migration_names(conn)?;
|
||||||
|
|
||||||
show_applied_migrations(&applied_migration_names);
|
show_applied_migrations(&applied_migration_names);
|
||||||
|
|
|
@ -6,7 +6,8 @@ use std::fs;
|
||||||
|
|
||||||
pub(crate) fn make_migration(app: &App, opts: MakeCommandOpt) -> StdResult<()> {
|
pub(crate) fn make_migration(app: &App, opts: MakeCommandOpt) -> StdResult<()> {
|
||||||
let config = app.config()?;
|
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
|
let migration_name: String = opts
|
||||||
.migration_name
|
.migration_name
|
||||||
|
@ -18,9 +19,10 @@ pub(crate) fn make_migration(app: &App, opts: MakeCommandOpt) -> StdResult<()> {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let migration_dir_path = config
|
let migration_dir_path = config.migration_dir_path().join(format!(
|
||||||
.migration_dir_path()
|
"{}_{}",
|
||||||
.join(format!("{}_{}", now, migration_name));
|
formatted_current_timestamp, migration_name
|
||||||
|
));
|
||||||
if !migration_dir_path.exists() {
|
if !migration_dir_path.exists() {
|
||||||
fs::create_dir_all(&migration_dir_path)?;
|
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 mut connection_manager = DatabaseConnectionManager::connect(&config.database)?;
|
||||||
let conn = connection_manager.connection();
|
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 applied_migration_names = migration_manager.applied_migration_names(conn)?;
|
||||||
let migrations = config.migrations()?;
|
let migrations = config.migrations()?;
|
||||||
|
|
|
@ -4,11 +4,25 @@ use serde::{Deserialize, Serialize};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::{env, fs, io};
|
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 {
|
fn search_for_directory_containing_file(path: &Path, file_name: &str) -> MigraResult<PathBuf> {
|
||||||
DEFAULT_DATABASE_CONNECTION_ENV.to_owned()
|
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")))]
|
#[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 {
|
// Database config //
|
||||||
#[serde(skip)]
|
//===========================================================================//
|
||||||
manifest_root: PathBuf,
|
|
||||||
|
|
||||||
root: PathBuf,
|
fn default_database_connection_env() -> String {
|
||||||
|
String::from("$DATABASE_URL")
|
||||||
#[serde(default)]
|
|
||||||
pub database: DatabaseConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[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(crate) struct DatabaseConfig {
|
||||||
pub client: Option<SupportedDatabaseClient>,
|
pub client: Option<SupportedDatabaseClient>,
|
||||||
|
|
||||||
|
@ -64,6 +75,15 @@ pub(crate) struct DatabaseConfig {
|
||||||
pub connection: String,
|
pub connection: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for DatabaseConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
DatabaseConfig {
|
||||||
|
connection: default_database_connection_env(),
|
||||||
|
client: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DatabaseConfig {
|
impl DatabaseConfig {
|
||||||
pub fn client(&self) -> SupportedDatabaseClient {
|
pub fn client(&self) -> SupportedDatabaseClient {
|
||||||
self.client.clone().unwrap_or_else(|| {
|
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 {
|
impl Default for Config {
|
||||||
fn default() -> Config {
|
fn default() -> Config {
|
||||||
Config {
|
Config {
|
||||||
manifest_root: PathBuf::default(),
|
manifest_root: PathBuf::default(),
|
||||||
root: PathBuf::from("database"),
|
root: PathBuf::from("database"),
|
||||||
database: DatabaseConfig {
|
database: DatabaseConfig::default(),
|
||||||
connection: default_database_connection_env(),
|
migrations: MigrationsConfig::default(),
|
||||||
..Default::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 {
|
impl Config {
|
||||||
pub fn read(config_path: Option<&PathBuf>) -> MigraResult<Config> {
|
pub fn read(config_path: Option<&PathBuf>) -> MigraResult<Config> {
|
||||||
let config_path = match config_path {
|
let config_path = match config_path {
|
||||||
|
@ -160,15 +252,13 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn directory_path(&self) -> PathBuf {
|
pub fn directory_path(&self) -> PathBuf {
|
||||||
self.manifest_root.join(&self.root)
|
self.manifest_root.join(&self.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn migration_dir_path(&self) -> PathBuf {
|
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>> {
|
pub fn migrations(&self) -> MigraResult<Vec<Migration>> {
|
||||||
|
|
|
@ -17,11 +17,14 @@ impl OpenDatabaseConnection for MySqlConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DatabaseStatements for MySqlConnection {
|
impl DatabaseStatements for MySqlConnection {
|
||||||
fn create_migration_table_stmt(&self) -> &'static str {
|
fn create_migration_table_stmt(&self, migrations_table_name: &str) -> String {
|
||||||
r#"CREATE TABLE IF NOT EXISTS migrations (
|
format!(
|
||||||
id int AUTO_INCREMENT PRIMARY KEY,
|
r#"CREATE TABLE IF NOT EXISTS {} (
|
||||||
name varchar(256) NOT NULL UNIQUE
|
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 {
|
impl DatabaseStatements for PostgresConnection {
|
||||||
fn create_migration_table_stmt(&self) -> &'static str {
|
fn create_migration_table_stmt(&self, migrations_table_name: &str) -> String {
|
||||||
r#"CREATE TABLE IF NOT EXISTS migrations (
|
format!(
|
||||||
id serial PRIMARY KEY,
|
r#"CREATE TABLE IF NOT EXISTS {} (
|
||||||
name text NOT NULL UNIQUE
|
id serial PRIMARY KEY,
|
||||||
)"#
|
name text NOT NULL UNIQUE
|
||||||
|
)"#,
|
||||||
|
migrations_table_name
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub trait OpenDatabaseConnection: Sized {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DatabaseStatements {
|
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 {
|
pub trait SupportsTransactionalDdl {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::connection::AnyConnection;
|
use super::connection::AnyConnection;
|
||||||
|
use crate::Config;
|
||||||
use crate::StdResult;
|
use crate::StdResult;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -50,11 +51,21 @@ impl Migration {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MigrationManager;
|
pub struct MigrationManager {
|
||||||
|
migrations_table_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl MigrationManager {
|
impl MigrationManager {
|
||||||
pub fn new() -> Self {
|
fn new(migrations_table_name: &str) -> Self {
|
||||||
MigrationManager
|
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<()> {
|
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)
|
conn.batch_execute(&stmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_migration_info(&self, conn: &mut AnyConnection, name: &str) -> StdResult<u64> {
|
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> {
|
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>> {
|
fn applied_migration_names(&self, conn: &mut AnyConnection) -> StdResult<Vec<String>> {
|
||||||
let res = conn
|
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| {
|
.or_else(|e| {
|
||||||
if is_migrations_table_not_found(&e) {
|
if is_migrations_table_not_found(&e) {
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
|
|
|
@ -44,6 +44,8 @@ mod init {
|
||||||
fn init_manifest_with_default_config() -> TestResult {
|
fn init_manifest_with_default_config() -> TestResult {
|
||||||
let manifest_path = "Migra.toml";
|
let manifest_path = "Migra.toml";
|
||||||
|
|
||||||
|
fs::remove_file(&manifest_path).ok();
|
||||||
|
|
||||||
Command::cargo_bin("migra")?
|
Command::cargo_bin("migra")?
|
||||||
.arg("init")
|
.arg("init")
|
||||||
.assert()
|
.assert()
|
||||||
|
@ -58,6 +60,10 @@ mod init {
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
connection = "$DATABASE_URL"
|
connection = "$DATABASE_URL"
|
||||||
|
|
||||||
|
[migrations]
|
||||||
|
directory = "migrations"
|
||||||
|
table_name = "migrations"
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -70,6 +76,8 @@ connection = "$DATABASE_URL"
|
||||||
fn init_manifest_in_custom_path() -> TestResult {
|
fn init_manifest_in_custom_path() -> TestResult {
|
||||||
let manifest_path = path_to_file("Migra.toml");
|
let manifest_path = path_to_file("Migra.toml");
|
||||||
|
|
||||||
|
fs::remove_file(&manifest_path).ok();
|
||||||
|
|
||||||
Command::cargo_bin("migra")?
|
Command::cargo_bin("migra")?
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(&manifest_path)
|
.arg(&manifest_path)
|
||||||
|
@ -86,6 +94,10 @@ connection = "$DATABASE_URL"
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
connection = "$DATABASE_URL"
|
connection = "$DATABASE_URL"
|
||||||
|
|
||||||
|
[migrations]
|
||||||
|
directory = "migrations"
|
||||||
|
table_name = "migrations"
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Reference in a new issue