refactor(core): move database schema ownership (#29068)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,17 +1,14 @@
|
||||
import type { Argv } from "yargs"
|
||||
import { spawn } from "child_process"
|
||||
import { Database } from "@/storage/db"
|
||||
import { drizzle } from "drizzle-orm/bun-sqlite"
|
||||
import { Database as BunDatabase } from "bun:sqlite"
|
||||
import { UI } from "../ui"
|
||||
import { cmd } from "./cmd"
|
||||
import { JsonMigration } from "@/storage/json-migration"
|
||||
import { EOL } from "os"
|
||||
import { errorMessage } from "../../util/error"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { Effect } from "effect"
|
||||
import { sql } from "drizzle-orm"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
|
||||
const QueryCommand = cmd({
|
||||
const QueryCommand = effectCmd({
|
||||
command: "$0 [query]",
|
||||
describe: "open an interactive sqlite3 shell or run a query",
|
||||
instance: false,
|
||||
builder: (yargs: Argv) => {
|
||||
return yargs
|
||||
.positional("query", {
|
||||
@@ -25,96 +22,41 @@ const QueryCommand = cmd({
|
||||
describe: "Output format",
|
||||
})
|
||||
},
|
||||
handler: async (args: { query?: string; format: string }) => {
|
||||
handler: Effect.fn("Cli.db.query")(function* (args: { query?: string; format: string }) {
|
||||
const query = args.query as string | undefined
|
||||
if (query) {
|
||||
const db = new BunDatabase(Database.getPath(), { readonly: true })
|
||||
try {
|
||||
const result = db.query(query).all() as Record<string, unknown>[]
|
||||
if (args.format === "json") {
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
} else if (result.length > 0) {
|
||||
const keys = Object.keys(result[0])
|
||||
console.log(keys.join("\t"))
|
||||
for (const row of result) {
|
||||
console.log(keys.map((k) => row[k]).join("\t"))
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
UI.error(errorMessage(err))
|
||||
process.exit(1)
|
||||
const { db } = yield* Database.Service
|
||||
const result = yield* db.all<Record<string, unknown>>(sql.raw(query)).pipe(Effect.orDie)
|
||||
if (args.format === "json") console.log(JSON.stringify(result, null, 2))
|
||||
else if (result.length > 0) {
|
||||
const keys = Object.keys(result[0])
|
||||
console.log(keys.join("\t"))
|
||||
for (const row of result) console.log(keys.map((key) => row[key]).join("\t"))
|
||||
}
|
||||
db.close()
|
||||
return
|
||||
}
|
||||
const child = spawn("sqlite3", [Database.getPath()], {
|
||||
const child = spawn("sqlite3", [Database.path()], {
|
||||
stdio: "inherit",
|
||||
})
|
||||
await new Promise((resolve) => child.on("close", resolve))
|
||||
},
|
||||
yield* Effect.promise(() => new Promise((resolve) => child.on("close", resolve)))
|
||||
}),
|
||||
})
|
||||
|
||||
const PathCommand = cmd({
|
||||
const PathCommand = effectCmd({
|
||||
command: "path",
|
||||
describe: "print the database path",
|
||||
handler: () => {
|
||||
console.log(Database.getPath())
|
||||
},
|
||||
instance: false,
|
||||
handler: Effect.fn("Cli.db.path")(function* () {
|
||||
console.log(Database.path())
|
||||
}),
|
||||
})
|
||||
|
||||
const MigrateCommand = cmd({
|
||||
command: "migrate",
|
||||
describe: "migrate JSON data to SQLite (merges with existing data)",
|
||||
handler: async () => {
|
||||
const sqlite = new BunDatabase(Database.getPath())
|
||||
const tty = process.stderr.isTTY
|
||||
const width = 36
|
||||
const orange = "\x1b[38;5;214m"
|
||||
const muted = "\x1b[0;2m"
|
||||
const reset = "\x1b[0m"
|
||||
let last = -1
|
||||
if (tty) process.stderr.write("\x1b[?25l")
|
||||
try {
|
||||
const stats = await JsonMigration.run(drizzle({ client: sqlite }), {
|
||||
progress: (event) => {
|
||||
const percent = Math.floor((event.current / event.total) * 100)
|
||||
if (percent === last) return
|
||||
last = percent
|
||||
if (tty) {
|
||||
const fill = Math.round((percent / 100) * width)
|
||||
const bar = `${"■".repeat(fill)}${"・".repeat(width - fill)}`
|
||||
process.stderr.write(
|
||||
`\r${orange}${bar} ${percent.toString().padStart(3)}%${reset} ${muted}${event.current}/${event.total}${reset} `,
|
||||
)
|
||||
} else {
|
||||
process.stderr.write(`sqlite-migration:${percent}${EOL}`)
|
||||
}
|
||||
},
|
||||
})
|
||||
if (tty) process.stderr.write("\n")
|
||||
if (tty) process.stderr.write("\x1b[?25h")
|
||||
else process.stderr.write(`sqlite-migration:done${EOL}`)
|
||||
UI.println(
|
||||
`Migration complete: ${stats.projects} projects, ${stats.sessions} sessions, ${stats.messages} messages`,
|
||||
)
|
||||
if (stats.errors.length > 0) {
|
||||
UI.println(`${stats.errors.length} errors occurred during migration`)
|
||||
}
|
||||
} catch (err) {
|
||||
if (tty) process.stderr.write("\x1b[?25h")
|
||||
UI.error(`Migration failed: ${errorMessage(err)}`)
|
||||
process.exit(1)
|
||||
} finally {
|
||||
sqlite.close()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const DbCommand = cmd({
|
||||
export const DbCommand = effectCmd({
|
||||
command: "db",
|
||||
describe: "database tools",
|
||||
instance: false,
|
||||
builder: (yargs: Argv) => {
|
||||
return yargs.command(QueryCommand).command(PathCommand).command(MigrateCommand).demandCommand()
|
||||
return yargs.command(QueryCommand).command(PathCommand).demandCommand()
|
||||
},
|
||||
handler: () => {},
|
||||
handler: Effect.fn("Cli.db")(function* () {}),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user