test: hold the upgrade path to being repeatable - #823
Merged
Conversation
Two guards over the way #822 stopped being repeatable, both of which fail today if the thing they describe is undone. A migration that is refused part way has to leave the operator something to run again. `40024210101.sql` gave CustomFieldData its natural primary key as two statements; DDL commits as it goes, so the drop stood alone when the key was refused on a duplicate, and every retry then died on `Can't DROP COLUMN id` before reaching the statement that had failed. Each version file must now be a single statement, or wholly inside one transaction with no DDL in it — DDL would commit and end the transaction under the statements after it — or written so that running it twice is harmless. Both files satisfy it, one each way. `Upgrade::getTargetUpgradeHandlers()` yields a handler's versions in the order `getAttributes()` returns them, which is the order they are written in the class. Nothing sorts them. They ascend today, so the schema change runs before the data migration that needs it; a version added at the top of the list would run first. Out-of-order migrations are not something an installation reports. The messages say what to do about it rather than only what is wrong, because whoever sees them is adding a migration and has a choice of three ways to satisfy the rule. CLAUDE.md records the pattern these two guard, which covers half of the recent fixes: a guard that is not where the change happens — a limit tested against a row already read, a counter written back from a stale read, a hash stored outside the transaction holding what it describes, two commits made out of one logical change. Where the codebase gets it right the guard and the change are one statement. The suite counts there are refreshed to match.
blaipr
force-pushed
the
test/the-upgrade-path-cannot-regress
branch
from
August 18, 2026 21:33
1d240f3 to
d2f05f5
Compare
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.
Two guards over the upgrade path, each holding a rule that #822 had just been broken by. Both fail today if the thing they describe is undone.
A migration that is refused part way must be runnable again
40024210101.sqlgaveCustomFieldDataits natural primary key as two statements. DDL commits as it goes, so the drop stood alone when the key was refused on a duplicate, and every retry then died onCan't DROP COLUMN idbefore reaching the statement that had actually failed — with the database version unchanged, becauseUpgradeDatabase::apply()writes it only once every statement has succeeded.Each version file must now be one of:
IF NOT EXISTS,IF EXISTS).Both current files satisfy it, one each way:
40024210101.sqlis a singleALTER, and40024240101.sqlis 31 DML statements betweenstart transactionandcommit.dbstructure.sqlis excluded, with the reason: it builds a database rather than upgrading one, so nothing has been applied when it runs and a failure leaves nothing half-done.Versions are applied in the order they are written
Upgrade::getTargetUpgradeHandlers()yields a handler's versions in the ordergetAttributes()returns them — source order. Nothing sorts them.They ascend today, so
UpgradeDatabaseruns the schema change before the row migration that depends on it. A version added at the top of the attribute list would run first, and out-of-order migrations are not something an installation reports; they are something it survives or does not. A second case refuses a handler that claims the same version twice, which would apply it twice.On the messages
They say what to do rather than only what is wrong, because whoever trips them is in the middle of adding a migration and has three ways to satisfy the rule. The DDL failure prints the offending statement.
Verification
Splitting the migration back into two statements fails the first guard; swapping the two
UpgradeVersionattributes fails the second. 3973 unit tests pass.This adds one unit-test file and touches no
srcand no schema, so the integration suite cannot be affected by it — CI covers it rather than a local run that could not have told us anything.