Skip to content

fix(migrations): unblock the runner and make 0049 apply on MySQL - #1705

Merged
BHUVANSH855 merged 1 commit into
AnthropicBots:mainfrom
MOHITKOURAV01:fix/1700-migration-version-collision
Aug 29, 2026
Merged

fix(migrations): unblock the runner and make 0049 apply on MySQL#1705
BHUVANSH855 merged 1 commit into
AnthropicBots:mainfrom
MOHITKOURAV01:fix/1700-migration-version-collision

Conversation

@MOHITKOURAV01

@MOHITKOURAV01 MOHITKOURAV01 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Closes #1700

What was wrong

Three migrations claimed version 0049. loadMigrations throws while building the file list, before it compares anything against schema_migrations, so the failure is total:

$ cd backend && npm run migrate:status
MigrationError: Duplicate migration version 0049: 0049_coupons_schema.sql and
0049_fraud_monitoring_queue.sql. Renumber one of them so the order is unambiguous.

Not the three files — the whole directory. No schema change could be applied to any environment. migrations/README.md names this hazard by name ("a collision takes the whole sequence down and not just the two files involved") and the three still merged, because each looked fine in isolation and in review. Nothing in CI runs the runner, so main stayed green.

The renumber

By merge order, which is the only ordering that does not rewrite history someone might have applied:

was now first appeared
0049_coupons_schema.sql 0049_coupons_schema.sql 2026-08-25 08:23
0049_product_search_fulltext.sql 0050_product_search_fulltext.sql 2026-08-25 17:35
0049_fraud_monitoring_queue.sql 0051_fraud_monitoring_queue.sql 2026-08-25 23:02

Renumbering is safe here precisely because of the collision: the runner refused the directory before it read schema_migrations, so none of the three has ever been applied anywhere. Environments are migrated to 0048.

0049 needed more than a number

While writing the guard I found the coupons migration could not have worked even alone. It was:

CREATE TABLE IF NOT EXISTS coupons ( ... );
ALTER TABLE coupons ADD COLUMN IF NOT EXISTS expires_at DATETIME NULL;

The CREATE TABLE was a no-op. coupons is already declared in 0001_baseline_schema.sql, and the baseline runs first on every database, fresh or adopted. CREATE TABLE IF NOT EXISTS against an existing table is skipped silently, so the migration would have recorded itself as applied having changed nothing. This is the exact case the README warns about:

A table has exactly one owning migration. Later files amend it with ALTER TABLE. A second CREATE TABLE IF NOT EXISTS for a table that already exists is skipped silently, which is how the schema came to depend on apply order in the first place.

The ALTER was invalid. ADD COLUMN IF NOT EXISTS is a MariaDB extension; MySQL 8 answers ERROR 1064. Sitting after the CREATE TABLE, it would have aborted the migration partway through — table present, version unrecorded — and the next run would then hit the "never edit an applied migration" checksum rule on the way past.

So the file is now the ALTERs it should always have been, against the baseline's table:

  • ADD COLUMN expires_at DATETIME NULLvalidateCoupon reads coupon.expires_at || coupon.end_date || coupon.expiry_date. The baseline's end_date is NOT NULL, so without this a coupon that never expires cannot be expressed: every row has to name a date it stops working.
  • MODIFY COLUMN type ENUM('percentage', 'percent', 'fixed', 'free_shipping')'percent' is accepted as a synonym for 'percentage' by both validateCoupon and pricing.service.js, and the admin coupon form submits it, so the column has to hold it. All three original members are kept: dropping one rewrites every row using it to ''.
  • An index on expires_at, covering the expiry check the new column introduces.

Plain ALTERs, not guarded ones — the runner applies each migration exactly once and records it, so nothing here needs to be idempotent.

Guard

backend/tests/migrationSequence.test.js, 65 cases:

  • No duplicate versions, and every .sql filename matches the runner's NNNN_name.sql pattern (anything else is ignored and reported rather than applied).
  • The baseline is still the lowest version0001 declares stored procedures and is not safe to re-run; a file numbered below it would be applied against no schema.
  • No MariaDB-only syntax in any migration: ADD COLUMN IF NOT EXISTS, DROP COLUMN IF EXISTS, ADD INDEX IF NOT EXISTS, CREATE OR REPLACE TABLE. This class of bug fails at deploy time on a fresh database rather than in review.
  • No table created by more than one migration — the README rule that 0049 was breaking, now checked instead of remembered.
  • The three renumbered files still target the tables they were written for, and the features they unblock (coupons, fraud_monitoring_queue, the products full-text index) all still have an owner.

It also turns two red suites green

The repository already had guards for this collision; both have been failing on main since the third 0049 merged, and neither could be seen because check:syntax fails first and skips the test job on every PR:

FAIL tests/migrationViewColumns.test.js
  ● the sequence is still well formed › no two migrations claim the same version

FAIL tests/fraudMonitoringQueue.test.js
  ● the table the middleware writes to › takes the next free migration number

Both pass on this branch. tests/migrationSequence.test.js adds the checks those two do not make — the filename pattern, the MariaDB-only dialect scan, and the one-owning-migration-per-table rule.

Verification

$ backend/node_modules/.bin/jest --config backend/jest.config.js tests/migrationSequence.test.js
Tests:       65 passed, 65 total

# related suites, unchanged:
$ ... tests/migrationViewColumns tests/fraudMonitoringQueue tests/couponValidator tests/couponEffectiveness
Tests:       135 passed, 135 total

Confirmed the guard is real by restoring main's migrations/ and re-running it:

✕ no two migrations claim the same version
✕ 0049_coupons_schema.sql uses no MariaDB-only syntax
✕ the coupons migration amends the baseline rather than re-creating it
✕ it adds the columns couponService reads
✕ nothing is left at a duplicated 0049
✕ no table is created by more than one migration
Tests:       9 failed, 56 passed, 65 total

CI note — merge #1701 first

The Syntax check job fails on this branch, and it is not this change:

❌ 1 of 654 JavaScript file(s) failed to parse:
  frontend/scripts/shop.js:2499
      Unexpected end of input

frontend/scripts/shop.js is unparsable on main — the responsive refactor duplicated and interleaved its initialization block. Every open PR against this repository inherits it, and because check:syntax is the first CI job and the other two are gated on it, Backend tests and Server boots are skipped rather than run. That is why this PR shows no test result.

#1701 fixes it. Once that merges, this branch picks the fix up from main with no rebase needed — the two touch no file in common. All gates pass locally on this branch's changes:

$ npm run check:boot     ✅
$ npm run check:modules  ✅
$ npm run check:assets   ✅
$ npm run check:a11y     ✅
$ npm run check:sitemap  ✅

and I verified the whole set merges cleanly by merging all five of these branches together locally: no conflicts, check:syntax green at 659 files, and the full Jest suite at 2976 passing.

The Vercel check fails on every PR in this repository with "Authorization required to deploy" against the bhuvanshs-projects team, unrelated to any change.

Three migrations landed claiming version 0049. loadMigrations throws while
building the file list, before it compares anything against schema_migrations,
so the failure is total: npm run migrate and even migrate:status refuse the
whole directory rather than the three files. Nothing could be applied to any
environment, and nothing in CI runs the runner, so main stayed green while the
schema pipeline was fully blocked.

Renumber by merge order. 0049 stays with coupons_schema, which merged first;
product_search_fulltext becomes 0050 and fraud_monitoring_queue 0051.
Renumbering is safe precisely because the collision meant none of the three
could ever have been applied.

0049_coupons_schema.sql needed more than a number. It was a second CREATE TABLE
IF NOT EXISTS for a table 0001_baseline_schema.sql already owns, which MySQL
skips silently -- so the migration would have recorded itself as applied having
changed nothing -- followed by ALTER TABLE ... ADD COLUMN IF NOT EXISTS, which
is MariaDB syntax that MySQL 8 rejects with ERROR 1064 after the table was
already created.

Rewrite it as the ALTERs it should always have been: add the nullable
expires_at that validateCoupon reads before falling back to the baseline's
NOT NULL end_date, and widen the type enum to hold 'percent', which both
validateCoupon and pricing.service.js accept and the admin form submits. Every
enum member the baseline declared is kept, since dropping one rewrites the rows
using it to ''.

Add backend/tests/migrationSequence.test.js: no duplicate versions, every
filename matching the runner's pattern, no MariaDB-only syntax, and no table
created by more than one migration -- the rule migrations/README.md states and
that a human has to remember at merge time.
@hydra-maintainer

Copy link
Copy Markdown

🔍 Quality Gate Report

✅ All quality gates passed!

Status Check Details
Linked Issue PR description references a closing issue ✅

@vercel

vercel Bot commented Aug 26, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Bhuvansh's projects Team on Vercel.

A member of the Team first needs to authorize it.

@hydra-maintainer

Copy link
Copy Markdown

🤖 AI Code Review

🔴 Score: 50/100 | comment

AI review unavailable at this time.


Automated AI review — a human maintainer will also review.

@hydra-maintainer

Copy link
Copy Markdown

💡 Suggested reviewers based on relevant file history: @Aditya8369, @Pcmhacker-hero

@BHUVANSH855 BHUVANSH855 added action: merge Pull Request is ready for merge. Hard Program's points label. Bug-Fix This PR fixes bug. labels Aug 29, 2026
@BHUVANSH855
BHUVANSH855 merged commit 8845de2 into AnthropicBots:main Aug 29, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action: merge Pull Request is ready for merge. Bug-Fix This PR fixes bug. Hard Program's points label. health: 🔧 needs work quality: ✅ passed

Projects

None yet

2 participants