# Contract: Database Patterns (Go / MySQL / MariaDB) Version: 1.9 See `README.md` for examples, migration snippets, and Docker test commands. ## Query and Startup Rules - Never execute SQL on the same transaction while iterating an open result cursor. Use a two-phase flow: read all rows, close the cursor, then execute writes. - This rule applies to `database/sql`, GORM transactions, and any repository call made while another cursor in the same transaction is still open. - User-visible records use soft delete or archive flags. Do not hard-delete records with history or foreign-key references. - Archive operations must be reversible from the UI. - Use `gorm:"-"` only for fields that must be ignored entirely. Use `gorm:"-:migration"` for fields populated by queries but excluded from migrations. - Always verify the DB connection before starting the HTTP server. Never serve traffic with an unverified DB connection. - Prevent N+1 queries. Do not query inside loops over rows from another query; use JOINs or batched `IN (...)` queries. ## Migration and Backup Rules - The migration engine owns backup creation. The operator must never be required to take a manual pre-migration backup. - Backup storage, retention, archive format, and restore-readiness must follow `backup-management`. - Before applying any unapplied migrations, take and verify a full DB backup. - Before applying a migration step that changes a table, take a targeted backup of each affected table. - Before writing any backup, verify that the output path resolves outside the git worktree and is not tracked or staged in git. - If any migration step in a session fails, roll back all steps applied in that session in reverse order. - If rollback is not sufficient, restore from the targeted backup taken before the failing step. - After rollback or restore, the DB must be back in the same state it had before the session started. - Migration failures must emit structured diagnostics naming the failed step, rollback actions, and final DB state. ## Migration Authoring Rules - For local-first desktop apps, migration recovery must also follow `local-first-recovery`. - Migrations are sequential and immutable after merge. - Each migration should be reversible where possible. - Do not rename a column in one step. Add new, backfill, and drop old across separate deploys. - Auto-apply on startup is allowed for internal tools only if the behavior is documented. - Every `.sql` migration file must start with: - `-- Tables affected: ...` - `-- recovery.not-started: ...` - `-- recovery.partial: ...` - `-- recovery.completed: ...` - one or more `-- verify: | ` checks - Verify queries must return rows only when something is wrong. - Verify queries must exclude NULL and empty values when those would create false positives. - A migration is recorded as applied only after all verify checks pass. ## Pre-Production Validation Rules - Test pending migrations on a dump of the current production DB, not on fixtures. - Use a local MariaDB Docker container matching the production version and collation. - Execute each migration file as one DB session so session variables such as `SET FOREIGN_KEY_CHECKS = 0` remain in effect for the whole file. - If migrations fail in Docker, fix them before touching production. ## Common Pitfalls - Do not use tools that naively split SQL on bare `;`. String literals may contain semicolons. - `SET FOREIGN_KEY_CHECKS = 0` is session-scoped. If the file is split across multiple sessions, FK checks come back on. - When adding a new FK to legacy data, repair missing parent rows before enforcing the constraint unless data loss is explicitly acceptable.