Implement parse relationship architecture

This commit is contained in:
Jinzhu 2020-02-01 12:46:52 +08:00
parent 010dc7e6dd
commit eea78f3f30
7 changed files with 226 additions and 62 deletions

View File

@ -59,7 +59,7 @@ type OverrideNameInterface interface {
type Where struct { type Where struct {
AndConditions AddConditions AndConditions AddConditions
ORConditions []ORConditions ORConditions []ORConditions
Builders []Expression builders []Expression
} }
func (where Where) Name() string { func (where Where) Name() string {
@ -74,8 +74,8 @@ func (where Where) Build(builder Builder) {
where.AndConditions.Build(builder) where.AndConditions.Build(builder)
} }
if len(where.Builders) > 0 { if len(where.builders) > 0 {
for _, b := range where.Builders { for _, b := range where.builders {
if withConditions { if withConditions {
builder.Write(" AND ") builder.Write(" AND ")
} }
@ -122,9 +122,9 @@ func (where Where) MergeExpression(expr Expression) {
if w, ok := expr.(Where); ok { if w, ok := expr.(Where); ok {
where.AndConditions = append(where.AndConditions, w.AndConditions...) where.AndConditions = append(where.AndConditions, w.AndConditions...)
where.ORConditions = append(where.ORConditions, w.ORConditions...) where.ORConditions = append(where.ORConditions, w.ORConditions...)
where.Builders = append(where.Builders, w.Builders...) where.builders = append(where.builders, w.builders...)
} else { } else {
where.Builders = append(where.Builders, expr) where.builders = append(where.builders, expr)
} }
} }
@ -135,6 +135,22 @@ type Select struct {
// Join join clause // Join join clause
type Join struct { type Join struct {
Table string
Type string // left join books on
ON []Expression
builders []Expression
}
func (join Join) Build(builder Builder) {
// TODO
}
func (join Join) MergeExpression(expr Expression) {
if j, ok := expr.(Join); ok {
join.builders = append(join.builders, j.builders...)
} else {
join.builders = append(join.builders, expr)
}
} }
// GroupBy group by clause // GroupBy group by clause

View File

@ -2,6 +2,12 @@ package clause
import "strings" import "strings"
// Column quote with name
type Column struct {
Table string
Name string
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Query Expressions // Query Expressions
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View File

@ -8,10 +8,10 @@ import (
"time" "time"
) )
type FieldType string type DataType string
const ( const (
Bool FieldType = "bool" Bool DataType = "bool"
Int = "int" Int = "int"
Uint = "uint" Uint = "uint"
Float = "float" Float = "float"
@ -24,7 +24,7 @@ type Field struct {
Name string Name string
DBName string DBName string
BindNames []string BindNames []string
DataType FieldType DataType DataType
DBDataType string DBDataType string
PrimaryKey bool PrimaryKey bool
AutoIncrement bool AutoIncrement bool
@ -42,8 +42,7 @@ type Field struct {
Tag reflect.StructTag Tag reflect.StructTag
TagSettings map[string]string TagSettings map[string]string
Schema *Schema Schema *Schema
EmbeddedbSchema *Schema EmbeddedSchema *Schema
Relationship string
} }
func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field { func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field {
@ -177,8 +176,8 @@ func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field {
} }
if _, ok := field.TagSettings["EMBEDDED"]; ok || fieldStruct.Anonymous { if _, ok := field.TagSettings["EMBEDDED"]; ok || fieldStruct.Anonymous {
field.EmbeddedbSchema = Parse(fieldValue, sync.Map{}, schema.namer) field.EmbeddedSchema, schema.err = Parse(fieldValue, sync.Map{}, schema.namer)
for _, ef := range field.EmbeddedbSchema.Fields { for _, ef := range field.EmbeddedSchema.Fields {
ef.BindNames = append([]string{fieldStruct.Name}, ef.BindNames...) ef.BindNames = append([]string{fieldStruct.Name}, ef.BindNames...)
if prefix, ok := field.TagSettings["EMBEDDED_PREFIX"]; ok { if prefix, ok := field.TagSettings["EMBEDDED_PREFIX"]; ok {
@ -189,13 +188,6 @@ func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field {
ef.TagSettings[k] = v ef.TagSettings[k] = v
} }
} }
} else {
switch fieldValue.Kind() {
case reflect.Struct:
field.Relationship = "one"
case reflect.Slice:
field.Relationship = "many"
}
} }
return field return field

View File

@ -10,8 +10,10 @@ import (
// Namer namer interface // Namer namer interface
type Namer interface { type Namer interface {
TableName(string) string TableName(table string) string
ColumnName(string) string ColumnName(column string) string
JoinTableName(table string) string
JoinTableColumnName(table, column string) string
} }
// NamingStrategy tables, columns naming strategy // NamingStrategy tables, columns naming strategy
@ -33,6 +35,16 @@ func (ns NamingStrategy) ColumnName(str string) string {
return toDBName(str) return toDBName(str)
} }
// JoinTableName convert string to join table name
func (ns NamingStrategy) JoinTableName(str string) string {
return ns.TablePrefix + toDBName(str)
}
// JoinTableColumnName convert string to join table column name
func (ns NamingStrategy) JoinTableColumnName(referenceTable, referenceColumn string) string {
return inflection.Singular(toDBName(referenceTable)) + toDBName(referenceColumn)
}
var ( var (
smap sync.Map smap sync.Map
// https://github.com/golang/lint/blob/master/lint.go#L770 // https://github.com/golang/lint/blob/master/lint.go#L770

View File

@ -1,43 +1,143 @@
package schema package schema
import (
"fmt"
"reflect"
"strings"
)
// RelationshipType relationship type // RelationshipType relationship type
type RelationshipType string type RelationshipType string
const ( const (
HasOneRel RelationshipType = "has_one" // HasOneRel has one relationship HasOne RelationshipType = "has_one" // HasOneRel has one relationship
HasManyRel RelationshipType = "has_many" // HasManyRel has many relationship HasMany RelationshipType = "has_many" // HasManyRel has many relationship
BelongsToRel RelationshipType = "belongs_to" // BelongsToRel belongs to relationship BelongsTo RelationshipType = "belongs_to" // BelongsToRel belongs to relationship
Many2ManyRel RelationshipType = "many_to_many" // Many2ManyRel many to many relationship Many2Many RelationshipType = "many_to_many" // Many2ManyRel many to many relationship
) )
type Relationships struct { type Relationships struct {
HasOne map[string]*Relationship HasOne []*Relationship
BelongsTo map[string]*Relationship BelongsTo []*Relationship
HasMany map[string]*Relationship HasMany []*Relationship
Many2Many map[string]*Relationship Many2Many []*Relationship
Relations map[string]*Relationship
} }
type Relationship struct { type Relationship struct {
Name string
Type RelationshipType Type RelationshipType
ForeignKeys []*RelationField // self Field *Field
AssociationForeignKeys []*RelationField // association Polymorphic *Polymorphic
JoinTable *JoinTable References []Reference
Schema *Schema
FieldSchema *Schema
JoinTable *Schema
ForeignKeys, AssociationForeignKeys []string
} }
type RelationField struct { type Polymorphic struct {
*Field PolymorphicID *Field
PolymorphicField *Field PolymorphicType *Field
PolymorphicValue string Value string
} }
type JoinTable struct { type Reference struct {
Table string PriamryKey *Field
ForeignKeys []*RelationField PriamryValue string
AssociationForeignKeys []*RelationField ForeignKey *Field
OwnPriamryKey bool
} }
func (schema *Schema) buildToOneRel(field *Field) { func (schema *Schema) parseRelation(field *Field) {
var (
fieldValue = reflect.New(field.FieldType).Interface()
relation = &Relationship{
Name: field.Name,
Field: field,
Schema: schema,
Type: RelationshipType(strings.ToLower(strings.TrimSpace(field.TagSettings["REL"]))),
ForeignKeys: toColumns(field.TagSettings["FOREIGNKEY"]),
AssociationForeignKeys: toColumns(field.TagSettings["ASSOCIATION_FOREIGNKEY"]),
}
)
if relation.FieldSchema, schema.err = Parse(fieldValue, schema.cacheStore, schema.namer); schema.err != nil {
return
}
// 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
// }
if polymorphic, _ := field.TagSettings["POLYMORPHIC"]; polymorphic != "" {
relation.Polymorphic = &Polymorphic{
Value: schema.Table,
PolymorphicType: relation.FieldSchema.FieldsByName[polymorphic+"Type"],
PolymorphicID: relation.FieldSchema.FieldsByName[polymorphic+"ID"],
}
if value, ok := field.TagSettings["POLYMORPHIC_VALUE"]; ok {
relation.Polymorphic.Value = strings.TrimSpace(value)
}
if relation.Polymorphic.PolymorphicType == nil {
schema.err = fmt.Errorf("invalid polymorphic type: %v for %v on field %v, missing field %v", relation.FieldSchema, schema, field.Name, polymorphic+"Type")
}
if relation.Polymorphic.PolymorphicID == nil {
schema.err = fmt.Errorf("invalid polymorphic type: %v for %v on field %v, missing field %v", relation.FieldSchema, schema, field.Name, polymorphic+"ID")
}
if schema.err == nil {
relation.References = append(relation.References, Reference{
PriamryValue: 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 key: %+v for %v on field %v", relation.ForeignKeys, schema, field.Name)
}
}
relation.References = append(relation.References, Reference{
PriamryKey: primaryKeyField,
ForeignKey: relation.Polymorphic.PolymorphicType,
OwnPriamryKey: true,
})
}
switch field.FieldType.Kind() {
case reflect.Struct:
relation.Type = HasOne
case reflect.Slice:
relation.Type = HasMany
}
return
}
switch field.FieldType.Kind() {
case reflect.Struct:
schema.parseStructRelation(relation, field)
case reflect.Slice:
schema.parseSliceRelation(relation, field)
default:
schema.err = fmt.Errorf("unsupported data type: %v (in %v#%v ", field.FieldType.PkgPath(), schema, field.Name)
}
} }
func (schema *Schema) buildToManyRel(field *Field) { func (schema *Schema) parseStructRelation(relation *Relationship, field *Field) error {
return nil
}
func (schema *Schema) parseSliceRelation(relation *Relationship, field *Field) error {
return nil
} }

View File

@ -1,6 +1,7 @@
package schema package schema
import ( import (
"fmt"
"go/ast" "go/ast"
"reflect" "reflect"
"strings" "strings"
@ -8,6 +9,7 @@ import (
) )
type Schema struct { type Schema struct {
Name string
ModelType reflect.Type ModelType reflect.Type
Table string Table string
PrioritizedPrimaryField *Field PrioritizedPrimaryField *Field
@ -16,42 +18,64 @@ type Schema struct {
FieldsByName map[string]*Field FieldsByName map[string]*Field
FieldsByDBName map[string]*Field FieldsByDBName map[string]*Field
Relationships Relationships Relationships Relationships
err error
namer Namer namer Namer
cacheStore sync.Map
}
func (schema Schema) String() string {
return schema.ModelType.PkgPath()
}
func (schema Schema) LookUpField(name string) *Field {
if field, ok := schema.FieldsByDBName[name]; ok {
return field
}
if field, ok := schema.FieldsByName[name]; ok {
return field
}
return nil
} }
// get data type from dialector // get data type from dialector
func Parse(dest interface{}, cacheStore sync.Map, namer Namer) *Schema { func Parse(dest interface{}, cacheStore sync.Map, namer Namer) (*Schema, error) {
modelType := reflect.ValueOf(dest).Type() modelType := reflect.ValueOf(dest).Type()
for modelType.Kind() == reflect.Slice || modelType.Kind() == reflect.Ptr { for modelType.Kind() == reflect.Slice || modelType.Kind() == reflect.Ptr {
modelType = modelType.Elem() modelType = modelType.Elem()
} }
if modelType.Kind() != reflect.Struct { if modelType.Kind() != reflect.Struct {
return nil if modelType.PkgPath() == "" {
return nil, fmt.Errorf("unsupported data %+v when parsing model", dest)
}
return nil, fmt.Errorf("unsupported data type %v when parsing model", modelType.PkgPath())
} }
if v, ok := cacheStore.Load(modelType); ok { if v, ok := cacheStore.Load(modelType); ok {
return v.(*Schema) return v.(*Schema), nil
} }
schema := &Schema{ schema := &Schema{
Name: modelType.Name(),
ModelType: modelType, ModelType: modelType,
Table: namer.TableName(modelType.Name()), Table: namer.TableName(modelType.Name()),
FieldsByName: map[string]*Field{}, FieldsByName: map[string]*Field{},
FieldsByDBName: map[string]*Field{}, FieldsByDBName: map[string]*Field{},
cacheStore: cacheStore,
} }
defer func() {
if schema.err != nil {
cacheStore.Delete(modelType)
}
}()
for i := 0; i < modelType.NumField(); i++ { for i := 0; i < modelType.NumField(); i++ {
fieldStruct := modelType.Field(i) if fieldStruct := modelType.Field(i); ast.IsExported(fieldStruct.Name) {
if !ast.IsExported(fieldStruct.Name) {
continue
}
field := schema.ParseField(fieldStruct) field := schema.ParseField(fieldStruct)
schema.Fields = append(schema.Fields, field) schema.Fields = append(schema.Fields, field)
if field.EmbeddedbSchema != nil { if field.EmbeddedSchema != nil {
for _, f := range field.EmbeddedbSchema.Fields { schema.Fields = append(schema.Fields, field.EmbeddedSchema.Fields...)
schema.Fields = append(schema.Fields, f)
} }
} }
} }
@ -85,7 +109,12 @@ func Parse(dest interface{}, cacheStore sync.Map, namer Namer) *Schema {
} }
schema.PrimaryFields = append(schema.PrimaryFields, field) schema.PrimaryFields = append(schema.PrimaryFields, field)
} }
if field.DataType == "" {
defer schema.parseRelation(field)
}
} }
return schema cacheStore.Store(modelType, schema)
return schema, schema.err
} }

View File

@ -29,3 +29,12 @@ func checkTruth(val string) bool {
} }
return true return true
} }
func toColumns(val string) (results []string) {
if val != "" {
for _, v := range strings.Split(val, ",") {
results = append(results, strings.TrimSpace(v))
}
}
return
}