# GORM 自动迁移与升级 SQL 交接说明

## 背景

- 当前服务启动时会执行 `main.go -> initialize.RegisterTables() -> initialize.bizModel()`。
- `initialize/gorm_biz.go` 中注册了 `book` 业务模型，并调用 `db.AutoMigrate(...)`。
- 因此即使没有手动执行 `.sql` 文件，只要服务启动并连接到数据库，`book` 相关业务表也可能被 GORM 自动创建。

## AutoMigrate 的信息来源

- GORM 不读取 `.ai-specs/doc-sql/*.sql` 中的基线 SQL。
- 表名来自各模型的 `TableName()`。
- 字段名来自 Go struct 字段名的 snake_case 映射。
- 字段类型、默认值、非空、索引、唯一索引、注释主要来自 `gorm` tag。
- 基础字段来自模型嵌入结构，例如 `model/book/base.go` 的 `HardDeleteModel`。

示例：

```go
Title string `gorm:"type:varchar(255);not null;comment:书名主标题"`
BookType string `gorm:"type:varchar(64);not null;index;comment:书籍类型字典值，对应 book_type"`
Rating float64 `gorm:"type:numeric(3,1);not null;default:0.0;comment:书籍评分，范围 0-10"`
```

## AutoMigrate 能做什么

- 新表不存在时，按 Model 创建表。
- 新字段不存在时，通常会补充字段。
- 新索引不存在时，通常会尝试创建索引。
- 部分字段类型、默认值、注释可能会按数据库驱动能力尝试调整。

## AutoMigrate 不能当作升级 SQL

- 它不知道旧版本到新版本的业务升级意图。
- 它不会可靠删除废弃字段。
- 它不会可靠删除废弃索引。
- 字段重命名通常会被识别为新增字段，旧字段仍保留。
- 字段类型收缩、非空约束、唯一约束、CHECK 约束、历史数据回填等需要人工判断。
- 它不能保证生产升级顺序、幂等性、兼容性和数据安全。

结论：`AutoMigrate` 是开发期快速补结构工具，不是正式数据库版本迁移方案。

## 升级 SQL 维护规则

- 修改 `.ai-specs/doc-sql/*.sql` 并导致表结构、字段、索引、约束、默认值或注释变化时，必须同步维护升级 SQL。
- 修改 `.ai-specs/doc-dict/*.md` 并导致系统字典主数据或字典项变化时，必须同步维护初始化数据和升级 SQL。
- 升级 SQL 固定放在 `.ai-transition/database-upgrade-doc`。
- 当前版本号以 `.ai-specs/sys-specs/database-upgrade-doc-spec.md` 的 `当前数据库表结构版本` 为准。
- 当前为 `v1` 时，兼容变更写入 `.ai-transition/database-upgrade-doc/v1.sql`。
- 升级 SQL 只写从既有数据库升级到当前目标结构的变更，不重复粘贴完整建表 SQL。
- 同一次业务变更涉及多张表时，写入同一个当前版本 SQL 文件，并按表分组。
- 字典主数据升级按 `sys_dictionaries.type` 防重复。
- 字典明细升级按 `sys_dictionary_details.sys_dictionary_id + value` 防重复。
- 固定值域字典需要写 `sys_dictionaries` 和 `sys_dictionary_details`；动态值域字典默认只写 `sys_dictionaries`。
- 字典升级 SQL 禁止裸 `INSERT`，必须兼容重复执行、手工已建数据和部分明细缺失。
- 字典升级 SQL 命中已软删数据时，恢复启用数据必须同步置空 `deleted_at`。

## 常见变更处理

- 新增字段：写 `ALTER TABLE ... ADD COLUMN ...`，并补 `COMMENT ON COLUMN`。
- 新增 `NOT NULL` 字段：先给默认值或先回填历史数据，再追加 `NOT NULL`。
- 修改字段类型：先评估历史数据是否兼容，再写 `ALTER TABLE ... ALTER COLUMN ... TYPE ...`。
- 删除字段：必须人工确认无业务读写依赖，再写 `ALTER TABLE ... DROP COLUMN IF EXISTS ...`。
- 新增普通索引：写 `CREATE INDEX IF NOT EXISTS idx_<table>_<field> ...`。
- 删除索引：写 `DROP INDEX IF EXISTS ...`。
- 新增唯一索引：先排查历史重复数据，必要时先清洗，再创建唯一索引。
- 修改字段名：优先写 `ALTER TABLE ... RENAME COLUMN ...`，不要依赖 GORM 自动识别。
- 修改注释：写 `COMMENT ON TABLE` 或 `COMMENT ON COLUMN`。
- 新增固定值域字典：同步修改 `source/system/dictionary.go`、`source/system/dictionary_detail.go` 和当前版本 SQL。
- 新增动态值域字典：同步修改 `source/system/dictionary.go` 和当前版本 SQL；没有固定值项时不改 `dictionary_detail.go`。
- 修改固定字典项展示信息：升级 SQL 先 `UPDATE` 已有 `value`，再 `INSERT` 缺失项。
- 废弃固定字典项：优先将 `status` 改为 `false`，不要直接删除历史值项。

## 配置建议

- 开发环境可以保留 `config.yaml` 中 `system.disable-auto-migrate: false`，便于快速补表。
- 生产或正式联调环境建议设置 `system.disable-auto-migrate: true`，统一使用 `.ai-transition/database-upgrade-doc/*.sql` 做可审计升级。
- 如果生产环境仍开启 `AutoMigrate`，必须接受它可能自动补字段或索引、但不会完整处理删除和复杂变更的风险。

## 交接检查清单

- 已确认 `.ai-specs/doc-sql/*.sql` 是目标结构说明，不会被程序自动执行。
- 已确认真实自动建表入口是 `initialize/gorm_biz.go` 的 `AutoMigrate`。
- 已确认 Model 的 `gorm` tag 是 AutoMigrate 生成 DDL 的主要依据。
- 已确认结构升级 SQL 需要人工维护，不能指望 GORM 生成。
- 已确认生产环境是否关闭自动迁移，并形成团队约定。
