mod migration;
mod migration_error;
mod schema;
pub use self::migration_error::*;
use ::expression::expression_methods::*;
use ::query_dsl::*;
use self::migration::*;
use self::migration_error::MigrationError::*;
use self::schema::NewMigration;
use self::schema::__diesel_schema_migrations::dsl::*;
use {Connection, QueryResult};
use std::collections::HashSet;
use std::env;
use std::path::{PathBuf, Path};
pub fn run_pending_migrations(conn: &Connection) -> Result<(), RunMigrationsError> {
try!(create_schema_migrations_table_if_needed(conn));
let already_run = try!(previously_run_migration_versions(conn));
let migrations_dir = try!(find_migrations_directory());
let all_migrations = try!(migrations_in_directory(&migrations_dir));
let pending_migrations = all_migrations.into_iter().filter(|m| {
!already_run.contains(m.version())
});
run_migrations(conn, pending_migrations)
}
pub fn revert_latest_migration(conn: &Connection) -> Result<String, RunMigrationsError> {
try!(create_schema_migrations_table_if_needed(conn));
let latest_migration_version = try!(latest_run_migration_version(conn));
revert_migration_with_version(conn, &latest_migration_version)
.map(|_| latest_migration_version)
}
#[doc(hidden)]
pub fn revert_migration_with_version(conn: &Connection, ver: &str) -> Result<(), RunMigrationsError> {
migration_with_version(ver)
.map_err(|e| e.into())
.and_then(|m| revert_migration(conn, m))
}
#[doc(hidden)]
pub fn run_migration_with_version(conn: &Connection, ver: &str) -> Result<(), RunMigrationsError> {
migration_with_version(ver)
.map_err(|e| e.into())
.and_then(|m| run_migration(conn, m))
}
fn migration_with_version(ver: &str) -> Result<Box<Migration>, MigrationError> {
let migrations_dir = try!(find_migrations_directory());
let all_migrations = try!(migrations_in_directory(&migrations_dir));
let migration = all_migrations.into_iter().find(|m| {
m.version() == ver
});
match migration {
Some(m) => Ok(m),
None => Err(UnknownMigrationVersion(ver.into())),
}
}
fn create_schema_migrations_table_if_needed(conn: &Connection) -> QueryResult<usize> {
conn.silence_notices(|| {
conn.execute("CREATE TABLE IF NOT EXISTS __diesel_schema_migrations (
version VARCHAR PRIMARY KEY NOT NULL,
run_on TIMESTAMP NOT NULL DEFAULT NOW()
)")
})
}
fn previously_run_migration_versions(conn: &Connection) -> QueryResult<HashSet<String>> {
__diesel_schema_migrations.select(version)
.load(&conn)
.map(|r| r.collect())
}
fn latest_run_migration_version(conn: &Connection) -> QueryResult<String> {
use ::expression::dsl::max;
__diesel_schema_migrations.select(max(version))
.first(&conn)
}
fn migrations_in_directory(path: &Path) -> Result<Vec<Box<Migration>>, MigrationError> {
use self::migration::migration_from;
try!(path.read_dir())
.filter_map(|entry| {
let entry = match entry {
Ok(e) => e,
Err(e) => return Some(Err(e.into())),
};
if !entry.file_name().to_string_lossy().starts_with(".") {
Some(migration_from(entry.path()))
} else {
None
}
}).collect()
}
fn run_migrations<T>(conn: &Connection, migrations: T)
-> Result<(), RunMigrationsError> where
T: Iterator<Item=Box<Migration>>
{
for migration in migrations {
try!(run_migration(conn, migration));
}
Ok(())
}
fn run_migration(conn: &Connection, migration: Box<Migration>)
-> Result<(), RunMigrationsError>
{
use ::query_builder::insert;
conn.transaction(|| {
println!("Running migration {}", migration.version());
try!(migration.run(conn));
try!(insert(&NewMigration(migration.version()))
.into(__diesel_schema_migrations)
.execute(&conn));
Ok(())
}).map_err(|e| e.into())
}
fn revert_migration(conn: &Connection, migration: Box<Migration>)
-> Result<(), RunMigrationsError>
{
use ::query_builder::delete;
try!(conn.transaction(|| {
println!("Rolling back migration {}", migration.version());
try!(migration.revert(conn));
let target = __diesel_schema_migrations.filter(version.eq(migration.version()));
try!(delete(target).execute(&conn));
Ok(())
}));
Ok(())
}
pub fn find_migrations_directory() -> Result<PathBuf, MigrationError> {
search_for_migrations_directory(&try!(env::current_dir()))
}
fn search_for_migrations_directory(path: &Path) -> Result<PathBuf, MigrationError> {
let migration_path = path.join("migrations");
if migration_path.is_dir() {
Ok(migration_path)
} else {
path.parent().map(search_for_migrations_directory)
.unwrap_or(Err(MigrationError::MigrationDirectoryNotFound))
}
}
#[cfg(test)]
mod tests {
extern crate tempdir;
use super::*;
use self::tempdir::TempDir;
use std::{env, fs};
#[test]
fn migration_directory_not_found_if_no_migration_dir_exists() {
let dir = TempDir::new("diesel").unwrap();
env::set_current_dir(dir.path()).unwrap();
assert_eq!(Err(MigrationError::MigrationDirectoryNotFound),
find_migrations_directory());
}
#[test]
fn migration_directory_defaults_to_pwd_slash_migrations() {
let dir = TempDir::new("diesel").unwrap();
let temp_path = dir.path().canonicalize().unwrap();
let migrations_path = temp_path.join("migrations");
env::set_current_dir(temp_path).unwrap();
fs::create_dir(&migrations_path).unwrap();
assert_eq!(Ok(migrations_path), find_migrations_directory());
}
#[test]
fn migration_directory_checks_parents() {
let dir = TempDir::new("diesel").unwrap();
let temp_path = dir.path().canonicalize().unwrap();
let migrations_path = temp_path.join("migrations");
let child_path = temp_path.join("child");
fs::create_dir(&child_path).unwrap();
fs::create_dir(&migrations_path).unwrap();
env::set_current_dir(child_path).unwrap();
assert_eq!(Ok(migrations_path), find_migrations_directory());
}
}