mirror of https://github.com/go-gorm/gorm.git
Implement schema parser
This commit is contained in:
parent
5959c81be6
commit
1079e17caf
|
@ -1,37 +0,0 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
ModelType reflect.Type
|
||||
Table string
|
||||
PrioritizedPrimaryField *Field
|
||||
PrimaryFields []*Field
|
||||
Fields []*Field
|
||||
FieldsByName map[string]*Field
|
||||
FieldsByDBName map[string]*Field
|
||||
Relationships Relationships
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
Name string
|
||||
DBName string
|
||||
DataType reflect.Type
|
||||
DBDataType string
|
||||
Tag reflect.StructTag
|
||||
TagSettings map[string]string
|
||||
PrimaryKey bool
|
||||
AutoIncrement bool
|
||||
Creatable bool
|
||||
Updatable bool
|
||||
Nullable bool
|
||||
Unique bool
|
||||
Precision int
|
||||
Size int
|
||||
HasDefaultValue bool
|
||||
DefaultValue string
|
||||
StructField reflect.StructField
|
||||
Model *Model
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FieldType string
|
||||
|
||||
const (
|
||||
Bool FieldType = "bool"
|
||||
Int = "int"
|
||||
Uint = "uint"
|
||||
Float = "float"
|
||||
String = "string"
|
||||
Time = "time"
|
||||
Bytes = "bytes"
|
||||
)
|
||||
|
||||
type Field struct {
|
||||
Name string
|
||||
DBName string
|
||||
BindNames []string
|
||||
DataType FieldType
|
||||
DBDataType string
|
||||
PrimaryKey bool
|
||||
AutoIncrement bool
|
||||
Creatable bool
|
||||
Updatable bool
|
||||
HasDefaultValue bool
|
||||
DefaultValue string
|
||||
NotNull bool
|
||||
Unique bool
|
||||
Comment string
|
||||
Size int
|
||||
Precision int
|
||||
FieldType reflect.Type
|
||||
StructField reflect.StructField
|
||||
Tag reflect.StructTag
|
||||
TagSettings map[string]string
|
||||
Schema *Schema
|
||||
EmbeddedbSchema *Schema
|
||||
Relationship string
|
||||
}
|
||||
|
||||
func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field {
|
||||
field := &Field{
|
||||
Name: fieldStruct.Name,
|
||||
BindNames: []string{fieldStruct.Name},
|
||||
FieldType: fieldStruct.Type,
|
||||
StructField: fieldStruct,
|
||||
Creatable: true,
|
||||
Updatable: true,
|
||||
Tag: fieldStruct.Tag,
|
||||
TagSettings: parseTagSetting(fieldStruct.Tag),
|
||||
}
|
||||
|
||||
for field.FieldType.Kind() == reflect.Ptr {
|
||||
field.FieldType = field.FieldType.Elem()
|
||||
}
|
||||
|
||||
fieldValue := reflect.New(field.FieldType)
|
||||
|
||||
// if field is valuer, used its value or first fields as data type
|
||||
if valuer, isValuer := fieldValue.Interface().(driver.Valuer); isValuer {
|
||||
var overrideFieldValue bool
|
||||
if v, err := valuer.Value(); v != nil && err == nil {
|
||||
overrideFieldValue = true
|
||||
fieldValue = reflect.ValueOf(v)
|
||||
}
|
||||
|
||||
if field.FieldType.Kind() == reflect.Struct {
|
||||
for i := 0; i < field.FieldType.NumField(); i++ {
|
||||
if !overrideFieldValue {
|
||||
newFieldType := field.FieldType.Field(i).Type
|
||||
for newFieldType.Kind() == reflect.Ptr {
|
||||
newFieldType = newFieldType.Elem()
|
||||
}
|
||||
|
||||
fieldValue = reflect.New(newFieldType)
|
||||
overrideFieldValue = true
|
||||
}
|
||||
|
||||
// copy tag settings from valuer
|
||||
for key, value := range parseTagSetting(field.FieldType.Field(i).Tag) {
|
||||
if _, ok := field.TagSettings[key]; !ok {
|
||||
field.TagSettings[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setup permission
|
||||
if _, ok := field.TagSettings["-"]; ok {
|
||||
field.Creatable = false
|
||||
field.Updatable = false
|
||||
}
|
||||
|
||||
if dbName, ok := field.TagSettings["COLUMN"]; ok {
|
||||
field.DBName = dbName
|
||||
}
|
||||
|
||||
if val, ok := field.TagSettings["PRIMARY_KEY"]; ok && checkTruth(val) {
|
||||
field.PrimaryKey = true
|
||||
}
|
||||
|
||||
if val, ok := field.TagSettings["AUTO_INCREMENT"]; ok && checkTruth(val) {
|
||||
field.AutoIncrement = true
|
||||
field.HasDefaultValue = true
|
||||
}
|
||||
|
||||
if v, ok := field.TagSettings["DEFAULT"]; ok {
|
||||
field.HasDefaultValue = true
|
||||
field.DefaultValue = v
|
||||
}
|
||||
|
||||
if num, ok := field.TagSettings["SIZE"]; ok {
|
||||
field.Size, _ = strconv.Atoi(num)
|
||||
}
|
||||
|
||||
if p, ok := field.TagSettings["PRECISION"]; ok {
|
||||
field.Precision, _ = strconv.Atoi(p)
|
||||
}
|
||||
|
||||
if val, ok := field.TagSettings["NOT NULL"]; ok && checkTruth(val) {
|
||||
field.NotNull = true
|
||||
}
|
||||
|
||||
if val, ok := field.TagSettings["UNIQUE"]; ok && checkTruth(val) {
|
||||
field.Unique = true
|
||||
}
|
||||
|
||||
if val, ok := field.TagSettings["COMMENT"]; ok {
|
||||
field.Comment = val
|
||||
}
|
||||
|
||||
if val, ok := field.TagSettings["TYPE"]; ok {
|
||||
field.DBDataType = val
|
||||
}
|
||||
|
||||
switch fieldValue.Kind() {
|
||||
case reflect.Bool:
|
||||
field.DataType = Bool
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
field.DataType = Int
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
field.DataType = Uint
|
||||
case reflect.Float32, reflect.Float64:
|
||||
field.DataType = Float
|
||||
case reflect.String:
|
||||
field.DataType = String
|
||||
case reflect.Struct:
|
||||
if _, ok := fieldValue.Interface().(time.Time); ok {
|
||||
field.DataType = Time
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
if fieldValue.Type().Elem() == reflect.TypeOf(uint8(0)) {
|
||||
field.DataType = Bytes
|
||||
}
|
||||
}
|
||||
|
||||
if field.Size == 0 {
|
||||
switch 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
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := field.TagSettings["EMBEDDED"]; ok || fieldStruct.Anonymous {
|
||||
field.EmbeddedbSchema = Parse(fieldValue, sync.Map{})
|
||||
for _, ef := range field.EmbeddedbSchema.Fields {
|
||||
ef.BindNames = append([]string{fieldStruct.Name}, ef.BindNames...)
|
||||
|
||||
if prefix, ok := field.TagSettings["EMBEDDED_PREFIX"]; ok {
|
||||
ef.DBName = prefix + ef.DBName
|
||||
}
|
||||
|
||||
for k, v := range field.TagSettings {
|
||||
ef.TagSettings[k] = v
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch fieldValue.Kind() {
|
||||
case reflect.Struct:
|
||||
field.Relationship = "one"
|
||||
case reflect.Slice:
|
||||
field.Relationship = "many"
|
||||
}
|
||||
}
|
||||
|
||||
return field
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package model
|
||||
package schema
|
||||
|
||||
// RelationshipType relationship type
|
||||
type RelationshipType string
|
||||
|
@ -35,3 +35,9 @@ type JoinTable struct {
|
|||
ForeignKeys []*RelationField
|
||||
AssociationForeignKeys []*RelationField
|
||||
}
|
||||
|
||||
func (schema *Schema) buildToOneRel(field *Field) {
|
||||
}
|
||||
|
||||
func (schema *Schema) buildToManyRel(field *Field) {
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Schema struct {
|
||||
ModelType reflect.Type
|
||||
Table string
|
||||
PrioritizedPrimaryField *Field
|
||||
PrimaryFields []*Field
|
||||
Fields []*Field
|
||||
FieldsByName map[string]*Field
|
||||
FieldsByDBName map[string]*Field
|
||||
Relationships Relationships
|
||||
}
|
||||
|
||||
// get data type from dialector
|
||||
func Parse(dest interface{}, cacheStore sync.Map) *Schema {
|
||||
modelType := reflect.ValueOf(dest).Type()
|
||||
for modelType.Kind() == reflect.Slice || modelType.Kind() == reflect.Ptr {
|
||||
modelType = modelType.Elem()
|
||||
}
|
||||
|
||||
if modelType.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v, ok := cacheStore.Load(modelType); ok {
|
||||
return v.(*Schema)
|
||||
}
|
||||
|
||||
schema := &Schema{
|
||||
ModelType: modelType,
|
||||
FieldsByName: map[string]*Field{},
|
||||
FieldsByDBName: map[string]*Field{},
|
||||
}
|
||||
|
||||
for i := 0; i < modelType.NumField(); i++ {
|
||||
fieldStruct := modelType.Field(i)
|
||||
if !ast.IsExported(fieldStruct.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
schema.Fields = append(schema.Fields, schema.ParseField(fieldStruct))
|
||||
// db namer
|
||||
}
|
||||
|
||||
for _, field := range schema.Fields {
|
||||
if field.DBName != "" {
|
||||
// nonexistence or shortest path or first appear prioritized
|
||||
if v, ok := schema.FieldsByDBName[field.DBName]; !ok || len(field.BindNames) < len(v.BindNames) {
|
||||
schema.FieldsByDBName[field.DBName] = field
|
||||
schema.FieldsByName[field.Name] = field
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := schema.FieldsByName[field.Name]; !ok {
|
||||
schema.FieldsByName[field.Name] = field
|
||||
}
|
||||
}
|
||||
|
||||
for db, field := range schema.FieldsByDBName {
|
||||
if strings.ToLower(db) == "id" {
|
||||
schema.PrioritizedPrimaryField = field
|
||||
}
|
||||
|
||||
if field.PrimaryKey {
|
||||
if schema.PrioritizedPrimaryField == nil {
|
||||
schema.PrioritizedPrimaryField = field
|
||||
}
|
||||
schema.PrimaryFields = append(schema.PrimaryFields, field)
|
||||
}
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func parseTagSetting(tags reflect.StructTag) map[string]string {
|
||||
setting := map[string]string{}
|
||||
|
||||
for _, value := range strings.Split(tags.Get("gorm"), ";") {
|
||||
if value != "" {
|
||||
v := strings.Split(value, ":")
|
||||
k := strings.TrimSpace(strings.ToUpper(v[0]))
|
||||
|
||||
if len(v) >= 2 {
|
||||
setting[k] = strings.Join(v[1:], ":")
|
||||
} else {
|
||||
setting[k] = k
|
||||
}
|
||||
}
|
||||
}
|
||||
return setting
|
||||
}
|
||||
|
||||
func checkTruth(val string) bool {
|
||||
if strings.ToLower(val) == "false" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
Loading…
Reference in New Issue