gorm/schema/schema.go

247 lines
6.5 KiB
Go

package schema
import (
"database/sql"
"go/ast"
"reflect"
"sort"
"time"
)
// Schema model schema definition
type Schema struct {
ModelType reflect.Type
PrimaryFields []*Field
Fields []*Field
TableName string
ParseErrors []error
}
// Field schema field definition
type Field struct {
DBName string
Name string
BindNames []string
Tag reflect.StructTag
TagSettings map[string]string
IsNormal bool
IsPrimaryKey bool
IsIgnored bool
IsForeignKey bool
DefaultValue string
HasDefaultValue bool
StructField reflect.StructField
Relationship *Relationship
}
// Parse parse struct and generate schema based on struct and tag definition
func Parse(dest interface{}) *Schema {
schema := Schema{}
// Get dest type
reflectType := reflect.ValueOf(dest).Type()
for reflectType.Kind() == reflect.Slice || reflectType.Kind() == reflect.Ptr {
reflectType = reflectType.Elem()
}
if reflectType.Kind() != reflect.Struct {
return nil
}
schema.ModelType = reflectType
onConflictFields := map[string]int{}
for i := 0; i < reflectType.NumField(); i++ {
fieldStruct := reflectType.Field(i)
if !ast.IsExported(fieldStruct.Name) {
continue
}
field := &Field{
Name: fieldStruct.Name,
BindNames: []string{fieldStruct.Name},
StructField: fieldStruct,
Tag: fieldStruct.Tag,
TagSettings: parseTagSetting(fieldStruct.Tag),
}
if _, ok := field.TagSettings["-"]; ok {
field.IsIgnored = true
} else {
if val, ok := field.TagSettings["PRIMARY_KEY"]; ok && checkTruth(val) {
field.IsPrimaryKey = true
schema.PrimaryFields = append(schema.PrimaryFields, field)
}
if val, ok := field.TagSettings["AUTO_INCREMENT"]; ok && checkTruth(val) && !field.IsPrimaryKey {
field.HasDefaultValue = true
}
if v, ok := field.TagSettings["DEFAULT"]; ok {
field.DefaultValue = v
field.HasDefaultValue = true
}
indirectType := fieldStruct.Type
for indirectType.Kind() == reflect.Ptr {
indirectType = indirectType.Elem()
}
fieldValue := reflect.New(indirectType).Interface()
if _, isScanner := fieldValue.(sql.Scanner); isScanner {
// scanner
field.IsNormal = true
if indirectType.Kind() == reflect.Struct {
// Use tag settings from scanner
for i := 0; i < indirectType.NumField(); i++ {
for key, value := range parseTagSetting(indirectType.Field(i).Tag) {
if _, ok := field.TagSettings[key]; !ok {
field.TagSettings[key] = value
}
}
}
}
} else if _, isTime := fieldValue.(*time.Time); isTime {
// time
field.IsNormal = true
} else if _, ok := field.TagSettings["EMBEDDED"]; ok || fieldStruct.Anonymous {
// embedded struct
if subSchema := Parse(fieldValue); subSchema != nil {
for _, subField := range subSchema.Fields {
subField = subField.clone()
subField.BindNames = append([]string{fieldStruct.Name}, subField.BindNames...)
if prefix, ok := field.TagSettings["EMBEDDED_PREFIX"]; ok {
subField.DBName = prefix + subField.DBName
}
if subField.IsPrimaryKey {
if _, ok := subField.TagSettings["PRIMARY_KEY"]; ok {
schema.PrimaryFields = append(schema.PrimaryFields, subField)
} else {
subField.IsPrimaryKey = false
}
}
for key, value := range field.TagSettings {
subField.TagSettings[key] = value
}
schema.Fields = append(schema.Fields, subField)
}
}
continue
} else {
// build relationships
switch indirectType.Kind() {
case reflect.Struct:
defer buildToOneRel(field, &schema)
case reflect.Slice:
defer buildToManyRel(field, &schema)
default:
field.IsNormal = true
}
}
}
// Even it is ignored, also possible to decode db value into the field
if dbName, ok := field.TagSettings["COLUMN"]; ok {
field.DBName = dbName
} else if field.DBName == "" {
field.DBName = ToDBName(fieldStruct.Name)
}
if _, ok := field.TagSettings["ON_EMBEDDED_CONFLICT"]; ok {
onConflictFields[field.Name] = len(schema.Fields)
}
schema.Fields = append(schema.Fields, field)
}
if len(onConflictFields) > 0 {
removeIdx := []int{}
updatePrimaryKey := func(field, conflictField *Field) {
if field != nil && field.IsPrimaryKey {
for i, p := range schema.PrimaryFields {
if p == field {
schema.PrimaryFields = append(schema.PrimaryFields[0:i], schema.PrimaryFields[i+1:]...)
}
}
}
if conflictField != nil && conflictField.IsPrimaryKey {
schema.PrimaryFields = append(schema.PrimaryFields, conflictField)
}
}
for _, idx := range onConflictFields {
conflictField := schema.Fields[idx]
for i, field := range schema.Fields {
if i != idx && conflictField.Name == field.Name {
switch conflictField.TagSettings["ON_EMBEDDED_CONFLICT"] {
case "replace":
// if original field is primary key, delete origianl one
// add conflicated one if it is primary key
if field.IsPrimaryKey {
updatePrimaryKey(field, conflictField)
}
removeIdx = append(removeIdx, i)
case "ignore":
// skip ignored field
updatePrimaryKey(conflictField, nil)
removeIdx = append(removeIdx, idx)
case "update":
// if original field is primary key, delete origianl one
// add conflicated one if it is primary key
if field.IsPrimaryKey {
updatePrimaryKey(field, conflictField)
}
for key, value := range field.TagSettings {
if _, ok := conflictField.TagSettings[key]; !ok {
conflictField.TagSettings[key] = value
}
}
conflictField.BindNames = field.BindNames
if column, ok := conflictField.TagSettings["COLUMN"]; ok {
conflictField.DBName = column
}
*field = *conflictField
removeIdx = append(removeIdx, idx)
}
}
}
}
sort.Ints(removeIdx)
for i := len(removeIdx) - 1; i >= 0; i-- {
schema.Fields = append(schema.Fields[0:removeIdx[i]], schema.Fields[removeIdx[i]+1:]...)
}
}
if len(schema.PrimaryFields) == 0 {
if field := getSchemaField("id", schema.Fields); field != nil {
field.IsPrimaryKey = true
schema.PrimaryFields = append(schema.PrimaryFields, field)
}
}
return &schema
}
func (schemaField *Field) clone() *Field {
clone := *schemaField
if schemaField.Relationship != nil {
relationship := *schemaField.Relationship
clone.Relationship = &relationship
}
for key, value := range schemaField.TagSettings {
clone.TagSettings[key] = value
}
return &clone
}