feat!: Add support for rolling back migrations#583
Conversation
|
| Branch | elijah/migrations-rollback |
| Testbed | github-ubuntu-latest |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result microseconds (µs) (Result Δ%) | Upper Boundary microseconds (µs) (Limit %) |
|---|---|---|---|
| empty_router/empty_router | 📈 view plot 🚷 view threshold | 5,918.90 µs(-4.04%)Baseline: 6,168.10 µs | 7,754.14 µs (76.33%) |
| json_api/json_api | 📈 view plot 🚷 view threshold | 996.84 µs(-7.77%)Baseline: 1,080.84 µs | 1,322.26 µs (75.39%) |
| nested_routers/nested_routers | 📈 view plot 🚷 view threshold | 967.47 µs(-3.23%)Baseline: 999.76 µs | 1,202.64 µs (80.45%) |
| single_root_route/single_root_route | 📈 view plot 🚷 view threshold | 931.94 µs(-3.16%)Baseline: 962.36 µs | 1,165.95 µs (79.93%) |
| single_root_route_burst/single_root_route_burst | 📈 view plot 🚷 view threshold | 16,320.00 µs(-7.66%)Baseline: 17,674.11 µs | 21,077.68 µs (77.43%) |
Codecov Report❌ Patch coverage is
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
m4tx
left a comment
There was a problem hiding this comment.
Looks like a solid start, but we need more work on this before it can be merged.
| // name. | ||
| // TODO: cli command should take an explicit crate name as arg when workspaces | ||
| // are supported. | ||
| let crate_name = bootstrapper.project().cli_metadata().name; |
There was a problem hiding this comment.
I think we can still potentially want to rollback other libraries' migrations, so I guess we should still support other crates here. For instance, in the admin example we have on the repo, we should still support rolling back cot's m_0001_initial that creates cot__database_user. This is perhaps not terribly useful on its own, but if someone uses libraries that define some more complex migrations, that's more useful. And I believe should be fairly simple to implement, too.
Also, another problem with this in the current form is that we can have multiple migrations called m_0001_initial and it's inclear which one will be chosen.
| .with_apps() | ||
| .with_database() | ||
| .await? | ||
| .with_cache() |
There was a problem hiding this comment.
We should be able to skip .with_cache() in the chain here and call boot() directly.
| } | ||
| } | ||
|
|
||
| struct MigrationRollback; |
There was a problem hiding this comment.
I believe a few features would make it much more useful:
- A subcommand that would list all available migrations
- A dry run mode to see which migrations would actually be rolled back
- Actual log lines being displayed.
cot-clijusteprintlns the info when executingmigration make, which I think we could replicate here.
| bootstrapper: Bootstrapper<WithConfig>, | ||
| ) -> Result<()> { | ||
| let file = matches | ||
| .get_one::<String>("file") |
There was a problem hiding this comment.
Shouldn't this be called "migration name"? I know this is usually the same as file name, but doesn't necessarily have to be.
| /// database or if there is an error while generating the migration | ||
| /// graph or if there is an error while unapplying a migration. | ||
| pub async fn rollback(&self, database: &Database, file: &str, app_name: &str) -> Result<()> { | ||
| info!("Rolling back migrations"); |
There was a problem hiding this comment.
Please add a TODO to add transactions here. And I should finally finish the work on #468...
Sadly, the lack of transactions means that we can potentially leave the database in an invalid state if one of the rollback fails. I'm wondering if the transactions would fix this issue, or are there any database engines that we support that don't support transactions on schema-changing operations? (SQLite could be one, but I'm not sure about the status in 2026)
|
|
||
| let mut migration_group = | ||
| CliTaskGroup::new(MIGRATION_GROUP_SUBCOMMAND).about("Database migration commands"); | ||
| migration_group.add_task(MigrationRollback); |
There was a problem hiding this comment.
The struct and its registration should be gated behind #[cfg(feature = "db)].
| operation.backwards(database).await?; | ||
| } | ||
|
|
||
| Self::mark_migration_unapplied(database, migration).await?; |
There was a problem hiding this comment.
Seems like we can rollback already rolled back migrations - this shouldn't be possible.
Related issue or discussion
fixes #547
Description
This allows rolling back migrations to the specified migration file.
Assuming the project has the following migration files in its migration dir:
The above command will roll back(unapply) the
m_0002_secondfile, leavingm_0001_initialapplied.Alternatively, the migration file number can be used for convenience:
Migrations are currently tied to a crate(the crate name is used), which means rolling back a migration in a project that imports apps from external crates will implicitly (and only) rollback migrations in the current project/crate. This might change when support for workspaces is added
As a bonus, this PR also adds CLI Group tasks as a means to group nested related subcommands under a parent subcommand:
Eg.
Type of change