v0.9.4 — migrate preflight --against
What's new
confiture migrate preflight --against <url> (issue #116)
Test pending migrations against a disposable database (typically seeded from pg_dump --schema-only) and report all failures in one pass — not just the first one.
The problem it solves: migrations written against a locally-built schema can fail on a production database that has accumulated incremental changes. The previous feedback cycle was: push → restore full backup (~30–40 min) → discover one failure → fix → repeat.
The new workflow:
```bash
1. Pull production schema (~1 MB, ~2 s)
pg_dump --schema-only -f /tmp/prod.sql postgresql://prod-host/mydb
2. Seed a local preflight database
createdb myapp_preflight
psql -d myapp_preflight < /tmp/prod.sql
3. Test all pending migrations — get all failures at once
confiture migrate preflight
--against postgresql://localhost/myapp_preflight
--env production
4. Fix, repeat step 3 until all pass — then ship
```
New options
| Option | Description |
|---|---|
--against <url> |
PostgreSQL URL of the preflight database |
--env <name> / --config <path> |
Detect pending migrations from a live tracking table |
--since <version> |
Test migrations at or after a version (inclusive, no second DB connection) |
--allow-non-transactional |
Run CREATE INDEX CONCURRENTLY etc. in autocommit mode |
--format json |
Output {"static": …, "against": …} envelope |
How it works
- Per-migration SAVEPOINTs allow execution to continue past individual failures
- An outer SAVEPOINT is always rolled back — the preflight DB is left unchanged
- Non-transactional migrations are skipped by default (neutral, exit 0); opt-in with
--allow-non-transactional - Exit 0 = all pass, exit 1 = failures detected, exit 2 = connection/config error
New public API
```python
from confiture import PreflightAgainstResult, PreflightAgainstMigration
from confiture.core._migrator.session import MigratorSession
session = MigratorSession(
config=None,
migrations_dir=Path("db/migrations"),
database_url_override="postgresql://localhost/myapp_preflight",
)
with session:
result = session.run_against(pending_files, against_url="postgresql://localhost/myapp_preflight")
print(result.all_passed, result.failures)
```
Full changelog
See CHANGELOG.md for details.
Full Changelog: v0.9.3...v0.9.4