aidog DB Migration
给 aidog SQLite 加 schema migration。本 skill 给编号 / 写法 / 幂等 / 执行 / 测试全流程,并把历史踩坑(sed 误伤列名、UNIQUE 软删冲突、rebuild 表范式)前置。
行号漂移,定位以文件名 / 符号名为准。
0. 认知纠偏(动手前必读)
前向单线,无 down/rollback。 migrations/ 现有 001-010 无任何 down/rollback 标记。SQLite 在 embedded app 里走启动时自动按序执行未应用的,不支持回滚。新 migration 只写 up,禁写 down。
SQLite 改不了现存表的列约束 → rebuild 表。 SQLite
ALTER TABLE只支持 RENAME TABLE / ADD COLUMN / DROP COLUMN(新版),不能给现存表加 UNIQUE / 改列类型 / 删约束。要加 UNIQUE(如 010 group_key)→ 建_new表 → 拷数据 → DROP 旧 → RENAME(范式见009_drop_group_path.sql/010_group_key.sql)。加列必带
DEFAULT。 现有表加NOT NULL列 → 必须给 DEFAULT,否则老行插不进。所有现存 migration 加列都带 DEFAULT(''/0/'[]')。列改名/拆分禁用
sed批改 SQL 源列名。 memorygroup-name-group-key-split踩坑:用 sed 批改 SQL 误伤源列名(同名字符串在别处)。列改名在 migration SQL 里显式写 rebuild + 列映射,不动旧 migration 文件,新列在代码层(db.rs / api.ts / models.rs)同步改名。UNIQUE 冲突先清软删行。 memory
move_group_platform踩坑:给现存表加 UNIQUE 遇历史deleted_at IS NOT NULL软删行撞约束 → rebuild 表前先在 INSERT SELECT 里过滤或合并软删行,别让历史软删数据撞新约束。
1. 编号 + 文件
src-tauri/migrations/NNN_short_snake_name.sql
- NNN = 现有最大 +1(当前 010 → 新建 011)。三位补零。
- 文件名 snake_case,语义短(
011_add_platform_manual_budget.sql)。 - 启动时
db.rs的 migration runner 按文件名排序顺序执行未应用项。禁跳号、禁改已发布 migration 的文件名(已发布 = 已在某些用户的库里记录过版本号)。
⚠️ 已发布 migration 禁改内容(用户库已记录该版本号,改了不重跑 → 库与代码不一致)。要修只能加新 migration 补。
2. 写法范式(按变更类型选)
A. 简单加列(加 DEFAULT)— 仿 005_proxy_log_est_cost.sql
-- Migration NNN: <一句话说明>
ALTER TABLE platform ADD COLUMN manual_budget_json TEXT NOT NULL DEFAULT '';
B. 加表 — 仿 001_init.sql / 003_model_price.sql
CREATE TABLE IF NOT EXISTS foo (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_foo_name ON foo(name);
全 IF NOT EXISTS(幂等)。列全带 DEFAULT。
C. 加 UNIQUE / 改列类型 / 拆列 — rebuild 表范式(仿 009 / 010)
-- Migration NNN: <说明>。SQLite 不能给现存表加 UNIQUE,rebuild 表(仿 010_group_key.sql)。
CREATE TABLE IF NOT EXISTS "foo_new" (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL DEFAULT '',
UNIQUE(name)
);
-- 仅当源表存在目标列时取已存值,否则 DEFAULT 兜底(首次迁移 + 兼容已迁移库)。
INSERT INTO "foo_new" (id, name)
SELECT id, COALESCE(name, '') FROM foo
WHERE deleted_at IS NULL OR name IS NOT NULL; -- ★ 清撞约束的软删/空行
DROP TABLE foo;
ALTER TABLE foo_new RENAME TO foo;
-- 重建索引(DROP TABLE 带走了)
CREATE INDEX IF NOT EXISTS idx_foo_name ON foo(name);
🔴 CHECKPOINT:
- INSERT SELECT 过滤或合并会撞新 UNIQUE 的历史软删行(见 §0-5)。
- rebuild 后重建所有原索引(DROP TABLE 删了)。
IF NOT EXISTS守foo_new(防中途失败重跑撞表)。
D. 数据回填 migration(不接 schema,纯 UPDATE)
仍用 NNN 编号,SQL 是 UPDATE ... WHERE ...。带幂等守(WHERE NOT EXISTS / WHERE col = '')。
3. 执行机制
- migration runner 在
db.rs(init/连接时跑)。按文件名顺序执行未应用项,记录到 schema 版本表。 - dev 验证:删 dev 库重跑全套(
rm ~/Library/Application\ Support/aidog/aidog.db*后yarn tauri dev),确认全套 migration 从空库跑通。 - 存量验证:保留旧 dev 库直接
yarn tauri dev,确认新 migration 在「已有 010 的库」上增量执行成功(这是真实用户路径)。
4. 代码层同步(schema 变更必伴随)
加列/拆列后,db.rs 的 CRUD(INSERT/UPDATE/SELECT 列清单)+ Rust struct(models.rs)+ TS 类型(api.ts)+ 前端表单同步。新列在 Rust struct 加字段(带 #[serde(default)] 兼容旧 JSON)。
拆列 / 改名(如 group_key 那种大改):db.rs 所有 SQL 引用 + models.rs struct + api.ts 类型 + 前端消费全链同步,禁只改 SQL 不改代码。memory
group-name-group-key-split:proxy_log.group_name RENAME→group_key 的 sed 批改误伤源列名是真坑。
5. 验证门禁
# 1. Rust 编译(runner 改了 db.rs)
cd src-tauri && cargo build && cargo clippy # warning 必须清(memory warnings-are-issues)
# 2. 全套 migration 从空库跑通
rm ~/Library/Application\ Support/aidog/aidog.db* # ⚠️ 仅 dev 库,禁删用户库
yarn tauri dev # 看启动日志 migration 执行无错
# 3. 存量库增量执行(保留旧 dev 库)
yarn tauri dev # 确认 011 在已有 010 的库增量成功
# 4. db 相关测试
cd src-tauri && cargo test # db.rs 有 #[test]
收尾自检:
- NNN 编号 = 最大+1,三位补零。
- 前向单线,无 down。
- 加列带 DEFAULT;加 UNIQUE/改约束走 rebuild 表范式。
- rebuild 表清撞约束的软删行 + 重建索引。
- 已发布 migration 文件未改。
- 代码层(db.rs SQL + models.rs + api.ts + 前端)全链同步。
- 空库重跑 + 存量库增量执行双验证通过。
反例黑名单(不要做)
- ❌ 写 down/rollback —— 前向单线,无回滚。
- ❌ 给现存表直接
ALTER TABLE ADD UNIQUE—— SQLite 不支持,rebuild 表。 - ❌ 加
NOT NULL列不带 DEFAULT —— 老行插不进。 - ❌ 用
sed批改旧 SQL 源列名做改名 —— 误伤同名字符串,rebuild 表显式映射。 - ❌ 改已发布 migration 文件内容 —— 用户库已记录版本号不重跑。
- ❌ rebuild 表忘重建索引 —— DROP TABLE 带走索引。
- ❌ rebuild 表 INSERT SELECT 不过滤软删行 —— 历史
deleted_at行撞新 UNIQUE。 - ❌ 只改 SQL 不改 db.rs/models.rs/api.ts —— 全链失配运行时炸。
相关
- 现有范式:
src-tauri/migrations/009_drop_group_path.sql、010_group_key.sql(rebuild 表 + UNIQUE) - 加列范式:
005_proxy_log_est_cost.sql、008_model_info_columns.sql - migration runner:
src-tauri/src/gateway/db.rs(init 时按序执行) - memory:
group-name-group-key-split、warnings-are-issues - 主要涉及文件:
src-tauri/migrations/**、src-tauri/src/gateway/db.rs