| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- package migrate
- // 裸 NUMERIC (GORM sqlite 方言的 bool 声明) 的迁移映射与数据探测。
- // MySQL 的 NUMERIC 是 DECIMAL 别名, 不在此列 (见 TestBareNumericInfer 的 mysql 断言)。
- import (
- "fmt"
- "path/filepath"
- "strings"
- "testing"
- "git.ycl.pub/yl32940/TsDB2/DB/core"
- "git.ycl.pub/yl32940/TsDB2/DB/core/types"
- )
- // buildSQLiteNumeric 建一个带裸 NUMERIC 列的 sqlite 源文件 (写完即关)。
- func buildSQLiteNumeric(t *testing.T, path, ddl string, rows []string) {
- t.Helper()
- src, err := OpenSQLite(path, quiet())
- if err != nil {
- t.Fatal(err)
- }
- defer src.Close()
- if _, err := src.db.Exec(ddl); err != nil {
- t.Fatalf("建表: %v", err)
- }
- for _, r := range rows {
- if _, err := src.db.Exec(r); err != nil {
- t.Fatalf("插行 %q: %v", r, err)
- }
- }
- }
- // runSQLiteToVRTX sqlite 源文件 → 全新 VRTX 目标, 返回目标快照。
- func runSQLiteToVRTX(t *testing.T, srcPath, dstPath, table string) vrtxSnapshot {
- t.Helper()
- src, err := OpenSQLite(srcPath, quiet())
- if err != nil {
- t.Fatal(err)
- }
- defer src.Close()
- dst, err := OpenVRTX(dstPath, quiet())
- if err != nil {
- t.Fatal(err)
- }
- if _, err := Run(src, dst, quiet()); err != nil {
- _ = dst.Close()
- t.Fatal(err)
- }
- // 先关句柄释放文件锁, 快照才能重新打开同一文件
- if err := dst.Close(); err != nil {
- t.Fatal(err)
- }
- return snapshotVRTX(t, dstPath, table)
- }
- // hasColumn 快照列清单里是否含 "name:type" 前缀条目。
- func hasColumn(snap vrtxSnapshot, prefix string) bool {
- for _, c := range snap.columns {
- if strings.HasPrefix(c, prefix) {
- return true
- }
- }
- return false
- }
- // TestBareNumericBool 裸 NUMERIC 全 0/1 → Bool 列, 值往返, 引擎侧写 Go bool 不拒。
- func TestBareNumericBool(t *testing.T) {
- dir := t.TempDir()
- srcPath := filepath.Join(dir, "src.sqlite")
- buildSQLiteNumeric(t, srcPath,
- `CREATE TABLE "flags" ("id" INTEGER PRIMARY KEY, "is_error" NUMERIC, "note" TEXT)`,
- []string{
- `INSERT INTO "flags" (id, is_error, note) VALUES (1, 0, 'a')`,
- `INSERT INTO "flags" (id, is_error, note) VALUES (2, 1, 'b')`,
- })
- dstPath := filepath.Join(dir, "dst.db")
- snap := runSQLiteToVRTX(t, srcPath, dstPath, "flags")
- if !hasColumn(snap, "is_error:bool") {
- t.Fatalf("is_error 应收成 bool 列, 快照: %v", snap.columns)
- }
- if v := fmt.Sprint(snap.rows[2]["is_error"]); v != "true" {
- t.Fatalf("is_error=1 应读回 true, 实际 %v (rows=%v)", v, snap.rows)
- }
- // 复刻 GORM 写 Go bool 的场景: 引擎侧直插不拒
- db, err := core.Open(dstPath)
- if err != nil {
- t.Fatal(err)
- }
- defer db.Close()
- tbl, _ := db.FindTable("flags")
- if _, err := tbl.InsertRowWithID(3, []types.FieldValue{
- {FieldName: []byte("id"), Value: uint64(3)},
- {FieldName: []byte("is_error"), Value: true},
- {FieldName: []byte("note"), Value: "c"},
- }); err != nil {
- t.Fatalf("Bool 列写 bool 被拒: %v", err)
- }
- }
- // TestBareNumericOutOfDomain 裸 NUMERIC 含域外值 (1.5) → 数据探测改回 Decimal。
- func TestBareNumericOutOfDomain(t *testing.T) {
- dir := t.TempDir()
- srcPath := filepath.Join(dir, "src.sqlite")
- buildSQLiteNumeric(t, srcPath,
- `CREATE TABLE "m" ("id" INTEGER PRIMARY KEY, "ratio" NUMERIC)`,
- []string{
- `INSERT INTO "m" (id, ratio) VALUES (1, 0)`,
- `INSERT INTO "m" (id, ratio) VALUES (2, 1)`,
- `INSERT INTO "m" (id, ratio) VALUES (3, 1.5)`,
- })
- dstPath := filepath.Join(dir, "dst.db")
- snap := runSQLiteToVRTX(t, srcPath, dstPath, "m")
- if !hasColumn(snap, "ratio:float64") {
- t.Fatalf("域外值应探测降级 float64, 快照: %v", snap.columns)
- }
- }
- // TestBareNumericInfer 单元: 裸 NUMERIC 的分方言映射。
- func TestBareNumericInfer(t *testing.T) {
- if ft, _ := inferSQLType("NUMERIC", false); ft != types.FieldType_Bool {
- t.Fatalf("sqlite 裸 NUMERIC 应收 Bool, 实际 %v", ft)
- }
- if ft, _ := inferSQLType("NUMERIC", true); ft != types.FieldType_Decimal {
- t.Fatalf("mysql 裸 NUMERIC 应收 Decimal, 实际 %v", ft)
- }
- if ft, _ := inferSQLType("NUMERIC(10,2)", false); ft != types.FieldType_Decimal {
- t.Fatalf("NUMERIC(10,2) 应收 Decimal, 实际 %v", ft)
- }
- if ft, _ := inferSQLType("DECIMAL", false); ft != types.FieldType_Decimal {
- t.Fatalf("裸 DECIMAL 应收 Decimal, 实际 %v", ft)
- }
- }
|