# SQLite to MySQL controlled migration ## Preconditions - Work from a dedicated branch and record the source commit. - Put the application in a planned write freeze. - Keep the source SQLite file outside the web root. - Create an immutable copy and verify its SHA-256 hash. - Provision an empty MySQL 8 database using `utf8mb4` and `utf8mb4_unicode_ci`. - Use a dedicated least-privilege migration account. Do not commit its password. ## Dry run Configure only the `MIGRATION_MYSQL_*` variables. Keep `DB_CONNECTION=sqlite`. ```sh php artisan migrate --database=migration_mysql --force php artisan db:transfer-sqlite-to-mysql ``` The transfer command does not write unless `--execute` is supplied. ## Transfer ```sh php artisan db:transfer-sqlite-to-mysql --execute --truncate php artisan db:transfer-sqlite-to-mysql --execute ``` The second execution proves idempotency. Both runs must finish with identical source and target row counts. Sessions, access tokens, cache, queues, reset tokens, and the migration repository are intentionally excluded; users must authenticate again. ## Validation gates - All migrations succeed on an empty MySQL database. - Every transferred table has equal source and target row counts. - Foreign-key validation reports no orphan rows. - Unique business keys have no duplicates. - JSON columns contain valid JSON. - Dates, times, decimals, booleans, Persian text, and emoji round-trip correctly. - Backend, frontend build, and browser tests pass with MySQL as the active test database. - A manual smoke test covers login, project/task CRUD, sprint, meeting, files, notifications, settings, and reports. ## Cutover 1. Enable maintenance/write-freeze mode. 2. Take and hash a final SQLite backup. 3. Re-run the idempotent transfer without `--truncate`. 4. Run validation gates. 5. Change production secrets to `DB_CONNECTION=mysql`. 6. Clear configuration cache, restart application and queue workers, then run health and smoke checks. 7. Retain the SQLite source read-only for the defined rollback period. ## Rollback If any validation or smoke check fails: 1. Stop writes immediately. 2. Restore the previous application configuration and `DB_CONNECTION=sqlite`. 3. Restore the verified final SQLite copy if the source was touched. 4. Restart application and workers. 5. Run `/up`, authentication, project, task, and file smoke checks. 6. Preserve MySQL unchanged for incident analysis; never merge partial target data back manually.