// Copyright 2017 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 xorm import ( "fmt" "reflect" "strconv" "strings" "time" "xorm.io/core" ) type tagContext struct { tagName string params []string preTag, nextTag string table *core.Table col *core.Column fieldValue reflect.Value isIndex bool isUnique bool indexNames map[string]int engine *Engine hasCacheTag bool hasNoCacheTag bool ignoreNext bool } // tagHandler describes tag handler for XORM type tagHandler func(ctx *tagContext) error var ( // defaultTagHandlers enumerates all the default tag handler defaultTagHandlers = map[string]tagHandler{ "<-": OnlyFromDBTagHandler, "->": OnlyToDBTagHandler, "PK": PKTagHandler, "NULL": NULLTagHandler, "NOT": IgnoreTagHandler, "AUTOINCR": AutoIncrTagHandler, "DEFAULT": DefaultTagHandler, "CREATED": CreatedTagHandler, "UPDATED": UpdatedTagHandler, "DELETED": DeletedTagHandler, "VERSION": VersionTagHandler, "UTC": UTCTagHandler, "LOCAL": LocalTagHandler, "NOTNULL": NotNullTagHandler, "INDEX": IndexTagHandler, "UNIQUE": UniqueTagHandler, "CACHE": CacheTagHandler, "NOCACHE": NoCacheTagHandler, "COMMENT": CommentTagHandler, } ) func init() { for k := range core.SqlTypes { defaultTagHandlers[k] = SQLTypeTagHandler } } // IgnoreTagHandler describes ignored tag handler func IgnoreTagHandler(ctx *tagContext) error { return nil } // OnlyFromDBTagHandler describes mapping direction tag handler func OnlyFromDBTagHandler(ctx *tagContext) error { ctx.col.MapType = core.ONLYFROMDB return nil } // OnlyToDBTagHandler describes mapping direction tag handler func OnlyToDBTagHandler(ctx *tagContext) error { ctx.col.MapType = core.ONLYTODB return nil } // PKTagHandler decribes primary key tag handler func PKTagHandler(ctx *tagContext) error { ctx.col.IsPrimaryKey = true ctx.col.Nullable = false return nil } // NULLTagHandler describes null tag handler func NULLTagHandler(ctx *tagContext) error { ctx.col.Nullable = (strings.ToUpper(ctx.preTag) != "NOT") return nil } // NotNullTagHandler describes notnull tag handler func NotNullTagHandler(ctx *tagContext) error { ctx.col.Nullable = false return nil } // AutoIncrTagHandler describes autoincr tag handler func AutoIncrTagHandler(ctx *tagContext) error { ctx.col.IsAutoIncrement = true /* if len(ctx.params) > 0 { autoStartInt, err := strconv.Atoi(ctx.params[0]) if err != nil { return err } ctx.col.AutoIncrStart = autoStartInt } else { ctx.col.AutoIncrStart = 1 } */ return nil } // DefaultTagHandler describes default tag handler func DefaultTagHandler(ctx *tagContext) error { if len(ctx.params) > 0 { ctx.col.Default = ctx.params[0] } else { ctx.col.Default = ctx.nextTag ctx.ignoreNext = true } return nil } // CreatedTagHandler describes created tag handler func CreatedTagHandler(ctx *tagContext) error { ctx.col.IsCreated = true return nil } // VersionTagHandler describes version tag handler func VersionTagHandler(ctx *tagContext) error { ctx.col.IsVersion = true ctx.col.Default = "1" return nil } // UTCTagHandler describes utc tag handler func UTCTagHandler(ctx *tagContext) error { ctx.col.TimeZone = time.UTC return nil } // LocalTagHandler describes local tag handler func LocalTagHandler(ctx *tagContext) error { if len(ctx.params) == 0 { ctx.col.TimeZone = time.Local } else { var err error ctx.col.TimeZone, err = time.LoadLocation(ctx.params[0]) if err != nil { return err } } return nil } // UpdatedTagHandler describes updated tag handler func UpdatedTagHandler(ctx *tagContext) error { ctx.col.IsUpdated = true return nil } // DeletedTagHandler describes deleted tag handler func DeletedTagHandler(ctx *tagContext) error { ctx.col.IsDeleted = true return nil } // IndexTagHandler describes index tag handler func IndexTagHandler(ctx *tagContext) error { if len(ctx.params) > 0 { ctx.indexNames[ctx.params[0]] = core.IndexType } else { ctx.isIndex = true } return nil } // UniqueTagHandler describes unique tag handler func UniqueTagHandler(ctx *tagContext) error { if len(ctx.params) > 0 { ctx.indexNames[ctx.params[0]] = core.UniqueType } else { ctx.isUnique = true } return nil } // CommentTagHandler add comment to column func CommentTagHandler(ctx *tagContext) error { if len(ctx.params) > 0 { ctx.col.Comment = strings.Trim(ctx.params[0], "' ") } return nil } // SQLTypeTagHandler describes SQL Type tag handler func SQLTypeTagHandler(ctx *tagContext) error { ctx.col.SQLType = core.SQLType{Name: ctx.tagName} if len(ctx.params) > 0 { if ctx.tagName == core.Enum { ctx.col.EnumOptions = make(map[string]int) for k, v := range ctx.params { v = strings.TrimSpace(v) v = strings.Trim(v, "'") ctx.col.EnumOptions[v] = k } } else if ctx.tagName == core.Set { ctx.col.SetOptions = make(map[string]int) for k, v := range ctx.params { v = strings.TrimSpace(v) v = strings.Trim(v, "'") ctx.col.SetOptions[v] = k } } else { var err error if len(ctx.params) == 2 { ctx.col.Length, err = strconv.Atoi(ctx.params[0]) if err != nil { return err } ctx.col.Length2, err = strconv.Atoi(ctx.params[1]) if err != nil { return err } } else if len(ctx.params) == 1 { ctx.col.Length, err = strconv.Atoi(ctx.params[0]) if err != nil { return err } } } } return nil } // ExtendsTagHandler describes extends tag handler func ExtendsTagHandler(ctx *tagContext) error { var fieldValue = ctx.fieldValue var isPtr = false switch fieldValue.Kind() { case reflect.Ptr: f := fieldValue.Type().Elem() if f.Kind() == reflect.Struct { fieldPtr := fieldValue fieldValue = fieldValue.Elem() if !fieldValue.IsValid() || fieldPtr.IsNil() { fieldValue = reflect.New(f).Elem() } } isPtr = true fallthrough case reflect.Struct: parentTable, err := ctx.engine.mapType(fieldValue) if err != nil { return err } for _, col := range parentTable.Columns() { col.FieldName = fmt.Sprintf("%v.%v", ctx.col.FieldName, col.FieldName) var tagPrefix = ctx.col.FieldName if len(ctx.params) > 0 { col.Nullable = isPtr tagPrefix = ctx.params[0] if col.IsPrimaryKey { col.Name = ctx.col.FieldName col.IsPrimaryKey = false } else { col.Name = fmt.Sprintf("%v%v", tagPrefix, col.Name) } } if col.Nullable { col.IsAutoIncrement = false col.IsPrimaryKey = false } ctx.table.AddColumn(col) for indexName, indexType := range col.Indexes { addIndex(indexName, ctx.table, col, indexType) } } default: //TODO: warning } return nil } // CacheTagHandler describes cache tag handler func CacheTagHandler(ctx *tagContext) error { if !ctx.hasCacheTag { ctx.hasCacheTag = true } return nil } // NoCacheTagHandler describes nocache tag handler func NoCacheTagHandler(ctx *tagContext) error { if !ctx.hasNoCacheTag { ctx.hasNoCacheTag = true } return nil }