Migrations Guide
forge includes a built-in migration system based on golang-migrate that automatically manages your database schema changes.
Overview​
Migrations are version-controlled database schema changes. They allow you to:
- Track schema changes over time
- Apply changes to different environments consistently
- Roll back changes if needed
- Collaborate with team members on schema changes
Creating Migrations​
Automatic Migration Generation​
Generate migrations from your model definitions:
forge makemigrations <name> --auto
This will:
- Scan your models directory
- Compare current models with database state
- Automatically detect dependencies from foreign keys and table references
- Generate migration files for any changes
- Save migrations to
migrations/directory
Verbose Mode​
Use --verbose to see detailed information about parse errors and warnings:
forge makemigrations <name> --auto --verbose
This helps debug issues with migration parsing and state loading.
Migration Files​
Migrations are stored in migrations/ directory:
migrations/
├── 000001_initial.up.sql
├── 000001_initial.down.sql
├── 000002_add_user_table.up.sql
├── 000002_add_user_table.down.sql
└── ...
Each migration has:
- Up migration (
.up.sql) - Applies the change - Down migration (
.down.sql) - Reverts the change
Dependency Auto-Detection​
The migration system automatically detects dependencies between migrations:
-- Auto-detected dependencies:
-- DEPENDS: 000001
CREATE TABLE posts (
id BIGSERIAL PRIMARY KEY,
author_id BIGINT NOT NULL,
FOREIGN KEY (author_id) REFERENCES users(id)
);
Dependencies are automatically detected from:
- Foreign key relationships in model definitions
- Table references in SQL (REFERENCES, ALTER TABLE, etc.)
The system validates that all dependencies exist before generating migrations.
Applying Migrations​
Apply All Pending Migrations​
forge migrate
This applies all migrations that haven't been run yet.
Apply Specific Migration​
forge migrate up 2
Applies migrations up to version 2.
Rollback​
forge migrate down 1
Rolls back the last migration.
Check Migration Status​
forge migrate status
Shows which migrations have been applied.
Show Migration Plan​
Preview what migration would be generated:
forge migrate show
Shows the migration plan that would be generated from current models. Use --sql to see the full SQL, or --verbose to see parse warnings.
Lint Migrations​
Check migration files for common issues:
forge migrate lint
Use --verbose to also see parse errors from the state loader:
forge migrate lint --verbose
Fake Migrations​
Mark migrations as applied without running them. Useful when:
- Migrating an existing database
- Marking initial migrations that already exist in the database
- Marking all pending migrations as applied
Fake Initial Migrations​
If you have an existing database with tables, mark the initial migrations that created those tables:
forge migrate fake --fake-initial
This will:
- Query the database for existing tables
- Find migrations that create those tables
- Mark them as applied without running them
Mark All Pending as Applied​
Mark all pending migrations as applied:
forge migrate fake
This marks all migrations with versions greater than the current version as applied. A confirmation prompt appears if more than 5 migrations would be faked.
Fake Specific Migration​
Mark a specific migration version as applied:
forge migrate fake <version>
Warning: Use fake commands with caution. Only use when you're certain the migration has already been applied or when you want to skip it.
Migration Examples​
Creating a Table​
Up Migration:
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(150) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(128) NOT NULL,
is_active BOOLEAN DEFAULT true,
date_joined TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_users_email ON users(email);
Down Migration:
DROP INDEX IF EXISTS idx_users_email;
DROP TABLE IF EXISTS users;
Adding a Column​
Up Migration:
ALTER TABLE users ADD COLUMN last_login TIMESTAMP;
Down Migration:
ALTER TABLE users DROP COLUMN last_login;
Adding a Foreign Key​
Up Migration:
ALTER TABLE posts ADD COLUMN author_id BIGINT;
ALTER TABLE posts ADD CONSTRAINT fk_posts_author
FOREIGN KEY (author_id) REFERENCES users(id)
ON DELETE CASCADE;
Down Migration:
ALTER TABLE posts DROP CONSTRAINT fk_posts_author;
ALTER TABLE posts DROP COLUMN author_id;
Creating an Index​
Up Migration:
CREATE INDEX idx_posts_created_at ON posts(created_at);
Down Migration:
DROP INDEX IF EXISTS idx_posts_created_at;
Data Migrations​
You can also include data migrations in your SQL files:
-- Update existing data
UPDATE users SET is_active = true WHERE is_active IS NULL;
-- Insert default data
INSERT INTO categories (name, slug) VALUES
('Technology', 'technology'),
('Science', 'science'),
('Arts', 'arts');
Best Practices​
1. Keep Migrations Small​
Break large changes into multiple migrations:
-- Good: Separate migrations
-- 000001_add_users_table.up.sql
-- 000002_add_posts_table.up.sql
-- 000003_add_comments_table.up.sql
-- Bad: One large migration
-- 000001_add_all_tables.up.sql
2. Always Write Down Migrations​
Every up migration should have a corresponding down migration:
-- Up
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- Down
ALTER TABLE users DROP COLUMN phone;
3. Test Migrations​
Test both up and down migrations:
# Apply
forge migrate
# Rollback
forge migrate down 1
# Re-apply
forge migrate up 1
4. Don't Modify Existing Migrations​
Once a migration is applied to production, don't modify it. Create a new migration instead.
5. Use Transactions​
Wrap migrations in transactions when possible:
BEGIN;
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
CREATE INDEX idx_users_phone ON users(phone);
COMMIT;
Migration Workflow​
Development​
- Modify your models
- Generate migrations:
forge makemigrations - Review generated SQL
- Apply migrations:
forge migrate - Test your application
Production​
- Review migrations before deploying
- Backup database
- Apply migrations:
forge migrate - Verify application works
- Keep backup until confident
Troubleshooting​
Migration Conflicts​
If you have conflicting migrations:
# Check status
forge migrate status
# Manually resolve conflicts
# Edit migration files as needed
Failed Migrations​
If a migration fails:
- Check the error message
- Fix the SQL in the migration file
- Rollback if needed:
forge migrate down 1 - Fix and re-apply:
forge migrate up 1
Database State Mismatch​
If your database state doesn't match migrations:
# Check current state
forge migrate status
# Force to specific version (use with caution)
forge migrate force 5
Parse Errors​
If you see parse errors or warnings:
# Use verbose mode to see detailed parse errors
forge migrate show --verbose
forge migrate lint --verbose
The migration system uses a robust three-pass retry mechanism to handle:
- Out-of-order CREATE TABLE statements
- Foreign key constraints referencing tables not yet created
- Indexes created before their tables
Parse errors are collected and can be viewed with verbose mode.
Advanced Topics​
Custom Migration SQL​
You can write custom SQL migrations:
-- Custom migration logic
DO $$
BEGIN
-- Complex migration logic
IF EXISTS (SELECT 1 FROM information_schema.tables
WHERE table_name = 'old_table') THEN
-- Migration code
END IF;
END $$;
Migration Dependencies​
Dependencies are automatically detected, but you can also specify them manually:
-- DEPENDS: 000001
-- DEPENDS: 000002
CREATE TABLE posts (
-- ...
);
For cross-application dependencies:
-- DEPENDS: app_name:000001
CREATE TABLE shared_table (
-- ...
);
State Loading Improvements​
The migration system includes improved state loading:
- Three-pass retry mechanism: Handles out-of-order migrations gracefully
- Table context tracking: Automatically infers table names for indexes
- Parse error collection: Collects and reports parse errors without failing
- Verbose logging: Optional detailed logging for debugging
Parser Improvements​
The SQL parser includes:
- Two-pass parsing: Processes CREATE TABLE statements first, then constraints
- Better error handling: Fails softly with UnknownChange for unparseable statements
- Table context inference: Tracks table context for DROP INDEX and CREATE INDEX
- Verbose mode: Logs UnknownChange statements and parse errors
Migration Hooks​
Add hooks to run code during migrations:
// In your migration file or code
func RunMigration(ctx context.Context, db *sql.DB) error {
// Custom migration logic
return nil
}
Next Steps​
- Schema Reference - Learn about model definitions
- Development Guide - Contributing to forge
- Deployment Guide - Deploying your application