migrator.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. package gormvrtx
  2. // Migrator: GORM schema 自省不经 SQL, 直接调 TsDB2 引擎 API。
  3. // 核心方法 (建表/删表/查表/增删列/查列/建删查索引/列类型) 为真实现;
  4. // 引擎无对应能力的 (改列类型/重命名/约束/视图) 返回明确的"不支持"错误,
  5. // 而不是静默做错。
  6. import (
  7. "errors"
  8. "fmt"
  9. "reflect"
  10. "strings"
  11. "time"
  12. "gorm.io/gorm"
  13. "gorm.io/gorm/clause"
  14. "gorm.io/gorm/schema"
  15. "git.ycl.pub/yl32940/TsDB2/DB/core"
  16. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  17. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  18. )
  19. // Migrator 实现 gorm.Migrator。
  20. type Migrator struct {
  21. db *gorm.DB
  22. eng *core.DB
  23. dsn string
  24. dialector *Dialector
  25. }
  26. var _ gorm.Migrator = Migrator{}
  27. // errNotSupported 构造"引擎不支持"错误。
  28. func errNotSupported(op string) error {
  29. return fmt.Errorf("vrtx: 不支持 %s (引擎无对应能力)", op)
  30. }
  31. // =============================================================================
  32. // 模型/表解析助手 (对齐 GORM 官方 migrator 的拿表名姿势)
  33. // =============================================================================
  34. // stmtFor 解析 dst 为 *gorm.Statement: 字符串视为表名, 其余经 schema 解析
  35. // (NamingStrategy / Tabler 接口都生效), stmt.Table 即最终表名。
  36. func (m Migrator) stmtFor(value interface{}) (*gorm.Statement, error) {
  37. stmt := &gorm.Statement{DB: m.db}
  38. if m.db != nil && m.db.Statement != nil {
  39. stmt.Table = m.db.Statement.Table
  40. stmt.TableExpr = m.db.Statement.TableExpr
  41. }
  42. if table, ok := value.(string); ok {
  43. stmt.Table = table
  44. } else if err := stmt.ParseWithSpecialTableName(value, stmt.Table); err != nil {
  45. return nil, err
  46. }
  47. if stmt.Table == "" {
  48. return nil, errors.New("vrtx: 无法确定表名")
  49. }
  50. return stmt, nil
  51. }
  52. // findTable 按表名取引擎表句柄。
  53. func (m Migrator) findTable(name string) (*core.Table, error) {
  54. tbl, ok := m.eng.FindTable(name)
  55. if !ok {
  56. return nil, fmt.Errorf("%w: %q", core.ErrTableNotFound, name)
  57. }
  58. return tbl, nil
  59. }
  60. // tableFieldIndex 列名 → 引擎字段主注册表下标 (含已删除列的位置; 索引 FieldIDs
  61. // 即按这个下标引用, 与驱动的 fieldIndex 约定一致), 大小写不敏感。
  62. func tableFieldIndex(tbl *core.Table, name string) int {
  63. for i := range tbl.Field {
  64. if !tbl.Field[i].IsDropped() && strings.EqualFold(string(tbl.Field[i].FieldName), name) {
  65. return i
  66. }
  67. }
  68. return -1
  69. }
  70. // fieldInfoOf GORM 字段 → 引擎列定义。显式 gorm:"type:..." 标签优先于 Go 类型默认
  71. // 映射 (报告 #10 的逃生门: string 字段默认无 size 会落成动态列, 带索引时引擎拒绝建
  72. // 索引 —— 用 type:varchar(500)/type:bigint 精确声明列类型/宽度即可绕过)。
  73. func (m Migrator) fieldInfoOf(f *schema.Field) (types.FieldInfo, error) {
  74. fi := types.FieldInfo{FieldName: []byte(f.DBName)}
  75. // 可空: NotNull==false 且非主键 → 允许 NULL。主键列是行定位键, 绝不可空。
  76. if !f.NotNull && !f.PrimaryKey {
  77. fi.Flag |= types.FlagNullable
  78. }
  79. if tag, ok := f.TagSettings["TYPE"]; ok {
  80. if name, size, ok := explicitTypeMapping(tag); ok {
  81. switch name {
  82. case "BIGINT", "INT":
  83. // 显式整型声明: 符号随 Go 类型 (uint64 超出 int64 可表数域, 不能错配)。
  84. if f.DataType == schema.Uint {
  85. fi.FieldType = types.FieldType_Uint64
  86. } else {
  87. fi.FieldType = types.FieldType_Int64
  88. }
  89. case "DOUBLE":
  90. fi.FieldType = types.FieldType_Float64
  91. case "BOOLEAN":
  92. fi.FieldType = types.FieldType_Bool
  93. case "DATETIME":
  94. fi.FieldType = types.FieldType_DateTime
  95. case "TEXT":
  96. fi.FieldType = types.FieldType_Dynamicbyte
  97. case "VARCHAR":
  98. return stringFieldInfo(fi, f.DBName, size)
  99. }
  100. return fi, nil
  101. }
  102. // 未知 type: 落回默认映射 (与 DataTypeOf 的注释一致)。
  103. }
  104. switch f.DataType {
  105. case schema.Bool:
  106. fi.FieldType = types.FieldType_Bool
  107. case schema.Int:
  108. fi.FieldType = types.FieldType_Int64
  109. case schema.Uint:
  110. fi.FieldType = types.FieldType_Uint64
  111. case schema.Float:
  112. fi.FieldType = types.FieldType_Float64
  113. case schema.String:
  114. return stringFieldInfo(fi, f.DBName, f.Size)
  115. case schema.Time:
  116. fi.FieldType = types.FieldType_DateTime
  117. case schema.Bytes:
  118. fi.FieldType = types.FieldType_Blob
  119. default:
  120. // struct / JSON 等: 文本化存动态列
  121. fi.FieldType = types.FieldType_Dynamicbyte
  122. }
  123. return fi, nil
  124. }
  125. // stringFieldInfo string 字段按声明宽度落成引擎列: 无宽度或声明超行内定长上限
  126. // (types.MaxInlineColumnWidth = 2048) → 动态列 (TEXT); 其余定长 string 并写宽度码。
  127. // 宽度声明兑现不了 (引擎档位限制) 返回错误。
  128. func stringFieldInfo(fi types.FieldInfo, dbName string, size int) (types.FieldInfo, error) {
  129. if size < 0 {
  130. size = 0
  131. }
  132. switch {
  133. case size == 0:
  134. fi.FieldType = types.FieldType_Dynamicbyte // 无长度语义的文本
  135. case size > types.MaxInlineColumnWidth:
  136. fi.FieldType = types.FieldType_Dynamicbyte // 声明超行内定长上限 → 动态列
  137. default:
  138. if err := types.CheckDeclaredStringSize(size); err != nil {
  139. return fi, fmt.Errorf("列 %q: %w", dbName, err)
  140. }
  141. fi.FieldType = types.FieldType_String
  142. fi.Flag |= types.WidthCodeFor(size)
  143. }
  144. return fi, nil
  145. }
  146. // isIntDataType 整型 (含 uint) GORM 字段 —— 主键索引只接受整型定位键。
  147. func isIntDataType(f *schema.Field) bool {
  148. return f.DataType == schema.Int || f.DataType == schema.Uint
  149. }
  150. // pkIndexName 行存储主键索引名 (与驱动 CREATE TABLE ... PRIMARY KEY 路径一致)。
  151. func pkIndexName(table string) string { return "pk_" + table }
  152. // uniqueIndexName 单列唯一索引名 (field.Unique 的承载)。
  153. func uniqueIndexName(table, col string) string { return "uk_" + table + "_" + col }
  154. // hasIndexIntent 字段是否声明了索引意图: index / uniqueIndex tag 或字段级 unique。
  155. // (无值标签经 GORM ParseTagSetting 会存成 "INDEX"/"UNIQUEINDEX" 字面值, 判空即覆盖
  156. // `gorm:"index"` 与 `gorm:"index:idx_x"` 两种写法。)
  157. func hasIndexIntent(f *schema.Field) bool {
  158. return f.Unique || f.TagSettings["INDEX"] != "" || f.TagSettings["UNIQUEINDEX"] != ""
  159. }
  160. // dynamicColumnErr 报告 #10 的友好错误: 字段带索引标签却落成动态列时, 在建表阶段就
  161. // 给出可操作的提示, 而不是等建索引时被引擎的 "动态列不能作为索引键" 顶回来。
  162. // 字段不带索引意图、或落成可索引类型时返回 nil。
  163. func (m Migrator) dynamicColumnErr(f *schema.Field) error {
  164. if !hasIndexIntent(f) {
  165. return nil
  166. }
  167. fi, err := m.fieldInfoOf(f)
  168. if err != nil {
  169. return err
  170. }
  171. if fi.FieldType == types.FieldType_Dynamicbyte {
  172. return fmt.Errorf("字段 %s 带索引标签但会落成动态列(无 size 或 size>2048),无法建索引:请给该字段加 gorm:\"size:1..2048\"", f.Name)
  173. }
  174. return nil
  175. }
  176. // indexTypeFor 单列 → B+ 树二级索引; 多列 → 复合索引。
  177. func indexTypeFor(fieldCnt int) page.IndexType {
  178. if fieldCnt > 1 {
  179. return page.IndexTypeCompose
  180. }
  181. return page.IndexTypeBTree
  182. }
  183. // createColumnIndex 按列下标在引擎表上建索引 (unique 透传)。
  184. func createColumnIndex(tbl *core.Table, name string, fieldIDs []byte, unique bool) error {
  185. if len(fieldIDs) == 0 {
  186. return fmt.Errorf("vrtx: 索引 %q 没有分量列", name)
  187. }
  188. _, err := tbl.CreateIndex(name, indexTypeFor(len(fieldIDs)), fieldIDs, unique)
  189. return err
  190. }
  191. // =============================================================================
  192. // AutoMigrate / Database
  193. // =============================================================================
  194. // AutoMigrate 首次建表 (含主键/唯一/普通索引), 已有表则补缺失的列与索引。
  195. // 引擎 AddField 不支持改列类型, 已存在列的类型微调一律放过 (宽松语义)。
  196. func (m Migrator) AutoMigrate(values ...interface{}) error {
  197. for _, value := range values {
  198. stmt, err := m.stmtFor(value)
  199. if err != nil {
  200. return err
  201. }
  202. if !m.HasTable(stmt.Table) {
  203. if err := m.CreateTable(value); err != nil {
  204. return err
  205. }
  206. continue
  207. }
  208. // 表已存在: 补列 (DBNames 排除 has-many 等关联字段 —— 它们没有列)
  209. for _, dbName := range stmt.Schema.DBNames {
  210. if !m.HasColumn(value, dbName) {
  211. if err := m.AddColumn(value, dbName); err != nil {
  212. return err
  213. }
  214. }
  215. }
  216. // 补索引: tag 声明的 (uniqueIndex/index) + 字段级 unique
  217. for _, idx := range stmt.Schema.ParseIndexes() {
  218. if m.HasIndex(value, idx.Name) {
  219. continue
  220. }
  221. if err := m.CreateIndex(value, idx.Name); err != nil {
  222. return err
  223. }
  224. }
  225. for _, f := range stmt.Schema.Fields {
  226. if !f.Unique || f.PrimaryKey {
  227. continue
  228. }
  229. // 报告 #10: 唯一约束落到动态列上同样在建索引前给友好错误。
  230. if err := m.dynamicColumnErr(f); err != nil {
  231. return fmt.Errorf("vrtx: 迁移 %q: %w", stmt.Table, err)
  232. }
  233. name := uniqueIndexName(stmt.Table, f.DBName)
  234. if m.HasIndex(value, name) {
  235. continue
  236. }
  237. tbl, err := m.findTable(stmt.Table)
  238. if err != nil {
  239. return err
  240. }
  241. if err := createColumnIndex(tbl, name, []byte{byte(tableFieldIndex(tbl, f.DBName))}, true); err != nil {
  242. return err
  243. }
  244. }
  245. }
  246. return nil
  247. }
  248. // CurrentDatabase 返回 DSN (引擎无 catalog 概念, 库=文件)。
  249. func (m Migrator) CurrentDatabase() string { return m.dsn }
  250. // FullDataTypeOf 纯类型名 (主键等约束在建表/建索引时表达, 不拼进类型)。
  251. func (m Migrator) FullDataTypeOf(field *schema.Field) clause.Expr {
  252. return clause.Expr{SQL: m.dialector.DataTypeOf(field)}
  253. }
  254. // GetTypeAliases 引擎无类型别名体系。
  255. func (m Migrator) GetTypeAliases(databaseTypeName string) []string { return nil }
  256. // =============================================================================
  257. // Tables
  258. // =============================================================================
  259. // CreateTable 对每个模型: 字段 → 引擎 CreateTable; 整型主键字段 → 行存储主键索引;
  260. // field.Unique → 唯一索引; uniqueIndex/index tag → 对应索引 (全部经引擎 API)。
  261. // 主键索引恰好建一次: 建前先查 GetRowStoreIndex (引擎 CreateTable 可能已自动建行存储,
  262. // 报告 #8 —— 不查会撞 ErrRowStoreExists "表上已存在行存储索引"); 行存储全表唯一, 复合
  263. // 主键只取声明的第一个整型字段; 唯一/普通索引循环对主键字段一律跳过。
  264. func (m Migrator) CreateTable(values ...interface{}) error {
  265. for _, value := range values {
  266. stmt, err := m.stmtFor(value)
  267. if err != nil {
  268. return err
  269. }
  270. if stmt.Schema == nil {
  271. return errors.New("vrtx: CreateTable 需要模型, 得不到 schema")
  272. }
  273. if m.HasTable(stmt.Table) {
  274. continue // 幂等: 已存在则跳过 (对齐 gorm 各方言的 IF NOT EXISTS 语义)
  275. }
  276. // 建表阶段先核一遍索引字段的可索引性 (报告 #10): 带 index/uniqueIndex/unique
  277. // 却落成动态列的字段, 在表创建前就给出可操作的错误, 而不是建索引时才被引擎的
  278. // "动态列不能作为索引键" 顶回来。
  279. for _, dbName := range stmt.Schema.DBNames {
  280. f := stmt.Schema.FieldsByDBName[dbName]
  281. if err := m.dynamicColumnErr(f); err != nil {
  282. return fmt.Errorf("vrtx: 建表 %q: %w", stmt.Table, err)
  283. }
  284. }
  285. infos := make([]types.FieldInfo, 0, len(stmt.Schema.DBNames))
  286. for _, dbName := range stmt.Schema.DBNames {
  287. fi, err := m.fieldInfoOf(stmt.Schema.FieldsByDBName[dbName])
  288. if err != nil {
  289. return fmt.Errorf("vrtx: 建表 %q: %w", stmt.Table, err)
  290. }
  291. infos = append(infos, fi)
  292. }
  293. tbl, err := m.eng.CreateTable(stmt.Table, infos)
  294. if err != nil {
  295. return fmt.Errorf("vrtx: 建表 %q: %w", stmt.Table, err)
  296. }
  297. // 整型主键 → 行存储主键索引 (引擎表的行定位键)。
  298. if _, hasRowStore := tbl.IndexMeta().GetRowStoreIndex(); !hasRowStore {
  299. for _, f := range stmt.Schema.Fields {
  300. if !f.PrimaryKey || !isIntDataType(f) {
  301. continue
  302. }
  303. idx := tableFieldIndex(tbl, f.DBName)
  304. if idx < 0 {
  305. return fmt.Errorf("vrtx: 建表 %q: 找不到主键列 %q", stmt.Table, f.DBName)
  306. }
  307. if _, err := tbl.CreateIndex(pkIndexName(stmt.Table), page.IndexTypePrimary, []byte{byte(idx)}); err != nil {
  308. return fmt.Errorf("vrtx: 建表 %q 主键索引: %w", stmt.Table, err)
  309. }
  310. break // 行存储全表唯一: 复合主键也只建行定位键这一份
  311. }
  312. }
  313. // 字段级 unique (主键已由行存储承载, 跳过)
  314. for _, f := range stmt.Schema.Fields {
  315. if !f.Unique || f.PrimaryKey || f.DBName == "" {
  316. continue
  317. }
  318. idx := tableFieldIndex(tbl, f.DBName)
  319. if idx < 0 {
  320. return fmt.Errorf("vrtx: 建表 %q: 找不到列 %q", stmt.Table, f.DBName)
  321. }
  322. if err := createColumnIndex(tbl, uniqueIndexName(stmt.Table, f.DBName), []byte{byte(idx)}, true); err != nil {
  323. return fmt.Errorf("vrtx: 建表 %q 唯一索引: %w", stmt.Table, err)
  324. }
  325. }
  326. // tag 声明的索引 (uniqueIndex / index, 含复合)
  327. for _, idx := range stmt.Schema.ParseIndexes() {
  328. // 分量全是主键列的索引定义直接跳过: 主键唯一性已由行存储承载, 对主键列再建
  329. // 二级索引纯属冗余 (primaryKey 与 uniqueIndex 标在同一字段时防止重复建)。
  330. allPK := len(idx.Fields) > 0
  331. for _, opt := range idx.Fields {
  332. if opt.Field == nil || !opt.Field.PrimaryKey {
  333. allPK = false
  334. break
  335. }
  336. }
  337. if allPK {
  338. continue
  339. }
  340. fieldIDs := make([]byte, 0, len(idx.Fields))
  341. for _, opt := range idx.Fields {
  342. if opt.Field == nil {
  343. return fmt.Errorf("vrtx: 建表 %q: 索引 %q 含表达式分量, 引擎不支持", stmt.Table, idx.Name)
  344. }
  345. // 复合索引里的无标签分量也要核可索引性 (它进了索引就该受索引约束)。
  346. if err := m.dynamicColumnErr(opt.Field); err != nil {
  347. return fmt.Errorf("vrtx: 建表 %q: %w", stmt.Table, err)
  348. }
  349. fi := tableFieldIndex(tbl, opt.Field.DBName)
  350. if fi < 0 {
  351. return fmt.Errorf("vrtx: 建表 %q: 索引 %q 引用了不存在的列 %q", stmt.Table, idx.Name, opt.Field.DBName)
  352. }
  353. fieldIDs = append(fieldIDs, byte(fi))
  354. }
  355. if err := createColumnIndex(tbl, idx.Name, fieldIDs, idx.Class == "UNIQUE"); err != nil {
  356. return fmt.Errorf("vrtx: 建表 %q 索引 %q: %w", stmt.Table, idx.Name, err)
  357. }
  358. }
  359. }
  360. return nil
  361. }
  362. // DropTable 逐表删除。
  363. func (m Migrator) DropTable(values ...interface{}) error {
  364. for _, value := range values {
  365. stmt, err := m.stmtFor(value)
  366. if err != nil {
  367. return err
  368. }
  369. if err := m.eng.DropTable(stmt.Table); err != nil {
  370. return fmt.Errorf("vrtx: 删表 %q: %w", stmt.Table, err)
  371. }
  372. }
  373. return nil
  374. }
  375. // HasTable 表存在性。
  376. func (m Migrator) HasTable(value interface{}) bool {
  377. stmt, err := m.stmtFor(value)
  378. if err != nil {
  379. return false
  380. }
  381. _, ok := m.eng.FindTable(stmt.Table)
  382. return ok
  383. }
  384. // RenameTable 引擎不支持表重命名。
  385. func (m Migrator) RenameTable(oldName, newName interface{}) error {
  386. return errNotSupported("RenameTable")
  387. }
  388. // GetTables 列出全部表名。
  389. func (m Migrator) GetTables() ([]string, error) {
  390. metas := m.eng.GetAllTables()
  391. names := make([]string, 0, len(metas))
  392. for _, meta := range metas {
  393. names = append(names, meta.Name)
  394. }
  395. return names, nil
  396. }
  397. // TableType 引擎无表类型体系 (无临时表/外部表之分)。
  398. func (m Migrator) TableType(dst interface{}) (gorm.TableType, error) {
  399. return nil, errNotSupported("TableType")
  400. }
  401. // =============================================================================
  402. // Columns
  403. // =============================================================================
  404. // AddColumn 往已有表加一列 (dst=模型, field=列名/DBName)。
  405. func (m Migrator) AddColumn(value interface{}, field string) error {
  406. stmt, err := m.stmtFor(value)
  407. if err != nil {
  408. return err
  409. }
  410. tbl, err := m.findTable(stmt.Table)
  411. if err != nil {
  412. return err
  413. }
  414. f := stmt.Schema.LookUpField(field)
  415. if f == nil {
  416. return fmt.Errorf("vrtx: 模型上没有列 %q", field)
  417. }
  418. fi, err := m.fieldInfoOf(f)
  419. if err != nil {
  420. return fmt.Errorf("vrtx: 加列 %q.%q: %w", stmt.Table, field, err)
  421. }
  422. if err := tbl.AddField(fi); err != nil {
  423. return fmt.Errorf("vrtx: 加列 %q.%q: %w", stmt.Table, field, err)
  424. }
  425. return nil
  426. }
  427. // DropColumn 软删一列 (引擎 master registry 保留, 旧行不受影响)。
  428. func (m Migrator) DropColumn(value interface{}, field string) error {
  429. stmt, err := m.stmtFor(value)
  430. if err != nil {
  431. return err
  432. }
  433. tbl, err := m.findTable(stmt.Table)
  434. if err != nil {
  435. return err
  436. }
  437. if err := tbl.DropField(field); err != nil {
  438. return fmt.Errorf("vrtx: 删列 %q.%q: %w", stmt.Table, field, err)
  439. }
  440. return nil
  441. }
  442. // AlterColumn 引擎 AddField 不支持改列类型/宽度, 明确报错。
  443. func (m Migrator) AlterColumn(value interface{}, field string) error {
  444. return errNotSupported("AlterColumn (引擎不支持修改列定义)")
  445. }
  446. // MigrateColumn 宽松实现: 列已存在即视为一致 (引擎不支持类型微调, 报错没有意义)。
  447. func (m Migrator) MigrateColumn(value interface{}, field *schema.Field, columnType gorm.ColumnType) error {
  448. return nil
  449. }
  450. // MigrateColumnUnique 同上: 唯一性由唯一索引承载, 列级核对一律放过。
  451. func (m Migrator) MigrateColumnUnique(value interface{}, field *schema.Field, columnType gorm.ColumnType) error {
  452. return nil
  453. }
  454. // HasColumn 遍历引擎字段按名匹配 (大小写不敏感)。
  455. func (m Migrator) HasColumn(value interface{}, field string) bool {
  456. stmt, err := m.stmtFor(value)
  457. if err != nil {
  458. return false
  459. }
  460. tbl, err := m.findTable(stmt.Table)
  461. if err != nil {
  462. return false
  463. }
  464. return tableFieldIndex(tbl, field) >= 0
  465. }
  466. // RenameColumn 引擎不支持列重命名。
  467. func (m Migrator) RenameColumn(value interface{}, oldName, field string) error {
  468. return errNotSupported("RenameColumn")
  469. }
  470. // ColumnTypes 从引擎元数据构造列类型列表 (供高级用法)。
  471. func (m Migrator) ColumnTypes(value interface{}) ([]gorm.ColumnType, error) {
  472. stmt, err := m.stmtFor(value)
  473. if err != nil {
  474. return nil, err
  475. }
  476. tbl, err := m.findTable(stmt.Table)
  477. if err != nil {
  478. return nil, err
  479. }
  480. rowStore, hasRowStore := tbl.IndexMeta().GetRowStoreIndex()
  481. cols := make([]gorm.ColumnType, 0, len(tbl.Field))
  482. for i := range tbl.Field {
  483. fi := &tbl.Field[i]
  484. if fi.IsDropped() {
  485. continue
  486. }
  487. param := fi.Flag & types.FlagScaleMask
  488. ct := &columnType{
  489. name: string(fi.FieldName),
  490. dbTypeName: types.TypeNameOf(fi.FieldType),
  491. nullable: fi.IsNullable(),
  492. scanType: scanTypeOf(fi.FieldType),
  493. }
  494. if fi.FieldType == types.FieldType_String {
  495. ct.length = int64(types.FieldSize(fi.FieldType, param))
  496. ct.hasLength = true
  497. }
  498. if fi.FieldType == types.FieldType_Decimal {
  499. ct.scale = int64(param)
  500. ct.hasScale = true
  501. }
  502. if hasRowStore {
  503. for _, fid := range rowStore.Fields() {
  504. if int(fid) == i {
  505. ct.primaryKey = true
  506. break
  507. }
  508. }
  509. }
  510. cols = append(cols, ct)
  511. }
  512. return cols, nil
  513. }
  514. // columnType 实现 gorm.ColumnType 接口 (一套小结构体, 值全部来自引擎元数据)。
  515. type columnType struct {
  516. name string
  517. dbTypeName string
  518. nullable bool
  519. primaryKey bool
  520. unique bool
  521. length int64
  522. hasLength bool
  523. scale int64
  524. hasScale bool
  525. scanType reflect.Type
  526. }
  527. func (c *columnType) Name() string { return c.name }
  528. func (c *columnType) DatabaseTypeName() string { return c.dbTypeName }
  529. func (c *columnType) ColumnType() (string, bool) {
  530. if c.hasLength {
  531. return fmt.Sprintf("%s(%d)", c.dbTypeName, c.length), true
  532. }
  533. if c.hasScale {
  534. return fmt.Sprintf("%s(38,%d)", c.dbTypeName, c.scale), true
  535. }
  536. return c.dbTypeName, true
  537. }
  538. func (c *columnType) PrimaryKey() (bool, bool) { return c.primaryKey, true }
  539. func (c *columnType) AutoIncrement() (bool, bool) { return c.primaryKey, true } // 主键镜像列由引擎自增
  540. func (c *columnType) Length() (int64, bool) { return c.length, c.hasLength }
  541. func (c *columnType) DecimalSize() (int64, int64, bool) {
  542. return 38, c.scale, c.hasScale
  543. }
  544. func (c *columnType) Nullable() (bool, bool) { return c.nullable, true }
  545. func (c *columnType) Unique() (bool, bool) { return c.unique, c.unique }
  546. func (c *columnType) ScanType() reflect.Type { return c.scanType }
  547. func (c *columnType) Comment() (string, bool) {
  548. return "", false // 引擎无列注释 (Comment 是库级介绍区)
  549. }
  550. func (c *columnType) DefaultValue() (string, bool) { return "", false }
  551. // scanTypeOf 引擎列类型 → Go 扫描类型。
  552. func scanTypeOf(ft types.FieldType) reflect.Type {
  553. switch ft {
  554. case types.FieldType_Bool:
  555. return reflect.TypeOf(false)
  556. case types.FieldType_Int8, types.FieldType_Int16, types.FieldType_Int32, types.FieldType_Int64,
  557. types.FieldType_Uint8, types.FieldType_Uint16, types.FieldType_Uint32, types.FieldType_Uint64,
  558. types.FieldType_Year:
  559. return reflect.TypeOf(int64(0))
  560. case types.FieldType_Float32, types.FieldType_Float64:
  561. return reflect.TypeOf(float64(0))
  562. case types.FieldType_DateTime:
  563. return reflect.TypeOf(time.Time{})
  564. case types.FieldType_Blob, types.FieldType_Dynamicbyte:
  565. return reflect.TypeOf([]byte(nil))
  566. default: // String / Decimal / UUID / Int128 等文本化输出
  567. return reflect.TypeOf("")
  568. }
  569. }
  570. // =============================================================================
  571. // Views —— 引擎无视图
  572. // =============================================================================
  573. func (m Migrator) CreateView(name string, option gorm.ViewOption) error {
  574. return errNotSupported("CreateView")
  575. }
  576. func (m Migrator) DropView(name string) error {
  577. return errNotSupported("DropView")
  578. }
  579. // =============================================================================
  580. // Constraints —— 引擎无约束体系 (唯一性由唯一索引承载)
  581. // =============================================================================
  582. func (m Migrator) CreateConstraint(value interface{}, name string) error {
  583. return errNotSupported("CreateConstraint (引擎无约束体系, 唯一性请用 uniqueIndex)")
  584. }
  585. func (m Migrator) DropConstraint(value interface{}, name string) error {
  586. return errNotSupported("DropConstraint")
  587. }
  588. func (m Migrator) HasConstraint(value interface{}, name string) bool { return false }
  589. // =============================================================================
  590. // Indexes
  591. // =============================================================================
  592. // CreateIndex 按模型的 uniqueIndex/index tag 解析列, 建引擎索引。
  593. func (m Migrator) CreateIndex(value interface{}, name string) error {
  594. stmt, err := m.stmtFor(value)
  595. if err != nil {
  596. return err
  597. }
  598. if stmt.Schema == nil {
  599. return errors.New("vrtx: CreateIndex 需要模型, 得不到 schema")
  600. }
  601. idx := stmt.Schema.LookIndex(name)
  602. if idx == nil {
  603. return fmt.Errorf("vrtx: 模型 %q 上没有索引 %q", stmt.Table, name)
  604. }
  605. tbl, err := m.findTable(stmt.Table)
  606. if err != nil {
  607. return err
  608. }
  609. fieldIDs := make([]byte, 0, len(idx.Fields))
  610. for _, opt := range idx.Fields {
  611. if opt.Field == nil {
  612. return fmt.Errorf("vrtx: 索引 %q 含表达式分量, 引擎不支持", name)
  613. }
  614. // 报告 #10: 分量落成动态列时给友好错误, 而不是引擎的 "动态列不能作为索引键"。
  615. if err := m.dynamicColumnErr(opt.Field); err != nil {
  616. return fmt.Errorf("vrtx: 建索引 %q: %w", name, err)
  617. }
  618. fi := tableFieldIndex(tbl, opt.Field.DBName)
  619. if fi < 0 {
  620. return fmt.Errorf("vrtx: 索引 %q 引用了不存在的列 %q", name, opt.Field.DBName)
  621. }
  622. fieldIDs = append(fieldIDs, byte(fi))
  623. }
  624. if err := createColumnIndex(tbl, idx.Name, fieldIDs, idx.Class == "UNIQUE"); err != nil {
  625. return fmt.Errorf("vrtx: 建索引 %q.%q: %w", stmt.Table, name, err)
  626. }
  627. return nil
  628. }
  629. // DropIndex 按名删索引。
  630. func (m Migrator) DropIndex(value interface{}, name string) error {
  631. stmt, err := m.stmtFor(value)
  632. if err != nil {
  633. return err
  634. }
  635. tbl, err := m.findTable(stmt.Table)
  636. if err != nil {
  637. return err
  638. }
  639. if err := tbl.DropIndex(name); err != nil {
  640. return fmt.Errorf("vrtx: 删索引 %q.%q: %w", stmt.Table, name, err)
  641. }
  642. return nil
  643. }
  644. // HasIndex 枚举引擎索引元数据按名匹配 (大小写不敏感)。
  645. func (m Migrator) HasIndex(value interface{}, name string) bool {
  646. stmt, err := m.stmtFor(value)
  647. if err != nil {
  648. return false
  649. }
  650. tbl, err := m.findTable(stmt.Table)
  651. if err != nil {
  652. return false
  653. }
  654. for _, meta := range tbl.IndexMeta().GetIndexes() {
  655. if strings.EqualFold(meta.Name(), name) {
  656. return true
  657. }
  658. }
  659. return false
  660. }
  661. // RenameIndex 引擎不支持索引重命名 (删了重建)。
  662. func (m Migrator) RenameIndex(value interface{}, oldName, newName string) error {
  663. return errNotSupported("RenameIndex (请删除后重建)")
  664. }
  665. // GetIndexes 包装引擎索引元数据, 能返尽返。
  666. func (m Migrator) GetIndexes(value interface{}) ([]gorm.Index, error) {
  667. stmt, err := m.stmtFor(value)
  668. if err != nil {
  669. return nil, err
  670. }
  671. tbl, err := m.findTable(stmt.Table)
  672. if err != nil {
  673. return nil, err
  674. }
  675. metas := tbl.IndexMeta().GetIndexes()
  676. out := make([]gorm.Index, 0, len(metas))
  677. for _, meta := range metas {
  678. out = append(out, indexMeta{table: stmt.Table, meta: meta, tbl: tbl})
  679. }
  680. return out, nil
  681. }
  682. // indexMeta 把引擎索引元数据适配成 gorm.Index。
  683. type indexMeta struct {
  684. table string
  685. meta core.IndexMeta
  686. tbl *core.Table
  687. }
  688. func (i indexMeta) Table() string { return i.table }
  689. func (i indexMeta) Name() string { return i.meta.Name() }
  690. func (i indexMeta) Columns() []string {
  691. ids := i.meta.Fields()
  692. cols := make([]string, 0, len(ids))
  693. for _, id := range ids {
  694. if int(id) >= 0 && int(id) < len(i.tbl.Field) {
  695. cols = append(cols, string(i.tbl.Field[id].FieldName))
  696. }
  697. }
  698. return cols
  699. }
  700. func (i indexMeta) PrimaryKey() (bool, bool) {
  701. return i.meta.IndexType == page.IndexTypePrimary, true
  702. }
  703. func (i indexMeta) Unique() (bool, bool) { return i.meta.Unique(), true }
  704. func (i indexMeta) Option() string { return "" }