<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: MergeBrake</title>
    <description>The latest articles on DEV Community by MergeBrake (@mergebrake_dc48d45a6a70d9c2d).</description>
    <link>https://dev.to/mergebrake_dc48d45a6a70d9c2d</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3928892%2F787a823a-1776-491b-80b8-53e4f4c10b35.png</url>
      <title>DEV Community: MergeBrake</title>
      <link>https://dev.to/mergebrake_dc48d45a6a70d9c2d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mergebrake_dc48d45a6a70d9c2d"/>
    <language>en</language>
    <item>
      <title>I scanned 1,066 Prisma migrations. Here are the risky patterns that kept showing up</title>
      <dc:creator>MergeBrake</dc:creator>
      <pubDate>Wed, 13 May 2026 09:09:30 +0000</pubDate>
      <link>https://dev.to/mergebrake_dc48d45a6a70d9c2d/i-scanned-1066-prisma-migrations-here-are-the-risky-patterns-that-kept-showing-up-27gd</link>
      <guid>https://dev.to/mergebrake_dc48d45a6a70d9c2d/i-scanned-1066-prisma-migrations-here-are-the-risky-patterns-that-kept-showing-up-27gd</guid>
      <description>&lt;p&gt;I scanned 1,066 Prisma migrations. Here are the risky patterns that kept showing&lt;br&gt;
  up&lt;/p&gt;

&lt;p&gt;I wanted to understand what risky database migrations look like in real-world&lt;br&gt;
  Prisma projects, not toy examples.&lt;/p&gt;

&lt;p&gt;So I ran an open-source migration scanner across the full migration history of&lt;br&gt;
  three public Postgres + Prisma repositories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;documenso&lt;/li&gt;
&lt;li&gt;trigger.dev&lt;/li&gt;
&lt;li&gt;formbricks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In total:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1,066 migration files&lt;/li&gt;
&lt;li&gt;2,339 findings&lt;/li&gt;
&lt;li&gt;21 rule types&lt;/li&gt;
&lt;li&gt;Postgres AST parsing, not regex&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;## The most common risky patterns&lt;/p&gt;

&lt;p&gt;Two patterns dominated the dataset.&lt;/p&gt;

&lt;p&gt;### 1. Foreign keys added without &lt;code&gt;NOT VALID&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Postgres validates a new foreign key against existing rows when the constraint&lt;br&gt;
  is added.&lt;/p&gt;

&lt;p&gt;On larger tables, that can create lock and rollout risk.&lt;/p&gt;

&lt;p&gt;A safer pattern is often:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
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:

  &amp;gt; This migration drops a column.

  But the question reviewers really need answered is:

  &amp;gt; 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:

  &amp;gt; This migration is risky.

  It is:

  &amp;gt; This migration drops users.full_name, and these exact TypeScript files still
  &amp;gt; 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?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>postgres</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
