fix(db): make document revision migration replay-safe

This commit is contained in:
dotta 2026-03-31 08:09:00 -05:00
parent 761dce559d
commit 90889c12d8
2 changed files with 79 additions and 5 deletions

View file

@ -169,4 +169,76 @@ describeEmbeddedPostgres("applyPendingMigrations", () => {
},
20_000,
);
it(
"replays migration 0046 safely when document revision columns already exist",
async () => {
const connectionString = await createTempDatabase();
await applyPendingMigrations(connectionString);
const sql = postgres(connectionString, { max: 1, onnotice: () => {} });
try {
const smoothSentinelsHash = await migrationHash("0046_smooth_sentinels.sql");
await sql.unsafe(
`DELETE FROM "drizzle"."__drizzle_migrations" WHERE hash = '${smoothSentinelsHash}'`,
);
const columns = await sql.unsafe<{ column_name: string; is_nullable: string; column_default: string | null }[]>(
`
SELECT column_name, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'document_revisions'
AND column_name IN ('title', 'format')
ORDER BY column_name
`,
);
expect(columns).toHaveLength(2);
} finally {
await sql.end();
}
const pendingState = await inspectMigrations(connectionString);
expect(pendingState).toMatchObject({
status: "needsMigrations",
pendingMigrations: ["0046_smooth_sentinels.sql"],
reason: "pending-migrations",
});
await applyPendingMigrations(connectionString);
const finalState = await inspectMigrations(connectionString);
expect(finalState.status).toBe("upToDate");
const verifySql = postgres(connectionString, { max: 1, onnotice: () => {} });
try {
const columns = await verifySql.unsafe<{ column_name: string; is_nullable: string; column_default: string | null }[]>(
`
SELECT column_name, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'document_revisions'
AND column_name IN ('title', 'format')
ORDER BY column_name
`,
);
expect(columns).toEqual([
expect.objectContaining({
column_name: "format",
is_nullable: "NO",
}),
expect.objectContaining({
column_name: "title",
is_nullable: "YES",
}),
]);
expect(columns[0]?.column_default).toContain("'markdown'");
} finally {
await verifySql.end();
}
},
20_000,
);
});

View file

@ -1,9 +1,11 @@
ALTER TABLE "document_revisions" ADD COLUMN "title" text;--> statement-breakpoint
ALTER TABLE "document_revisions" ADD COLUMN "format" text DEFAULT 'markdown' NOT NULL;
ALTER TABLE "document_revisions" ADD COLUMN IF NOT EXISTS "title" text;--> statement-breakpoint
ALTER TABLE "document_revisions" ADD COLUMN IF NOT EXISTS "format" text;--> statement-breakpoint
ALTER TABLE "document_revisions" ALTER COLUMN "format" SET DEFAULT 'markdown';
--> statement-breakpoint
UPDATE "document_revisions" AS "dr"
SET
"title" = "d"."title",
"format" = COALESCE("d"."format", 'markdown')
"title" = COALESCE("dr"."title", "d"."title"),
"format" = COALESCE("dr"."format", "d"."format", 'markdown')
FROM "documents" AS "d"
WHERE "d"."id" = "dr"."document_id";
WHERE "d"."id" = "dr"."document_id";--> statement-breakpoint
ALTER TABLE "document_revisions" ALTER COLUMN "format" SET NOT NULL;