Migrations: rollback, reset, refresh, status, db:wipe, pretend, locking and transactional runs - #347
Open
techmahedy wants to merge 1 commit into
Open
techmahedy wants to merge 1 commit into
techmahedy wants to merge 1 commit into
Conversation
…ng and transactional runs
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.
Pull Request Checklist
Summary
The framework could only run migrations forward (
migrate) or drop everything (migrate:fresh). A mistake in one migration meant wiping the database. This PR adds the missing commands and makes the migrator safer to run in production.Bug fix
MigrationRepository::log()computedMAX(batch) + 1for every migration, so a run of five migrations got five different batch numbers. Batch-based rollback would therefore undo one migration at a time. The batch is now computed once perrun();--stepopts into one batch per migration.Databases that already ran migrations keep their old numbering, so rollback still goes one migration at a time for those until new migrations run.
New commands
migrate:rollback--step=Nmigrations, or one--batch=N. Resolves every file first, so a missing file aborts before anything is reverted.migrate:resetmigrate:refresh--step=N) and re-runs. Optional--seed.migrate:status--pendingexits 1 when anything is pending (for CI);--jsonfor machine-readable output.db:wipeChanges to existing commands
migrate: new--step,--pretend,--force,--seed. Each migration printsname ..... 12ms DONE.migrate:fresh: new--seed,--force. The confirmation now uses the console question helper instead of readingSTDINdirectly. The class was renamed fromMigrateRefreshCommandtoMigrateFreshCommand(command name unchanged) so the newmigrate:refreshcan use that name.Behaviour
--pretendprints the SQL a migration or rollback would run and changes nothing, including themigrationstable. It captures statements that go throughDatabase::execute()(allSchemacalls andDB::execute()). Statements issued throughDB::statement()are not captured.migrationsrow) runs in a transaction, so a failure leaves no half-applied schema. MySQL commits DDL implicitly, so it runs without one. A migration can opt out withpublic bool $withinTransaction = false;(e.g.CREATE INDEX CONCURRENTLY).GET_LOCK) and PostgreSQL (pg_try_advisory_lock) take a lock for the whole run/rollback, so two deploys cannot migrate at once; the second fails with a clear error. SQLite is not locked.migrationstable now also storeschecksum,execution_time(ms) andran_at.migrate:statusmarks a migration "modified" when its file changed after it ran (line endings are ignored) and "missing" when the file is gone. Existingmigrationstables get the three nullable columns added automatically on the next migrate; old rows have no checksum and are never flagged.fresh,refresh,resetandwipealways ask for confirmation; the other commands ask only in production. Without a terminal they refuse unless--forceis passed.Migration <file> failed: <message>with the original exception asprevious.Compatibility
Migrator::run()gained an optional thirdarray $optionsargument; existing calls are unaffected.Migration::$withinTransaction(defaulttrue). Behaviour change: migrations on PostgreSQL/SQLite now run in a transaction. Anything that cannot run inside one (including toggling SQLitePRAGMA foreign_keys) must set it tofalse.Migrator::rollback(),reset(),status(),getPendingMigrations();MigrationRepository::getRecords(),getRollbackCandidates(),delete(),upgrade();Database::pretend(),isPretending().Files
Database/Migration/Migrator.php,MigrationRepository.php,Migration.php, andDatabase/Database.php(pretend capture).Console/Commands/Migrations/:MigrateCommand,MigrateFreshCommand(renamed), and newMigrateRollbackCommand,MigrateResetCommand,MigrateRefreshCommand,MigrateStatusCommand,DbWipeCommand.Console/Support/InteractsWithMigrations.php: shared confirmation, progress output and seeding.Testing
MigratorTest(16 tests, real SQLite file and real migration files): shared/step batches, rollback by batch/step/last batch, reset, missing file aborts untouched, pretend leaves nothing behind, failed migration leaves no partial table, opt-out of the transaction, status flags modified/missing, legacymigrationstable upgraded in place.MigratorPgsqlTest: the same 16 tests on PostgreSQL (opt-in viaDOPPAR_TEST_PGSQL_HOST/DOPPAR_TEST_PGSQL_DATABASE; it drops every table in that database).MigrateCommandsTest(11 tests): output, exit codes, option validation,--forceand production guard,--pending,--json.--seedwas only checked to invokedb:seed.Checklist