Skip to content

[Bug] Three migrations claim version 0049 — the runner refuses to apply anything, and 0049_coupons_schema.sql uses MariaDB-only ADD COLUMN IF NOT EXISTS #1700

Description

@MOHITKOURAV01

Summary

Three migrations landed on main claiming version 0049. The runner refuses to load the directory at all when versions collide, so npm run migrate and npm run migrate:status both fail and no migration can be applied — not the colliding three, not anything added after them. Separately, 0049_coupons_schema.sql ends with ALTER TABLE ... ADD COLUMN IF NOT EXISTS, which is MariaDB syntax that MySQL 8 rejects.

The collision

$ ls migrations | tail -4
0048_newsletter_spent_confirm_token.sql
0049_coupons_schema.sql
0049_fraud_monitoring_queue.sql
0049_product_search_fulltext.sql

backend/scripts/migrate.js (loadMigrations):

if (versions.has(version)) {
    throw new MigrationError(
        `Duplicate migration version ${version}: ${versions.get(version)} and ${filename}. ` +
        `Renumber one of them so the order is unambiguous.`
    );
}

The throw happens while building the file list, before anything is compared against schema_migrations, so the failure is total:

MigrationError: Duplicate migration version 0049: 0049_coupons_schema.sql and
0049_fraud_monitoring_queue.sql. Renumber one of them so the order is unambiguous.

migrations/README.md calls this out by name — "a collision takes the whole sequence down and not just the two files involved" — but the three files merged from separate branches (#1666, #1667, #1671) that each looked fine in isolation.

The invalid ALTER

migrations/0049_coupons_schema.sql, last line:

ALTER TABLE coupons ADD COLUMN IF NOT EXISTS expires_at DATETIME NULL;

ADD COLUMN IF NOT EXISTS is a MariaDB extension. MySQL 8.0 answers:

ERROR 1064 (42000): You have an error in your SQL syntax; check the manual ...
near 'IF NOT EXISTS expires_at DATETIME NULL'

This repo runs MySQL (mysql2, utf8mb4_unicode_ci, ENGINE=InnoDB). The statement is also redundant — expires_at DATETIME NULL is already declared in the CREATE TABLE three lines above it — so once the collision is resolved this migration would still abort partway through on a fresh MySQL database, leaving coupons created but the migration unrecorded, and the next run would then hit the "never edit an applied migration" checksum rule.

Impact

  • No schema change can be applied to any environment. coupons (needed by couponService), fraud_monitoring_queue and the products full-text index are all unreachable, which means coupon validation, the fraud queue and product autocomplete all fall back to their error paths against a database that will never have the tables.
  • Nothing in CI runs the migration runner, so main is green while the schema pipeline is fully blocked.

Steps to reproduce

cd backend && npm run migrate:status
# MigrationError: Duplicate migration version 0049 ...

Expected

  • One migration per version number: renumber two of the three to 0050 / 0051, keeping 0049 for whichever merged first.
  • 0049_coupons_schema.sql contains only MySQL-valid syntax — drop the redundant MariaDB-only ALTER.
  • A test asserts the migrations directory has no duplicate version prefix and that no migration uses ADD COLUMN IF NOT EXISTS, so the next collision is caught in CI rather than at deploy time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions