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 | ||||
| } | ||||
							
								
								
									
										202
									
								
								schema/field.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								schema/field.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @ -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
									
								
							
							
						
						
									
										80
									
								
								schema/schema.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										31
									
								
								schema/utils.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Jinzhu
						Jinzhu