mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2026-05-08 09:38:55 -04:00
Handle baseline migration for databases previously set up with db:push
When switching from db:push to db:migrate on an existing database, the migration table is empty so Drizzle tries to re-run all migrations, failing with "relation already exists". Detect this case (tables exist but no migration history) and seed the __drizzle_migrations tracking table with all current migrations so Drizzle treats them as already applied. Future migrations run normally. https://claude.ai/code/session_012sqEgNQpx676isepeoX4Mi
This commit is contained in:
@@ -6,6 +6,10 @@
|
|||||||
* This applies any pending migrations from the drizzle/ directory to the
|
* This applies any pending migrations from the drizzle/ directory to the
|
||||||
* database specified by DATABASE_URL. It is safe to run multiple times —
|
* database specified by DATABASE_URL. It is safe to run multiple times —
|
||||||
* Drizzle tracks applied migrations in the __drizzle_migrations table.
|
* Drizzle tracks applied migrations in the __drizzle_migrations table.
|
||||||
|
*
|
||||||
|
* If the database was previously set up via `db:push` (no migration history),
|
||||||
|
* this script will baseline it: seed the migration history without re-running
|
||||||
|
* the SQL, so only future migrations are applied.
|
||||||
*/
|
*/
|
||||||
import * as dotenv from "dotenv";
|
import * as dotenv from "dotenv";
|
||||||
|
|
||||||
@@ -17,6 +21,8 @@ import { Pool } from "pg";
|
|||||||
import { drizzle } from "drizzle-orm/node-postgres";
|
import { drizzle } from "drizzle-orm/node-postgres";
|
||||||
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import crypto from "crypto";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
const databaseUrl = process.env.DATABASE_URL;
|
const databaseUrl = process.env.DATABASE_URL;
|
||||||
@@ -36,9 +42,80 @@ const pool = new Pool({
|
|||||||
|
|
||||||
const db = drizzle(pool);
|
const db = drizzle(pool);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baseline: if the DB has existing tables but no migration history, seed the
|
||||||
|
* __drizzle_migrations table so Drizzle won't try to re-run already-applied SQL.
|
||||||
|
*/
|
||||||
|
async function baselineIfNeeded(client: Pool) {
|
||||||
|
// Check if migration tracking table exists and has entries
|
||||||
|
const { rows: migRows } = await client.query<{ count: string }>(`
|
||||||
|
SELECT COUNT(*)::text AS count
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'drizzle'
|
||||||
|
AND table_name = '__drizzle_migrations'
|
||||||
|
`);
|
||||||
|
const hasMigrationsTable = parseInt(migRows[0]?.count ?? "0") > 0;
|
||||||
|
|
||||||
|
if (hasMigrationsTable) {
|
||||||
|
const { rows: entryRows } = await client.query<{ count: string }>(
|
||||||
|
`SELECT COUNT(*)::text AS count FROM drizzle.__drizzle_migrations`
|
||||||
|
);
|
||||||
|
if (parseInt(entryRows[0]?.count ?? "0") > 0) {
|
||||||
|
// Migration history exists — normal flow
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No migration history. Check if the DB already has our tables (was db:push'd).
|
||||||
|
const { rows: tableRows } = await client.query<{ count: string }>(`
|
||||||
|
SELECT COUNT(*)::text AS count
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'beenvoice_account'
|
||||||
|
`);
|
||||||
|
const dbAlreadyExists = parseInt(tableRows[0]?.count ?? "0") > 0;
|
||||||
|
|
||||||
|
if (!dbAlreadyExists) {
|
||||||
|
// Fresh database — let migrate() run normally
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[migrate] Existing database detected without migration history — baselining...");
|
||||||
|
|
||||||
|
// Create the drizzle schema + migrations table if needed
|
||||||
|
await client.query(`CREATE SCHEMA IF NOT EXISTS drizzle`);
|
||||||
|
await client.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS drizzle.__drizzle_migrations (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
hash text NOT NULL,
|
||||||
|
created_at bigint
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Read the journal and seed a record for every migration file
|
||||||
|
const journal = JSON.parse(
|
||||||
|
fs.readFileSync(path.join(migrationsFolder, "meta/_journal.json"), "utf8")
|
||||||
|
) as { entries: { idx: number; tag: string; when: number }[] };
|
||||||
|
|
||||||
|
for (const entry of journal.entries) {
|
||||||
|
const sqlPath = path.join(migrationsFolder, `${entry.tag}.sql`);
|
||||||
|
const sql = fs.readFileSync(sqlPath, "utf8");
|
||||||
|
const hash = crypto.createHash("sha256").update(sql).digest("hex");
|
||||||
|
|
||||||
|
await client.query(
|
||||||
|
`INSERT INTO drizzle.__drizzle_migrations (hash, created_at) VALUES ($1, $2)`,
|
||||||
|
[hash, entry.when]
|
||||||
|
);
|
||||||
|
console.log(`[migrate] Baselined: ${entry.tag}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[migrate] Baseline complete — future migrations will apply normally");
|
||||||
|
}
|
||||||
|
|
||||||
console.log("[migrate] Running migrations from", migrationsFolder);
|
console.log("[migrate] Running migrations from", migrationsFolder);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
await baselineIfNeeded(pool);
|
||||||
await migrate(db, { migrationsFolder });
|
await migrate(db, { migrationsFolder });
|
||||||
console.log("[migrate] All migrations applied successfully");
|
console.log("[migrate] All migrations applied successfully");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user