#![deny(warnings, missing_debug_implementations, missing_copy_implementations)]
#![allow(
clippy::option_map_unwrap_or_else,
clippy::option_map_unwrap_or,
clippy::match_same_arms,
clippy::type_complexity
)]
#![warn(
clippy::option_unwrap_used,
clippy::result_unwrap_used,
clippy::print_stdout,
clippy::wrong_pub_self_convention,
clippy::mut_mut,
clippy::non_ascii_literal,
clippy::similar_names,
clippy::unicode_not_nfc,
clippy::enum_glob_use,
clippy::if_not_else,
clippy::items_after_statements,
clippy::used_underscore_binding
)]
#![cfg_attr(test, allow(clippy::option_unwrap_used, clippy::result_unwrap_used))]
#[macro_use]
extern crate diesel;
#[cfg(feature = "barrel")]
extern crate barrel;
#[doc(hidden)]
pub mod connection;
pub mod migration;
#[doc(hidden)]
pub mod schema;
#[doc(inline)]
pub use self::connection::MigrationConnection;
#[doc(inline)]
pub use self::migration::*;
pub use diesel::migration::*;
use std::fs::DirEntry;
use std::io::{stdout, Write};
use self::schema::__diesel_schema_migrations::dsl::*;
use diesel::expression_methods::*;
use diesel::{Connection, QueryDsl, QueryResult, RunQueryDsl};
use std::env;
use std::path::{Path, PathBuf};
pub static TIMESTAMP_FORMAT: &str = "%Y-%m-%d-%H%M%S";
pub fn run_pending_migrations<Conn>(conn: &Conn) -> Result<(), RunMigrationsError>
where
Conn: MigrationConnection,
{
let migrations_dir = find_migrations_directory()?;
run_pending_migrations_in_directory(conn, &migrations_dir, &mut stdout())
}
#[doc(hidden)]
pub fn run_pending_migrations_in_directory<Conn>(
conn: &Conn,
migrations_dir: &Path,
output: &mut Write,
) -> Result<(), RunMigrationsError>
where
Conn: MigrationConnection,
{
let all_migrations = migrations_in_directory(migrations_dir)?;
run_migrations(conn, all_migrations, output)
}
pub fn mark_migrations_in_directory<Conn>(
conn: &Conn,
migrations_dir: &Path,
) -> Result<Vec<(Box<Migration>, bool)>, RunMigrationsError>
where
Conn: MigrationConnection,
{
let migrations = migrations_in_directory(migrations_dir)?;
setup_database(conn)?;
let already_run = conn.previously_run_migration_versions()?;
let migrations = migrations
.into_iter()
.map(|m| {
let applied = already_run.contains(&m.version().to_string());
(m, applied)
})
.collect();
Ok(migrations)
}
pub fn any_pending_migrations<Conn>(conn: &Conn) -> Result<bool, RunMigrationsError>
where
Conn: MigrationConnection,
{
let migrations_dir = find_migrations_directory()?;
let all_migrations = migrations_in_directory(&migrations_dir)?;
let already_run = conn.previously_run_migration_versions()?;
let pending = all_migrations
.into_iter()
.any(|m| !already_run.contains(&m.version().to_string()));
Ok(pending)
}
pub fn revert_latest_migration<Conn>(conn: &Conn) -> Result<String, RunMigrationsError>
where
Conn: MigrationConnection,
{
let migrations_dir = find_migrations_directory()?;
revert_latest_migration_in_directory(conn, &migrations_dir)
}
pub fn revert_latest_migration_in_directory<Conn>(
conn: &Conn,
path: &Path,
) -> Result<String, RunMigrationsError>
where
Conn: MigrationConnection,
{
setup_database(conn)?;
let latest_migration_version = conn
.latest_run_migration_version()?
.ok_or_else(|| RunMigrationsError::MigrationError(MigrationError::NoMigrationRun))?;
revert_migration_with_version(conn, path, &latest_migration_version, &mut stdout())
.map(|_| latest_migration_version)
}
#[doc(hidden)]
pub fn revert_migration_with_version<Conn: Connection>(
conn: &Conn,
migrations_dir: &Path,
ver: &str,
output: &mut Write,
) -> Result<(), RunMigrationsError> {
migration_with_version(migrations_dir, ver)
.map_err(|e| e.into())
.and_then(|m| revert_migration(conn, &m, output))
}
#[doc(hidden)]
pub fn run_migration_with_version<Conn>(
conn: &Conn,
migrations_dir: &Path,
ver: &str,
output: &mut Write,
) -> Result<(), RunMigrationsError>
where
Conn: MigrationConnection,
{
migration_with_version(migrations_dir, ver)
.map_err(|e| e.into())
.and_then(|m| run_migration(conn, &*m, output))
}
fn migration_with_version(
migrations_dir: &Path,
ver: &str,
) -> Result<Box<Migration>, MigrationError> {
let all_migrations = migrations_in_directory(migrations_dir)?;
let migration = all_migrations.into_iter().find(|m| m.version() == ver);
match migration {
Some(m) => Ok(m),
None => Err(MigrationError::UnknownMigrationVersion(ver.into())),
}
}
#[doc(hidden)]
pub fn setup_database<Conn: Connection>(conn: &Conn) -> QueryResult<usize> {
create_schema_migrations_table_if_needed(conn)
}
fn create_schema_migrations_table_if_needed<Conn: Connection>(conn: &Conn) -> QueryResult<usize> {
conn.execute(
"CREATE TABLE IF NOT EXISTS __diesel_schema_migrations (\
version VARCHAR(50) PRIMARY KEY NOT NULL,\
run_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP\
)",
)
}
#[doc(hidden)]
pub fn migration_paths_in_directory(path: &Path) -> Result<Vec<DirEntry>, MigrationError> {
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('.') {
None
} else {
Some(Ok(entry))
}
})
.collect()
}
fn migrations_in_directory(path: &Path) -> Result<Vec<Box<Migration>>, MigrationError> {
use self::migration::migration_from;
migration_paths_in_directory(path)?
.iter()
.map(|e| migration_from(e.path()))
.collect()
}
pub fn run_migrations<Conn, List>(
conn: &Conn,
migrations: List,
output: &mut Write,
) -> Result<(), RunMigrationsError>
where
Conn: MigrationConnection,
List: IntoIterator,
List::Item: Migration,
{
setup_database(conn)?;
let already_run = conn.previously_run_migration_versions()?;
let mut pending_migrations: Vec<_> = migrations
.into_iter()
.filter(|m| !already_run.contains(&m.version().to_string()))
.collect();
pending_migrations.sort_by(|a, b| a.version().cmp(b.version()));
for migration in pending_migrations {
run_migration(conn, &migration, output)?;
}
Ok(())
}
fn run_migration<Conn>(
conn: &Conn,
migration: &Migration,
output: &mut Write,
) -> Result<(), RunMigrationsError>
where
Conn: MigrationConnection,
{
conn.transaction(|| {
if migration.version() != "00000000000000" {
writeln!(output, "Running migration {}", name(&migration))?;
}
if let Err(e) = migration.run(conn) {
writeln!(
output,
"Executing migration script {}",
file_name(&migration, "up.sql")
)?;
return Err(e);
}
conn.insert_new_migration(migration.version())?;
Ok(())
})
}
fn revert_migration<Conn: Connection>(
conn: &Conn,
migration: &Migration,
output: &mut Write,
) -> Result<(), RunMigrationsError> {
conn.transaction(|| {
writeln!(output, "Rolling back migration {}", name(&migration))?;
if let Err(e) = migration.revert(conn) {
writeln!(
output,
"Executing migration script {}",
file_name(&migration, "down.sql")
)?;
return Err(e);
}
let target = __diesel_schema_migrations.filter(version.eq(migration.version()));
::diesel::delete(target).execute(conn)?;
Ok(())
})
}
pub fn find_migrations_directory() -> Result<PathBuf, MigrationError> {
search_for_migrations_directory(&env::current_dir()?)
}
pub 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::fs;
#[test]
fn migration_directory_not_found_if_no_migration_dir_exists() {
let dir = TempDir::new("diesel").unwrap();
assert_eq!(
Err(MigrationError::MigrationDirectoryNotFound),
search_for_migrations_directory(dir.path())
);
}
#[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");
fs::create_dir(&migrations_path).unwrap();
assert_eq!(
Ok(migrations_path),
search_for_migrations_directory(&temp_path)
);
}
#[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();
assert_eq!(
Ok(migrations_path),
search_for_migrations_directory(&child_path)
);
}
}