From d1ca076be8cc51b28f00604a456fe577c10488ff Mon Sep 17 00:00:00 2001 From: Munteanu Flavius-Ioan Date: Thu, 26 Feb 2026 00:58:53 +0200 Subject: [PATCH 1/5] docs(docs): add pgfence migration safety integration guide Add pgfence as a Prisma Migrate integration that analyzes SQL migration files for dangerous PostgreSQL lock patterns before deployment. Includes install instructions, CI pipeline setup, and size-aware risk scoring. Also adds a cross-reference in the deploy docs. --- .../docs/guides/integrations/meta.json | 1 + .../docs/guides/integrations/pgfence.mdx | 160 ++++++++++++++++++ ...y-database-changes-with-prisma-migrate.mdx | 18 ++ 3 files changed, 179 insertions(+) create mode 100644 apps/docs/content/docs/guides/integrations/pgfence.mdx diff --git a/apps/docs/content/docs/guides/integrations/meta.json b/apps/docs/content/docs/guides/integrations/meta.json index 71d37468b5..993d80fd7f 100644 --- a/apps/docs/content/docs/guides/integrations/meta.json +++ b/apps/docs/content/docs/guides/integrations/meta.json @@ -7,6 +7,7 @@ "vercel-deployment", "deno", "datadog", + "pgfence", "permit-io", "shopify", "neon-accelerate", diff --git a/apps/docs/content/docs/guides/integrations/pgfence.mdx b/apps/docs/content/docs/guides/integrations/pgfence.mdx new file mode 100644 index 0000000000..dcf7e0878a --- /dev/null +++ b/apps/docs/content/docs/guides/integrations/pgfence.mdx @@ -0,0 +1,160 @@ +--- +title: pgfence +description: Analyze Prisma Migrate SQL files for dangerous lock patterns, risk levels, and safe rewrite recipes before deploying to production +url: /guides/integrations/pgfence +metaTitle: How to use pgfence with Prisma Migrate for safe PostgreSQL migrations +metaDescription: Learn how to analyze Prisma migration SQL files with pgfence to detect dangerous lock patterns, understand risk levels, and get safe rewrite recipes before deploying. +--- + +## Introduction + +[pgfence](https://pgfence.dev) is a PostgreSQL migration safety CLI that analyzes SQL migration files and reports lock modes, risk levels, and safe rewrite recipes. It uses PostgreSQL's actual parser ([libpg-query](https://github.com/pganalyze/libpg-query-node)) to understand exactly what each DDL statement does, what locks it acquires, and what it blocks. + +Prisma Migrate generates plain SQL files at `prisma/migrations/*/migration.sql`. pgfence can analyze those files directly, catching dangerous patterns before they reach production. + +Common issues pgfence detects include: + +- `CREATE INDEX` without `CONCURRENTLY` (blocks writes) +- `ALTER COLUMN TYPE` (full table rewrite with `ACCESS EXCLUSIVE` lock) +- `ADD COLUMN ... NOT NULL` without a safe default (blocks reads and writes) +- Missing `lock_timeout` settings (risk of lock queue death spirals) + +For each dangerous pattern, pgfence provides the exact safe alternative -- the expand/contract sequence you should use instead. + +## Prerequisites + +- [Node.js v20+](https://nodejs.org/) +- A Prisma project using PostgreSQL as the database provider +- Existing migrations in `prisma/migrations/` + +## 1. Install pgfence + +Add pgfence as a development dependency in your project: + +```npm +npm install -D @flvmnt/pgfence +``` + +## 2. Analyze your migrations locally + +Run pgfence against your Prisma migration files: + +```bash +npx pgfence analyze prisma/migrations/**/migration.sql +``` + +pgfence parses every SQL statement and reports the lock mode, risk level, and any safe rewrites available. + +### Understanding the output + +pgfence assigns a risk level to each statement based on the PostgreSQL lock it acquires: + +| Risk level | Meaning | +|------------|---------| +| **LOW** | Safe operations with minimal locking (e.g., `ADD COLUMN` with a constant default on PG 11+) | +| **MEDIUM** | Operations that block writes but not reads (e.g., `CREATE INDEX` without `CONCURRENTLY`) | +| **HIGH** | Operations that block both reads and writes (e.g., `ADD FOREIGN KEY` without `NOT VALID`) | +| **CRITICAL** | Operations that take `ACCESS EXCLUSIVE` locks on large tables (e.g., `DROP TABLE`, `TRUNCATE`) | + +Here is an example of pgfence analyzing a migration that adds an index without `CONCURRENTLY`: + +```sql +-- prisma/migrations/20240115_add_index/migration.sql +CREATE INDEX "User_email_idx" ON "User"("email"); +``` + +```bash +npx pgfence analyze prisma/migrations/20240115_add_index/migration.sql +``` + +pgfence will flag this as a `MEDIUM` risk because `CREATE INDEX` takes a `SHARE` lock, which blocks all writes to the table for the duration of the index build. It will suggest using `CREATE INDEX CONCURRENTLY` instead. + +:::warning +Prisma Migrate does not generate `CONCURRENTLY` variants automatically. If pgfence flags an index creation, you should manually edit the generated migration SQL file to add `CONCURRENTLY` before applying it. Note that `CREATE INDEX CONCURRENTLY` cannot run inside a transaction, so you will also need to ensure the migration runs outside a transaction block. +::: + +## 3. Use JSON output for programmatic checks + +pgfence supports JSON output, which is useful for integrating with other tools or scripts: + +```bash +npx pgfence analyze --output json prisma/migrations/**/migration.sql +``` + +You can also set a maximum risk threshold for CI pipelines. The command exits with code 1 if any statement exceeds the threshold: + +```bash +npx pgfence analyze --ci --max-risk medium prisma/migrations/**/migration.sql +``` + +## 4. Add pgfence to your CI pipeline + +Add pgfence as a safety check that runs before `prisma migrate deploy` in your CI/CD pipeline. This catches dangerous migration patterns before they reach your production database. + +Here is a GitHub Actions workflow that runs pgfence on every push that includes migration changes: + +```yaml title=".github/workflows/migration-safety.yml" +name: Migration safety check + +on: + pull_request: + paths: + - prisma/migrations/** + +jobs: + pgfence: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install dependencies + run: npm ci + + - name: Run pgfence analysis + run: npx pgfence analyze --ci --max-risk medium prisma/migrations/**/migration.sql +``` + +This workflow only triggers when migration files change. If pgfence detects any statement with risk higher than `MEDIUM`, the check fails and blocks the pull request from merging. + +:::info +You can adjust the `--max-risk` threshold to match your team's risk tolerance. Options are `low`, `medium`, `high`, and `critical`. +::: + +### Combining pgfence with deploy + +If you have an existing deployment workflow, add pgfence as a step before `prisma migrate deploy`: + +```yaml title=".github/workflows/deploy.yml" +- name: Run pgfence migration safety check + run: npx pgfence analyze --ci --max-risk medium prisma/migrations/**/migration.sql + +- name: Apply pending migrations + run: npx prisma migrate deploy + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} +``` + +## 5. Size-aware risk scoring (optional) + +pgfence can adjust risk levels based on actual table sizes. A `CREATE INDEX` on a 100-row table is very different from the same operation on a 10-million-row table. + +To use size-aware scoring without giving pgfence direct database access, export a stats snapshot from your database and pass it to pgfence: + +```bash +npx pgfence analyze --stats-file pgfence-stats.json prisma/migrations/**/migration.sql +``` + +The stats file contains row counts and table sizes from `pg_stat_user_tables`. See the [pgfence documentation](https://github.com/flvmnt/pgfence) for details on generating this file. + +## Next steps + +- [pgfence documentation and source code](https://github.com/flvmnt/pgfence) +- [pgfence on npm](https://www.npmjs.com/package/@flvmnt/pgfence) +- [Prisma Migrate overview](/orm/prisma-migrate/understanding-prisma-migrate/overview) +- [Deploying database changes with Prisma Migrate](/orm/prisma-client/deployment/deploy-database-changes-with-prisma-migrate) diff --git a/apps/docs/content/docs/orm/prisma-client/deployment/deploy-database-changes-with-prisma-migrate.mdx b/apps/docs/content/docs/orm/prisma-client/deployment/deploy-database-changes-with-prisma-migrate.mdx index bb3e212324..a4663fde80 100644 --- a/apps/docs/content/docs/orm/prisma-client/deployment/deploy-database-changes-with-prisma-migrate.mdx +++ b/apps/docs/content/docs/orm/prisma-client/deployment/deploy-database-changes-with-prisma-migrate.mdx @@ -67,3 +67,21 @@ jobs: The highlighted line shows that this action will only run if there is a change in the `prisma/migrations` directory, so `npx prisma migrate deploy` will only run when migrations are updated. Ensure you have the `DATABASE_URL` variable [set as a secret in your repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions), without quotes around the connection string. + +## Pre-deploy migration safety checks + +Before running `prisma migrate deploy`, you can analyze your migration SQL files for potentially dangerous patterns using a migration safety tool like [pgfence](/guides/integrations/pgfence). pgfence detects operations that acquire heavy locks (such as `CREATE INDEX` without `CONCURRENTLY` or `ALTER COLUMN TYPE`), reports risk levels, and provides safe rewrite recipes. + +To add pgfence as a pre-deploy step in your GitHub Actions workflow: + +```yaml +- name: Run migration safety check + run: npx pgfence analyze --ci --max-risk medium prisma/migrations/**/migration.sql + +- name: Apply all pending migrations to the database + run: npx prisma migrate deploy + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} +``` + +For a full setup guide, see the [pgfence integration guide](/guides/integrations/pgfence). From 3415b32435ae5e4eb2f3149d8c6c89de5c9edc33 Mon Sep 17 00:00:00 2001 From: Munteanu Flavius-Ioan Date: Thu, 26 Feb 2026 01:26:55 +0200 Subject: [PATCH 2/5] fix: pin pgfence version, remove hidden unicode chars, fix links --- .../content/docs/guides/integrations/pgfence.mdx | 16 ++++++++-------- ...ploy-database-changes-with-prisma-migrate.mdx | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/docs/content/docs/guides/integrations/pgfence.mdx b/apps/docs/content/docs/guides/integrations/pgfence.mdx index dcf7e0878a..d25211d3d1 100644 --- a/apps/docs/content/docs/guides/integrations/pgfence.mdx +++ b/apps/docs/content/docs/guides/integrations/pgfence.mdx @@ -40,7 +40,7 @@ npm install -D @flvmnt/pgfence Run pgfence against your Prisma migration files: ```bash -npx pgfence analyze prisma/migrations/**/migration.sql +npx --yes @flvmnt/pgfence@0.2.1 analyze prisma/migrations/**/migration.sql ``` pgfence parses every SQL statement and reports the lock mode, risk level, and any safe rewrites available. @@ -64,7 +64,7 @@ CREATE INDEX "User_email_idx" ON "User"("email"); ``` ```bash -npx pgfence analyze prisma/migrations/20240115_add_index/migration.sql +npx --yes @flvmnt/pgfence@0.2.1 analyze prisma/migrations/20240115_add_index/migration.sql ``` pgfence will flag this as a `MEDIUM` risk because `CREATE INDEX` takes a `SHARE` lock, which blocks all writes to the table for the duration of the index build. It will suggest using `CREATE INDEX CONCURRENTLY` instead. @@ -78,13 +78,13 @@ Prisma Migrate does not generate `CONCURRENTLY` variants automatically. If pgfen pgfence supports JSON output, which is useful for integrating with other tools or scripts: ```bash -npx pgfence analyze --output json prisma/migrations/**/migration.sql +npx --yes @flvmnt/pgfence@0.2.1 analyze --output json prisma/migrations/**/migration.sql ``` You can also set a maximum risk threshold for CI pipelines. The command exits with code 1 if any statement exceeds the threshold: ```bash -npx pgfence analyze --ci --max-risk medium prisma/migrations/**/migration.sql +npx --yes @flvmnt/pgfence@0.2.1 analyze --ci --max-risk medium prisma/migrations/**/migration.sql ``` ## 4. Add pgfence to your CI pipeline @@ -117,7 +117,7 @@ jobs: run: npm ci - name: Run pgfence analysis - run: npx pgfence analyze --ci --max-risk medium prisma/migrations/**/migration.sql + run: npx --yes @flvmnt/pgfence@0.2.1 analyze --ci --max-risk medium prisma/migrations/**/migration.sql ``` This workflow only triggers when migration files change. If pgfence detects any statement with risk higher than `MEDIUM`, the check fails and blocks the pull request from merging. @@ -132,7 +132,7 @@ If you have an existing deployment workflow, add pgfence as a step before `prism ```yaml title=".github/workflows/deploy.yml" - name: Run pgfence migration safety check - run: npx pgfence analyze --ci --max-risk medium prisma/migrations/**/migration.sql + run: npx --yes @flvmnt/pgfence@0.2.1 analyze --ci --max-risk medium prisma/migrations/**/migration.sql - name: Apply pending migrations run: npx prisma migrate deploy @@ -147,7 +147,7 @@ pgfence can adjust risk levels based on actual table sizes. A `CREATE INDEX` on To use size-aware scoring without giving pgfence direct database access, export a stats snapshot from your database and pass it to pgfence: ```bash -npx pgfence analyze --stats-file pgfence-stats.json prisma/migrations/**/migration.sql +npx --yes @flvmnt/pgfence@0.2.1 analyze --stats-file pgfence-stats.json prisma/migrations/**/migration.sql ``` The stats file contains row counts and table sizes from `pg_stat_user_tables`. See the [pgfence documentation](https://github.com/flvmnt/pgfence) for details on generating this file. @@ -156,5 +156,5 @@ The stats file contains row counts and table sizes from `pg_stat_user_tables`. S - [pgfence documentation and source code](https://github.com/flvmnt/pgfence) - [pgfence on npm](https://www.npmjs.com/package/@flvmnt/pgfence) -- [Prisma Migrate overview](/orm/prisma-migrate/understanding-prisma-migrate/overview) +- [Prisma Migrate overview](/orm/prisma-migrate) - [Deploying database changes with Prisma Migrate](/orm/prisma-client/deployment/deploy-database-changes-with-prisma-migrate) diff --git a/apps/docs/content/docs/orm/prisma-client/deployment/deploy-database-changes-with-prisma-migrate.mdx b/apps/docs/content/docs/orm/prisma-client/deployment/deploy-database-changes-with-prisma-migrate.mdx index a4663fde80..13886097f7 100644 --- a/apps/docs/content/docs/orm/prisma-client/deployment/deploy-database-changes-with-prisma-migrate.mdx +++ b/apps/docs/content/docs/orm/prisma-client/deployment/deploy-database-changes-with-prisma-migrate.mdx @@ -76,7 +76,7 @@ To add pgfence as a pre-deploy step in your GitHub Actions workflow: ```yaml - name: Run migration safety check - run: npx pgfence analyze --ci --max-risk medium prisma/migrations/**/migration.sql + run: npx --yes @flvmnt/pgfence@0.2.1 analyze --ci --max-risk medium prisma/migrations/**/migration.sql - name: Apply all pending migrations to the database run: npx prisma migrate deploy From 5f97f6363b6d52e8722eb086ac0ccb315bb51214 Mon Sep 17 00:00:00 2001 From: Munteanu Flavius-Ioan Date: Thu, 26 Feb 2026 01:38:25 +0200 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20resolve=20CodeRabbit=20findings=20?= =?UTF-8?q?=E2=80=94=20match=20trigger=20text,=20strip=20hidden=20chars?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrected workflow description to say "every pull request" to match the actual pull_request trigger in the YAML. Verified no hidden Unicode characters remain and all npx commands are pinned to @0.2.1. --- apps/docs/content/docs/guides/integrations/pgfence.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/content/docs/guides/integrations/pgfence.mdx b/apps/docs/content/docs/guides/integrations/pgfence.mdx index d25211d3d1..2450841f00 100644 --- a/apps/docs/content/docs/guides/integrations/pgfence.mdx +++ b/apps/docs/content/docs/guides/integrations/pgfence.mdx @@ -91,7 +91,7 @@ npx --yes @flvmnt/pgfence@0.2.1 analyze --ci --max-risk medium prisma/migrations Add pgfence as a safety check that runs before `prisma migrate deploy` in your CI/CD pipeline. This catches dangerous migration patterns before they reach your production database. -Here is a GitHub Actions workflow that runs pgfence on every push that includes migration changes: +Here is a GitHub Actions workflow that runs pgfence on every pull request that includes migration changes: ```yaml title=".github/workflows/migration-safety.yml" name: Migration safety check From f43a68dfc3a8cf26de4247c69d7f5796dd48a6a5 Mon Sep 17 00:00:00 2001 From: Munteanu Flavius-Ioan Date: Thu, 26 Feb 2026 01:46:41 +0200 Subject: [PATCH 4/5] fix: pin install version, improve stats file docs link --- apps/docs/content/docs/guides/integrations/pgfence.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/content/docs/guides/integrations/pgfence.mdx b/apps/docs/content/docs/guides/integrations/pgfence.mdx index 2450841f00..161ca8a3a6 100644 --- a/apps/docs/content/docs/guides/integrations/pgfence.mdx +++ b/apps/docs/content/docs/guides/integrations/pgfence.mdx @@ -32,7 +32,7 @@ For each dangerous pattern, pgfence provides the exact safe alternative -- the e Add pgfence as a development dependency in your project: ```npm -npm install -D @flvmnt/pgfence +npm install -D @flvmnt/pgfence@0.2.1 ``` ## 2. Analyze your migrations locally @@ -150,7 +150,7 @@ To use size-aware scoring without giving pgfence direct database access, export npx --yes @flvmnt/pgfence@0.2.1 analyze --stats-file pgfence-stats.json prisma/migrations/**/migration.sql ``` -The stats file contains row counts and table sizes from `pg_stat_user_tables`. See the [pgfence documentation](https://github.com/flvmnt/pgfence) for details on generating this file. +The stats file contains row counts and table sizes from `pg_stat_user_tables`. Run `npx --yes @flvmnt/pgfence@0.2.1 extract-stats --db-url ` to generate this file, or see the [pgfence README](https://github.com/flvmnt/pgfence#db-size-aware-risk-scoring) for details. ## Next steps From 664ee220ecfe1f3db22b0b23c12783bc546ac50f Mon Sep 17 00:00:00 2001 From: Munteanu Flavius-Ioan Date: Thu, 26 Feb 2026 01:59:41 +0200 Subject: [PATCH 5/5] fix: correct HIGH risk lock description and add globstar to CI examples - HIGH risk: SHARE ROW EXCLUSIVE blocks writes and competing DDL, not plain reads. Only ACCESS EXCLUSIVE blocks reads. - Add `shopt -s globstar` before ** glob patterns in GitHub Actions workflow examples for correctness. --- apps/docs/content/docs/guides/integrations/pgfence.mdx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/docs/content/docs/guides/integrations/pgfence.mdx b/apps/docs/content/docs/guides/integrations/pgfence.mdx index 161ca8a3a6..06626edd6e 100644 --- a/apps/docs/content/docs/guides/integrations/pgfence.mdx +++ b/apps/docs/content/docs/guides/integrations/pgfence.mdx @@ -53,7 +53,7 @@ pgfence assigns a risk level to each statement based on the PostgreSQL lock it a |------------|---------| | **LOW** | Safe operations with minimal locking (e.g., `ADD COLUMN` with a constant default on PG 11+) | | **MEDIUM** | Operations that block writes but not reads (e.g., `CREATE INDEX` without `CONCURRENTLY`) | -| **HIGH** | Operations that block both reads and writes (e.g., `ADD FOREIGN KEY` without `NOT VALID`) | +| **HIGH** | Operations that block writes and competing DDL, but not plain reads (e.g., `ADD FOREIGN KEY` without `NOT VALID`) | | **CRITICAL** | Operations that take `ACCESS EXCLUSIVE` locks on large tables (e.g., `DROP TABLE`, `TRUNCATE`) | Here is an example of pgfence analyzing a migration that adds an index without `CONCURRENTLY`: @@ -117,7 +117,9 @@ jobs: run: npm ci - name: Run pgfence analysis - run: npx --yes @flvmnt/pgfence@0.2.1 analyze --ci --max-risk medium prisma/migrations/**/migration.sql + run: | + shopt -s globstar + npx --yes @flvmnt/pgfence@0.2.1 analyze --ci --max-risk medium prisma/migrations/**/migration.sql ``` This workflow only triggers when migration files change. If pgfence detects any statement with risk higher than `MEDIUM`, the check fails and blocks the pull request from merging. @@ -132,7 +134,9 @@ If you have an existing deployment workflow, add pgfence as a step before `prism ```yaml title=".github/workflows/deploy.yml" - name: Run pgfence migration safety check - run: npx --yes @flvmnt/pgfence@0.2.1 analyze --ci --max-risk medium prisma/migrations/**/migration.sql + run: | + shopt -s globstar + npx --yes @flvmnt/pgfence@0.2.1 analyze --ci --max-risk medium prisma/migrations/**/migration.sql - name: Apply pending migrations run: npx prisma migrate deploy