|
|
- // Copyright 2020 The Xorm Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
-
- package tags
-
- import (
- "encoding/gob"
- "errors"
- "fmt"
- "reflect"
- "strings"
- "sync"
- "time"
-
- "xorm.io/xorm/caches"
- "xorm.io/xorm/convert"
- "xorm.io/xorm/dialects"
- "xorm.io/xorm/names"
- "xorm.io/xorm/schemas"
- )
-
- var (
- ErrUnsupportedType = errors.New("Unsupported type")
- )
-
- type Parser struct {
- identifier string
- dialect dialects.Dialect
- columnMapper names.Mapper
- tableMapper names.Mapper
- handlers map[string]Handler
- cacherMgr *caches.Manager
- tableCache sync.Map // map[reflect.Type]*schemas.Table
- }
-
- func NewParser(identifier string, dialect dialects.Dialect, tableMapper, columnMapper names.Mapper, cacherMgr *caches.Manager) *Parser {
- return &Parser{
- identifier: identifier,
- dialect: dialect,
- tableMapper: tableMapper,
- columnMapper: columnMapper,
- handlers: defaultTagHandlers,
- cacherMgr: cacherMgr,
- }
- }
-
- func (parser *Parser) GetTableMapper() names.Mapper {
- return parser.tableMapper
- }
-
- func (parser *Parser) SetTableMapper(mapper names.Mapper) {
- parser.ClearCaches()
- parser.tableMapper = mapper
- }
-
- func (parser *Parser) GetColumnMapper() names.Mapper {
- return parser.columnMapper
- }
-
- func (parser *Parser) SetColumnMapper(mapper names.Mapper) {
- parser.ClearCaches()
- parser.columnMapper = mapper
- }
-
- func (parser *Parser) ParseWithCache(v reflect.Value) (*schemas.Table, error) {
- t := v.Type()
- tableI, ok := parser.tableCache.Load(t)
- if ok {
- return tableI.(*schemas.Table), nil
- }
-
- table, err := parser.Parse(v)
- if err != nil {
- return nil, err
- }
-
- parser.tableCache.Store(t, table)
-
- if parser.cacherMgr.GetDefaultCacher() != nil {
- if v.CanAddr() {
- gob.Register(v.Addr().Interface())
- } else {
- gob.Register(v.Interface())
- }
- }
-
- return table, nil
- }
-
- // ClearCacheTable removes the database mapper of a type from the cache
- func (parser *Parser) ClearCacheTable(t reflect.Type) {
- parser.tableCache.Delete(t)
- }
-
- // ClearCaches removes all the cached table information parsed by structs
- func (parser *Parser) ClearCaches() {
- parser.tableCache = sync.Map{}
- }
-
- func addIndex(indexName string, table *schemas.Table, col *schemas.Column, indexType int) {
- if index, ok := table.Indexes[indexName]; ok {
- index.AddColumn(col.Name)
- col.Indexes[index.Name] = indexType
- } else {
- index := schemas.NewIndex(indexName, indexType)
- index.AddColumn(col.Name)
- table.AddIndex(index)
- col.Indexes[index.Name] = indexType
- }
- }
-
- // Parse parses a struct as a table information
- func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) {
- t := v.Type()
- if t.Kind() == reflect.Ptr {
- t = t.Elem()
- v = v.Elem()
- }
- if t.Kind() != reflect.Struct {
- return nil, ErrUnsupportedType
- }
-
- table := schemas.NewEmptyTable()
- table.Type = t
- table.Name = names.GetTableName(parser.tableMapper, v)
-
- var idFieldColName string
- var hasCacheTag, hasNoCacheTag bool
-
- for i := 0; i < t.NumField(); i++ {
- tag := t.Field(i).Tag
-
- ormTagStr := tag.Get(parser.identifier)
- var col *schemas.Column
- fieldValue := v.Field(i)
- fieldType := fieldValue.Type()
-
- if ormTagStr != "" {
- col = &schemas.Column{
- FieldName: t.Field(i).Name,
- Nullable: true,
- IsPrimaryKey: false,
- IsAutoIncrement: false,
- MapType: schemas.TWOSIDES,
- Indexes: make(map[string]int),
- DefaultIsEmpty: true,
- }
- tags := splitTag(ormTagStr)
-
- if len(tags) > 0 {
- if tags[0] == "-" {
- continue
- }
-
- var ctx = Context{
- table: table,
- col: col,
- fieldValue: fieldValue,
- indexNames: make(map[string]int),
- parser: parser,
- }
-
- if strings.HasPrefix(strings.ToUpper(tags[0]), "EXTENDS") {
- pStart := strings.Index(tags[0], "(")
- if pStart > -1 && strings.HasSuffix(tags[0], ")") {
- var tagPrefix = strings.TrimFunc(tags[0][pStart+1:len(tags[0])-1], func(r rune) bool {
- return r == '\'' || r == '"'
- })
-
- ctx.params = []string{tagPrefix}
- }
-
- if err := ExtendsTagHandler(&ctx); err != nil {
- return nil, err
- }
- continue
- }
-
- for j, key := range tags {
- if ctx.ignoreNext {
- ctx.ignoreNext = false
- continue
- }
-
- k := strings.ToUpper(key)
- ctx.tagName = k
- ctx.params = []string{}
-
- pStart := strings.Index(k, "(")
- if pStart == 0 {
- return nil, errors.New("( could not be the first character")
- }
- if pStart > -1 {
- if !strings.HasSuffix(k, ")") {
- return nil, fmt.Errorf("field %s tag %s cannot match ) character", col.FieldName, key)
- }
-
- ctx.tagName = k[:pStart]
- ctx.params = strings.Split(key[pStart+1:len(k)-1], ",")
- }
-
- if j > 0 {
- ctx.preTag = strings.ToUpper(tags[j-1])
- }
- if j < len(tags)-1 {
- ctx.nextTag = tags[j+1]
- } else {
- ctx.nextTag = ""
- }
-
- if h, ok := parser.handlers[ctx.tagName]; ok {
- if err := h(&ctx); err != nil {
- return nil, err
- }
- } else {
- if strings.HasPrefix(key, "'") && strings.HasSuffix(key, "'") {
- col.Name = key[1 : len(key)-1]
- } else {
- col.Name = key
- }
- }
-
- if ctx.hasCacheTag {
- hasCacheTag = true
- }
- if ctx.hasNoCacheTag {
- hasNoCacheTag = true
- }
- }
-
- if col.SQLType.Name == "" {
- col.SQLType = schemas.Type2SQLType(fieldType)
- }
- parser.dialect.SQLType(col)
- if col.Length == 0 {
- col.Length = col.SQLType.DefaultLength
- }
- if col.Length2 == 0 {
- col.Length2 = col.SQLType.DefaultLength2
- }
- if col.Name == "" {
- col.Name = parser.columnMapper.Obj2Table(t.Field(i).Name)
- }
-
- if ctx.isUnique {
- ctx.indexNames[col.Name] = schemas.UniqueType
- } else if ctx.isIndex {
- ctx.indexNames[col.Name] = schemas.IndexType
- }
-
- for indexName, indexType := range ctx.indexNames {
- addIndex(indexName, table, col, indexType)
- }
- }
- } else {
- var sqlType schemas.SQLType
- if fieldValue.CanAddr() {
- if _, ok := fieldValue.Addr().Interface().(convert.Conversion); ok {
- sqlType = schemas.SQLType{Name: schemas.Text}
- }
- }
- if _, ok := fieldValue.Interface().(convert.Conversion); ok {
- sqlType = schemas.SQLType{Name: schemas.Text}
- } else {
- sqlType = schemas.Type2SQLType(fieldType)
- }
- col = schemas.NewColumn(parser.columnMapper.Obj2Table(t.Field(i).Name),
- t.Field(i).Name, sqlType, sqlType.DefaultLength,
- sqlType.DefaultLength2, true)
-
- if fieldType.Kind() == reflect.Int64 && (strings.ToUpper(col.FieldName) == "ID" || strings.HasSuffix(strings.ToUpper(col.FieldName), ".ID")) {
- idFieldColName = col.Name
- }
- }
- if col.IsAutoIncrement {
- col.Nullable = false
- }
-
- table.AddColumn(col)
-
- } // end for
-
- if idFieldColName != "" && len(table.PrimaryKeys) == 0 {
- col := table.GetColumn(idFieldColName)
- col.IsPrimaryKey = true
- col.IsAutoIncrement = true
- col.Nullable = false
- table.PrimaryKeys = append(table.PrimaryKeys, col.Name)
- table.AutoIncrement = col.Name
- }
-
- if hasCacheTag {
- if parser.cacherMgr.GetDefaultCacher() != nil { // !nash! use engine's cacher if provided
- //engine.logger.Info("enable cache on table:", table.Name)
- parser.cacherMgr.SetCacher(table.Name, parser.cacherMgr.GetDefaultCacher())
- } else {
- //engine.logger.Info("enable LRU cache on table:", table.Name)
- parser.cacherMgr.SetCacher(table.Name, caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000))
- }
- }
- if hasNoCacheTag {
- //engine.logger.Info("disable cache on table:", table.Name)
- parser.cacherMgr.SetCacher(table.Name, nil)
- }
-
- return table, nil
- }
|