Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --strict flag #441

Merged
merged 20 commits into from Nov 14, 2023
Merged

Add --strict flag #441

merged 20 commits into from Nov 14, 2023

Conversation

philip-hartmann
Copy link
Contributor

@philip-hartmann philip-hartmann commented May 4, 2023

This adds the --strict-order flag which ignores all out of order pending migrations. With the flag set dbmate up and dbmate migrate only apply pending migrations which succeed the latest applied migration - in other words, only pending migrations with a higher version number than the latest applied migration will be used. dbmate --strict-order status also only lists in order pending migrations. Rollbacks work as usual.

Refs: #159

@gregwebs
Copy link

gregwebs commented May 5, 2023

This code change looks good to me. From a UX perspective I find --strict-order a confusing name. Something like --new-only would make more sense to me.

It's also wort noting that my PR #438 would allow you to select a range of migrations, such as the range that doesn't include some older migrations- so a slightly more manual version of this. For me it seems like it would work because this use case wouldn't come up a lot.

@philip-hartmann
Copy link
Contributor Author

@amacneil proposed the name --strict-order for the flag in #159, so I went with it. The flag comes in handy as a permanent flag you would choose to make your migration strategy more strict, to avoid migration conflicts mentioned in #159 and kevlarr/jrny/issues/17.

I also do not see --strict-order in conflict with #438, as they both tackle different use cases.

@gregwebs
Copy link

gregwebs commented May 7, 2023

Definitely not in conflict. We don't have to bikeshed about the name- it's fine as is- but just thought I would throw a perspective out there.

@amacneil
Copy link
Owner

My linked comment was 3 years ago, but I think probably just --strict is even better for the flag name? It seems to be in the spirit of "be strict about applying migrations" in order, and fail if you can't".

I think this should be a flag on the verb, rather than a global flag. It only applies to the up/migrate command.

I'll let others do code review @gregwebs @dossy

Limits the ´--strict-order´ flag to the up, migrate and status verb.
main.go Outdated
@@ -164,6 +176,11 @@ func NewApp() *cli.App {
Name: "status",
Usage: "List applied and pending migrations",
Flags: []cli.Flag{
&cli.BoolFlag{
Copy link

@gregwebs gregwebs May 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a precedent for duplication with the verbose flag, but it seems like it would be better to define the flag just once

strictFlag := cli.BoolFlag{...}
...
flags: []cli.Flag{&strictFlag}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could really tidy up the duplicate flag usage. I was already a little annoyed writing that part but I tried to do it similar to the verbose flag. I will change it 👍

@gregwebs
Copy link

I think there would be some benefit to instead of just ignoring migrations to have another state in the UI to show that migrations were cutoff due to the strict setting. Users may set this as an env variable and then get confused as to why migrations aren't showing up.

That being said, users that set this will probably be more advanced and I don't have an answer as to what exactly to show users (just that there is a strict cutoff, or a different kind of marking instead of applied/unapplied).
As is this makes for a simple implementation and UI improvements could still be made with future changes.

Copy link
Collaborator

@dossy dossy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good to me.

For the record, I would never expect something called "strict" to ever ignore anything. "--ignore-outdated" may be more verbose but I would feel more confident guessing exactly how the expected behavior of that option would modify execution, vs. "strict" which I would expect would result in a fatal error if there were unapplied migrations or anything else that would not pass a "strict" test.

But, if the consensus is that "strict" is unambiguous and the implemented behavior matches the expectation of others aside from me, then I'm okay with it.

pkg/dbmate/db.go Outdated Show resolved Hide resolved
@philip-hartmann
Copy link
Contributor Author

The more I think about the flag name the more I am bothered by it. @gregwebs and @dossy raise good points about the unambiguousness of the flag, both on the user level and in the code itself too. A more verbose name would definitely not hurt and would make a condition like if db.Strict much more expressive. Also, could there be a case that strict will be useful for something else in the future or is strict in the context of dbmate unambiguous with applying migrations in order?

@amacneil
Copy link
Owner

Actually, I didn't fully comprehend the PR description, but I was expecting this behavior too:

"strict" which I would expect would result in a fatal error if there were unapplied migrations or anything else that would not pass a "strict" test.

Would that be a better solution, rather than simply ignoring out of order migrations? If any migrations have a lower version number than the current "latest" version, then throw an error and exit 1?

Combined with something like #434 the user could then resolve the issue by manually applying migrations.

@esceer
Copy link

esceer commented Jul 17, 2023

Is this strict feature planned to be merged anytime soon? We are eager to use it once available.

@x80486
Copy link

x80486 commented Jul 25, 2023

I think it would be great if when this is merged, a fix command could be implemented so the migrations that are borked can be renamed correctly and "align" them automatically.

@philip-hartmann
Copy link
Contributor Author

Would that be a better solution, rather than simply ignoring out of order migrations? If any migrations have a lower version number than the current "latest" version, then throw an error and exit 1?

Sounds reasonable. I've made the appropriate changes. Strict mode throws if a pending migration is not strictly in numerical order with already applied migrations.

Migrations with the same version number are not yet properly checked though. Dbmate only saves the version number of applied migrations and not the full filename. Therefore we cannot properly compare pending migrations with already applied migrations.

@dossy
Copy link
Collaborator

dossy commented Jul 30, 2023

Migrations with the same version number are not yet properly checked though. Dbmate only saves the version number of applied migrations and not the full filename. Therefore we cannot properly compare pending migrations with already applied migrations.

In my opinion, version numbers should be unique, and two files with the same version number should raise an error and halt.

This change would break backwards-compatibility, unfortunately.

What do others think?

@amacneil
Copy link
Owner

amacneil commented Aug 1, 2023

In my opinion, version numbers should be unique, and two files with the same version number should raise an error and halt.
This change would break backwards-compatibility, unfortunately.

I think two files with the same version number should be considered an error always. We could introduce it behind a different flag and enable it by default in the next major version bump. However, I think this is an unrelated problem to the current PR.

@amacneil amacneil changed the title Add --strict-order flag Add --strict flag Aug 1, 2023
main.go Outdated Show resolved Hide resolved
pkg/dbmate/db.go Outdated
@@ -441,6 +453,10 @@ func (db *DB) FindMigrations() ([]Migration, error) {
migration.Applied = true
}

if db.Strict && !migration.Applied && migration.Version < latestAppliedMigration {
return nil, fmt.Errorf("cannot apply migration `%s` after `%s` in --strict mode, migrations have to be in numerical order", migration.Version, latestAppliedMigration)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to my other comment, I think this error message could be a bit more helpful. we can mirror whatever words are selected for the flag description.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the error message to the following:

migration `%s` is out of order with already applied migrations, the version number has to be higher than the applied migration `%s` in --strict mode

@amacneil
Copy link
Owner

amacneil commented Aug 1, 2023

I'm 👍 on the current functionality of this PR, after we resolve my comment about the words used to describe the feature and error message.

pkg/dbmate/db.go Outdated
@@ -441,6 +453,10 @@ func (db *DB) FindMigrations() ([]Migration, error) {
migration.Applied = true
}

if db.Strict && !migration.Applied && migration.Version < latestAppliedMigration {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the right place for this check to live. FindMigrations() should not fail regardless of what migrations have or haven't been applied.

The check should be implemented in the Migrate() function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without an additional db query the check needs access to a list of applied migrations to find the applied migration with the highest version number. FindMigrations() already queries those to check if a migration is already applied. There are several ways to do the check outside of FindMigrations() but I did not want to make too many unrelated changes which might be in conflict with other PRs, e.g. changing the return values of FindMigrations() would be in conflict with PR #438.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put the strict version check in Migrate() and applied some small changes. FindMigrations() already returns a sorted list of migrations so we only need to check the first pendig migration with the highest version number from all applied migrations.

@philip-hartmann
Copy link
Contributor Author

@amacneil my last changes are from 3 months ago without any feedback/review so far, is there anything else open about this PR?

pkg/dbmate/db.go Outdated Show resolved Hide resolved
pkg/dbmate/db.go Outdated Show resolved Hide resolved
Copy link
Collaborator

@dossy dossy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks good to me now, the only thing left is to add a few new test cases to pkg/dbmate/db_test.go:

  • while strict=false, allows applying migration out of order, the expected behavior before this change was introduced
  • while strict=true, allows applying migration in order when no previous migrations have been applied yet
  • while strict=true, allows applying migration in order when at least one previous migration has been applied and the new migration is in the correct order
  • while strict=true, raises an error when trying to apply a migration out of order

If you need help implementing the tests, let me know and I can help.

@philip-hartmann
Copy link
Contributor Author

@dossy I've added 2 tests which should cover the 4 cases. Can you please check them?

Copy link
Collaborator

@dossy dossy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code reviewed, tests passing: unless someone objects, I think this is good to merge now.

@dossy
Copy link
Collaborator

dossy commented Nov 10, 2023

@amacneil If there's something still unresolved that you'd like to see changed in this PR, could you add a note here letting us know? Otherwise, I'd like to clear your change requested bit on this PR and merge it.

@dossy dossy dismissed amacneil’s stale review November 14, 2023 04:33

Concerns have been addressed, PR is otherwise ready to merge.

@dossy dossy merged commit 1658a46 into amacneil:main Nov 14, 2023
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants