feat: add --apply flag to execute migrations directly - #11
Merged
Conversation
Closes the gap where MigraDiff only ever generated SQL and left users to pipe it to psql themselves, with no reliable way to know whether migradiff_history reflected a migration that actually ran. --apply executes the generated migration against dburl_from (the database being migrated, matching the CLI's own help text and the README's psql dburl_from < migration.sql convention) inside a single transaction -- any failing statement rolls back everything, and nothing is recorded, so migradiff_history only ever reflects migrations that were confirmed applied. On success it auto-records history against dburl_from without needing a separate --record-history call. Rejected up front with --from-file (dburl_from would be an ephemeral throwaway database) and --promote (the chain's from/to direction isn't yet reconciled with which database --apply should execute against -- tracked as a follow-up in PROJECT_PLAN.md rather than guessed at here). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
--apply, which executes the generated migration againstdburl_from(the database being migrated) instead of only printing it — closing the gap called out in the README where MigraDiff only ever generated SQL and every user had to pipe it topsqlthemselves.dburl_from'smigradiff_historytable — no need to also pass--record-history. On failure, nothing is recorded and the command exits with code 4, somigradiff_historyonly ever reflects migrations that were confirmed applied (resolves the "was this actually applied?" ambiguity noted inhistory.py's design and the README's known limitation).--from-file(there's no live database to apply to — the schema files load into ephemeral throwaway databases) or--promote(the chain's from/to direction isn't yet reconciled with which database--applyshould execute against — tracked as a follow-up inPROJECT_PLAN.mdrather than guessed at here).--output jsongets a new"apply"object with the outcome (applied,target,statement_count,error,history_recorded,history_error).Design note: why
dburl_from, notdburl_targetTraced this precisely rather than guessing: the README's own basic-usage example (
psql postgres://db_production < migration.sql, wheredb_productionis the first positional arg) and the existingMigration.apply()method both already establish that generated SQL is meant to be applied todburl_from. Plain--record-history(unrelated, pre-existing behavior) records intodburl_targetinstead — that's left untouched; only--apply's own auto-recording targetsdburl_from, since that's the database that was actually changed. Documented clearly in the README andCLAUDE.mdso this doesn't cause confusion later.Test plan
tests/test_command_apply.py(11 tests, real Postgres, no mocks): happy path (table created + history recorded againstdburl_from, notdburl_target), no-op on empty diff, safe-mode blocking (destructive changes never execute without--force-destructive), JSON output shape, both incompatible-flag rejections, and a direct atomicity test against_apply_migration()that forces a mid-migration failure and asserts full rollback + zero history recorded🤖 Generated with Claude Code