| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- package fixture
- import (
- "database/sql"
- "os"
- "path/filepath"
- "strings"
- "testing"
- )
- // 本文件单测结构对齐工具的**纯函数部分**:ALGORITHM 子句、DDL 解析、MODIFY 语句构造。
- // 真库比对与执行由 cmd/ac15fixture align 完成(需连库,不进单测)。
- func TestAlgorithmSuffix(t *testing.T) {
- cases := map[string]string{
- "instant": ", ALGORITHM=INSTANT",
- "INSTANT": ", ALGORITHM=INSTANT",
- "inplace": ", ALGORITHM=INPLACE, LOCK=NONE",
- "copy": ", ALGORITHM=COPY",
- "none": "",
- "": "",
- "garbage": "",
- }
- for in, want := range cases {
- if got := algorithmSuffix(in); got != want {
- t.Errorf("algorithmSuffix(%q) = %q, want %q", in, got, want)
- }
- }
- }
- // TestBuildModifySQLKeepsOtherAttributes 是本工具最关键的约束:
- // MODIFY 只许改 DEFAULT(与 ON UPDATE),type / NULL 性 / 注释必须**照抄实库**。
- func TestBuildModifySQLKeepsOtherAttributes(t *testing.T) {
- live := liveColumn{
- Table: "t_x", Column: "created_at", ColumnType: "datetime(3)",
- Nullable: true, Comment: "创建时间",
- }
- ddl := ddlColumn{Table: "t_x", Column: "created_at", NotNull: true, HasDefault: true}
- // 默认(不修 NULL 性):NULL 性必须保持实库的"可空"
- got := buildModifySQL(live, ddl, false, "none")
- want := "ALTER TABLE `t_x` MODIFY COLUMN `created_at` datetime(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间'"
- if got != want {
- t.Errorf("不修 NULL 性时语句不符:\n got=%q\nwant=%q", got, want)
- }
- // 修 NULL 性:只多一个 NOT NULL,其它不变
- got = buildModifySQL(live, ddl, true, "none")
- want = "ALTER TABLE `t_x` MODIFY COLUMN `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间'"
- if got != want {
- t.Errorf("修 NULL 性时语句不符:\n got=%q\nwant=%q", got, want)
- }
- // updated_at 必须带 ON UPDATE,且位于 DEFAULT 之后、COMMENT 之前
- live2 := liveColumn{Table: "t_x", Column: "updated_at", ColumnType: "datetime(3)"}
- ddl2 := ddlColumn{Table: "t_x", Column: "updated_at", NotNull: true, HasDefault: true, OnUpdate: true}
- got = buildModifySQL(live2, ddl2, false, "copy")
- want = "ALTER TABLE `t_x` MODIFY COLUMN `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), ALGORITHM=COPY"
- if got != want {
- t.Errorf("updated_at 语句不符:\n got=%q\nwant=%q", got, want)
- }
- }
- // TestBuildModifySQLEscapesComment 证明注释里的单引号会被转义(不破坏语句)。
- func TestBuildModifySQLEscapesComment(t *testing.T) {
- live := liveColumn{Table: "t", Column: "created_at", ColumnType: "datetime(3)", Comment: "it's fine"}
- ddl := ddlColumn{Table: "t", Column: "created_at", NotNull: true, HasDefault: true}
- got := buildModifySQL(live, ddl, false, "none")
- if !strings.Contains(got, "COMMENT 'it''s fine'") {
- t.Errorf("注释未正确转义:%q", got)
- }
- }
- // TestParseDDLFile 用一个小样本验证解析口径(NOT NULL / DEFAULT / ON UPDATE / COMMENT)。
- func TestParseDDLFile(t *testing.T) {
- src := `
- -- 注释行应被忽略
- SET NAMES utf8mb4;
- CREATE TABLE ` + "`t_demo`" + ` (
- ` + "`id`" + ` BIGINT NOT NULL AUTO_INCREMENT,
- ` + "`created_at`" + ` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
- ` + "`updated_at`" + ` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
- ` + "`update_time`" + ` DATETIME(3) DEFAULT NULL COMMENT '最后修改时间',
- PRIMARY KEY (` + "`id`" + `)
- ) ENGINE=InnoDB;
- `
- dir := t.TempDir()
- p := filepath.Join(dir, "demo.sql")
- if err := os.WriteFile(p, []byte(src), 0o600); err != nil {
- t.Fatal(err)
- }
- cols, err := ParseDDLFile(p, []string{"created_at", "updated_at", "update_time"})
- if err != nil {
- t.Fatal(err)
- }
- if len(cols) != 3 {
- t.Fatalf("应解析到 3 列,实际 %d:%+v", len(cols), cols)
- }
- byName := map[string]ddlColumn{}
- for _, c := range cols {
- byName[c.Column] = c
- }
- if c := byName["created_at"]; !c.NotNull || !c.HasDefault || c.OnUpdate {
- t.Errorf("created_at 解析不符:%+v", c)
- }
- if c := byName["updated_at"]; !c.OnUpdate || !c.NotNull {
- t.Errorf("updated_at 解析不符:%+v", c)
- }
- // update_time 的 DDL 是 DEFAULT NULL —— HasDefault 会被识别成 true,
- // 但 NotNull=false,所以分类时应落到 ddl-no-default 之外的分支由 NULL 性判断先拦住。
- if c := byName["update_time"]; c.NotNull {
- t.Errorf("update_time DDL 是可空,不该解析成 NOT NULL:%+v", c)
- }
- }
- // TestCreateTimeAssertionNeedsMarker 证明「没有运行标记时断言不放行」
- // (不允许"没标记就当通过"这种自欺)。
- func TestCreateTimeAssertionNeedsMarker(t *testing.T) {
- // 用一个必然查不到的 db 指针不行(会 panic),这里只验证函数签名与语义说明存在:
- // 真正的 marker 语义由 cmd/ac15fixture verify 的集成路径覆盖。
- var _ = sql.NullString{}
- if RunMarkerAction == "" {
- t.Error("RunMarkerAction 不应为空")
- }
- }
|