github.com/ecodeclub/eorm@v0.0.2-0.20231001112437-dae71da914d0/internal/model/model.go (about) 1 // Copyright 2021 ecodeclub 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package model 16 17 import ( 18 "reflect" 19 "strings" 20 "sync" 21 22 "github.com/ecodeclub/eorm/internal/sharding" 23 24 "github.com/ecodeclub/eorm/internal/errs" 25 26 // nolint 27 "unicode" 28 ) 29 30 // TableMeta represents data model, or a table 31 type TableMeta struct { 32 TableName string 33 Columns []*ColumnMeta 34 // FieldMap 是字段名到列元数据的映射 35 FieldMap map[string]*ColumnMeta 36 // ColumnMap 是列名到列元数据的映射 37 ColumnMap map[string]*ColumnMeta 38 Typ reflect.Type 39 40 ShardingAlgorithm sharding.Algorithm 41 } 42 43 // ColumnMeta represents model's field, or column 44 type ColumnMeta struct { 45 ColumnName string 46 FieldName string 47 Typ reflect.Type 48 IsPrimaryKey bool 49 // Offset 是字段偏移量。需要注意的是,这里的字段偏移量是相对于整个结构体的偏移量 50 // 例如在组合的情况下, 51 // type A struct { 52 // name string 53 // B 54 // } 55 // type B struct { 56 // age int 57 // } 58 // age 的偏移量是相对于 A 的起始地址的偏移量 59 Offset uintptr 60 // FieldIndexes 用于表达从最外层结构体找到当前ColumnMeta对应的Field所需要的索引集 61 FieldIndexes []int 62 } 63 64 // TableMetaOption represents options of TableMeta, this options will cover default cover. 65 type TableMetaOption func(meta *TableMeta) 66 67 func WithTableShardingAlgorithm(algorithm sharding.Algorithm) TableMetaOption { 68 return func(meta *TableMeta) { 69 meta.ShardingAlgorithm = algorithm 70 } 71 } 72 73 // MetaRegistry stores table metadata 74 type MetaRegistry interface { 75 Get(table interface{}) (*TableMeta, error) 76 Register(table interface{}, opts ...TableMetaOption) (*TableMeta, error) 77 } 78 79 func NewMetaRegistry() MetaRegistry { 80 return &tagMetaRegistry{} 81 } 82 83 // tagMetaRegistry is the default implementation based on tag eorm 84 type tagMetaRegistry struct { 85 metas sync.Map 86 } 87 88 func NewTagMetaRegistry() MetaRegistry { 89 return &tagMetaRegistry{} 90 } 91 92 // Get the metadata for each column of the data table, 93 // If there is none, it will register one and return the metadata for each column 94 func (t *tagMetaRegistry) Get(table interface{}) (*TableMeta, error) { 95 if v, ok := t.metas.Load(reflect.TypeOf(table)); ok { 96 return v.(*TableMeta), nil 97 } 98 return t.Register(table) 99 } 100 101 // Register function generates a metadata for each column and places it in a thread-safe mapping to facilitate direct access to the metadata. 102 // And the metadata can be modified by user-defined methods opts 103 func (t *tagMetaRegistry) Register(table interface{}, opts ...TableMetaOption) (*TableMeta, error) { 104 rtype := reflect.TypeOf(table) 105 if rtype.Kind() != reflect.Ptr || rtype.Elem().Kind() != reflect.Struct { 106 return nil, errs.ErrPointerOnly 107 } 108 v := rtype.Elem() 109 lens := v.NumField() 110 columnMetas := make([]*ColumnMeta, 0, lens) 111 fieldMap := make(map[string]*ColumnMeta, lens) 112 columnMap := make(map[string]*ColumnMeta, lens) 113 err := t.parseFields(v, []int{}, &columnMetas, fieldMap, 0) 114 if err != nil { 115 return nil, err 116 } 117 118 for _, columnMeta := range columnMetas { 119 columnMap[columnMeta.ColumnName] = columnMeta 120 } 121 122 tableMeta := &TableMeta{ 123 Columns: columnMetas, 124 TableName: underscoreName(v.Name()), 125 Typ: rtype, 126 FieldMap: fieldMap, 127 ColumnMap: columnMap, 128 } 129 for _, o := range opts { 130 o(tableMeta) 131 } 132 133 t.metas.Store(rtype, tableMeta) 134 return tableMeta, nil 135 136 } 137 138 func (t *tagMetaRegistry) parseFields(v reflect.Type, fieldIndexes []int, 139 columnMetas *[]*ColumnMeta, fieldMap map[string]*ColumnMeta, 140 pOffset uintptr) error { 141 lens := v.NumField() 142 for i := 0; i < lens; i++ { 143 structField := v.Field(i) 144 tag := structField.Tag.Get("eorm") 145 var isKey, isIgnore bool 146 for _, t := range strings.Split(tag, ",") { 147 switch t { 148 case "primary_key": 149 isKey = true 150 case "-": 151 isIgnore = true 152 } 153 } 154 if isIgnore { 155 // skip the field. 156 continue 157 } 158 // 检查列有没有冲突 159 if fieldMap[structField.Name] != nil { 160 return errs.NewFieldConflictError(v.Name() + "." + structField.Name) 161 } 162 // 是组合 163 if structField.Anonymous { 164 // 不支持使用指针的组合 165 if structField.Type.Kind() != reflect.Struct { 166 return errs.ErrCombinationIsNotStruct 167 } 168 // 递归解析 169 o := structField.Offset + pOffset 170 err := t.parseFields(structField.Type, append(fieldIndexes, i), columnMetas, fieldMap, o) 171 if err != nil { 172 return err 173 } 174 continue 175 } 176 177 columnMeta := &ColumnMeta{ 178 ColumnName: underscoreName(structField.Name), 179 FieldName: structField.Name, 180 Typ: structField.Type, 181 IsPrimaryKey: isKey, 182 Offset: structField.Offset + pOffset, 183 FieldIndexes: append(fieldIndexes, i), 184 } 185 *columnMetas = append(*columnMetas, columnMeta) 186 fieldMap[columnMeta.FieldName] = columnMeta 187 } 188 return nil 189 } 190 191 // IgnoreFieldsOption function provide an option to ignore some fields when register table. 192 func IgnoreFieldsOption(fieldNames ...string) TableMetaOption { 193 return func(meta *TableMeta) { 194 for _, field := range fieldNames { 195 // has field in the TableMeta 196 if _, ok := meta.FieldMap[field]; ok { 197 // delete field in columns slice 198 for index, column := range meta.Columns { 199 if column.FieldName == field { 200 meta.Columns = append(meta.Columns[:index], meta.Columns[index+1:]...) 201 break 202 } 203 } 204 // delete field in fieldMap 205 delete(meta.FieldMap, field) 206 } 207 } 208 } 209 } 210 211 // underscoreName function mainly converts upper case to lower case and adds an underscore in between 212 func underscoreName(tableName string) string { 213 var buf []byte 214 for i, v := range tableName { 215 if unicode.IsUpper(v) { 216 if i != 0 { 217 buf = append(buf, '_') 218 } 219 buf = append(buf, byte(unicode.ToLower(v))) 220 } else { 221 buf = append(buf, byte(v)) 222 } 223 224 } 225 return string(buf) 226 }