feat(database): add "database mysql upgrade" command#2005
Draft
martin-helmich wants to merge 2 commits into
Draft
feat(database): add "database mysql upgrade" command#2005martin-helmich wants to merge 2 commits into
martin-helmich wants to merge 2 commits into
Conversation
Adds a command to upgrade a MySQL database to a newer version, backed by
the PATCH /v2/mysql-databases/{id} endpoint, which accepts a version field.
Upgrade candidates are determined from the mysql-versions endpoint, keeping
only enabled versions newer than the one the database currently runs; MySQL
does not support downgrades. Versions are compared numerically via semver
coercion, since the "<major>.<minor>" format is not valid semver and would
otherwise sort lexically (e.g. "10.11" < "8.0").
The target version can be given explicitly, set to "latest", or selected
interactively. Upgrades are confirmed unless --force is passed, and --wait
polls until the database reports the target version and is ready again.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The --wait flag returned immediately, leaving the database at status "pending". Patching the version updates the database's `version` field right away, as it holds the desired version rather than the running one, and the status still reads "ready" from before the upgrade for a moment after patching. The first poll therefore observed the target version in a "ready" database and considered the upgrade done before it had started. Capture `statusSetAt` before patching and require it to have changed, which proves the database has changed state at least once since. Both timestamps originate from the API, so comparing them is not affected by client clock skew. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
martin-helmich
added a commit
that referenced
this pull request
Jul 15, 2026
) `database mysql list` displayed `pending` for a database that the API reported as `migrating`. ## Cause The status column did not use the `status` field returned by the API. It derived the value from the `isReady` boolean instead: ```ts get: (row) => { if (!row.isReady) { return "pending"; } return "ready"; }, ``` `DatabaseDatabaseStatus` has five values — `pending`, `ready`, `migrating`, `importing` and `error` — so this collapsed everything that was not ready into `pending`. Besides `migrating`, this also affected `error`: a failed database was indistinguishable from one that is still being worked on. The same code existed in `database list`, which had a second problem. `DatabaseRedisDatabase` has no `isReady` field, so Redis rows were hardcoded to `isReady: true` and always displayed as `ready`, whatever their actual state. ## Fix Both MySQL and Redis databases report `status`, so the column now uses it directly and the Redis `isReady` hardcode is gone. Since the column key already matches the field, the custom getter is no longer needed. Verified against a mock serving all five states; each is now reported verbatim, and a pending Redis database no longer shows as ready. Found while testing #2005, where an upgrade puts the database into `migrating`, but this is an independent pre-existing bug, hence the separate PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <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.
Adds a
database mysql upgradecommand for upgrading a MySQL database to a newer version.Background
The API has no dedicated upgrade operation.
PATCH /v2/mysql-databases/{id}accepts an optionalversionfield alongsidedescriptionandcharacterSettings, and that is what this command drives.Behaviour
GET /v2/mysql-versions, filtered to enabled versions newer than the database's current one. MySQL does not support downgrades, so older versions are never offered.<major>.<minor>format is not valid semver, and a lexical sort would place10.11before8.0.--versiontakes an explicit version orlatest; if omitted, the version is prompted for interactively, followingapp upgrade.--versionfails with the list of valid targets rather than sending a doomed request.--forceis passed, since they are disruptive.--waitpolls until the upgrade has actually completed (see below).Notes for reviewers
The interesting part is
--wait. Two API behaviours make the obvious implementation wrong, both found by testing against a real database:versionholds the desired version and is updated as soon as the patch is accepted, so it does not indicate that the upgrade finished.statusstill readsreadyfrom before the upgrade for a moment after patching.Together these mean a naive "target version and
ready" check returns before the upgrade even starts, leaving the database atpending. UnlikeAppAppInstallation,DatabaseMySqlDatabasehas no current/desired pair, so thecurrent == desiredapproach used bywaitUntilAppStateHasNormalizedis not available here.Instead,
statusSetAtis captured before patching and required to have changed beforereadyis trusted, proving the database changed state at least once since. Both timestamps come from the API, so the comparison is immune to client clock skew. This isisUpgradeComplete()inlib/resources/database/mysql/upgrade.ts, covered by unit tests including the stale-readyregression.The existing command tests in this area are all
it.skip'd ("to be fixed later"), so rather than add another skipped test, the version-selection and completion logic are extracted intolib/resources/database/mysql/and covered by real unit tests.🤖 Generated with Claude Code