DEV Community

MergeBrake
MergeBrake

Posted on

I scanned 1,066 Prisma migrations. Here are the risky patterns that kept showing up

I scanned 1,066 Prisma migrations. Here are the risky patterns that kept showing
up

I wanted to understand what risky database migrations look like in real-world
Prisma projects, not toy examples.

So I ran an open-source migration scanner across the full migration history of
three public Postgres + Prisma repositories:

  • documenso
  • trigger.dev
  • formbricks

In total:

  • 1,066 migration files
  • 2,339 findings
  • 21 rule types
  • Postgres AST parsing, not regex

Important note: these findings do not mean the projects are broken. Many are
historical migrations, intentional refactors, or safe in context. The goal was
to identify recurring risk patterns that deserve review before merge.

## The most common risky patterns

Two patterns dominated the dataset.

### 1. Foreign keys added without NOT VALID

Postgres validates a new foreign key against existing rows when the constraint
is added.

On larger tables, that can create lock and rollout risk.

A safer pattern is often:


sql
  ALTER TABLE orders
  ADD CONSTRAINT orders_user_id_fkey
  FOREIGN KEY (user_id)
  REFERENCES users(id)
  NOT VALID;

  ALTER TABLE orders
  VALIDATE CONSTRAINT orders_user_id_fkey;

  This splits adding the constraint from validating historical data.

  ### 2. Indexes created without CONCURRENTLY

  This one showed up constantly.

  CREATE INDEX orders_user_id_idx ON orders(user_id);

  For production tables, the safer version is usually:

  CREATE INDEX CONCURRENTLY orders_user_id_idx ON orders(user_id);

  The catch: CREATE INDEX CONCURRENTLY has transaction caveats, so migration tools
  and ORMs do not always emit it automatically.

  ## Destructive changes were also common

  The dataset also included many examples of:

  - DROP COLUMN
  - DROP TABLE
  - RENAME COLUMN
  - ALTER COLUMN TYPE
  - DROP CONSTRAINT
  - TRUNCATE
  - UPDATE without WHERE

  Again, these are not automatically bugs. Sometimes they are correct.

  The dangerous part is deploy order.

  For example:

  ALTER TABLE users DROP COLUMN full_name;

  This can be safe only if every deployed app version, background job, API
  serializer, and raw SQL query has already stopped reading users.full_name.

  That is where SQL-only linting stops being enough.

  ## Why SQL-only linting is not enough

  A SQL linter can tell you:

  > This migration drops a column.

  But the question reviewers really need answered is:

  > What application code will break if this merges?

  For modern apps, the column name in SQL may not even match the field name in
  code.

  Example with Prisma:

  model User {
    fullName String @map("full_name")
  }

  The migration drops:

  ALTER TABLE users DROP COLUMN full_name;

  But the app reads:

  user.fullName

  A plain grep for full_name misses that. A schema-aware scanner needs to
  understand the ORM mapping.

  ## The pattern I care about most

  The highest-signal finding is not simply:

  > This migration is risky.

  It is:

  > This migration drops users.full_name, and these exact TypeScript files still
  > read it through fullName.

  That is the difference between a generic warning and an actionable PR review.

  ## What I built

  I built MergeBrake as an open-source GitHub Action/CLI to test this idea.

  It scans Postgres migrations, maps Prisma/Drizzle schema symbols to application
  code, and comments on the pull request with:

  - the risky migration
  - the app-code references it may break
  - a SAFE / EXPAND_CONTRACT / BLOCK verdict
  - a suggested rollout plan

  It is not a migration executor and it never touches the database. It is a pre-
  merge review guard.

  The repo and case studies are here:

  https://github.com/mergebrake/mergebrake

  The dogfood results are here:

  https://github.com/mergebrake/mergebrake/tree/main/examples/dogfood

  I would be curious to hear from Prisma/Postgres teams: do you already review
  migration deploy order manually, or do you rely on CI checks for this?
Enter fullscreen mode Exit fullscreen mode

Top comments (0)