Skip to content

Commit 497ca52

Browse files
committed
feat(migration): add function to undo migrations
Right now, there is only a `run` function to programatically run the migrations, which is great, but nothing to run the migrations down. This function adds the possibility to undo the migrations until a specific version (could say -1 or 0 to remove them all). With this feature, it's now possible, in the end to end or integration tests to run the migrations and undo them between each test set and therefore test the migrations themselves. This is the kind of feature that some ORM have like sequelize in nodejs that allow you to undo migrations programatically. Reference to the doc: https://sequelize.org/v7/manual/migrations.html#undoing-migrations Signed-off-by: Jérémie Drouet <[email protected]>
1 parent 2182925 commit 497ca52

File tree

1 file changed

+60
-0
lines changed

1 file changed

+60
-0
lines changed

sqlx-core/src/migrate/migrator.rs

+60
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,64 @@ impl Migrator {
137137

138138
Ok(())
139139
}
140+
141+
/// Run down migrations against the database until a specific version.
142+
///
143+
/// # Examples
144+
///
145+
/// ```rust,no_run
146+
/// # use sqlx_core::migrate::MigrateError;
147+
/// # #[cfg(feature = "sqlite")]
148+
/// # fn main() -> Result<(), MigrateError> {
149+
/// # sqlx_rt::block_on(async move {
150+
/// # use sqlx_core::migrate::Migrator;
151+
/// let m = Migrator::new(std::path::Path::new("./migrations")).await?;
152+
/// let pool = sqlx_core::sqlite::SqlitePoolOptions::new().connect("sqlite::memory:").await?;
153+
/// m.undo(&pool, 4).await
154+
/// # })
155+
/// # }
156+
/// ```
157+
pub async fn undo<'a, A>(&self, migrator: A, target: i64) -> Result<(), MigrateError>
158+
where
159+
A: Acquire<'a>,
160+
<A::Connection as Deref>::Target: Migrate,
161+
{
162+
let mut conn = migrator.acquire().await?;
163+
164+
// lock the database for exclusive access by the migrator
165+
conn.lock().await?;
166+
167+
// creates [_migrations] table only if needed
168+
// eventually this will likely migrate previous versions of the table
169+
conn.ensure_migrations_table().await?;
170+
171+
let version = conn.dirty_version().await?;
172+
if let Some(version) = version {
173+
return Err(MigrateError::Dirty(version));
174+
}
175+
176+
let applied_migrations = conn.list_applied_migrations().await?;
177+
validate_applied_migrations(&applied_migrations, self)?;
178+
179+
let applied_migrations: HashMap<_, _> = applied_migrations
180+
.into_iter()
181+
.map(|m| (m.version, m))
182+
.collect();
183+
184+
for migration in self
185+
.iter()
186+
.rev()
187+
.filter(|m| m.migration_type.is_down_migration())
188+
.filter(|m| applied_migrations.contains_key(&m.version))
189+
.filter(|m| m.version > target)
190+
{
191+
conn.apply(migration).await?;
192+
}
193+
194+
// unlock the migrator to allow other migrators to run
195+
// but do nothing as we already migrated
196+
conn.unlock().await?;
197+
198+
Ok(())
199+
}
140200
}

0 commit comments

Comments
 (0)