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

Allow running Golang based post migration steps #1253

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

guggero
Copy link

@guggero guggero commented Mar 28, 2025

This is an alternative approach to #932.
Often more complex data migrations cannot be fully expressed in SQL alone.

This PR introduces the ability to run a Golang based callback directly after a SQL based migration step was executed (and before the version is updated from dirty to clean in the migration table).

The new feature can be seen in action here: lightninglabs/taproot-assets@1b9a8cc

@coveralls
Copy link

coveralls commented Mar 28, 2025

Coverage Status

coverage: 56.417% (+0.1%) from 56.319%
when pulling 08353de on guggero:post-migrate-exec
into 604248c on golang-migrate:master.

Copy link

@bhandras bhandras left a comment

Choose a reason for hiding this comment

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

Nice and simple solution for in-code migrations!


// If there is a post execution function for
// this migration, run it now.
cb, ok := m.opts.postStepCallbacks[migr.Version]
Copy link

Choose a reason for hiding this comment

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

IIUC if the post step callback fails, then the version remains dirty. This could end up being a problem requiring down migration from the user. I wonder if it'd be better to just run the migration, the callback and a final SetVersion in a transaction?

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, I agree. But the thing is: The version is also dirty if the SQL based migration fails, as we also error out then.
And there doesn't seem to be the concept of DB transactions in the migration tool, my guess is because not every supported database backend can do transactions...

So not really sure what to do differently here.

Choose a reason for hiding this comment

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

IIUC if the post step callback fails, then the version remains dirty.

This appears to be aa quirk related to the way the library works:

migrate/FAQ.md

Lines 62 to 65 in 604248c

#### What does "dirty" database mean?
Before a migration runs, each database sets a dirty flag. Execution stops if a migration fails and the dirty state persists,
which prevents attempts to run more migrations on top of a failed migration. You need to manually fix the error
and then "force" the expected version.
.

In the context of our desired usage we typically catch situations like this via unit tests of the migration itself.

Choose a reason for hiding this comment

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

After reading a bit more of the codebase, I think it's possible to run the callback function in the same db transaction as the migration:

if m.config.NoTxWrap {
return m.executeQueryNoTx(query)
}
return m.executeQuery(query)
.

With the way the interfaces work, if we modify those (using something other than a func opt), then we'd need to update every single driver in the codebase.

Perhaps the slimmest change would be to add the functional opt to the Run method in the main Driver interface?

guggero added 2 commits April 9, 2025 16:39
This is a preparatory commit that adds the ability to add new
options to any of the constructor functions, without breaking backward
compatibility.
An actual option is going to be added in the next commit, this just
introduces the mechanism.
@guggero guggero force-pushed the post-migrate-exec branch from 0e91936 to 08353de Compare April 9, 2025 14:39
Copy link

@ffranr ffranr left a comment

Choose a reason for hiding this comment

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

Looks like a great addition to me! The post-migration step callback executes while the driver lock—held during the migration—is still in place. And if the callback fails, it results in the same failed state as a migration failure, so no change in behavior there.

Comment on lines +828 to +831
err := cb(migr, m.databaseDrv)
if err != nil {
return err
}
Copy link

Choose a reason for hiding this comment

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

I think you should wrap err in the return statement with a contextual message using fmt.Errorf for better clarity.

return fmt.Errorf("failed to execute post migration callback: %w", err)

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.

5 participants