Skip to content

fix(db-postgres): keep generated down migrations idempotent#16767

Open
youcefzemmar wants to merge 1 commit into
payloadcms:mainfrom
youcefzemmar:fix/auto-generated-migrations-have-problems-with-the-d
Open

fix(db-postgres): keep generated down migrations idempotent#16767
youcefzemmar wants to merge 1 commit into
payloadcms:mainfrom
youcefzemmar:fix/auto-generated-migrations-have-problems-with-the-d

Conversation

@youcefzemmar
Copy link
Copy Markdown

What

Auto-generated migrations on Postgres can fail on the down step (and sometimes on the up step when a collection is dropped) because drizzle-kit emits explicit DROP CONSTRAINT / DROP INDEX statements that follow a DROP TABLE … CASCADE. By the time those explicit drops run, Postgres has already removed the dependent objects, so each subsequent drop errors out with constraint … does not exist / index … does not exist.

This PR rewrites the generated statements so each DROP CONSTRAINT and DROP INDEX carries IF EXISTS. The rewrite happens once, at migration-generation time inside sanitizeStatements, so existing user migrations are not touched — only newly created files.

Why

DROP TABLE "x" CASCADE; followed by ALTER TABLE "x_rels" DROP CONSTRAINT "x_rels_x_fk"; is a common shape in down migrations that delete a collection. The CASCADE has already removed x_rels_x_fk as a side effect, so the explicit drop fails and the whole migration aborts mid-way. IF EXISTS lets drizzle-kit's ordering survive Postgres's dependency-resolution behavior without changing the generator upstream.

The same pattern already lives in the v2→v3 predefined migration (groupUpSQLStatements.ts:156), which converts DROP CONSTRAINT to DROP CONSTRAINT IF EXISTS for the same reason. This PR generalizes that fix to every Postgres migration the adapter writes.

Fix

  • New utility packages/drizzle/src/utilities/makePostgresDropsIdempotent.ts — pure transform over string[] of generated SQL. Anchored on \bDROP CONSTRAINT\b / \bDROP INDEX\b with a negative lookahead for IF EXISTS, so it's safe to run more than once and won't touch CREATE INDEX or quoted strings that don't contain those tokens.
  • Re-exported via @payloadcms/drizzle/postgres.
  • Wired into sanitizeStatements in packages/db-postgres/src/index.ts and packages/db-vercel-postgres/src/index.ts. SQLite adapters are unaffected — SQLite doesn't support ALTER TABLE … DROP CONSTRAINT and doesn't exhibit the regression.

Testing

  • Added packages/drizzle/src/utilities/makePostgresDropsIdempotent.spec.ts covering: single-statement rewrites for both DROP CONSTRAINT and DROP INDEX, idempotency when IF EXISTS is already present, no-ops on CREATE INDEX / DROP TABLE / ADD COLUMN, and multi-drop ALTER TABLE statements.
  • Verified the regex behavior directly with node against the same five cases — all pass.
  • pnpm install is broken in my local sandbox (unable to open database file from pnpm's content-addressable store), so I couldn't run the project's vitest/eslint locally; the spec runs in CI under the unit project (vitest.config.ts:52).

Notes

  • The existing test/database/up-down-migration/int.spec.ts is gated on PAYLOAD_DATABASE=postgres and won't exercise this branch in default CI. Left untouched on purpose — the unit test of the transform is the load-bearing test for this fix, and the int spec would need a multi-config setup (create collection, migrate up, drop collection, migrate up/down) to reproduce end-to-end. Happy to follow up on that scenario if you'd like it in this PR.
  • The transformation runs at generation time, so user-owned existing migration files in repos are not rewritten — consistent with how Payload treats those files as authored artifacts.

Fixes #14800.

Generated migrations sequence the dependent drops drizzle-kit emits,
but Postgres also drops foreign keys when its referenced table is
dropped with CASCADE. Subsequent ALTER TABLE … DROP CONSTRAINT / DROP
INDEX statements in the same script then fail because the objects are
already gone. Rewrite those drops to use IF EXISTS in db-postgres and
db-vercel-postgres before the SQL is written to the migration file.

Fixes payloadcms#14800.
@youcefzemmar youcefzemmar marked this pull request as ready for review May 28, 2026 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto-generated migrations have problems with the down migration

1 participant