Skip to content

Commit b06c41e

Browse files
author
Markus Gasser
committed
Add the possibility to skip migrations
1 parent 91d26ba commit b06c41e

File tree

16 files changed

+395
-61
lines changed

16 files changed

+395
-61
lines changed

sqlx-cli/src/database.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ pub async fn reset(
5757

5858
pub async fn setup(migration_source: &str, connect_opts: &ConnectOpts) -> anyhow::Result<()> {
5959
create(connect_opts).await?;
60-
migrate::run(migration_source, connect_opts, false, false, None).await
60+
migrate::run(migration_source, connect_opts, false, false, None, false).await
6161
}
6262

6363
async fn ask_to_continue_drop(db_url: String) -> bool {

sqlx-cli/src/lib.rs

+18
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,24 @@ async fn do_run(opt: Opt) -> Result<()> {
7373
dry_run,
7474
*ignore_missing,
7575
target_version,
76+
false,
77+
)
78+
.await?
79+
}
80+
MigrateCommand::Skip {
81+
source,
82+
dry_run,
83+
ignore_missing,
84+
connect_opts,
85+
target_version,
86+
} => {
87+
migrate::run(
88+
&source,
89+
&connect_opts,
90+
dry_run,
91+
*ignore_missing,
92+
target_version,
93+
true,
7694
)
7795
.await?
7896
}

sqlx-cli/src/migrate.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ pub async fn run(
277277
dry_run: bool,
278278
ignore_missing: bool,
279279
target_version: Option<i64>,
280+
skip: bool,
280281
) -> anyhow::Result<()> {
281282
let migrator = Migrator::new(Path::new(migration_source)).await?;
282283
if let Some(target_version) = target_version {
@@ -326,18 +327,20 @@ pub async fn run(
326327
}
327328
}
328329
None => {
329-
let skip =
330+
let exceeds_target =
330331
target_version.is_some_and(|target_version| migration.version > target_version);
331332

332-
let elapsed = if dry_run || skip {
333+
let elapsed = if dry_run || exceeds_target {
333334
Duration::new(0, 0)
334335
} else {
335-
conn.apply(migration).await?
336+
conn.apply(migration, skip).await?
336337
};
337-
let text = if skip {
338+
let text = if exceeds_target {
338339
"Skipped"
339340
} else if dry_run {
340341
"Can apply"
342+
} else if skip {
343+
"Skipped on request"
341344
} else {
342345
"Applied"
343346
};

sqlx-cli/src/opt.rs

+21
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,27 @@ pub enum MigrateCommand {
183183
target_version: Option<i64>,
184184
},
185185

186+
/// Skip all pending migrations.
187+
Skip {
188+
#[clap(flatten)]
189+
source: Source,
190+
191+
/// List all the migrations to be skipped without marking them as applied.
192+
#[clap(long)]
193+
dry_run: bool,
194+
195+
#[clap(flatten)]
196+
ignore_missing: IgnoreMissing,
197+
198+
#[clap(flatten)]
199+
connect_opts: ConnectOpts,
200+
201+
/// Apply migrations up to the specified version. If unspecified, apply all
202+
/// pending migrations. If already at the target version, then no-op.
203+
#[clap(long)]
204+
target_version: Option<i64>,
205+
},
206+
186207
/// Revert the latest migration with a down file.
187208
Revert {
188209
#[clap(flatten)]

sqlx-cli/tests/common/mod.rs

+23-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@ pub struct TestDatabase {
1212
migrations: String,
1313
}
1414

15+
pub enum MigrateCommand {
16+
Run,
17+
Revert,
18+
Skip,
19+
}
20+
21+
impl AsRef<str> for MigrateCommand {
22+
fn as_ref(&self) -> &str {
23+
match self {
24+
MigrateCommand::Run => "run",
25+
MigrateCommand::Revert => "revert",
26+
MigrateCommand::Skip => "skip",
27+
}
28+
}
29+
}
30+
1531
impl TestDatabase {
1632
pub fn new(name: &str, migrations: &str) -> Self {
1733
let migrations_path = Path::new("tests").join(migrations);
@@ -38,7 +54,12 @@ impl TestDatabase {
3854
format!("sqlite://{}", self.file_path.display())
3955
}
4056

41-
pub fn run_migration(&self, revert: bool, version: Option<i64>, dry_run: bool) -> Assert {
57+
pub fn run_migration(
58+
&self,
59+
command: MigrateCommand,
60+
version: Option<i64>,
61+
dry_run: bool,
62+
) -> Assert {
4263
let ver = match version {
4364
Some(v) => v.to_string(),
4465
None => String::from(""),
@@ -50,10 +71,7 @@ impl TestDatabase {
5071
vec![
5172
"sqlx",
5273
"migrate",
53-
match revert {
54-
true => "revert",
55-
false => "run",
56-
},
74+
command.as_ref(),
5775
"--database-url",
5876
&self.connection_string(),
5977
"--source",

sqlx-cli/tests/migrate.rs

+108-24
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
mod common;
22

3-
use common::TestDatabase;
3+
use common::{MigrateCommand, TestDatabase};
44

55
#[tokio::test]
66
async fn run_reversible_migrations() {
@@ -11,10 +11,10 @@ async fn run_reversible_migrations() {
1111
20230401000000,
1212
20230501000000,
1313
];
14-
// Without --target-version specified.k
14+
// Without --target-version specified.
1515
{
1616
let db = TestDatabase::new("migrate_run_reversible_latest", "migrations_reversible");
17-
db.run_migration(false, None, false).success();
17+
db.run_migration(MigrateCommand::Run, None, false).success();
1818
assert_eq!(db.applied_migrations().await, all_migrations);
1919
}
2020
// With --target-version specified.
@@ -25,17 +25,17 @@ async fn run_reversible_migrations() {
2525
);
2626

2727
// Move to latest, explicitly specified.
28-
db.run_migration(false, Some(20230501000000), false)
28+
db.run_migration(MigrateCommand::Run, Some(20230501000000), false)
2929
.success();
3030
assert_eq!(db.applied_migrations().await, all_migrations);
3131

3232
// Move to latest when we're already at the latest.
33-
db.run_migration(false, Some(20230501000000), false)
33+
db.run_migration(MigrateCommand::Run, Some(20230501000000), false)
3434
.success();
3535
assert_eq!(db.applied_migrations().await, all_migrations);
3636

3737
// Upgrade to an old version.
38-
db.run_migration(false, Some(20230301000000), false)
38+
db.run_migration(MigrateCommand::Run, Some(20230301000000), false)
3939
.failure();
4040
assert_eq!(db.applied_migrations().await, all_migrations);
4141
}
@@ -47,34 +47,34 @@ async fn run_reversible_migrations() {
4747
);
4848

4949
// First version
50-
db.run_migration(false, Some(20230101000000), false)
50+
db.run_migration(MigrateCommand::Run, Some(20230101000000), false)
5151
.success();
5252
assert_eq!(db.applied_migrations().await, vec![20230101000000]);
5353

5454
// Dry run upgrade to latest.
55-
db.run_migration(false, None, true).success();
55+
db.run_migration(MigrateCommand::Run, None, true).success();
5656
assert_eq!(db.applied_migrations().await, vec![20230101000000]);
5757

5858
// Dry run upgrade + 2
59-
db.run_migration(false, Some(20230301000000), true)
59+
db.run_migration(MigrateCommand::Run, Some(20230301000000), true)
6060
.success();
6161
assert_eq!(db.applied_migrations().await, vec![20230101000000]);
6262

6363
// Upgrade to non-existent version.
64-
db.run_migration(false, Some(20230901000000999), false)
64+
db.run_migration(MigrateCommand::Run, Some(20230901000000999), false)
6565
.failure();
6666
assert_eq!(db.applied_migrations().await, vec![20230101000000]);
6767

6868
// Upgrade + 1
69-
db.run_migration(false, Some(20230201000000), false)
69+
db.run_migration(MigrateCommand::Run, Some(20230201000000), false)
7070
.success();
7171
assert_eq!(
7272
db.applied_migrations().await,
7373
vec![20230101000000, 20230201000000]
7474
);
7575

7676
// Upgrade + 2
77-
db.run_migration(false, Some(20230401000000), false)
77+
db.run_migration(MigrateCommand::Run, Some(20230401000000), false)
7878
.success();
7979
assert_eq!(db.applied_migrations().await, all_migrations[..4]);
8080
}
@@ -93,55 +93,139 @@ async fn revert_migrations() {
9393
// Without --target-version
9494
{
9595
let db = TestDatabase::new("migrate_revert_incremental", "migrations_reversible");
96-
db.run_migration(false, None, false).success();
96+
db.run_migration(MigrateCommand::Run, None, false).success();
9797

9898
// Dry-run
99-
db.run_migration(true, None, true).success();
99+
db.run_migration(MigrateCommand::Revert, None, true)
100+
.success();
100101
assert_eq!(db.applied_migrations().await, all_migrations);
101102

102103
// Downgrade one
103-
db.run_migration(true, None, false).success();
104+
db.run_migration(MigrateCommand::Revert, None, false)
105+
.success();
104106
assert_eq!(db.applied_migrations().await, all_migrations[..4]);
105107

106108
// Downgrade one
107-
db.run_migration(true, None, false).success();
109+
db.run_migration(MigrateCommand::Revert, None, false)
110+
.success();
108111
assert_eq!(db.applied_migrations().await, all_migrations[..3]);
109112
}
110113
// With --target-version
111114
{
112115
let db = TestDatabase::new("migrate_revert_incremental", "migrations_reversible");
113-
db.run_migration(false, None, false).success();
116+
db.run_migration(MigrateCommand::Run, None, false).success();
114117

115118
// Dry-run downgrade to version 3.
116-
db.run_migration(true, Some(20230301000000), true).success();
119+
db.run_migration(MigrateCommand::Revert, Some(20230301000000), true)
120+
.success();
117121
assert_eq!(db.applied_migrations().await, all_migrations);
118122

119123
// Downgrade to version 3.
120-
db.run_migration(true, Some(20230301000000), false)
124+
db.run_migration(MigrateCommand::Revert, Some(20230301000000), false)
121125
.success();
122126
assert_eq!(db.applied_migrations().await, all_migrations[..3]);
123127

124128
// Try downgrading to the same version.
125-
db.run_migration(true, Some(20230301000000), false)
129+
db.run_migration(MigrateCommand::Revert, Some(20230301000000), false)
126130
.success();
127131
assert_eq!(db.applied_migrations().await, all_migrations[..3]);
128132

129133
// Try downgrading to a newer version.
130-
db.run_migration(true, Some(20230401000000), false)
134+
db.run_migration(MigrateCommand::Revert, Some(20230401000000), false)
131135
.failure();
132136
assert_eq!(db.applied_migrations().await, all_migrations[..3]);
133137

134138
// Try downgrading to a non-existent version.
135-
db.run_migration(true, Some(9999), false).failure();
139+
db.run_migration(MigrateCommand::Revert, Some(9999), false)
140+
.failure();
136141
assert_eq!(db.applied_migrations().await, all_migrations[..3]);
137142

138143
// Ensure we can still upgrade
139-
db.run_migration(false, Some(20230401000000), false)
144+
db.run_migration(MigrateCommand::Run, Some(20230401000000), false)
140145
.success();
141146
assert_eq!(db.applied_migrations().await, all_migrations[..4]);
142147

143148
// Downgrade to zero.
144-
db.run_migration(true, Some(0), false).success();
149+
db.run_migration(MigrateCommand::Revert, Some(0), false)
150+
.success();
145151
assert_eq!(db.applied_migrations().await, vec![] as Vec<i64>);
146152
}
147153
}
154+
#[tokio::test]
155+
async fn skip_reversible_migrations() {
156+
let all_migrations: Vec<i64> = vec![
157+
20230101000000,
158+
20230201000000,
159+
20230301000000,
160+
20230401000000,
161+
20230501000000,
162+
];
163+
// Without --target-version specified.
164+
{
165+
let db = TestDatabase::new("migrate_skip_reversible_latest", "migrations_reversible");
166+
db.run_migration(MigrateCommand::Skip, None, false)
167+
.success();
168+
assert_eq!(db.applied_migrations().await, all_migrations);
169+
}
170+
// With --target-version specified.
171+
{
172+
let db = TestDatabase::new(
173+
"migrate_skip_reversible_latest_explicit",
174+
"migrations_reversible",
175+
);
176+
177+
// Move to latest, explicitly specified.
178+
db.run_migration(MigrateCommand::Run, Some(20230501000000), false)
179+
.success();
180+
assert_eq!(db.applied_migrations().await, all_migrations);
181+
182+
// Skip to latest when we're already at the latest.
183+
db.run_migration(MigrateCommand::Skip, Some(20230501000000), false)
184+
.success();
185+
assert_eq!(db.applied_migrations().await, all_migrations);
186+
187+
// Upgrade to an old version.
188+
db.run_migration(MigrateCommand::Skip, Some(20230301000000), false)
189+
.failure();
190+
assert_eq!(db.applied_migrations().await, all_migrations);
191+
}
192+
// With --target-version, incrementally upgrade.
193+
{
194+
let db = TestDatabase::new(
195+
"migrate_skip_reversible_incremental",
196+
"migrations_reversible",
197+
);
198+
199+
// Run first version
200+
db.run_migration(MigrateCommand::Run, Some(20230101000000), false)
201+
.success();
202+
assert_eq!(db.applied_migrations().await, vec![20230101000000]);
203+
204+
// Skip and dry run upgrade to latest.
205+
db.run_migration(MigrateCommand::Skip, None, true).success();
206+
assert_eq!(db.applied_migrations().await, vec![20230101000000]);
207+
208+
// Skip and dry run upgrade + 2
209+
db.run_migration(MigrateCommand::Skip, Some(20230301000000), true)
210+
.success();
211+
assert_eq!(db.applied_migrations().await, vec![20230101000000]);
212+
213+
// Skip to to non-existent version.
214+
db.run_migration(MigrateCommand::Skip, Some(20230901000000999), false)
215+
.failure();
216+
assert_eq!(db.applied_migrations().await, vec![20230101000000]);
217+
218+
// Upgrade + 1
219+
db.run_migration(MigrateCommand::Run, Some(20230201000000), false)
220+
.success();
221+
assert_eq!(
222+
db.applied_migrations().await,
223+
vec![20230101000000, 20230201000000]
224+
);
225+
226+
// Skip + 2
227+
db.run_migration(MigrateCommand::Skip, Some(20230401000000), false)
228+
.success();
229+
assert_eq!(db.applied_migrations().await, all_migrations[..4]);
230+
}
231+
}

sqlx-core/src/any/migrate.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@ impl Migrate for AnyConnection {
6969
fn apply<'e: 'm, 'm>(
7070
&'e mut self,
7171
migration: &'m Migration,
72+
skip: bool,
7273
) -> BoxFuture<'m, Result<Duration, MigrateError>> {
73-
Box::pin(async { self.get_migrate()?.apply(migration).await })
74+
Box::pin(async move { self.get_migrate()?.apply(migration, skip).await })
7475
}
7576

7677
fn revert<'e: 'm, 'm>(

0 commit comments

Comments
 (0)