sqltype.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. package migrate
  2. import (
  3. "TsDB2/DB/core/types"
  4. "strconv"
  5. "strings"
  6. )
  7. // SQL ↔ VRTX 类型映射。两个方言共用一个"就近取档"规则:
  8. //
  9. // SQL VRTX
  10. // TINYINT Int8 (有符号, -128..127)
  11. // SMALLINT Int16
  12. // INT / MEDIUMINT Int32
  13. // BIGINT / INTEGER Int64 (SQLite 只有 INTEGER, 一律按 8 字节收)
  14. // 上面四者的 UNSIGNED 变体 1byte / 2byte / 4byte / 8byte (只有显式写了 UNSIGNED 才走无符号档)
  15. // FLOAT (MySQL) Float32 (4 字节; SQLite 的 FLOAT 是 8 字节, 见 inferSQLType)
  16. // DOUBLE / REAL Float64
  17. // DECIMAL(p,s) / NUMERIC(p,s) Decimal(s) (p,s ≤ 38 才能精确表示, 否则退回 Float64)
  18. // BOOLEAN / BOOL Bool
  19. // YEAR Year (2 字节年份, 0..32767)
  20. // UUID UUID (16 字节 RFC4122)
  21. // CHAR(n) / VARCHAR(n) 按容量 n 就近取 16/32/64/128/256byte 档
  22. // TEXT / JSON dynamic (字节原样存, 不解释内容)
  23. // BLOB / BINARY blob (同为动态列, 但语义是"文件/二进制大对象")
  24. // DATE / DATETIME / TIMESTAMP 8byte (Unix 纳秒时间戳, 见 inferSQLType 的说明)
  25. //
  26. // 有符号与无符号落在不同档位不是洁癖: 无符号档 (1/2/4/8byte) 盘上存的就是补码原样,
  27. // 有符号档 (Int8..Int64) 存的是保序编码 —— 同一个值在两种档位下盘上字节不同, 混用会让
  28. // 索引区间扫描与 ORDER BY 出错。SQL 的整数默认有符号, 因此这里默认收编到 Int8..Int64。
  29. //
  30. // 这里的 n 是【字节容量】不是字符数 —— VRTX 的定长档按字节算容量, 而 SQL 的长度单位是
  31. // 字符, 两者只有在单字节字符集下才重合。MySQL 侧直接读 CHARACTER_OCTET_LENGTH
  32. // (varchar(50) 在 utf8mb4 下就是 200 字节 → 256byte 档); SQLite 的长度声明本就不强制,
  33. // 仍按声明里的 n 算 (见 inferSQLType)。按字符数取档会把列开窄: 一个 25 字的中文别名
  34. // 就有 75 字节, 塞进 64byte 只能报错或截断。
  35. //
  36. // 反过来 (VRTX → SQL) 用 sqlTypeName, 定长档声明成 CHAR/VARCHAR(n)。因为目标方言按字符
  37. // 计数 (utf8mb4 下 VARCHAR(64) 实际能装 256 字节), 再读回来会宽一档 —— 也就是【不带
  38. // __vrtx_meta 时定长字符列会逐轮变宽】(数据不丢, 只是档位漂移)。要原样往返就带 meta,
  39. // 而 meta 是默认开着的。
  40. // bucketType 按容量取定长档位 (>256 字节没有定长档 → 变长列)。
  41. func bucketType(size int) types.FieldType {
  42. switch {
  43. case size <= 0:
  44. return types.FieldType_Dynamicbyte
  45. case size <= 16:
  46. return types.FieldType_16byte
  47. case size <= 32:
  48. return types.FieldType_32byte
  49. case size <= 64:
  50. return types.FieldType_64byte
  51. case size <= 128:
  52. return types.FieldType_128byte
  53. case size <= 256:
  54. return types.FieldType_256byte
  55. default:
  56. return types.FieldType_Dynamicbyte
  57. }
  58. }
  59. // inferSQLType 由 SQL 列类型 (含长度) 推断 VRTX 类型, 返回 (类型, 列参数)。
  60. // decl 可以是 "varchar(32)", "BIGINT UNSIGNED", "DECIMAL(18,2)" 这类原文。
  61. //
  62. // mysql 用于区分同名类型在不同方言下的宽度: MySQL 的 FLOAT 是 4 字节, 而 SQLite
  63. // 只有 REAL 亲和性 (8 字节), 声明成 FLOAT 也是双精度 —— 同一个 decl 不能按同一档收。
  64. func inferSQLType(decl string, mysql bool) (types.FieldType, byte) {
  65. u := strings.ToUpper(strings.TrimSpace(decl))
  66. base, args := splitTypeArgs(u)
  67. size := 0
  68. if len(args) > 0 {
  69. size = args[0]
  70. }
  71. // SQL 的整数默认是有符号的, 只有显式写了 UNSIGNED 才收编到无符号档。
  72. // 这里看原文 u 而不是 base: splitTypeAndSize 会把 "BIGINT UNSIGNED" 的后缀丢掉。
  73. signed := !strings.Contains(u, "UNSIGNED")
  74. switch base {
  75. case "TINYINT":
  76. if signed {
  77. return types.FieldType_Int8, 0
  78. }
  79. return types.FieldType_1byte, 0
  80. case "SMALLINT":
  81. if signed {
  82. return types.FieldType_Int16, 0
  83. }
  84. return types.FieldType_2byte, 0
  85. case "MEDIUMINT", "INT", "INTEGER", "INT2", "INT4", "INT8", "BIGINT", "SERIAL", "BIGSERIAL":
  86. // SQLite 的 INTEGER 能装整个 int64, 按 8 字节收最稳; MySQL 的 INT 就是 4 字节。
  87. if base == "INT" || base == "INT4" || base == "MEDIUMINT" {
  88. if signed {
  89. return types.FieldType_Int32, 0
  90. }
  91. return types.FieldType_4byte, 0
  92. }
  93. if signed {
  94. return types.FieldType_Int64, 0
  95. }
  96. return types.FieldType_8byte, 0
  97. case "FLOAT":
  98. // MySQL 的 FLOAT 是 IEEE754 单精度 (4 字节), 按 4 字节收; SQLite 只有 REAL
  99. // 亲和性, 声明成 FLOAT 也是双精度, 折进 Float64 不丢东西。
  100. if mysql {
  101. return types.FieldType_Float32, 0
  102. }
  103. return types.FieldType_Float64, 0
  104. case "DOUBLE", "DOUBLE PRECISION", "REAL":
  105. return types.FieldType_Float64, 0
  106. case "DECIMAL", "NUMERIC":
  107. // DECIMAL(p,s) 收成定点 Decimal(s) —— 不像以前折成 float 那样丢精度。
  108. // 上限 38 位是 128 位系数的容量: p 或 s 超出就没法精确表达, 退回 Float64
  109. // (与改动前一致), 至少不会假装精确。
  110. prec, scale := 38, 0
  111. if len(args) >= 1 {
  112. prec = args[0]
  113. }
  114. if len(args) >= 2 {
  115. scale = args[1]
  116. }
  117. if prec <= types.MaxDecimalScale && scale >= 0 && scale <= types.MaxDecimalScale {
  118. return types.FieldType_Decimal, byte(scale)
  119. }
  120. return types.FieldType_Float64, 0
  121. case "BOOLEAN", "BOOL":
  122. // 独立出 Bool 类型: 以前借用 1byte, "写 2 也算真" 让等值/索引给出多套答案。
  123. return types.FieldType_Bool, 0
  124. case "YEAR":
  125. // 2 字节年份, 与 MySQL YEAR 的取值区间一致。
  126. return types.FieldType_Year, 0
  127. case "UUID":
  128. return types.FieldType_UUID, 0
  129. case "CHAR", "CHARACTER", "VARCHAR", "NCHAR", "NVARCHAR", "VARCHAR2":
  130. if size > 0 {
  131. return bucketType(size), 0
  132. }
  133. return types.FieldType_32byte, 0
  134. case "TEXT", "TINYTEXT", "MEDIUMTEXT", "LONGTEXT", "CLOB", "NCLOB":
  135. return types.FieldType_Dynamicbyte, 0
  136. case "BLOB", "TINYBLOB", "MEDIUMBLOB", "LONGBLOB", "BINARY", "VARBINARY", "BYTE":
  137. // 二进制大对象收成 Blob: 与 Dynamicbyte 盘上同为动态列, 但类型名保留了
  138. // "这是二进制大对象"的语义 (导回 SQL 时也还原成 BLOB/LONGBLOB, 而不是 TEXT)。
  139. return types.FieldType_Blob, 0
  140. case "DATE", "DATETIME", "TIMESTAMP":
  141. // VRTX 没有日期类型: 时间就用 8 字节整数表达 —— 时序索引的键 (_ts) 正是这么
  142. // 存的 (见 types.FieldType_8byte 的说明), 时间范围查询/分段裁剪都按它比较。
  143. // 所以 SQL 的时间列落 8byte: 能比较大小、能建索引、能当时序键。值在
  144. // value.go 里换算成 Unix 纳秒。
  145. //
  146. // 这里以前落在 dynamic, 后果是时间列在 VRTX 里成了"一堆字节": 不能建索引
  147. // (引擎直接拒: 动态列不能作为索引键), 也不能做时间范围查询 —— 在一个
  148. // 时序数据库里, 这等于把时间列废掉。列名再叫 ctime 也救不回来。
  149. return types.FieldType_8byte, 0
  150. case "TIME", "INTERVAL", "JSON", "JSONB", "XML", "ENUM", "SET",
  151. "BIT", "GEOMETRY", "POINT":
  152. // 这些在 VRTX 里没有等价物, 按字节原样存 (读出来就是它本来的文本/编码),
  153. // 好处是往返不丢内容, 坏处是没法用 VRTX 的类型语义去查它。
  154. // TIME 是"当日时刻/时长"不是时间点, 不换成时间戳。
  155. return types.FieldType_Dynamicbyte, 0
  156. }
  157. return types.FieldType_Dynamicbyte, 0
  158. }
  159. // splitTypeArgs 拆出类型名与括号内全部整数参数:
  160. //
  161. // "decimal(18,2)" → ("DECIMAL", [18 2])
  162. // "varchar(32)" → ("VARCHAR", [32])
  163. // "bigint unsigned" → ("BIGINT", nil)
  164. func splitTypeArgs(u string) (string, []int) {
  165. base := u
  166. var args []int
  167. if i := strings.IndexByte(base, '('); i >= 0 {
  168. if j := strings.LastIndexByte(base, ')'); j > i {
  169. for _, part := range strings.Split(base[i+1:j], ",") {
  170. if n, err := strconv.Atoi(strings.TrimSpace(part)); err == nil {
  171. args = append(args, n)
  172. }
  173. }
  174. }
  175. base = strings.TrimSpace(base[:i])
  176. }
  177. if f := strings.Fields(base); len(f) > 0 {
  178. // "DOUBLE PRECISION" / "BIGINT UNSIGNED" 这类: 前缀就是主类型
  179. if f[0] == "DOUBLE" && len(f) > 1 && f[1] == "PRECISION" {
  180. return "DOUBLE", args
  181. }
  182. base = f[0]
  183. }
  184. return base, args
  185. }
  186. // splitTypeAndSize 拆出 "varchar(32)" → ("VARCHAR", 32); "bigint unsigned" → ("BIGINT", 0)。
  187. // 只关心第一个参数; DECIMAL(p,s) 这类多参数场景用 splitTypeArgs。
  188. func splitTypeAndSize(u string) (string, int) {
  189. base, args := splitTypeArgs(u)
  190. if len(args) > 0 {
  191. return base, args[0]
  192. }
  193. return base, 0
  194. }
  195. // sqlTypeName VRTX 类型 → SQL 声明。
  196. //
  197. // param: 列级类型参数 (Decimal 的 scale), 其余类型忽略
  198. // mysql: false 用 VARCHAR(n)/BLOB 这套 (SQLite), true 用 MySQL 的写法
  199. func sqlTypeName(t types.FieldType, param byte, mysql bool) string {
  200. switch t {
  201. case types.FieldType_1byte:
  202. if mysql {
  203. return "TINYINT UNSIGNED"
  204. }
  205. return "INTEGER"
  206. case types.FieldType_2byte:
  207. if mysql {
  208. return "SMALLINT UNSIGNED"
  209. }
  210. return "INTEGER"
  211. case types.FieldType_4byte:
  212. if mysql {
  213. return "INT UNSIGNED"
  214. }
  215. return "INTEGER"
  216. case types.FieldType_8byte:
  217. if mysql {
  218. return "BIGINT UNSIGNED"
  219. }
  220. return "INTEGER"
  221. case types.FieldType_Int8, types.FieldType_Int16, types.FieldType_Int32, types.FieldType_Int64:
  222. // MySQL 侧要落到有符号列, 才与 VRTX 的有符号档语义对齐。
  223. // SQLite 只有 INTEGER (有符号 64 位), 窄档声明回去会被读成 Int64 ——
  224. // 与改动前 1/2/4byte 一并声明成 INTEGER 的损失是同一类, 精确档位靠 __vrtx_meta 保。
  225. if !mysql {
  226. return "INTEGER"
  227. }
  228. switch t {
  229. case types.FieldType_Int8:
  230. return "TINYINT"
  231. case types.FieldType_Int16:
  232. return "SMALLINT"
  233. case types.FieldType_Int32:
  234. return "INT"
  235. }
  236. return "BIGINT"
  237. case types.FieldType_Float64:
  238. if mysql {
  239. return "DOUBLE"
  240. }
  241. return "REAL"
  242. case types.FieldType_Float32:
  243. // MySQL 的 FLOAT 就是 4 字节, 能原样读回 Float32; SQLite 没有单精度列,
  244. // 落 REAL (8 字节), 精确档位靠 __vrtx_meta 还原。
  245. if mysql {
  246. return "FLOAT"
  247. }
  248. return "REAL"
  249. case types.FieldType_Decimal:
  250. // 38 是 128 位系数的十进制容量 (与 types.MaxDecimalScale 一致)。
  251. if mysql {
  252. return "DECIMAL(" + strconv.Itoa(int(types.MaxDecimalScale)) + "," + strconv.Itoa(int(param)) + ")"
  253. }
  254. // SQLite 的 NUMERIC 亲和性会把 "1.50" 转成 REAL 1.5, 丢掉 scale 与高位精度;
  255. // 用 TEXT 原样存定点文本, 回来再按列 scale 解析。
  256. return "TEXT"
  257. case types.FieldType_Bool:
  258. // SQLite 接受 BOOLEAN 声明 (NUMERIC 亲和性, 存 0/1), 声明回去也能被认成 Bool。
  259. return "BOOLEAN"
  260. case types.FieldType_Year:
  261. return "YEAR"
  262. case types.FieldType_UUID:
  263. // 16 字节二进制, 紧凑且无损; 人类可读的 8-4-4-4-12 形式由 UUID.String() 给。
  264. if mysql {
  265. return "BINARY(16)"
  266. }
  267. return "BLOB"
  268. case types.FieldType_Int128:
  269. // 128 位整数没有 SQL 原生类型: 存十进制文本, 往返靠 String/Parse。
  270. if mysql {
  271. return "VARCHAR(40)"
  272. }
  273. return "TEXT"
  274. case types.FieldType_Blob:
  275. // MySQL 侧用 LONGBLOB: 迁移是"源库有多宽就声明多宽", 宽声明不会丢内容;
  276. // SQLite 只有 BLOB 一种写法。
  277. if mysql {
  278. return "LONGBLOB"
  279. }
  280. return "BLOB"
  281. case types.FieldType_16byte, types.FieldType_32byte, types.FieldType_64byte,
  282. types.FieldType_128byte, types.FieldType_256byte:
  283. return "VARCHAR(" + strconv.Itoa(int(types.GetSize(t))) + ")"
  284. default:
  285. // Dynamicbyte (变长文本/二进制) 落到 BLOB/LONGBLOB 而不是 TEXT:
  286. // - 它本来就可能是任意二进制 (绑 []byte 出去), 声明成 TEXT 在 MySQL 下
  287. // 会按列字符集解释 —— 非法 UTF-8 直接报错或被改坏;
  288. // - SQLite 的 TEXT 亲和性还会把看起来像数字的字符串转成数值 ("123" → 123),
  289. // 而 BLOB 亲和性不做任何转换。
  290. //
  291. // 代价: 没有 __vrtx_meta 时, 声明出去的 BLOB 再读回来会被认成 FieldType_Blob
  292. // 而不是 Dynamicbyte。两者盘上布局与行为完全一致 (都是动态列), 数据不受影响,
  293. // 只是"名字"变了; 需要精确类型就靠 meta (默认开启)。
  294. if mysql {
  295. return "LONGBLOB"
  296. }
  297. return "BLOB"
  298. }
  299. }
  300. // sqlTimeKind SQL 列声明的时间语义 ("datetime" / "date" / "timestamp"), 空即不是时间列。
  301. func sqlTimeKind(decl string) string {
  302. base, _ := splitTypeAndSize(strings.ToUpper(strings.TrimSpace(decl)))
  303. switch base {
  304. case "DATETIME":
  305. return "datetime"
  306. case "TIMESTAMP":
  307. return "timestamp"
  308. case "DATE":
  309. return "date"
  310. }
  311. return ""
  312. }
  313. // sqlTimeName 时间语义 → SQL 列声明。
  314. //
  315. // MySQL 侧带上 (6): 原列若不是 DATETIME(0) 而是带小数秒的, 声明成 DATETIME(0)
  316. // 会把微秒截掉 —— 时间戳本身是纳秒精度, 声明不能比它窄。
  317. func sqlTimeName(kind string, mysql bool) string {
  318. switch kind {
  319. case "date":
  320. return "DATE"
  321. case "timestamp":
  322. if mysql {
  323. return "TIMESTAMP(6)"
  324. }
  325. return "TIMESTAMP"
  326. case "datetime":
  327. if mysql {
  328. return "DATETIME(6)"
  329. }
  330. return "DATETIME"
  331. }
  332. return "DATETIME"
  333. }
  334. // columnTypeName 一列的 SQL 声明: 时间列按当初的时间语义还原 (DATETIME / DATE /
  335. // TIMESTAMP), 其余按 VRTX 档位就近取 (sqlTypeName)。
  336. func columnTypeName(c Column, mysql bool) string {
  337. if c.IsTime() {
  338. return sqlTimeName(c.TimeKind, mysql)
  339. }
  340. return sqlTypeName(c.Type, c.Param, mysql)
  341. }