A tool for managing PostgreSQL schema migrations with automatic dependency resolution and state tracking.
pgmg handles both traditional sequential migrations for tables and declarative management of views, functions, and types. It automatically manages dependencies and ensures your database objects are recreated in the correct order when table changes affect them. This means that you can manage your views, functions, and types just like normal code, and that you do not need to order them alphanumerically in your filesystem.
# Analyze what changes need to be applied
pgmg plan --migrations-dir=./migrations --code-dir=./sql --connection-string=...
# Apply pending changes
pgmg apply --migrations-dir=./migrations --code-dir=./sql --connection-string=...project/
├── migrations/ # Sequential migration files
│ ├── 001_initial_tables.sql
│ ├── 002_add_user_email.sql
│ └── 003_add_orders_table.sql
│
└── sql/ # Declarative SQL objects
├── views/
│ ├── active_users.sql
│ └── user_stats.sql
├── functions/
│ ├── calculate_total.sql
│ └── get_user_summary.sql
└── types/
└── user_status.sql
Shows what changes would be applied without modifying the database:
$ pgmg plan --migrations-dir=./migrations --code-dir=./sql --connection-string=...
PLAN:
Pending data migration: 003_add_orders_table.sql
Affected objects that will be recreated:
- view.user_stats (depends on modified tables)
- function.get_user_summary (depends on user_stats)
Code changes detected:
- views/active_users.sql (modified)
- functions/calculate_total.sql (new)
Execution order:
1. DROP VIEW user_stats CASCADE
2. Run migration: 003_add_orders_table.sql
3. CREATE VIEW user_stats (from sql/views/user_stats.sql)
4. CREATE FUNCTION get_user_summary (from sql/functions/get_user_summary.sql)
5. CREATE OR REPLACE VIEW active_users (from sql/views/active_users.sql)
6. CREATE FUNCTION calculate_total (from sql/functions/calculate_total.sql)The plan command is read-only and can be run safely at any time.
Executes the changes identified by plan:
$ pgmg apply
Executing changes:
↓ Dropping view: user_stats CASCADE
✓ Applied migration: 003_add_orders_table.sql
✓ Created view: user_stats
✓ Created function: get_user_summary
✓ Replaced view: active_users
✓ Created function: calculate_total
Updated state tracking:
✓ Recorded migration: 003_add_orders_table.sql
✓ Updated 4 object hashes
All changes applied successfully.- Create your migration file:
-- migrations/004_add_products_table.sql
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
price DECIMAL(10,2)
);- Create or update views that use the new table:
-- sql/views/product_summary.sql
CREATE VIEW product_summary AS
SELECT COUNT(*) as total_products, AVG(price) as avg_price
FROM products;- Run plan to see what will happen:
pgmg plan --migrations-dir=./migrations --code-dir=./sql --connection-string=...- Apply the changes:
pgmg apply --migrations-dir=./migrations --code-dir=./sql --connection-string=...- Create your migration:
-- migrations/005_add_user_score.sql
ALTER TABLE users ADD COLUMN score INTEGER DEFAULT 0;- Update any views that should use the new column:
-- sql/views/user_stats.sql (modified)
CREATE VIEW user_stats AS
SELECT id, name, score, COUNT(orders.id) as order_count
FROM users
LEFT JOIN orders ON users.id = orders.user_id
GROUP BY users.id, users.name, users.score;- Run pgmg - it will automatically handle dropping and recreating the view:
pgmg plan [args...] # See that user_stats will be recreated
pgmg apply [args...] # Execute the changesWhen you only modify SQL files without table migrations:
# Edit a view or function
vim sql/views/user_stats.sql
# Apply the changes directly
pgmg apply --code-dir=./sql --connection-string=...During development, you can run pgmg watch --code-dir=./sql to automatically reload database
objects upon file changes.
pgmg uses two tables to track state:
-- Track which migrations have been applied
CREATE TABLE pgmg_migrations (
name TEXT PRIMARY KEY,
applied_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Track current state of all SQL objects
CREATE TABLE pgmg_state (
object_name TEXT PRIMARY KEY, -- e.g. 'view.user_stats'
object_hash TEXT NOT NULL, -- SHA256 of the SQL definition
last_applied TIMESTAMP NOT NULL DEFAULT NOW()
);-
Dependency Analysis
- Parses SQL files to build a dependency graph
- Uses PostgreSQL's error messages to discover dependencies
- Orders operations to respect dependencies
-
Change Detection
- Compares file hashes with stored hashes in
pgmg_state - Identifies new, modified, and deleted objects
- Detects which objects are affected by table migrations
- Compares file hashes with stored hashes in
-
Migration Wrapping
- When a table migration affects views/functions, pgmg:
- Drops the dependent objects before the migration
- Runs the migration
- Recreates objects using current definitions from sql/
- When a table migration affects views/functions, pgmg:
-
Atomic Execution
- All changes run in a single transaction when possible
- On success: updates both state tables
- On failure: rolls back everything
-
Declarative SQL Objects: Views, functions, and types are defined declaratively in the sql/ directory. The current file is always the source of truth.
-
Hash-Based Change Detection: Objects are recreated when their content hash changes, ensuring the database matches the code.
-
Automatic Dependency Resolution: Rather than manually managing DROP/CREATE order, pgmg determines the correct sequence automatically.
-
Unified Workflow: Whether applying table migrations, code changes, or both, the workflow is consistent: plan then apply.
If an apply fails:
- The transaction rolls back, leaving the database unchanged
- State tables remain consistent with actual database state
- Running plan again shows the same pending changes
- Fix the SQL files and run apply again
- No manual coordination: Table changes automatically trigger view recreation
- Version control friendly: Only source SQL in git, not generated migrations
- Self-healing: Drift between code and database is automatically corrected
- Safe iterations: Plan shows exactly what will happen before execution
Instead of supplying the --migrations-dir, --code-dir, and --connection-string to every command, they can be specified in a pgmg.toml file in the root of your project.