feat(迁移): 0000 改成可执行,空库能自举
0000_crazy_gateway.sql 原本是 drizzle-kit pull 的产物,整份被 /* */ 包着、 可执行语句 0 条,所以任何新库的结构都只能先手工 psql 灌一遍 schema.sql 再打基线。 现在它的内容由 docs/specs/schema.sql(2026-08-07 的生产 pg_dump --schema-only) 机械转换而来:去掉 psql 专有指令 12 条、去掉 7 张 Django 遗留表及其索引外键 41 条, 保留 212 条语句、顺序不动。转换脚本入库在 docs/spikes/。 不含 Django 那 7 张表,是因为 meta/0000_snapshot.json 从来就没有它们(pull 当时 tablesFilter 滤掉了),不建它们才和快照一致;0002 那串 DROP ... IF EXISTS 在新库上 空转、在生产库上真删,两边跑同一串迁移落点相同。 改 0000 对生产库没有影响:migrator 只比 created_at、从不校验 hash,而生产库那行 baseline-0000-faked 早把它挡在门外了。 migrate.ts 配套:空库直接从 0000 建起(并自建 drizzle 记账表——原来这步由 drizzle 的 migrate() 顺手做掉);自举时不触发破坏性闸门,因为空库上没有数据可丢,拦下来只会逼 每个新环境都带一次 OJ2_ALLOW_DESTRUCTIVE,把这道闸训练成习惯动作。「有表但没基线」 仍然 exit 3。 验证:空库自举出来的结构,和「灌 schema.sql + 打基线 + 跑迁移」这条老路子跑出来的 结构,pg_dump --schema-only 逐字节一致(734 行,零差异)。 转换时踩到两个坑,都写进注释了:注释里不能出现 statement-breakpoint 的字面量 (readMigrationFiles 纯文本切分,会把注释从中间切开);过滤 Django 对象要看 public.X 形式的对象引用,不能扫语句字样——user 表有一列叫 auth_token。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -77,26 +77,35 @@ export async function runMigrations() {
|
||||
from drizzle.__drizzle_migrations
|
||||
`.catch(() => null)
|
||||
|
||||
// 基线缺失。这个库没法靠迁移自举 —— 0000 是 `drizzle-kit pull` 的产物,
|
||||
// 整个文件被块注释包着,一条可执行语句都没有。结构只能来自 docs/specs/schema.sql
|
||||
// 或生产 dump,然后手工把 0000 标记成已执行。
|
||||
// 没有基线记录,两种情况分开处理:空库直接从 0000 建起来,有表的库要人来确认。
|
||||
const lastApplied = applied === null ? -1 : Number(applied[0]?.last ?? -1)
|
||||
if (lastApplied < 0) {
|
||||
const bootstrapping = lastApplied < 0
|
||||
if (bootstrapping) {
|
||||
const rows = await client<{ count: number }[]>`
|
||||
select count(*)::int as count from information_schema.tables where table_schema = 'public'
|
||||
`
|
||||
const tableCount = rows[0]?.count ?? 0
|
||||
console.error(
|
||||
tableCount > 0
|
||||
? `库里已经有 ${tableCount} 张表,但没有迁移基线记录。\n` +
|
||||
"直接迁移会从 0000 跑起,而 0000 是 introspect 产物、整份被注释掉,跑不了。\n\n" +
|
||||
BASELINE_HOWTO
|
||||
: "这是个空库,迁移没法自举建表(0000 是 introspect 产物,整份被注释掉)。\n" +
|
||||
"先把结构灌进去:\n\n" +
|
||||
" psql -d <库> -f docs/specs/schema.sql\n\n" +
|
||||
BASELINE_HOWTO,
|
||||
)
|
||||
process.exit(3)
|
||||
|
||||
// 有表却没有基线记录 —— 这个库不是 OJ2 从 0000 建起来的(多半是从旧后端接管、
|
||||
// 或者从生产 dump 恢复出来的)。0000 是完整建表,直接跑必然撞上已存在的表,
|
||||
// 而且是整个事务回滚。这种情况只能由人确认之后手工打基线。
|
||||
if (tableCount > 0) {
|
||||
console.error(
|
||||
`库里已经有 ${tableCount} 张表,但没有迁移基线记录。\n` +
|
||||
"直接迁移会从 0000 跑起,而 0000 是完整建表,撞上已存在的表会整条回滚。\n\n" +
|
||||
BASELINE_HOWTO,
|
||||
)
|
||||
process.exit(3)
|
||||
}
|
||||
|
||||
// 空库:drizzle 的记账表还不存在,先建出来。原来这一步由 drizzle 的 migrate()
|
||||
// 顺手做掉,换成自己的执行器之后得自己建。
|
||||
console.log("空库,从 0000 开始自举。")
|
||||
await client`create schema if not exists drizzle`
|
||||
await client`
|
||||
create table if not exists drizzle.__drizzle_migrations (
|
||||
id serial primary key, hash text not null, created_at bigint)
|
||||
`
|
||||
}
|
||||
|
||||
const pending = files.filter((f) => f.folderMillis > lastApplied)
|
||||
@@ -124,7 +133,10 @@ export async function runMigrations() {
|
||||
}))
|
||||
.filter(({ reasons }) => reasons.length > 0)
|
||||
|
||||
if (blocked.length > 0 && process.env.OJ2_ALLOW_DESTRUCTIVE !== "1") {
|
||||
// 自举时不拦:空库上没有数据可丢,0002 那串 DROP ... IF EXISTS 全是空转。
|
||||
// 拦下来只会逼着每个新环境都带一次 OJ2_ALLOW_DESTRUCTIVE,把这道闸训练成习惯动作 ——
|
||||
// 那正是它想避免的事。
|
||||
if (blocked.length > 0 && !bootstrapping && process.env.OJ2_ALLOW_DESTRUCTIVE !== "1") {
|
||||
console.error(
|
||||
"待执行的迁移里有破坏性语句,已停下:\n" +
|
||||
blocked.map(({ tag, reasons }) => ` · ${tag}:${reasons.join(" / ")}`).join("\n") +
|
||||
|
||||
Reference in New Issue
Block a user