migrator.go 27 KB

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