414 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			414 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package orm
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"reflect"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
var errSkipField = errors.New("skip field")
 | 
						|
 | 
						|
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
 | 
						|
}
 | 
						|
 | 
						|
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 == false {
 | 
						|
		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
 | 
						|
}
 | 
						|
 | 
						|
func (f *fields) GetByName(name string) *fieldInfo {
 | 
						|
	return f.fields[name]
 | 
						|
}
 | 
						|
 | 
						|
func (f *fields) GetByColumn(column string) *fieldInfo {
 | 
						|
	return f.columns[column]
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
}
 | 
						|
 | 
						|
type fieldInfo struct {
 | 
						|
	mi                  *modelInfo
 | 
						|
	fieldIndex          int
 | 
						|
	fieldType           int
 | 
						|
	dbcol               bool
 | 
						|
	inModel             bool
 | 
						|
	name                string
 | 
						|
	fullName            string
 | 
						|
	column              string
 | 
						|
	addrValue           reflect.Value
 | 
						|
	sf                  reflect.StructField
 | 
						|
	auto                bool
 | 
						|
	pk                  bool
 | 
						|
	null                bool
 | 
						|
	index               bool
 | 
						|
	unique              bool
 | 
						|
	initial             StrTo
 | 
						|
	size                int
 | 
						|
	auto_now            bool
 | 
						|
	auto_now_add        bool
 | 
						|
	rel                 bool
 | 
						|
	reverse             bool
 | 
						|
	reverseField        string
 | 
						|
	reverseFieldInfo    *fieldInfo
 | 
						|
	relTable            string
 | 
						|
	relThrough          string
 | 
						|
	relThroughModelInfo *modelInfo
 | 
						|
	relModelInfo        *modelInfo
 | 
						|
	digits              int
 | 
						|
	decimals            int
 | 
						|
	isFielder           bool
 | 
						|
	onDelete            string
 | 
						|
}
 | 
						|
 | 
						|
func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField) (fi *fieldInfo, err error) {
 | 
						|
	var (
 | 
						|
		tag       string
 | 
						|
		tagValue  string
 | 
						|
		initial   StrTo
 | 
						|
		fieldType int
 | 
						|
		attrs     map[string]bool
 | 
						|
		tags      map[string]string
 | 
						|
		addrField reflect.Value
 | 
						|
	)
 | 
						|
 | 
						|
	fi = new(fieldInfo)
 | 
						|
 | 
						|
	if field.Kind() != reflect.Ptr && field.Kind() != reflect.Slice && field.CanAddr() {
 | 
						|
		addrField = field.Addr()
 | 
						|
	} else {
 | 
						|
		addrField = field
 | 
						|
	}
 | 
						|
 | 
						|
	parseStructTag(sf.Tag.Get(defaultStructTagName), &attrs, &tags)
 | 
						|
 | 
						|
	if _, ok := attrs["-"]; ok {
 | 
						|
		return nil, errSkipField
 | 
						|
	}
 | 
						|
 | 
						|
	digits := tags["digits"]
 | 
						|
	decimals := tags["decimals"]
 | 
						|
	size := tags["size"]
 | 
						|
	onDelete := tags["on_delete"]
 | 
						|
 | 
						|
	initial.Clear()
 | 
						|
	if v, ok := tags["default"]; ok {
 | 
						|
		initial.Set(v)
 | 
						|
	}
 | 
						|
 | 
						|
checkType:
 | 
						|
	switch f := addrField.Interface().(type) {
 | 
						|
	case 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&IsRelField > 0 {
 | 
						|
			err = fmt.Errorf("unsupport rel type custom field")
 | 
						|
			goto end
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		tag = "rel"
 | 
						|
		tagValue = tags[tag]
 | 
						|
		if tagValue != "" {
 | 
						|
			switch tagValue {
 | 
						|
			case "fk":
 | 
						|
				fieldType = RelForeignKey
 | 
						|
				break checkType
 | 
						|
			case "one":
 | 
						|
				fieldType = RelOneToOne
 | 
						|
				break checkType
 | 
						|
			case "m2m":
 | 
						|
				fieldType = 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("error")
 | 
						|
				goto wrongTag
 | 
						|
			}
 | 
						|
		}
 | 
						|
		tag = "reverse"
 | 
						|
		tagValue = tags[tag]
 | 
						|
		if tagValue != "" {
 | 
						|
			switch tagValue {
 | 
						|
			case "one":
 | 
						|
				fieldType = RelReverseOne
 | 
						|
				break checkType
 | 
						|
			case "many":
 | 
						|
				fieldType = RelReverseMany
 | 
						|
				break checkType
 | 
						|
			default:
 | 
						|
				err = fmt.Errorf("error")
 | 
						|
				goto wrongTag
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		fieldType, err = getFieldType(addrField)
 | 
						|
		if err != nil {
 | 
						|
			goto end
 | 
						|
		}
 | 
						|
		if fieldType == TypeCharField && tags["type"] == "text" {
 | 
						|
			fieldType = TypeTextField
 | 
						|
		}
 | 
						|
		if fieldType == TypeFloatField && (digits != "" || decimals != "") {
 | 
						|
			fieldType = TypeDecimalField
 | 
						|
		}
 | 
						|
		if fieldType == TypeDateTimeField && tags["type"] == "date" {
 | 
						|
			fieldType = TypeDateField
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	switch fieldType {
 | 
						|
	case RelForeignKey, RelOneToOne, RelReverseOne:
 | 
						|
		if field.Kind() != reflect.Ptr {
 | 
						|
			err = fmt.Errorf("rel/reverse:one field must be *%s", field.Type().Name())
 | 
						|
			goto end
 | 
						|
		}
 | 
						|
	case RelManyToMany, 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&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 + "." + sf.Name
 | 
						|
 | 
						|
	fi.null = attrs["null"]
 | 
						|
	fi.index = attrs["index"]
 | 
						|
	fi.auto = attrs["auto"]
 | 
						|
	fi.pk = attrs["pk"]
 | 
						|
	fi.unique = attrs["unique"]
 | 
						|
 | 
						|
	switch fieldType {
 | 
						|
	case RelManyToMany, RelReverseMany, RelReverseOne:
 | 
						|
		fi.null = false
 | 
						|
		fi.index = false
 | 
						|
		fi.auto = false
 | 
						|
		fi.pk = false
 | 
						|
		fi.unique = false
 | 
						|
	default:
 | 
						|
		fi.dbcol = true
 | 
						|
	}
 | 
						|
 | 
						|
	switch fieldType {
 | 
						|
	case RelForeignKey, RelOneToOne, RelManyToMany:
 | 
						|
		fi.rel = true
 | 
						|
		if fieldType == RelOneToOne {
 | 
						|
			fi.unique = true
 | 
						|
		}
 | 
						|
	case RelReverseMany, RelReverseOne:
 | 
						|
		fi.reverse = true
 | 
						|
	}
 | 
						|
 | 
						|
	if fi.rel && fi.dbcol {
 | 
						|
		switch onDelete {
 | 
						|
		case od_CASCADE, od_DO_NOTHING:
 | 
						|
		case od_SET_DEFAULT:
 | 
						|
			if initial.Exist() == false {
 | 
						|
				err = errors.New("on_delete: set_default need set field a default value")
 | 
						|
				goto end
 | 
						|
			}
 | 
						|
		case od_SET_NULL:
 | 
						|
			if fi.null == false {
 | 
						|
				err = errors.New("on_delete: set_null need set field null")
 | 
						|
				goto end
 | 
						|
			}
 | 
						|
		default:
 | 
						|
			if onDelete == "" {
 | 
						|
				onDelete = od_CASCADE
 | 
						|
			} 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 TypeBooleanField:
 | 
						|
	case TypeCharField:
 | 
						|
		if size != "" {
 | 
						|
			v, e := StrTo(size).Int32()
 | 
						|
			if e != nil {
 | 
						|
				err = fmt.Errorf("wrong size value `%s`", size)
 | 
						|
			} else {
 | 
						|
				fi.size = int(v)
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			fi.size = 255
 | 
						|
		}
 | 
						|
	case TypeTextField:
 | 
						|
		fi.index = false
 | 
						|
		fi.unique = false
 | 
						|
	case TypeDateField, TypeDateTimeField:
 | 
						|
		if attrs["auto_now"] {
 | 
						|
			fi.auto_now = true
 | 
						|
		} else if attrs["auto_now_add"] {
 | 
						|
			fi.auto_now_add = true
 | 
						|
		}
 | 
						|
	case TypeFloatField:
 | 
						|
	case TypeDecimalField:
 | 
						|
		d1 := digits
 | 
						|
		d2 := decimals
 | 
						|
		v1, er1 := StrTo(d1).Int8()
 | 
						|
		v2, er2 := 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&IsIntegerField > 0:
 | 
						|
		case fieldType&IsRelField > 0:
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if fieldType&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
 | 
						|
	}
 | 
						|
 | 
						|
	if fi.auto || fi.pk || fi.unique || fieldType == TypeDateField || fieldType == TypeDateTimeField {
 | 
						|
		// can not set default
 | 
						|
		initial.Clear()
 | 
						|
	}
 | 
						|
 | 
						|
	if initial.Exist() {
 | 
						|
		v := initial
 | 
						|
		switch fieldType {
 | 
						|
		case TypeBooleanField:
 | 
						|
			_, err = v.Bool()
 | 
						|
		case TypeFloatField, TypeDecimalField:
 | 
						|
			_, err = v.Float64()
 | 
						|
		case TypeBitField:
 | 
						|
			_, err = v.Int8()
 | 
						|
		case TypeSmallIntegerField:
 | 
						|
			_, err = v.Int16()
 | 
						|
		case TypeIntegerField:
 | 
						|
			_, err = v.Int32()
 | 
						|
		case TypeBigIntegerField:
 | 
						|
			_, err = v.Int64()
 | 
						|
		case TypePositiveBitField:
 | 
						|
			_, err = v.Uint8()
 | 
						|
		case TypePositiveSmallIntegerField:
 | 
						|
			_, err = v.Uint16()
 | 
						|
		case TypePositiveIntegerField:
 | 
						|
			_, err = v.Uint32()
 | 
						|
		case 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)
 | 
						|
}
 |