Skip to content

[All]: Add rollback/down migrations support#5476

Open
roor0 wants to merge 4 commits intodrizzle-team:mainfrom
roor0:feat/down-migrations
Open

[All]: Add rollback/down migrations support#5476
roor0 wants to merge 4 commits intodrizzle-team:mainfrom
roor0:feat/down-migrations

Conversation

@roor0
Copy link

@roor0 roor0 commented Mar 12, 2026

Closes #2352
Closes #4005

Summary

  • Adds rollback() function to all database drivers (PostgreSQL, MySQL, SingleStore, SQLite, LibSQL, D1, Durable Objects, Expo, etc.) that undoes applied migrations by running companion .down.sql files
  • Adds --down flag to drizzle-kit generate to produce .down.sql files alongside forward migrations, with rename-aware generation for all dialects
  • Adds hasDown field to migration journal entries and downMigrations block to embedded/bundled migration output
  • Fixes hash collision bug in SQLite rollback by using rowid for deletion instead of hash (critical for bundled migrations where hash is empty)
  • Supports rename-aware down SQL generation across all dialects (PostgreSQL, MySQL, SingleStore, SQLite, LibSQL) — renames produce proper RENAME statements in rollback SQL instead of destructive DROP/CREATE

Test plan

  • Unit tests for down SQL file generation and embedded migration bundling (drizzle-kit/tests/migrate/down-sql.test.ts)
  • Integration tests for SQLite rollback with better-sqlite3 (integration-tests/tests/sqlite/rollback.test.ts)
  • Integration tests for PostgreSQL rollback with PGlite (integration-tests/tests/pg/pglite.test.ts)
  • Bundled migration rollback tests verifying rowid-based deletion fixes hash collision bug
  • Tests for rename-aware down SQL generation for SQLite and LibSQL

roor0 added 4 commits March 6, 2026 17:54
Implements bidirectional migrations across all dialects and drivers.

drizzle-kit:
- `writeResult()` auto-generates and writes `<tag>.down.sql` alongside each `<tag>.sql` by calling the diff function with prev/cur arguments swapped (no-rename auto-resolvers used so it is non-interactive)
- `embeddedMigrations()` bundles `.down.sql` imports for expo-sqlite, op-sqlite, and durable-sqlite
- `Journal` entry type gains optional `hasDown: boolean` flag

drizzle-orm/src/migrator.ts:
- `MigrationMeta` gains `downSql?: string[]`
- `readMigrationFiles()` reads `<tag>.down.sql` alongside each migration if it exists

Dialect rollback() methods (pg-core, mysql-core, singlestore-core, sqlite-core sync+async):
- Query the last `steps` rows from `__drizzle_migrations` DESC
- Match each by hash against the in-memory migration list
- Throw a descriptive error if the migration file or down SQL is missing
- Execute down SQL statements in reverse order inside a transaction
- DELETE the rolled-back rows from `__drizzle_migrations`

Per-driver rollback() exports added to all 22 driver migrator files:
- Standard drivers (node-postgres, postgres-js, pglite, neon-serverless, vercel-postgres, aws-data-api/pg, mysql2, planetscale-serverless, tidb-serverless, singlestore, better-sqlite3, bun-sqlite, bun-sql, sql-js): delegate to dialect.rollback()
- Proxy drivers (pg-proxy, mysql-proxy, sqlite-proxy, singlestore-proxy): build queries array and pass to existing ProxyMigrator callback
- Batch drivers (libsql, d1): collect statements and use session.migrate()/session.batch()
- Embedded drivers (expo-sqlite, op-sqlite, durable-sqlite): MigrationConfig gains optional `downMigrations` bundle field; rollback works with the same embedded bundle pattern as migrate()
- neon-http, xata-http: custom implementations without transactions (noted in JSDoc)
drizzle-kit/tests/migrate/down-sql.test.ts (10 tests):
- writeResult() writes .down.sql when downSqlStatements are provided
- writeResult() does not write .down.sql when downSqlStatements is undefined
- writeResult() sets hasDown: true in journal entry when down SQL is provided
- writeResult() does not set hasDown when downSqlStatements is undefined
- writeResult() does not set hasDown when downSqlStatements is empty array
- writeResult() respects breakpoints delimiter in .down.sql
- embeddedMigrations() includes downMigrations block when entries have hasDown
- embeddedMigrations() omits downMigrations block when no entries have hasDown
- embeddedMigrations() only imports .down.sql for entries that have hasDown
- embeddedMigrations() adds expo header for expo driver

integration-tests/tests/sqlite/rollback.test.ts (13 tests):
- readMigrationFiles() populates downSql when .down.sql exists
- readMigrationFiles() leaves downSql undefined when .down.sql is absent
- readMigrationFiles() leaves downSql undefined when .down.sql is empty
- readMigrationFiles() leaves downSql undefined when .down.sql is whitespace only
- readMigrationFiles() splits downSql on statement-breakpoint delimiter
- readMigrationFiles() reads downSql independently per migration
- rollback(1) removes last migration table and tracking row
- rollback(2) undoes both migrations
- rollback with default steps=1 rolls back one migration
- rollback when no migrations applied is a no-op
- rollback then migrate re-applies the migration
- rollback throws when migration file not found by hash
- rollback throws when migration has no down SQL

Adds migration fixtures in integration-tests/drizzle2/sqlite-rollback/
with two migrations each having .down.sql files.
Wrap the forward resolvers with capture helpers so rename decisions
(table, column, view) made during `generate` are inverted for the
down SQL diff, matching the rename-aware approach already applied to
PostgreSQL, MySQL, and SingleStore.
Use rowid instead of hash for SQLite migration deletion to fix hash
collision bugs with bundled migrations (empty hash). Add PGlite rollback
integration tests and bundled migration rollback tests. Fix down-sql
test assertions for updated key format.
@roor0 roor0 changed the title feat: add rollback/down migrations support [All]: Add rollback/down migrations support Mar 12, 2026
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.

[FEATURE]: Reverse/Down Migrations [FEATURE]: Migration Rollback feature

1 participant