sqltype.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. package migrate
  2. import (
  3. "TsDB2/DB/core/types"
  4. "fmt"
  5. "strconv"
  6. "strings"
  7. )
  8. // SQL ↔ VRTX 类型映射。两个方言共用一个"就近取档"规则:
  9. //
  10. // SQL VRTX
  11. // TINYINT Int8 (有符号, -128..127)
  12. // SMALLINT Int16
  13. // INT / MEDIUMINT Int32
  14. // BIGINT / INTEGER Int64 (SQLite 只有 INTEGER, 一律按 8 字节收)
  15. // 上面四者的 UNSIGNED 变体 1byte / 2byte / 4byte / 8byte (只有显式写了 UNSIGNED 才走无符号档)
  16. // FLOAT (MySQL) Float32 (4 字节; SQLite 的 FLOAT 是 8 字节, 见 inferSQLType)
  17. // DOUBLE / REAL Float64
  18. // DECIMAL(p,s) / NUMERIC(p,s) Decimal(s) (p,s ≤ 38 才能精确表示, 否则退回 Float64)
  19. // BOOLEAN / BOOL Bool
  20. // YEAR Year (2 字节年份, 0..32767)
  21. // UUID UUID (16 字节 RFC4122)
  22. // CHAR(n) / VARCHAR(n) 按字节容量取最近的宽度档 (16..2048, 超过则 dynamic)
  23. // TEXT / JSON dynamic (字节原样存, 不解释内容)
  24. // BLOB / BINARY blob (同为动态列, 但语义是"文件/二进制大对象")
  25. // DATE / DATETIME / TIMESTAMP 8byte (Unix 纳秒时间戳, 见 inferSQLType 的说明)
  26. //
  27. // 有符号与无符号落在不同档位不是洁癖: 无符号档 (1/2/4/8byte) 盘上存的就是补码原样,
  28. // 有符号档 (Int8..Int64) 存的是保序编码 —— 同一个值在两种档位下盘上字节不同, 混用会让
  29. // 索引区间扫描与 ORDER BY 出错。SQL 的整数默认有符号, 因此这里默认收编到 Int8..Int64。
  30. //
  31. // 这里的 n 是【字节容量】不是字符数 —— VRTX 的定长档按字节算容量, 而 SQL 的长度单位是
  32. // 字符, 两者只有在单字节字符集下才重合。MySQL 侧直接读 CHARACTER_OCTET_LENGTH
  33. // (varchar(50) 在 utf8mb4 下就是 200 字节 → 224 字节档); SQLite 的长度声明本就不强制,
  34. // 仍按声明里的 n 算 (见 inferSQLType)。按字符数取档会把列开窄: 一个 25 字的中文别名
  35. // 就有 75 字节, 塞进 72 字节档只能报错。
  36. //
  37. // 档位由 types.WidthCodeFor 算出 (向上取, 最坏多占 25%), 并写进列的 Flag 低 5 位:
  38. // 于是"这一列多宽"随 schema 落盘, 不再依赖类型枚举。varchar(1000) 这种旧设计里表达
  39. // 不出的列 (256 档会截断, dynamic 又不能建索引) 现在能原样落成 1024 字节的定长档。
  40. //
  41. // 反过来 (VRTX → SQL) 用 sqlTypeName, 定长档声明成 VARCHAR(该列的真实字节宽度)。因为
  42. // 目标方言按字符计数 (utf8mb4 下 VARCHAR(64) 实际能装 256 字节), 再读回来会宽一档
  43. // —— 也就是【不带 __vrtx_meta 时定长字符列会逐轮变宽】(数据不丢, 只是档位漂移)。
  44. // 要原样往返就带 meta, 而 meta 是默认开着的。
  45. //
  46. // 【声明容量的上界】落动态列 (超过 MaxInlineColumnWidth) 不等于"声明多大都行": 动态列的
  47. // 内容整体落在一个堆页里, 单值上限是 types.MaxStringSize。源库里 varchar(60000) 这种
  48. // 声明在这里【兑现不了】, 而丢掉声明值换成 dynamic 是"悄悄把容量改小"—— 所以读端会把
  49. // 它记进迁移报告 (见 capacityNote)。这条闸门只对"声明了长度的字符/二进制列"生效:
  50. // TEXT / BLOB 家族的长度是类型自身的上界而非用户声明, 不该按它报警。
  51. // bucketColumn 按字节容量取定长档位, 返回 (类型, 列级宽度码)。
  52. //
  53. // 宽度码写进 types.FieldInfo.Flag 低 5 位, 是这一列行内宽度的唯一依据 (见 types.FieldSize);
  54. // 返回的 FieldType 只决定回显名与"没有宽度码时"的默认宽度。
  55. //
  56. // 超过 types.MaxInlineColumnWidth (2048) 的列只能落变长档: 定长档的存在意义是"可索引、
  57. // 可定长比较", 变长列两者都没有 (引擎建索引时会明确拒绝)。
  58. func bucketColumn(size int) (types.FieldType, byte) {
  59. code := types.WidthCodeFor(size)
  60. if code == 0 {
  61. return types.FieldType_Dynamicbyte, 0
  62. }
  63. ft := types.FieldType_256byte
  64. switch {
  65. case size <= 16:
  66. ft = types.FieldType_16byte
  67. case size <= 32:
  68. ft = types.FieldType_32byte
  69. case size <= 64:
  70. ft = types.FieldType_64byte
  71. case size <= 128:
  72. ft = types.FieldType_128byte
  73. }
  74. return ft, code
  75. }
  76. // inferSQLTypeWithNote 与 inferSQLType 同源, 额外报告"这一列声明的容量兑现不了"。
  77. //
  78. // 为什么另立一个入口而不是给它加第三个返回值: 类型推断有二十来个分支, 每个分支都得写一个
  79. // 恒为空的第三个返回值, 噪声比信息多; 而"容量能不能兑现"只有读源结构 (拿得到声明原文) 时
  80. // 才关心, 于是把它挂在入口上, 由读端显式选用 —— 想要这句话的人用这个函数, 只关心类型的
  81. // 人继续用 inferSQLType。
  82. func inferSQLTypeWithNote(decl string, mysql bool) (types.FieldType, byte, string) {
  83. ft, param := inferSQLType(decl, mysql)
  84. return ft, param, capacityNote(decl)
  85. }
  86. // capacityNote 报告这一列的声明容量被引擎单值上限 (types.MaxStringSize) 封顶; 未超限返回 ""。
  87. //
  88. // 只对【声明了长度的字符 / 二进制列】报, 刻意不含 TEXT / BLOB 家族 —— 它们的 decl 里那个
  89. // 长度不是用户的声明: MySQL 的 information_schema 会把 "text" 报成
  90. // CHARACTER_OCTET_LENGTH = 65535 (mysqlColumnDecl 于是写成 "text(65535)"), 拿它去报
  91. // "声明兑现不了"是误报。那类列本来就没有容量声明, 引擎的单值上限是已知限制, 由写入侧
  92. // 兜底 (写入超限会明确拒绝并报出列名), 不在这里重复报。
  93. func capacityNote(decl string) string {
  94. base, args := splitTypeArgs(strings.ToUpper(strings.TrimSpace(decl)))
  95. switch base {
  96. case "CHAR", "CHARACTER", "VARCHAR", "NCHAR", "NVARCHAR", "VARCHAR2",
  97. "BINARY", "VARBINARY":
  98. default:
  99. return ""
  100. }
  101. if len(args) == 0 {
  102. return ""
  103. }
  104. // 错误文本复用 types 里那一份: 两个方向 (ORM 的 maxlen / 迁移的 SQL 长度) 的容量闸门
  105. // 是同一个约束, 提示语也该是同一句, 否则用户会以为是两回事。
  106. if err := types.CheckDeclaredStringSize(args[0]); err != nil {
  107. return err.Error()
  108. }
  109. return ""
  110. }
  111. // degradeNote 报告这一列的类型在 VRTX 端只能【降级收编】—— 与 capacityNote 是两回事:
  112. // - capacityNote: 类型不变, 只是上限比声明的小 (装不下那么多);
  113. // - degradeNote: 类型变了, 语义跟着变 (存的东西是另一回事)。
  114. //
  115. // 目前只有一种: DECIMAL(p,s) 超出定点列的表达范围时 inferSQLType 退回 float64。退回本身
  116. // 是对的 (总比假装精确好), 但它【有损】, 必须报出来 —— 用户以为迁过来的是定点数, 实际拿到
  117. // 的是浮点数: 0.1+0.2 不再等于 0.3, 大额值丢有效位, 聚合结果对不上账。
  118. //
  119. // 与容量报告同一条原则: 报出来只影响提示, 不改变类型 (类型由 inferSQLType 单独决定,
  120. // 两边必须逐字节一致, 否则"报了一句"就悄悄改了列的类型, 比不报更糟)。
  121. func degradeNote(decl string) string {
  122. base, args := splitTypeArgs(strings.ToUpper(strings.TrimSpace(decl)))
  123. if base != "DECIMAL" && base != "NUMERIC" {
  124. return ""
  125. }
  126. if len(args) == 0 {
  127. return ""
  128. }
  129. prec, scale := 38, 0
  130. if len(args) >= 1 {
  131. prec = args[0]
  132. }
  133. if len(args) >= 2 {
  134. scale = args[1]
  135. }
  136. // 与 inferSQLType 的判定条件必须一致 —— 这里说"降级了"而那边其实没降 (或反过来),
  137. // 报告就成了假消息。
  138. if prec <= types.MaxDecimalPrecision && scale >= 0 && scale <= types.MaxDecimalScale {
  139. return ""
  140. }
  141. // 指名是哪一条超了: 精度与小数位是两个上限 (38 / 31), 只报"超限"用户不知道该改哪个。
  142. which := fmt.Sprintf("精度 %d 超过上限 %d", prec, types.MaxDecimalPrecision)
  143. if scale > types.MaxDecimalScale {
  144. which = fmt.Sprintf("小数位 %d 超过上限 %d", scale, types.MaxDecimalScale)
  145. }
  146. return fmt.Sprintf("DECIMAL(%d,%d) 无法精确表达 (%s), 已退化成 float64: "+
  147. "需要精确比较/聚合的列会丢精度", prec, scale, which)
  148. }
  149. // inferSQLType 由 SQL 列类型 (含长度) 推断 VRTX 类型, 返回 (类型, 列参数)。
  150. // decl 可以是 "varchar(32)", "BIGINT UNSIGNED", "DECIMAL(18,2)" 这类原文。
  151. //
  152. // mysql 用于区分同名类型在不同方言下的宽度: MySQL 的 FLOAT 是 4 字节, 而 SQLite
  153. // 只有 REAL 亲和性 (8 字节), 声明成 FLOAT 也是双精度 —— 同一个 decl 不能按同一档收。
  154. func inferSQLType(decl string, mysql bool) (types.FieldType, byte) {
  155. u := strings.ToUpper(strings.TrimSpace(decl))
  156. base, args := splitTypeArgs(u)
  157. size := 0
  158. if len(args) > 0 {
  159. size = args[0]
  160. }
  161. // SQL 的整数默认是有符号的, 只有显式写了 UNSIGNED 才收编到无符号档。
  162. // 这里看原文 u 而不是 base: splitTypeAndSize 会把 "BIGINT UNSIGNED" 的后缀丢掉。
  163. signed := !strings.Contains(u, "UNSIGNED")
  164. switch base {
  165. case "TINYINT":
  166. if signed {
  167. return types.FieldType_Int8, 0
  168. }
  169. return types.FieldType_1byte, 0
  170. case "SMALLINT":
  171. if signed {
  172. return types.FieldType_Int16, 0
  173. }
  174. return types.FieldType_2byte, 0
  175. case "MEDIUMINT", "INT", "INTEGER", "INT2", "INT4", "INT8", "BIGINT", "SERIAL", "BIGSERIAL":
  176. // SQLite 的 INTEGER 能装整个 int64, 按 8 字节收最稳; MySQL 的 INT 就是 4 字节。
  177. if base == "INT" || base == "INT4" || base == "MEDIUMINT" {
  178. if signed {
  179. return types.FieldType_Int32, 0
  180. }
  181. return types.FieldType_4byte, 0
  182. }
  183. if signed {
  184. return types.FieldType_Int64, 0
  185. }
  186. return types.FieldType_8byte, 0
  187. case "FLOAT":
  188. // MySQL 的 FLOAT 是 IEEE754 单精度 (4 字节), 按 4 字节收; SQLite 只有 REAL
  189. // 亲和性, 声明成 FLOAT 也是双精度, 折进 Float64 不丢东西。
  190. if mysql {
  191. return types.FieldType_Float32, 0
  192. }
  193. return types.FieldType_Float64, 0
  194. case "DOUBLE", "DOUBLE PRECISION", "REAL":
  195. return types.FieldType_Float64, 0
  196. case "DECIMAL", "NUMERIC":
  197. // DECIMAL(p,s) 收成定点 Decimal(s) —— 不像以前折成 float 那样丢精度。
  198. // p 的上界是 38 (128 位系数的十进制容量, types.MaxDecimalPrecision),
  199. // s 的上界是 31 (Flag 低 5 位, types.MaxDecimalScale), 两个是【不同】的约束 ——
  200. // 以前拿 MaxDecimalScale(31) 去卡 p, 于是 DECIMAL(38,2) 这种完全表达得了的列
  201. // 被误判成"表达不了"而退成 float64: 精度不是被能力限制, 是被写错的上限限制的。
  202. prec, scale := 38, 0
  203. if len(args) >= 1 {
  204. prec = args[0]
  205. }
  206. if len(args) >= 2 {
  207. scale = args[1]
  208. }
  209. if prec <= types.MaxDecimalPrecision && scale >= 0 && scale <= types.MaxDecimalScale {
  210. return types.FieldType_Decimal, byte(scale)
  211. }
  212. // 真的表达不了才退 Float64 (至少不假装精确) —— 降级本身要报出来, 见 degradeNote。
  213. return types.FieldType_Float64, 0
  214. case "BOOLEAN", "BOOL":
  215. // 独立出 Bool 类型: 以前借用 1byte, "写 2 也算真" 让等值/索引给出多套答案。
  216. return types.FieldType_Bool, 0
  217. case "YEAR":
  218. // 2 字节年份, 与 MySQL YEAR 的取值区间一致。
  219. return types.FieldType_Year, 0
  220. case "UUID":
  221. return types.FieldType_UUID, 0
  222. case "CHAR", "CHARACTER", "VARCHAR", "NCHAR", "NVARCHAR", "VARCHAR2":
  223. if size > 0 {
  224. return bucketColumn(size)
  225. }
  226. return types.FieldType_32byte, 0
  227. case "TEXT", "TINYTEXT", "MEDIUMTEXT", "LONGTEXT", "CLOB", "NCLOB":
  228. return types.FieldType_Dynamicbyte, 0
  229. case "BLOB", "TINYBLOB", "MEDIUMBLOB", "LONGBLOB", "BINARY", "VARBINARY", "BYTE":
  230. // 二进制大对象收成 Blob: 与 Dynamicbyte 盘上同为动态列, 但类型名保留了
  231. // "这是二进制大对象"的语义 (导回 SQL 时也还原成 BLOB/LONGBLOB, 而不是 TEXT)。
  232. return types.FieldType_Blob, 0
  233. case "DATE", "DATETIME", "TIMESTAMP":
  234. // VRTX 没有日期类型: 时间就用 8 字节整数表达 —— 时序索引的键 (_ts) 正是这么
  235. // 存的 (见 types.FieldType_8byte 的说明), 时间范围查询/分段裁剪都按它比较。
  236. // 所以 SQL 的时间列落 8byte: 能比较大小、能建索引、能当时序键。值在
  237. // value.go 里换算成 Unix 纳秒。
  238. //
  239. // 这里以前落在 dynamic, 后果是时间列在 VRTX 里成了"一堆字节": 不能建索引
  240. // (引擎直接拒: 动态列不能作为索引键), 也不能做时间范围查询 —— 在一个
  241. // 时序数据库里, 这等于把时间列废掉。列名再叫 ctime 也救不回来。
  242. return types.FieldType_8byte, 0
  243. case "TIME", "INTERVAL", "JSON", "JSONB", "XML", "ENUM", "SET",
  244. "BIT", "GEOMETRY", "POINT":
  245. // 这些在 VRTX 里没有等价物, 按字节原样存 (读出来就是它本来的文本/编码),
  246. // 好处是往返不丢内容, 坏处是没法用 VRTX 的类型语义去查它。
  247. // TIME 是"当日时刻/时长"不是时间点, 不换成时间戳。
  248. return types.FieldType_Dynamicbyte, 0
  249. }
  250. return types.FieldType_Dynamicbyte, 0
  251. }
  252. // splitTypeArgs 拆出类型名与括号内全部整数参数:
  253. //
  254. // "decimal(18,2)" → ("DECIMAL", [18 2])
  255. // "varchar(32)" → ("VARCHAR", [32])
  256. // "bigint unsigned" → ("BIGINT", nil)
  257. func splitTypeArgs(u string) (string, []int) {
  258. base := u
  259. var args []int
  260. if i := strings.IndexByte(base, '('); i >= 0 {
  261. if j := strings.LastIndexByte(base, ')'); j > i {
  262. for _, part := range strings.Split(base[i+1:j], ",") {
  263. if n, err := strconv.Atoi(strings.TrimSpace(part)); err == nil {
  264. args = append(args, n)
  265. }
  266. }
  267. }
  268. base = strings.TrimSpace(base[:i])
  269. }
  270. if f := strings.Fields(base); len(f) > 0 {
  271. // "DOUBLE PRECISION" / "BIGINT UNSIGNED" 这类: 前缀就是主类型
  272. if f[0] == "DOUBLE" && len(f) > 1 && f[1] == "PRECISION" {
  273. return "DOUBLE", args
  274. }
  275. base = f[0]
  276. }
  277. return base, args
  278. }
  279. // splitTypeAndSize 拆出 "varchar(32)" → ("VARCHAR", 32); "bigint unsigned" → ("BIGINT", 0)。
  280. // 只关心第一个参数; DECIMAL(p,s) 这类多参数场景用 splitTypeArgs。
  281. func splitTypeAndSize(u string) (string, int) {
  282. base, args := splitTypeArgs(u)
  283. if len(args) > 0 {
  284. return base, args[0]
  285. }
  286. return base, 0
  287. }
  288. // sqlTypeName VRTX 类型 → SQL 声明。
  289. //
  290. // param: 列级类型参数 —— Decimal 是 scale, 字符串家族是行内宽度码, 其余类型忽略
  291. // mysql: false 用 VARCHAR(n)/BLOB 这套 (SQLite), true 用 MySQL 的写法
  292. func sqlTypeName(t types.FieldType, param byte, mysql bool) string {
  293. switch t {
  294. case types.FieldType_1byte:
  295. if mysql {
  296. return "TINYINT UNSIGNED"
  297. }
  298. return "INTEGER"
  299. case types.FieldType_2byte:
  300. if mysql {
  301. return "SMALLINT UNSIGNED"
  302. }
  303. return "INTEGER"
  304. case types.FieldType_4byte:
  305. if mysql {
  306. return "INT UNSIGNED"
  307. }
  308. return "INTEGER"
  309. case types.FieldType_8byte:
  310. if mysql {
  311. return "BIGINT UNSIGNED"
  312. }
  313. return "INTEGER"
  314. case types.FieldType_Int8, types.FieldType_Int16, types.FieldType_Int32, types.FieldType_Int64:
  315. // MySQL 侧要落到有符号列, 才与 VRTX 的有符号档语义对齐。
  316. // SQLite 只有 INTEGER (有符号 64 位), 窄档声明回去会被读成 Int64 ——
  317. // 与改动前 1/2/4byte 一并声明成 INTEGER 的损失是同一类, 精确档位靠 __vrtx_meta 保。
  318. if !mysql {
  319. return "INTEGER"
  320. }
  321. switch t {
  322. case types.FieldType_Int8:
  323. return "TINYINT"
  324. case types.FieldType_Int16:
  325. return "SMALLINT"
  326. case types.FieldType_Int32:
  327. return "INT"
  328. }
  329. return "BIGINT"
  330. case types.FieldType_Float64:
  331. if mysql {
  332. return "DOUBLE"
  333. }
  334. return "REAL"
  335. case types.FieldType_Float32:
  336. // MySQL 的 FLOAT 就是 4 字节, 能原样读回 Float32; SQLite 没有单精度列,
  337. // 落 REAL (8 字节), 精确档位靠 __vrtx_meta 还原。
  338. if mysql {
  339. return "FLOAT"
  340. }
  341. return "REAL"
  342. case types.FieldType_Decimal:
  343. // p 取 MaxDecimalPrecision(38), 不是 MaxDecimalScale(31): 声明必须能装下 VRTX 里
  344. // 任何一个 Decimal 值。若按 (31,s) 声明, scale=30 的列就只剩 1 位整数位, 而 VRTX
  345. // 的系数上限是 10^38-1 —— 导出 8 位整数的值时会被 MySQL 判成超范围而拒绝。
  346. // 声明宽了不会丢东西 (迁移是"引擎能给多宽就声明多宽"), 声明窄了才丢。
  347. if mysql {
  348. return "DECIMAL(" + strconv.Itoa(types.MaxDecimalPrecision) + "," + strconv.Itoa(int(param)) + ")"
  349. }
  350. // SQLite 的 NUMERIC 亲和性会把 "1.50" 转成 REAL 1.5, 丢掉 scale 与高位精度;
  351. // 用 TEXT 原样存定点文本, 回来再按列 scale 解析。
  352. return "TEXT"
  353. case types.FieldType_Bool:
  354. // SQLite 接受 BOOLEAN 声明 (NUMERIC 亲和性, 存 0/1), 声明回去也能被认成 Bool。
  355. return "BOOLEAN"
  356. case types.FieldType_Year:
  357. return "YEAR"
  358. case types.FieldType_UUID:
  359. // 16 字节二进制, 紧凑且无损; 人类可读的 8-4-4-4-12 形式由 UUID.String() 给。
  360. if mysql {
  361. return "BINARY(16)"
  362. }
  363. return "BLOB"
  364. case types.FieldType_Int128:
  365. // 128 位整数没有 SQL 原生类型: 存十进制文本, 往返靠 String/Parse。
  366. if mysql {
  367. return "VARCHAR(40)"
  368. }
  369. return "TEXT"
  370. case types.FieldType_Blob:
  371. // MySQL 侧用 LONGBLOB: 迁移是"源库有多宽就声明多宽", 宽声明不会丢内容;
  372. // SQLite 只有 BLOB 一种写法。
  373. if mysql {
  374. return "LONGBLOB"
  375. }
  376. return "BLOB"
  377. case types.FieldType_16byte, types.FieldType_32byte, types.FieldType_64byte,
  378. types.FieldType_128byte, types.FieldType_256byte:
  379. // 声明该列的【真实】字节宽度: 宽度码 (maxlen:1000 → 1024) 与类型默认档
  380. // (256byte → 256) 是两回事, 只按类型声明会把宽度码声明出来的列开窄。
  381. return "VARCHAR(" + strconv.Itoa(int(types.FieldSize(t, param))) + ")"
  382. default:
  383. // Dynamicbyte (变长文本/二进制) 落到 BLOB/LONGBLOB 而不是 TEXT:
  384. // - 它本来就可能是任意二进制 (绑 []byte 出去), 声明成 TEXT 在 MySQL 下
  385. // 会按列字符集解释 —— 非法 UTF-8 直接报错或被改坏;
  386. // - SQLite 的 TEXT 亲和性还会把看起来像数字的字符串转成数值 ("123" → 123),
  387. // 而 BLOB 亲和性不做任何转换。
  388. //
  389. // 代价: 没有 __vrtx_meta 时, 声明出去的 BLOB 再读回来会被认成 FieldType_Blob
  390. // 而不是 Dynamicbyte。两者盘上布局与行为完全一致 (都是动态列), 数据不受影响,
  391. // 只是"名字"变了; 需要精确类型就靠 meta (默认开启)。
  392. if mysql {
  393. return "LONGBLOB"
  394. }
  395. return "BLOB"
  396. }
  397. }
  398. // sqlTimeKind SQL 列声明的时间语义 ("datetime" / "date" / "timestamp"), 空即不是时间列。
  399. func sqlTimeKind(decl string) string {
  400. base, _ := splitTypeAndSize(strings.ToUpper(strings.TrimSpace(decl)))
  401. switch base {
  402. case "DATETIME":
  403. return "datetime"
  404. case "TIMESTAMP":
  405. return "timestamp"
  406. case "DATE":
  407. return "date"
  408. }
  409. return ""
  410. }
  411. // sqlTimeName 时间语义 → SQL 列声明。
  412. //
  413. // MySQL 侧带上 (6): 原列若不是 DATETIME(0) 而是带小数秒的, 声明成 DATETIME(0)
  414. // 会把微秒截掉 —— 时间戳本身是纳秒精度, 声明不能比它窄。
  415. func sqlTimeName(kind string, mysql bool) string {
  416. switch kind {
  417. case "date":
  418. return "DATE"
  419. case "timestamp":
  420. if mysql {
  421. return "TIMESTAMP(6)"
  422. }
  423. return "TIMESTAMP"
  424. case "datetime":
  425. if mysql {
  426. return "DATETIME(6)"
  427. }
  428. return "DATETIME"
  429. }
  430. return "DATETIME"
  431. }
  432. // columnTypeName 一列的 SQL 声明: 时间列按当初的时间语义还原 (DATETIME / DATE /
  433. // TIMESTAMP), 其余按 VRTX 档位就近取 (sqlTypeName)。
  434. func columnTypeName(c Column, mysql bool) string {
  435. if c.IsTime() {
  436. return sqlTimeName(c.TimeKind, mysql)
  437. }
  438. return sqlTypeName(c.Type, c.Param, mysql)
  439. }