Skip to content

Conversation

@ericklaus-wf
Copy link
Owner

@ericklaus-wf ericklaus-wf commented Oct 24, 2016

Fixes bad behavior when some migrations in the db table are unknown to the current sql-migrate instance. That might happen when one branch applies some migrations and a branch lacking those migrations tries to add some of its own.

Fixed behaviors:

  • If most recent migration is unknown, migrate up reports "nothing to do"
    • Migrations are now applied
  • If any migrations are unknown, sql-migrate status panics
    • A log warning is now emitted indicating which migrations are unknown

Some weirdness remains:

  • If the latest migration is unknown sql-migrate redo reports "nothing to do"
  • I'd rather that sql-migrate status include the fact of unknown statuses in the table it prints, rather than as separate warnings. I want to get some UX feedback on how that might look. Currently, the printout looks like:
Migration in database "20160901151959_def-1.sql" unknown to sql-migrate; it will not be possible to migrate down this migration
Migration in database "20160921154454_def-2.sql" unknown to sql-migrate; it will not be possible to migrate down this migration
+-----------------------------------------------+-------------------------------+
|                   MIGRATION                   |            APPLIED            |
+-----------------------------------------------+-------------------------------+
| 00000000000000_initial.sql         | 1970-01-01 00:00:00 +0000 UTC |
| 20160406182900_abc.sql             | 2016-04-12 20:24:51 +0000 UTC |
| 20160414135200_def.sql               | 2016-10-12 20:54:49 +0000 UTC |
| 20161021194400_hig.sql                | no                            |
+-----------------------------------------------+-------------------------------+

@ericklaus-wf
Copy link
Owner Author

Perhaps we could add a column to the sql-migrate status table (probably before migration) that includes a status code. Maybe "!" for "unknown" and "a" for "not applied"?

migrate.go Outdated
if migrations[index].Id == current {
// Searches the arrays for the latest elements with common ids.
// Returns the indexes of the common id.
func lastCommon(x []*Migration, y []*Migration) (int, int) {
Copy link
Owner Author

Choose a reason for hiding this comment

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

Rename x and y to left and right to avoid unreadable array accesses like x[i] and y[j].

return []*Migration{}
}
migrationsMap := map[string]*Migration{}
for _, m := range existingMigrations {
Copy link
Owner Author

Choose a reason for hiding this comment

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

Add note about the order of setting migrationsMap. It's important that we fill migrationsMap from existingMigrations first, then from migrations. migrations is the only array with Up and Down set, and we want those values if possible.

}

// Filter a slice of migrations into ones that should be applied.
func ToApply(migrations, existingMigrations []*Migration, direction MigrationDirection) []*Migration {
Copy link
Owner Author

Choose a reason for hiding this comment

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

This is a breaking change to a public method. It doesn't feel like the kind of method that library clients would actually use. However, when PRing upstream, I'll offer to rewrite this as ToApplySafe or something similar and avoid breaking the signature of ToApply().

@ericklaus-wf ericklaus-wf changed the title Handle unknown Handle unknown migrations Oct 25, 2016
migrate.go Outdated
// Figure out which migrations to apply
toApply := ToApply(migrations, record.Id, dir)
toApply := ToApply(migrations, existingMigrations, dir)
if err != nil {

Choose a reason for hiding this comment

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

I'm confused by this err check.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Oops. At one point, I made ToApply() return an error, but changed my mind. I never took out the error check.

migrate.go Outdated
}
}
if xIndexMatch == -1 {
return -1, -1 // We never found a match; the arrays have nothing in common

Choose a reason for hiding this comment

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

Could use const none = -1 if you wanted

migrate.go Outdated
}

func toApplyDown(migrations, existingMigrations []*Migration) []*Migration {
if len(existingMigrations) == -1 {

Choose a reason for hiding this comment

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

Should this be == 0?

migrate.go Outdated
}
// When a Migration appears in both existingMigrations and migrations,
// we want the Migration from migrations, since only that list has
// the Up and Down fields set.

Choose a reason for hiding this comment

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

Would this comment make more sense if it were on the second loop, which does the overwriting you describe?

@tomdeering-wf
Copy link

tomdeering-wf commented Oct 25, 2016

Otherwise looks good. Thanks for taking care of this!

migrate.go Outdated
if len(existingMigrations) == 0 {
return migrations
}
_, index := lastCommon(existingMigrations, migrations)

Choose a reason for hiding this comment

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

If I didn't know what bug this was fixing, I'd wonder why it is being done. Could you add some comments about it?

@johnlockwood-wf
Copy link

johnlockwood-wf commented Oct 25, 2016

Can there be an unknown, unapplied migration? such as when you sql-migrate status, does that add an entry into the migrations table?

@johnlockwood-wf
Copy link

Going ahead and applying a new migration on top of a database that has an unknown, unapplied migration seems safe, but if the unknown migration was applied, that seems like it could leave the db in a state incompatible with the new migration.

@ericklaus-wf
Copy link
Owner Author

@johnlockwood-wf Are you saying, if the database has had an unknown migration applied, when we apply our known migration, we may leave the db in an incompatible state?

@johnlockwood-wf
Copy link

johnlockwood-wf commented Oct 26, 2016

@ericklaus-wf an applied, unknown migration may change the db into a state incompatible with the new, known migration.

@johnlockwood-wf
Copy link

the unknown migration applied may make the db incompatible with a new known migration

On Oct 26, 2016, at 6:49 AM, ericklaus-wf notifications@github.com wrote:

@johnlockwood-wf Are you saying, if the database has had an unknown migration applied, when we apply our known migration, we may leave the db in an incompatible state?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

@ericklaus-wf
Copy link
Owner Author

That's true, but the problem already exists. Consider this: a database has migrations 1 and 2 applied and these migrations are the master branch. Branch A adds migration 3. Branch B adds migration 4. Branch B is merged to master and deployed. Migration 4 is applied. The database is now in state "1, 2, 4". Branch A is merged to master and deployed. Migration 3 is applied. The database is now in state "1, 2, 3, 4".

This change does expand the area where this can happen though. It will now be possible for branch A to be deployed without merging to master and have migration 3 applied against the database.

@johnlockwood-wf
Copy link

johnlockwood-wf commented Oct 27, 2016

If I was going to PR this back to the main fork, I might add a flag for the new behavior and without the flag, an unknown migration would cause the program to exit with a non-0 code. I'd consider an interactive mode, where the user was prompted for a choice of whether to go ahead with applying a migration after an unknown had been applied.

@ericklaus-wf
Copy link
Owner Author

ericklaus-wf commented Oct 27, 2016

To me, this is behavior that I expect, so I'd be annoyed by having this behind a flag. But I think I see your point.

It would be a little complicated to put this behind a flag. sql-migrate is both a library and a command line tool. Propagating the flag through would be a bit messy but possible.

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.

4 participants