paste PostgreSQL or JSON, get your schema reviewed like a senior would

SQL schema diff: spotting breaking changes before deploy

Comparing two versions of a schema by hand is error-prone exactly where it hurts most: the column that disappeared, the type that narrowed, the NOT NULL that appeared. A text diff (git diff on the dump) helps little because formatting noise and column order hide what matters. What counts is the semantic diff: what actually changed and what it breaks.

Breaking vs non-breaking

ChangeBreaks?Why
New nullable column (or with a DEFAULT)noold writes stay valid
Column removed or renamedyesevery SELECT/INSERT that names it fails; a rename is drop + add to readers
Type widened (INT to BIGINT, VARCHAR(50) to 120)usually noexisting values fit; watch for overflow on consumers
Type narrowed or swapped (VARCHAR to INT)yesexisting data may not fit or convert
NOT NULL added without a DEFAULTyesold INSERTs that omit the field start failing
UNIQUE addeddependsfails if there's already a duplicate; changes the write contract

Two of the "yes" rows destroy data, not just compatibility: a dropped column takes its values with it, and a narrowed type truncates or refuses what's already stored. Those deserve a louder alarm than the rest, a failed INSERT pages someone; silently lost data doesn't.

The rename ambiguity

A diff of two schema versions can't tell a rename from a drop-plus-add. phone VARCHAR(20) gone and mobile VARCHAR(20) new is either "we renamed it" (migration: ALTER TABLE ... RENAME COLUMN, data preserved) or "we dropped one field and added an unrelated one" (migration: DROP + ADD, data on the old one gone). Same diff, opposite migrations. Any tool that auto-applies one of the two readings is guessing with your data; the honest output is "this looks like a rename, confirm it", which is exactly why Schemint marks the pattern uncertain instead of generating the destructive version quietly.

Expand / contract, step by step

For a breaking change on a live system, do it in phases so every deploy is compatible with its neighbors. The column rename, done safely:

-- V12, expand: add the new column, backfill, dual-write via app code
ALTER TABLE customers ADD COLUMN mobile VARCHAR(20);
UPDATE customers SET mobile = phone WHERE mobile IS NULL;

-- (deploy app version that writes both, reads mobile)

-- V13, contract: nothing reads phone anymore
ALTER TABLE customers DROP COLUMN phone;

The NOT NULL variant of the same dance: add the column nullable with a DEFAULT, backfill, deploy code that always writes it, then ALTER TABLE ... SET NOT NULL as the contract step. At every point a rollback of the application meets a schema it understands, and a rollback of the migration meets code that still works. Tedious? It is. But it's the tedium that doesn't take down production at 3am.

Automate the detection

The point of reviewing the schema diff in the PR is to catch a breaking change before the deploy, not after the incident. Eyeballing two DDL files fails quietly, the reviewer's eye slides over a VARCHAR(180) that became VARCHAR(120), and "I don't think this breaks anything" carries the deploy. A tool that classifies every change (added, removed, narrowed, NOT NULL'd, possibly-renamed) turns that sentence into a checklist: each breaking item either gets an expand/contract plan or an explicit "no readers exist, accepted" in the PR description. The diff also drafts the forward migration, which is the part worth generating; the judgment calls above are the part worth a human.

Schemint's SQL Diff compares two sets of CREATE TABLE and lists what was added, removed and changed, marking what's breaking, what loses data and what looks like a rename. Paste v1 and v2 and review it in seconds. Open it.

Related