migrate.md - maoxiaoyue/hypgo GitHub Wiki

pkg/migrate — Migration Diff 自動生成

掃描 Go Model struct 的 bun tag,比對快照檔案,自動產生 up/down SQL migration。

設計理念

AI 修改 Model struct 後,手動寫 SQL migration 容易遺漏欄位。Migration Diff 讓框架自動偵測變更並產生正確的 SQL:

Model struct 變更 → 反射掃描 bun tag → 比對上次快照 → 產生 SQL

快速上手

1. 註冊 Model

import "github.com/maoxiaoyue/hypgo/pkg/migrate"

type User struct {
    bun.BaseModel `bun:"table:users"`
    ID        int64     `bun:"id,pk,autoincrement"`
    Name      string    `bun:"name,notnull"`
    Email     string    `bun:"email,notnull,unique"`
    Bio       string    `bun:"bio,type:text"`
    CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp"`
}

registry := migrate.NewRegistry()
registry.Register((*User)(nil))

2. 掃描 & Diff

// 掃描 Model → 提取 TableSchema
tables := migrate.ScanModels(registry)

// 載入上次快照
snapshot, _ := migrate.LoadSnapshot(".hyp/schema_snapshot.json")

// 比對 → 產生 ChangeSet
changes := migrate.Diff(tables, snapshot)

// 產生 SQL
up, down := migrate.GenerateSQL(changes, "postgres")

fmt.Println(up)
// → ALTER TABLE "users" ADD COLUMN "bio" TEXT;

3. 儲存

// 儲存新快照
migrate.SaveSnapshot(".hyp/schema_snapshot.json", tables)

// 取得 migration 檔名
upFile, downFile := migrate.MigrationFiles("migrations/")
// → migrations/20260326_200000_auto.up.sql
// → migrations/20260326_200000_auto.down.sql

完整工作流程

func generateMigration() {
    // 1. 註冊所有 Model
    registry := migrate.NewRegistry()
    registry.Register(
        (*models.User)(nil),
        (*models.Post)(nil),
        (*models.Comment)(nil),
    )

    // 2. 掃描
    tables := migrate.ScanModels(registry)

    // 3. Diff
    snapshot, _ := migrate.LoadSnapshot(".hyp/schema_snapshot.json")
    changes := migrate.Diff(tables, snapshot)

    if len(changes) == 0 {
        fmt.Println("No changes detected")
        return
    }

    // 4. 顯示變更
    for _, c := range changes {
        fmt.Println(c.String())
    }

    // 5. 產生 SQL
    up, down := migrate.GenerateSQL(changes, "postgres")

    // 6. 儲存 migration 檔案
    upFile, downFile := migrate.MigrationFiles("migrations/")
    os.WriteFile(upFile, []byte(up), 0644)
    os.WriteFile(downFile, []byte(down), 0644)

    // 7. 更新快照
    migrate.SaveSnapshot(".hyp/schema_snapshot.json", tables)
}

Scanner — bun tag 解析

Scanner 從 struct field 的 bun:"..." tag 提取欄位資訊:

bun tag 解析結果
bun:"id,pk,autoincrement" PrimaryKey=true, AutoIncrement=true
bun:"name,notnull" NotNull=true
bun:"email,notnull,unique" NotNull=true, Unique=true
bun:"bio,type:text" SQLType="text"
bun:"score,default:0" Default="0"
bun:"table:users,alias:u" 表名="users"(BaseModel tag)
bun:"-" 跳過此欄位

Diff — 變更偵測

變更類型 說明
AddTable 快照中不存在的新表
DropTable Model 中已移除的表
AddColumn 表中新增的欄位
DropColumn 表中已移除的欄位
AlterColumn 欄位屬性變更(型別、NotNull、Default 等)

SQL Generator — 多 Dialect 支援

特性 PostgreSQL MySQL
識別符引號 "users" `users`
自增主鍵 int64 BIGSERIAL BIGINT AUTO_INCREMENT
自增主鍵 int SERIAL INT AUTO_INCREMENT
浮點數 DOUBLE PRECISION DOUBLE
時間戳 TIMESTAMPTZ DATETIME
ALTER NOT NULL SET/DROP NOT NULL MODIFY COLUMN

Go → SQL 型別對映

Go Type SQL Type
int64 BIGINT
int, int32 INTEGER
int16 SMALLINT
float64 DOUBLE PRECISION / DOUBLE
float32 REAL
bool BOOLEAN
string TEXT
time.Time TIMESTAMPTZ / DATETIME

若有 type:xxx tag,優先使用指定的 SQL 型別。

快照格式

{
  "tables": {
    "users": {
      "name": "users",
      "columns": [
        {"name": "id", "go_type": "int64", "primary_key": true, "auto_increment": true},
        {"name": "name", "go_type": "string", "not_null": true},
        {"name": "email", "go_type": "string", "not_null": true, "unique": true}
      ]
    }
  }
}

架構

pkg/migrate/
├── registry.go     ModelRegistry — Model 註冊
├── scanner.go      ScanModel() — 反射提取 bun tag → TableSchema
├── snapshot.go     快照讀寫(JSON)
├── diff.go         Diff() — 比對新舊 schema → ChangeSet
├── generator.go    GenerateSQL() — ChangeSet → SQL(pg/mysql)
└── migrate_test.go 22 個單元測試

測試

go test ./pkg/migrate/... -v

涵蓋:Registry、Scanner(User/Post/非 struct)、toSnakeCase、Diff(AddTable/DropTable/AddColumn/DropColumn/AlterColumn/NoChanges)、SQL Generator(Postgres/MySQL/AutoIncrement/DropTable/Empty)、Snapshot(Save/Load/不存在/JSON)、完整工作流程整合測試。