Files
Robert Fairburn 68d9a37665 Cleanup an edge case sql error in migration cleanup (#48179)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved SQL generation for certain migration updates to avoid a
self-referencing subquery issue.
* Updated the generated query flow to select the target row first, then
apply the update more reliably.
* Aligned the version lookup query formatting with the revised condition
handling.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-24 12:20:37 -05:00
..

Migration cleanup

migration-cleanup repairs Fleet's MySQL migration status tables after a release candidate branch renumbers migration files that may already have run in an existing database.

The normal target is the RC branch that contains the final, renumbered migration filenames, such as rc-minor-fleet-v4.86.0 or rc-patch-fleet-v4.73.2. The tool compares that branch to main, finds migration file renames in server/datastore/mysql/migrations/tables and server/datastore/mysql/migrations/data, and generates SQL for the matching migration_status_tables or migration_status_data rows.

When to use it

Use this after a database has already run migrations from an RC build and later Fleet cannot complete startup or fleet prepare db because the same migrations were renumbered on the RC branch.

The tool only updates Fleet's migration status tables. It does not run, roll back, or modify migration files.

Before applying against a shared or production database:

  • take a database backup
  • run against the writer database
  • run --dry-run first
  • inspect the generated SQL

Build and help

Run these commands from the root of a fleetdm/fleet checkout. If you run the tool from somewhere else, pass -c /path/to/fleet so it can inspect the correct git history.

go run ./tools/migration-cleanup --help
go build -o build/migration-cleanup ./tools/migration-cleanup

Generate SQL

SQL generation is the default mode and does not connect to MySQL.

go run ./tools/migration-cleanup -b rc-minor-fleet-v4.86.0

Write the SQL to a file:

go run ./tools/migration-cleanup \
  -b rc-minor-fleet-v4.86.0 \
  -o migration-cleanup.sql

Use -c when running the tool from a different working tree:

go run ./tools/migration-cleanup \
  -c /path/to/fleet \
  -b rc-minor-fleet-v4.86.0

If the branch only exists on the remote, pass the plain branch name. The tool tries the local branch first, then origin/<branch>.

Dry run

Dry run connects to MySQL, loads the real rows from migration_status_tables and migration_status_data, simulates the generated SQL, and reports whether the final state would be valid. It does not execute the generated SQL.

go run ./tools/migration-cleanup \
  -b rc-minor-fleet-v4.86.0 \
  --dry-run \
  --db-host 127.0.0.1 \
  --db-user fleet \
  --db-password insecure \
  --db-name fleet

You can also use Fleet MySQL config flags or a Fleet config file:

go run ./tools/migration-cleanup \
  -b rc-minor-fleet-v4.86.0 \
  --dry-run \
  --config fleet.yml

Apply

Apply runs the same generated SQL in a transaction.

go run ./tools/migration-cleanup \
  -b rc-minor-fleet-v4.86.0 \
  --apply \
  --db-host 127.0.0.1 \
  --db-user fleet \
  --db-password insecure \
  --db-name fleet

--dry-run and --apply are mutually exclusive.

Example: rc-minor-fleet-v4.86.0

This was the move-up renumber case tested against a local MySQL database that had already run the pre-renumber 4.86 RC migrations. Running the latest RC binary against that database failed while rerunning a migration that had already been applied under its old version ID.

Generate SQL:

go run ./tools/migration-cleanup -b rc-minor-fleet-v4.86.0

Detected renumbers:

Found 11 migration renumber(s):
  [tables] 20260427134220 -> 20260522195224
  [tables] 20260428125634 -> 20260522195225
  [tables] 20260429180725 -> 20260522195226
  [tables] 20260430103635 -> 20260522195227
  [tables] 20260506132626 -> 20260522195229
  [tables] 20260506171058 -> 20260522195230
  [tables] 20260512143542 -> 20260522195231
  [tables] 20260512173249 -> 20260522195232
  [tables] 20260512173250 -> 20260522195233
  [tables] 20260518124441 -> 20260522195234
  [tables] 20260518150028 -> 20260522195235

The dry run for the broken local database reported the real duplicate row and the id shifts the generated SQL would perform:

migration_status_tables: duplicate version_id=20260522195224; would keep id=518, delete ids=[530]
migration_status_tables: would make space by shifting 0 row(s) after version_id=20260522195235 by +12
migration_status_tables: would shift 11 row(s) by +12
Dry-run: SQL will apply cleanly.

After --apply, rerunning the latest 4.86 RC Fleet migrations reported that migrations were already complete.

Example: rc-patch-fleet-v4.73.2

This was the move-down renumber style case used to verify the generated SQL for older patch RCs.

go run ./tools/migration-cleanup -b rc-patch-fleet-v4.73.2

Detected renumbers:

Found 2 migration renumber(s):
  [tables] 20250904115553 -> 20250816115553
  [tables] 20250918154557 -> 20250817154557

For this shape, the generated SQL remaps those version IDs, removes duplicate status rows if present, computes the id increment from the current table state, and shifts affected rows with ORDER BY id DESC. The generated "make space" update may affect zero rows; that is expected when there are no later rows to move.

Notes

  • --branch should point at the branch with the final migration filenames.
  • The generated SQL is the source of truth for SQL output, dry-run simulation, and apply mode.
  • Dry run validates against real table data, but apply should still be preceded by a database backup.
  • The tool is intentionally scoped to grouped migration renumbering on an RC branch. It is not a general-purpose migration status editor.