How To: Create a Database Migration¶
Steps¶
make migrate-create MSG="add_feature_name" # Generate migration
make test-migrations # REQUIRED: Test from scratch (needs Docker!)
make migrate # Apply locally
Requirements¶
- Docker MUST be running --
make test-migrationsuses real PostgreSQL. Start OrbStack if needed:open -a OrbStack - Use IF NOT EXISTS -- Migrations must be idempotent (CI re-runs them)
- Test locally before pushing -- CI will fail if migrations aren't idempotent
- Guard table references -- CI runs from empty DB. Use
IF EXISTSchecks for tables outside the migration chain.
View Dependencies¶
The plates_with_current_valuation view depends on plates columns. When altering plates columns:
- Drop the view first
- Alter the column
- Recreate the view
See VIEW_RECREATE_SQL in existing migrations for the pattern.
SQL Escaping¶
Avoid PostgreSQL's format() with %s/%I specifiers in op.execute() -- psycopg2's % escaping conflicts with SQLAlchemy.
# Preferred -- use quote_ident() and string concatenation
op.execute("DO $$ ... 'p.' || quote_ident(col) ... $$")
op.execute("DO $$ ... EXECUTE 'SELECT ' || cols || ' FROM t' ... $$")
Testing Matrix¶
| What you changed | Run this | Requires Docker? |
|---|---|---|
backend/app/models/*.py |
make test-backend + make test-backend-pg |
Yes |
backend/alembic/** |
make test-migrations |
Yes |
What make test-migrations Does¶
- Resets database (drops all data)
- Runs all migrations from scratch (
alembic upgrade head) - Re-runs upgrade (idempotency check)
- Rewinds 2 revisions and re-upgrades (resilience check)
- Validates schema matches SQLAlchemy models (
schema_check.py)