pdf-quiz-generator/docs/migrations.md

76 lines
3.1 KiB
Markdown

# Database Migrations (Alembic)
## What's a migration?
A migration is a versioned, ordered change to the database schema — adding a column, renaming a table, changing a type. Each change is a Python file in `backend/alembic/versions/`. Alembic tracks which ones have been applied in an `alembic_version` table in Postgres, so it knows what's new next time.
Before Alembic was wired up in this project, schema was bootstrapped via `Base.metadata.create_all()` in `backend/app/main.py` — which only creates *missing tables*, never alters existing ones. Every column change required a manual `ALTER TABLE`. Alembic fixes that: schema changes become versioned, reversible, and reproducible across environments.
## Current setup
- `backend/alembic.ini` contains no hardcoded URL; `backend/alembic/env.py` injects `DATABASE_URL` from the container's environment.
- The live production DB is stamped at the latest revision in `backend/alembic/versions/`.
- `Base.metadata.create_all()` is still called at startup as a safety net for fresh deploys. Do **not** remove it without first generating a complete baseline migration from the current live schema.
## Developer workflow
```bash
# where am I?
docker compose exec backend alembic current
docker compose exec backend alembic heads
# create a new migration (auto-diffs your model against the live DB)
docker compose exec backend alembic revision --autogenerate -m "add some column"
# review the generated file under backend/alembic/versions/ BEFORE applying
# apply pending migrations
docker compose exec backend alembic upgrade head
# roll back the last one
docker compose exec backend alembic downgrade -1
# mark the DB as being at a revision without running anything (use with care)
docker compose exec backend alembic stamp <revision>
```
After adding a migration in dev, rebuild the backend image so the new file is in the container:
```bash
docker compose build backend celery
docker compose up -d backend celery --force-recreate
```
## When to write a migration
Any schema change:
- New column, dropped column, renamed field
- New table, dropped table
- Altered index
- New foreign key
- Changed nullability or default value
Always: edit model → generate migration → review → apply → commit both files together.
## Gotchas
- Migrations run inside a transaction. A failed migration rolls back cleanly, so the DB stays consistent.
- `--autogenerate` doesn't catch everything. Things you must hand-edit into the file:
- `server_default` changes
- CHECK constraints
- Enum value additions
- Data migrations (moving rows around as part of a schema change)
- The `alembic_version` table should only ever have one row. Multiple rows = branched heads; run `alembic merge` to reconcile.
- Don't edit a migration after it's been applied to any shared environment — write a new one instead.
## Rollback patterns
```bash
# Undo the last migration
docker compose exec backend alembic downgrade -1
# Jump to a specific revision (by hash prefix)
docker compose exec backend alembic downgrade 9bac7bf02e38
# Jump all the way back (rarely what you want)
docker compose exec backend alembic downgrade base
```