diff --git a/bin/diesel b/bin/diesel new file mode 100755 index 000000000000..90c6bd5171f3 --- /dev/null +++ b/bin/diesel @@ -0,0 +1,2 @@ +#!/bin/sh +cd diesel_cli && cargo run -- $@ diff --git a/bin/test b/bin/test index 4c82c147d808..b13c5920a16d 100755 --- a/bin/test +++ b/bin/test @@ -1,3 +1,4 @@ #!/bin/sh (cd diesel && cargo test --features unstable) && + (cd diesel_cli && cargo test) && (cd diesel_tests && cargo test --features unstable --no-default-features) diff --git a/diesel/src/migrations/mod.rs b/diesel/src/migrations/mod.rs index cde27938f7c8..d7e6fb8f32e8 100644 --- a/diesel/src/migrations/mod.rs +++ b/diesel/src/migrations/mod.rs @@ -97,33 +97,41 @@ pub fn run_pending_migrations(conn: &Connection) -> Result<(), RunMigrationsErro run_migrations(conn, pending_migrations) } -/// Reverts the last migration that was run. This function will return an `Err` if no migrations -/// have ever been run. +/// Reverts the last migration that was run. Returns the version that was reverted. Returns an +/// `Err` if no migrations have ever been run. /// /// See the [module level documentation](index.html) for information on how migrations should be /// structured, and where Diesel will look for them by default. -pub fn revert_latest_migration(conn: &Connection) -> Result<(), RunMigrationsError> { +pub fn revert_latest_migration(conn: &Connection) -> Result { 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) + revert_migration_with_version(conn, &latest_migration_version) + .map(|_| latest_migration_version) } -/// Reverts the migration with the given version. This function will return an `Err` if a migration -/// with that version cannot be found, an error occurs reading it, or if an error occurs when -/// reverting it. -/// -/// See the [module level documentation](index.html) for information on how migrations should be -/// structured, and where Diesel will look for them by default. -pub fn revert_migration_with_version(conn: &Connection, ver: String) -> Result<(), RunMigrationsError> { - try!(create_schema_migrations_table_if_needed(conn)); +#[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, MigrationError> { let migrations_dir = try!(find_migrations_directory()); let all_migrations = try!(migrations_in_directory(&migrations_dir)); - let migration_to_revert = all_migrations.into_iter().find(|m| { + let migration = all_migrations.into_iter().find(|m| { m.version() == ver }); - match migration_to_revert { - Some(m) => revert_migration(&conn, m), - None => Err(UnknownMigrationVersion(ver).into()), + match migration { + Some(m) => Ok(m), + None => Err(UnknownMigrationVersion(ver.into())), } } @@ -167,21 +175,27 @@ fn run_migrations(conn: &Connection, migrations: T) -> Result<(), RunMigrationsError> where T: Iterator> { - use ::query_builder::insert; - for migration in migrations { - try!(conn.transaction(|| { - println!("Running migration {}", migration.version()); - try!(migration.run(conn)); - try!(insert(&NewMigration(migration.version())) - .into(__diesel_schema_migrations) - .execute(&conn)); - Ok(()) - })); + try!(run_migration(conn, migration)); } Ok(()) } +fn run_migration(conn: &Connection, migration: Box) + -> 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) -> Result<(), RunMigrationsError> { diff --git a/diesel_cli/Cargo.toml b/diesel_cli/Cargo.toml new file mode 100644 index 000000000000..defd20f7726e --- /dev/null +++ b/diesel_cli/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "diesel_cli" +version = "0.4.0" +authors = ["Sean Griffin "] + +[[bin]] +name = "diesel" + +[dependencies] +diesel = "^0.3.0" +clap = "^1.5.5" diff --git a/diesel_cli/src/main.rs b/diesel_cli/src/main.rs new file mode 100644 index 000000000000..d2221674bd87 --- /dev/null +++ b/diesel_cli/src/main.rs @@ -0,0 +1,90 @@ +#[macro_use] +extern crate clap; +extern crate diesel; + +use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; +use diesel::migrations; +use std::env; + +fn main() { + let database_arg = || Arg::with_name("DATABASE_URL") + .long("database-url") + .help("Specifies the database URL to connect to. Falls back to \ + the DATABASE_URL environment variable if unspecified.") + .takes_value(true); + + let migration_subcommand = SubCommand::with_name("migration") + .setting(AppSettings::VersionlessSubcommands) + .subcommand( + SubCommand::with_name("run") + .about("Runs all pending migrations") + .arg(database_arg()) + ).subcommand( + SubCommand::with_name("revert") + .about("Reverts the latest run migration") + .arg(database_arg()) + ).subcommand( + SubCommand::with_name("redo") + .about("Reverts and re-runs the latest migration. Useful \ + for testing that a migration can in fact be reverted.") + .arg(database_arg()) + ).subcommand( + SubCommand::with_name("generate") + .about("Generate a new migration with the given name, and \ + the current timestamp as the version") + .arg(Arg::with_name("MIGRATION_NAME") + .help("The name of the migration to create") + .required(true) + ) + ).setting(AppSettings::SubcommandRequiredElseHelp); + + let matches = App::new("diesel") + .version(env!("CARGO_PKG_VERSION")) + .setting(AppSettings::VersionlessSubcommands) + .subcommand(migration_subcommand) + .setting(AppSettings::SubcommandRequiredElseHelp) + .get_matches(); + + match matches.subcommand() { + ("migration", Some(matches)) => run_migration_command(matches), + _ => unreachable!(), + } +} + +// FIXME: We can improve the error handling instead of `unwrap` here. +fn run_migration_command(matches: &ArgMatches) { + match matches.subcommand() { + ("run", Some(args)) => { + migrations::run_pending_migrations(&connection(&database_url(args))) + .unwrap(); + } + ("revert", Some(args)) => { + migrations::revert_latest_migration(&connection(&database_url(args))) + .unwrap(); + } + ("redo", Some(args)) => { + let connection = connection(&database_url(args)); + connection.transaction(|| { + let reverted_version = try!(migrations::revert_latest_migration(&connection)); + migrations::run_migration_with_version(&connection, &reverted_version) + }).unwrap(); + } + ("generate", Some(args)) => { + panic!("Migration generator is not implemented this pass") + } + _ => unreachable!("The cli parser should prevent reaching here"), + } +} + +fn database_url(matches: &ArgMatches) -> String { + matches.value_of("DATABASE_URL") + .map(|s| s.into()) + .or(env::var("DATABASE_URL").ok()) + .expect("The --database-url argument must be passed, \ + or the DATABASE_URL environment variable must be set.") +} + +fn connection(database_url: &str) -> diesel::Connection { + diesel::Connection::establish(database_url) + .expect(&format!("Error connecting to {}", database_url)) +}