agentleFS
Rain-klRain-kl/OpenFlare/.agents/skills/database-migration/SKILL.md

database-migration

Skill288 starsChanged 3 months agoApache-2.0View on GitHub

Wavelet 项目专用:当新增或修改数据库表结构、索引、初始化数据、系统配置 seed、模板 seed、默认管理员、goose SQL 迁移、internal/infra/persistence/migrator、ClickHouse 分析库 DDL 或数据库升级流程时必须使用。本技能指导在 internal/infra/persistence/migrator/goose 下编写 PostgreSQL/SQLite 双方言 SQL 迁移,以及在 goose/clickhouse 下编写 ClickHouse 单方言分析表迁移,并完成验证。

  • Deletes or force-pushes
Files
SKILL.md

Wavelet 数据库升级操作指南

Wavelet 使用 github.com/pressly/goose/v3 执行 SQL 迁移。迁移入口是 internal/infra/persistence/migrator.Migrate(),SQL 文件嵌入在二进制中。

基本规则

  • SQL 迁移文件放在:
    • internal/infra/persistence/migrator/goose/postgres/
    • internal/infra/persistence/migrator/goose/sqlite/
  • PostgreSQL 和 SQLite 必须使用同一个版本号、同一个语义文件名。
  • 迁移文件使用 goose SQL 标记:
-- +goose Up
...

-- +goose Down
...
  • 不要把表结构、默认系统配置、默认模板、默认管理员初始化写回 Go 代码。
  • 编辑表结构(DDL)和插入表数据(DML/Seed)不要放在同一个 SQL 文件里,必须分成两个独立的 SQL 文件完成(例如,先通过一个文件修改表结构,再通过下一个递增版本号的文件插入/初始化数据)。
  • 插入定时任务(schedules 表数据)时绝对不能指定 id,必须依靠数据库自增(Identity 或 AUTOINCREMENT)自动分配,防止与用户手动或后续插入的定时任务产生 ID 冲突。
  • 不要添加物理外键;关系字段使用显式索引。
  • 数据库默认值应匹配 Go model 零值或业务兜底值。
  • 系统配置仍然保存字符串值;布尔值写 "true" / "false",数字写十进制字符串,复杂结构写合法 JSON 字符串。

新增迁移流程

  1. 先确认涉及的 Go model、读写路径和前端/接口消费方。
  2. 选择下一个递增版本号,格式建议 YYYYMMDDNNNN,例如:
202606090002_add_example_column.sql
  1. 在 PostgreSQL 和 SQLite 目录各新增同名 SQL 文件。
  2. 写 Up:
    • 表结构变更使用 SQL DDL。
    • 初始化/seed 数据使用 SQL INSERT。
    • 需要幂等时使用 IF NOT EXISTS 或 ON CONFLICT ... DO NOTHING。
  3. 写 Down:
    • 能安全回滚的结构变更写反向 DDL。
    • seed 数据按 key/name 等稳定标识删除。
  4. 如果变更 API handler,运行 make swagger。
  5. 至少运行:
go test ./internal/infra/persistence/migrator
go test ./internal/model ./internal/apps/config ./internal/apps/admin/system_config
make code-check

方言注意事项

  • PostgreSQL 自增主键用 BIGSERIAL;SQLite 自增主键用 INTEGER PRIMARY KEY AUTOINCREMENT。
  • PostgreSQL 时间类型优先 TIMESTAMPTZ;SQLite 使用 DATETIME。
  • PostgreSQL JSON 字段用 JSONB;SQLite 用 JSON 或 TEXT。
  • 两个方言目录的字段名、索引名、seed 数据语义必须保持一致。

修改默认系统配置

  • 新增或调整系统配置 seed 时,更新两个方言的 SQL 文件。
  • visibility 使用常量语义:0 不公开,1 通过 /api/v1/config/public 返回。
  • 公共配置 API 直接返回所有 visibility = 1 的配置键值,不要在 handler 中重新硬编码 key 列表。

验证重点

  • goose 能在空库上完整执行。
  • system_configs、默认 admin、内置模板能按预期初始化。
  • 新增表/列与 Go model 的列名、类型和默认值兼容。
  • 前端或接口消费的公共配置值仍按字符串解析。

ClickHouse 分析库(辅助 OLAP)

ClickHouse 是辅助 OLAP 存储,与 PostgreSQL/SQLite 主库完全独立的迁移与访问管线:

  • 主库(PG/SQLite):业务事务数据、goose_db_version、双方言 SQL。
  • 分析库(ClickHouse):分析型数据、goose_clickhouse_version、单方言 SQL。日志用途表还必须在主库建回落并走 logstore(见该 skill);CH 目录仍只放 CH DDL。

不要把 ClickHouse 表结构混入 PG/SQLite 迁移目录,也不要在 support-files/、internal/apps/ 或 internal/repository/ 中手写 DDL。

目录与职责

路径职责
internal/infra/persistence/migrator/goose/clickhouse/唯一 ClickHouse DDL 来源(goose SQL,嵌入二进制)
internal/model/analytics/分析表 Go model,列名须与 goose DDL 一致
internal/repository/analytics/所有 ClickHouse 读写(批量写入、查询、聚合)
internal/infra/persistence/clickhouse.go连接初始化(ChConn 原生批量、ChDB GORM 查询)

迁移入口与版本表

  • 入口:migrator.MigrateClickHouse(),在 cmd/root.go 的 PreRun 中于 migrator.Migrate() 之后调用。
  • 仅当 clickhouse.enabled: true 时执行;禁用时直接跳过(见 TestMigrateClickHouseSkipsWhenDisabled)。
  • 版本表:goose_clickhouse_version,与主库 goose_db_version 分离,互不影响。
  • 方言:仅 ClickHouse,无 SQLite 镜像目录。

ClickHouse 迁移规则

  1. DDL 只写 goose SQL:CREATE TABLE IF NOT EXISTS ...,禁止 GORM AutoMigrate、禁止在 repository 或 handler 中建表。
  2. 无事务:ClickHouse 不支持 goose 事务包装;每个 Up/Down 语句独立提交。
  3. 幂等 Up:表用 IF NOT EXISTS;Down 用 DROP TABLE IF EXISTS。
  4. Down 谨慎:MergeTree 等引擎上 DROP TABLE 会立即删除数据,生产环境通常只前滚;仅在开发/测试需要回滚时编写 Down。
  5. DDL 与 DML 分离:与主库相同,表结构变更与数据初始化分文件、分版本号;分析表通常无 seed,批量写入由 repository 在运行时完成。
  6. 引擎与排序键:在 SQL 中显式声明 ENGINE、PARTITION BY、ORDER BY 等,与查询模式对齐(例如按 created_at 分区)。
  7. 禁止重复 DDL:不要在 support-files/、apps 初始化逻辑或 repository/analytics 中复制建表语句。

新增分析表工作流

按以下顺序落地,避免列名或类型漂移:

  1. Model:在 internal/model/analytics/ 定义 struct,gorm:"column:..." 与 DDL 列名一一对应;实现 TableName(),批量写入表可提供 InsertColumns() / BatchInsertSQL()。
  2. Goose SQL:在 internal/infra/persistence/migrator/goose/clickhouse/ 新增递增版本文件(格式同主库,如 YYYYMMDDNNNN_create_xxx.sql),编写 -- +goose Up / -- +goose Down。
  3. Repository:在 internal/repository/analytics/ 实现 BatchInsert*(db.ChConn 一次 PrepareBatch + 多行 Append + 一次 Send)与查询(db.ChDB);连接未初始化时返回明确错误,不要在 handler 写 SQL,不要在 repository 内维护 channel/goroutine。
  4. Apps:在 internal/apps/<domain>/ 编排采集与入队;高频写入通过 internal/infra/persistence/batchwriter 各域独立实例异步 flush(详见 clickhouse-batchwriter 技能)。日志/分析用途表还要同时建 PG/SQLite 回落并接入 logstore(见 logstore 技能),FlushFunc 调 logstore.Active 而不是 analyticsrepo;普通业务分析表仍只读 repository。

ClickHouse 验证

至少运行:

go test ./internal/infra/persistence/migrator
go test ./internal/repository/analytics
make code-check

验证重点:

  • goose 能在空 ClickHouse 实例上完整执行 Up。
  • internal/model/analytics 列名、类型与 goose SQL 一致。
  • repository 读写路径不依赖 handler 内联 SQL。
  • clickhouse.enabled: false 时启动不报错、不执行迁移。

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.