Skip to content
Merged
27 changes: 27 additions & 0 deletions .ai/wheels/troubleshooting/shared-dev-databases.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,33 @@ If SOME DB versions > target are orphans and SOME have local files, the
down branch runs as usual but emits a warning naming the orphans (they
get skipped by the existing loop because it iterates files only).

## Schema enrichment (Plan 3)

`wheels_migrator_versions` carries two extra columns added in 4.0.x:
`name VARCHAR(255) NULL` and `applied_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP`
(`TEXT` on SQLite — DEFAULT not supported on existing-table ADD COLUMN).
Added automatically on first migrator call via `$ensureTrackingColumns()`,
gated by `application[appKey].$trackingColumnsEnsured` cache so the ALTER
runs once per app process. Failure is non-fatal (legacy schema still works).

Populated by `$setVersionAsMigrated(version, migrationName)` when:
- The enriched columns flag is set, AND
- The caller passes a non-empty `migrationName`

Read by `$getOrphanVersionsWithMeta()` → array of `{version, name, appliedAt}`
structs. Falls back to bare-version structs when columns aren't ensured
or the SELECT fails (e.g. concurrent connection hasn't committed the ALTER).

Display via `$buildInfoOutput()` and `cli.cfm`'s `doctor` case render:
- `[x] <version> <name>` for applied local-file rows (timestamps are only
surfaced on orphan rows because `getAvailableMigrations()` doesn't
re-query the tracking table for applied_at on local files)
- `[?] <version> ********** NO FILE **********` for legacy NULL orphans
- `[?] <version> <name> (applied <timestamp>)` for enriched orphans

Existing rows (pre-enrichment) get NULL for both columns. Going-forward-only;
no backfill at bootstrap time.

## Reconciliation commands

Three CLI subcommands for manual reconciliation against the tracking
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ All historical references to "CFWheels" in this changelog have been preserved fo

- Three new `wheels migrate` subcommands for manual reconciliation against the tracking table — Flyway `validate` / `repair` / `SkipExecutingMigrations` analogues. `wheels migrate doctor` prints a single-command health report covering applied/pending/orphan versions plus a human-readable summary; pure read, never mutates. `wheels migrate forget <version> --yes` removes a single orphan row from `wheels_migrator_versions` (refuses if a matching local file exists — use `migrate down` for legitimate rollbacks — and refuses if the version isn't in the table). `wheels migrate pretend <version> --yes` records a version as applied without running its `up()` (refuses if already applied or if no local file matches, so future `down()` calls still work). Both `forget` and `pretend` require explicit `--yes` to mutate; without it they print what would happen and exit. Implementation lives in `Migrator.cfc::doctor()`, `forgetVersion()`, `pretendVersion()`. Covers the shared-dev-DB pattern surfaced in #2780 beyond what the orphan auto-detection in #2798 could resolve automatically.

### Changed

- `wheels_migrator_versions` gains two additive nullable columns: `name VARCHAR(255)` (the migration's human-readable name, e.g. `create_users`) and `applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP` (when the migration was applied). Added automatically on the first migrator call after upgrade via the new `Migrator.$ensureTrackingColumns()` helper — idempotent, gated by `application[appKey].$trackingColumnsEnsured` so the ALTER runs once per app process, and non-fatal (legacy schema continues to work if the ALTER fails). Newly applied migrations populate both columns; existing rows pre-dating the enrichment stay NULL. `wheels migrate info` and `wheels migrate doctor` now show `[?] <version> <name> (applied <timestamp>)` for orphan rows when the columns are populated, instead of just the literal `********** NO FILE **********` — letting you see *what* a peer applied and *when* even though the file isn't in your branch yet. SQLite skips the column DEFAULT (not supported on existing-table ADD COLUMN) and gets explicit timestamps from CFML on insert. Per-engine SQL covers MySQL, PostgreSQL, SQLite, MSSQL, Oracle, H2, and CockroachDB (#2780).

### Fixed

- `wheels migrate latest` no longer takes a misleading "down" branch and silently no-ops when `wheels_migrator_versions` records a version whose migration file isn't in the current checkout (shared dev DB / peer migration not yet pulled). `Migrator.migrateTo()` now diffs the tracking table against `app/migrator/migrations/` via the new `$getOrphanVersions()` helper and branches on orphan-at-top before the existing direction check — applying pending local migrations with a clear warning when all DB versions above target are orphans, emitting "Nothing to do" naming target vs current when none are pending, and warning + letting the existing down loop continue in mixed cases (orphan rows skip naturally because the loop iterates files). `wheels migrate info` also now shows orphan rows with `[?] <version> ********** NO FILE **********` (Rails-style) and a footer explaining the cause (#2780)
Expand Down
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -545,12 +545,14 @@ Specs extend `wheels.wheelstest.BrowserTest`. Install Playwright once: `wheels b
`wheels_migrator_versions` can drift from on-disk files when several developers share a single dev database (peer applied a migration whose file isn't yet in your branch). Detected and surfaced automatically; reconciliation is explicit:

- `wheels migrate latest` — when a peer's tracked version sits above your latest local file, it now applies pending local migrations with a warning instead of silently no-op'ing on a "down" branch.
- `wheels migrate info` — orphan rows render as `[?] <version> ********** NO FILE **********` (Rails-style) with a footer explaining the cause.
- `wheels migrate info` — orphan rows render as `[?] <version> <name> (applied <timestamp>)` when the enriched `wheels_migrator_versions.name` / `.applied_at` columns are populated, or `[?] <version> ********** NO FILE **********` (Rails-style) for legacy rows.
- `wheels migrate doctor` — single-command health report. Lists orphans + pending; pure read.
- `wheels migrate forget <version> --yes` — delete a stale tracking row (refuses if a matching local file exists, refuses if version not in table).
- `wheels migrate pretend <version> --yes` — record a version as applied without running `up()` (refuses if already applied or no matching file).

Both `forget` and `pretend` are dry-run by default; `--yes` is required to mutate. Helpers live on `Migrator.cfc`: `$getOrphanVersions()`, `doctor()`, `forgetVersion()`, `pretendVersion()`, `$buildInfoOutput()`. Deep reference: [.ai/wheels/troubleshooting/shared-dev-databases.md](.ai/wheels/troubleshooting/shared-dev-databases.md). User-facing guide: `web/sites/guides/src/content/docs/v4-0-0/basics/shared-development-databases.mdx`. Shipped in #2798 + #2799.
Tracking-table schema: `wheels_migrator_versions(version, core_level, name, applied_at)`. The `name` and `applied_at` columns are additive (NULL for legacy rows) and added automatically via `$ensureTrackingColumns()` on first migrator call after upgrade. Both columns are populated by `$setVersionAsMigrated(version, migrationName)` going forward; existing rows stay NULL and display version-only.

Both `forget` and `pretend` are dry-run by default; `--yes` is required to mutate. Helpers live on `Migrator.cfc`: `$getOrphanVersions()`, `$getOrphanVersionsWithMeta()`, `doctor()`, `forgetVersion()`, `pretendVersion()`, `$buildInfoOutput()`, `$ensureTrackingColumns()`. Deep reference: [.ai/wheels/troubleshooting/shared-dev-databases.md](.ai/wheels/troubleshooting/shared-dev-databases.md). User-facing guide: `web/sites/guides/src/content/docs/v4-0-0/basics/shared-development-databases.mdx`. Shipped across #2798, #2799, and the schema enrichment PR.

### Auto-Migration

Expand Down
Loading
Loading