Skip to content

Commit 76d435d

Browse files
committed
Seed Drizzle journal for applied migrations
1 parent d2cf8b6 commit 76d435d

1 file changed

Lines changed: 61 additions & 6 deletions

File tree

packages/db/src/migrate.ts

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,54 @@ interface JournalEntry {
2828
}
2929

3030
/**
31-
* If Drizzle's journal table is empty but app tables already exist (from db:push),
32-
* seed the journal with hashes for all existing migration files so Drizzle skips them.
31+
* Check whether a single migration's schema changes have already been applied.
32+
* Supports CREATE TABLE and ALTER TABLE ADD COLUMN statements.
33+
* Returns true only if every statement in the migration is already reflected in the DB.
34+
*/
35+
async function isMigrationApplied(pool: mysql.Pool, migrationSql: string): Promise<boolean> {
36+
const statements = migrationSql
37+
.split(';')
38+
.map((s) => s.trim())
39+
.filter(Boolean)
40+
41+
for (const stmt of statements) {
42+
// ALTER TABLE `x` ADD `y` ...
43+
const addCol = stmt.match(
44+
/ALTER\s+TABLE\s+`(\w+)`\s+ADD\s+(?:COLUMN\s+)?`(\w+)`/i,
45+
)
46+
if (addCol) {
47+
const [rows] = await pool.query(
48+
'SELECT COUNT(*) AS cnt FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?',
49+
[addCol[1], addCol[2]],
50+
)
51+
if ((rows as Array<{ cnt: number }>)[0]?.cnt === 0) return false
52+
continue
53+
}
54+
55+
// CREATE TABLE `x` ...
56+
const createTbl = stmt.match(
57+
/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?`(\w+)`/i,
58+
)
59+
if (createTbl) {
60+
const [rows] = await pool.query('SHOW TABLES LIKE ?', [createTbl[1]])
61+
if ((rows as unknown[]).length === 0) return false
62+
continue
63+
}
64+
65+
// Statement type we can't verify — assume not applied
66+
return false
67+
}
68+
69+
return statements.length > 0
70+
}
71+
72+
/**
73+
* Ensure the Drizzle journal reflects migrations whose schema changes are
74+
* already present in the database (e.g. applied via `db:push`).
75+
*
76+
* Handles both empty journals and partially populated ones — any migration
77+
* whose hash is missing from the journal but whose changes already exist in
78+
* the DB will be seeded so Drizzle doesn't try to re-run it.
3379
*/
3480
async function seedDrizzleJournalIfNeeded(pool: mysql.Pool): Promise<void> {
3581
// Ensure the journal table exists
@@ -41,14 +87,16 @@ async function seedDrizzleJournalIfNeeded(pool: mysql.Pool): Promise<void> {
4187
)
4288
`)
4389

44-
const [rows] = await pool.query('SELECT COUNT(*) as cnt FROM `__drizzle_migrations`')
45-
const count = (rows as Array<{ cnt: number }>)[0]?.cnt ?? 0
46-
if (count > 0) return // journal already populated
47-
4890
// Check if app tables exist (canary: sandboxes)
4991
const [tables] = await pool.query(`SHOW TABLES LIKE 'sandboxes'`)
5092
if ((tables as unknown[]).length === 0) return // fresh DB, nothing to seed
5193

94+
// Collect hashes already in the journal
95+
const [existingRows] = await pool.query('SELECT `hash` FROM `__drizzle_migrations`')
96+
const existingHashes = new Set(
97+
(existingRows as Array<{ hash: string }>).map((r) => r.hash),
98+
)
99+
52100
// Read the Drizzle journal to find all migration entries
53101
const journalPath = resolve(__dirname, '..', 'drizzle', 'meta', '_journal.json')
54102
const journal = JSON.parse(readFileSync(journalPath, 'utf-8')) as {
@@ -59,6 +107,13 @@ async function seedDrizzleJournalIfNeeded(pool: mysql.Pool): Promise<void> {
59107
const sqlPath = resolve(__dirname, '..', 'drizzle', `${entry.tag}.sql`)
60108
const sql = readFileSync(sqlPath, 'utf-8')
61109
const hash = createHash('sha256').update(sql).digest('hex')
110+
111+
if (existingHashes.has(hash)) continue // already recorded
112+
113+
// Only seed if the migration's changes are already in the DB
114+
const applied = await isMigrationApplied(pool, sql)
115+
if (!applied) continue
116+
62117
await pool.query(
63118
'INSERT INTO `__drizzle_migrations` (`hash`, `created_at`) VALUES (?, ?)',
64119
[hash, entry.when],

0 commit comments

Comments
 (0)