forked from mirror/gorm
Implement parse relationship architecture
This commit is contained in:
parent
010dc7e6dd
commit
eea78f3f30
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue