|
@@ -0,0 +1,91 @@
|
|
|
|
|
+// 列类型归一单测(AC 对账工具口径的回归护栏)。
|
|
|
|
|
+//
|
|
|
|
|
+// 设计约束(v0.1.2 §2.4):归一必须"只放过显示宽度差异",**绝不能**把精度/长度差异一并放过,
|
|
|
|
|
+// 否则 decimal(12,3)、varchar(26)、datetime(无毫秒)等真缺陷会被静默吞掉。
|
|
|
|
|
+// 因此本文件的正例(必须等价)与负例(必须不等价)同等重要,负例不得删减。
|
|
|
|
|
+package dbtest
|
|
|
|
|
+
|
|
|
|
|
+import "testing"
|
|
|
|
|
+
|
|
|
|
|
+// TestNormalizeColumnTypePositive 正例:显示宽度/同义词差异必须归一到同一签名。
|
|
|
|
|
+func TestNormalizeColumnTypePositive(t *testing.T) {
|
|
|
|
|
+ cases := []struct {
|
|
|
|
|
+ name string
|
|
|
|
|
+ a string
|
|
|
|
|
+ b string
|
|
|
|
|
+ want string // 归一后的期望签名(同时验证归一结果本身)
|
|
|
|
|
+ }{
|
|
|
|
|
+ {"TINYINT(1) ≡ tinyint(核心假阳性场景)", "TINYINT(1)", "tinyint", "TINYINT"},
|
|
|
|
|
+ {"TINYINT(1) ≡ TINYINT(1)", "TINYINT(1)", "tinyint(1)", "TINYINT"},
|
|
|
|
|
+ {"BOOLEAN ≡ tinyint(bool 别名)", "BOOLEAN", "tinyint", "TINYINT"},
|
|
|
|
|
+ {"bool ≡ TINYINT(1)", "bool", "TINYINT(1)", "TINYINT"},
|
|
|
|
|
+ {"INT(11) ≡ int", "INT(11)", "int", "INT"},
|
|
|
|
|
+ {"INTEGER ≡ INT(同义词)", "INTEGER", "INT(10)", "INT"},
|
|
|
|
|
+ {"BIGINT(20) ≡ bigint", "BIGINT(20)", "bigint", "BIGINT"},
|
|
|
|
|
+ {"SMALLINT(6) ≡ smallint", "SMALLINT(6)", "smallint", "SMALLINT"},
|
|
|
|
|
+ {"MEDIUMINT(9) ≡ mediumint", "MEDIUMINT(9)", "mediumint", "MEDIUMINT"},
|
|
|
|
|
+ {"空白折叠", " tinyint ( 1 ) ", "TINYINT", "TINYINT"},
|
|
|
|
|
+ {"属性保留但仍等价", "BIGINT(20) UNSIGNED", "bigint unsigned", "BIGINT UNSIGNED"},
|
|
|
|
|
+ {"decimal 相同参数", "DECIMAL(12,2)", "decimal(12,2)", "DECIMAL(12,2)"},
|
|
|
|
|
+ {"varchar 相同长度", "VARCHAR(26)", "varchar(26)", "VARCHAR(26)"},
|
|
|
|
|
+ {"datetime(3) 相同精度", "DATETIME(3)", "datetime(3)", "DATETIME(3)"},
|
|
|
|
|
+ {"DATE 无参数", "DATE", "date", "DATE"},
|
|
|
|
|
+ {"LONGTEXT", "LONGTEXT", "longtext", "LONGTEXT"},
|
|
|
|
|
+ }
|
|
|
|
|
+ for _, tc := range cases {
|
|
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
+ if got := NormalizeColumnType(tc.a); got != tc.want {
|
|
|
|
|
+ t.Fatalf("NormalizeColumnType(%q) = %q,期望 %q", tc.a, got, tc.want)
|
|
|
|
|
+ }
|
|
|
|
|
+ if !SameColumnType(tc.a, tc.b) {
|
|
|
|
|
+ t.Fatalf("%q 与 %q 应判定等价(归一后 %q vs %q)",
|
|
|
|
|
+ tc.a, tc.b, NormalizeColumnType(tc.a), NormalizeColumnType(tc.b))
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// TestNormalizeColumnTypeNegative 负例(**不得删减**):精度/长度/族差异必须判为不一致。
|
|
|
|
|
+//
|
|
|
|
|
+// 这些是 DDL 与真库之间真正会出现的偏差类型;若归一规则把参数也吃掉,这里会全绿但线上漏检。
|
|
|
|
|
+func TestNormalizeColumnTypeNegative(t *testing.T) {
|
|
|
|
|
+ cases := []struct {
|
|
|
|
|
+ name string
|
|
|
|
|
+ a string
|
|
|
|
|
+ b string
|
|
|
|
|
+ }{
|
|
|
|
|
+ {"decimal 精度不同", "DECIMAL(12,2)", "DECIMAL(12,3)"},
|
|
|
|
|
+ {"decimal 位数不同", "DECIMAL(12,2)", "DECIMAL(10,2)"},
|
|
|
|
|
+ {"varchar 长度不同", "VARCHAR(32)", "VARCHAR(26)"},
|
|
|
|
|
+ {"char vs varchar", "CHAR(26)", "VARCHAR(26)"},
|
|
|
|
|
+ {"datetime 无毫秒 vs 有毫秒", "DATETIME", "DATETIME(3)"},
|
|
|
|
|
+ {"datetime 精度不同", "DATETIME(3)", "DATETIME(6)"},
|
|
|
|
|
+ {"整数族不同", "TINYINT", "SMALLINT"},
|
|
|
|
|
+ {"整数族不同(2)", "INT", "BIGINT"},
|
|
|
|
|
+ {"有符号 vs 无符号", "BIGINT", "BIGINT UNSIGNED"},
|
|
|
|
|
+ {"文本族不同", "LONGTEXT", "TEXT"},
|
|
|
|
|
+ {"double 精度不同", "DOUBLE(10,3)", "DOUBLE(10,2)"},
|
|
|
|
|
+ {"空值 vs 有值", "", "TINYINT"},
|
|
|
|
|
+ }
|
|
|
|
|
+ for _, tc := range cases {
|
|
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
+ if SameColumnType(tc.a, tc.b) {
|
|
|
|
|
+ t.Fatalf("%q 与 %q 必须判为不一致(归一后 %q vs %q)",
|
|
|
|
|
+ tc.a, tc.b, NormalizeColumnType(tc.a), NormalizeColumnType(tc.b))
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// TestNormalizeColumnTypeIdempotent 幂等:归一结果再归一必须不变(防止二次归一把精度吃掉)。
|
|
|
|
|
+func TestNormalizeColumnTypeIdempotent(t *testing.T) {
|
|
|
|
|
+ for _, raw := range []string{
|
|
|
|
|
+ "tinyint(1)", "TINYINT", "boolean", "int(11)", "integer", "decimal(12,2)",
|
|
|
|
|
+ "varchar(26)", "datetime(3)", "datetime", "bigint unsigned", "longtext", "", " ",
|
|
|
|
|
+ } {
|
|
|
|
|
+ once := NormalizeColumnType(raw)
|
|
|
|
|
+ if twice := NormalizeColumnType(once); twice != once {
|
|
|
|
|
+ t.Fatalf("归一不幂等:%q → %q → %q", raw, once, twice)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|