pgevolve
Postgres-specific declarative schema management.
pgevolve treats a directory of CREATE-style SQL files as the source of
truth for one or more Postgres schemas, introspects a live database to
derive its current state, and computes ordered, dependency-aware migration
plans that bring the database to the desired state. It refuses to lose
data unless explicitly authorized in a per-plan intent file.
Current release: v0.4.6 (Postgres 14–18). See
CHANGELOG.md for per-release detail.
Documentation: https://saosebastiao.github.io/pgevolve/
Install
From crates.io (recommended):
From source:
pgevolve requires Rust 1.95+.
Getting started
# 1. Scaffold a project (creates `pgevolve.toml`, `schema/`, `plans/`).
# 2. Author your schema as CREATE-style SQL under `schema/`.
# File layout is enforced by the `layout_profile` setting; the default
# (`schema-mirror`) wants `schema/<schema>/<kind>/<name>.sql`.
# 3. Plan a migration against a configured environment.
# wrote plans/2026-05-23-<short-id>/{plan.sql, intent.toml, manifest.toml}
# 4. Review the generated plan.sql + intent.toml in code review.
# 5. Apply it.
Full walkthrough: docs/user/getting-started.md.
CLI surface
| Command | What it does | Touches DB | Writes files |
|---|---|---|---|
pgevolve init |
Scaffold project files | no | yes |
pgevolve lint |
Run universal + layout-profile rules | no | no |
pgevolve validate |
Parse + build source IR; with --shadow, round-trip the IR through ephemeral Postgres; --shadow-validate cross-checks dep graph against pg_depend |
shadow only | no |
pgevolve diff --db <env> |
Build source + catalog IR; print change set (--format=human|json|sql); --shadow-validate opt-in |
read-only | no |
pgevolve plan --db <env> [-o <dir>] |
Full pipeline; write plan directory; gates on unwaived LintAtPlan findings; --shadow-validate opt-in |
read-only | yes |
pgevolve apply <plan-dir> --db <env> |
Execute plan | read+write | no |
pgevolve status --db <env> |
Show recent applies and per-step state | read-only | no |
pgevolve dump --db <env> -o <dir> |
Introspect live DB and write source SQL | read-only | yes |
pgevolve bootstrap --db <env> |
Install/upgrade the pgevolve metadata schema |
read+write | no |
pgevolve graph |
Render source dep graph (--graph-format dot|mermaid); no DB required |
no | optional (-o) |
pgevolve doctor --db <env> |
Health check: bootstrap status, NOT VALID constraints, INVALID indexes, object counts, recent failures | read-only | no |
pgevolve cluster … |
Cluster-level surface: plan/apply role + role-attribute changes (CREATE ROLE, ALTER ROLE, GRANT role TO role) |
varies | varies |
pgevolve rewrite-table <qname> --db <env> --confirm-rewrite |
Destructive table rewrite — skeleton; not yet implemented | — | — |
Exit codes: 0 success, 1 lint/validation error, 2 drift or
pre-flight mismatch, 3 apply error, 4 config or CLI input error.
Full reference: docs/user/commands.md.
Configuration
pgevolve.toml:
[]
= "myapp"
= "schema"
= "plans"
= "schema-mirror" # also: kind-grouped | feature-grouped | free-form | <path-to-custom.toml>
[]
= ["app", "billing"] # schemas under pgevolve's control
= ["app.legacy_etl_*"] # qname or glob
[]
= "online" # "atomic" | "online"
[]
= true
= true
= true
= true
[]
= "postgres://localhost/myapp_dev"
[]
= "DATABASE_URL_PROD" # read DSN from env var
= "online"
Connection precedence (mirrors psql):
--url CLI flag → [environments.<env>].url →
[environments.<env>].url_env → PGEVOLVE_DATABASE_URL →
libpq env (PGHOST, PGUSER, ...).
Full reference: docs/user/configuration.md.
Highlights
- Declarative source of truth.
CREATE-style SQL underschema/is the only source; the plan is always derived, never authored. - Lenient drift. Cross-cutting state (owner, grants, RLS policies,
reloptions) marked
Nonein source is treated as unmanaged — the differ never reverts catalog values you haven't claimed. Per-featureunmanaged-*lints surface the drift instead. - Audited apply. Every step lands in
pgevolve.apply_log/pgevolve.plan_stepswith success/failure/abort transitions; the executor refuses to apply a tampered plan (BLAKE3 plan-id + manifest-embedded pre-image catalog). - Online rewrites by default. Concurrent index create/drop,
NOT VALID+VALIDATEfor FK and CHECK, the 4-stepSET NOT NULLvia CHECK pattern,REFRESH MATERIALIZED VIEW CONCURRENTLYupgrade. Opt out per-environment withstrategy = "atomic". - All actively-maintained PG majors. PG 14, 15, 16, 17, 18 covered in CI on every push; per-version SQL paths in the catalog reader.
- Conformance-driven. ~260 fixture-based end-to-end tests gate
CI. Every claimed capability has a fixture; see
docs/spec/testing.md.
Documentation
docs/user/— user guide: installation, getting started, configuration reference, command reference, cookbook, troubleshooting, plan-format walkthrough.docs/system/— architecture and internals: architecture, IR, planner, executor.docs/spec/— living capability catalogue: every object kind, column type, constraint, index option, lint rule, CLI command, with implementation status. The authoritative answer to "does pgevolve do X?".docs/CONSTITUTION.md— binding principles for the project (license, dependency policy, type-system rigor, release discipline). Worth reading once if you intend to contribute.docs/v1.md— v1.0 charter: the gate, stability commitments, quality bar, PG-version policy, parking lot. Read this to understand what pgevolve commits to keep stable, and what's deliberately deferred past v1.0.docs/RELEASING.md— release runbook.
License
Dual-licensed under MIT or Apache-2.0,
at your option. All dependencies are permissive-licensed
(MIT/Apache-2.0/BSD-family/ISC/Unicode/CC0/Zlib); no copyleft. Policy
enforced by cargo deny check (see deny.toml) in CI.