Implement schema parser

This commit is contained in:
Jinzhu 2020-01-31 12:22:37 +08:00
parent 5959c81be6
commit 1079e17caf
5 changed files with 320 additions and 38 deletions

View File

@ -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
}

202
schema/field.go Normal file
View File

@ -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
}

View File

@ -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) {
}

80
schema/schema.go Normal file
View File

@ -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
}

31
schema/utils.go Normal file
View File

@ -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
}