fix(core): adopt consolidated migration aliases
The V1-to-V2 consolidation replaced a shipped migration chain with a new ID, causing upgraded databases to replay already-applied schema changes. Treat the terminal pre-consolidation migration as an alias and record the canonical migration without rerunning its SQL.
This commit is contained in:
@@ -13,6 +13,7 @@ const lock = Semaphore.makeUnsafe(1)
|
||||
|
||||
export type Migration = {
|
||||
id: string
|
||||
aliases?: string[]
|
||||
foreignKeys?: boolean
|
||||
up: (tx: Transaction) => Effect.Effect<void, unknown, Global.Service>
|
||||
}
|
||||
@@ -77,6 +78,14 @@ export function applyOnly(db: Database, input: Migration[]) {
|
||||
|
||||
for (const migration of input) {
|
||||
if (completed.has(migration.id)) continue
|
||||
const alias = migration.aliases?.find((id) => completed.has(id))
|
||||
if (alias) {
|
||||
yield* db.run(
|
||||
sql`INSERT INTO ${sql.identifier("migration")} (id, time_completed) VALUES (${migration.id}, ${Date.now()})`,
|
||||
)
|
||||
yield* Effect.logInfo("database migration adopted", { migration: migration.id, alias })
|
||||
continue
|
||||
}
|
||||
const started = Date.now()
|
||||
yield* Effect.logInfo("database migration started", { migration: migration.id })
|
||||
const apply = db.transaction((tx) =>
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { DatabaseMigration } from "../migration"
|
||||
|
||||
const migration: DatabaseMigration.Migration = {
|
||||
id: "20260804233008_loose_psylocke",
|
||||
aliases: ["20260730195856_optional_session_title"],
|
||||
up(tx) {
|
||||
return Effect.gen(function* () {
|
||||
yield* tx.run(`
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Database } from "@opencode-ai/core/database/database"
|
||||
import { tmpdir } from "./fixture/tmpdir"
|
||||
import type { SqlClient } from "effect/unstable/sql/SqlClient"
|
||||
import legacyCredentialsMigration from "@opencode-ai/core/database/migration/20260805200742_import_legacy_credentials"
|
||||
import consolidatedV2Migration from "@opencode-ai/core/database/migration/20260804233008_loose_psylocke"
|
||||
import { Global } from "@opencode-ai/util/global"
|
||||
|
||||
const run = <A, E>(
|
||||
@@ -114,6 +115,28 @@ describe("DatabaseMigration", () => {
|
||||
)
|
||||
})
|
||||
|
||||
test("adopts the consolidated V2 migration after the superseded chain", 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 event (id text PRIMARY KEY, created integer DEFAULT 0 NOT NULL)`)
|
||||
yield* db.run(sql`CREATE TABLE migration (id text PRIMARY KEY, time_completed integer NOT NULL)`)
|
||||
yield* db.run(sql`
|
||||
INSERT INTO migration (id, time_completed)
|
||||
VALUES ('20260730195856_optional_session_title', 1)
|
||||
`)
|
||||
|
||||
yield* DatabaseMigration.applyOnly(db, [consolidatedV2Migration])
|
||||
|
||||
expect(yield* db.get(sql`SELECT id FROM migration WHERE id = ${consolidatedV2Migration.id}`)).toEqual({
|
||||
id: consolidatedV2Migration.id,
|
||||
})
|
||||
expect(yield* db.get(sql`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'kv'`)).toBeUndefined()
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test("imports legacy JSON credentials without changing the source file or existing credentials", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const source = path.join(tmp.path, "auth.json")
|
||||
|
||||
Reference in New Issue
Block a user