[Pg] Run CREATE INDEX CONCURRENTLY migration statements outside of transactions - #6133
Open
luskin wants to merge 3 commits into
Open
[Pg] Run CREATE INDEX CONCURRENTLY migration statements outside of transactions#6133luskin wants to merge 3 commits into
luskin wants to merge 3 commits into
Conversation
…tions CREATE INDEX CONCURRENTLY, CREATE UNIQUE INDEX CONCURRENTLY and DROP INDEX CONCURRENTLY cannot run inside a transaction block, so applying a migration generated from an index with .concurrently() always failed. When no pending migration contains such a statement, all pending migrations still run in a single transaction, exactly as before. When one does, already batched statements are committed before the concurrent statement runs on its own connection state, and each migration is applied and recorded individually so a failure never rolls back a migration that was already recorded as applied. Fixes drizzle-team#860 Co-authored-by: Cursor <cursoragent@cursor.com>
Rename journalEntry to insertMigrationSql: in this codebase "journal" refers to meta/_journal.json, while this SQL inserts into the migrations table. Name the concurrent-statement mode check, add a test that consecutive concurrent statements do not open empty transactions, use one log accessor in the unit tests, and assert the exact unique constraint in the integration test instead of any rejection. Co-authored-by: Cursor <cursoragent@cursor.com>
… in tests In Postgres grammar these clauses follow CONCURRENTLY, so detection is unaffected; the tests make that explicit. Co-authored-by: Cursor <cursoragent@cursor.com>
luskin
force-pushed
the
pg/concurrent-index-migrations
branch
from
August 14, 2026 20:05
faf6e60 to
d5081de
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #860
Problem
PgDialect.migrate()wraps all pending migrations in a single transaction. PostgreSQL forbidsCREATE INDEX CONCURRENTLY(andCREATE UNIQUE INDEX CONCURRENTLY/DROP INDEX CONCURRENTLY) inside a transaction block, so any migration generated from an index with.concurrently()failed with:drizzle-kit already generates the
CONCURRENTLYSQL; it just could not be applied bymigrate()(or bydrizzle-kit migrate, which delegates to the same code).Solution
The Postgres migrator now detects statements that start with
CREATE [UNIQUE] INDEX CONCURRENTLYorDROP INDEX CONCURRENTLY(after leading whitespace/comments) and executes them outside of the migration transaction:CREATE INDEX CONCURRENTLYwaits for open transactions to finish, so keeping our own transaction open would self-deadlock), the concurrent statement runs on the session directly, and the remaining statements resume in a new transaction. Each migration is applied and recorded in__drizzle_migrationsindividually, so a failure never rolls back a migration that was already recorded as applied.Since concurrent index builds are inherently non-transactional, a migration containing one cannot be fully atomic. If such a statement fails mid-build, PostgreSQL can leave an
INVALIDindex behind, which has to be dropped before retrying - the same caveat as runningCREATE INDEX CONCURRENTLYmanually.Notes:
neon-http,xata-http) already ran statements individually and are unaffected. All drivers that usePgDialect.migrate(node-postgres, postgres-js, pglite, neon-serverless, vercel-postgres, aws-data-api, bun-sql, netlify) inherit the fix.breakpoints: false(all statements in one string) remain unsupported forCONCURRENTLY, as before.Tests
drizzle-orm/tests/pg-migrator.test.ts(new, 24 unit tests): single-transaction behavior preserved (including rollback of all pending migrations on failure), transaction boundaries and journal placement around concurrent statements, per-migration recording, custommigrationsTable/migrationsSchema, failure ordering, and positive/negative statement classification (case-insensitivity,UNIQUE,IF NOT EXISTS, leading comments, quoted identifiers namedconcurrently, etc.).integration-tests/tests/pg/node-postgres.test.ts+integration-tests/drizzle2/pg-concurrently/fixture (new): applies a migration with twoCONCURRENTLYindexes plus a follow-up migration against real PostgreSQL, verifies both indexes exist and areindisvalid, both migrations are journaled, a secondmigrate()call is a no-op, and the unique index enforces uniqueness. Without the fix this test fails with the exact error from [BUG]: Can't create index concurrently due to transaction block #860 (verified).drizzle-ormunit suite (590 tests),drizzle-ormtype tests,integration-teststsc, existing migrator integration tests for node-postgres, postgres-js and pglite, and dprint all pass.Made with Cursor