Skip to content

Commit 732a7d1

Browse files
committed
Revert the n last migrations
1 parent dc9fcd6 commit 732a7d1

5 files changed

Lines changed: 131 additions & 19 deletions

File tree

diesel_cli/src/cli.rs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,36 @@ pub fn build_cli() -> App<'static, 'static> {
2222
SubCommand::with_name("revert")
2323
.about("Reverts the latest run migration")
2424
.arg(
25-
Arg::with_name("REVERT_ALL")
26-
.long("all")
27-
.help("Reverts all migrations")
25+
Arg::with_name("REVERT_NUMBER")
26+
.long("number")
27+
.short("n")
28+
.help("Reverts the last migration files")
2829
.long_help(
29-
"When this option is specified all migrations \
30-
will be reverted. Useful for testing your revert \
31-
scripts only.",
30+
"When this option is specified the last `n` migration files \
31+
will be reverted. By default revert the last one.",
3232
)
33-
)
33+
.takes_value(true)
34+
.required(true)
35+
.default_value("1"),
36+
),
3437
)
3538
.subcommand(
3639
SubCommand::with_name("redo")
3740
.about(
3841
"Reverts and re-runs the latest migration. Useful \
39-
for testing that a migration can in fact be reverted.",
42+
for testing that a migration can in fact be reverted.",
4043
)
4144
.arg(
4245
Arg::with_name("REDO_ALL")
4346
.long("all")
4447
.help("Reverts and re-runs all migrations.")
4548
.long_help(
4649
"When this option is specified all migrations \
47-
will be reverts and re-runs. Useful for testing \
48-
that your migrations can be reverted and applied."
49-
)
50-
)
51-
)
50+
will be reverted and re-runs. Useful for testing \
51+
that your migrations can be reverted and applied.",
52+
),
53+
),
54+
)
5255
.subcommand(
5356
SubCommand::with_name("list")
5457
.about("Lists all available migrations, marking those that have been applied."),

diesel_cli/src/main.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,23 @@ fn run_migration_command(matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
7171
)?;
7272
regenerate_schema_if_file_specified(matches)?;
7373
}
74-
("revert", Some(_)) => {
74+
("revert", Some(args)) => {
7575
let database_url = database::database_url(matches);
7676
let dir = migrations_dir(matches).unwrap_or_else(handle_error);
77-
call_with_conn!(
78-
database_url,
79-
migrations::revert_latest_migration_in_directory(&dir)
80-
)?;
77+
78+
match args.value_of("REVERT_NUMBER") {
79+
Some("all") => println!("Match on all files"),
80+
Some(n) => {
81+
let x = n.parse::<i64>().unwrap_or_else(handle_error);
82+
83+
call_with_conn!(
84+
database_url,
85+
migrations::revert_latest_migrations_in_directory(&dir, x)
86+
)?;
87+
}
88+
None => unreachable!("REVERT_NUMBER has a default value"),
89+
}
90+
8191
regenerate_schema_if_file_specified(matches)?;
8292
}
8393
("redo", Some(_)) => {

diesel_cli/tests/migration_revert.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,54 @@ fn migration_revert_respects_migration_dir_from_diesel_toml() {
128128
);
129129
assert!(!db.table_exists("users"));
130130
}
131+
132+
#[test]
133+
fn migration_revert_runs_the_last_two_migration_down() {
134+
let p = project("migration_revert").folder("migrations").build();
135+
let db = database(&p.database_url());
136+
137+
p.create_migration(
138+
"2017-08-31-210424_create_customers",
139+
"CREATE TABLE customers ( id INTEGER PRIMARY KEY )",
140+
"DROP TABLE customers",
141+
);
142+
143+
p.create_migration(
144+
"2017-09-03-210424_create_contracts",
145+
"CREATE TABLE contracts ( id INTEGER PRIMARY KEY )",
146+
"DROP TABLE contracts",
147+
);
148+
149+
p.create_migration(
150+
"2017-09-12-210424_create_bills",
151+
"CREATE TABLE bills ( id INTEGER PRIMARY KEY )",
152+
"DROP TABLE bills",
153+
);
154+
155+
// Make sure the project is setup
156+
p.command("setup").run();
157+
158+
assert!(db.table_exists("customers"));
159+
assert!(db.table_exists("contracts"));
160+
assert!(db.table_exists("bills"));
161+
162+
// Reverts the last two migration files. The `contracts` and `bills` tables should be dropped.
163+
// The `customers` table shouldn't be reverted.
164+
let result = p
165+
.command("migration")
166+
.arg("revert")
167+
.arg("-n")
168+
.arg("2")
169+
.run();
170+
171+
assert!(result.is_success(), "Result was unsuccessful {:?}", result);
172+
assert!(
173+
result.stdout().contains("Rolling back migration 2017-09-12-210424_create_bills\nRolling back migration 2017-09-03-210424_create_contracts"),
174+
"Unexpected stdout {}",
175+
result.stdout()
176+
);
177+
178+
assert!(db.table_exists("customers"));
179+
assert!(!db.table_exists("contracts"));
180+
assert!(!db.table_exists("bills"));
181+
}

diesel_migrations/migrations_internals/src/connection.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use diesel::deserialize::FromSql;
22
use diesel::expression::bound::Bound;
33
use diesel::expression::QueryMetadata;
4-
use diesel::helper_types::{max, Limit, Select};
4+
use diesel::helper_types::{max, Desc, Limit, Order, Select};
55
use diesel::insertable::ColumnInsertValue;
66
use diesel::prelude::*;
77
use diesel::query_builder::{InsertStatement, QueryFragment, ValuesClause};
@@ -19,6 +19,7 @@ use super::schema::__diesel_schema_migrations::dsl::*;
1919
pub trait MigrationConnection: diesel::migration::MigrationConnection {
2020
fn previously_run_migration_versions(&self) -> QueryResult<HashSet<String>>;
2121
fn latest_run_migration_version(&self) -> QueryResult<Option<String>>;
22+
fn latest_run_migration_versions(&self, number: i64) -> QueryResult<Vec<String>>;
2223
fn insert_new_migration(&self, version: &str) -> QueryResult<()>;
2324
}
2425

@@ -38,6 +39,8 @@ where
3839
Select<__diesel_schema_migrations, version>: LoadQuery<T, String>,
3940
Limit<Select<__diesel_schema_migrations, max<version>>>: QueryFragment<T::Backend>,
4041
T::Backend: QueryMetadata<Nullable<VarChar>>,
42+
Order<Limit<Select<__diesel_schema_migrations, version>>, Desc<version>>:
43+
QueryFragment<T::Backend>,
4144
{
4245
fn previously_run_migration_versions(&self) -> QueryResult<HashSet<String>> {
4346
__diesel_schema_migrations
@@ -51,6 +54,14 @@ where
5154
__diesel_schema_migrations.select(max(version)).first(self)
5255
}
5356

57+
fn latest_run_migration_versions(&self, number: i64) -> QueryResult<Vec<String>> {
58+
__diesel_schema_migrations
59+
.select(version)
60+
.order(version.desc())
61+
.limit(number)
62+
.load(self)
63+
}
64+
5465
fn insert_new_migration(&self, ver: &str) -> QueryResult<()> {
5566
::diesel::insert_into(__diesel_schema_migrations)
5667
.values(&version.eq(ver))

diesel_migrations/migrations_internals/src/lib.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,43 @@ where
200200
Ok(pending)
201201
}
202202

203+
// TODO : see if we need a function named revert_latest_migrations
204+
// to call this one.
205+
pub fn revert_latest_migrations_in_directory<Conn>(
206+
conn: &Conn,
207+
path: &Path,
208+
number: i64,
209+
) -> Result<Vec<String>, RunMigrationsError>
210+
where
211+
Conn: MigrationConnection,
212+
{
213+
setup_database(conn)?;
214+
215+
let latest_migration_versions = match conn.latest_run_migration_versions(number) {
216+
Ok(migration_versions) => migration_versions,
217+
Err(_) => {
218+
return Err(RunMigrationsError::MigrationError(
219+
MigrationError::NoMigrationRun,
220+
))
221+
}
222+
};
223+
224+
if latest_migration_versions.is_empty() {
225+
return Err(RunMigrationsError::MigrationError(
226+
MigrationError::NoMigrationRun,
227+
));
228+
}
229+
230+
for migration_version in &latest_migration_versions {
231+
// TODO see how to handle the errors... even if these errors
232+
// are displayed on stdout.
233+
revert_migration_with_version(conn, path, &migration_version, &mut stdout())
234+
.unwrap_or_else(|_| ());
235+
}
236+
237+
Ok(latest_migration_versions)
238+
}
239+
203240
/// Reverts the last migration that was run. Returns the version that was reverted. Returns an
204241
/// `Err` if no migrations have ever been run.
205242
///

0 commit comments

Comments
 (0)