value.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934
  1. package migrate
  2. import (
  3. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  4. "fmt"
  5. "math"
  6. "math/big"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "unicode/utf8"
  11. )
  12. // ============================ 值转换 ============================
  13. //
  14. // 两端拿到的值形态不同:
  15. // VRTX 侧 (deserializeRow 的产出): uint8 / int16 / int32 / uint64 / float64 / []byte
  16. // SQL 侧 (database/sql 的驱动值): int64 / float64 / []byte / string / nil / time.Time
  17. //
  18. // 中间表示不做统一 (会把值搬两遍), 而是让目标端按自己的列类型把来源值"收编":
  19. // 走到 VRTX 用 coerceForVRTX, 走到 SQL 用 coerceForSQL。
  20. // zeroForVRTX 某种 VRTX 列类型的零值。
  21. //
  22. // VRTX 没有 NULL 这个概念 (变长列的行内引用 PageID==0 就是"空"), 所以 SQL 的 NULL
  23. // 落到这里只能是零值 —— 这是单向的信息损失, 文档里明说。
  24. func zeroForVRTX(ft types.FieldType) any {
  25. switch ft {
  26. case types.FieldType_Uint8:
  27. return uint8(0)
  28. case types.FieldType_Uint16:
  29. return int16(0)
  30. case types.FieldType_Uint32:
  31. return int32(0)
  32. case types.FieldType_Uint64:
  33. return uint64(0)
  34. case types.FieldType_DateTime:
  35. // 引擎 DateTime 列的零值: 零毫秒 (Unix 0)。非空列上的"没有时间"就落它。
  36. return uint64(0)
  37. case types.FieldType_Int8:
  38. return int8(0)
  39. case types.FieldType_Int16:
  40. return int16(0)
  41. case types.FieldType_Int32:
  42. return int32(0)
  43. case types.FieldType_Int64:
  44. return int64(0)
  45. case types.FieldType_Float64:
  46. return float64(0)
  47. case types.FieldType_Float32:
  48. return float32(0)
  49. case types.FieldType_Bool:
  50. return false
  51. case types.FieldType_Decimal:
  52. return types.Decimal{}
  53. case types.FieldType_UUID:
  54. return types.UUID{}
  55. case types.FieldType_Year:
  56. return int16(0)
  57. case types.FieldType_Int128:
  58. return new(big.Int)
  59. default:
  60. return []byte{}
  61. }
  62. }
  63. // coerceForVRTX 把来源值规范成 core.writeFieldValue 认得的确切 Go 类型。
  64. //
  65. // 必须逐一对齐 (uint8→uint8, uint16→int16, uint32→int32, uint64→uint64,
  66. // Int8→int8, Int16→int16, Int32→int32, Int64→int64,
  67. // float64→float64, 定长/变长→[]byte): writeFieldValue 内部是类型断言,
  68. // 给错类型不是"写进去一个错值", 而是直接 panic。
  69. //
  70. // param 是列级参数 (Decimal 的 scale): 定点列必须按它对齐小数位, 否则同一列里
  71. // 混进另一种 scale 的行, B+ 树的字节序比较会静默出错。
  72. //
  73. // nullable 决定 nil 怎么落地: 可空列写 NULL (原样把 nil 交给 core, 由它置 NULL
  74. // 位图的位), 不可空列才退化成零值。
  75. func coerceForVRTX(ft types.FieldType, param byte, v any, col string, strict bool, nullable bool) (any, error) {
  76. if v == nil {
  77. // SQL 侧的 NULL 读出来就是 nil (见 SQL.Scan 的 *any)。可空列必须把它写成
  78. // NULL —— 落成零值等于"把没有值"变成"值是 0 / 空串", 而且不可逆: 导回去
  79. // 分不出这行的 0 原本是 NULL 还是真 0。
  80. if nullable {
  81. return nil, nil
  82. }
  83. return zeroForVRTX(ft), nil
  84. }
  85. switch ft {
  86. case types.FieldType_Uint8:
  87. n, err := asInt64(v)
  88. if err != nil {
  89. return nil, fmt.Errorf("列 %s: %w", col, err)
  90. }
  91. if n < 0 || n > math.MaxUint8 {
  92. if strict {
  93. return nil, fmt.Errorf("列 %s (uint8): 值 %d 超出 0..255", col, n)
  94. }
  95. n &= 0xFF
  96. }
  97. return uint8(n), nil
  98. case types.FieldType_Uint16:
  99. n, err := asInt64(v)
  100. if err != nil {
  101. return nil, fmt.Errorf("列 %s: %w", col, err)
  102. }
  103. // 无符号档盘上存的是 16 位位模式, 读取载体是 int16 (见 codec.DecodeValue 与
  104. // writeFieldValue 里的 value.(int16))。所以"装得下"的判据是这 16 位放得下,
  105. // 而不是"int16 的正数区放得下":
  106. // 0..65535 无符号来源的原样值 (MySQL SMALLINT UNSIGNED 的 33400 就在这一档);
  107. // -32768..-1 回读 / SQLite INTEGER 给出的负位模式 (0x8278 读出来是 -32136)。
  108. // 按 int16 区间判会把 32768..65535 这半个无符号区误判成越界: 位模式明明装得下,
  109. // 报错拦掉的却是完全合法的数据。
  110. if strict && (n < math.MinInt16 || n > math.MaxUint16) {
  111. return nil, fmt.Errorf("列 %s (uint16): 值 %d 超出 16 位位模式可表达的 -32768..65535", col, n)
  112. }
  113. return int16(uint16(n)), nil
  114. case types.FieldType_Uint32:
  115. n, err := asInt64(v)
  116. if err != nil {
  117. return nil, fmt.Errorf("列 %s: %w", col, err)
  118. }
  119. // 同 uint16: 32 位位模式, 载体 int32。0..4294967295 是无符号来源的原样值,
  120. // -2147483648..-1 是回读给出的负位模式。
  121. if strict && (n < math.MinInt32 || n > math.MaxUint32) {
  122. return nil, fmt.Errorf("列 %s (uint32): 值 %d 超出 32 位位模式可表达的 -2147483648..4294967295", col, n)
  123. }
  124. return int32(uint32(n)), nil
  125. case types.FieldType_DateTime:
  126. // 引擎专用时间列 (Unix 毫秒): time.Time 直传、文本解析、整数按毫秒 ——
  127. // 换算约定见 coerceDateTimeForVRTX。
  128. return coerceDateTimeForVRTX(v, col, nullable)
  129. case types.FieldType_Uint64:
  130. // 有符号来源按"64 位位模式"原样搬: int64(-1) → uint64(0xFFFF...)。
  131. // 导出回 SQL 时再用 int64(u) 写, 于是 MySQL 的 -1 ↔ VRTX 的 0xFFFF...
  132. // 往返无损 —— VRTX 的 uint64 本身不分正负, 同一个位模式 Go 侧既可当
  133. // uint64 也可当 int64 读, 正是 ORM 让 int64 字段能存负数的原理。
  134. u, err := asUint64(v)
  135. if err != nil {
  136. return nil, fmt.Errorf("列 %s: %w", col, err)
  137. }
  138. return u, nil
  139. case types.FieldType_Int8:
  140. n, err := asInt64(v)
  141. if err != nil {
  142. return nil, fmt.Errorf("列 %s: %w", col, err)
  143. }
  144. if strict && (n < math.MinInt8 || n > math.MaxInt8) {
  145. return nil, fmt.Errorf("列 %s (int8): 值 %d 超出 -128..127", col, n)
  146. }
  147. return int8(n), nil
  148. case types.FieldType_Int16:
  149. n, err := asInt64(v)
  150. if err != nil {
  151. return nil, fmt.Errorf("列 %s: %w", col, err)
  152. }
  153. if strict && (n < math.MinInt16 || n > math.MaxInt16) {
  154. return nil, fmt.Errorf("列 %s (int16): 值 %d 超出 int16", col, n)
  155. }
  156. return int16(n), nil
  157. case types.FieldType_Int32:
  158. n, err := asInt64(v)
  159. if err != nil {
  160. return nil, fmt.Errorf("列 %s: %w", col, err)
  161. }
  162. if strict && (n < math.MinInt32 || n > math.MaxInt32) {
  163. return nil, fmt.Errorf("列 %s (int32): 值 %d 超出 int32", col, n)
  164. }
  165. return int32(n), nil
  166. case types.FieldType_Int64:
  167. n, err := asInt64(v)
  168. if err != nil {
  169. return nil, fmt.Errorf("列 %s: %w", col, err)
  170. }
  171. return n, nil
  172. case types.FieldType_Float64:
  173. f, err := asFloat64(v)
  174. if err != nil {
  175. return nil, fmt.Errorf("列 %s: %w", col, err)
  176. }
  177. return f, nil
  178. // ===== 扩展类型: 一律归到 core 的标准解码载体 =====
  179. // (Bool→bool, Float32→float32, Decimal→types.Decimal, UUID→types.UUID,
  180. // Year→int16, Int128→*big.Int) —— 与 core/types.DecodeValue 反着走一遍。
  181. case types.FieldType_Float32:
  182. return coerceFloat32(v, col, strict)
  183. case types.FieldType_Bool:
  184. n, err := asInt64(v)
  185. if err != nil {
  186. return nil, fmt.Errorf("列 %s: %w", col, err)
  187. }
  188. if strict && n != 0 && n != 1 {
  189. return nil, fmt.Errorf("列 %s (bool): 值 %d 不是 0/1 —— "+
  190. "bool 只认 0/1, 否则等值与索引会给出多套答案", col, n)
  191. }
  192. return n != 0, nil
  193. case types.FieldType_Decimal:
  194. d, err := types.DecimalFromValue(v)
  195. if err != nil {
  196. return nil, fmt.Errorf("列 %s: %w", col, err)
  197. }
  198. if uint16(param) != d.Scale() {
  199. d, err = d.Rescale(uint16(param))
  200. if err != nil {
  201. return nil, fmt.Errorf("列 %s: %w", col, err)
  202. }
  203. }
  204. return d, nil
  205. case types.FieldType_UUID:
  206. u, err := asUUID(v)
  207. if err != nil {
  208. return nil, fmt.Errorf("列 %s: %w", col, err)
  209. }
  210. return u, nil
  211. case types.FieldType_Year:
  212. n, err := asInt64(v)
  213. if err != nil {
  214. return nil, fmt.Errorf("列 %s: %w", col, err)
  215. }
  216. if strict && (n < 0 || n > math.MaxInt16) {
  217. return nil, fmt.Errorf("列 %s (year): 值 %d 超出 0..32767", col, n)
  218. }
  219. return int16(n), nil
  220. case types.FieldType_Int128:
  221. bi, err := asBigInt(v)
  222. if err != nil {
  223. return nil, fmt.Errorf("列 %s: %w", col, err)
  224. }
  225. return bi, nil
  226. case types.FieldType_Blob:
  227. // Blob 列: 中间表示里给的是【内容本身】(VRTX.Scan 已把行内的 8B 引用解成字节),
  228. // 这里原样交给 core —— 行内那 8 字节链引用由 core 的 Table.blobFieldRef 落链页得到
  229. // (见 types/blob.go: "把 io.Reader / []byte 写进 FieldType_Blob 列, 引擎自动走链页")。
  230. //
  231. // 刻意不走下面 default 的定长容量闸门: types.FieldSize(Blob) 只有 8 —— 那是行内引用的
  232. // 宽度, 不是容量。拿它当上限会把任何超过 8 字节的 blob 判成"超过定长列容量"。单值上限
  233. // 由链长决定 (约 4GB), 超了 core 会返 ErrBlobTooLarge。
  234. // 也不补零 / 不裁零: blob 是二进制, 0x00 是内容的一部分。
  235. b, err := asBytes(v)
  236. if err != nil {
  237. return nil, fmt.Errorf("列 %s: %w", col, err)
  238. }
  239. return b, nil
  240. default:
  241. // 定长字符串 / 变长列
  242. b, err := asBytes(v)
  243. if err != nil {
  244. return nil, fmt.Errorf("列 %s: %w", col, err)
  245. }
  246. if !types.IsDynamic(ft) {
  247. // 容量取列的真实宽度 (宽度码), 不是类型默认档: 对声明 1024 字节的列按 256
  248. // 判断会把合法值判成超长。
  249. size := int(types.FieldSize(ft, param))
  250. if len(b) > size {
  251. if strict {
  252. return nil, fmt.Errorf("列 %s (%s): 值 %d 字节超过定长列容量 %d 字节, "+
  253. "导入会截断 —— 请把该列改成 dynamic 或放宽源值", col, TypeName(ft), len(b), size)
  254. }
  255. b = b[:size]
  256. }
  257. }
  258. return b, nil
  259. }
  260. }
  261. // coerceFloat32 收编到单精度。来源已是 float32 时原样; float64 只在 strict 下要求
  262. // 无损 —— 让"迁移会丢精度"这件事在严格档下当场暴露, 而不是事后对不上数。
  263. func coerceFloat32(v any, col string, strict bool) (float32, error) {
  264. if f32, ok := v.(float32); ok {
  265. return f32, nil
  266. }
  267. f, err := asFloat64(v)
  268. if err != nil {
  269. return 0, fmt.Errorf("列 %s: %w", col, err)
  270. }
  271. fb := float32(f)
  272. if strict && float64(fb) != f {
  273. return 0, fmt.Errorf("列 %s (float32): 值 %v 用 float32 表示会损失精度", col, f)
  274. }
  275. return fb, nil
  276. }
  277. // asUUID 把来源值收成 UUID: UUID 值 / 16 字节 / 文本 (带或不带连字符)。
  278. func asUUID(v any) (types.UUID, error) {
  279. var u types.UUID
  280. switch x := v.(type) {
  281. case types.UUID:
  282. return x, nil
  283. case []byte:
  284. if len(x) != types.UUIDSize {
  285. return u, fmt.Errorf("UUID 需要 %d 字节, 得到 %d 字节", types.UUIDSize, len(x))
  286. }
  287. copy(u[:], x)
  288. return u, nil
  289. case string:
  290. return types.ParseUUID(x)
  291. default:
  292. return u, fmt.Errorf("无法把 %T 当 UUID", v)
  293. }
  294. }
  295. // asBigInt 把来源值收成 *big.Int (128 位整数的标准载体)。
  296. func asBigInt(v any) (*big.Int, error) {
  297. switch x := v.(type) {
  298. case *big.Int:
  299. if x == nil {
  300. return new(big.Int), nil
  301. }
  302. return new(big.Int).Set(x), nil
  303. case big.Int:
  304. return new(big.Int).Set(&x), nil
  305. case types.Decimal:
  306. if x.Scale() != 0 {
  307. return nil, fmt.Errorf("int128 列不能接收带小数的值 %s", x.String())
  308. }
  309. return x.Coefficient(), nil
  310. case []byte:
  311. return parseInt128(string(x))
  312. case string:
  313. return parseInt128(x)
  314. case uint64:
  315. return new(big.Int).SetUint64(x), nil
  316. case int64:
  317. return big.NewInt(x), nil
  318. default:
  319. n, err := asInt64(v)
  320. if err != nil {
  321. return nil, err
  322. }
  323. return big.NewInt(n), nil
  324. }
  325. }
  326. // parseInt128 解析十进制/十六进制文本的 128 位整数 (base 0 支持 0x 前缀)。
  327. func parseInt128(s string) (*big.Int, error) {
  328. s = strings.TrimSpace(s)
  329. bi, ok := new(big.Int).SetString(s, 0)
  330. if !ok {
  331. return nil, fmt.Errorf("无法把 %q 当 128 位整数", s)
  332. }
  333. return bi, nil
  334. }
  335. // coerceForSQL 把来源值规范成 database/sql 能绑定的类型。
  336. //
  337. // 三个约定:
  338. // - uint64 必须按方言选 Go 类型 (mysql 参数), 因为两个方言的声明本就不同,
  339. // 绑定类型得跟着走 —— 见下面 FieldType_Uint64 分支。
  340. // - 字节类列 (定长串 / 变长) 若是合法 UTF-8 就绑成 string, 否则绑 []byte。
  341. // 前者让 SQLite 把它存成 TEXT (BLOB 亲和性的列不会自动转换),
  342. // 后者保证二进制列不被改坏。定长列导出时去掉尾部补零。
  343. // - 时间列一律还原成时间文本, 不写整数: FieldType_DateTime 列按【毫秒】解释,
  344. // 存量 Uint64+TimeKind 旧表按【纳秒】解释 (两条路的判别就是列的 FieldType)。
  345. //
  346. // mysql 来自目标方言 (Sink), 与 sqlTypeName 的第二个参数同源。
  347. func coerceForSQL(c Column, v any, mysql bool) (any, error) {
  348. if v == nil {
  349. // 可空列: 原样写 NULL。以前所有列都不可空, 这里一路把 nil 落成零值 ——
  350. // 在可空列上那是【静默改数据】: VRTX 的 NULL 到了 SQL 变成 0 / 空串 /
  351. // 1970-01-01, 而且不可逆, 再导回来分不出它原本是 NULL。
  352. if c.Nullable {
  353. return nil, nil
  354. }
  355. if c.IsDynamic() {
  356. return []byte{}, nil
  357. }
  358. if c.IsTime() {
  359. // 时间列的零值给 Unix 0, 不是数字 0 —— 后者塞进 DATETIME 列不是
  360. // "1970" 而是让 MySQL 报错 (严格模式) 或存成 0000-00-00。
  361. return formatSQLTime(0, c.TimeKind), nil
  362. }
  363. return zeroForSQL(c), nil
  364. }
  365. switch c.Type {
  366. case types.FieldType_Uint8:
  367. n, err := asInt64(v)
  368. if err != nil {
  369. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  370. }
  371. return int64(uint8(n)), nil
  372. case types.FieldType_Uint16:
  373. n, err := asInt64(v)
  374. if err != nil {
  375. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  376. }
  377. // 出库一侧给的是【无符号数值】, 不是 int64 原样 —— 与 uint8 那条同形。
  378. // 位模式 0x8278 读出来是 -32136, 原样绑 int64 送进 MySQL 的 SMALLINT UNSIGNED
  379. // (sqlTypeName 就是这么声明的) 会被判成 Out of range: 严格模式直接报错, 非严格
  380. // 模式截成 0 —— 那是静默改数据。取回无符号值 33400 才是这一列真实存的东西。
  381. // SQLite 侧声明是 INTEGER, 没有无符号概念, 但 0..65535 它整个装得下, 同样给
  382. // 无符号值即可 (只有 uint64 才必须按位模式降级, 因为 SQLite 的 INTEGER 装不下
  383. // 2^63 以上)。
  384. return int64(uint16(n)), nil
  385. case types.FieldType_Uint32:
  386. n, err := asInt64(v)
  387. if err != nil {
  388. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  389. }
  390. // 同 uint16: 给无符号数值, 别把 4294967295 当成 -1 送给 MySQL 的 INT UNSIGNED。
  391. return int64(uint32(n)), nil
  392. case types.FieldType_DateTime:
  393. // 时间列: 盘上 (引擎 DecodeValue) 是 uint64 毫秒, 回 SQL 得还原成时间文本 ——
  394. // 否则 MySQL 的 DATETIME 列会收到一个 1.7e12 的整数 (驱动不会替我们转)。
  395. // time.Time 先按绝对时间取毫秒, 其余形态按数值 (毫秒) 收。
  396. var ms int64
  397. switch x := v.(type) {
  398. case time.Time:
  399. ms = x.UnixMilli()
  400. default:
  401. u, err := asUint64(v)
  402. if err != nil {
  403. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  404. }
  405. ms = int64(u)
  406. }
  407. return formatSQLTime(ms, c.TimeKind), nil
  408. case types.FieldType_Uint64:
  409. u, err := asUint64(v)
  410. if err != nil {
  411. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  412. }
  413. if c.IsTime() {
  414. // 存量旧表: 这列是当年迁成 uint64 存 Unix【纳秒】的时间列 (meta 里带
  415. // TimeKind 标记), 回 SQL 仍按纳秒还原成时间文本。新表是 FieldType_DateTime,
  416. // 走上面那条毫秒路 —— 两条路并存, 判别依据就是列的 FieldType。
  417. return formatSQLTimeNS(int64(u), c.TimeKind), nil
  418. }
  419. // 绑定的 Go 类型必须跟目标列的声明一致, 而两个方言的声明不同:
  420. // - MySQL: sqlTypeName 声明成 BIGINT UNSIGNED (uint64 是无符号档),
  421. // 所以绑 uint64 —— 整个 2^64 区间都写得进去。以前一律绑 int64(u),
  422. // 高位为 1 的值 (包括 VRTX 从 int64 负值搬过来的位模式) 在严格模式下
  423. // 会被 MySQL 判成 "Out of range value" 直接报错, 非严格模式则被静默改写。
  424. // - SQLite: 只有有符号的 INTEGER, 绑 int64 —— 位模式原样存进 64 位整数,
  425. // 读回来还是同一个 8 字节。
  426. // 两条路共同的保证是【位模式不变】, 这正是 VRTX uint64 的语义 (不分正负)。
  427. if mysql {
  428. return u, nil
  429. }
  430. return int64(u), nil
  431. case types.FieldType_Int8, types.FieldType_Int16, types.FieldType_Int32, types.FieldType_Int64:
  432. // 有符号档存的就是数值本身 (盘上的保序编码由 core 负责), 直接绑 int64
  433. n, err := asInt64(v)
  434. if err != nil {
  435. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  436. }
  437. return n, nil
  438. case types.FieldType_Float64:
  439. f, err := asFloat64(v)
  440. if err != nil {
  441. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  442. }
  443. return f, nil
  444. // ===== 扩展类型 =====
  445. // Decimal / Int128 一律绑文本: 目标列分别是 DECIMAL(38,s)/VARCHAR(40) (MySQL) 与
  446. // TEXT (SQLite), 文本既能精确表达 128 位整数, 也不会被驱动转成浮点。
  447. // UUID 绑 16 字节 (BINARY(16)/BLOB)。Bool 绑 bool, Year 绑 int64。
  448. case types.FieldType_Float32:
  449. f, err := asFloat64(v)
  450. if err != nil {
  451. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  452. }
  453. return float32(f), nil
  454. case types.FieldType_Bool:
  455. n, err := asInt64(v)
  456. if err != nil {
  457. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  458. }
  459. return n != 0, nil
  460. case types.FieldType_Decimal:
  461. d, err := types.DecimalFromValue(v)
  462. if err != nil {
  463. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  464. }
  465. if uint16(c.Param) != d.Scale() {
  466. d, err = d.Rescale(uint16(c.Param))
  467. if err != nil {
  468. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  469. }
  470. }
  471. return d.String(), nil
  472. case types.FieldType_UUID:
  473. u, err := asUUID(v)
  474. if err != nil {
  475. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  476. }
  477. return append([]byte(nil), u[:]...), nil
  478. case types.FieldType_Year:
  479. n, err := asInt64(v)
  480. if err != nil {
  481. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  482. }
  483. return n, nil
  484. case types.FieldType_Int128:
  485. bi, err := asBigInt(v)
  486. if err != nil {
  487. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  488. }
  489. return bi.String(), nil
  490. case types.FieldType_Blob:
  491. // Blob 列: 中间表示里已是内容本身 (VRTX.Scan 解过引用), 原样绑给 SQL。
  492. //
  493. // 不做 UTF-8 判定也不去尾零 —— 那是"字符串列"的规矩 (见 default 分支)。blob 是二进制,
  494. // 把末尾的 0x00 当补位裁掉就是静默改数据; 绑成 string 还会让 SQLite 按 TEXT 亲和性存,
  495. // 真正的二进制或非 UTF-8 内容会被写坏。
  496. b, err := asBytes(v)
  497. if err != nil {
  498. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  499. }
  500. return b, nil
  501. default:
  502. b, err := asBytes(v)
  503. if err != nil {
  504. return nil, fmt.Errorf("列 %s: %w", c.Name, err)
  505. }
  506. if !c.IsDynamic() {
  507. b = trimTrailingNUL(b)
  508. }
  509. if utf8.Valid(b) {
  510. return string(b), nil
  511. }
  512. return b, nil
  513. }
  514. }
  515. func zeroForSQL(c Column) any {
  516. switch c.Type {
  517. case types.FieldType_Float64:
  518. return float64(0)
  519. case types.FieldType_Float32:
  520. return float32(0)
  521. case types.FieldType_Bool:
  522. return false
  523. case types.FieldType_Decimal:
  524. // 零值也要按列 scale 输出定点文本 ("0.00"), 否则 DECIMAL 列会收到 "0"。
  525. return types.NewDecimalInt64(0, uint16(c.Param)).String()
  526. case types.FieldType_UUID:
  527. return make([]byte, types.UUIDSize)
  528. case types.FieldType_Year:
  529. return int64(0)
  530. case types.FieldType_Int128:
  531. return "0"
  532. case types.FieldType_Blob:
  533. // blob 列的零值是"空内容"(长度 0), 不是行内那 8 字节链引用 —— core 收到空 []byte
  534. // 会给出空引用 (不分配链页)。
  535. return []byte{}
  536. case types.FieldType_Uint8, types.FieldType_Uint16, types.FieldType_Uint32, types.FieldType_Uint64,
  537. types.FieldType_Int8, types.FieldType_Int16, types.FieldType_Int32, types.FieldType_Int64:
  538. return int64(0)
  539. case types.FieldType_DateTime:
  540. // 与 coerceForSQL 的 nil 分支同一约定: 零时间给 Unix 0 (1970-01-01), 不给数字 0。
  541. return formatSQLTime(0, "datetime")
  542. default:
  543. return ""
  544. }
  545. }
  546. // trimTrailingNUL 去掉定长列右补的 \x00。
  547. // VRTX 定长字符串是"右补零到固定宽度", SQL 侧不该带上这些补位 (否则 varchar
  548. // 里会多出一堆看不见的字符, 也没法用等值条件查)。
  549. func trimTrailingNUL(b []byte) []byte {
  550. end := len(b)
  551. for end > 0 && b[end-1] == 0 {
  552. end--
  553. }
  554. return b[:end]
  555. }
  556. // ============================ 底层取值 ============================
  557. func asInt64(v any) (int64, error) {
  558. switch x := v.(type) {
  559. case int64:
  560. return x, nil
  561. case int:
  562. return int64(x), nil
  563. case int32:
  564. return int64(x), nil
  565. case int16:
  566. return int64(x), nil
  567. case int8:
  568. return int64(x), nil
  569. case uint64:
  570. if x > math.MaxInt64 {
  571. return 0, fmt.Errorf("值 %d 超出 int64", x)
  572. }
  573. return int64(x), nil
  574. case uint32:
  575. return int64(x), nil
  576. case uint16:
  577. return int64(x), nil
  578. case uint8:
  579. return int64(x), nil
  580. case uint:
  581. return int64(x), nil
  582. case float64:
  583. return roundFloatToInt(x)
  584. case float32:
  585. return roundFloatToInt(float64(x))
  586. case bool:
  587. if x {
  588. return 1, nil
  589. }
  590. return 0, nil
  591. case []byte:
  592. return parseIntOrTime(string(x))
  593. case string:
  594. return parseIntOrTime(x)
  595. case time.Time:
  596. return x.UnixNano(), nil
  597. default:
  598. return 0, fmt.Errorf("无法把 %T 当整数", v)
  599. }
  600. }
  601. func roundFloatToInt(f float64) (int64, error) {
  602. if math.IsNaN(f) || math.IsInf(f, 0) {
  603. return 0, fmt.Errorf("值 %v 不是有限数", f)
  604. }
  605. // 上界必须是 >= 而不是 >: math.MaxInt64 (2^63-1) 在 float64 里存不下, 会被舍入
  606. // 到 2^63 —— 于是 f == 2^63 时 "f > MaxInt64" 是假, 放过去之后 int64(f) 越界,
  607. // Go 对越界的 float→int 转换是未定义结果 (amd64 上给 MinInt64), 静默存成反数。
  608. // 下界不受这个影响: MinInt64 = -2^63 在 float64 里可以精确表示。
  609. if f >= math.MaxInt64 || f < math.MinInt64 {
  610. return 0, fmt.Errorf("值 %v 超出 int64", f)
  611. }
  612. return int64(math.Round(f)), nil
  613. }
  614. // asUint64 与 asInt64 的差别只有一处: 有符号的负值不报错, 而是按位模式搬进 uint64。
  615. func asUint64(v any) (uint64, error) {
  616. switch x := v.(type) {
  617. case uint64:
  618. return x, nil
  619. case uint32:
  620. return uint64(x), nil
  621. case uint16:
  622. return uint64(x), nil
  623. case uint8:
  624. return uint64(x), nil
  625. case uint:
  626. return uint64(x), nil
  627. case int64:
  628. return uint64(x), nil // 位模式, 负数 → 高位全 1
  629. case int:
  630. return uint64(x), nil
  631. case int32:
  632. return uint64(x), nil
  633. case int16:
  634. return uint64(x), nil
  635. case int8:
  636. return uint64(x), nil
  637. case float64:
  638. n, err := roundFloatToInt(x)
  639. if err != nil {
  640. return 0, err
  641. }
  642. return uint64(n), nil
  643. case float32:
  644. n, err := roundFloatToInt(float64(x))
  645. if err != nil {
  646. return 0, err
  647. }
  648. return uint64(n), nil
  649. case bool:
  650. if x {
  651. return 1, nil
  652. }
  653. return 0, nil
  654. case []byte:
  655. return parseUintOrTime(string(x))
  656. case string:
  657. return parseUintOrTime(x)
  658. case time.Time:
  659. return uint64(x.UnixNano()), nil
  660. default:
  661. return 0, fmt.Errorf("无法把 %T 当整数", v)
  662. }
  663. }
  664. // ============================ 时间列 ============================
  665. //
  666. // 时间列在 VRTX 侧有两种盘面, 读路径按【列的 FieldType】分岔:
  667. //
  668. // - FieldType_DateTime (新): 引擎专用时间类型, 行内 8 字节 Unix【毫秒】;
  669. // - FieldType_Uint64 + TimeKind 标记 (旧): 更早的实现把时间列迁成 uint64 存
  670. // Unix【纳秒】, 存量表的数据就是这个盘面 —— 判别靠 TimeKind (新表是 DateTime,
  671. // 旧表才会出现 Uint64 列带着它)。
  672. //
  673. // SQL 时间列迁进来要换算成目标盘面, 迁出去要换算回时间文本 —— 这一节就是这两个方向。
  674. // sqlTimeLayouts SQL 时间文本的常见写法。MySQL 的 DATETIME / TIMESTAMP 打出来
  675. // 就是第一个; SQLite 存的是 TEXT, 可能带 T 和时区; DATE 只有日期部分。
  676. var sqlTimeLayouts = []string{
  677. "2006-01-02 15:04:05.999999999",
  678. "2006-01-02 15:04:05",
  679. "2006-01-02T15:04:05.999999999Z07:00",
  680. "2006-01-02T15:04:05",
  681. "2006-01-02",
  682. }
  683. // parseSQLTime 把 SQL 时间文本解析成 time.Time。
  684. //
  685. // 一律按 UTC 解释 (除非文本自带偏移): MySQL 的 DATETIME 本身不带时区, 若用本地
  686. // 时区去解释, 同一份数据在不同机器上会迁出不同的时间戳 —— 迁移工具不该引入
  687. // 这种漂移。往返一致性优先于"看着像本地时间"。
  688. func parseSQLTime(s string) (time.Time, bool) {
  689. for _, layout := range sqlTimeLayouts {
  690. if tm, err := time.ParseInLocation(layout, s, time.UTC); err == nil {
  691. return tm, true
  692. }
  693. }
  694. return time.Time{}, false
  695. }
  696. // parseIntOrTime 先按整数读, 读不动再按时间文本读 (时间文本 → Unix 纳秒)。
  697. //
  698. // 这是【旧盘面】的入口: 时间列映射成 uint64 后 (存量 Uint64+TimeKind 表), 值可能
  699. // 是 time.Time (MySQL 驱动开了 parseTime), 也可能是文本 (parseTime=false 的默认档,
  700. // 以及 SQLite 的 DATETIME 列) —— 后者必须在这里换算, 否则写进 uint64 列时会被
  701. // 当成"不是数字"直接报错。新的 FieldType_DateTime 列不走这里 (见 coerceDateTimeForVRTX)。
  702. func parseIntOrTime(s string) (int64, error) {
  703. s = strings.TrimSpace(s)
  704. if n, err := strconv.ParseInt(s, 10, 64); err == nil {
  705. return n, nil
  706. }
  707. if tm, ok := parseSQLTime(s); ok {
  708. return tm.UnixNano(), nil
  709. }
  710. return 0, fmt.Errorf("无法把 %q 当整数 (也不是能识别的时间文本)", s)
  711. }
  712. // parseUintOrTime 同 parseIntOrTime, 只是落在无符号上。
  713. func parseUintOrTime(s string) (uint64, error) {
  714. s = strings.TrimSpace(s)
  715. if n, err := strconv.ParseUint(s, 10, 64); err == nil {
  716. return n, nil
  717. }
  718. if tm, ok := parseSQLTime(s); ok {
  719. return uint64(tm.UnixNano()), nil
  720. }
  721. return 0, fmt.Errorf("无法把 %q 当整数 (也不是能识别的时间文本)", s)
  722. }
  723. // coerceDateTimeForVRTX SQL 侧时间值 → FieldType_DateTime 列的形态 (time.Time / 毫秒数)。
  724. //
  725. // 引擎 codec 认两种输入: time.Time (编码层自行截毫秒) 与毫秒数值。SQL 侧的驱动值
  726. // 是 time.Time / 文本 / 整数, 这里统一收编:
  727. //
  728. // - time.Time 原样直传 (与 gormvrtx / SQL driver 的写入路径同一约定);
  729. // - 文本按 UTC 解析成 time.Time (parseSQLTime, 不用本地时区 —— 同一批数据在
  730. // 不同时区的机器上必须迁出同一个时间戳);
  731. // - 整数按【毫秒】解释 —— DateTime 列的盘上单位就是毫秒 (Uint64+TimeKind 的
  732. // 旧库才是纳秒, 那条路在 coerceForSQL 的 Uint64 分支)。
  733. //
  734. // 零时间对齐 gormvrtx 语义: 可空列写 NULL (零 time.Time 与 nil 一样表示"没有时间"),
  735. // 非空列落零毫秒 (Unix 0)。
  736. func coerceDateTimeForVRTX(v any, col string, nullable bool) (any, error) {
  737. switch x := v.(type) {
  738. case time.Time:
  739. if x.IsZero() {
  740. if nullable {
  741. return nil, nil
  742. }
  743. return uint64(0), nil
  744. }
  745. return x, nil
  746. case string:
  747. return parseSQLTimeValue(x, col)
  748. case []byte:
  749. return parseSQLTimeValue(string(x), col)
  750. default:
  751. n, err := asInt64(v)
  752. if err != nil {
  753. return nil, fmt.Errorf("列 %s: %w", col, err)
  754. }
  755. if n < 0 {
  756. return nil, fmt.Errorf("列 %s: 时间戳不能为负: %d", col, n)
  757. }
  758. return uint64(n), nil // 已是毫秒
  759. }
  760. }
  761. // parseSQLTimeValue 文本 → time.Time, 解析失败报出列名。
  762. func parseSQLTimeValue(s, col string) (any, error) {
  763. tm, ok := parseSQLTime(strings.TrimSpace(s))
  764. if !ok {
  765. return nil, fmt.Errorf("列 %s: 无法把 %q 当时间文本", col, s)
  766. }
  767. return tm, nil
  768. }
  769. // formatSQLTime Unix 毫秒 → SQL 时间文本 (FieldType_DateTime 列的读路径)。
  770. //
  771. // DATE 只留日期; DATETIME / TIMESTAMP 留到毫秒 —— 这正是 DateTime 列的全部精度,
  772. // 转文本无损。
  773. func formatSQLTime(ms int64, kind string) string {
  774. tm := time.UnixMilli(ms).UTC()
  775. if kind == "date" {
  776. return tm.Format("2006-01-02")
  777. }
  778. if ms%1000 == 0 {
  779. return tm.Format("2006-01-02 15:04:05") // 整秒别拖一串 .000
  780. }
  781. return tm.Format("2006-01-02 15:04:05.000")
  782. }
  783. // formatSQLTimeNS Unix 纳秒 → SQL 时间文本 (存量 Uint64+TimeKind 旧表的读路径)。
  784. //
  785. // 只能写到微秒 (MySQL DATETIME(6) 的上限) —— 纳秒进、微秒出是旧盘面自身的精度
  786. // 上限, 不是工具丢的。新表走 formatSQLTime, 毫秒无损。
  787. func formatSQLTimeNS(ns int64, kind string) string {
  788. tm := time.Unix(0, ns).UTC()
  789. if kind == "date" {
  790. return tm.Format("2006-01-02")
  791. }
  792. if ns%int64(time.Second) == 0 {
  793. return tm.Format("2006-01-02 15:04:05") // 整秒别拖一串 .000000
  794. }
  795. return tm.Format("2006-01-02 15:04:05.000000")
  796. }
  797. func asFloat64(v any) (float64, error) {
  798. switch x := v.(type) {
  799. case float64:
  800. return x, nil
  801. case float32:
  802. return float64(x), nil
  803. case int64:
  804. return float64(x), nil
  805. case int:
  806. return float64(x), nil
  807. case int32:
  808. return float64(x), nil
  809. case int16:
  810. return float64(x), nil
  811. case int8:
  812. return float64(x), nil
  813. case uint64:
  814. return float64(x), nil
  815. case uint32:
  816. return float64(x), nil
  817. case uint16:
  818. return float64(x), nil
  819. case uint8:
  820. return float64(x), nil
  821. case []byte:
  822. f, err := strconv.ParseFloat(strings.TrimSpace(string(x)), 64)
  823. if err != nil {
  824. return 0, fmt.Errorf("无法把 %q 当浮点", x)
  825. }
  826. return f, nil
  827. case string:
  828. f, err := strconv.ParseFloat(strings.TrimSpace(x), 64)
  829. if err != nil {
  830. return 0, fmt.Errorf("无法把 %q 当浮点", x)
  831. }
  832. return f, nil
  833. default:
  834. return 0, fmt.Errorf("无法把 %T 当浮点", v)
  835. }
  836. }
  837. func asBytes(v any) ([]byte, error) {
  838. switch x := v.(type) {
  839. case []byte:
  840. return x, nil
  841. case string:
  842. return []byte(x), nil
  843. case nil:
  844. return []byte{}, nil
  845. case time.Time:
  846. return []byte(x.Format("2006-01-02 15:04:05.999999999")), nil
  847. case bool:
  848. if x {
  849. return []byte{1}, nil
  850. }
  851. return []byte{0}, nil
  852. case int64:
  853. return []byte(strconv.FormatInt(x, 10)), nil
  854. case int:
  855. return []byte(strconv.Itoa(x)), nil
  856. case int32:
  857. return []byte(strconv.FormatInt(int64(x), 10)), nil
  858. case int16:
  859. return []byte(strconv.FormatInt(int64(x), 10)), nil
  860. case int8:
  861. return []byte(strconv.FormatInt(int64(x), 10)), nil
  862. case uint64:
  863. return []byte(strconv.FormatUint(x, 10)), nil
  864. case uint32:
  865. return []byte(strconv.FormatUint(uint64(x), 10)), nil
  866. case uint16:
  867. return []byte(strconv.FormatUint(uint64(x), 10)), nil
  868. case uint8:
  869. return []byte(strconv.FormatUint(uint64(x), 10)), nil
  870. case float64:
  871. return []byte(strconv.FormatFloat(x, 'g', -1, 64)), nil
  872. case float32:
  873. return []byte(strconv.FormatFloat(float64(x), 'g', -1, 32)), nil
  874. default:
  875. return nil, fmt.Errorf("无法把 %T 当字节串", v)
  876. }
  877. }