gitea/vendor/xorm.io/xorm/tags/parser.go

309 lines
7.5 KiB
Go

// 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
}