migrate

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: MIT Imports: 17 Imported by: 0

README

migratex

基于 pressly/goose/v3 的数据库迁移工程化封装:SQL 迁移文件 + Cobra 命令 + 生成器 + 静态 lint + MySQL 并发锁。

migratex 不自研迁移执行引擎——读取、排序、执行 up/down、版本管理全部由 goose Provider 完成;migratex 负责把它工程化:

职责 承担方
迁移文件生成(make migration)、SQL 模板 migratex
Cobra 命令、参数、危险操作防护(fresh --force migratex
静态 lint(版本冲突、缺 Up/Down、未完成迁移) migratex
孤儿版本检测(数据库有记录、源文件缺失) migratex
MySQL GET_LOCK 并发互斥(goose 官方无 MySQL locker) migratex
读取迁移、排序、执行 up/down、版本表管理 goose
业务查询/写入/事务/model 定义/连接管理 GORM(调用方)

边界:正式数据库结构变更只走 SQL 迁移文件;GORM 只做业务 ORM 与连接管理,不使用 AutoMigrate 变更正式库结构;迁移文件不引用业务 model。

限制(v0.1.0)

  • 迁移执行引擎仅支持 MySQLNewCommands 对其他方言返回 ErrUnsupportedDatabase
  • 不支持 -- +goose StatementBegin/StatementEnd 复杂语句块(存储过程/触发器),lint 直接报错
  • goose 版本表是"当前应用集合",不是审计流水:回滚会删除版本行,不保留历史轨迹

接入前提

  • Go 1.26;迁移执行仅支持 MySQL。
  • 调用方自行维护 GORM 的连接池和凭据;迁移库不读取或写入 .env 文件。
  • MySQL DSN 必须包含 parseTime=true。迁移账户至少需要迁移目标库的建表、改表、建/删索引及读写 goose_db_version 的权限。
  • 在生产环境中,应用进程使用的连接池 MaxOpenConns 不得设为 1migrate fresh --forcemigrate refresh --force 需要一条连接持有迁移锁,并使用另一条连接删表/回滚和重建。

安装与目录

发布版本请固定使用 tag,而非 latest

go get github.com/gtkit/migratex@<发布版本>

推荐从应用模块根目录执行命令,目录约定如下:

shop/
├── cmd/shop/main.go
├── database/migrations/
├── internal/models/
└── internal/repository/

WithProjectName 用于设置应用的 Go module path,只影响 make modelmake cmdmake migration create_* 等生成代码的 import 路径。默认可省略:未显式设置时,生成命令会从执行所在目录向上查找项目 go.mod 自动解析 module path;仅当既未设置、又无法从 go.mod 解析时,这些生成命令才返回包装 migration.ErrInvalidArgument 的错误并拒绝落盘(migrate/lint/help 等不需要 import 前缀的命令不受影响)。正式生产二进制通常只注册 commands.Migrate;代码生成命令可仅保留在开发工具二进制中。

完整接入 Demo

仓库提供了可编译示例:examples/basic/main.go。以下代码可直接作为应用入口,唯一需要替换的是 module path、二进制名和各目录:

package main

import (
	"log"
	"os"
	"time"

	migrate "github.com/gtkit/migratex"
	"github.com/spf13/cobra"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

func main() {
	dsn := os.Getenv("APP_MYSQL_DSN")
	if dsn == "" {
		log.Fatal("APP_MYSQL_DSN is required")
	}

	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatalf("open database: %v", err)
	}

	commands, err := migrate.NewCommands(
		db,
		migrate.WithProjectName("example.com/shop"),
		migrate.WithMigrationDir("database/migrations"),
		migrate.WithTimeout(5*time.Minute),
	)
	if err != nil {
		log.Fatalf("create migration commands: %v", err)
	}

	root := &cobra.Command{Use: "shop", SilenceUsage: true}
	root.AddCommand(commands.Migrate, commands.Make)
	if err := root.Execute(); err != nil {
		log.Fatal(err)
	}
}

在项目根目录设置无真实密钥提交的环境变量后执行:

export APP_MYSQL_DSN='app_user:<password>@tcp(127.0.0.1:3306)/shop?charset=utf8mb4&parseTime=true&loc=Local'

go run ./cmd/shop migrate lint --strict
go run ./cmd/shop migrate status

上例使用 cmd/shop;验证仓库自带示例时相应替换为 go run ./examples/basic。DSN 中的 <password> 是占位符,禁止写入代码、README 之外的真实配置或 Git 历史。

迁移文件格式

单文件 goose 注解格式,纯数字 UTC 时间戳版本号:

database/migrations/
├── 20260713153045_create_users_table.sql
└── 20260713153102_add_email_to_users_table.sql
-- +goose NO TRANSACTION

-- +goose Up
ALTER TABLE `users` ADD COLUMN `email` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '邮箱';

-- +goose Down
ALTER TABLE `users` DROP COLUMN `email`;

规则:

  • 版本号为文件名第一个 _ 之前的纯数字(goose 解析规则),生成器保证严格递增;
  • 每个迁移必须同时有 UpDown 注解段;
  • MySQL DDL 迁移标注 -- +goose NO TRANSACTION——MySQL DDL 隐式提交,外层事务提供不了原子性,标注让语义诚实(lint 对缺标注的 DDL 报 warning);
  • 模板不生成幂等 guard(IF NOT EXISTS、存在性检查):版本模型保证每个迁移只执行一次,静默跳过反而会掩盖结构漂移;
  • 文件一旦执行,不再修改;需要变更就新建迁移。

命令参考

migrate
命令 作用
migrate up 执行所有 pending 迁移
migrate down --force [--step=N] 回滚已应用迁移,默认最新一条;--step=N 最多回滚 N 条,已应用不足 N 条时回滚全部并成功,输出实际数量;N 必须为正整数;必须带 --force
migrate down-to <version> --force 回滚到指定版本(不含该版本);必须带 --force
migrate reset --force 回滚全部迁移;必须带 --force
migrate refresh --force 单锁内先回滚全部(走各迁移 Down)再重跑全部 Up;⚠ 会执行所有 Down,可能删除字段、表与数据,不作为常规生产发布命令;不删版本表(区别于 fresh);必须带 --force;需要连接池至少 2 条连接
migrate mark-applied [--to <version>] --force 把 pending 版本写入版本表标记为已应用,不执行任何迁移 SQL,用于 baseline 已存在等价 schema 的存量库;--to 只标记不超过该版本的 pending;必须带 --force,执行前打印将标记的版本清单
migrate status 当前版本 + 每个迁移的 Applied/Pending 与执行时间 + 孤儿版本检测;只读,不加锁、不创建版本表
migrate pretend 打印 pending 迁移文件及其 Up SQL;只读,不执行任何语句(不是数据库执行计划)
migrate fresh --force 删除当前库全部基表(不动 view)后重新执行所有迁移,全程持有迁移锁;不带 --force 拒绝执行;需要连接池至少 2 条连接(一条持锁),SetMaxOpenConns(1) 时快速报错
migrate lint [--strict] 静态检查迁移文件,不连数据库,可进 CI;--strict 下 warning 也失败

写类命令(up/down/down-to/reset/refresh/mark-applied/fresh)在触碰数据库前会对迁移目录强制执行与 migrate lint 相同的静态检查,存在 error 级问题(如 empty_up/empty_down 半成品)时直接拒绝执行——goose 对空 Up/Down 迁移会静默"成功"并写/删版本记录,强制 lint 是防止版本状态漂移的执行前防线。

refreshfresh 的区别:fresh 直接 DROP 当前库全部基表后重放全部迁移(无视 Down 脚本,用于测试库/灾备重建);refresh 只通过各迁移的 Down 段回滚再重跑(信任 Down、保留迁移历史路径)。两者都会毁数据;freshrefreshreset 均强制 --force,其中 fresh 因直接删表风险更高,只应用于本地或隔离测试库。

mark-applied 只写 goose 版本表、不跑迁移 SQL:仅当数据库结构已等价于这些迁移的净效果时才可使用(如从其他工具迁移过来、或手工建好了历史表)。它不比对实际表结构是否真的匹配——标错会让 up 跳过真实建表、造成 schema 漂移,故强制 --force。典型流程:库已对应到历史某版本时用 --to <version> 标记到该点,其后的新迁移仍由 migrate up 正常执行。

make migration
# 建表(同时生成 model/repository 脚手架)
myapp make migration create_users_table

# 从已注册的 GORM model 生成固定 SQL 建表迁移(不生成脚手架)
myapp make migration create_users_table --from-model

# 加字段:--type 必填,拒绝生成半成品
myapp make migration add_email_to_users_table --type 'VARCHAR(128)' --not-null --default "''" --comment '邮箱'

# 加字段并指定位置
myapp make migration add_pay_mch_id_to_orders_table \
    --type 'VARCHAR(64)' --not-null --default "''" \
    --comment '支付商户号' --after pay_channel

# 修改表(生成空骨架,补全前 lint 会以 empty_up/empty_down 拦截执行)
myapp make migration update_users_table

# 删字段 / 删索引 / 删表(Down 无法自动推导,需按骨架注释补全反向 SQL,
# 否则 empty_down 会拦截所有写类命令——空 Down 会被 goose 当成功回滚,造成漂移)
myapp make migration drop_column_email_from_users_table
myapp make migration drop_index_email_from_users_table
myapp make migration drop_users_table

make migration 在写入任何文件前完成全部校验(版本合法性、参数、路径冲突),校验失败不落盘——create_* 不会留下 model/repository 孤儿脚手架;迁移目录中存在任何非法版本的 .sql 文件(非 <14 位 UTC 时间戳>_<名称>.sql)时拒绝生成,先重命名存量文件。

create_*_table --from-model 复用 WithDDLModels 中表名匹配的 GORM model,并以 GORM dry-run 生成完整的建表 SQL(包括索引等关联 DDL);生成文件的 Down 为直接 DROP TABLE,不含 IF EXISTS / IF NOT EXISTS guard。迁移名中的 <table> 必须与 GORM 解析出的实际表名完全一致,例如默认 User model 的表名为 users 时应使用 create_users_table。该模式要求在创建命令时已注册模型:

commands, err := migrate.NewCommands(
	db,
	migrate.WithDDLModels(&models.User{}),
)
if err != nil {
	return err
}

模型仅在生成时读取。生成后的 .sql 文件才是迁移事实来源:提交后不得根据当前 model 重写它;后续字段、索引或约束变更必须创建新的迁移。--from-model 仅支持建表,不负责从当前 model 自动推导已有表的差异。

make modelmake ddlmake ddl diff 等代码/DDL 生成命令与迁移执行解耦,make ddl 只产出草稿 SQL 供人工审核;migrate up 始终执行版本化 SQL,不做 AutoMigrate 推断。

make model
# 生成 user 模型的 model + repository 脚手架
myapp make model user

一条命令生成以下文件(均 writeSkipIfExists,已存在则跳过、不覆盖):

文件 内容
<ModelDir>/model.go BaseID + BaseTimeField 公共嵌入字段(首次生成)
<ModelDir>/doc.go model 包注释(首次生成)
<ModelDir>/<name>.go 模型结构体 + TableName + GetStringID + 基于标准库 encoding/json 的序列化方法
<RepositoryDir>/<name>/<name>_i.go Repository + New(db *gorm.DB) + mdbCtx 事务收口
<RepositoryDir>/<name>/<name>_util.go Get / ExistsByID / CreateOrUpdate
<RepositoryDir>/dbtx/dbtx.go 基于 context 的事务传递与 gorm 事务管理器(首次生成)

生成的仓储遵循以下契约:

  • 事务收口mdbCtx 统一返回 dbtx.DB(ctx, r.db)——ctx 内携带事务句柄时自动参与同一事务,否则用底座连接。用 dbtx.NewManager(db).Do(ctx, fn) 开启事务,仓储无需改动即事务安全。
  • 查询区分「未找到」与「库故障」Get 返回 (<Model>, found bool, err error)gorm.ErrRecordNotFound 被拦在仓储内以 found=false, err=nil 表达,其余库错误以 err != nil 上抛,不吞错。
  • 存在性判断用强类型主键ExistsByID(ctx, id int64),不生成基于动态字段名的 IsExist(免于 SQL 注入且无需白名单)。

dbtx 包的 import 路径由 WithRepositoryDir 推导(默认 <module>/internal/repository/dbtx),与其生成位置保持同源。生成的 model / repository / dbtx 只依赖标准库与 gorm,在全新空项目(go.mod + gorm 依赖)内即可直接 go build 通过,不再依赖 internal/pkg/paginatorgithub.com/gtkit/json

分页按需自行实现——工具不内置分页约定。下面是一段纯 gorm 的示例,可直接加进生成的仓储:

// Paginate 返回第 page 页(每页 size 条)及总数;调用方据 total 自行构造分页元数据。
func (r *Repository) Paginate(ctx context.Context, page, size int) (items []models.User, total int64, err error) {
	if page < 1 {
		page = 1
	}
	if size < 1 {
		size = 20
	}
	db := r.mdbCtx(ctx).Model(models.User{})
	if err = db.Count(&total).Error; err != nil {
		return nil, 0, fmt.Errorf("count users: %w", err)
	}
	if err = db.Order("id DESC").Offset((page - 1) * size).Limit(size).Find(&items).Error; err != nil {
		return nil, 0, fmt.Errorf("list users: %w", err)
	}
	return items, total, nil
}
make cmd
# 生成一个 cobra 命令脚手架(名称用 snake_case)
myapp make cmd backup_database

生成 <CmdDir>/<name>.go(默认 cmd/backup_database.gowriteFailIfExists——已存在则报错不覆盖),内容是导出的 Cmd<Struct>(如 CmdBackupDatabase)命令变量及其构造;不自动注册,请在你的 root 命令上显式 rootCmd.AddCommand(cmd.CmdBackupDatabase)(不假设你的命令骨架形态)。输出目录用 WithCmdDir--cmd-dir 覆盖。

lint 规则
规则 级别 说明
invalid_filename error 文件名不符合 <数字版本>_<名称>.sql
invalid_version error 版本前缀不是正整数(0 为 goose 保留)
duplicate_version error 多个文件版本号相同
missing_up / missing_down error 缺少 Up / Down 注解段
empty_up error Up 段没有 SQL(未完成的迁移)
empty_down error Down 段没有 SQL(goose 会把空 Down 当成功回滚并删版本记录,造成漂移)
statement_block_not_supported error 使用了 StatementBegin/End 复杂块
nonstandard_version error 版本前缀不是合法的 14 位 UTC 时间戳(migratex 版本号强约束为生成器时间戳格式,不兼容任意正整数版本)
missing_no_transaction warning 含 DDL 但缺 NO TRANSACTION 标注

error 级问题除使 migrate lint 失败外,也会让所有写类命令在触碰数据库前拒绝执行。

日常与生产操作流程

开发一个迁移

以下流程以新增字段为例;所有命令均在应用模块根目录执行:

# 1. 生成可直接执行的字段迁移
shop make migration add_email_to_users_table \
    --type 'VARCHAR(128)' --not-null --default "''" --comment '邮箱'

# 2. 审核生成的 Up/Down SQL;对 update_*、drop_* 草稿必须手动补全两个方向
editor database/migrations/<14位UTC时间戳>_add_email_to_users_table.sql

# 3. 提交前只做静态检查,再查看将执行的文件和 SQL
shop migrate lint --strict
shop migrate pretend

# 4. 在专用开发库执行,并核对状态
shop migrate up
shop migrate status

pretend 展示的是 pending 文件的 Up 段原文,不是数据库优化器确认过的执行计划;它用于人工审核,不能替代备份、SQL 审查或预发演练。已在任一环境执行的迁移文件必须永久保留且不可修改。

部署到生产
  1. 合并迁移前在 CI 执行 shop migrate lint --strict,并确保新增版本号高于目标分支已有最大版本;
  2. 先对目标数据库做可恢复备份,并在预发库按同一版本文件执行 shop migrate up
  3. 在发布流程中以单独迁移 Job / 单个发布步骤执行 shop migrate up,成功后才滚动业务实例;不要在每个应用实例启动钩子中无条件执行迁移;
  4. 迁移 Job 成功后执行 shop migrate status,确认没有 Pending 或 orphan version;
  5. 业务发布需要兼容扩容/缩容时采用 expand-contract:先加可空字段或索引、发布兼容代码、回填数据、最后在后续发布删除旧结构。

MySQL GET_LOCK 会保护并发写类命令,但锁只解决“同一批迁移不能交错”,不替代发布编排、备份或结构兼容设计。migrate fresh --force 是本地/隔离测试库工具,禁止用于生产库

回滚与故障恢复
  • 已确认 Down SQL 可逆且业务兼容时,可执行 shop migrate down --forceshop migrate down-to <version> --force;生产事故优先评估前向修复,避免回滚导致数据丢失。
  • 若 MySQL DDL 的某条语句失败,先停止自动重试,执行 shop migrate status 并人工核对真实 schema;手工回滚或补齐已生效的部分后,再运行 shop migrate up
  • 若 status 报告 orphan version,说明版本表记录与源文件不一致;禁止删除版本行或伪造 SQL 文件,必须先核对每个环境的 schema 和发布记录。
  • reset 会回滚全部迁移,仅适用于可丢弃数据的开发/测试库;生产恢复不使用该命令作为常规手段。

版本纪律(多人协作必读)

goose 按版本号线性执行,已应用版本之前的"迟到"迁移默认报错、绝不静默跳过。团队纪律:

  1. 迁移只从主干最新代码生成(生成器保证版本号严格递增且恒为合法 14 位 UTC 时间戳,同秒冲突取下一秒;lint 对非时间戳版本报 error,手工命名的文件同样受此约束);
  2. 包含迁移的分支合并前必须 rebase;版本落后时删掉旧文件重新生成;
  3. CI 里跑 migrate lint,并校验新增迁移版本号大于目标分支最大版本;
  4. 如果迟到迁移已合并但尚未在任何环境执行:将文件重命名为新版本号后重新执行——这是唯一推荐的修复路径,migratex 不提供 allow-missing 开关;
  5. 如果迟到迁移已在其他环境执行过:属于事故,需人工核对各环境 schema 后处理,不要靠重命名掩盖。

失败与恢复

goose 没有 dirty 锁死状态:迁移失败即停,失败的迁移不会记入版本表,修复后重跑 migrate up 会从该迁移重试。

注意 MySQL DDL 非事务:一个迁移里有多条语句时,第二条失败后第一条已经生效,直接重跑会在第一条上撞"已存在"。因此:

  1. 一个迁移尽量只做一件事;能合并的列/索引变更合并成一条 ALTER TABLE(MySQL 8.0 单条 DDL 原子);
  2. 失败后先看报错、人工核对数据库真实结构;
  3. 手工回滚/补齐已生效的部分,再重跑 migrate up

migrate status 会额外报告孤儿版本(版本表里有记录但源文件已删除)——这是 goose 自身 Status 的盲区,出现时说明有人删了已应用的迁移文件,需要人工处理。

并发与锁

  • 写类命令(up/down/down-to/reset/refresh/mark-applied/fresh)通过 MySQL GET_LOCK advisory lock 互斥,锁名由数据库名 + 版本表名稳定派生,等待时长跟随 WithTimeoutfresh 从删表起、refresh 从回滚起全程持有该锁,mark-applied 在写版本表期间持锁,窗口不会与其他迁移交错;
  • 读类命令(status/pretend/lint)真正只读:不获取锁、不创建版本表、无任何数据库写入(status/pretend 直查版本表而非 goose 的 Provider.Status——后者会加会话锁并建表),可与执行中的迁移并行;
  • 多实例部署同时启动执行迁移是安全的:后到者等锁,拿到锁后发现无 pending 即空跑。

Options

Option 默认值 说明
WithProjectName(string) 缺省时从 go.mod 解析 module path,用于代码生成 import 路径;未设置时从项目 go.mod 自动解析
WithMigrationDir(string) database/migrations 迁移文件目录
WithModelDir(string) internal/models model 生成目录
WithRepositoryDir(string) internal/repository repository 生成目录
WithDDLDir(string) database/ddl DDL 输出目录
WithCmdDir(string) cmd make cmd 生成目录
WithTimeout(time.Duration) 5 分钟 迁移操作超时,同时决定锁等待上限
WithLogger(migration.Logger) stdout 日志 结构化日志注入(goose 日志也经此输出)
WithDDLModels(...any) 注册可用于 make ddl 的 GORM 模型

错误判定:所有失败路径返回包装 sentinel 的 error,可用 errors.Is 判定 migration.ErrInvalidArgumentmigration.ErrUnsupportedDatabasemigration.ErrLintFailed

除不传 Option 与 nil Option 外,Option 的非法输入(空项目/目录、非正超时、nil logger)会使 NewCommands 返回 migration.ErrInvalidArgument,不会静默回退默认配置。

测试

本仓库的完整发布检查:

make verify-release

该命令运行 vetgolangci-lintgofumptgosecgovulncheck、竞态测试、基准、覆盖率和 CHANGELOG 校验;总覆盖率默认必须达到 80%,CI 可通过 COVERAGE_MIN=85 make verify-release 提高阈值。make tag 会先拒绝脏工作区和已存在版本标签,再运行相同发布检查,成功后仅创建本地中文附注标签,不会修改版本、创建 commit 或推送远端。

单元测试直接 go test ./...。迁移执行的集成测试需要真实 MySQL,通过环境变量显式开启:

# ⚠️ 目标库会被清空,必须指向专用测试库;DSN 必须带 parseTime=true
MIGRATEX_MYSQL_DSN='root:pass@tcp(127.0.0.1:3306)/migratex_test?parseTime=true' \
    go test -race -count=1 ./...

未设置该变量时集成测试自动跳过。

License

MIT,见 LICENSE

Documentation

Overview

Package migrate 提供基于 goose/v3 + GORM + Cobra 的数据库迁移与代码生成命令. 迁移执行引擎当前仅支持 MySQL.

通过 NewCommands 构建命令树并挂载到应用的根命令:

cmds, err := migrate.NewCommands(db,
	migrate.WithProjectName("example.com/app"),
	migrate.WithMigrationDir("database/migrations"),
)
if err != nil {
	return err
}
rootCmd.AddCommand(cmds.Migrate, cmds.Make)

迁移是 goose 注解格式的单文件 SQL(<纯数字版本>_<名称>.sql,含 "-- +goose Up" 与 "-- +goose Down" 区段),由 make migration 生成; 读取、排序、执行与版本管理由 pressly/goose/v3 Provider 完成, migratex 负责命令封装、文件生成、静态 lint、孤儿版本检测与 MySQL advisory lock 并发互斥.

并发安全:写类命令(up/down/down-to/reset/refresh/mark-applied/fresh)之间由 MySQL GET_LOCK 互斥;读类命令(status/pretend/lint)不获取锁,可与迁移并行执行. down、down-to、reset、refresh、mark-applied 与 fresh 必须显式传入 --force;refresh 在单锁内 先回滚全部再重跑全部;mark-applied 只把 pending 版本写入 goose 版本表标记为已应用、不执行迁移 SQL,用于 baseline 已存在等价 schema 的存量库.

设计约束:迁移文件与业务 model 解耦(显式 SQL 固定迁移语义), 保证 fresh、测试库初始化和灾备重建可以从零重放全部历史迁移; 正式库结构变更不使用 GORM AutoMigrate.

Index

Examples

Constants

View Source
const Version = "v0.3.0"

Version 是 migratex 的当前版本,与仓库最新 tag 保持一致.

Variables

This section is empty.

Functions

This section is empty.

Types

type Commands

type Commands struct {
	Migrate *cobra.Command
	Make    *cobra.Command
}

Commands 是构建好的命令树集合. Migrate 对应 `migrate` 命令(up/down/down-to/reset/refresh/mark-applied/status/pretend/fresh/lint), Make 对应 `make` 命令(model/migration/ddl/cmd). 调用方按需挂载:生产二进制可只挂 Migrate,把代码生成排除在外.

func NewCommands

func NewCommands(db *gorm.DB, opts ...Option) (*Commands, error)

NewCommands 构建迁移与代码生成命令树. db 为 nil 或 Option 输入非法时返回 migration.ErrInvalidArgument;opts 中的 nil Option 被忽略; 迁移执行引擎当前仅支持 MySQL,其他方言返回 migration.ErrUnsupportedDatabase. 每次调用返回独立的命令树,同一进程内多次调用互不影响.

并发安全:读类命令(status/pretend/lint)只读——不获取迁移锁、不创建版本表、 无任何数据库写入,可与迁移并行执行;写类命令(up/down/down-to/reset/refresh/mark-applied/fresh) 之间由 MySQL advisory lock 互斥(fresh 从删表起、refresh 从回滚起、mark-applied 写版本表期间全程持锁), 且执行前会对迁移目录强制静态 lint,存在 error 级问题(如 empty_up/empty_down 半成品)时拒绝触碰数据库. mark-applied 只写 goose 版本表、不执行迁移 SQL,用于 baseline 已存在等价 schema 的存量库.

Example

ExampleNewCommands 展示如何构建命令树并挂载到应用的根命令. 迁移执行引擎当前仅支持 MySQL;DSN 需带 parseTime=true. 本示例不连接真实数据库,仅演示用法(无 Output 断言,编译校验).

package main

import (
	"log"

	migrate "github.com/gtkit/migratex"
	"github.com/gtkit/migratex/migration"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

func main() {
	db, err := gorm.Open(mysql.Open("user:pass@tcp(127.0.0.1:3306)/app?parseTime=true"), &gorm.Config{})
	if err != nil {
		log.Fatal(err)
	}

	cmds, err := migrate.NewCommands(db,
		migrate.WithProjectName("example.com/app"),
		migrate.WithMigrationDir("database/migrations"),
		migrate.WithLogger(&migration.NopLogger{}),
	)
	if err != nil {
		log.Fatal(err)
	}

	// 挂载到应用根命令:
	// rootCmd.AddCommand(cmds.Migrate, cmds.Make)
	_ = cmds
}

type Option

type Option func(*config)

Option 配置选项函数. 非法输入会使 NewCommands 返回 migration.ErrInvalidArgument.

func WithCmdDir added in v0.3.0

func WithCmdDir(dir string) Option

WithCmdDir 设置 make cmd 生成目录(默认 "cmd").

func WithDDLDir

func WithDDLDir(dir string) Option

WithDDLDir 设置 DDL 输出目录(默认 "database/ddl").

func WithDDLModels

func WithDDLModels(models ...any) Option

WithDDLModels 注册可用于 make ddl 的 GORM 模型.

func WithLogger

func WithLogger(l migration.Logger) Option

WithLogger 设置结构化日志实现. 传入 nil 会使 NewCommands 返回 migration.ErrInvalidArgument;生产环境推荐注入 zerolog/zap 等实现.

func WithMigrationDir

func WithMigrationDir(dir string) Option

WithMigrationDir 设置迁移文件目录(默认 "database/migrations").

func WithModelDir

func WithModelDir(dir string) Option

WithModelDir 设置 model 生成目录(默认 "internal/models").

func WithProjectName

func WithProjectName(name string) Option

WithProjectName 设置项目名称(module path),用于代码生成的 import 路径.

func WithRepositoryDir

func WithRepositoryDir(dir string) Option

WithRepositoryDir 设置 repository 生成目录(默认 "internal/repository").

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout 设置迁移操作超时(默认 5 分钟). 超时同时决定 MySQL advisory lock 的最长等待时间.

Directories

Path Synopsis
Package console 命令行辅助方法.
Package console 命令行辅助方法.
examples
basic command
Package file 文件操作辅助函数.
Package file 文件操作辅助函数.
Package make 命令行的 make 命令.
Package make 命令行的 make 命令.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL