1. 实现配置文件解析
2. 实现数据库连接
This commit is contained in:
66
vendor/gorm.io/gorm/schema/constraint.go
generated
vendored
Normal file
66
vendor/gorm.io/gorm/schema/constraint.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// reg match english letters and midline
|
||||
var regEnLetterAndMidline = regexp.MustCompile("^[A-Za-z-_]+$")
|
||||
|
||||
type CheckConstraint struct {
|
||||
Name string
|
||||
Constraint string // length(phone) >= 10
|
||||
*Field
|
||||
}
|
||||
|
||||
func (chk *CheckConstraint) GetName() string { return chk.Name }
|
||||
|
||||
func (chk *CheckConstraint) Build() (sql string, vars []interface{}) {
|
||||
return "CONSTRAINT ? CHECK (?)", []interface{}{clause.Column{Name: chk.Name}, clause.Expr{SQL: chk.Constraint}}
|
||||
}
|
||||
|
||||
// ParseCheckConstraints parse schema check constraints
|
||||
func (schema *Schema) ParseCheckConstraints() map[string]CheckConstraint {
|
||||
checks := map[string]CheckConstraint{}
|
||||
for _, field := range schema.FieldsByDBName {
|
||||
if chk := field.TagSettings["CHECK"]; chk != "" {
|
||||
names := strings.Split(chk, ",")
|
||||
if len(names) > 1 && regEnLetterAndMidline.MatchString(names[0]) {
|
||||
checks[names[0]] = CheckConstraint{Name: names[0], Constraint: strings.Join(names[1:], ","), Field: field}
|
||||
} else {
|
||||
if names[0] == "" {
|
||||
chk = strings.Join(names[1:], ",")
|
||||
}
|
||||
name := schema.namer.CheckerName(schema.Table, field.DBName)
|
||||
checks[name] = CheckConstraint{Name: name, Constraint: chk, Field: field}
|
||||
}
|
||||
}
|
||||
}
|
||||
return checks
|
||||
}
|
||||
|
||||
type UniqueConstraint struct {
|
||||
Name string
|
||||
Field *Field
|
||||
}
|
||||
|
||||
func (uni *UniqueConstraint) GetName() string { return uni.Name }
|
||||
|
||||
func (uni *UniqueConstraint) Build() (sql string, vars []interface{}) {
|
||||
return "CONSTRAINT ? UNIQUE (?)", []interface{}{clause.Column{Name: uni.Name}, clause.Column{Name: uni.Field.DBName}}
|
||||
}
|
||||
|
||||
// ParseUniqueConstraints parse schema unique constraints
|
||||
func (schema *Schema) ParseUniqueConstraints() map[string]UniqueConstraint {
|
||||
uniques := make(map[string]UniqueConstraint)
|
||||
for _, field := range schema.Fields {
|
||||
if field.Unique {
|
||||
name := schema.namer.UniqueName(schema.Table, field.DBName)
|
||||
uniques[name] = UniqueConstraint{Name: name, Field: field}
|
||||
}
|
||||
}
|
||||
return uniques
|
||||
}
|
||||
996
vendor/gorm.io/gorm/schema/field.go
generated
vendored
Normal file
996
vendor/gorm.io/gorm/schema/field.go
generated
vendored
Normal file
@@ -0,0 +1,996 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/now"
|
||||
"gorm.io/gorm/clause"
|
||||
"gorm.io/gorm/utils"
|
||||
)
|
||||
|
||||
// special types' reflect type
|
||||
var (
|
||||
TimeReflectType = reflect.TypeOf(time.Time{})
|
||||
TimePtrReflectType = reflect.TypeOf(&time.Time{})
|
||||
ByteReflectType = reflect.TypeOf(uint8(0))
|
||||
)
|
||||
|
||||
type (
|
||||
// DataType GORM data type
|
||||
DataType string
|
||||
// TimeType GORM time type
|
||||
TimeType int64
|
||||
)
|
||||
|
||||
// GORM time types
|
||||
const (
|
||||
UnixTime TimeType = 1
|
||||
UnixSecond TimeType = 2
|
||||
UnixMillisecond TimeType = 3
|
||||
UnixNanosecond TimeType = 4
|
||||
)
|
||||
|
||||
// GORM fields types
|
||||
const (
|
||||
Bool DataType = "bool"
|
||||
Int DataType = "int"
|
||||
Uint DataType = "uint"
|
||||
Float DataType = "float"
|
||||
String DataType = "string"
|
||||
Time DataType = "time"
|
||||
Bytes DataType = "bytes"
|
||||
)
|
||||
|
||||
const DefaultAutoIncrementIncrement int64 = 1
|
||||
|
||||
// Field is the representation of model schema's field
|
||||
type Field struct {
|
||||
Name string
|
||||
DBName string
|
||||
BindNames []string
|
||||
DataType DataType
|
||||
GORMDataType DataType
|
||||
PrimaryKey bool
|
||||
AutoIncrement bool
|
||||
AutoIncrementIncrement int64
|
||||
Creatable bool
|
||||
Updatable bool
|
||||
Readable bool
|
||||
AutoCreateTime TimeType
|
||||
AutoUpdateTime TimeType
|
||||
HasDefaultValue bool
|
||||
DefaultValue string
|
||||
DefaultValueInterface interface{}
|
||||
NotNull bool
|
||||
Unique bool
|
||||
Comment string
|
||||
Size int
|
||||
Precision int
|
||||
Scale int
|
||||
IgnoreMigration bool
|
||||
FieldType reflect.Type
|
||||
IndirectFieldType reflect.Type
|
||||
StructField reflect.StructField
|
||||
Tag reflect.StructTag
|
||||
TagSettings map[string]string
|
||||
Schema *Schema
|
||||
EmbeddedSchema *Schema
|
||||
OwnerSchema *Schema
|
||||
ReflectValueOf func(context.Context, reflect.Value) reflect.Value
|
||||
ValueOf func(context.Context, reflect.Value) (value interface{}, zero bool)
|
||||
Set func(context.Context, reflect.Value, interface{}) error
|
||||
Serializer SerializerInterface
|
||||
NewValuePool FieldNewValuePool
|
||||
|
||||
// In some db (e.g. MySQL), Unique and UniqueIndex are indistinguishable.
|
||||
// When a column has a (not Mul) UniqueIndex, Migrator always reports its gorm.ColumnType is Unique.
|
||||
// It causes field unnecessarily migration.
|
||||
// Therefore, we need to record the UniqueIndex on this column (exclude Mul UniqueIndex) for MigrateColumnUnique.
|
||||
UniqueIndex string
|
||||
}
|
||||
|
||||
func (field *Field) BindName() string {
|
||||
return strings.Join(field.BindNames, ".")
|
||||
}
|
||||
|
||||
// ParseField parses reflect.StructField to Field
|
||||
func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field {
|
||||
var (
|
||||
err error
|
||||
tagSetting = ParseTagSetting(fieldStruct.Tag.Get("gorm"), ";")
|
||||
)
|
||||
|
||||
field := &Field{
|
||||
Name: fieldStruct.Name,
|
||||
DBName: tagSetting["COLUMN"],
|
||||
BindNames: []string{fieldStruct.Name},
|
||||
FieldType: fieldStruct.Type,
|
||||
IndirectFieldType: fieldStruct.Type,
|
||||
StructField: fieldStruct,
|
||||
Tag: fieldStruct.Tag,
|
||||
TagSettings: tagSetting,
|
||||
Schema: schema,
|
||||
Creatable: true,
|
||||
Updatable: true,
|
||||
Readable: true,
|
||||
PrimaryKey: utils.CheckTruth(tagSetting["PRIMARYKEY"], tagSetting["PRIMARY_KEY"]),
|
||||
AutoIncrement: utils.CheckTruth(tagSetting["AUTOINCREMENT"]),
|
||||
HasDefaultValue: utils.CheckTruth(tagSetting["AUTOINCREMENT"]),
|
||||
NotNull: utils.CheckTruth(tagSetting["NOT NULL"], tagSetting["NOTNULL"]),
|
||||
Unique: utils.CheckTruth(tagSetting["UNIQUE"]),
|
||||
Comment: tagSetting["COMMENT"],
|
||||
AutoIncrementIncrement: DefaultAutoIncrementIncrement,
|
||||
}
|
||||
|
||||
for field.IndirectFieldType.Kind() == reflect.Ptr {
|
||||
field.IndirectFieldType = field.IndirectFieldType.Elem()
|
||||
}
|
||||
|
||||
fieldValue := reflect.New(field.IndirectFieldType)
|
||||
// if field is valuer, used its value or first field as data type
|
||||
valuer, isValuer := fieldValue.Interface().(driver.Valuer)
|
||||
if isValuer {
|
||||
if _, ok := fieldValue.Interface().(GormDataTypeInterface); !ok {
|
||||
if v, err := valuer.Value(); reflect.ValueOf(v).IsValid() && err == nil {
|
||||
fieldValue = reflect.ValueOf(v)
|
||||
}
|
||||
|
||||
// Use the field struct's first field type as data type, e.g: use `string` for sql.NullString
|
||||
var getRealFieldValue func(reflect.Value)
|
||||
getRealFieldValue = func(v reflect.Value) {
|
||||
var (
|
||||
rv = reflect.Indirect(v)
|
||||
rvType = rv.Type()
|
||||
)
|
||||
|
||||
if rv.Kind() == reflect.Struct && !rvType.ConvertibleTo(TimeReflectType) {
|
||||
for i := 0; i < rvType.NumField(); i++ {
|
||||
for key, value := range ParseTagSetting(rvType.Field(i).Tag.Get("gorm"), ";") {
|
||||
if _, ok := field.TagSettings[key]; !ok {
|
||||
field.TagSettings[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < rvType.NumField(); i++ {
|
||||
newFieldType := rvType.Field(i).Type
|
||||
for newFieldType.Kind() == reflect.Ptr {
|
||||
newFieldType = newFieldType.Elem()
|
||||
}
|
||||
|
||||
fieldValue = reflect.New(newFieldType)
|
||||
if rvType != reflect.Indirect(fieldValue).Type() {
|
||||
getRealFieldValue(fieldValue)
|
||||
}
|
||||
|
||||
if fieldValue.IsValid() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getRealFieldValue(fieldValue)
|
||||
}
|
||||
}
|
||||
|
||||
if v, isSerializer := fieldValue.Interface().(SerializerInterface); isSerializer {
|
||||
field.DataType = String
|
||||
field.Serializer = v
|
||||
} else {
|
||||
serializerName := field.TagSettings["JSON"]
|
||||
if serializerName == "" {
|
||||
serializerName = field.TagSettings["SERIALIZER"]
|
||||
}
|
||||
if serializerName != "" {
|
||||
if serializer, ok := GetSerializer(serializerName); ok {
|
||||
// Set default data type to string for serializer
|
||||
field.DataType = String
|
||||
field.Serializer = serializer
|
||||
} else {
|
||||
schema.err = fmt.Errorf("invalid serializer type %v", serializerName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if num, ok := field.TagSettings["AUTOINCREMENTINCREMENT"]; ok {
|
||||
field.AutoIncrementIncrement, _ = strconv.ParseInt(num, 10, 64)
|
||||
}
|
||||
|
||||
if v, ok := field.TagSettings["DEFAULT"]; ok {
|
||||
field.HasDefaultValue = true
|
||||
field.DefaultValue = v
|
||||
}
|
||||
|
||||
if num, ok := field.TagSettings["SIZE"]; ok {
|
||||
if field.Size, err = strconv.Atoi(num); err != nil {
|
||||
field.Size = -1
|
||||
}
|
||||
}
|
||||
|
||||
if p, ok := field.TagSettings["PRECISION"]; ok {
|
||||
field.Precision, _ = strconv.Atoi(p)
|
||||
}
|
||||
|
||||
if s, ok := field.TagSettings["SCALE"]; ok {
|
||||
field.Scale, _ = strconv.Atoi(s)
|
||||
}
|
||||
|
||||
// default value is function or null or blank (primary keys)
|
||||
field.DefaultValue = strings.TrimSpace(field.DefaultValue)
|
||||
skipParseDefaultValue := strings.Contains(field.DefaultValue, "(") &&
|
||||
strings.Contains(field.DefaultValue, ")") || strings.ToLower(field.DefaultValue) == "null" || field.DefaultValue == ""
|
||||
switch reflect.Indirect(fieldValue).Kind() {
|
||||
case reflect.Bool:
|
||||
field.DataType = Bool
|
||||
if field.HasDefaultValue && !skipParseDefaultValue {
|
||||
if field.DefaultValueInterface, err = strconv.ParseBool(field.DefaultValue); err != nil {
|
||||
schema.err = fmt.Errorf("failed to parse %s as default value for bool, got error: %v", field.DefaultValue, err)
|
||||
}
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
field.DataType = Int
|
||||
if field.HasDefaultValue && !skipParseDefaultValue {
|
||||
if field.DefaultValueInterface, err = strconv.ParseInt(field.DefaultValue, 0, 64); err != nil {
|
||||
schema.err = fmt.Errorf("failed to parse %s as default value for int, got error: %v", field.DefaultValue, err)
|
||||
}
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
field.DataType = Uint
|
||||
if field.HasDefaultValue && !skipParseDefaultValue {
|
||||
if field.DefaultValueInterface, err = strconv.ParseUint(field.DefaultValue, 0, 64); err != nil {
|
||||
schema.err = fmt.Errorf("failed to parse %s as default value for uint, got error: %v", field.DefaultValue, err)
|
||||
}
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
field.DataType = Float
|
||||
if field.HasDefaultValue && !skipParseDefaultValue {
|
||||
if field.DefaultValueInterface, err = strconv.ParseFloat(field.DefaultValue, 64); err != nil {
|
||||
schema.err = fmt.Errorf("failed to parse %s as default value for float, got error: %v", field.DefaultValue, err)
|
||||
}
|
||||
}
|
||||
case reflect.String:
|
||||
field.DataType = String
|
||||
if field.HasDefaultValue && !skipParseDefaultValue {
|
||||
field.DefaultValue = strings.Trim(field.DefaultValue, "'")
|
||||
field.DefaultValue = strings.Trim(field.DefaultValue, `"`)
|
||||
field.DefaultValueInterface = field.DefaultValue
|
||||
}
|
||||
case reflect.Struct:
|
||||
if _, ok := fieldValue.Interface().(*time.Time); ok {
|
||||
field.DataType = Time
|
||||
} else if fieldValue.Type().ConvertibleTo(TimeReflectType) {
|
||||
field.DataType = Time
|
||||
} else if fieldValue.Type().ConvertibleTo(TimePtrReflectType) {
|
||||
field.DataType = Time
|
||||
}
|
||||
if field.HasDefaultValue && !skipParseDefaultValue && field.DataType == Time {
|
||||
if t, err := now.Parse(field.DefaultValue); err == nil {
|
||||
field.DefaultValueInterface = t
|
||||
}
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
if reflect.Indirect(fieldValue).Type().Elem() == ByteReflectType && field.DataType == "" {
|
||||
field.DataType = Bytes
|
||||
}
|
||||
}
|
||||
|
||||
if dataTyper, ok := fieldValue.Interface().(GormDataTypeInterface); ok {
|
||||
field.DataType = DataType(dataTyper.GormDataType())
|
||||
}
|
||||
|
||||
if v, ok := field.TagSettings["AUTOCREATETIME"]; (ok && utils.CheckTruth(v)) || (!ok && field.Name == "CreatedAt" && (field.DataType == Time || field.DataType == Int || field.DataType == Uint)) {
|
||||
if field.DataType == Time {
|
||||
field.AutoCreateTime = UnixTime
|
||||
} else if strings.ToUpper(v) == "NANO" {
|
||||
field.AutoCreateTime = UnixNanosecond
|
||||
} else if strings.ToUpper(v) == "MILLI" {
|
||||
field.AutoCreateTime = UnixMillisecond
|
||||
} else {
|
||||
field.AutoCreateTime = UnixSecond
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := field.TagSettings["AUTOUPDATETIME"]; (ok && utils.CheckTruth(v)) || (!ok && field.Name == "UpdatedAt" && (field.DataType == Time || field.DataType == Int || field.DataType == Uint)) {
|
||||
if field.DataType == Time {
|
||||
field.AutoUpdateTime = UnixTime
|
||||
} else if strings.ToUpper(v) == "NANO" {
|
||||
field.AutoUpdateTime = UnixNanosecond
|
||||
} else if strings.ToUpper(v) == "MILLI" {
|
||||
field.AutoUpdateTime = UnixMillisecond
|
||||
} else {
|
||||
field.AutoUpdateTime = UnixSecond
|
||||
}
|
||||
}
|
||||
|
||||
if field.GORMDataType == "" {
|
||||
field.GORMDataType = field.DataType
|
||||
}
|
||||
|
||||
if val, ok := field.TagSettings["TYPE"]; ok {
|
||||
switch DataType(strings.ToLower(val)) {
|
||||
case Bool, Int, Uint, Float, String, Time, Bytes:
|
||||
field.DataType = DataType(strings.ToLower(val))
|
||||
default:
|
||||
field.DataType = DataType(val)
|
||||
}
|
||||
}
|
||||
|
||||
if field.Size == 0 {
|
||||
switch reflect.Indirect(fieldValue).Kind() {
|
||||
case reflect.Int, reflect.Int64, reflect.Uint, reflect.Uint64, reflect.Float64:
|
||||
field.Size = 64
|
||||
case reflect.Int8, reflect.Uint8:
|
||||
field.Size = 8
|
||||
case reflect.Int16, reflect.Uint16:
|
||||
field.Size = 16
|
||||
case reflect.Int32, reflect.Uint32, reflect.Float32:
|
||||
field.Size = 32
|
||||
}
|
||||
}
|
||||
|
||||
// setup permission
|
||||
if val, ok := field.TagSettings["-"]; ok {
|
||||
val = strings.ToLower(strings.TrimSpace(val))
|
||||
switch val {
|
||||
case "-":
|
||||
field.Creatable = false
|
||||
field.Updatable = false
|
||||
field.Readable = false
|
||||
field.DataType = ""
|
||||
case "all":
|
||||
field.Creatable = false
|
||||
field.Updatable = false
|
||||
field.Readable = false
|
||||
field.DataType = ""
|
||||
field.IgnoreMigration = true
|
||||
case "migration":
|
||||
field.IgnoreMigration = true
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := field.TagSettings["->"]; ok {
|
||||
field.Creatable = false
|
||||
field.Updatable = false
|
||||
if strings.ToLower(v) == "false" {
|
||||
field.Readable = false
|
||||
} else {
|
||||
field.Readable = true
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := field.TagSettings["<-"]; ok {
|
||||
field.Creatable = true
|
||||
field.Updatable = true
|
||||
|
||||
if v != "<-" {
|
||||
if !strings.Contains(v, "create") {
|
||||
field.Creatable = false
|
||||
}
|
||||
|
||||
if !strings.Contains(v, "update") {
|
||||
field.Updatable = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normal anonymous field or having `EMBEDDED` tag
|
||||
if _, ok := field.TagSettings["EMBEDDED"]; ok || (field.GORMDataType != Time && field.GORMDataType != Bytes && !isValuer &&
|
||||
fieldStruct.Anonymous && (field.Creatable || field.Updatable || field.Readable)) {
|
||||
kind := reflect.Indirect(fieldValue).Kind()
|
||||
switch kind {
|
||||
case reflect.Struct:
|
||||
var err error
|
||||
field.Creatable = false
|
||||
field.Updatable = false
|
||||
field.Readable = false
|
||||
|
||||
cacheStore := &sync.Map{}
|
||||
cacheStore.Store(embeddedCacheKey, true)
|
||||
if field.EmbeddedSchema, err = getOrParse(fieldValue.Interface(), cacheStore, embeddedNamer{Table: schema.Table, Namer: schema.namer}); err != nil {
|
||||
schema.err = err
|
||||
}
|
||||
|
||||
for _, ef := range field.EmbeddedSchema.Fields {
|
||||
ef.Schema = schema
|
||||
ef.OwnerSchema = field.EmbeddedSchema
|
||||
ef.BindNames = append([]string{fieldStruct.Name}, ef.BindNames...)
|
||||
// index is negative means is pointer
|
||||
if field.FieldType.Kind() == reflect.Struct {
|
||||
ef.StructField.Index = append([]int{fieldStruct.Index[0]}, ef.StructField.Index...)
|
||||
} else {
|
||||
ef.StructField.Index = append([]int{-fieldStruct.Index[0] - 1}, ef.StructField.Index...)
|
||||
}
|
||||
|
||||
if prefix, ok := field.TagSettings["EMBEDDEDPREFIX"]; ok && ef.DBName != "" {
|
||||
ef.DBName = prefix + ef.DBName
|
||||
}
|
||||
|
||||
if ef.PrimaryKey {
|
||||
if !utils.CheckTruth(ef.TagSettings["PRIMARYKEY"], ef.TagSettings["PRIMARY_KEY"]) {
|
||||
ef.PrimaryKey = false
|
||||
|
||||
if val, ok := ef.TagSettings["AUTOINCREMENT"]; !ok || !utils.CheckTruth(val) {
|
||||
ef.AutoIncrement = false
|
||||
}
|
||||
|
||||
if !ef.AutoIncrement && ef.DefaultValue == "" {
|
||||
ef.HasDefaultValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range field.TagSettings {
|
||||
ef.TagSettings[k] = v
|
||||
}
|
||||
}
|
||||
case reflect.Invalid, reflect.Uintptr, reflect.Array, reflect.Chan, reflect.Func, reflect.Interface,
|
||||
reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer, reflect.Complex64, reflect.Complex128:
|
||||
schema.err = fmt.Errorf("invalid embedded struct for %s's field %s, should be struct, but got %v", field.Schema.Name, field.Name, field.FieldType)
|
||||
}
|
||||
}
|
||||
|
||||
return field
|
||||
}
|
||||
|
||||
// create valuer, setter when parse struct
|
||||
func (field *Field) setupValuerAndSetter() {
|
||||
// Setup NewValuePool
|
||||
field.setupNewValuePool()
|
||||
|
||||
// ValueOf returns field's value and if it is zero
|
||||
fieldIndex := field.StructField.Index[0]
|
||||
switch {
|
||||
case len(field.StructField.Index) == 1 && fieldIndex > 0:
|
||||
field.ValueOf = func(ctx context.Context, value reflect.Value) (interface{}, bool) {
|
||||
fieldValue := reflect.Indirect(value).Field(fieldIndex)
|
||||
return fieldValue.Interface(), fieldValue.IsZero()
|
||||
}
|
||||
default:
|
||||
field.ValueOf = func(ctx context.Context, v reflect.Value) (interface{}, bool) {
|
||||
v = reflect.Indirect(v)
|
||||
for _, fieldIdx := range field.StructField.Index {
|
||||
if fieldIdx >= 0 {
|
||||
v = v.Field(fieldIdx)
|
||||
} else {
|
||||
v = v.Field(-fieldIdx - 1)
|
||||
|
||||
if !v.IsNil() {
|
||||
v = v.Elem()
|
||||
} else {
|
||||
return nil, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fv, zero := v.Interface(), v.IsZero()
|
||||
return fv, zero
|
||||
}
|
||||
}
|
||||
|
||||
if field.Serializer != nil {
|
||||
oldValuerOf := field.ValueOf
|
||||
field.ValueOf = func(ctx context.Context, v reflect.Value) (interface{}, bool) {
|
||||
value, zero := oldValuerOf(ctx, v)
|
||||
|
||||
s, ok := value.(SerializerValuerInterface)
|
||||
if !ok {
|
||||
s = field.Serializer
|
||||
}
|
||||
|
||||
return &serializer{
|
||||
Field: field,
|
||||
SerializeValuer: s,
|
||||
Destination: v,
|
||||
Context: ctx,
|
||||
fieldValue: value,
|
||||
}, zero
|
||||
}
|
||||
}
|
||||
|
||||
// ReflectValueOf returns field's reflect value
|
||||
switch {
|
||||
case len(field.StructField.Index) == 1 && fieldIndex > 0:
|
||||
field.ReflectValueOf = func(ctx context.Context, value reflect.Value) reflect.Value {
|
||||
return reflect.Indirect(value).Field(fieldIndex)
|
||||
}
|
||||
default:
|
||||
field.ReflectValueOf = func(ctx context.Context, v reflect.Value) reflect.Value {
|
||||
v = reflect.Indirect(v)
|
||||
for idx, fieldIdx := range field.StructField.Index {
|
||||
if fieldIdx >= 0 {
|
||||
v = v.Field(fieldIdx)
|
||||
} else {
|
||||
v = v.Field(-fieldIdx - 1)
|
||||
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
|
||||
if idx < len(field.StructField.Index)-1 {
|
||||
v = v.Elem()
|
||||
}
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
fallbackSetter := func(ctx context.Context, value reflect.Value, v interface{}, setter func(context.Context, reflect.Value, interface{}) error) (err error) {
|
||||
if v == nil {
|
||||
field.ReflectValueOf(ctx, value).Set(reflect.New(field.FieldType).Elem())
|
||||
} else {
|
||||
reflectV := reflect.ValueOf(v)
|
||||
// Optimal value type acquisition for v
|
||||
reflectValType := reflectV.Type()
|
||||
|
||||
if reflectValType.AssignableTo(field.FieldType) {
|
||||
if reflectV.Kind() == reflect.Ptr && reflectV.Elem().Kind() == reflect.Ptr {
|
||||
reflectV = reflect.Indirect(reflectV)
|
||||
}
|
||||
field.ReflectValueOf(ctx, value).Set(reflectV)
|
||||
return
|
||||
} else if reflectValType.ConvertibleTo(field.FieldType) {
|
||||
field.ReflectValueOf(ctx, value).Set(reflectV.Convert(field.FieldType))
|
||||
return
|
||||
} else if field.FieldType.Kind() == reflect.Ptr {
|
||||
fieldValue := field.ReflectValueOf(ctx, value)
|
||||
fieldType := field.FieldType.Elem()
|
||||
|
||||
if reflectValType.AssignableTo(fieldType) {
|
||||
if !fieldValue.IsValid() {
|
||||
fieldValue = reflect.New(fieldType)
|
||||
} else if fieldValue.IsNil() {
|
||||
fieldValue.Set(reflect.New(fieldType))
|
||||
}
|
||||
fieldValue.Elem().Set(reflectV)
|
||||
return
|
||||
} else if reflectValType.ConvertibleTo(fieldType) {
|
||||
if fieldValue.IsNil() {
|
||||
fieldValue.Set(reflect.New(fieldType))
|
||||
}
|
||||
|
||||
fieldValue.Elem().Set(reflectV.Convert(fieldType))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if reflectV.Kind() == reflect.Ptr {
|
||||
if reflectV.IsNil() {
|
||||
field.ReflectValueOf(ctx, value).Set(reflect.New(field.FieldType).Elem())
|
||||
} else if reflectV.Type().Elem().AssignableTo(field.FieldType) {
|
||||
field.ReflectValueOf(ctx, value).Set(reflectV.Elem())
|
||||
return
|
||||
} else {
|
||||
err = setter(ctx, value, reflectV.Elem().Interface())
|
||||
}
|
||||
} else if valuer, ok := v.(driver.Valuer); ok {
|
||||
if v, err = valuer.Value(); err == nil {
|
||||
err = setter(ctx, value, v)
|
||||
}
|
||||
} else if _, ok := v.(clause.Expr); !ok {
|
||||
return fmt.Errorf("failed to set value %#v to field %s", v, field.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Set
|
||||
switch field.FieldType.Kind() {
|
||||
case reflect.Bool:
|
||||
field.Set = func(ctx context.Context, value reflect.Value, v interface{}) error {
|
||||
switch data := v.(type) {
|
||||
case **bool:
|
||||
if data != nil && *data != nil {
|
||||
field.ReflectValueOf(ctx, value).SetBool(**data)
|
||||
}
|
||||
case bool:
|
||||
field.ReflectValueOf(ctx, value).SetBool(data)
|
||||
case int64:
|
||||
field.ReflectValueOf(ctx, value).SetBool(data > 0)
|
||||
case string:
|
||||
b, _ := strconv.ParseBool(data)
|
||||
field.ReflectValueOf(ctx, value).SetBool(b)
|
||||
default:
|
||||
return fallbackSetter(ctx, value, v, field.Set)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
field.Set = func(ctx context.Context, value reflect.Value, v interface{}) (err error) {
|
||||
switch data := v.(type) {
|
||||
case **int64:
|
||||
if data != nil && *data != nil {
|
||||
field.ReflectValueOf(ctx, value).SetInt(**data)
|
||||
}
|
||||
case **int:
|
||||
if data != nil && *data != nil {
|
||||
field.ReflectValueOf(ctx, value).SetInt(int64(**data))
|
||||
}
|
||||
case **int8:
|
||||
if data != nil && *data != nil {
|
||||
field.ReflectValueOf(ctx, value).SetInt(int64(**data))
|
||||
}
|
||||
case **int16:
|
||||
if data != nil && *data != nil {
|
||||
field.ReflectValueOf(ctx, value).SetInt(int64(**data))
|
||||
}
|
||||
case **int32:
|
||||
if data != nil && *data != nil {
|
||||
field.ReflectValueOf(ctx, value).SetInt(int64(**data))
|
||||
}
|
||||
case int64:
|
||||
field.ReflectValueOf(ctx, value).SetInt(data)
|
||||
case int:
|
||||
field.ReflectValueOf(ctx, value).SetInt(int64(data))
|
||||
case int8:
|
||||
field.ReflectValueOf(ctx, value).SetInt(int64(data))
|
||||
case int16:
|
||||
field.ReflectValueOf(ctx, value).SetInt(int64(data))
|
||||
case int32:
|
||||
field.ReflectValueOf(ctx, value).SetInt(int64(data))
|
||||
case uint:
|
||||
field.ReflectValueOf(ctx, value).SetInt(int64(data))
|
||||
case uint8:
|
||||
field.ReflectValueOf(ctx, value).SetInt(int64(data))
|
||||
case uint16:
|
||||
field.ReflectValueOf(ctx, value).SetInt(int64(data))
|
||||
case uint32:
|
||||
field.ReflectValueOf(ctx, value).SetInt(int64(data))
|
||||
case uint64:
|
||||
field.ReflectValueOf(ctx, value).SetInt(int64(data))
|
||||
case float32:
|
||||
field.ReflectValueOf(ctx, value).SetInt(int64(data))
|
||||
case float64:
|
||||
field.ReflectValueOf(ctx, value).SetInt(int64(data))
|
||||
case []byte:
|
||||
return field.Set(ctx, value, string(data))
|
||||
case string:
|
||||
if i, err := strconv.ParseInt(data, 0, 64); err == nil {
|
||||
field.ReflectValueOf(ctx, value).SetInt(i)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
case time.Time:
|
||||
if field.AutoCreateTime == UnixNanosecond || field.AutoUpdateTime == UnixNanosecond {
|
||||
field.ReflectValueOf(ctx, value).SetInt(data.UnixNano())
|
||||
} else if field.AutoCreateTime == UnixMillisecond || field.AutoUpdateTime == UnixMillisecond {
|
||||
field.ReflectValueOf(ctx, value).SetInt(data.UnixNano() / 1e6)
|
||||
} else {
|
||||
field.ReflectValueOf(ctx, value).SetInt(data.Unix())
|
||||
}
|
||||
case *time.Time:
|
||||
if data != nil {
|
||||
if field.AutoCreateTime == UnixNanosecond || field.AutoUpdateTime == UnixNanosecond {
|
||||
field.ReflectValueOf(ctx, value).SetInt(data.UnixNano())
|
||||
} else if field.AutoCreateTime == UnixMillisecond || field.AutoUpdateTime == UnixMillisecond {
|
||||
field.ReflectValueOf(ctx, value).SetInt(data.UnixNano() / 1e6)
|
||||
} else {
|
||||
field.ReflectValueOf(ctx, value).SetInt(data.Unix())
|
||||
}
|
||||
} else {
|
||||
field.ReflectValueOf(ctx, value).SetInt(0)
|
||||
}
|
||||
default:
|
||||
return fallbackSetter(ctx, value, v, field.Set)
|
||||
}
|
||||
return err
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
field.Set = func(ctx context.Context, value reflect.Value, v interface{}) (err error) {
|
||||
switch data := v.(type) {
|
||||
case **uint64:
|
||||
if data != nil && *data != nil {
|
||||
field.ReflectValueOf(ctx, value).SetUint(**data)
|
||||
}
|
||||
case **uint:
|
||||
if data != nil && *data != nil {
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(**data))
|
||||
}
|
||||
case **uint8:
|
||||
if data != nil && *data != nil {
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(**data))
|
||||
}
|
||||
case **uint16:
|
||||
if data != nil && *data != nil {
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(**data))
|
||||
}
|
||||
case **uint32:
|
||||
if data != nil && *data != nil {
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(**data))
|
||||
}
|
||||
case uint64:
|
||||
field.ReflectValueOf(ctx, value).SetUint(data)
|
||||
case uint:
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(data))
|
||||
case uint8:
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(data))
|
||||
case uint16:
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(data))
|
||||
case uint32:
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(data))
|
||||
case int64:
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(data))
|
||||
case int:
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(data))
|
||||
case int8:
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(data))
|
||||
case int16:
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(data))
|
||||
case int32:
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(data))
|
||||
case float32:
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(data))
|
||||
case float64:
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(data))
|
||||
case []byte:
|
||||
return field.Set(ctx, value, string(data))
|
||||
case time.Time:
|
||||
if field.AutoCreateTime == UnixNanosecond || field.AutoUpdateTime == UnixNanosecond {
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(data.UnixNano()))
|
||||
} else if field.AutoCreateTime == UnixMillisecond || field.AutoUpdateTime == UnixMillisecond {
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(data.UnixNano() / 1e6))
|
||||
} else {
|
||||
field.ReflectValueOf(ctx, value).SetUint(uint64(data.Unix()))
|
||||
}
|
||||
case string:
|
||||
if i, err := strconv.ParseUint(data, 0, 64); err == nil {
|
||||
field.ReflectValueOf(ctx, value).SetUint(i)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fallbackSetter(ctx, value, v, field.Set)
|
||||
}
|
||||
return err
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
field.Set = func(ctx context.Context, value reflect.Value, v interface{}) (err error) {
|
||||
switch data := v.(type) {
|
||||
case **float64:
|
||||
if data != nil && *data != nil {
|
||||
field.ReflectValueOf(ctx, value).SetFloat(**data)
|
||||
}
|
||||
case **float32:
|
||||
if data != nil && *data != nil {
|
||||
field.ReflectValueOf(ctx, value).SetFloat(float64(**data))
|
||||
}
|
||||
case float64:
|
||||
field.ReflectValueOf(ctx, value).SetFloat(data)
|
||||
case float32:
|
||||
field.ReflectValueOf(ctx, value).SetFloat(float64(data))
|
||||
case int64:
|
||||
field.ReflectValueOf(ctx, value).SetFloat(float64(data))
|
||||
case int:
|
||||
field.ReflectValueOf(ctx, value).SetFloat(float64(data))
|
||||
case int8:
|
||||
field.ReflectValueOf(ctx, value).SetFloat(float64(data))
|
||||
case int16:
|
||||
field.ReflectValueOf(ctx, value).SetFloat(float64(data))
|
||||
case int32:
|
||||
field.ReflectValueOf(ctx, value).SetFloat(float64(data))
|
||||
case uint:
|
||||
field.ReflectValueOf(ctx, value).SetFloat(float64(data))
|
||||
case uint8:
|
||||
field.ReflectValueOf(ctx, value).SetFloat(float64(data))
|
||||
case uint16:
|
||||
field.ReflectValueOf(ctx, value).SetFloat(float64(data))
|
||||
case uint32:
|
||||
field.ReflectValueOf(ctx, value).SetFloat(float64(data))
|
||||
case uint64:
|
||||
field.ReflectValueOf(ctx, value).SetFloat(float64(data))
|
||||
case []byte:
|
||||
return field.Set(ctx, value, string(data))
|
||||
case string:
|
||||
if i, err := strconv.ParseFloat(data, 64); err == nil {
|
||||
field.ReflectValueOf(ctx, value).SetFloat(i)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fallbackSetter(ctx, value, v, field.Set)
|
||||
}
|
||||
return err
|
||||
}
|
||||
case reflect.String:
|
||||
field.Set = func(ctx context.Context, value reflect.Value, v interface{}) (err error) {
|
||||
switch data := v.(type) {
|
||||
case **string:
|
||||
if data != nil && *data != nil {
|
||||
field.ReflectValueOf(ctx, value).SetString(**data)
|
||||
}
|
||||
case string:
|
||||
field.ReflectValueOf(ctx, value).SetString(data)
|
||||
case []byte:
|
||||
field.ReflectValueOf(ctx, value).SetString(string(data))
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
field.ReflectValueOf(ctx, value).SetString(utils.ToString(data))
|
||||
case float64, float32:
|
||||
field.ReflectValueOf(ctx, value).SetString(fmt.Sprintf("%."+strconv.Itoa(field.Precision)+"f", data))
|
||||
default:
|
||||
return fallbackSetter(ctx, value, v, field.Set)
|
||||
}
|
||||
return err
|
||||
}
|
||||
default:
|
||||
fieldValue := reflect.New(field.FieldType)
|
||||
switch fieldValue.Elem().Interface().(type) {
|
||||
case time.Time:
|
||||
field.Set = func(ctx context.Context, value reflect.Value, v interface{}) error {
|
||||
switch data := v.(type) {
|
||||
case **time.Time:
|
||||
if data != nil && *data != nil {
|
||||
field.Set(ctx, value, *data)
|
||||
}
|
||||
case time.Time:
|
||||
field.ReflectValueOf(ctx, value).Set(reflect.ValueOf(v))
|
||||
case *time.Time:
|
||||
if data != nil {
|
||||
field.ReflectValueOf(ctx, value).Set(reflect.ValueOf(data).Elem())
|
||||
} else {
|
||||
field.ReflectValueOf(ctx, value).Set(reflect.ValueOf(time.Time{}))
|
||||
}
|
||||
case string:
|
||||
if t, err := now.Parse(data); err == nil {
|
||||
field.ReflectValueOf(ctx, value).Set(reflect.ValueOf(t))
|
||||
} else {
|
||||
return fmt.Errorf("failed to set string %v to time.Time field %s, failed to parse it as time, got error %v", v, field.Name, err)
|
||||
}
|
||||
default:
|
||||
return fallbackSetter(ctx, value, v, field.Set)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case *time.Time:
|
||||
field.Set = func(ctx context.Context, value reflect.Value, v interface{}) error {
|
||||
switch data := v.(type) {
|
||||
case **time.Time:
|
||||
if data != nil && *data != nil {
|
||||
field.ReflectValueOf(ctx, value).Set(reflect.ValueOf(*data))
|
||||
}
|
||||
case time.Time:
|
||||
fieldValue := field.ReflectValueOf(ctx, value)
|
||||
if fieldValue.IsNil() {
|
||||
fieldValue.Set(reflect.New(field.FieldType.Elem()))
|
||||
}
|
||||
fieldValue.Elem().Set(reflect.ValueOf(v))
|
||||
case *time.Time:
|
||||
field.ReflectValueOf(ctx, value).Set(reflect.ValueOf(v))
|
||||
case string:
|
||||
if t, err := now.Parse(data); err == nil {
|
||||
fieldValue := field.ReflectValueOf(ctx, value)
|
||||
if fieldValue.IsNil() {
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
fieldValue.Set(reflect.New(field.FieldType.Elem()))
|
||||
}
|
||||
fieldValue.Elem().Set(reflect.ValueOf(t))
|
||||
} else {
|
||||
return fmt.Errorf("failed to set string %v to time.Time field %s, failed to parse it as time, got error %v", v, field.Name, err)
|
||||
}
|
||||
default:
|
||||
return fallbackSetter(ctx, value, v, field.Set)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
if _, ok := fieldValue.Elem().Interface().(sql.Scanner); ok {
|
||||
// pointer scanner
|
||||
field.Set = func(ctx context.Context, value reflect.Value, v interface{}) (err error) {
|
||||
reflectV := reflect.ValueOf(v)
|
||||
if !reflectV.IsValid() {
|
||||
field.ReflectValueOf(ctx, value).Set(reflect.New(field.FieldType).Elem())
|
||||
} else if reflectV.Kind() == reflect.Ptr && reflectV.IsNil() {
|
||||
return
|
||||
} else if reflectV.Type().AssignableTo(field.FieldType) {
|
||||
field.ReflectValueOf(ctx, value).Set(reflectV)
|
||||
} else if reflectV.Kind() == reflect.Ptr {
|
||||
return field.Set(ctx, value, reflectV.Elem().Interface())
|
||||
} else {
|
||||
fieldValue := field.ReflectValueOf(ctx, value)
|
||||
if fieldValue.IsNil() {
|
||||
fieldValue.Set(reflect.New(field.FieldType.Elem()))
|
||||
}
|
||||
|
||||
if valuer, ok := v.(driver.Valuer); ok {
|
||||
v, _ = valuer.Value()
|
||||
}
|
||||
|
||||
err = fieldValue.Interface().(sql.Scanner).Scan(v)
|
||||
}
|
||||
return
|
||||
}
|
||||
} else if _, ok := fieldValue.Interface().(sql.Scanner); ok {
|
||||
// struct scanner
|
||||
field.Set = func(ctx context.Context, value reflect.Value, v interface{}) (err error) {
|
||||
reflectV := reflect.ValueOf(v)
|
||||
if !reflectV.IsValid() {
|
||||
field.ReflectValueOf(ctx, value).Set(reflect.New(field.FieldType).Elem())
|
||||
} else if reflectV.Kind() == reflect.Ptr && reflectV.IsNil() {
|
||||
return
|
||||
} else if reflectV.Type().AssignableTo(field.FieldType) {
|
||||
field.ReflectValueOf(ctx, value).Set(reflectV)
|
||||
} else if reflectV.Kind() == reflect.Ptr {
|
||||
return field.Set(ctx, value, reflectV.Elem().Interface())
|
||||
} else {
|
||||
if valuer, ok := v.(driver.Valuer); ok {
|
||||
v, _ = valuer.Value()
|
||||
}
|
||||
|
||||
err = field.ReflectValueOf(ctx, value).Addr().Interface().(sql.Scanner).Scan(v)
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
field.Set = func(ctx context.Context, value reflect.Value, v interface{}) (err error) {
|
||||
return fallbackSetter(ctx, value, v, field.Set)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if field.Serializer != nil {
|
||||
var (
|
||||
oldFieldSetter = field.Set
|
||||
sameElemType bool
|
||||
sameType = field.FieldType == reflect.ValueOf(field.Serializer).Type()
|
||||
)
|
||||
|
||||
if reflect.ValueOf(field.Serializer).Kind() == reflect.Ptr {
|
||||
sameElemType = field.FieldType == reflect.ValueOf(field.Serializer).Type().Elem()
|
||||
}
|
||||
|
||||
serializerValue := reflect.Indirect(reflect.ValueOf(field.Serializer))
|
||||
serializerType := serializerValue.Type()
|
||||
field.Set = func(ctx context.Context, value reflect.Value, v interface{}) (err error) {
|
||||
if s, ok := v.(*serializer); ok {
|
||||
if s.fieldValue != nil {
|
||||
err = oldFieldSetter(ctx, value, s.fieldValue)
|
||||
} else if err = s.Serializer.Scan(ctx, field, value, s.value); err == nil {
|
||||
if sameElemType {
|
||||
field.ReflectValueOf(ctx, value).Set(reflect.ValueOf(s.Serializer).Elem())
|
||||
} else if sameType {
|
||||
field.ReflectValueOf(ctx, value).Set(reflect.ValueOf(s.Serializer))
|
||||
}
|
||||
si := reflect.New(serializerType)
|
||||
si.Elem().Set(serializerValue)
|
||||
s.Serializer = si.Interface().(SerializerInterface)
|
||||
}
|
||||
} else {
|
||||
err = oldFieldSetter(ctx, value, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (field *Field) setupNewValuePool() {
|
||||
if field.Serializer != nil {
|
||||
serializerValue := reflect.Indirect(reflect.ValueOf(field.Serializer))
|
||||
serializerType := serializerValue.Type()
|
||||
field.NewValuePool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
si := reflect.New(serializerType)
|
||||
si.Elem().Set(serializerValue)
|
||||
return &serializer{
|
||||
Field: field,
|
||||
Serializer: si.Interface().(SerializerInterface),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if field.NewValuePool == nil {
|
||||
field.NewValuePool = poolInitializer(reflect.PtrTo(field.IndirectFieldType))
|
||||
}
|
||||
}
|
||||
166
vendor/gorm.io/gorm/schema/index.go
generated
vendored
Normal file
166
vendor/gorm.io/gorm/schema/index.go
generated
vendored
Normal file
@@ -0,0 +1,166 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Index struct {
|
||||
Name string
|
||||
Class string // UNIQUE | FULLTEXT | SPATIAL
|
||||
Type string // btree, hash, gist, spgist, gin, and brin
|
||||
Where string
|
||||
Comment string
|
||||
Option string // WITH PARSER parser_name
|
||||
Fields []IndexOption // Note: IndexOption's Field maybe the same
|
||||
}
|
||||
|
||||
type IndexOption struct {
|
||||
*Field
|
||||
Expression string
|
||||
Sort string // DESC, ASC
|
||||
Collate string
|
||||
Length int
|
||||
priority int
|
||||
}
|
||||
|
||||
// ParseIndexes parse schema indexes
|
||||
func (schema *Schema) ParseIndexes() map[string]Index {
|
||||
indexes := map[string]Index{}
|
||||
|
||||
for _, field := range schema.Fields {
|
||||
if field.TagSettings["INDEX"] != "" || field.TagSettings["UNIQUEINDEX"] != "" {
|
||||
fieldIndexes, err := parseFieldIndexes(field)
|
||||
if err != nil {
|
||||
schema.err = err
|
||||
break
|
||||
}
|
||||
for _, index := range fieldIndexes {
|
||||
idx := indexes[index.Name]
|
||||
idx.Name = index.Name
|
||||
if idx.Class == "" {
|
||||
idx.Class = index.Class
|
||||
}
|
||||
if idx.Type == "" {
|
||||
idx.Type = index.Type
|
||||
}
|
||||
if idx.Where == "" {
|
||||
idx.Where = index.Where
|
||||
}
|
||||
if idx.Comment == "" {
|
||||
idx.Comment = index.Comment
|
||||
}
|
||||
if idx.Option == "" {
|
||||
idx.Option = index.Option
|
||||
}
|
||||
|
||||
idx.Fields = append(idx.Fields, index.Fields...)
|
||||
sort.Slice(idx.Fields, func(i, j int) bool {
|
||||
return idx.Fields[i].priority < idx.Fields[j].priority
|
||||
})
|
||||
|
||||
indexes[index.Name] = idx
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, index := range indexes {
|
||||
if index.Class == "UNIQUE" && len(index.Fields) == 1 {
|
||||
index.Fields[0].Field.UniqueIndex = index.Name
|
||||
}
|
||||
}
|
||||
return indexes
|
||||
}
|
||||
|
||||
func (schema *Schema) LookIndex(name string) *Index {
|
||||
if schema != nil {
|
||||
indexes := schema.ParseIndexes()
|
||||
for _, index := range indexes {
|
||||
if index.Name == name {
|
||||
return &index
|
||||
}
|
||||
|
||||
for _, field := range index.Fields {
|
||||
if field.Name == name {
|
||||
return &index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseFieldIndexes(field *Field) (indexes []Index, err error) {
|
||||
for _, value := range strings.Split(field.Tag.Get("gorm"), ";") {
|
||||
if value != "" {
|
||||
v := strings.Split(value, ":")
|
||||
k := strings.TrimSpace(strings.ToUpper(v[0]))
|
||||
if k == "INDEX" || k == "UNIQUEINDEX" {
|
||||
var (
|
||||
name string
|
||||
tag = strings.Join(v[1:], ":")
|
||||
idx = strings.Index(tag, ",")
|
||||
tagSetting = strings.Join(strings.Split(tag, ",")[1:], ",")
|
||||
settings = ParseTagSetting(tagSetting, ",")
|
||||
length, _ = strconv.Atoi(settings["LENGTH"])
|
||||
)
|
||||
|
||||
if idx == -1 {
|
||||
idx = len(tag)
|
||||
}
|
||||
|
||||
if idx != -1 {
|
||||
name = tag[0:idx]
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
subName := field.Name
|
||||
const key = "COMPOSITE"
|
||||
if composite, found := settings[key]; found {
|
||||
if len(composite) == 0 || composite == key {
|
||||
err = fmt.Errorf(
|
||||
"The composite tag of %s.%s cannot be empty",
|
||||
field.Schema.Name,
|
||||
field.Name)
|
||||
return
|
||||
}
|
||||
subName = composite
|
||||
}
|
||||
name = field.Schema.namer.IndexName(
|
||||
field.Schema.Table, subName)
|
||||
}
|
||||
|
||||
if (k == "UNIQUEINDEX") || settings["UNIQUE"] != "" {
|
||||
settings["CLASS"] = "UNIQUE"
|
||||
}
|
||||
|
||||
priority, err := strconv.Atoi(settings["PRIORITY"])
|
||||
if err != nil {
|
||||
priority = 10
|
||||
}
|
||||
|
||||
indexes = append(indexes, Index{
|
||||
Name: name,
|
||||
Class: settings["CLASS"],
|
||||
Type: settings["TYPE"],
|
||||
Where: settings["WHERE"],
|
||||
Comment: settings["COMMENT"],
|
||||
Option: settings["OPTION"],
|
||||
Fields: []IndexOption{{
|
||||
Field: field,
|
||||
Expression: settings["EXPRESSION"],
|
||||
Sort: settings["SORT"],
|
||||
Collate: settings["COLLATE"],
|
||||
Length: length,
|
||||
priority: priority,
|
||||
}},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
42
vendor/gorm.io/gorm/schema/interfaces.go
generated
vendored
Normal file
42
vendor/gorm.io/gorm/schema/interfaces.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// ConstraintInterface database constraint interface
|
||||
type ConstraintInterface interface {
|
||||
GetName() string
|
||||
Build() (sql string, vars []interface{})
|
||||
}
|
||||
|
||||
// GormDataTypeInterface gorm data type interface
|
||||
type GormDataTypeInterface interface {
|
||||
GormDataType() string
|
||||
}
|
||||
|
||||
// FieldNewValuePool field new scan value pool
|
||||
type FieldNewValuePool interface {
|
||||
Get() interface{}
|
||||
Put(interface{})
|
||||
}
|
||||
|
||||
// CreateClausesInterface create clauses interface
|
||||
type CreateClausesInterface interface {
|
||||
CreateClauses(*Field) []clause.Interface
|
||||
}
|
||||
|
||||
// QueryClausesInterface query clauses interface
|
||||
type QueryClausesInterface interface {
|
||||
QueryClauses(*Field) []clause.Interface
|
||||
}
|
||||
|
||||
// UpdateClausesInterface update clauses interface
|
||||
type UpdateClausesInterface interface {
|
||||
UpdateClauses(*Field) []clause.Interface
|
||||
}
|
||||
|
||||
// DeleteClausesInterface delete clauses interface
|
||||
type DeleteClausesInterface interface {
|
||||
DeleteClauses(*Field) []clause.Interface
|
||||
}
|
||||
194
vendor/gorm.io/gorm/schema/naming.go
generated
vendored
Normal file
194
vendor/gorm.io/gorm/schema/naming.go
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/jinzhu/inflection"
|
||||
)
|
||||
|
||||
// Namer namer interface
|
||||
type Namer interface {
|
||||
TableName(table string) string
|
||||
SchemaName(table string) string
|
||||
ColumnName(table, column string) string
|
||||
JoinTableName(joinTable string) string
|
||||
RelationshipFKName(Relationship) string
|
||||
CheckerName(table, column string) string
|
||||
IndexName(table, column string) string
|
||||
UniqueName(table, column string) string
|
||||
}
|
||||
|
||||
// Replacer replacer interface like strings.Replacer
|
||||
type Replacer interface {
|
||||
Replace(name string) string
|
||||
}
|
||||
|
||||
var _ Namer = (*NamingStrategy)(nil)
|
||||
|
||||
// NamingStrategy tables, columns naming strategy
|
||||
type NamingStrategy struct {
|
||||
TablePrefix string
|
||||
SingularTable bool
|
||||
NameReplacer Replacer
|
||||
NoLowerCase bool
|
||||
IdentifierMaxLength int
|
||||
}
|
||||
|
||||
// TableName convert string to table name
|
||||
func (ns NamingStrategy) TableName(str string) string {
|
||||
if ns.SingularTable {
|
||||
return ns.TablePrefix + ns.toDBName(str)
|
||||
}
|
||||
return ns.TablePrefix + inflection.Plural(ns.toDBName(str))
|
||||
}
|
||||
|
||||
// SchemaName generate schema name from table name, don't guarantee it is the reverse value of TableName
|
||||
func (ns NamingStrategy) SchemaName(table string) string {
|
||||
table = strings.TrimPrefix(table, ns.TablePrefix)
|
||||
|
||||
if ns.SingularTable {
|
||||
return ns.toSchemaName(table)
|
||||
}
|
||||
return ns.toSchemaName(inflection.Singular(table))
|
||||
}
|
||||
|
||||
// ColumnName convert string to column name
|
||||
func (ns NamingStrategy) ColumnName(table, column string) string {
|
||||
return ns.toDBName(column)
|
||||
}
|
||||
|
||||
// JoinTableName convert string to join table name
|
||||
func (ns NamingStrategy) JoinTableName(str string) string {
|
||||
if !ns.NoLowerCase && strings.ToLower(str) == str {
|
||||
return ns.TablePrefix + str
|
||||
}
|
||||
|
||||
if ns.SingularTable {
|
||||
return ns.TablePrefix + ns.toDBName(str)
|
||||
}
|
||||
return ns.TablePrefix + inflection.Plural(ns.toDBName(str))
|
||||
}
|
||||
|
||||
// RelationshipFKName generate fk name for relation
|
||||
func (ns NamingStrategy) RelationshipFKName(rel Relationship) string {
|
||||
return ns.formatName("fk", rel.Schema.Table, ns.toDBName(rel.Name))
|
||||
}
|
||||
|
||||
// CheckerName generate checker name
|
||||
func (ns NamingStrategy) CheckerName(table, column string) string {
|
||||
return ns.formatName("chk", table, column)
|
||||
}
|
||||
|
||||
// IndexName generate index name
|
||||
func (ns NamingStrategy) IndexName(table, column string) string {
|
||||
return ns.formatName("idx", table, ns.toDBName(column))
|
||||
}
|
||||
|
||||
// UniqueName generate unique constraint name
|
||||
func (ns NamingStrategy) UniqueName(table, column string) string {
|
||||
return ns.formatName("uni", table, ns.toDBName(column))
|
||||
}
|
||||
|
||||
func (ns NamingStrategy) formatName(prefix, table, name string) string {
|
||||
formattedName := strings.ReplaceAll(strings.Join([]string{
|
||||
prefix, table, name,
|
||||
}, "_"), ".", "_")
|
||||
|
||||
if ns.IdentifierMaxLength == 0 {
|
||||
ns.IdentifierMaxLength = 64
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(formattedName) > ns.IdentifierMaxLength {
|
||||
h := sha1.New()
|
||||
h.Write([]byte(formattedName))
|
||||
bs := h.Sum(nil)
|
||||
|
||||
formattedName = formattedName[0:ns.IdentifierMaxLength-8] + hex.EncodeToString(bs)[:8]
|
||||
}
|
||||
return formattedName
|
||||
}
|
||||
|
||||
var (
|
||||
// https://github.com/golang/lint/blob/master/lint.go#L770
|
||||
commonInitialisms = []string{"API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "LHS", "QPS", "RAM", "RHS", "RPC", "SLA", "SMTP", "SSH", "TLS", "TTL", "UID", "UI", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XSRF", "XSS"}
|
||||
commonInitialismsReplacer *strings.Replacer
|
||||
)
|
||||
|
||||
func init() {
|
||||
commonInitialismsForReplacer := make([]string, 0, len(commonInitialisms))
|
||||
for _, initialism := range commonInitialisms {
|
||||
commonInitialismsForReplacer = append(commonInitialismsForReplacer, initialism, strings.Title(strings.ToLower(initialism)))
|
||||
}
|
||||
commonInitialismsReplacer = strings.NewReplacer(commonInitialismsForReplacer...)
|
||||
}
|
||||
|
||||
func (ns NamingStrategy) toDBName(name string) string {
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if ns.NameReplacer != nil {
|
||||
tmpName := ns.NameReplacer.Replace(name)
|
||||
|
||||
if tmpName == "" {
|
||||
return name
|
||||
}
|
||||
|
||||
name = tmpName
|
||||
}
|
||||
|
||||
if ns.NoLowerCase {
|
||||
return name
|
||||
}
|
||||
|
||||
var (
|
||||
value = commonInitialismsReplacer.Replace(name)
|
||||
buf strings.Builder
|
||||
lastCase, nextCase, nextNumber bool // upper case == true
|
||||
curCase = value[0] <= 'Z' && value[0] >= 'A'
|
||||
)
|
||||
|
||||
for i, v := range value[:len(value)-1] {
|
||||
nextCase = value[i+1] <= 'Z' && value[i+1] >= 'A'
|
||||
nextNumber = value[i+1] >= '0' && value[i+1] <= '9'
|
||||
|
||||
if curCase {
|
||||
if lastCase && (nextCase || nextNumber) {
|
||||
buf.WriteRune(v + 32)
|
||||
} else {
|
||||
if i > 0 && value[i-1] != '_' && value[i+1] != '_' {
|
||||
buf.WriteByte('_')
|
||||
}
|
||||
buf.WriteRune(v + 32)
|
||||
}
|
||||
} else {
|
||||
buf.WriteRune(v)
|
||||
}
|
||||
|
||||
lastCase = curCase
|
||||
curCase = nextCase
|
||||
}
|
||||
|
||||
if curCase {
|
||||
if !lastCase && len(value) > 1 {
|
||||
buf.WriteByte('_')
|
||||
}
|
||||
buf.WriteByte(value[len(value)-1] + 32)
|
||||
} else {
|
||||
buf.WriteByte(value[len(value)-1])
|
||||
}
|
||||
ret := buf.String()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (ns NamingStrategy) toSchemaName(name string) string {
|
||||
result := strings.ReplaceAll(strings.Title(strings.ReplaceAll(name, "_", " ")), " ", "")
|
||||
for _, initialism := range commonInitialisms {
|
||||
result = regexp.MustCompile(strings.Title(strings.ToLower(initialism))+"([A-Z]|$|_)").ReplaceAllString(result, initialism+"$1")
|
||||
}
|
||||
return result
|
||||
}
|
||||
19
vendor/gorm.io/gorm/schema/pool.go
generated
vendored
Normal file
19
vendor/gorm.io/gorm/schema/pool.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// sync pools
|
||||
var (
|
||||
normalPool sync.Map
|
||||
poolInitializer = func(reflectType reflect.Type) FieldNewValuePool {
|
||||
v, _ := normalPool.LoadOrStore(reflectType, &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return reflect.New(reflectType).Interface()
|
||||
},
|
||||
})
|
||||
return v.(FieldNewValuePool)
|
||||
}
|
||||
)
|
||||
764
vendor/gorm.io/gorm/schema/relationship.go
generated
vendored
Normal file
764
vendor/gorm.io/gorm/schema/relationship.go
generated
vendored
Normal file
@@ -0,0 +1,764 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/jinzhu/inflection"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// RelationshipType relationship type
|
||||
type RelationshipType string
|
||||
|
||||
const (
|
||||
HasOne RelationshipType = "has_one" // HasOneRel has one relationship
|
||||
HasMany RelationshipType = "has_many" // HasManyRel has many relationship
|
||||
BelongsTo RelationshipType = "belongs_to" // BelongsToRel belongs to relationship
|
||||
Many2Many RelationshipType = "many_to_many" // Many2ManyRel many to many relationship
|
||||
has RelationshipType = "has"
|
||||
)
|
||||
|
||||
type Relationships struct {
|
||||
HasOne []*Relationship
|
||||
BelongsTo []*Relationship
|
||||
HasMany []*Relationship
|
||||
Many2Many []*Relationship
|
||||
Relations map[string]*Relationship
|
||||
|
||||
EmbeddedRelations map[string]*Relationships
|
||||
}
|
||||
|
||||
type Relationship struct {
|
||||
Name string
|
||||
Type RelationshipType
|
||||
Field *Field
|
||||
Polymorphic *Polymorphic
|
||||
References []*Reference
|
||||
Schema *Schema
|
||||
FieldSchema *Schema
|
||||
JoinTable *Schema
|
||||
foreignKeys, primaryKeys []string
|
||||
}
|
||||
|
||||
type Polymorphic struct {
|
||||
PolymorphicID *Field
|
||||
PolymorphicType *Field
|
||||
Value string
|
||||
}
|
||||
|
||||
type Reference struct {
|
||||
PrimaryKey *Field
|
||||
PrimaryValue string
|
||||
ForeignKey *Field
|
||||
OwnPrimaryKey bool
|
||||
}
|
||||
|
||||
func (schema *Schema) parseRelation(field *Field) *Relationship {
|
||||
var (
|
||||
err error
|
||||
fieldValue = reflect.New(field.IndirectFieldType).Interface()
|
||||
relation = &Relationship{
|
||||
Name: field.Name,
|
||||
Field: field,
|
||||
Schema: schema,
|
||||
foreignKeys: toColumns(field.TagSettings["FOREIGNKEY"]),
|
||||
primaryKeys: toColumns(field.TagSettings["REFERENCES"]),
|
||||
}
|
||||
)
|
||||
|
||||
cacheStore := schema.cacheStore
|
||||
|
||||
if relation.FieldSchema, err = getOrParse(fieldValue, cacheStore, schema.namer); err != nil {
|
||||
schema.err = err
|
||||
return nil
|
||||
}
|
||||
|
||||
if hasPolymorphicRelation(field.TagSettings) {
|
||||
schema.buildPolymorphicRelation(relation, field)
|
||||
} else if many2many := field.TagSettings["MANY2MANY"]; many2many != "" {
|
||||
schema.buildMany2ManyRelation(relation, field, many2many)
|
||||
} else if belongsTo := field.TagSettings["BELONGSTO"]; belongsTo != "" {
|
||||
schema.guessRelation(relation, field, guessBelongs)
|
||||
} else {
|
||||
switch field.IndirectFieldType.Kind() {
|
||||
case reflect.Struct:
|
||||
schema.guessRelation(relation, field, guessGuess)
|
||||
case reflect.Slice:
|
||||
schema.guessRelation(relation, field, guessHas)
|
||||
default:
|
||||
schema.err = fmt.Errorf("unsupported data type %v for %v on field %s", relation.FieldSchema, schema,
|
||||
field.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if relation.Type == has {
|
||||
// don't add relations to embedded schema, which might be shared
|
||||
if relation.FieldSchema != relation.Schema && relation.Polymorphic == nil && field.OwnerSchema == nil {
|
||||
relation.FieldSchema.Relationships.Relations["_"+relation.Schema.Name+"_"+relation.Name] = relation
|
||||
}
|
||||
|
||||
switch field.IndirectFieldType.Kind() {
|
||||
case reflect.Struct:
|
||||
relation.Type = HasOne
|
||||
case reflect.Slice:
|
||||
relation.Type = HasMany
|
||||
}
|
||||
}
|
||||
|
||||
if schema.err == nil {
|
||||
schema.setRelation(relation)
|
||||
switch relation.Type {
|
||||
case HasOne:
|
||||
schema.Relationships.HasOne = append(schema.Relationships.HasOne, relation)
|
||||
case HasMany:
|
||||
schema.Relationships.HasMany = append(schema.Relationships.HasMany, relation)
|
||||
case BelongsTo:
|
||||
schema.Relationships.BelongsTo = append(schema.Relationships.BelongsTo, relation)
|
||||
case Many2Many:
|
||||
schema.Relationships.Many2Many = append(schema.Relationships.Many2Many, relation)
|
||||
}
|
||||
}
|
||||
|
||||
return relation
|
||||
}
|
||||
|
||||
// hasPolymorphicRelation check if has polymorphic relation
|
||||
// 1. `POLYMORPHIC` tag
|
||||
// 2. `POLYMORPHICTYPE` and `POLYMORPHICID` tag
|
||||
func hasPolymorphicRelation(tagSettings map[string]string) bool {
|
||||
if _, ok := tagSettings["POLYMORPHIC"]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
_, hasType := tagSettings["POLYMORPHICTYPE"]
|
||||
_, hasId := tagSettings["POLYMORPHICID"]
|
||||
|
||||
return hasType && hasId
|
||||
}
|
||||
|
||||
func (schema *Schema) setRelation(relation *Relationship) {
|
||||
// set non-embedded relation
|
||||
if rel := schema.Relationships.Relations[relation.Name]; rel != nil {
|
||||
if len(rel.Field.BindNames) > 1 {
|
||||
schema.Relationships.Relations[relation.Name] = relation
|
||||
}
|
||||
} else {
|
||||
schema.Relationships.Relations[relation.Name] = relation
|
||||
}
|
||||
|
||||
// set embedded relation
|
||||
if len(relation.Field.BindNames) <= 1 {
|
||||
return
|
||||
}
|
||||
relationships := &schema.Relationships
|
||||
for i, name := range relation.Field.BindNames {
|
||||
if i < len(relation.Field.BindNames)-1 {
|
||||
if relationships.EmbeddedRelations == nil {
|
||||
relationships.EmbeddedRelations = map[string]*Relationships{}
|
||||
}
|
||||
if r := relationships.EmbeddedRelations[name]; r == nil {
|
||||
relationships.EmbeddedRelations[name] = &Relationships{}
|
||||
}
|
||||
relationships = relationships.EmbeddedRelations[name]
|
||||
} else {
|
||||
if relationships.Relations == nil {
|
||||
relationships.Relations = map[string]*Relationship{}
|
||||
}
|
||||
relationships.Relations[relation.Name] = relation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// User has many Toys, its `Polymorphic` is `Owner`, Pet has one Toy, its `Polymorphic` is `Owner`
|
||||
//
|
||||
// type User struct {
|
||||
// Toys []Toy `gorm:"polymorphic:Owner;"`
|
||||
// }
|
||||
// type Pet struct {
|
||||
// Toy Toy `gorm:"polymorphic:Owner;"`
|
||||
// }
|
||||
// type Toy struct {
|
||||
// OwnerID int
|
||||
// OwnerType string
|
||||
// }
|
||||
func (schema *Schema) buildPolymorphicRelation(relation *Relationship, field *Field) {
|
||||
polymorphic := field.TagSettings["POLYMORPHIC"]
|
||||
|
||||
relation.Polymorphic = &Polymorphic{
|
||||
Value: schema.Table,
|
||||
}
|
||||
|
||||
var (
|
||||
typeName = polymorphic + "Type"
|
||||
typeId = polymorphic + "ID"
|
||||
)
|
||||
|
||||
if value, ok := field.TagSettings["POLYMORPHICTYPE"]; ok {
|
||||
typeName = strings.TrimSpace(value)
|
||||
}
|
||||
|
||||
if value, ok := field.TagSettings["POLYMORPHICID"]; ok {
|
||||
typeId = strings.TrimSpace(value)
|
||||
}
|
||||
|
||||
relation.Polymorphic.PolymorphicType = relation.FieldSchema.FieldsByName[typeName]
|
||||
relation.Polymorphic.PolymorphicID = relation.FieldSchema.FieldsByName[typeId]
|
||||
|
||||
if value, ok := field.TagSettings["POLYMORPHICVALUE"]; ok {
|
||||
relation.Polymorphic.Value = strings.TrimSpace(value)
|
||||
}
|
||||
|
||||
if relation.Polymorphic.PolymorphicType == nil {
|
||||
schema.err = fmt.Errorf("invalid polymorphic type %v for %v on field %s, missing field %s",
|
||||
relation.FieldSchema, schema, field.Name, polymorphic+"Type")
|
||||
}
|
||||
|
||||
if relation.Polymorphic.PolymorphicID == nil {
|
||||
schema.err = fmt.Errorf("invalid polymorphic type %v for %v on field %s, missing field %s",
|
||||
relation.FieldSchema, schema, field.Name, polymorphic+"ID")
|
||||
}
|
||||
|
||||
if schema.err == nil {
|
||||
relation.References = append(relation.References, &Reference{
|
||||
PrimaryValue: relation.Polymorphic.Value,
|
||||
ForeignKey: relation.Polymorphic.PolymorphicType,
|
||||
})
|
||||
|
||||
primaryKeyField := schema.PrioritizedPrimaryField
|
||||
if len(relation.foreignKeys) > 0 {
|
||||
if primaryKeyField = schema.LookUpField(relation.foreignKeys[0]); primaryKeyField == nil || len(relation.foreignKeys) > 1 {
|
||||
schema.err = fmt.Errorf("invalid polymorphic foreign keys %+v for %v on field %s", relation.foreignKeys,
|
||||
schema, field.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if primaryKeyField == nil {
|
||||
schema.err = fmt.Errorf("invalid polymorphic type %v for %v on field %s, missing primaryKey field",
|
||||
relation.FieldSchema, schema, field.Name)
|
||||
return
|
||||
}
|
||||
|
||||
// use same data type for foreign keys
|
||||
if copyableDataType(primaryKeyField.DataType) {
|
||||
relation.Polymorphic.PolymorphicID.DataType = primaryKeyField.DataType
|
||||
}
|
||||
relation.Polymorphic.PolymorphicID.GORMDataType = primaryKeyField.GORMDataType
|
||||
if relation.Polymorphic.PolymorphicID.Size == 0 {
|
||||
relation.Polymorphic.PolymorphicID.Size = primaryKeyField.Size
|
||||
}
|
||||
|
||||
relation.References = append(relation.References, &Reference{
|
||||
PrimaryKey: primaryKeyField,
|
||||
ForeignKey: relation.Polymorphic.PolymorphicID,
|
||||
OwnPrimaryKey: true,
|
||||
})
|
||||
}
|
||||
|
||||
relation.Type = has
|
||||
}
|
||||
|
||||
func (schema *Schema) buildMany2ManyRelation(relation *Relationship, field *Field, many2many string) {
|
||||
relation.Type = Many2Many
|
||||
|
||||
var (
|
||||
err error
|
||||
joinTableFields []reflect.StructField
|
||||
fieldsMap = map[string]*Field{}
|
||||
ownFieldsMap = map[string]*Field{} // fix self join many2many
|
||||
referFieldsMap = map[string]*Field{}
|
||||
joinForeignKeys = toColumns(field.TagSettings["JOINFOREIGNKEY"])
|
||||
joinReferences = toColumns(field.TagSettings["JOINREFERENCES"])
|
||||
)
|
||||
|
||||
ownForeignFields := schema.PrimaryFields
|
||||
refForeignFields := relation.FieldSchema.PrimaryFields
|
||||
|
||||
if len(relation.foreignKeys) > 0 {
|
||||
ownForeignFields = []*Field{}
|
||||
for _, foreignKey := range relation.foreignKeys {
|
||||
if field := schema.LookUpField(foreignKey); field != nil {
|
||||
ownForeignFields = append(ownForeignFields, field)
|
||||
} else {
|
||||
schema.err = fmt.Errorf("invalid foreign key: %s", foreignKey)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(relation.primaryKeys) > 0 {
|
||||
refForeignFields = []*Field{}
|
||||
for _, foreignKey := range relation.primaryKeys {
|
||||
if field := relation.FieldSchema.LookUpField(foreignKey); field != nil {
|
||||
refForeignFields = append(refForeignFields, field)
|
||||
} else {
|
||||
schema.err = fmt.Errorf("invalid foreign key: %s", foreignKey)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for idx, ownField := range ownForeignFields {
|
||||
joinFieldName := strings.Title(schema.Name) + ownField.Name
|
||||
if len(joinForeignKeys) > idx {
|
||||
joinFieldName = strings.Title(joinForeignKeys[idx])
|
||||
}
|
||||
|
||||
ownFieldsMap[joinFieldName] = ownField
|
||||
fieldsMap[joinFieldName] = ownField
|
||||
joinTableFields = append(joinTableFields, reflect.StructField{
|
||||
Name: joinFieldName,
|
||||
PkgPath: ownField.StructField.PkgPath,
|
||||
Type: ownField.StructField.Type,
|
||||
Tag: removeSettingFromTag(appendSettingFromTag(ownField.StructField.Tag, "primaryKey"),
|
||||
"column", "autoincrement", "index", "unique", "uniqueindex"),
|
||||
})
|
||||
}
|
||||
|
||||
for idx, relField := range refForeignFields {
|
||||
joinFieldName := strings.Title(relation.FieldSchema.Name) + relField.Name
|
||||
|
||||
if _, ok := ownFieldsMap[joinFieldName]; ok {
|
||||
if field.Name != relation.FieldSchema.Name {
|
||||
joinFieldName = inflection.Singular(field.Name) + relField.Name
|
||||
} else {
|
||||
joinFieldName += "Reference"
|
||||
}
|
||||
}
|
||||
|
||||
if len(joinReferences) > idx {
|
||||
joinFieldName = strings.Title(joinReferences[idx])
|
||||
}
|
||||
|
||||
referFieldsMap[joinFieldName] = relField
|
||||
|
||||
if _, ok := fieldsMap[joinFieldName]; !ok {
|
||||
fieldsMap[joinFieldName] = relField
|
||||
joinTableFields = append(joinTableFields, reflect.StructField{
|
||||
Name: joinFieldName,
|
||||
PkgPath: relField.StructField.PkgPath,
|
||||
Type: relField.StructField.Type,
|
||||
Tag: removeSettingFromTag(appendSettingFromTag(relField.StructField.Tag, "primaryKey"),
|
||||
"column", "autoincrement", "index", "unique", "uniqueindex"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
joinTableFields = append(joinTableFields, reflect.StructField{
|
||||
Name: strings.Title(schema.Name) + field.Name,
|
||||
Type: schema.ModelType,
|
||||
Tag: `gorm:"-"`,
|
||||
})
|
||||
|
||||
if relation.JoinTable, err = Parse(reflect.New(reflect.StructOf(joinTableFields)).Interface(), schema.cacheStore,
|
||||
schema.namer); err != nil {
|
||||
schema.err = err
|
||||
}
|
||||
relation.JoinTable.Name = many2many
|
||||
relation.JoinTable.Table = schema.namer.JoinTableName(many2many)
|
||||
relation.JoinTable.PrimaryFields = make([]*Field, 0, len(relation.JoinTable.Fields))
|
||||
|
||||
relName := relation.Schema.Name
|
||||
relRefName := relation.FieldSchema.Name
|
||||
if relName == relRefName {
|
||||
relRefName = relation.Field.Name
|
||||
}
|
||||
|
||||
if _, ok := relation.JoinTable.Relationships.Relations[relName]; !ok {
|
||||
relation.JoinTable.Relationships.Relations[relName] = &Relationship{
|
||||
Name: relName,
|
||||
Type: BelongsTo,
|
||||
Schema: relation.JoinTable,
|
||||
FieldSchema: relation.Schema,
|
||||
}
|
||||
} else {
|
||||
relation.JoinTable.Relationships.Relations[relName].References = []*Reference{}
|
||||
}
|
||||
|
||||
if _, ok := relation.JoinTable.Relationships.Relations[relRefName]; !ok {
|
||||
relation.JoinTable.Relationships.Relations[relRefName] = &Relationship{
|
||||
Name: relRefName,
|
||||
Type: BelongsTo,
|
||||
Schema: relation.JoinTable,
|
||||
FieldSchema: relation.FieldSchema,
|
||||
}
|
||||
} else {
|
||||
relation.JoinTable.Relationships.Relations[relRefName].References = []*Reference{}
|
||||
}
|
||||
|
||||
// build references
|
||||
for _, f := range relation.JoinTable.Fields {
|
||||
if f.Creatable || f.Readable || f.Updatable {
|
||||
// use same data type for foreign keys
|
||||
if copyableDataType(fieldsMap[f.Name].DataType) {
|
||||
f.DataType = fieldsMap[f.Name].DataType
|
||||
}
|
||||
f.GORMDataType = fieldsMap[f.Name].GORMDataType
|
||||
if f.Size == 0 {
|
||||
f.Size = fieldsMap[f.Name].Size
|
||||
}
|
||||
relation.JoinTable.PrimaryFields = append(relation.JoinTable.PrimaryFields, f)
|
||||
|
||||
if of, ok := ownFieldsMap[f.Name]; ok {
|
||||
joinRel := relation.JoinTable.Relationships.Relations[relName]
|
||||
joinRel.Field = relation.Field
|
||||
joinRel.References = append(joinRel.References, &Reference{
|
||||
PrimaryKey: of,
|
||||
ForeignKey: f,
|
||||
})
|
||||
|
||||
relation.References = append(relation.References, &Reference{
|
||||
PrimaryKey: of,
|
||||
ForeignKey: f,
|
||||
OwnPrimaryKey: true,
|
||||
})
|
||||
}
|
||||
|
||||
if rf, ok := referFieldsMap[f.Name]; ok {
|
||||
joinRefRel := relation.JoinTable.Relationships.Relations[relRefName]
|
||||
if joinRefRel.Field == nil {
|
||||
joinRefRel.Field = relation.Field
|
||||
}
|
||||
joinRefRel.References = append(joinRefRel.References, &Reference{
|
||||
PrimaryKey: rf,
|
||||
ForeignKey: f,
|
||||
})
|
||||
|
||||
relation.References = append(relation.References, &Reference{
|
||||
PrimaryKey: rf,
|
||||
ForeignKey: f,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type guessLevel int
|
||||
|
||||
const (
|
||||
guessGuess guessLevel = iota
|
||||
guessBelongs
|
||||
guessEmbeddedBelongs
|
||||
guessHas
|
||||
guessEmbeddedHas
|
||||
)
|
||||
|
||||
func (schema *Schema) guessRelation(relation *Relationship, field *Field, cgl guessLevel) {
|
||||
var (
|
||||
primaryFields, foreignFields []*Field
|
||||
primarySchema, foreignSchema = schema, relation.FieldSchema
|
||||
gl = cgl
|
||||
)
|
||||
|
||||
if gl == guessGuess {
|
||||
if field.Schema == relation.FieldSchema {
|
||||
gl = guessBelongs
|
||||
} else {
|
||||
gl = guessHas
|
||||
}
|
||||
}
|
||||
|
||||
reguessOrErr := func() {
|
||||
switch cgl {
|
||||
case guessGuess:
|
||||
schema.guessRelation(relation, field, guessBelongs)
|
||||
case guessBelongs:
|
||||
schema.guessRelation(relation, field, guessEmbeddedBelongs)
|
||||
case guessEmbeddedBelongs:
|
||||
schema.guessRelation(relation, field, guessHas)
|
||||
case guessHas:
|
||||
schema.guessRelation(relation, field, guessEmbeddedHas)
|
||||
// case guessEmbeddedHas:
|
||||
default:
|
||||
schema.err = fmt.Errorf("invalid field found for struct %v's field %s: define a valid foreign key for relations or implement the Valuer/Scanner interface",
|
||||
schema, field.Name)
|
||||
}
|
||||
}
|
||||
|
||||
switch gl {
|
||||
case guessBelongs:
|
||||
primarySchema, foreignSchema = relation.FieldSchema, schema
|
||||
case guessEmbeddedBelongs:
|
||||
if field.OwnerSchema == nil {
|
||||
reguessOrErr()
|
||||
return
|
||||
}
|
||||
primarySchema, foreignSchema = relation.FieldSchema, field.OwnerSchema
|
||||
case guessHas:
|
||||
case guessEmbeddedHas:
|
||||
if field.OwnerSchema == nil {
|
||||
reguessOrErr()
|
||||
return
|
||||
}
|
||||
primarySchema, foreignSchema = field.OwnerSchema, relation.FieldSchema
|
||||
}
|
||||
|
||||
if len(relation.foreignKeys) > 0 {
|
||||
for _, foreignKey := range relation.foreignKeys {
|
||||
f := foreignSchema.LookUpField(foreignKey)
|
||||
if f == nil {
|
||||
reguessOrErr()
|
||||
return
|
||||
}
|
||||
foreignFields = append(foreignFields, f)
|
||||
}
|
||||
} else {
|
||||
primarySchemaName := primarySchema.Name
|
||||
if primarySchemaName == "" {
|
||||
primarySchemaName = relation.FieldSchema.Name
|
||||
}
|
||||
|
||||
if len(relation.primaryKeys) > 0 {
|
||||
for _, primaryKey := range relation.primaryKeys {
|
||||
if f := primarySchema.LookUpField(primaryKey); f != nil {
|
||||
primaryFields = append(primaryFields, f)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
primaryFields = primarySchema.PrimaryFields
|
||||
}
|
||||
|
||||
primaryFieldLoop:
|
||||
for _, primaryField := range primaryFields {
|
||||
lookUpName := primarySchemaName + primaryField.Name
|
||||
if gl == guessBelongs {
|
||||
lookUpName = field.Name + primaryField.Name
|
||||
}
|
||||
|
||||
lookUpNames := []string{lookUpName}
|
||||
if len(primaryFields) == 1 {
|
||||
lookUpNames = append(lookUpNames, strings.TrimSuffix(lookUpName, primaryField.Name)+"ID",
|
||||
strings.TrimSuffix(lookUpName, primaryField.Name)+"Id", schema.namer.ColumnName(foreignSchema.Table,
|
||||
strings.TrimSuffix(lookUpName, primaryField.Name)+"ID"))
|
||||
}
|
||||
|
||||
for _, name := range lookUpNames {
|
||||
if f := foreignSchema.LookUpFieldByBindName(field.BindNames, name); f != nil {
|
||||
foreignFields = append(foreignFields, f)
|
||||
primaryFields = append(primaryFields, primaryField)
|
||||
continue primaryFieldLoop
|
||||
}
|
||||
}
|
||||
for _, name := range lookUpNames {
|
||||
if f := foreignSchema.LookUpField(name); f != nil {
|
||||
foreignFields = append(foreignFields, f)
|
||||
primaryFields = append(primaryFields, primaryField)
|
||||
continue primaryFieldLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(foreignFields) == 0:
|
||||
reguessOrErr()
|
||||
return
|
||||
case len(relation.primaryKeys) > 0:
|
||||
for idx, primaryKey := range relation.primaryKeys {
|
||||
if f := primarySchema.LookUpField(primaryKey); f != nil {
|
||||
if len(primaryFields) < idx+1 {
|
||||
primaryFields = append(primaryFields, f)
|
||||
} else if f != primaryFields[idx] {
|
||||
reguessOrErr()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
reguessOrErr()
|
||||
return
|
||||
}
|
||||
}
|
||||
case len(primaryFields) == 0:
|
||||
if len(foreignFields) == 1 && primarySchema.PrioritizedPrimaryField != nil {
|
||||
primaryFields = append(primaryFields, primarySchema.PrioritizedPrimaryField)
|
||||
} else if len(primarySchema.PrimaryFields) == len(foreignFields) {
|
||||
primaryFields = append(primaryFields, primarySchema.PrimaryFields...)
|
||||
} else {
|
||||
reguessOrErr()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// build references
|
||||
for idx, foreignField := range foreignFields {
|
||||
// use same data type for foreign keys
|
||||
if copyableDataType(primaryFields[idx].DataType) {
|
||||
foreignField.DataType = primaryFields[idx].DataType
|
||||
}
|
||||
foreignField.GORMDataType = primaryFields[idx].GORMDataType
|
||||
if foreignField.Size == 0 {
|
||||
foreignField.Size = primaryFields[idx].Size
|
||||
}
|
||||
|
||||
relation.References = append(relation.References, &Reference{
|
||||
PrimaryKey: primaryFields[idx],
|
||||
ForeignKey: foreignField,
|
||||
OwnPrimaryKey: (schema == primarySchema && gl == guessHas) || (field.OwnerSchema == primarySchema && gl == guessEmbeddedHas),
|
||||
})
|
||||
}
|
||||
|
||||
if gl == guessHas || gl == guessEmbeddedHas {
|
||||
relation.Type = has
|
||||
} else {
|
||||
relation.Type = BelongsTo
|
||||
}
|
||||
}
|
||||
|
||||
// Constraint is ForeignKey Constraint
|
||||
type Constraint struct {
|
||||
Name string
|
||||
Field *Field
|
||||
Schema *Schema
|
||||
ForeignKeys []*Field
|
||||
ReferenceSchema *Schema
|
||||
References []*Field
|
||||
OnDelete string
|
||||
OnUpdate string
|
||||
}
|
||||
|
||||
func (constraint *Constraint) GetName() string { return constraint.Name }
|
||||
|
||||
func (constraint *Constraint) Build() (sql string, vars []interface{}) {
|
||||
sql = "CONSTRAINT ? FOREIGN KEY ? REFERENCES ??"
|
||||
if constraint.OnDelete != "" {
|
||||
sql += " ON DELETE " + constraint.OnDelete
|
||||
}
|
||||
|
||||
if constraint.OnUpdate != "" {
|
||||
sql += " ON UPDATE " + constraint.OnUpdate
|
||||
}
|
||||
|
||||
foreignKeys := make([]interface{}, 0, len(constraint.ForeignKeys))
|
||||
for _, field := range constraint.ForeignKeys {
|
||||
foreignKeys = append(foreignKeys, clause.Column{Name: field.DBName})
|
||||
}
|
||||
|
||||
references := make([]interface{}, 0, len(constraint.References))
|
||||
for _, field := range constraint.References {
|
||||
references = append(references, clause.Column{Name: field.DBName})
|
||||
}
|
||||
vars = append(vars, clause.Table{Name: constraint.Name}, foreignKeys, clause.Table{Name: constraint.ReferenceSchema.Table}, references)
|
||||
return
|
||||
}
|
||||
|
||||
func (rel *Relationship) ParseConstraint() *Constraint {
|
||||
str := rel.Field.TagSettings["CONSTRAINT"]
|
||||
if str == "-" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if rel.Type == BelongsTo {
|
||||
for _, r := range rel.FieldSchema.Relationships.Relations {
|
||||
if r != rel && r.FieldSchema == rel.Schema && len(rel.References) == len(r.References) {
|
||||
matched := true
|
||||
for idx, ref := range r.References {
|
||||
if !(rel.References[idx].PrimaryKey == ref.PrimaryKey && rel.References[idx].ForeignKey == ref.ForeignKey &&
|
||||
rel.References[idx].PrimaryValue == ref.PrimaryValue) {
|
||||
matched = false
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
name string
|
||||
idx = strings.Index(str, ",")
|
||||
settings = ParseTagSetting(str, ",")
|
||||
)
|
||||
|
||||
// optimize match english letters and midline
|
||||
// The following code is basically called in for.
|
||||
// In order to avoid the performance problems caused by repeated compilation of regular expressions,
|
||||
// it only needs to be done once outside, so optimization is done here.
|
||||
if idx != -1 && regEnLetterAndMidline.MatchString(str[0:idx]) {
|
||||
name = str[0:idx]
|
||||
} else {
|
||||
name = rel.Schema.namer.RelationshipFKName(*rel)
|
||||
}
|
||||
|
||||
constraint := Constraint{
|
||||
Name: name,
|
||||
Field: rel.Field,
|
||||
OnUpdate: settings["ONUPDATE"],
|
||||
OnDelete: settings["ONDELETE"],
|
||||
}
|
||||
|
||||
for _, ref := range rel.References {
|
||||
if ref.PrimaryKey != nil && (rel.JoinTable == nil || ref.OwnPrimaryKey) {
|
||||
constraint.ForeignKeys = append(constraint.ForeignKeys, ref.ForeignKey)
|
||||
constraint.References = append(constraint.References, ref.PrimaryKey)
|
||||
|
||||
if ref.OwnPrimaryKey {
|
||||
constraint.Schema = ref.ForeignKey.Schema
|
||||
constraint.ReferenceSchema = rel.Schema
|
||||
} else {
|
||||
constraint.Schema = rel.Schema
|
||||
constraint.ReferenceSchema = ref.PrimaryKey.Schema
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &constraint
|
||||
}
|
||||
|
||||
func (rel *Relationship) ToQueryConditions(ctx context.Context, reflectValue reflect.Value) (conds []clause.Expression) {
|
||||
table := rel.FieldSchema.Table
|
||||
foreignFields := []*Field{}
|
||||
relForeignKeys := []string{}
|
||||
|
||||
if rel.JoinTable != nil {
|
||||
table = rel.JoinTable.Table
|
||||
for _, ref := range rel.References {
|
||||
if ref.OwnPrimaryKey {
|
||||
foreignFields = append(foreignFields, ref.PrimaryKey)
|
||||
relForeignKeys = append(relForeignKeys, ref.ForeignKey.DBName)
|
||||
} else if ref.PrimaryValue != "" {
|
||||
conds = append(conds, clause.Eq{
|
||||
Column: clause.Column{Table: rel.JoinTable.Table, Name: ref.ForeignKey.DBName},
|
||||
Value: ref.PrimaryValue,
|
||||
})
|
||||
} else {
|
||||
conds = append(conds, clause.Eq{
|
||||
Column: clause.Column{Table: rel.JoinTable.Table, Name: ref.ForeignKey.DBName},
|
||||
Value: clause.Column{Table: rel.FieldSchema.Table, Name: ref.PrimaryKey.DBName},
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, ref := range rel.References {
|
||||
if ref.OwnPrimaryKey {
|
||||
relForeignKeys = append(relForeignKeys, ref.ForeignKey.DBName)
|
||||
foreignFields = append(foreignFields, ref.PrimaryKey)
|
||||
} else if ref.PrimaryValue != "" {
|
||||
conds = append(conds, clause.Eq{
|
||||
Column: clause.Column{Table: rel.FieldSchema.Table, Name: ref.ForeignKey.DBName},
|
||||
Value: ref.PrimaryValue,
|
||||
})
|
||||
} else {
|
||||
relForeignKeys = append(relForeignKeys, ref.PrimaryKey.DBName)
|
||||
foreignFields = append(foreignFields, ref.ForeignKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, foreignValues := GetIdentityFieldValuesMap(ctx, reflectValue, foreignFields)
|
||||
column, values := ToQueryValues(table, relForeignKeys, foreignValues)
|
||||
|
||||
conds = append(conds, clause.IN{Column: column, Values: values})
|
||||
return
|
||||
}
|
||||
|
||||
func copyableDataType(str DataType) bool {
|
||||
for _, s := range []string{"auto_increment", "primary key"} {
|
||||
if strings.Contains(strings.ToLower(string(str)), s) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
423
vendor/gorm.io/gorm/schema/schema.go
generated
vendored
Normal file
423
vendor/gorm.io/gorm/schema/schema.go
generated
vendored
Normal file
@@ -0,0 +1,423 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gorm.io/gorm/clause"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
type callbackType string
|
||||
|
||||
const (
|
||||
callbackTypeBeforeCreate callbackType = "BeforeCreate"
|
||||
callbackTypeBeforeUpdate callbackType = "BeforeUpdate"
|
||||
callbackTypeAfterCreate callbackType = "AfterCreate"
|
||||
callbackTypeAfterUpdate callbackType = "AfterUpdate"
|
||||
callbackTypeBeforeSave callbackType = "BeforeSave"
|
||||
callbackTypeAfterSave callbackType = "AfterSave"
|
||||
callbackTypeBeforeDelete callbackType = "BeforeDelete"
|
||||
callbackTypeAfterDelete callbackType = "AfterDelete"
|
||||
callbackTypeAfterFind callbackType = "AfterFind"
|
||||
)
|
||||
|
||||
// ErrUnsupportedDataType unsupported data type
|
||||
var ErrUnsupportedDataType = errors.New("unsupported data type")
|
||||
|
||||
type Schema struct {
|
||||
Name string
|
||||
ModelType reflect.Type
|
||||
Table string
|
||||
PrioritizedPrimaryField *Field
|
||||
DBNames []string
|
||||
PrimaryFields []*Field
|
||||
PrimaryFieldDBNames []string
|
||||
Fields []*Field
|
||||
FieldsByName map[string]*Field
|
||||
FieldsByBindName map[string]*Field // embedded fields is 'Embed.Field'
|
||||
FieldsByDBName map[string]*Field
|
||||
FieldsWithDefaultDBValue []*Field // fields with default value assigned by database
|
||||
Relationships Relationships
|
||||
CreateClauses []clause.Interface
|
||||
QueryClauses []clause.Interface
|
||||
UpdateClauses []clause.Interface
|
||||
DeleteClauses []clause.Interface
|
||||
BeforeCreate, AfterCreate bool
|
||||
BeforeUpdate, AfterUpdate bool
|
||||
BeforeDelete, AfterDelete bool
|
||||
BeforeSave, AfterSave bool
|
||||
AfterFind bool
|
||||
err error
|
||||
initialized chan struct{}
|
||||
namer Namer
|
||||
cacheStore *sync.Map
|
||||
}
|
||||
|
||||
func (schema Schema) String() string {
|
||||
if schema.ModelType.Name() == "" {
|
||||
return fmt.Sprintf("%s(%s)", schema.Name, schema.Table)
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", schema.ModelType.PkgPath(), schema.ModelType.Name())
|
||||
}
|
||||
|
||||
func (schema Schema) MakeSlice() reflect.Value {
|
||||
slice := reflect.MakeSlice(reflect.SliceOf(reflect.PtrTo(schema.ModelType)), 0, 20)
|
||||
results := reflect.New(slice.Type())
|
||||
results.Elem().Set(slice)
|
||||
return results
|
||||
}
|
||||
|
||||
func (schema Schema) LookUpField(name string) *Field {
|
||||
if field, ok := schema.FieldsByDBName[name]; ok {
|
||||
return field
|
||||
}
|
||||
if field, ok := schema.FieldsByName[name]; ok {
|
||||
return field
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookUpFieldByBindName looks for the closest field in the embedded struct.
|
||||
//
|
||||
// type Struct struct {
|
||||
// Embedded struct {
|
||||
// ID string // is selected by LookUpFieldByBindName([]string{"Embedded", "ID"}, "ID")
|
||||
// }
|
||||
// ID string // is selected by LookUpFieldByBindName([]string{"ID"}, "ID")
|
||||
// }
|
||||
func (schema Schema) LookUpFieldByBindName(bindNames []string, name string) *Field {
|
||||
if len(bindNames) == 0 {
|
||||
return nil
|
||||
}
|
||||
for i := len(bindNames) - 1; i >= 0; i-- {
|
||||
find := strings.Join(bindNames[:i], ".") + "." + name
|
||||
if field, ok := schema.FieldsByBindName[find]; ok {
|
||||
return field
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Tabler interface {
|
||||
TableName() string
|
||||
}
|
||||
|
||||
type TablerWithNamer interface {
|
||||
TableName(Namer) string
|
||||
}
|
||||
|
||||
// Parse get data type from dialector
|
||||
func Parse(dest interface{}, cacheStore *sync.Map, namer Namer) (*Schema, error) {
|
||||
return ParseWithSpecialTableName(dest, cacheStore, namer, "")
|
||||
}
|
||||
|
||||
// ParseWithSpecialTableName get data type from dialector with extra schema table
|
||||
func ParseWithSpecialTableName(dest interface{}, cacheStore *sync.Map, namer Namer, specialTableName string) (*Schema, error) {
|
||||
if dest == nil {
|
||||
return nil, fmt.Errorf("%w: %+v", ErrUnsupportedDataType, dest)
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(dest)
|
||||
if value.Kind() == reflect.Ptr && value.IsNil() {
|
||||
value = reflect.New(value.Type().Elem())
|
||||
}
|
||||
modelType := reflect.Indirect(value).Type()
|
||||
|
||||
if modelType.Kind() == reflect.Interface {
|
||||
modelType = reflect.Indirect(reflect.ValueOf(dest)).Elem().Type()
|
||||
}
|
||||
|
||||
for modelType.Kind() == reflect.Slice || modelType.Kind() == reflect.Array || modelType.Kind() == reflect.Ptr {
|
||||
modelType = modelType.Elem()
|
||||
}
|
||||
|
||||
if modelType.Kind() != reflect.Struct {
|
||||
if modelType.PkgPath() == "" {
|
||||
return nil, fmt.Errorf("%w: %+v", ErrUnsupportedDataType, dest)
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s.%s", ErrUnsupportedDataType, modelType.PkgPath(), modelType.Name())
|
||||
}
|
||||
|
||||
// Cache the Schema for performance,
|
||||
// Use the modelType or modelType + schemaTable (if it present) as cache key.
|
||||
var schemaCacheKey interface{}
|
||||
if specialTableName != "" {
|
||||
schemaCacheKey = fmt.Sprintf("%p-%s", modelType, specialTableName)
|
||||
} else {
|
||||
schemaCacheKey = modelType
|
||||
}
|
||||
|
||||
// Load exist schema cache, return if exists
|
||||
if v, ok := cacheStore.Load(schemaCacheKey); ok {
|
||||
s := v.(*Schema)
|
||||
// Wait for the initialization of other goroutines to complete
|
||||
<-s.initialized
|
||||
return s, s.err
|
||||
}
|
||||
|
||||
modelValue := reflect.New(modelType)
|
||||
tableName := namer.TableName(modelType.Name())
|
||||
if tabler, ok := modelValue.Interface().(Tabler); ok {
|
||||
tableName = tabler.TableName()
|
||||
}
|
||||
if tabler, ok := modelValue.Interface().(TablerWithNamer); ok {
|
||||
tableName = tabler.TableName(namer)
|
||||
}
|
||||
if en, ok := namer.(embeddedNamer); ok {
|
||||
tableName = en.Table
|
||||
}
|
||||
if specialTableName != "" && specialTableName != tableName {
|
||||
tableName = specialTableName
|
||||
}
|
||||
|
||||
schema := &Schema{
|
||||
Name: modelType.Name(),
|
||||
ModelType: modelType,
|
||||
Table: tableName,
|
||||
FieldsByName: map[string]*Field{},
|
||||
FieldsByBindName: map[string]*Field{},
|
||||
FieldsByDBName: map[string]*Field{},
|
||||
Relationships: Relationships{Relations: map[string]*Relationship{}},
|
||||
cacheStore: cacheStore,
|
||||
namer: namer,
|
||||
initialized: make(chan struct{}),
|
||||
}
|
||||
// When the schema initialization is completed, the channel will be closed
|
||||
defer close(schema.initialized)
|
||||
|
||||
// Load exist schema cache, return if exists
|
||||
if v, ok := cacheStore.Load(schemaCacheKey); ok {
|
||||
s := v.(*Schema)
|
||||
// Wait for the initialization of other goroutines to complete
|
||||
<-s.initialized
|
||||
return s, s.err
|
||||
}
|
||||
|
||||
for i := 0; i < modelType.NumField(); i++ {
|
||||
if fieldStruct := modelType.Field(i); ast.IsExported(fieldStruct.Name) {
|
||||
if field := schema.ParseField(fieldStruct); field.EmbeddedSchema != nil {
|
||||
schema.Fields = append(schema.Fields, field.EmbeddedSchema.Fields...)
|
||||
} else {
|
||||
schema.Fields = append(schema.Fields, field)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, field := range schema.Fields {
|
||||
if field.DBName == "" && field.DataType != "" {
|
||||
field.DBName = namer.ColumnName(schema.Table, field.Name)
|
||||
}
|
||||
|
||||
bindName := field.BindName()
|
||||
if field.DBName != "" {
|
||||
// nonexistence or shortest path or first appear prioritized if has permission
|
||||
if v, ok := schema.FieldsByDBName[field.DBName]; !ok || ((field.Creatable || field.Updatable || field.Readable) && len(field.BindNames) < len(v.BindNames)) {
|
||||
if _, ok := schema.FieldsByDBName[field.DBName]; !ok {
|
||||
schema.DBNames = append(schema.DBNames, field.DBName)
|
||||
}
|
||||
schema.FieldsByDBName[field.DBName] = field
|
||||
schema.FieldsByName[field.Name] = field
|
||||
schema.FieldsByBindName[bindName] = field
|
||||
|
||||
if v != nil && v.PrimaryKey {
|
||||
for idx, f := range schema.PrimaryFields {
|
||||
if f == v {
|
||||
schema.PrimaryFields = append(schema.PrimaryFields[0:idx], schema.PrimaryFields[idx+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if field.PrimaryKey {
|
||||
schema.PrimaryFields = append(schema.PrimaryFields, field)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if of, ok := schema.FieldsByName[field.Name]; !ok || of.TagSettings["-"] == "-" {
|
||||
schema.FieldsByName[field.Name] = field
|
||||
}
|
||||
if of, ok := schema.FieldsByBindName[bindName]; !ok || of.TagSettings["-"] == "-" {
|
||||
schema.FieldsByBindName[bindName] = field
|
||||
}
|
||||
|
||||
field.setupValuerAndSetter()
|
||||
}
|
||||
|
||||
prioritizedPrimaryField := schema.LookUpField("id")
|
||||
if prioritizedPrimaryField == nil {
|
||||
prioritizedPrimaryField = schema.LookUpField("ID")
|
||||
}
|
||||
|
||||
if prioritizedPrimaryField != nil {
|
||||
if prioritizedPrimaryField.PrimaryKey {
|
||||
schema.PrioritizedPrimaryField = prioritizedPrimaryField
|
||||
} else if len(schema.PrimaryFields) == 0 {
|
||||
prioritizedPrimaryField.PrimaryKey = true
|
||||
schema.PrioritizedPrimaryField = prioritizedPrimaryField
|
||||
schema.PrimaryFields = append(schema.PrimaryFields, prioritizedPrimaryField)
|
||||
}
|
||||
}
|
||||
|
||||
if schema.PrioritizedPrimaryField == nil {
|
||||
if len(schema.PrimaryFields) == 1 {
|
||||
schema.PrioritizedPrimaryField = schema.PrimaryFields[0]
|
||||
} else if len(schema.PrimaryFields) > 1 {
|
||||
// If there are multiple primary keys, the AUTOINCREMENT field is prioritized
|
||||
for _, field := range schema.PrimaryFields {
|
||||
if field.AutoIncrement {
|
||||
schema.PrioritizedPrimaryField = field
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, field := range schema.PrimaryFields {
|
||||
schema.PrimaryFieldDBNames = append(schema.PrimaryFieldDBNames, field.DBName)
|
||||
}
|
||||
|
||||
for _, field := range schema.Fields {
|
||||
if field.DataType != "" && field.HasDefaultValue && field.DefaultValueInterface == nil {
|
||||
schema.FieldsWithDefaultDBValue = append(schema.FieldsWithDefaultDBValue, field)
|
||||
}
|
||||
}
|
||||
|
||||
if field := schema.PrioritizedPrimaryField; field != nil {
|
||||
switch field.GORMDataType {
|
||||
case Int, Uint:
|
||||
if _, ok := field.TagSettings["AUTOINCREMENT"]; !ok {
|
||||
if !field.HasDefaultValue || field.DefaultValueInterface != nil {
|
||||
schema.FieldsWithDefaultDBValue = append(schema.FieldsWithDefaultDBValue, field)
|
||||
}
|
||||
|
||||
field.HasDefaultValue = true
|
||||
field.AutoIncrement = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callbackTypes := []callbackType{
|
||||
callbackTypeBeforeCreate, callbackTypeAfterCreate,
|
||||
callbackTypeBeforeUpdate, callbackTypeAfterUpdate,
|
||||
callbackTypeBeforeSave, callbackTypeAfterSave,
|
||||
callbackTypeBeforeDelete, callbackTypeAfterDelete,
|
||||
callbackTypeAfterFind,
|
||||
}
|
||||
for _, cbName := range callbackTypes {
|
||||
if methodValue := callBackToMethodValue(modelValue, cbName); methodValue.IsValid() {
|
||||
switch methodValue.Type().String() {
|
||||
case "func(*gorm.DB) error": // TODO hack
|
||||
reflect.Indirect(reflect.ValueOf(schema)).FieldByName(string(cbName)).SetBool(true)
|
||||
default:
|
||||
logger.Default.Warn(context.Background(), "Model %v don't match %vInterface, should be `%v(*gorm.DB) error`. Please see https://gorm.io/docs/hooks.html", schema, cbName, cbName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the schema
|
||||
if v, loaded := cacheStore.LoadOrStore(schemaCacheKey, schema); loaded {
|
||||
s := v.(*Schema)
|
||||
// Wait for the initialization of other goroutines to complete
|
||||
<-s.initialized
|
||||
return s, s.err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if schema.err != nil {
|
||||
logger.Default.Error(context.Background(), schema.err.Error())
|
||||
cacheStore.Delete(modelType)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, embedded := schema.cacheStore.Load(embeddedCacheKey); !embedded {
|
||||
for _, field := range schema.Fields {
|
||||
if field.DataType == "" && (field.Creatable || field.Updatable || field.Readable) {
|
||||
if schema.parseRelation(field); schema.err != nil {
|
||||
return schema, schema.err
|
||||
} else {
|
||||
schema.FieldsByName[field.Name] = field
|
||||
schema.FieldsByBindName[field.BindName()] = field
|
||||
}
|
||||
}
|
||||
|
||||
fieldValue := reflect.New(field.IndirectFieldType)
|
||||
fieldInterface := fieldValue.Interface()
|
||||
if fc, ok := fieldInterface.(CreateClausesInterface); ok {
|
||||
field.Schema.CreateClauses = append(field.Schema.CreateClauses, fc.CreateClauses(field)...)
|
||||
}
|
||||
|
||||
if fc, ok := fieldInterface.(QueryClausesInterface); ok {
|
||||
field.Schema.QueryClauses = append(field.Schema.QueryClauses, fc.QueryClauses(field)...)
|
||||
}
|
||||
|
||||
if fc, ok := fieldInterface.(UpdateClausesInterface); ok {
|
||||
field.Schema.UpdateClauses = append(field.Schema.UpdateClauses, fc.UpdateClauses(field)...)
|
||||
}
|
||||
|
||||
if fc, ok := fieldInterface.(DeleteClausesInterface); ok {
|
||||
field.Schema.DeleteClauses = append(field.Schema.DeleteClauses, fc.DeleteClauses(field)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return schema, schema.err
|
||||
}
|
||||
|
||||
// This unrolling is needed to show to the compiler the exact set of methods
|
||||
// that can be used on the modelType.
|
||||
// Prior to go1.22 any use of MethodByName would cause the linker to
|
||||
// abandon dead code elimination for the entire binary.
|
||||
// As of go1.22 the compiler supports one special case of a string constant
|
||||
// being passed to MethodByName. For enterprise customers or those building
|
||||
// large binaries, this gives a significant reduction in binary size.
|
||||
// https://github.com/golang/go/issues/62257
|
||||
func callBackToMethodValue(modelType reflect.Value, cbType callbackType) reflect.Value {
|
||||
switch cbType {
|
||||
case callbackTypeBeforeCreate:
|
||||
return modelType.MethodByName(string(callbackTypeBeforeCreate))
|
||||
case callbackTypeAfterCreate:
|
||||
return modelType.MethodByName(string(callbackTypeAfterCreate))
|
||||
case callbackTypeBeforeUpdate:
|
||||
return modelType.MethodByName(string(callbackTypeBeforeUpdate))
|
||||
case callbackTypeAfterUpdate:
|
||||
return modelType.MethodByName(string(callbackTypeAfterUpdate))
|
||||
case callbackTypeBeforeSave:
|
||||
return modelType.MethodByName(string(callbackTypeBeforeSave))
|
||||
case callbackTypeAfterSave:
|
||||
return modelType.MethodByName(string(callbackTypeAfterSave))
|
||||
case callbackTypeBeforeDelete:
|
||||
return modelType.MethodByName(string(callbackTypeBeforeDelete))
|
||||
case callbackTypeAfterDelete:
|
||||
return modelType.MethodByName(string(callbackTypeAfterDelete))
|
||||
case callbackTypeAfterFind:
|
||||
return modelType.MethodByName(string(callbackTypeAfterFind))
|
||||
default:
|
||||
return reflect.ValueOf(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func getOrParse(dest interface{}, cacheStore *sync.Map, namer Namer) (*Schema, error) {
|
||||
modelType := reflect.ValueOf(dest).Type()
|
||||
for modelType.Kind() == reflect.Slice || modelType.Kind() == reflect.Array || modelType.Kind() == reflect.Ptr {
|
||||
modelType = modelType.Elem()
|
||||
}
|
||||
|
||||
if modelType.Kind() != reflect.Struct {
|
||||
if modelType.PkgPath() == "" {
|
||||
return nil, fmt.Errorf("%w: %+v", ErrUnsupportedDataType, dest)
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s.%s", ErrUnsupportedDataType, modelType.PkgPath(), modelType.Name())
|
||||
}
|
||||
|
||||
if v, ok := cacheStore.Load(modelType); ok {
|
||||
return v.(*Schema), nil
|
||||
}
|
||||
|
||||
return Parse(dest, cacheStore, namer)
|
||||
}
|
||||
170
vendor/gorm.io/gorm/schema/serializer.go
generated
vendored
Normal file
170
vendor/gorm.io/gorm/schema/serializer.go
generated
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var serializerMap = sync.Map{}
|
||||
|
||||
// RegisterSerializer register serializer
|
||||
func RegisterSerializer(name string, serializer SerializerInterface) {
|
||||
serializerMap.Store(strings.ToLower(name), serializer)
|
||||
}
|
||||
|
||||
// GetSerializer get serializer
|
||||
func GetSerializer(name string) (serializer SerializerInterface, ok bool) {
|
||||
v, ok := serializerMap.Load(strings.ToLower(name))
|
||||
if ok {
|
||||
serializer, ok = v.(SerializerInterface)
|
||||
}
|
||||
return serializer, ok
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterSerializer("json", JSONSerializer{})
|
||||
RegisterSerializer("unixtime", UnixSecondSerializer{})
|
||||
RegisterSerializer("gob", GobSerializer{})
|
||||
}
|
||||
|
||||
// Serializer field value serializer
|
||||
type serializer struct {
|
||||
Field *Field
|
||||
Serializer SerializerInterface
|
||||
SerializeValuer SerializerValuerInterface
|
||||
Destination reflect.Value
|
||||
Context context.Context
|
||||
value interface{}
|
||||
fieldValue interface{}
|
||||
}
|
||||
|
||||
// Scan implements sql.Scanner interface
|
||||
func (s *serializer) Scan(value interface{}) error {
|
||||
s.value = value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements driver.Valuer interface
|
||||
func (s serializer) Value() (driver.Value, error) {
|
||||
return s.SerializeValuer.Value(s.Context, s.Field, s.Destination, s.fieldValue)
|
||||
}
|
||||
|
||||
// SerializerInterface serializer interface
|
||||
type SerializerInterface interface {
|
||||
Scan(ctx context.Context, field *Field, dst reflect.Value, dbValue interface{}) error
|
||||
SerializerValuerInterface
|
||||
}
|
||||
|
||||
// SerializerValuerInterface serializer valuer interface
|
||||
type SerializerValuerInterface interface {
|
||||
Value(ctx context.Context, field *Field, dst reflect.Value, fieldValue interface{}) (interface{}, error)
|
||||
}
|
||||
|
||||
// JSONSerializer json serializer
|
||||
type JSONSerializer struct{}
|
||||
|
||||
// Scan implements serializer interface
|
||||
func (JSONSerializer) Scan(ctx context.Context, field *Field, dst reflect.Value, dbValue interface{}) (err error) {
|
||||
fieldValue := reflect.New(field.FieldType)
|
||||
|
||||
if dbValue != nil {
|
||||
var bytes []byte
|
||||
switch v := dbValue.(type) {
|
||||
case []byte:
|
||||
bytes = v
|
||||
case string:
|
||||
bytes = []byte(v)
|
||||
default:
|
||||
return fmt.Errorf("failed to unmarshal JSONB value: %#v", dbValue)
|
||||
}
|
||||
|
||||
if len(bytes) > 0 {
|
||||
err = json.Unmarshal(bytes, fieldValue.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
field.ReflectValueOf(ctx, dst).Set(fieldValue.Elem())
|
||||
return
|
||||
}
|
||||
|
||||
// Value implements serializer interface
|
||||
func (JSONSerializer) Value(ctx context.Context, field *Field, dst reflect.Value, fieldValue interface{}) (interface{}, error) {
|
||||
result, err := json.Marshal(fieldValue)
|
||||
if string(result) == "null" {
|
||||
if field.TagSettings["NOT NULL"] != "" {
|
||||
return "", nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return string(result), err
|
||||
}
|
||||
|
||||
// UnixSecondSerializer json serializer
|
||||
type UnixSecondSerializer struct{}
|
||||
|
||||
// Scan implements serializer interface
|
||||
func (UnixSecondSerializer) Scan(ctx context.Context, field *Field, dst reflect.Value, dbValue interface{}) (err error) {
|
||||
t := sql.NullTime{}
|
||||
if err = t.Scan(dbValue); err == nil && t.Valid {
|
||||
err = field.Set(ctx, dst, t.Time.Unix())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Value implements serializer interface
|
||||
func (UnixSecondSerializer) Value(ctx context.Context, field *Field, dst reflect.Value, fieldValue interface{}) (result interface{}, err error) {
|
||||
rv := reflect.ValueOf(fieldValue)
|
||||
switch v := fieldValue.(type) {
|
||||
case int64, int, uint, uint64, int32, uint32, int16, uint16:
|
||||
result = time.Unix(reflect.Indirect(rv).Int(), 0)
|
||||
case *int64, *int, *uint, *uint64, *int32, *uint32, *int16, *uint16:
|
||||
if rv.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
result = time.Unix(reflect.Indirect(rv).Int(), 0)
|
||||
default:
|
||||
err = fmt.Errorf("invalid field type %#v for UnixSecondSerializer, only int, uint supported", v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GobSerializer gob serializer
|
||||
type GobSerializer struct{}
|
||||
|
||||
// Scan implements serializer interface
|
||||
func (GobSerializer) Scan(ctx context.Context, field *Field, dst reflect.Value, dbValue interface{}) (err error) {
|
||||
fieldValue := reflect.New(field.FieldType)
|
||||
|
||||
if dbValue != nil {
|
||||
var bytesValue []byte
|
||||
switch v := dbValue.(type) {
|
||||
case []byte:
|
||||
bytesValue = v
|
||||
default:
|
||||
return fmt.Errorf("failed to unmarshal gob value: %#v", dbValue)
|
||||
}
|
||||
if len(bytesValue) > 0 {
|
||||
decoder := gob.NewDecoder(bytes.NewBuffer(bytesValue))
|
||||
err = decoder.Decode(fieldValue.Interface())
|
||||
}
|
||||
}
|
||||
field.ReflectValueOf(ctx, dst).Set(fieldValue.Elem())
|
||||
return
|
||||
}
|
||||
|
||||
// Value implements serializer interface
|
||||
func (GobSerializer) Value(ctx context.Context, field *Field, dst reflect.Value, fieldValue interface{}) (interface{}, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
err := gob.NewEncoder(buf).Encode(fieldValue)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
213
vendor/gorm.io/gorm/schema/utils.go
generated
vendored
Normal file
213
vendor/gorm.io/gorm/schema/utils.go
generated
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm/clause"
|
||||
"gorm.io/gorm/utils"
|
||||
)
|
||||
|
||||
var embeddedCacheKey = "embedded_cache_store"
|
||||
|
||||
func ParseTagSetting(str string, sep string) map[string]string {
|
||||
settings := map[string]string{}
|
||||
names := strings.Split(str, sep)
|
||||
|
||||
for i := 0; i < len(names); i++ {
|
||||
j := i
|
||||
if len(names[j]) > 0 {
|
||||
for {
|
||||
if names[j][len(names[j])-1] == '\\' {
|
||||
i++
|
||||
names[j] = names[j][0:len(names[j])-1] + sep + names[i]
|
||||
names[i] = ""
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
values := strings.Split(names[j], ":")
|
||||
k := strings.TrimSpace(strings.ToUpper(values[0]))
|
||||
|
||||
if len(values) >= 2 {
|
||||
settings[k] = strings.Join(values[1:], ":")
|
||||
} else if k != "" {
|
||||
settings[k] = k
|
||||
}
|
||||
}
|
||||
|
||||
return settings
|
||||
}
|
||||
|
||||
func toColumns(val string) (results []string) {
|
||||
if val != "" {
|
||||
for _, v := range strings.Split(val, ",") {
|
||||
results = append(results, strings.TrimSpace(v))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func removeSettingFromTag(tag reflect.StructTag, names ...string) reflect.StructTag {
|
||||
for _, name := range names {
|
||||
tag = reflect.StructTag(regexp.MustCompile(`(?i)(gorm:.*?)(`+name+`(:.*?)?)(;|("))`).ReplaceAllString(string(tag), "${1}${5}"))
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func appendSettingFromTag(tag reflect.StructTag, value string) reflect.StructTag {
|
||||
t := tag.Get("gorm")
|
||||
if strings.Contains(t, value) {
|
||||
return tag
|
||||
}
|
||||
return reflect.StructTag(fmt.Sprintf(`gorm:"%s;%s"`, value, t))
|
||||
}
|
||||
|
||||
// GetRelationsValues get relations's values from a reflect value
|
||||
func GetRelationsValues(ctx context.Context, reflectValue reflect.Value, rels []*Relationship) (reflectResults reflect.Value) {
|
||||
for _, rel := range rels {
|
||||
reflectResults = reflect.MakeSlice(reflect.SliceOf(reflect.PtrTo(rel.FieldSchema.ModelType)), 0, 1)
|
||||
|
||||
appendToResults := func(value reflect.Value) {
|
||||
if _, isZero := rel.Field.ValueOf(ctx, value); !isZero {
|
||||
result := reflect.Indirect(rel.Field.ReflectValueOf(ctx, value))
|
||||
switch result.Kind() {
|
||||
case reflect.Struct:
|
||||
reflectResults = reflect.Append(reflectResults, result.Addr())
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := 0; i < result.Len(); i++ {
|
||||
if elem := result.Index(i); elem.Kind() == reflect.Ptr {
|
||||
reflectResults = reflect.Append(reflectResults, elem)
|
||||
} else {
|
||||
reflectResults = reflect.Append(reflectResults, elem.Addr())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch reflectValue.Kind() {
|
||||
case reflect.Struct:
|
||||
appendToResults(reflectValue)
|
||||
case reflect.Slice:
|
||||
for i := 0; i < reflectValue.Len(); i++ {
|
||||
appendToResults(reflectValue.Index(i))
|
||||
}
|
||||
}
|
||||
|
||||
reflectValue = reflectResults
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetIdentityFieldValuesMap get identity map from fields
|
||||
func GetIdentityFieldValuesMap(ctx context.Context, reflectValue reflect.Value, fields []*Field) (map[string][]reflect.Value, [][]interface{}) {
|
||||
var (
|
||||
results = [][]interface{}{}
|
||||
dataResults = map[string][]reflect.Value{}
|
||||
loaded = map[interface{}]bool{}
|
||||
notZero, zero bool
|
||||
)
|
||||
|
||||
if reflectValue.Kind() == reflect.Ptr ||
|
||||
reflectValue.Kind() == reflect.Interface {
|
||||
reflectValue = reflectValue.Elem()
|
||||
}
|
||||
|
||||
switch reflectValue.Kind() {
|
||||
case reflect.Struct:
|
||||
results = [][]interface{}{make([]interface{}, len(fields))}
|
||||
|
||||
for idx, field := range fields {
|
||||
results[0][idx], zero = field.ValueOf(ctx, reflectValue)
|
||||
notZero = notZero || !zero
|
||||
}
|
||||
|
||||
if !notZero {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
dataResults[utils.ToStringKey(results[0]...)] = []reflect.Value{reflectValue}
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := 0; i < reflectValue.Len(); i++ {
|
||||
elem := reflectValue.Index(i)
|
||||
elemKey := elem.Interface()
|
||||
if elem.Kind() != reflect.Ptr && elem.CanAddr() {
|
||||
elemKey = elem.Addr().Interface()
|
||||
}
|
||||
|
||||
if _, ok := loaded[elemKey]; ok {
|
||||
continue
|
||||
}
|
||||
loaded[elemKey] = true
|
||||
|
||||
fieldValues := make([]interface{}, len(fields))
|
||||
notZero = false
|
||||
for idx, field := range fields {
|
||||
fieldValues[idx], zero = field.ValueOf(ctx, elem)
|
||||
notZero = notZero || !zero
|
||||
}
|
||||
|
||||
if notZero {
|
||||
dataKey := utils.ToStringKey(fieldValues...)
|
||||
if _, ok := dataResults[dataKey]; !ok {
|
||||
results = append(results, fieldValues)
|
||||
dataResults[dataKey] = []reflect.Value{elem}
|
||||
} else {
|
||||
dataResults[dataKey] = append(dataResults[dataKey], elem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataResults, results
|
||||
}
|
||||
|
||||
// GetIdentityFieldValuesMapFromValues get identity map from fields
|
||||
func GetIdentityFieldValuesMapFromValues(ctx context.Context, values []interface{}, fields []*Field) (map[string][]reflect.Value, [][]interface{}) {
|
||||
resultsMap := map[string][]reflect.Value{}
|
||||
results := [][]interface{}{}
|
||||
|
||||
for _, v := range values {
|
||||
rm, rs := GetIdentityFieldValuesMap(ctx, reflect.Indirect(reflect.ValueOf(v)), fields)
|
||||
for k, v := range rm {
|
||||
resultsMap[k] = append(resultsMap[k], v...)
|
||||
}
|
||||
results = append(results, rs...)
|
||||
}
|
||||
return resultsMap, results
|
||||
}
|
||||
|
||||
// ToQueryValues to query values
|
||||
func ToQueryValues(table string, foreignKeys []string, foreignValues [][]interface{}) (interface{}, []interface{}) {
|
||||
queryValues := make([]interface{}, len(foreignValues))
|
||||
if len(foreignKeys) == 1 {
|
||||
for idx, r := range foreignValues {
|
||||
queryValues[idx] = r[0]
|
||||
}
|
||||
|
||||
return clause.Column{Table: table, Name: foreignKeys[0]}, queryValues
|
||||
}
|
||||
|
||||
columns := make([]clause.Column, len(foreignKeys))
|
||||
for idx, key := range foreignKeys {
|
||||
columns[idx] = clause.Column{Table: table, Name: key}
|
||||
}
|
||||
|
||||
for idx, r := range foreignValues {
|
||||
queryValues[idx] = r
|
||||
}
|
||||
|
||||
return columns, queryValues
|
||||
}
|
||||
|
||||
type embeddedNamer struct {
|
||||
Table string
|
||||
Namer
|
||||
}
|
||||
Reference in New Issue
Block a user