value_test.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. package migrate
  2. import (
  3. "TsDB2/DB/core/types"
  4. "math"
  5. "testing"
  6. )
  7. // TestRoundFloatToIntBoundary 钉住上界的写法。
  8. //
  9. // float64(math.MaxInt64) 存不下 2^63-1, 会被舍入成 2^63 —— 于是 "f > MaxInt64"
  10. // 这个看着像校验的守卫, 恰好放过了唯一越界的那一个值, 而 int64(2^63) 是越界转换
  11. // (结果未定义, amd64 上回绕成 MinInt64): 一个超范围的值静默变成反数存进去。
  12. // 下界不受影响: MinInt64 = -2^63 在 float64 里可以精确表示。
  13. func TestRoundFloatToIntBoundary(t *testing.T) {
  14. if got, err := roundFloatToInt(0); err != nil || got != 0 {
  15. t.Errorf("0 应当原样通过, 实际 (%d, %v)", got, err)
  16. }
  17. if _, err := roundFloatToInt(float64(math.MaxInt32)); err != nil {
  18. t.Errorf("MaxInt32 远在范围内, 不该报错: %v", err)
  19. }
  20. // 下界是可以精确表示的那个值, 不该被误伤
  21. if got, err := roundFloatToInt(math.MinInt64); err != nil || got != math.MinInt64 {
  22. t.Errorf("MinInt64 应当原样通过, 实际 (%d, %v)", got, err)
  23. }
  24. // 注意别写 math.MinInt64 - 1 当越界样例: 那个数学值在 float64 里根本没有对应的
  25. // 格点, 会被舍入回 -2^63 这个合法值。上界那边正好相反 —— MaxInt64 舍入到 2^63,
  26. // 恰好越过边界, 所以 >= 才是对的。
  27. for _, f := range []float64{
  28. math.MaxInt64, // 2^63: 越界, 但 float64(MaxInt64) 也正好等于它
  29. math.MaxInt64 * 2,
  30. math.Pow(2, 63) * 4,
  31. math.Inf(1),
  32. math.Inf(-1),
  33. math.NaN(),
  34. } {
  35. if got, err := roundFloatToInt(f); err == nil {
  36. t.Errorf("%v 超出 int64, 应当报错, 实际返回 %d", f, got)
  37. }
  38. }
  39. }
  40. // TestCoerceForSQL8ByteByDialect 8byte 绑定的 Go 类型必须跟目标列的【声明】一致,
  41. // 而两个方言的声明本就不同 (见 sqlTypeName):
  42. //
  43. // - MySQL 声明 BIGINT UNSIGNED → 绑 uint64。以前一律绑 int64(u), 高位为 1 的值
  44. // (包括 VRTX 从 int64 负值搬过来的位模式) 在严格模式下会被判成
  45. // "Out of range value" 直接报错, 非严格模式则被静默改写。
  46. // - SQLite 只有有符号的 INTEGER → 绑 int64, 位模式原样存。
  47. //
  48. // 共同的保证是【位模式不变】; 两种绑定在各自的列里都必须无损。
  49. func TestCoerceForSQL8ByteByDialect(t *testing.T) {
  50. col := Column{Name: "u", Type: types.FieldType_8byte}
  51. cases := []struct {
  52. name string
  53. in any
  54. wantMySQL any
  55. wantSQLite any
  56. }{
  57. {"uint64 上界", uint64(math.MaxUint64), uint64(math.MaxUint64), int64(-1)},
  58. {"int64 负数 (位模式)", int64(-1), uint64(math.MaxUint64), int64(-1)},
  59. {"普通值", int64(42), uint64(42), int64(42)},
  60. {"2^63 (无符号档才装得下)", uint64(math.MaxInt64) + 1, uint64(math.MaxInt64) + 1, int64(math.MinInt64)},
  61. }
  62. for _, c := range cases {
  63. gotMySQL, err := coerceForSQL(col, c.in, true)
  64. if err != nil {
  65. t.Errorf("%s: MySQL 侧不该报错: %v", c.name, err)
  66. } else if gotMySQL != c.wantMySQL {
  67. t.Errorf("%s: MySQL 侧 = %v (%T), 期望 %v (%T)",
  68. c.name, gotMySQL, gotMySQL, c.wantMySQL, c.wantMySQL)
  69. }
  70. gotSQLite, err := coerceForSQL(col, c.in, false)
  71. if err != nil {
  72. t.Errorf("%s: SQLite 侧不该报错: %v", c.name, err)
  73. } else if gotSQLite != c.wantSQLite {
  74. t.Errorf("%s: SQLite 侧 = %v (%T), 期望 %v (%T)",
  75. c.name, gotSQLite, gotSQLite, c.wantSQLite, c.wantSQLite)
  76. }
  77. }
  78. // 时间列走的还是"还原成时间文本"那条路, 与方言无关 —— 别被上面的方言分支带走。
  79. timeCol := Column{Name: "t", Type: types.FieldType_8byte, TimeKind: "datetime"}
  80. for _, mysql := range []bool{true, false} {
  81. v, err := coerceForSQL(timeCol, int64(0), mysql)
  82. if err != nil {
  83. t.Fatalf("时间列 (mysql=%v) 不该报错: %v", mysql, err)
  84. }
  85. if _, ok := v.(string); !ok {
  86. t.Errorf("时间列 (mysql=%v) 应当还原成时间文本, 实际 %T", mysql, v)
  87. }
  88. }
  89. }