// Copyright 2014 beego Author. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package models import ( "errors" "fmt" "reflect" "strings" "github.com/beego/beego/v2/client/orm" ) var errSkipField = errors.New("skip field") // Fields field info collection type Fields struct { Pk *FieldInfo Columns map[string]*FieldInfo Fields map[string]*FieldInfo FieldsLow map[string]*FieldInfo FieldsByType map[int][]*FieldInfo FieldsRel []*FieldInfo FieldsReverse []*FieldInfo FieldsDB []*FieldInfo Rels []*FieldInfo Orders []string DBcols []string } // Add adds field info func (f *Fields) Add(fi *FieldInfo) (added bool) { if f.Fields[fi.Name] == nil && f.Columns[fi.Column] == nil { f.Columns[fi.Column] = fi f.Fields[fi.Name] = fi f.FieldsLow[strings.ToLower(fi.Name)] = fi } else { return } if _, ok := f.FieldsByType[fi.FieldType]; !ok { f.FieldsByType[fi.FieldType] = make([]*FieldInfo, 0) } f.FieldsByType[fi.FieldType] = append(f.FieldsByType[fi.FieldType], fi) f.Orders = append(f.Orders, fi.Column) if fi.DBcol { f.DBcols = append(f.DBcols, fi.Column) f.FieldsDB = append(f.FieldsDB, fi) } if fi.Rel { f.FieldsRel = append(f.FieldsRel, fi) } if fi.Reverse { f.FieldsReverse = append(f.FieldsReverse, fi) } return true } // GetByName get field info by name func (f *Fields) GetByName(name string) *FieldInfo { return f.Fields[name] } // GetByColumn get field info by column name func (f *Fields) GetByColumn(column string) *FieldInfo { return f.Columns[column] } // GetByAny get field info by string, name is prior func (f *Fields) GetByAny(name string) (*FieldInfo, bool) { if fi, ok := f.Fields[name]; ok { return fi, ok } if fi, ok := f.FieldsLow[strings.ToLower(name)]; ok { return fi, ok } if fi, ok := f.Columns[name]; ok { return fi, ok } return nil, false } // NewFields create new field info collection func NewFields() *Fields { f := new(Fields) f.Fields = make(map[string]*FieldInfo) f.FieldsLow = make(map[string]*FieldInfo) f.Columns = make(map[string]*FieldInfo) f.FieldsByType = make(map[int][]*FieldInfo) return f } // FieldInfo single field info type FieldInfo struct { DBcol bool // table column fk and onetoone InModel bool Auto bool Pk bool Null bool Index bool Unique bool ColDefault bool // whether has default tag ToText bool AutoNow bool AutoNowAdd bool Rel bool // if type equal to RelForeignKey, RelOneToOne, RelManyToMany then true Reverse bool IsFielder bool // implement Fielder interface Mi *ModelInfo FieldIndex []int FieldType int Name string FullName string Column string AddrValue reflect.Value Sf reflect.StructField Initial orm.StrTo // store the default value Size int ReverseField string ReverseFieldInfo *FieldInfo ReverseFieldInfoTwo *FieldInfo ReverseFieldInfoM2M *FieldInfo RelTable string RelThrough string RelThroughModelInfo *ModelInfo RelModelInfo *ModelInfo Digits int Decimals int OnDelete string Description string TimePrecision *int } // NewFieldInfo new field info func NewFieldInfo(mi *ModelInfo, field reflect.Value, sf reflect.StructField, mName string) (fi *FieldInfo, err error) { var ( tag string tagValue string initial orm.StrTo // store the default value fieldType int attrs map[string]bool tags map[string]string addrField reflect.Value ) fi = new(FieldInfo) // if field which CanAddr is the follow type // A value is addressable if it is an element of a slice, // an element of an addressable array, a field of an // addressable struct, or the result of dereferencing a pointer. addrField = field if field.CanAddr() && field.Kind() != reflect.Ptr { addrField = field.Addr() if _, ok := addrField.Interface().(orm.Fielder); !ok { if field.Kind() == reflect.Slice { addrField = field } } } attrs, tags = ParseStructTag(sf.Tag.Get(DefaultStructTagName)) if _, ok := attrs["-"]; ok { return nil, errSkipField } digits := tags["digits"] decimals := tags["decimals"] size := tags["size"] onDelete := tags["on_delete"] precision := tags["precision"] initial.Clear() if v, ok := tags["default"]; ok { initial.Set(v) } checkType: switch f := addrField.Interface().(type) { case orm.Fielder: fi.IsFielder = true if field.Kind() == reflect.Ptr { err = fmt.Errorf("the model Fielder can not be use ptr") goto end } fieldType = f.FieldType() if fieldType&orm.IsRelField > 0 { err = fmt.Errorf("unsupport type custom field, please refer to https://github.com/beego/beego/v2/blob/master/orm/models_fields.go#L24-L42") goto end } default: tag = "rel" tagValue = tags[tag] if tagValue != "" { switch tagValue { case "fk": fieldType = orm.RelForeignKey break checkType case "one": fieldType = orm.RelOneToOne break checkType case "m2m": fieldType = orm.RelManyToMany if tv := tags["rel_table"]; tv != "" { fi.RelTable = tv } else if tv := tags["rel_through"]; tv != "" { fi.RelThrough = tv } break checkType default: err = fmt.Errorf("rel only allow these value: fk, one, m2m") goto wrongTag } } tag = "reverse" tagValue = tags[tag] if tagValue != "" { switch tagValue { case "one": fieldType = orm.RelReverseOne break checkType case "many": fieldType = orm.RelReverseMany if tv := tags["rel_table"]; tv != "" { fi.RelTable = tv } else if tv := tags["rel_through"]; tv != "" { fi.RelThrough = tv } break checkType default: err = fmt.Errorf("reverse only allow these value: one, many") goto wrongTag } } fieldType, err = getFieldType(addrField) if err != nil { goto end } if fieldType == orm.TypeVarCharField { switch tags["type"] { case "char": fieldType = orm.TypeCharField case "text": fieldType = orm.TypeTextField case "json": fieldType = orm.TypeJSONField case "jsonb": fieldType = orm.TypeJsonbField } } if fieldType == orm.TypeFloatField && (digits != "" || decimals != "") { fieldType = orm.TypeDecimalField } if fieldType == orm.TypeDateTimeField && tags["type"] == "date" { fieldType = orm.TypeDateField } if fieldType == orm.TypeTimeField && tags["type"] == "time" { fieldType = orm.TypeTimeField } } // check the rel and reverse type // rel should Ptr // reverse should slice []*struct switch fieldType { case orm.RelForeignKey, orm.RelOneToOne, orm.RelReverseOne: if field.Kind() != reflect.Ptr { err = fmt.Errorf("rel/reverse:one field must be *%s", field.Type().Name()) goto end } case orm.RelManyToMany, orm.RelReverseMany: if field.Kind() != reflect.Slice { err = fmt.Errorf("rel/reverse:many field must be slice") goto end } else { if field.Type().Elem().Kind() != reflect.Ptr { err = fmt.Errorf("rel/reverse:many slice must be []*%s", field.Type().Elem().Name()) goto end } } } if fieldType&orm.IsFieldType == 0 { err = fmt.Errorf("wrong field type") goto end } fi.FieldType = fieldType fi.Name = sf.Name fi.Column = getColumnName(fieldType, addrField, sf, tags["column"]) fi.AddrValue = addrField fi.Sf = sf fi.FullName = mi.FullName + mName + "." + sf.Name fi.Description = tags["description"] fi.Null = attrs["null"] fi.Index = attrs["index"] fi.Auto = attrs["auto"] fi.Pk = attrs["pk"] fi.Unique = attrs["unique"] // Mark object property if there is attribute "default" in the orm configuration if _, ok := tags["default"]; ok { fi.ColDefault = true } switch fieldType { case orm.RelManyToMany, orm.RelReverseMany, orm.RelReverseOne: fi.Null = false fi.Index = false fi.Auto = false fi.Pk = false fi.Unique = false default: fi.DBcol = true } switch fieldType { case orm.RelForeignKey, orm.RelOneToOne, orm.RelManyToMany: fi.Rel = true if fieldType == orm.RelOneToOne { fi.Unique = true } case orm.RelReverseMany, orm.RelReverseOne: fi.Reverse = true } if fi.Rel && fi.DBcol { switch onDelete { case OdCascade, OdDoNothing: case OdSetDefault: if !initial.Exist() { err = errors.New("on_delete: set_default need set field a default value") goto end } case OdSetNULL: if !fi.Null { err = errors.New("on_delete: set_null need set field null") goto end } default: if onDelete == "" { onDelete = OdCascade } else { err = fmt.Errorf("on_delete value expected choice in `cascade,set_null,set_default,do_nothing`, unknown `%s`", onDelete) goto end } } fi.OnDelete = onDelete } switch fieldType { case orm.TypeBooleanField: case orm.TypeVarCharField, orm.TypeCharField, orm.TypeJSONField, orm.TypeJsonbField: if size != "" { v, e := orm.StrTo(size).Int32() if e != nil { err = fmt.Errorf("wrong size value `%s`", size) } else { fi.Size = int(v) } } else { fi.Size = 255 fi.ToText = true } case orm.TypeTextField: fi.Index = false fi.Unique = false case orm.TypeTimeField, orm.TypeDateField, orm.TypeDateTimeField: if fieldType == orm.TypeDateTimeField { if precision != "" { v, e := orm.StrTo(precision).Int() if e != nil { err = fmt.Errorf("convert %s to int error:%v", precision, e) } else { fi.TimePrecision = &v } } } if attrs["auto_now"] { fi.AutoNow = true } else if attrs["auto_now_add"] { fi.AutoNowAdd = true } case orm.TypeFloatField: case orm.TypeDecimalField: d1 := digits d2 := decimals v1, er1 := orm.StrTo(d1).Int8() v2, er2 := orm.StrTo(d2).Int8() if er1 != nil || er2 != nil { err = fmt.Errorf("wrong digits/decimals value %s/%s", d2, d1) goto end } fi.Digits = int(v1) fi.Decimals = int(v2) default: switch { case fieldType&orm.IsIntegerField > 0: case fieldType&orm.IsRelField > 0: } } if fieldType&orm.IsIntegerField == 0 { if fi.Auto { err = fmt.Errorf("non-integer type cannot set auto") goto end } } if fi.Auto || fi.Pk { if fi.Auto { switch addrField.Elem().Kind() { case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64: default: err = fmt.Errorf("auto primary key only support int, int32, int64, uint, uint32, uint64 but found `%s`", addrField.Elem().Kind()) goto end } fi.Pk = true } fi.Null = false fi.Index = false fi.Unique = false } if fi.Unique { fi.Index = false } // can not set default for these type if fi.Auto || fi.Pk || fi.Unique || fieldType == orm.TypeTimeField || fieldType == orm.TypeDateField || fieldType == orm.TypeDateTimeField { initial.Clear() } if initial.Exist() { v := initial switch fieldType { case orm.TypeBooleanField: _, err = v.Bool() case orm.TypeFloatField, orm.TypeDecimalField: _, err = v.Float64() case orm.TypeBitField: _, err = v.Int8() case orm.TypeSmallIntegerField: _, err = v.Int16() case orm.TypeIntegerField: _, err = v.Int32() case orm.TypeBigIntegerField: _, err = v.Int64() case orm.TypePositiveBitField: _, err = v.Uint8() case orm.TypePositiveSmallIntegerField: _, err = v.Uint16() case orm.TypePositiveIntegerField: _, err = v.Uint32() case orm.TypePositiveBigIntegerField: _, err = v.Uint64() } if err != nil { tag, tagValue = "default", tags["default"] goto wrongTag } } fi.Initial = initial end: if err != nil { return nil, err } return wrongTag: return nil, fmt.Errorf("wrong tag format: `%s:\"%s\"`, %s", tag, tagValue, err) }