How dibs works
dibs keeps your Postgres schema in sync with your Rust types. It constantly reconciles intent (your Rust schema) with reality (the live database), then generates the SQL to make them match.
The pipeline
Most dibs commands follow the same steps:
myapp-db crate.Architecture
Your "db crate" is a small process that speaks RPC to the CLI (via Roam):
A typical exchange looks like:
dibs scans your crate for registered tables (Facet annotations), builds an internal schema model, and returns it over RPC.
The result is a structured diff - see below.
The solver orders these operations - see below.
Why RPC? So you don't have to boot your entire app just to work with schemas and migrations.
A typical workspace has three crates: myapp-db (schema + migrations), myapp-queries (generated query helpers), and myapp (your application). The CLI talks to myapp-db via RPC; your app imports myapp-queries for typed database access.
The diff
When intent and reality don't match, dibs produces a diff: a typed list of schema operations - create/drop/rename tables, add/alter/drop columns, indexes, constraints, foreign keys. This isn't raw SQL; it's structured data that dibs can reason about for ordering and safety.
The solver
The same set of changes can succeed or fail depending on order - you can't add a foreign key before the referenced table exists, or drop a table while others still reference it.
The solver orders operations by simulating them on a virtual schema:
- Start from the current schema state
- Pick any change whose preconditions are satisfied
- Apply it to the virtual schema
- Repeat until all changes are scheduled
After ordering, dibs verifies that applying the changes to "current" produces "desired". If the solver can't make progress, there's either a true dependency cycle or a bug in diff generation.
SQL generation
The solver's ordered operations become concrete DDL: CREATE TABLE, ALTER TABLE, CREATE INDEX, etc. Because dibs knows what it's trying to do (not just the final SQL text), it can provide better errors and tooling.
Running migrations
Migrations are Rust functions. Each runs in its own transaction - if it fails, the transaction rolls back and subsequent migrations don't run.
When something fails, dibs attaches context (what SQL was running, source location when available) so you get actionable errors instead of "postgres said no."
Metadata tables
dibs maintains __dibs_* tables to record source locations (file/line/column), doc comments, and which migration created what. This powers richer tooling in the CLI, editors, and admin UIs - but it's separate from your app schema and ignored during diffing.