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  }