package db import ( "math" "testing" ) // ───────────────────────────────────────────── // classifyDBType // ───────────────────────────────────────────── func TestClassifyDBType(t *testing.T) { cases := []struct { typeName string want int }{ // MySQL 整数类型 {"INT", dbTypeInteger}, {"TINYINT", dbTypeInteger}, {"SMALLINT", dbTypeInteger}, {"MEDIUMINT", dbTypeInteger}, {"BIGINT", dbTypeInteger}, {"INT UNSIGNED", dbTypeInteger}, {"BIGINT UNSIGNED", dbTypeInteger}, {"TINYINT(1)", dbTypeInteger}, {"YEAR", dbTypeInteger}, {"BOOL", dbTypeInteger}, {"BOOLEAN", dbTypeInteger}, // PostgreSQL 整数类型 {"INT2", dbTypeInteger}, {"INT4", dbTypeInteger}, {"INT8", dbTypeInteger}, {"SERIAL", dbTypeInteger}, {"BIGSERIAL", dbTypeInteger}, {"SMALLSERIAL", dbTypeInteger}, {"OID", dbTypeInteger}, // DECIMAL / NUMERIC {"DECIMAL", dbTypeDecimal}, {"DECIMAL(10,2)", dbTypeDecimal}, {"NUMERIC", dbTypeDecimal}, {"NUMERIC(8,4)", dbTypeDecimal}, {"NEWDECIMAL", dbTypeDecimal}, // FLOAT / DOUBLE {"FLOAT", dbTypeFloat}, {"DOUBLE", dbTypeFloat}, {"REAL", dbTypeFloat}, {"FLOAT4", dbTypeFloat}, {"FLOAT8", dbTypeFloat}, {"DOUBLE PRECISION", dbTypeFloat}, // 未知类型 {"VARCHAR", dbTypeUnknown}, {"TEXT", dbTypeUnknown}, {"DATETIME", dbTypeUnknown}, {"TIMESTAMP", dbTypeUnknown}, {"BLOB", dbTypeUnknown}, {"", dbTypeUnknown}, } for _, c := range cases { t.Run(c.typeName, func(t *testing.T) { got := classifyDBType(c.typeName) if got != c.want { t.Errorf("classifyDBType(%q) = %d, want %d", c.typeName, got, c.want) } }) } } // ───────────────────────────────────────────── // fixFloatValue // ───────────────────────────────────────────── func TestFixFloatValue(t *testing.T) { t.Run("integer category: round to int64", func(t *testing.T) { // 这是核心场景:float64 精度噪声导致整数变为 1.9999... got := fixFloatValue(1.9999999999999998, dbTypeInteger, 0) if got != int64(2) { t.Errorf("got %v (%T), want int64(2)", got, got) } }) t.Run("integer category: 2.0000000000001 rounds to 2", func(t *testing.T) { got := fixFloatValue(2.0000000000001, dbTypeInteger, 0) if got != int64(2) { t.Errorf("got %v (%T), want int64(2)", got, got) } }) t.Run("decimal category: scale=2 roundtrip precision", func(t *testing.T) { got := fixFloatValue(1.1999999999999999, dbTypeDecimal, 2) f, ok := got.(float64) if !ok { t.Fatalf("expected float64, got %T", got) } if f != 1.2 { t.Errorf("got %v, want 1.2", f) } }) t.Run("decimal category: scale=2, 1.20000000001 -> 1.2", func(t *testing.T) { got := fixFloatValue(1.20000000001, dbTypeDecimal, 2) f := got.(float64) if f != 1.2 { t.Errorf("got %v, want 1.2", f) } }) t.Run("decimal category: scale=-1 (unknown), no modification", func(t *testing.T) { got := fixFloatValue(1.23456789, dbTypeDecimal, -1) if got != 1.23456789 { t.Errorf("got %v, want 1.23456789", got) } }) t.Run("decimal category: scale=0, round to whole number", func(t *testing.T) { got := fixFloatValue(2.7, dbTypeDecimal, 0) if got != float64(3) { t.Errorf("got %v (%T), want float64(3)", got, got) } }) t.Run("float category: scale=-1 no modification", func(t *testing.T) { got := fixFloatValue(3.14159265, dbTypeFloat, -1) if got != 3.14159265 { t.Errorf("got %v, want 3.14159265", got) } }) t.Run("unknown category: passthrough", func(t *testing.T) { got := fixFloatValue(99.9, dbTypeUnknown, -1) if got != 99.9 { t.Errorf("got %v, want 99.9", got) } }) t.Run("NaN -> 0", func(t *testing.T) { got := fixFloatValue(math.NaN(), dbTypeInteger, 0) if got != float64(0) { t.Errorf("got %v, want 0", got) } }) t.Run("+Inf -> 0", func(t *testing.T) { got := fixFloatValue(math.Inf(1), dbTypeDecimal, 2) if got != float64(0) { t.Errorf("got %v, want 0", got) } }) } // ───────────────────────────────────────────── // roundFloat // ───────────────────────────────────────────── func TestRoundFloat(t *testing.T) { t.Run("scale=2: 1.19999... -> 1.2", func(t *testing.T) { got := roundFloat(1.1999999999999999, 2) if got != float64(1.2) { t.Errorf("got %v, want 1.2", got) } }) t.Run("scale=2: 1.20000...1 -> 1.2", func(t *testing.T) { got := roundFloat(1.20000000001, 2) if got != float64(1.2) { t.Errorf("got %v, want 1.2", got) } }) t.Run("scale=0: round to integer float", func(t *testing.T) { got := roundFloat(2.5, 0) if got != float64(3) { t.Errorf("got %v, want 3.0", got) } }) t.Run("scale=-1: passthrough unchanged", func(t *testing.T) { got := roundFloat(1.23456789, -1) if got != 1.23456789 { t.Errorf("got %v, want 1.23456789", got) } }) t.Run("NaN -> 0", func(t *testing.T) { got := roundFloat(math.NaN(), 2) if got != float64(0) { t.Errorf("got %v, want 0", got) } }) } // ───────────────────────────────────────────── // convertBytes // ───────────────────────────────────────────── func TestConvertBytes(t *testing.T) { t.Run("integer category: '2' -> int64(2)", func(t *testing.T) { got := convertBytes([]byte("2"), dbTypeInteger, 0) if got != int64(2) { t.Errorf("got %v (%T), want int64(2)", got, got) } }) t.Run("integer category: '2.00' -> int64(2)", func(t *testing.T) { got := convertBytes([]byte("2.00"), dbTypeInteger, 0) if got != int64(2) { t.Errorf("got %v (%T), want int64(2)", got, got) } }) t.Run("decimal category: '1.20' scale=2 -> float64(1.2)", func(t *testing.T) { got := convertBytes([]byte("1.20"), dbTypeDecimal, 2) if got != float64(1.2) { t.Errorf("got %v (%T), want float64(1.2)", got, got) } }) t.Run("decimal category: '0.00' scale=2 -> float64(0)", func(t *testing.T) { got := convertBytes([]byte("0.00"), dbTypeDecimal, 2) if got != float64(0) { t.Errorf("got %v (%T), want float64(0)", got, got) } }) t.Run("float category: '3.14' scale=-1 -> float64(3.14)", func(t *testing.T) { got := convertBytes([]byte("3.14"), dbTypeFloat, -1) if got != float64(3.14) { t.Errorf("got %v (%T), want float64(3.14)", got, got) } }) t.Run("unknown category: passthrough as string", func(t *testing.T) { got := convertBytes([]byte("hello"), dbTypeUnknown, 0) if got != "hello" { t.Errorf("got %v (%T), want 'hello'", got, got) } }) t.Run("integer category: invalid bytes -> string fallback", func(t *testing.T) { got := convertBytes([]byte("abc"), dbTypeInteger, 0) if got != "abc" { t.Errorf("got %v (%T), want 'abc'", got, got) } }) }