orm: refactor ORM introducing internal/models pkg
This commit is contained in:
33
client/orm/internal/buffers/buffers.go
Normal file
33
client/orm/internal/buffers/buffers.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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 buffers
|
||||
|
||||
import "github.com/valyala/bytebufferpool"
|
||||
|
||||
var _ Buffer = &bytebufferpool.ByteBuffer{}
|
||||
|
||||
type Buffer interface {
|
||||
Write(p []byte) (int, error)
|
||||
WriteString(s string) (int, error)
|
||||
WriteByte(c byte) error
|
||||
}
|
||||
|
||||
func Get() Buffer {
|
||||
return bytebufferpool.Get()
|
||||
}
|
||||
|
||||
func Put(bf Buffer) {
|
||||
bytebufferpool.Put(bf.(*bytebufferpool.ByteBuffer))
|
||||
}
|
||||
487
client/orm/internal/models/models_info_f.go
Normal file
487
client/orm/internal/models/models_info_f.go
Normal file
@@ -0,0 +1,487 @@
|
||||
// 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)
|
||||
}
|
||||
150
client/orm/internal/models/models_info_m.go
Normal file
150
client/orm/internal/models/models_info_m.go
Normal file
@@ -0,0 +1,150 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
// ModelInfo single model info
|
||||
type ModelInfo struct {
|
||||
Manual bool
|
||||
IsThrough bool
|
||||
Pkg string
|
||||
Name string
|
||||
FullName string
|
||||
Table string
|
||||
Model interface{}
|
||||
Fields *Fields
|
||||
AddrField reflect.Value // store the original struct value
|
||||
Uniques []string
|
||||
}
|
||||
|
||||
// NewModelInfo new model info
|
||||
func NewModelInfo(val reflect.Value) (mi *ModelInfo) {
|
||||
mi = &ModelInfo{}
|
||||
mi.Fields = NewFields()
|
||||
ind := reflect.Indirect(val)
|
||||
mi.AddrField = val
|
||||
mi.Name = ind.Type().Name()
|
||||
mi.FullName = GetFullName(ind.Type())
|
||||
AddModelFields(mi, ind, "", []int{})
|
||||
return
|
||||
}
|
||||
|
||||
// AddModelFields index: FieldByIndex returns the nested field corresponding to index
|
||||
func AddModelFields(mi *ModelInfo, ind reflect.Value, mName string, index []int) {
|
||||
var (
|
||||
err error
|
||||
fi *FieldInfo
|
||||
sf reflect.StructField
|
||||
)
|
||||
|
||||
for i := 0; i < ind.NumField(); i++ {
|
||||
field := ind.Field(i)
|
||||
sf = ind.Type().Field(i)
|
||||
// if the field is unexported skip
|
||||
if sf.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
// add anonymous struct Fields
|
||||
if sf.Anonymous {
|
||||
AddModelFields(mi, field, mName+"."+sf.Name, append(index, i))
|
||||
continue
|
||||
}
|
||||
|
||||
fi, err = NewFieldInfo(mi, field, sf, mName)
|
||||
if err == errSkipField {
|
||||
err = nil
|
||||
continue
|
||||
} else if err != nil {
|
||||
break
|
||||
}
|
||||
// record current field index
|
||||
fi.FieldIndex = append(fi.FieldIndex, index...)
|
||||
fi.FieldIndex = append(fi.FieldIndex, i)
|
||||
fi.Mi = mi
|
||||
fi.InModel = true
|
||||
if !mi.Fields.Add(fi) {
|
||||
err = fmt.Errorf("duplicate column name: %s", fi.Column)
|
||||
break
|
||||
}
|
||||
if fi.Pk {
|
||||
if mi.Fields.Pk != nil {
|
||||
err = fmt.Errorf("one model must have one pk field only")
|
||||
break
|
||||
} else {
|
||||
mi.Fields.Pk = fi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(fmt.Errorf("field: %s.%s, %s", ind.Type(), sf.Name, err))
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
// NewM2MModelInfo combine related model info to new model info.
|
||||
// prepare for relation models query.
|
||||
func NewM2MModelInfo(m1, m2 *ModelInfo) (mi *ModelInfo) {
|
||||
mi = new(ModelInfo)
|
||||
mi.Fields = NewFields()
|
||||
mi.Table = m1.Table + "_" + m2.Table + "s"
|
||||
mi.Name = CamelString(mi.Table)
|
||||
mi.FullName = m1.Pkg + "." + mi.Name
|
||||
|
||||
fa := new(FieldInfo) // pk
|
||||
f1 := new(FieldInfo) // m1 table RelForeignKey
|
||||
f2 := new(FieldInfo) // m2 table RelForeignKey
|
||||
fa.FieldType = orm.TypeBigIntegerField
|
||||
fa.Auto = true
|
||||
fa.Pk = true
|
||||
fa.DBcol = true
|
||||
fa.Name = "Id"
|
||||
fa.Column = "id"
|
||||
fa.FullName = mi.FullName + "." + fa.Name
|
||||
|
||||
f1.DBcol = true
|
||||
f2.DBcol = true
|
||||
f1.FieldType = orm.RelForeignKey
|
||||
f2.FieldType = orm.RelForeignKey
|
||||
f1.Name = CamelString(m1.Table)
|
||||
f2.Name = CamelString(m2.Table)
|
||||
f1.FullName = mi.FullName + "." + f1.Name
|
||||
f2.FullName = mi.FullName + "." + f2.Name
|
||||
f1.Column = m1.Table + "_id"
|
||||
f2.Column = m2.Table + "_id"
|
||||
f1.Rel = true
|
||||
f2.Rel = true
|
||||
f1.RelTable = m1.Table
|
||||
f2.RelTable = m2.Table
|
||||
f1.RelModelInfo = m1
|
||||
f2.RelModelInfo = m2
|
||||
f1.Mi = mi
|
||||
f2.Mi = mi
|
||||
|
||||
mi.Fields.Add(fa)
|
||||
mi.Fields.Add(f1)
|
||||
mi.Fields.Add(f2)
|
||||
mi.Fields.Pk = fa
|
||||
|
||||
mi.Uniques = []string{f1.Column, f2.Column}
|
||||
return
|
||||
}
|
||||
325
client/orm/internal/models/models_utils.go
Normal file
325
client/orm/internal/models/models_utils.go
Normal file
@@ -0,0 +1,325 @@
|
||||
// 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 (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
// 1 is attr
|
||||
// 2 is tag
|
||||
var supportTag = map[string]int{
|
||||
"-": 1,
|
||||
"null": 1,
|
||||
"index": 1,
|
||||
"unique": 1,
|
||||
"pk": 1,
|
||||
"auto": 1,
|
||||
"auto_now": 1,
|
||||
"auto_now_add": 1,
|
||||
"size": 2,
|
||||
"column": 2,
|
||||
"default": 2,
|
||||
"rel": 2,
|
||||
"reverse": 2,
|
||||
"rel_table": 2,
|
||||
"rel_through": 2,
|
||||
"digits": 2,
|
||||
"decimals": 2,
|
||||
"on_delete": 2,
|
||||
"type": 2,
|
||||
"description": 2,
|
||||
"precision": 2,
|
||||
}
|
||||
|
||||
type fn func(string) string
|
||||
|
||||
var (
|
||||
NameStrategyMap = map[string]fn{
|
||||
DefaultNameStrategy: SnakeString,
|
||||
SnakeAcronymNameStrategy: SnakeStringWithAcronym,
|
||||
}
|
||||
DefaultNameStrategy = "snakeString"
|
||||
SnakeAcronymNameStrategy = "snakeStringWithAcronym"
|
||||
NameStrategy = DefaultNameStrategy
|
||||
defaultStructTagDelim = ";"
|
||||
DefaultStructTagName = "orm"
|
||||
)
|
||||
|
||||
// GetFullName get reflect.Type name with package path.
|
||||
func GetFullName(typ reflect.Type) string {
|
||||
return typ.PkgPath() + "." + typ.Name()
|
||||
}
|
||||
|
||||
// GetTableName get struct table name.
|
||||
// If the struct implement the TableName, then get the result as tablename
|
||||
// else use the struct name which will apply snakeString.
|
||||
func GetTableName(val reflect.Value) string {
|
||||
if fun := val.MethodByName("TableName"); fun.IsValid() {
|
||||
vals := fun.Call([]reflect.Value{})
|
||||
// has return and the first val is string
|
||||
if len(vals) > 0 && vals[0].Kind() == reflect.String {
|
||||
return vals[0].String()
|
||||
}
|
||||
}
|
||||
return SnakeString(reflect.Indirect(val).Type().Name())
|
||||
}
|
||||
|
||||
// GetTableEngine get table engine, myisam or innodb.
|
||||
func GetTableEngine(val reflect.Value) string {
|
||||
fun := val.MethodByName("TableEngine")
|
||||
if fun.IsValid() {
|
||||
vals := fun.Call([]reflect.Value{})
|
||||
if len(vals) > 0 && vals[0].Kind() == reflect.String {
|
||||
return vals[0].String()
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetTableIndex get table index from method.
|
||||
func GetTableIndex(val reflect.Value) [][]string {
|
||||
fun := val.MethodByName("TableIndex")
|
||||
if fun.IsValid() {
|
||||
vals := fun.Call([]reflect.Value{})
|
||||
if len(vals) > 0 && vals[0].CanInterface() {
|
||||
if d, ok := vals[0].Interface().([][]string); ok {
|
||||
return d
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTableUnique get table unique from method
|
||||
func GetTableUnique(val reflect.Value) [][]string {
|
||||
fun := val.MethodByName("TableUnique")
|
||||
if fun.IsValid() {
|
||||
vals := fun.Call([]reflect.Value{})
|
||||
if len(vals) > 0 && vals[0].CanInterface() {
|
||||
if d, ok := vals[0].Interface().([][]string); ok {
|
||||
return d
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsApplicableTableForDB get whether the table needs to be created for the database alias
|
||||
func IsApplicableTableForDB(val reflect.Value, db string) bool {
|
||||
if !val.IsValid() {
|
||||
return true
|
||||
}
|
||||
fun := val.MethodByName("IsApplicableTableForDB")
|
||||
if fun.IsValid() {
|
||||
vals := fun.Call([]reflect.Value{reflect.ValueOf(db)})
|
||||
if len(vals) > 0 && vals[0].Kind() == reflect.Bool {
|
||||
return vals[0].Bool()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// get snaked column name
|
||||
func getColumnName(ft int, addrField reflect.Value, sf reflect.StructField, col string) string {
|
||||
column := col
|
||||
if col == "" {
|
||||
column = NameStrategyMap[NameStrategy](sf.Name)
|
||||
}
|
||||
switch ft {
|
||||
case orm.RelForeignKey, orm.RelOneToOne:
|
||||
if len(col) == 0 {
|
||||
column = column + "_id"
|
||||
}
|
||||
case orm.RelManyToMany, orm.RelReverseMany, orm.RelReverseOne:
|
||||
column = sf.Name
|
||||
}
|
||||
return column
|
||||
}
|
||||
|
||||
// return field type as type constant from reflect.Value
|
||||
func getFieldType(val reflect.Value) (ft int, err error) {
|
||||
switch val.Type() {
|
||||
case reflect.TypeOf(new(int8)):
|
||||
ft = orm.TypeBitField
|
||||
case reflect.TypeOf(new(int16)):
|
||||
ft = orm.TypeSmallIntegerField
|
||||
case reflect.TypeOf(new(int32)),
|
||||
reflect.TypeOf(new(int)):
|
||||
ft = orm.TypeIntegerField
|
||||
case reflect.TypeOf(new(int64)):
|
||||
ft = orm.TypeBigIntegerField
|
||||
case reflect.TypeOf(new(uint8)):
|
||||
ft = orm.TypePositiveBitField
|
||||
case reflect.TypeOf(new(uint16)):
|
||||
ft = orm.TypePositiveSmallIntegerField
|
||||
case reflect.TypeOf(new(uint32)),
|
||||
reflect.TypeOf(new(uint)):
|
||||
ft = orm.TypePositiveIntegerField
|
||||
case reflect.TypeOf(new(uint64)):
|
||||
ft = orm.TypePositiveBigIntegerField
|
||||
case reflect.TypeOf(new(float32)),
|
||||
reflect.TypeOf(new(float64)):
|
||||
ft = orm.TypeFloatField
|
||||
case reflect.TypeOf(new(bool)):
|
||||
ft = orm.TypeBooleanField
|
||||
case reflect.TypeOf(new(string)):
|
||||
ft = orm.TypeVarCharField
|
||||
case reflect.TypeOf(new(time.Time)):
|
||||
ft = orm.TypeDateTimeField
|
||||
default:
|
||||
elm := reflect.Indirect(val)
|
||||
switch elm.Kind() {
|
||||
case reflect.Int8:
|
||||
ft = orm.TypeBitField
|
||||
case reflect.Int16:
|
||||
ft = orm.TypeSmallIntegerField
|
||||
case reflect.Int32, reflect.Int:
|
||||
ft = orm.TypeIntegerField
|
||||
case reflect.Int64:
|
||||
ft = orm.TypeBigIntegerField
|
||||
case reflect.Uint8:
|
||||
ft = orm.TypePositiveBitField
|
||||
case reflect.Uint16:
|
||||
ft = orm.TypePositiveSmallIntegerField
|
||||
case reflect.Uint32, reflect.Uint:
|
||||
ft = orm.TypePositiveIntegerField
|
||||
case reflect.Uint64:
|
||||
ft = orm.TypePositiveBigIntegerField
|
||||
case reflect.Float32, reflect.Float64:
|
||||
ft = orm.TypeFloatField
|
||||
case reflect.Bool:
|
||||
ft = orm.TypeBooleanField
|
||||
case reflect.String:
|
||||
ft = orm.TypeVarCharField
|
||||
default:
|
||||
if elm.Interface() == nil {
|
||||
panic(fmt.Errorf("%s is nil pointer, may be miss setting tag", val))
|
||||
}
|
||||
switch elm.Interface().(type) {
|
||||
case sql.NullInt64:
|
||||
ft = orm.TypeBigIntegerField
|
||||
case sql.NullFloat64:
|
||||
ft = orm.TypeFloatField
|
||||
case sql.NullBool:
|
||||
ft = orm.TypeBooleanField
|
||||
case sql.NullString:
|
||||
ft = orm.TypeVarCharField
|
||||
case time.Time:
|
||||
ft = orm.TypeDateTimeField
|
||||
}
|
||||
}
|
||||
}
|
||||
if ft&orm.IsFieldType == 0 {
|
||||
err = fmt.Errorf("unsupport field type %s, may be miss setting tag", val)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParseStructTag parse struct tag string
|
||||
func ParseStructTag(data string) (attrs map[string]bool, tags map[string]string) {
|
||||
attrs = make(map[string]bool)
|
||||
tags = make(map[string]string)
|
||||
for _, v := range strings.Split(data, defaultStructTagDelim) {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
v = strings.TrimSpace(v)
|
||||
if t := strings.ToLower(v); supportTag[t] == 1 {
|
||||
attrs[t] = true
|
||||
} else if i := strings.Index(v, "("); i > 0 && strings.Index(v, ")") == len(v)-1 {
|
||||
name := t[:i]
|
||||
if supportTag[name] == 2 {
|
||||
v = v[i+1 : len(v)-1]
|
||||
tags[name] = v
|
||||
}
|
||||
} else {
|
||||
orm.DebugLog.Println("unsupport orm tag", v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SnakeStringWithAcronym(s string) string {
|
||||
data := make([]byte, 0, len(s)*2)
|
||||
num := len(s)
|
||||
for i := 0; i < num; i++ {
|
||||
d := s[i]
|
||||
before := false
|
||||
after := false
|
||||
if i > 0 {
|
||||
before = s[i-1] >= 'a' && s[i-1] <= 'z'
|
||||
}
|
||||
if i+1 < num {
|
||||
after = s[i+1] >= 'a' && s[i+1] <= 'z'
|
||||
}
|
||||
if i > 0 && d >= 'A' && d <= 'Z' && (before || after) {
|
||||
data = append(data, '_')
|
||||
}
|
||||
data = append(data, d)
|
||||
}
|
||||
return strings.ToLower(string(data))
|
||||
}
|
||||
|
||||
// SnakeString snake string, XxYy to xx_yy , XxYY to xx_y_y
|
||||
func SnakeString(s string) string {
|
||||
data := make([]byte, 0, len(s)*2)
|
||||
j := false
|
||||
num := len(s)
|
||||
for i := 0; i < num; i++ {
|
||||
d := s[i]
|
||||
if i > 0 && d >= 'A' && d <= 'Z' && j {
|
||||
data = append(data, '_')
|
||||
}
|
||||
if d != '_' {
|
||||
j = true
|
||||
}
|
||||
data = append(data, d)
|
||||
}
|
||||
return strings.ToLower(string(data))
|
||||
}
|
||||
|
||||
// CamelString camel string, xx_yy to XxYy
|
||||
func CamelString(s string) string {
|
||||
data := make([]byte, 0, len(s))
|
||||
flag, num := true, len(s)-1
|
||||
for i := 0; i <= num; i++ {
|
||||
d := s[i]
|
||||
if d == '_' {
|
||||
flag = true
|
||||
continue
|
||||
} else if flag {
|
||||
if d >= 'a' && d <= 'z' {
|
||||
d = d - 32
|
||||
}
|
||||
flag = false
|
||||
}
|
||||
data = append(data, d)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
const (
|
||||
OdCascade = "cascade"
|
||||
OdSetNULL = "set_null"
|
||||
OdSetDefault = "set_default"
|
||||
OdDoNothing = "do_nothing"
|
||||
)
|
||||
86
client/orm/internal/models/models_utils_test.go
Normal file
86
client/orm/internal/models/models_utils_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2020
|
||||
//
|
||||
// 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 (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type NotApplicableModel struct {
|
||||
Id int
|
||||
}
|
||||
|
||||
func (n *NotApplicableModel) IsApplicableTableForDB(db string) bool {
|
||||
return db == "default"
|
||||
}
|
||||
|
||||
func TestIsApplicableTableForDB(t *testing.T) {
|
||||
assert.False(t, IsApplicableTableForDB(reflect.ValueOf(&NotApplicableModel{}), "defa"))
|
||||
assert.True(t, IsApplicableTableForDB(reflect.ValueOf(&NotApplicableModel{}), "default"))
|
||||
}
|
||||
|
||||
func TestSnakeString(t *testing.T) {
|
||||
camel := []string{"PicUrl", "HelloWorld", "HelloWorld", "HelLOWord", "PicUrl1", "XyXX"}
|
||||
snake := []string{"pic_url", "hello_world", "hello_world", "hel_l_o_word", "pic_url1", "xy_x_x"}
|
||||
|
||||
answer := make(map[string]string)
|
||||
for i, v := range camel {
|
||||
answer[v] = snake[i]
|
||||
}
|
||||
|
||||
for _, v := range camel {
|
||||
res := SnakeString(v)
|
||||
if res != answer[v] {
|
||||
t.Error("Unit Test Fail:", v, res, answer[v])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnakeStringWithAcronym(t *testing.T) {
|
||||
camel := []string{"ID", "PicURL", "HelloWorld", "HelloWorld", "HelLOWord", "PicUrl1", "XyXX"}
|
||||
snake := []string{"id", "pic_url", "hello_world", "hello_world", "hel_lo_word", "pic_url1", "xy_xx"}
|
||||
|
||||
answer := make(map[string]string)
|
||||
for i, v := range camel {
|
||||
answer[v] = snake[i]
|
||||
}
|
||||
|
||||
for _, v := range camel {
|
||||
res := SnakeStringWithAcronym(v)
|
||||
if res != answer[v] {
|
||||
t.Error("Unit Test Fail:", v, res, answer[v])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCamelString(t *testing.T) {
|
||||
snake := []string{"pic_url", "hello_world_", "hello__World", "_HelLO_Word", "pic_url_1", "pic_url__1"}
|
||||
camel := []string{"PicUrl", "HelloWorld", "HelloWorld", "HelLOWord", "PicUrl1", "PicUrl1"}
|
||||
|
||||
answer := make(map[string]string)
|
||||
for i, v := range snake {
|
||||
answer[v] = camel[i]
|
||||
}
|
||||
|
||||
for _, v := range snake {
|
||||
res := CamelString(v)
|
||||
if res != answer[v] {
|
||||
t.Error("Unit Test Fail:", v, res, answer[v])
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user