You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
638 lines
19 KiB
638 lines
19 KiB
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 |
|
} |
|
|
|
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 polymorphic := field.TagSettings["POLYMORPHIC"]; polymorphic != "" { |
|
schema.buildPolymorphicRelation(relation, field, polymorphic) |
|
} 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.Relationships.Relations[relation.Name] = 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 |
|
} |
|
|
|
// 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 string) { |
|
relation.Polymorphic = &Polymorphic{ |
|
Value: schema.Table, |
|
PolymorphicType: relation.FieldSchema.FieldsByName[polymorphic+"Type"], |
|
PolymorphicID: relation.FieldSchema.FieldsByName[polymorphic+"ID"], |
|
} |
|
|
|
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) |
|
} |
|
} |
|
|
|
// 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]bool{} // fix self join many2many |
|
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] = true |
|
fieldsMap[joinFieldName] = ownField |
|
joinTableFields = append(joinTableFields, reflect.StructField{ |
|
Name: joinFieldName, |
|
PkgPath: ownField.StructField.PkgPath, |
|
Type: ownField.StructField.Type, |
|
Tag: removeSettingFromTag(ownField.StructField.Tag, "column", "autoincrement", "index", "unique", "uniqueindex"), |
|
}) |
|
} |
|
|
|
for idx, relField := range refForeignFields { |
|
joinFieldName := strings.Title(relation.FieldSchema.Name) + relField.Name |
|
if len(joinReferences) > idx { |
|
joinFieldName = strings.Title(joinReferences[idx]) |
|
} |
|
|
|
if _, ok := ownFieldsMap[joinFieldName]; ok { |
|
if field.Name != relation.FieldSchema.Name { |
|
joinFieldName = inflection.Singular(field.Name) + relField.Name |
|
} else { |
|
joinFieldName += "Reference" |
|
} |
|
} |
|
|
|
fieldsMap[joinFieldName] = relField |
|
joinTableFields = append(joinTableFields, reflect.StructField{ |
|
Name: joinFieldName, |
|
PkgPath: relField.StructField.PkgPath, |
|
Type: relField.StructField.Type, |
|
Tag: removeSettingFromTag(relField.StructField.Tag, "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) |
|
ownPrimaryField := schema == fieldsMap[f.Name].Schema && ownFieldsMap[f.Name] |
|
|
|
if ownPrimaryField { |
|
joinRel := relation.JoinTable.Relationships.Relations[relName] |
|
joinRel.Field = relation.Field |
|
joinRel.References = append(joinRel.References, &Reference{ |
|
PrimaryKey: fieldsMap[f.Name], |
|
ForeignKey: f, |
|
}) |
|
} else { |
|
joinRefRel := relation.JoinTable.Relationships.Relations[relRefName] |
|
if joinRefRel.Field == nil { |
|
joinRefRel.Field = relation.Field |
|
} |
|
joinRefRel.References = append(joinRefRel.References, &Reference{ |
|
PrimaryKey: fieldsMap[f.Name], |
|
ForeignKey: f, |
|
}) |
|
} |
|
|
|
relation.References = append(relation.References, &Reference{ |
|
PrimaryKey: fieldsMap[f.Name], |
|
ForeignKey: f, |
|
OwnPrimaryKey: ownPrimaryField, |
|
}) |
|
} |
|
} |
|
} |
|
|
|
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 { |
|
primarySchema, foreignSchema = relation.FieldSchema, field.OwnerSchema |
|
} else { |
|
reguessOrErr() |
|
return |
|
} |
|
case guessHas: |
|
case guessEmbeddedHas: |
|
if field.OwnerSchema != nil { |
|
primarySchema, foreignSchema = field.OwnerSchema, relation.FieldSchema |
|
} else { |
|
reguessOrErr() |
|
return |
|
} |
|
} |
|
|
|
if len(relation.foreignKeys) > 0 { |
|
for _, foreignKey := range relation.foreignKeys { |
|
if f := foreignSchema.LookUpField(foreignKey); f != nil { |
|
foreignFields = append(foreignFields, f) |
|
} else { |
|
reguessOrErr() |
|
return |
|
} |
|
} |
|
} else { |
|
var primaryFields []*Field |
|
var 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 |
|
} |
|
|
|
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.LookUpField(name); f != nil { |
|
foreignFields = append(foreignFields, f) |
|
primaryFields = append(primaryFields, primaryField) |
|
break |
|
} |
|
} |
|
} |
|
} |
|
|
|
if len(foreignFields) == 0 { |
|
reguessOrErr() |
|
return |
|
} else if 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 |
|
} |
|
} |
|
} else if 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 |
|
} |
|
} |
|
|
|
type Constraint struct { |
|
Name string |
|
Field *Field |
|
Schema *Schema |
|
ForeignKeys []*Field |
|
ReferenceSchema *Schema |
|
References []*Field |
|
OnDelete string |
|
OnUpdate string |
|
} |
|
|
|
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 |
|
}
|
|
|