package DB import ( "TsDB2/DB/core/page" "TsDB2/DB/core/types" "fmt" "reflect" ) // FieldDef ORM 字段定义 type FieldDef struct { Name string // Go 字段名 ColName string // 数据库列名 (来自 tsdb tag 或字段名) FieldType types.FieldType // 存储类型 GoKind reflect.Kind // Go 原始类型 Index int // 在结构体中的字段索引 } // ModelInfo 模型元信息 type ModelInfo struct { Fields []FieldDef // 用户定义字段 (不含自动主键) AllFields []FieldDef // 全部字段 (含自动主键,按存储顺序) PKField string // 主键列名 ("_id" 或 "_ts") PKIndex int // 主键在 AllFields 中的索引 IsTimeSeries bool // 是否时序表 CoreFields []types.FieldInfo // 传给 core 的字段列表 (含自动主键) CorePKField byte // 主键在 CoreFields 中的索引 (用于 CreateIndex) StructType reflect.Type // 结构体类型 (用于反射) PKInStruct bool // 主键字段是否在结构体中 (嵌入基类时有) } // goKindToFieldType Go 类型到数据库字段类型的默认映射 var goKindToFieldType = map[reflect.Kind]types.FieldType{ reflect.Int8: types.FieldType_1byte, reflect.Int16: types.FieldType_2byte, reflect.Int32: types.FieldType_4byte, reflect.Int64: types.FieldType_8byte, reflect.Int: types.FieldType_8byte, reflect.Uint8: types.FieldType_1byte, reflect.Uint16: types.FieldType_2byte, reflect.Uint32: types.FieldType_4byte, reflect.Uint64: types.FieldType_8byte, reflect.Float32: types.FieldType_4byte, reflect.Float64: types.FieldType_8byte, reflect.Bool: types.FieldType_1byte, } // tagToFieldType 结构体标签到数据库字段类型的映射 var tagToFieldType = map[string]types.FieldType{ "1byte": types.FieldType_1byte, "2byte": types.FieldType_2byte, "4byte": types.FieldType_4byte, "8byte": types.FieldType_8byte, "16byte": types.FieldType_16byte, "32byte": types.FieldType_32byte, "64byte": types.FieldType_64byte, "128byte": types.FieldType_128byte, "256byte": types.FieldType_256byte, "dynamic": types.FieldType_Dynamicbyte, } var ( baseModelType = reflect.TypeOf(BaseModel{}) timeSeriesModelType = reflect.TypeOf(TimeSeriesModel{}) ) // ParseModel 解析结构体,提取模型信息 // obj 必须是指向结构体的指针 (可以是零值) func ParseModel(obj interface{}) (*ModelInfo, error) { t := reflect.TypeOf(obj) if t.Kind() == reflect.Ptr { t = t.Elem() } if t.Kind() != reflect.Struct { return nil, fmt.Errorf("tsdb: obj must be a struct, got %s", t.Kind()) } info := &ModelInfo{ StructType: t, PKField: "_id", } // 扫描字段,检测嵌入的基类 fieldIdx := 0 for i := 0; i < t.NumField(); i++ { sf := t.Field(i) // 处理嵌入结构体 if sf.Anonymous { if sf.Type == baseModelType { info.IsTimeSeries = false info.PKField = "_id" info.PKInStruct = true // 添加 _id 主键列 info.AllFields = append(info.AllFields, FieldDef{ Name: "ID", ColName: "_id", FieldType: types.FieldType_8byte, GoKind: reflect.Uint64, Index: len(info.AllFields), }) info.PKIndex = len(info.AllFields) - 1 continue } if sf.Type == timeSeriesModelType { info.IsTimeSeries = true info.PKField = "_ts" info.PKInStruct = true // 添加 _ts 主键列 info.AllFields = append(info.AllFields, FieldDef{ Name: "TS", ColName: "_ts", FieldType: types.FieldType_8byte, GoKind: reflect.Uint64, Index: len(info.AllFields), }) info.PKIndex = len(info.AllFields) - 1 continue } // 其他嵌入结构体: 递归展开 if sf.Type.Kind() == reflect.Struct { if err := parseEmbeddedFields(sf.Type, info, &fieldIdx); err != nil { return nil, err } continue } continue } // 普通字段 fd, err := parseField(sf, fieldIdx) if err != nil { return nil, fmt.Errorf("field %s: %w", sf.Name, err) } info.Fields = append(info.Fields, fd) info.AllFields = append(info.AllFields, fd) fieldIdx++ } // 如果没有嵌入任何基类,默认添加 _id 主键 if len(info.AllFields) == 0 || (info.PKField != "_id" && info.PKField != "_ts") { info.PKField = "_id" info.IsTimeSeries = false info.PKInStruct = false pk := FieldDef{ Name: "ID", ColName: "_id", FieldType: types.FieldType_8byte, GoKind: reflect.Uint64, Index: 0, } // 将 PK 插入到 AllFields 开头 newAll := make([]FieldDef, 0, len(info.AllFields)+1) newAll = append(newAll, pk) for j, f := range info.AllFields { f.Index = j + 1 newAll = append(newAll, f) } for j := range info.Fields { info.Fields[j].Index++ } info.AllFields = newAll info.PKIndex = 0 } // 构建 CoreFields info.CoreFields = make([]types.FieldInfo, len(info.AllFields)) for i, f := range info.AllFields { info.CoreFields[i] = types.FieldInfo{ FieldType: f.FieldType, FieldName: []byte(f.ColName), } } info.CorePKField = byte(info.PKIndex) return info, nil } // parseEmbeddedFields 递归解析嵌入结构体的字段 func parseEmbeddedFields(t reflect.Type, info *ModelInfo, fieldIdx *int) error { for i := 0; i < t.NumField(); i++ { sf := t.Field(i) if sf.Anonymous && sf.Type.Kind() == reflect.Struct { if err := parseEmbeddedFields(sf.Type, info, fieldIdx); err != nil { return err } continue } fd, err := parseField(sf, *fieldIdx) if err != nil { return err } info.Fields = append(info.Fields, fd) info.AllFields = append(info.AllFields, fd) *fieldIdx++ } return nil } // parseField 解析单个结构体字段 func parseField(sf reflect.StructField, idx int) (FieldDef, error) { fd := FieldDef{ Name: sf.Name, GoKind: sf.Type.Kind(), Index: idx, } // 列名: 优先使用 tsdb tag 中的列名 fd.ColName = sf.Name if tag, ok := sf.Tag.Lookup("tsdb"); ok && tag != "" { // tag 格式: "列名" 或 "列名,类型" 或 "类型" parts := parseTag(tag) if len(parts) >= 1 && parts[0] != "" { // 如果第一部分不是已知类型名,则视为列名 if _, isType := tagToFieldType[parts[0]]; !isType { fd.ColName = parts[0] } } } // 字段类型: 优先使用 tsdb tag if tag, ok := sf.Tag.Lookup("tsdb"); ok && tag != "" { parts := parseTag(tag) for _, p := range parts { if ft, isType := tagToFieldType[p]; isType { fd.FieldType = ft return fd, nil } } } // 自动推断 if ft, ok := goKindToFieldType[sf.Type.Kind()]; ok { fd.FieldType = ft return fd, nil } // string → 256byte if sf.Type.Kind() == reflect.String { fd.FieldType = types.FieldType_256byte return fd, nil } // []byte → dynamic if sf.Type.Kind() == reflect.Slice && sf.Type.Elem().Kind() == reflect.Uint8 { fd.FieldType = types.FieldType_Dynamicbyte return fd, nil } return fd, fmt.Errorf("unsupported type: %s", sf.Type.String()) } // parseTag 解析 tsdb tag: "col_name,type" 或 "type" 或 "col_name" func parseTag(tag string) []string { var parts []string current := "" for _, c := range tag { if c == ',' { parts = append(parts, current) current = "" } else { current += string(c) } } if current != "" { parts = append(parts, current) } return parts } // GetIndexType 返回该模型应创建的一级索引类型 func (m *ModelInfo) GetIndexType() page.IndexType { if m.IsTimeSeries { return page.IndexTypeTime } return page.IndexTypePrimary }