mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-04 21:50:19 +00:00
fix(core): repair poisoned session metadata migration
This commit is contained in:
parent
0a36433062
commit
ee92348be7
2 changed files with 98 additions and 1 deletions
|
|
@ -23,6 +23,7 @@ export function applyOnly(db: Database, input: Migration[]) {
|
|||
yield* db.run(
|
||||
sql`CREATE TABLE IF NOT EXISTS ${sql.identifier("migration")} (id TEXT PRIMARY KEY, time_completed INTEGER NOT NULL)`,
|
||||
)
|
||||
const skipMigrations = !!process.env.OPENCODE_SKIP_MIGRATIONS
|
||||
let completed = new Set(
|
||||
(yield* db.all<{ id: string }>(sql`SELECT id FROM ${sql.identifier("migration")}`)).map((row) => row.id),
|
||||
)
|
||||
|
|
@ -43,12 +44,14 @@ export function applyOnly(db: Database, input: Migration[]) {
|
|||
)
|
||||
}
|
||||
}
|
||||
if (!skipMigrations) yield* repairKnownSchemaDrift(db, input, completed)
|
||||
|
||||
for (const migration of input) {
|
||||
if (completed.has(migration.id)) continue
|
||||
if (skipMigrations) continue
|
||||
yield* db.transaction((tx) =>
|
||||
Effect.gen(function* () {
|
||||
if (!process.env.OPENCODE_SKIP_MIGRATIONS) yield* migration.up(tx)
|
||||
yield* migration.up(tx)
|
||||
yield* tx.run(
|
||||
sql`INSERT INTO ${sql.identifier("migration")} (id, time_completed) VALUES (${migration.id}, ${Date.now()})`,
|
||||
)
|
||||
|
|
@ -57,3 +60,15 @@ export function applyOnly(db: Database, input: Migration[]) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
function repairKnownSchemaDrift(db: Database, input: Migration[], completed: Set<string>) {
|
||||
return Effect.gen(function* () {
|
||||
const sessionMetadata = input.find((migration) => migration.id === "20260511173437_session-metadata")
|
||||
if (!sessionMetadata) return
|
||||
if (!completed.has(sessionMetadata.id)) return
|
||||
if (!(yield* db.get(sql`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ${"session"}`))) return
|
||||
|
||||
// Older Drizzle skip-migration runs could record this id without applying its SQL.
|
||||
yield* db.transaction((tx) => sessionMetadata.up(tx))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -426,6 +426,88 @@ describe("DatabaseMigration", () => {
|
|||
)
|
||||
})
|
||||
|
||||
test("runs session metadata migration after importing older drizzle state", async () => {
|
||||
await run(
|
||||
Effect.gen(function* () {
|
||||
const db = yield* makeDb
|
||||
yield* db.run(sql`CREATE TABLE session (id text PRIMARY KEY)`)
|
||||
yield* db.run(
|
||||
sql`CREATE TABLE __drizzle_migrations (id INTEGER PRIMARY KEY, hash text NOT NULL, created_at numeric, name text, applied_at TEXT)`,
|
||||
)
|
||||
yield* db.run(sql`
|
||||
INSERT INTO __drizzle_migrations (hash, created_at, name, applied_at)
|
||||
VALUES ('hash', 1, '20260511000411_data_migration_state', ${new Date().toISOString()})
|
||||
`)
|
||||
|
||||
yield* DatabaseMigration.applyOnly(db, [sessionMetadataMigration])
|
||||
|
||||
expect(
|
||||
(yield* db.all<{ name: string }>(sql`PRAGMA table_info(session)`)).map((column) => column.name),
|
||||
).toContain("metadata")
|
||||
expect(yield* db.all(sql`SELECT id FROM migration ORDER BY id`)).toEqual([
|
||||
{ id: "20260511000411_data_migration_state" },
|
||||
{ id: "20260511173437_session-metadata" },
|
||||
])
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test("repairs imported session metadata migration state when the column is missing", async () => {
|
||||
await run(
|
||||
Effect.gen(function* () {
|
||||
const db = yield* makeDb
|
||||
yield* db.run(sql`CREATE TABLE session (id text PRIMARY KEY)`)
|
||||
yield* db.run(
|
||||
sql`CREATE TABLE __drizzle_migrations (id INTEGER PRIMARY KEY, hash text NOT NULL, created_at numeric, name text, applied_at TEXT)`,
|
||||
)
|
||||
yield* db.run(sql`
|
||||
INSERT INTO __drizzle_migrations (hash, created_at, name, applied_at)
|
||||
VALUES ('hash', 1, '20260511173437_session-metadata', ${new Date().toISOString()})
|
||||
`)
|
||||
|
||||
yield* DatabaseMigration.applyOnly(db, [sessionMetadataMigration])
|
||||
|
||||
expect(
|
||||
(yield* db.all<{ name: string }>(sql`PRAGMA table_info(session)`)).map((column) => column.name),
|
||||
).toContain("metadata")
|
||||
expect(yield* db.all(sql`SELECT id FROM migration ORDER BY id`)).toEqual([
|
||||
{ id: "20260511173437_session-metadata" },
|
||||
])
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test("does not record migrations as complete when migrations are skipped", async () => {
|
||||
const previous = process.env.OPENCODE_SKIP_MIGRATIONS
|
||||
try {
|
||||
await run(
|
||||
Effect.gen(function* () {
|
||||
const db = yield* makeDb
|
||||
yield* db.run(sql`CREATE TABLE session (id text PRIMARY KEY)`)
|
||||
|
||||
process.env.OPENCODE_SKIP_MIGRATIONS = "true"
|
||||
yield* DatabaseMigration.applyOnly(db, [sessionMetadataMigration])
|
||||
|
||||
expect(yield* db.all(sql`SELECT id FROM migration`)).toEqual([])
|
||||
expect(
|
||||
(yield* db.all<{ name: string }>(sql`PRAGMA table_info(session)`)).map((column) => column.name),
|
||||
).not.toContain("metadata")
|
||||
|
||||
delete process.env.OPENCODE_SKIP_MIGRATIONS
|
||||
yield* DatabaseMigration.applyOnly(db, [sessionMetadataMigration])
|
||||
|
||||
expect(yield* db.all(sql`SELECT id FROM migration`)).toEqual([{ id: "20260511173437_session-metadata" }])
|
||||
expect(
|
||||
(yield* db.all<{ name: string }>(sql`PRAGMA table_info(session)`)).map((column) => column.name),
|
||||
).toContain("metadata")
|
||||
}),
|
||||
)
|
||||
} finally {
|
||||
if (previous === undefined) delete process.env.OPENCODE_SKIP_MIGRATIONS
|
||||
else process.env.OPENCODE_SKIP_MIGRATIONS = previous
|
||||
}
|
||||
})
|
||||
|
||||
test("does not replay a migrated session metadata column", async () => {
|
||||
await run(
|
||||
Effect.gen(function* () {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue