--- name: database-migrations description: Database migration patterns for Prisma, Drizzle, Django, SQLAlchemy, Go migrations. Use when: schema changes, adding columns/tables/indexes, data migrations, rollback strategies. --- # Database Migration Patterns Based on ECC database-migrations skill. ## Universal Rules 1. **Every migration is reversible** — always write DOWN (rollback) migration 2. **Deploy in phases** when possible: - Phase 1: Add new column/table (non-breaking) - Phase 2: Backfill data / dual-write - Phase 3: Switch reads to new schema - Phase 4: Remove old column/table 3. **Never drop data in a migration** without explicit user confirmation 4. **Test migrations** against a copy of production data 5. **Index new columns** that will be queried frequently 6. **Add NOT NULL with DEFAULT** when adding columns to existing tables ## Migration Strategy by Framework ### Prisma (TypeScript) ```bash npx prisma migrate dev --name add_user_role ``` - Edit schema.prisma → generate migration → review → apply - Always check the generated SQL ### Drizzle (TypeScript) ```bash npx drizzle-kit generate:pg --name add_user_role ``` - Migration files are TypeScript - Easier to review and modify than raw SQL ### Django (Python) ```bash python manage.py makemigrations python manage.py migrate python manage.py showmigrations ``` - Django auto-detects changes - For data migrations: use RunPython - For complex operations: use migrations.RunSQL ### SQLAlchemy (Python) ```bash alembic revision --autogenerate -m "add_user_role" alembic upgrade head ``` - Review auto-generated migrations carefully - Add missing operations manually ### Go (goose, golang-migrate) ```bash goose create add_user_role sql # or migrate create -ext sql -dir migrations -seq add_user_role ``` - SQL files: up and down - Explicit and reviewable ## Data Migration Patterns ### Adding a NOT NULL column with default ```sql -- Safe: existing rows get default value ALTER TABLE users ADD COLUMN role VARCHAR(50) NOT NULL DEFAULT 'user'; -- Then optionally remove default ALTER TABLE users ALTER COLUMN role DROP DEFAULT; ``` ### Adding an index ```sql -- Use CONCURRENTLY in production (PostgreSQL) CREATE INDEX CONCURRENTLY idx_users_email ON users(email); ``` ### Renaming a column (zero-downtime) 1. Add new column 2. Deploy code that writes to both columns 3. Backfill data from old to new 4. Switch reads to new column 5. Remove old column ### Backfill patterns ```sql -- Batch to avoid locking UPDATE users SET status = 'active' WHERE id IN ( SELECT id FROM users WHERE status = 'pending' LIMIT 10000 ); ``` ## Rollback Testing Before deploying: 1. Apply migration on staging 2. Roll it back 3. Verify data integrity 4. Re-apply to confirm idempotent ## Checklist - [ ] Migration file created with descriptive name - [ ] DOWN migration written and tested - [ ] Migration tested on staging DB - [ ] Indexes added for new query columns - [ ] NOT NULL columns have DEFAULT values - [ ] No raw DROP TABLE (use CASCADE with caution) - [ ] Data migrations are idempotent - [ ] Migration runs within acceptable time (< 5 min for online)