| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823 |
- package gormvrtx
- // Migrator: GORM schema 自省不经 SQL, 直接调 TsDB2 引擎 API。
- // 核心方法 (建表/删表/查表/增删列/查列/建删查索引/列类型) 为真实现;
- // 引擎无对应能力的 (改列类型/重命名/约束/视图) 返回明确的"不支持"错误,
- // 而不是静默做错。
- import (
- "errors"
- "fmt"
- "reflect"
- "strings"
- "time"
- "gorm.io/gorm"
- "gorm.io/gorm/clause"
- "gorm.io/gorm/schema"
- "git.ycl.pub/yl32940/TsDB2/DB/core"
- "git.ycl.pub/yl32940/TsDB2/DB/core/page"
- "git.ycl.pub/yl32940/TsDB2/DB/core/types"
- )
- // Migrator 实现 gorm.Migrator。
- type Migrator struct {
- db *gorm.DB
- eng *core.DB
- dsn string
- dialector *Dialector
- }
- var _ gorm.Migrator = Migrator{}
- // errNotSupported 构造"引擎不支持"错误。
- func errNotSupported(op string) error {
- return fmt.Errorf("vrtx: 不支持 %s (引擎无对应能力)", op)
- }
- // =============================================================================
- // 模型/表解析助手 (对齐 GORM 官方 migrator 的拿表名姿势)
- // =============================================================================
- // stmtFor 解析 dst 为 *gorm.Statement: 字符串视为表名, 其余经 schema 解析
- // (NamingStrategy / Tabler 接口都生效), stmt.Table 即最终表名。
- func (m Migrator) stmtFor(value interface{}) (*gorm.Statement, error) {
- stmt := &gorm.Statement{DB: m.db}
- if m.db != nil && m.db.Statement != nil {
- stmt.Table = m.db.Statement.Table
- stmt.TableExpr = m.db.Statement.TableExpr
- }
- if table, ok := value.(string); ok {
- stmt.Table = table
- } else if err := stmt.ParseWithSpecialTableName(value, stmt.Table); err != nil {
- return nil, err
- }
- if stmt.Table == "" {
- return nil, errors.New("vrtx: 无法确定表名")
- }
- return stmt, nil
- }
- // findTable 按表名取引擎表句柄。
- func (m Migrator) findTable(name string) (*core.Table, error) {
- tbl, ok := m.eng.FindTable(name)
- if !ok {
- return nil, fmt.Errorf("%w: %q", core.ErrTableNotFound, name)
- }
- return tbl, nil
- }
- // tableFieldIndex 列名 → 引擎字段主注册表下标 (含已删除列的位置; 索引 FieldIDs
- // 即按这个下标引用, 与驱动的 fieldIndex 约定一致), 大小写不敏感。
- func tableFieldIndex(tbl *core.Table, name string) int {
- for i := range tbl.Field {
- if !tbl.Field[i].IsDropped() && strings.EqualFold(string(tbl.Field[i].FieldName), name) {
- return i
- }
- }
- return -1
- }
- // fieldInfoOf GORM 字段 → 引擎列定义。显式 gorm:"type:..." 标签优先于 Go 类型默认
- // 映射 (报告 #10 的逃生门: string 字段默认无 size 会落成 Blob 链式列, 带索引时引擎
- // 拒绝建索引 —— 用 type:varchar(500)/type:bigint 精确声明列类型/宽度即可绕过)。
- func (m Migrator) fieldInfoOf(f *schema.Field) (types.FieldInfo, error) {
- fi := types.FieldInfo{FieldName: []byte(f.DBName)}
- // 可空: NotNull==false 且非主键 → 允许 NULL。主键列是行定位键, 绝不可空。
- if !f.NotNull && !f.PrimaryKey {
- fi.Flag |= types.FlagNullable
- }
- if tag, ok := f.TagSettings["TYPE"]; ok {
- if name, size, ok := explicitTypeMapping(tag); ok {
- switch name {
- case "BIGINT", "INT":
- // 显式整型声明: 符号随 Go 类型 (uint64 超出 int64 可表数域, 不能错配)。
- if f.DataType == schema.Uint {
- fi.FieldType = types.FieldType_Uint64
- } else {
- fi.FieldType = types.FieldType_Int64
- }
- case "DOUBLE":
- fi.FieldType = types.FieldType_Float64
- case "BOOLEAN":
- fi.FieldType = types.FieldType_Bool
- case "DATETIME":
- fi.FieldType = types.FieldType_DateTime
- case "TEXT":
- // 显式 type:text 与无 size string 同一归宿: Blob 链式列 (权衡见
- // stringFieldInfo) —— 动态列单值上限 16360, 不定长大文本运行时会撞墙。
- fi.FieldType = types.FieldType_Blob
- case "VARCHAR":
- if size <= 0 {
- // 显式 type:varchar 却没给宽度: 与 DataTypeOf 的注释同一口径,
- // 按动态列处理 (无长度语义的文本), 不落到下面的 Blob 默认。
- fi.FieldType = types.FieldType_Dynamicbyte
- return fi, nil
- }
- return stringFieldInfo(fi, f.DBName, size)
- }
- return fi, nil
- }
- // 未知 type: 落回默认映射 (与 DataTypeOf 的注释一致)。
- }
- switch f.DataType {
- case schema.Bool:
- fi.FieldType = types.FieldType_Bool
- case schema.Int:
- fi.FieldType = types.FieldType_Int64
- case schema.Uint:
- fi.FieldType = types.FieldType_Uint64
- case schema.Float:
- fi.FieldType = types.FieldType_Float64
- case schema.String:
- return stringFieldInfo(fi, f.DBName, f.Size)
- case schema.Time:
- fi.FieldType = types.FieldType_DateTime
- case schema.Bytes:
- fi.FieldType = types.FieldType_Blob
- default:
- // struct / JSON 等: 文本化存动态列
- fi.FieldType = types.FieldType_Dynamicbyte
- }
- return fi, nil
- }
- // stringFieldInfo string 字段按声明宽度落成引擎列: 无宽度 → Blob 链式列; 声明超行内
- // 定长上限 (types.MaxInlineColumnWidth = 2048) → 动态列; 其余定长 string 并写宽度码。
- // 宽度声明兑现不了 (引擎档位限制) 返回错误。
- //
- // 无宽度落 Blob 而不是旧行为的动态列: 动态列单值上限 types.MaxStringSize (16360 字节,
- // 内容整块落一个堆页), 不定长大文本写入超限即报错; Blob 列行内 8B BlobRef + 链页,
- // 单值约 4GB —— 这正是 migrate 对超宽 TEXT 源列的自动归宿。读回时驱动把链内容物化
- // 成 []byte, GORM 可扫进 string。代价: 该列失去 LIKE/索引能力 (行内只有引用)。
- func stringFieldInfo(fi types.FieldInfo, dbName string, size int) (types.FieldInfo, error) {
- if size < 0 {
- size = 0
- }
- switch {
- case size == 0:
- fi.FieldType = types.FieldType_Blob // 不定长大文本: 链式存储
- case size > types.MaxInlineColumnWidth:
- fi.FieldType = types.FieldType_Dynamicbyte // 声明超行内定长上限 → 动态列
- default:
- if err := types.CheckDeclaredStringSize(size); err != nil {
- return fi, fmt.Errorf("列 %q: %w", dbName, err)
- }
- fi.FieldType = types.FieldType_String
- fi.Flag |= types.WidthCodeFor(size)
- }
- return fi, nil
- }
- // isIntDataType 整型 (含 uint) GORM 字段 —— 主键索引只接受整型定位键。
- func isIntDataType(f *schema.Field) bool {
- return f.DataType == schema.Int || f.DataType == schema.Uint
- }
- // pkIndexName 行存储主键索引名 (与驱动 CREATE TABLE ... PRIMARY KEY 路径一致)。
- func pkIndexName(table string) string { return "pk_" + table }
- // uniqueIndexName 单列唯一索引名 (field.Unique 的承载)。
- func uniqueIndexName(table, col string) string { return "uk_" + table + "_" + col }
- // hasIndexIntent 字段是否声明了索引意图: index / uniqueIndex tag 或字段级 unique。
- // (无值标签经 GORM ParseTagSetting 会存成 "INDEX"/"UNIQUEINDEX" 字面值, 判空即覆盖
- // `gorm:"index"` 与 `gorm:"index:idx_x"` 两种写法。)
- func hasIndexIntent(f *schema.Field) bool {
- return f.Unique || f.TagSettings["INDEX"] != "" || f.TagSettings["UNIQUEINDEX"] != ""
- }
- // dynamicColumnErr 报告 #10 的友好错误: 字段带索引标签却落成不可索引的列时, 在建表
- // 阶段就给出可操作的提示, 而不是等建索引时被引擎顶回来。不可索引的列有两种:
- // 动态列 (行内堆引用) 与 Blob 链式列 (行内 8B 链引用) —— 无 size 的 string 字段
- // 现在是后者的默认归宿。字段不带索引意图、或落成可索引类型时返回 nil。
- func (m Migrator) dynamicColumnErr(f *schema.Field) error {
- if !hasIndexIntent(f) {
- return nil
- }
- fi, err := m.fieldInfoOf(f)
- if err != nil {
- return err
- }
- switch fi.FieldType {
- case types.FieldType_Dynamicbyte:
- return fmt.Errorf("字段 %s 带索引标签但会落成动态列(size>2048),无法建索引:请给该字段加 gorm:\"size:1..2048\"", f.Name)
- case types.FieldType_Blob:
- return fmt.Errorf("字段 %s 带索引标签但会落成 Blob 链式列(无 size 字符串字段的默认归宿),无法建索引:需要可搜索的文本请给该字段加 gorm:\"size:1..2048\" 走定长档", f.Name)
- }
- return nil
- }
- // indexTypeFor 单列 → B+ 树二级索引; 多列 → 复合索引。
- func indexTypeFor(fieldCnt int) page.IndexType {
- if fieldCnt > 1 {
- return page.IndexTypeCompose
- }
- return page.IndexTypeBTree
- }
- // createColumnIndex 按列下标在引擎表上建索引 (unique 透传)。
- func createColumnIndex(tbl *core.Table, name string, fieldIDs []byte, unique bool) error {
- if len(fieldIDs) == 0 {
- return fmt.Errorf("vrtx: 索引 %q 没有分量列", name)
- }
- _, err := tbl.CreateIndex(name, indexTypeFor(len(fieldIDs)), fieldIDs, unique)
- return err
- }
- // =============================================================================
- // AutoMigrate / Database
- // =============================================================================
- // AutoMigrate 首次建表 (含主键/唯一/普通索引), 已有表则补缺失的列与索引。
- // 引擎 AddField 不支持改列类型, 已存在列的类型微调一律放过 (宽松语义)。
- func (m Migrator) AutoMigrate(values ...interface{}) error {
- for _, value := range values {
- stmt, err := m.stmtFor(value)
- if err != nil {
- return err
- }
- if !m.HasTable(stmt.Table) {
- if err := m.CreateTable(value); err != nil {
- return err
- }
- continue
- }
- // 表已存在: 补列 (DBNames 排除 has-many 等关联字段 —— 它们没有列)
- for _, dbName := range stmt.Schema.DBNames {
- if !m.HasColumn(value, dbName) {
- if err := m.AddColumn(value, dbName); err != nil {
- return err
- }
- }
- }
- // 补索引: tag 声明的 (uniqueIndex/index) + 字段级 unique
- for _, idx := range stmt.Schema.ParseIndexes() {
- if m.HasIndex(value, idx.Name) {
- continue
- }
- if err := m.CreateIndex(value, idx.Name); err != nil {
- return err
- }
- }
- for _, f := range stmt.Schema.Fields {
- if !f.Unique || f.PrimaryKey {
- continue
- }
- // 报告 #10: 唯一约束落到动态列上同样在建索引前给友好错误。
- if err := m.dynamicColumnErr(f); err != nil {
- return fmt.Errorf("vrtx: 迁移 %q: %w", stmt.Table, err)
- }
- name := uniqueIndexName(stmt.Table, f.DBName)
- if m.HasIndex(value, name) {
- continue
- }
- tbl, err := m.findTable(stmt.Table)
- if err != nil {
- return err
- }
- if err := createColumnIndex(tbl, name, []byte{byte(tableFieldIndex(tbl, f.DBName))}, true); err != nil {
- return err
- }
- }
- }
- return nil
- }
- // CurrentDatabase 返回 DSN (引擎无 catalog 概念, 库=文件)。
- func (m Migrator) CurrentDatabase() string { return m.dsn }
- // FullDataTypeOf 纯类型名 (主键等约束在建表/建索引时表达, 不拼进类型)。
- func (m Migrator) FullDataTypeOf(field *schema.Field) clause.Expr {
- return clause.Expr{SQL: m.dialector.DataTypeOf(field)}
- }
- // GetTypeAliases 引擎无类型别名体系。
- func (m Migrator) GetTypeAliases(databaseTypeName string) []string { return nil }
- // =============================================================================
- // Tables
- // =============================================================================
- // CreateTable 对每个模型: 字段 → 引擎 CreateTable; 整型主键字段 → 行存储主键索引;
- // field.Unique → 唯一索引; uniqueIndex/index tag → 对应索引 (全部经引擎 API)。
- // 主键索引恰好建一次: 建前先查 GetRowStoreIndex (引擎 CreateTable 可能已自动建行存储,
- // 报告 #8 —— 不查会撞 ErrRowStoreExists "表上已存在行存储索引"); 行存储全表唯一, 复合
- // 主键只取声明的第一个整型字段; 唯一/普通索引循环对主键字段一律跳过。
- func (m Migrator) CreateTable(values ...interface{}) error {
- for _, value := range values {
- stmt, err := m.stmtFor(value)
- if err != nil {
- return err
- }
- if stmt.Schema == nil {
- return errors.New("vrtx: CreateTable 需要模型, 得不到 schema")
- }
- if m.HasTable(stmt.Table) {
- continue // 幂等: 已存在则跳过 (对齐 gorm 各方言的 IF NOT EXISTS 语义)
- }
- // 建表阶段先核一遍索引字段的可索引性 (报告 #10): 带 index/uniqueIndex/unique
- // 却落成动态列的字段, 在表创建前就给出可操作的错误, 而不是建索引时才被引擎的
- // "动态列不能作为索引键" 顶回来。
- for _, dbName := range stmt.Schema.DBNames {
- f := stmt.Schema.FieldsByDBName[dbName]
- if err := m.dynamicColumnErr(f); err != nil {
- return fmt.Errorf("vrtx: 建表 %q: %w", stmt.Table, err)
- }
- }
- infos := make([]types.FieldInfo, 0, len(stmt.Schema.DBNames))
- for _, dbName := range stmt.Schema.DBNames {
- fi, err := m.fieldInfoOf(stmt.Schema.FieldsByDBName[dbName])
- if err != nil {
- return fmt.Errorf("vrtx: 建表 %q: %w", stmt.Table, err)
- }
- infos = append(infos, fi)
- }
- tbl, err := m.eng.CreateTable(stmt.Table, infos)
- if err != nil {
- return fmt.Errorf("vrtx: 建表 %q: %w", stmt.Table, err)
- }
- // 整型主键 → 行存储主键索引 (引擎表的行定位键)。
- if _, hasRowStore := tbl.IndexMeta().GetRowStoreIndex(); !hasRowStore {
- for _, f := range stmt.Schema.Fields {
- if !f.PrimaryKey || !isIntDataType(f) {
- continue
- }
- idx := tableFieldIndex(tbl, f.DBName)
- if idx < 0 {
- return fmt.Errorf("vrtx: 建表 %q: 找不到主键列 %q", stmt.Table, f.DBName)
- }
- if _, err := tbl.CreateIndex(pkIndexName(stmt.Table), page.IndexTypePrimary, []byte{byte(idx)}); err != nil {
- return fmt.Errorf("vrtx: 建表 %q 主键索引: %w", stmt.Table, err)
- }
- break // 行存储全表唯一: 复合主键也只建行定位键这一份
- }
- }
- // 字段级 unique (主键已由行存储承载, 跳过)
- for _, f := range stmt.Schema.Fields {
- if !f.Unique || f.PrimaryKey || f.DBName == "" {
- continue
- }
- idx := tableFieldIndex(tbl, f.DBName)
- if idx < 0 {
- return fmt.Errorf("vrtx: 建表 %q: 找不到列 %q", stmt.Table, f.DBName)
- }
- if err := createColumnIndex(tbl, uniqueIndexName(stmt.Table, f.DBName), []byte{byte(idx)}, true); err != nil {
- return fmt.Errorf("vrtx: 建表 %q 唯一索引: %w", stmt.Table, err)
- }
- }
- // tag 声明的索引 (uniqueIndex / index, 含复合)
- for _, idx := range stmt.Schema.ParseIndexes() {
- // 分量全是主键列的索引定义直接跳过: 主键唯一性已由行存储承载, 对主键列再建
- // 二级索引纯属冗余 (primaryKey 与 uniqueIndex 标在同一字段时防止重复建)。
- allPK := len(idx.Fields) > 0
- for _, opt := range idx.Fields {
- if opt.Field == nil || !opt.Field.PrimaryKey {
- allPK = false
- break
- }
- }
- if allPK {
- continue
- }
- fieldIDs := make([]byte, 0, len(idx.Fields))
- for _, opt := range idx.Fields {
- if opt.Field == nil {
- return fmt.Errorf("vrtx: 建表 %q: 索引 %q 含表达式分量, 引擎不支持", stmt.Table, idx.Name)
- }
- // 复合索引里的无标签分量也要核可索引性 (它进了索引就该受索引约束)。
- if err := m.dynamicColumnErr(opt.Field); err != nil {
- return fmt.Errorf("vrtx: 建表 %q: %w", stmt.Table, err)
- }
- fi := tableFieldIndex(tbl, opt.Field.DBName)
- if fi < 0 {
- return fmt.Errorf("vrtx: 建表 %q: 索引 %q 引用了不存在的列 %q", stmt.Table, idx.Name, opt.Field.DBName)
- }
- fieldIDs = append(fieldIDs, byte(fi))
- }
- if err := createColumnIndex(tbl, idx.Name, fieldIDs, idx.Class == "UNIQUE"); err != nil {
- return fmt.Errorf("vrtx: 建表 %q 索引 %q: %w", stmt.Table, idx.Name, err)
- }
- }
- }
- return nil
- }
- // DropTable 逐表删除。
- func (m Migrator) DropTable(values ...interface{}) error {
- for _, value := range values {
- stmt, err := m.stmtFor(value)
- if err != nil {
- return err
- }
- if err := m.eng.DropTable(stmt.Table); err != nil {
- return fmt.Errorf("vrtx: 删表 %q: %w", stmt.Table, err)
- }
- }
- return nil
- }
- // HasTable 表存在性。
- func (m Migrator) HasTable(value interface{}) bool {
- stmt, err := m.stmtFor(value)
- if err != nil {
- return false
- }
- _, ok := m.eng.FindTable(stmt.Table)
- return ok
- }
- // RenameTable 调 core.DB.RenameTable (DDL 隐式提交)。
- // oldName/newName 支持 string 或实现 schema.Tabler 的模型。
- func (m Migrator) RenameTable(oldName, newName interface{}) error {
- oldTable := tableNameOf(oldName)
- if oldTable == "" {
- return fmt.Errorf("vrtx: RenameTable 无法解析 oldName")
- }
- newTable := tableNameOf(newName)
- if newTable == "" {
- return fmt.Errorf("vrtx: RenameTable 无法解析 newName")
- }
- if err := m.eng.RenameTable(oldTable, newTable); err != nil {
- return fmt.Errorf("vrtx: 重命名表 %q → %q: %w", oldTable, newTable, err)
- }
- return nil
- }
- // tableNameOf 把 GORM RenameTable 参数 (string / schema.Tabler) 解析成表名字符串。
- func tableNameOf(v interface{}) string {
- switch x := v.(type) {
- case string:
- return x
- case schema.Tabler:
- return x.TableName()
- }
- return ""
- }
- // GetTables 列出全部表名。
- func (m Migrator) GetTables() ([]string, error) {
- metas := m.eng.GetAllTables()
- names := make([]string, 0, len(metas))
- for _, meta := range metas {
- names = append(names, meta.Name)
- }
- return names, nil
- }
- // TableType 引擎无表类型体系 (无临时表/外部表之分)。
- func (m Migrator) TableType(dst interface{}) (gorm.TableType, error) {
- return nil, errNotSupported("TableType")
- }
- // =============================================================================
- // Columns
- // =============================================================================
- // AddColumn 往已有表加一列 (dst=模型, field=列名/DBName)。
- func (m Migrator) AddColumn(value interface{}, field string) error {
- stmt, err := m.stmtFor(value)
- if err != nil {
- return err
- }
- tbl, err := m.findTable(stmt.Table)
- if err != nil {
- return err
- }
- f := stmt.Schema.LookUpField(field)
- if f == nil {
- return fmt.Errorf("vrtx: 模型上没有列 %q", field)
- }
- fi, err := m.fieldInfoOf(f)
- if err != nil {
- return fmt.Errorf("vrtx: 加列 %q.%q: %w", stmt.Table, field, err)
- }
- if err := tbl.AddField(fi); err != nil {
- return fmt.Errorf("vrtx: 加列 %q.%q: %w", stmt.Table, field, err)
- }
- return nil
- }
- // DropColumn 软删一列 (引擎 master registry 保留, 旧行不受影响)。
- func (m Migrator) DropColumn(value interface{}, field string) error {
- stmt, err := m.stmtFor(value)
- if err != nil {
- return err
- }
- tbl, err := m.findTable(stmt.Table)
- if err != nil {
- return err
- }
- if err := tbl.DropField(field); err != nil {
- return fmt.Errorf("vrtx: 删列 %q.%q: %w", stmt.Table, field, err)
- }
- return nil
- }
- // AlterColumn 引擎 AddField 不支持改列类型/宽度, 明确报错。
- func (m Migrator) AlterColumn(value interface{}, field string) error {
- return errNotSupported("AlterColumn (引擎不支持修改列定义)")
- }
- // MigrateColumn 宽松实现: 列已存在即视为一致 (引擎不支持类型微调, 报错没有意义)。
- func (m Migrator) MigrateColumn(value interface{}, field *schema.Field, columnType gorm.ColumnType) error {
- return nil
- }
- // MigrateColumnUnique 同上: 唯一性由唯一索引承载, 列级核对一律放过。
- func (m Migrator) MigrateColumnUnique(value interface{}, field *schema.Field, columnType gorm.ColumnType) error {
- return nil
- }
- // HasColumn 遍历引擎字段按名匹配 (大小写不敏感)。
- func (m Migrator) HasColumn(value interface{}, field string) bool {
- stmt, err := m.stmtFor(value)
- if err != nil {
- return false
- }
- tbl, err := m.findTable(stmt.Table)
- if err != nil {
- return false
- }
- return tableFieldIndex(tbl, field) >= 0
- }
- // RenameColumn 调 core.DB.RenameColumn (DDL 隐式提交)。
- func (m Migrator) RenameColumn(value interface{}, oldName, field string) error {
- stmt, err := m.stmtFor(value)
- if err != nil {
- return err
- }
- if err := m.eng.RenameColumn(stmt.Table, oldName, field); err != nil {
- return fmt.Errorf("vrtx: 重命名列 %q.%q → %q: %w", stmt.Table, oldName, field, err)
- }
- return nil
- }
- // ColumnTypes 从引擎元数据构造列类型列表 (供高级用法)。
- func (m Migrator) ColumnTypes(value interface{}) ([]gorm.ColumnType, error) {
- stmt, err := m.stmtFor(value)
- if err != nil {
- return nil, err
- }
- tbl, err := m.findTable(stmt.Table)
- if err != nil {
- return nil, err
- }
- rowStore, hasRowStore := tbl.IndexMeta().GetRowStoreIndex()
- cols := make([]gorm.ColumnType, 0, len(tbl.Field))
- for i := range tbl.Field {
- fi := &tbl.Field[i]
- if fi.IsDropped() {
- continue
- }
- param := fi.Flag & types.FlagScaleMask
- ct := &columnType{
- name: string(fi.FieldName),
- dbTypeName: types.TypeNameOf(fi.FieldType),
- nullable: fi.IsNullable(),
- scanType: scanTypeOf(fi.FieldType),
- }
- if fi.FieldType == types.FieldType_String {
- ct.length = int64(types.FieldSize(fi.FieldType, param))
- ct.hasLength = true
- }
- if fi.FieldType == types.FieldType_Decimal {
- ct.scale = int64(param)
- ct.hasScale = true
- }
- if hasRowStore {
- for _, fid := range rowStore.Fields() {
- if int(fid) == i {
- ct.primaryKey = true
- break
- }
- }
- }
- cols = append(cols, ct)
- }
- return cols, nil
- }
- // columnType 实现 gorm.ColumnType 接口 (一套小结构体, 值全部来自引擎元数据)。
- type columnType struct {
- name string
- dbTypeName string
- nullable bool
- primaryKey bool
- unique bool
- length int64
- hasLength bool
- scale int64
- hasScale bool
- scanType reflect.Type
- }
- func (c *columnType) Name() string { return c.name }
- func (c *columnType) DatabaseTypeName() string { return c.dbTypeName }
- func (c *columnType) ColumnType() (string, bool) {
- if c.hasLength {
- return fmt.Sprintf("%s(%d)", c.dbTypeName, c.length), true
- }
- if c.hasScale {
- return fmt.Sprintf("%s(38,%d)", c.dbTypeName, c.scale), true
- }
- return c.dbTypeName, true
- }
- func (c *columnType) PrimaryKey() (bool, bool) { return c.primaryKey, true }
- func (c *columnType) AutoIncrement() (bool, bool) { return c.primaryKey, true } // 主键镜像列由引擎自增
- func (c *columnType) Length() (int64, bool) { return c.length, c.hasLength }
- func (c *columnType) DecimalSize() (int64, int64, bool) {
- return 38, c.scale, c.hasScale
- }
- func (c *columnType) Nullable() (bool, bool) { return c.nullable, true }
- func (c *columnType) Unique() (bool, bool) { return c.unique, c.unique }
- func (c *columnType) ScanType() reflect.Type { return c.scanType }
- func (c *columnType) Comment() (string, bool) {
- return "", false // 引擎无列注释 (Comment 是库级介绍区)
- }
- func (c *columnType) DefaultValue() (string, bool) { return "", false }
- // scanTypeOf 引擎列类型 → Go 扫描类型。
- func scanTypeOf(ft types.FieldType) reflect.Type {
- switch ft {
- case types.FieldType_Bool:
- return reflect.TypeOf(false)
- case types.FieldType_Int8, types.FieldType_Int16, types.FieldType_Int32, types.FieldType_Int64,
- types.FieldType_Uint8, types.FieldType_Uint16, types.FieldType_Uint32, types.FieldType_Uint64,
- types.FieldType_Year:
- return reflect.TypeOf(int64(0))
- case types.FieldType_Float32, types.FieldType_Float64:
- return reflect.TypeOf(float64(0))
- case types.FieldType_DateTime:
- return reflect.TypeOf(time.Time{})
- case types.FieldType_Blob, types.FieldType_Dynamicbyte:
- return reflect.TypeOf([]byte(nil))
- default: // String / Decimal / UUID / Int128 等文本化输出
- return reflect.TypeOf("")
- }
- }
- // =============================================================================
- // Views —— 引擎无视图
- // =============================================================================
- func (m Migrator) CreateView(name string, option gorm.ViewOption) error {
- return errNotSupported("CreateView")
- }
- func (m Migrator) DropView(name string) error {
- return errNotSupported("DropView")
- }
- // =============================================================================
- // Constraints —— 引擎无约束体系 (唯一性由唯一索引承载)
- // =============================================================================
- func (m Migrator) CreateConstraint(value interface{}, name string) error {
- return errNotSupported("CreateConstraint (引擎无约束体系, 唯一性请用 uniqueIndex)")
- }
- func (m Migrator) DropConstraint(value interface{}, name string) error {
- return errNotSupported("DropConstraint")
- }
- func (m Migrator) HasConstraint(value interface{}, name string) bool { return false }
- // =============================================================================
- // Indexes
- // =============================================================================
- // CreateIndex 按模型的 uniqueIndex/index tag 解析列, 建引擎索引。
- func (m Migrator) CreateIndex(value interface{}, name string) error {
- stmt, err := m.stmtFor(value)
- if err != nil {
- return err
- }
- if stmt.Schema == nil {
- return errors.New("vrtx: CreateIndex 需要模型, 得不到 schema")
- }
- idx := stmt.Schema.LookIndex(name)
- if idx == nil {
- return fmt.Errorf("vrtx: 模型 %q 上没有索引 %q", stmt.Table, name)
- }
- tbl, err := m.findTable(stmt.Table)
- if err != nil {
- return err
- }
- fieldIDs := make([]byte, 0, len(idx.Fields))
- for _, opt := range idx.Fields {
- if opt.Field == nil {
- return fmt.Errorf("vrtx: 索引 %q 含表达式分量, 引擎不支持", name)
- }
- // 报告 #10: 分量落成动态列时给友好错误, 而不是引擎的 "动态列不能作为索引键"。
- if err := m.dynamicColumnErr(opt.Field); err != nil {
- return fmt.Errorf("vrtx: 建索引 %q: %w", name, err)
- }
- fi := tableFieldIndex(tbl, opt.Field.DBName)
- if fi < 0 {
- return fmt.Errorf("vrtx: 索引 %q 引用了不存在的列 %q", name, opt.Field.DBName)
- }
- fieldIDs = append(fieldIDs, byte(fi))
- }
- if err := createColumnIndex(tbl, idx.Name, fieldIDs, idx.Class == "UNIQUE"); err != nil {
- return fmt.Errorf("vrtx: 建索引 %q.%q: %w", stmt.Table, name, err)
- }
- return nil
- }
- // DropIndex 按名删索引。
- func (m Migrator) DropIndex(value interface{}, name string) error {
- stmt, err := m.stmtFor(value)
- if err != nil {
- return err
- }
- tbl, err := m.findTable(stmt.Table)
- if err != nil {
- return err
- }
- if err := tbl.DropIndex(name); err != nil {
- return fmt.Errorf("vrtx: 删索引 %q.%q: %w", stmt.Table, name, err)
- }
- return nil
- }
- // HasIndex 枚举引擎索引元数据按名匹配 (大小写不敏感)。
- func (m Migrator) HasIndex(value interface{}, name string) bool {
- stmt, err := m.stmtFor(value)
- if err != nil {
- return false
- }
- tbl, err := m.findTable(stmt.Table)
- if err != nil {
- return false
- }
- for _, meta := range tbl.IndexMeta().GetIndexes() {
- if strings.EqualFold(meta.Name(), name) {
- return true
- }
- }
- return false
- }
- // RenameIndex 调 core.Table.RenameIndex (DDL 隐式提交)。
- func (m Migrator) RenameIndex(value interface{}, oldName, newName string) error {
- stmt, err := m.stmtFor(value)
- if err != nil {
- return err
- }
- tbl, err := m.findTable(stmt.Table)
- if err != nil {
- return err
- }
- if err := tbl.RenameIndex(oldName, newName); err != nil {
- return fmt.Errorf("vrtx: 重命名索引 %q.%q → %q: %w", stmt.Table, oldName, newName, err)
- }
- return nil
- }
- // GetIndexes 包装引擎索引元数据, 能返尽返。
- func (m Migrator) GetIndexes(value interface{}) ([]gorm.Index, error) {
- stmt, err := m.stmtFor(value)
- if err != nil {
- return nil, err
- }
- tbl, err := m.findTable(stmt.Table)
- if err != nil {
- return nil, err
- }
- metas := tbl.IndexMeta().GetIndexes()
- out := make([]gorm.Index, 0, len(metas))
- for _, meta := range metas {
- out = append(out, indexMeta{table: stmt.Table, meta: meta, tbl: tbl})
- }
- return out, nil
- }
- // indexMeta 把引擎索引元数据适配成 gorm.Index。
- type indexMeta struct {
- table string
- meta core.IndexMeta
- tbl *core.Table
- }
- func (i indexMeta) Table() string { return i.table }
- func (i indexMeta) Name() string { return i.meta.Name() }
- func (i indexMeta) Columns() []string {
- ids := i.meta.Fields()
- cols := make([]string, 0, len(ids))
- for _, id := range ids {
- if int(id) >= 0 && int(id) < len(i.tbl.Field) {
- cols = append(cols, string(i.tbl.Field[id].FieldName))
- }
- }
- return cols
- }
- func (i indexMeta) PrimaryKey() (bool, bool) {
- return i.meta.IndexType == page.IndexTypePrimary, true
- }
- func (i indexMeta) Unique() (bool, bool) { return i.meta.Unique(), true }
- func (i indexMeta) Option() string { return "" }
|