From 4242d850ffe89b1015c818f502addf4c40956f85 Mon Sep 17 00:00:00 2001 From: xing Date: Fri, 19 May 2023 23:14:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=20hasone/many=E4=B8=8D?= =?UTF-8?q?=E7=94=A8=E5=8F=8D=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/condition.go | 38 +++++------ model/query_test.go | 126 ++++++++++++++++++++++++++---------- model/querycondition.go | 14 ++-- model/relation.go | 138 ++++++++++++++++++---------------------- 4 files changed, 183 insertions(+), 133 deletions(-) diff --git a/model/condition.go b/model/condition.go index 5b8cf7a..716d88a 100644 --- a/model/condition.go +++ b/model/condition.go @@ -1,18 +1,19 @@ package model +import "context" + type QueryCondition struct { - Where ParseWhere - From string - Fields string - Group string - Order SqlBuilder - Join SqlBuilder - Having SqlBuilder - Limit int - Offset int - In [][]any - Relation map[string]*QueryCondition - WithJoin bool + Where ParseWhere + From string + Fields string + Group string + Order SqlBuilder + Join SqlBuilder + Having SqlBuilder + Limit int + Offset int + In [][]any + RelationFn []func() (bool, bool, *QueryCondition, func() (func(any) []any, func(any, any) error, any, Relationship)) } func Conditions(fns ...Condition) *QueryCondition { @@ -87,17 +88,16 @@ func In(in ...[]any) Condition { } } -func With(tableTag string, q *QueryCondition) Condition { +func WithCtx(ctx *context.Context) Condition { return func(c *QueryCondition) { - if c.Relation == nil { - c.Relation = map[string]*QueryCondition{} - } - c.Relation[tableTag] = q + *ctx = context.WithValue(*ctx, "ancestorsQueryCondition", c) } } -func WithJoin(isJoin bool) Condition { +func WithFn(getVal, isJoin bool, q *QueryCondition, fn func() (func(any) []any, func(any, any) error, any, Relationship)) Condition { return func(c *QueryCondition) { - c.WithJoin = isJoin + c.RelationFn = append(c.RelationFn, func() (bool, bool, *QueryCondition, func() (func(any) []any, func(any, any) error, any, Relationship)) { + return getVal, isJoin, q, fn + }) } } diff --git a/model/query_test.go b/model/query_test.go index 671e5f8..e51c6a0 100644 --- a/model/query_test.go +++ b/model/query_test.go @@ -3,6 +3,7 @@ package model import ( "context" "database/sql" + "errors" "fmt" "github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/safety" @@ -14,31 +15,74 @@ import ( ) type post struct { - Id uint64 `gorm:"column:ID" db:"ID" json:"ID" form:"ID"` - PostAuthor uint64 `gorm:"column:post_author" db:"post_author" json:"post_author" form:"post_author"` - PostDate time.Time `gorm:"column:post_date" db:"post_date" json:"post_date" form:"post_date"` - PostDateGmt time.Time `gorm:"column:post_date_gmt" db:"post_date_gmt" json:"post_date_gmt" form:"post_date_gmt"` - PostContent string `gorm:"column:post_content" db:"post_content" json:"post_content" form:"post_content"` - PostTitle string `gorm:"column:post_title" db:"post_title" json:"post_title" form:"post_title"` - PostExcerpt string `gorm:"column:post_excerpt" db:"post_excerpt" json:"post_excerpt" form:"post_excerpt"` - PostStatus string `gorm:"column:post_status" db:"post_status" json:"post_status" form:"post_status"` - CommentStatus string `gorm:"column:comment_status" db:"comment_status" json:"comment_status" form:"comment_status"` - PingStatus string `gorm:"column:ping_status" db:"ping_status" json:"ping_status" form:"ping_status"` - PostPassword string `gorm:"column:post_password" db:"post_password" json:"post_password" form:"post_password"` - PostName string `gorm:"column:post_name" db:"post_name" json:"post_name" form:"post_name"` - ToPing string `gorm:"column:to_ping" db:"to_ping" json:"to_ping" form:"to_ping"` - Pinged string `gorm:"column:pinged" db:"pinged" json:"pinged" form:"pinged"` - PostModified time.Time `gorm:"column:post_modified" db:"post_modified" json:"post_modified" form:"post_modified"` - PostModifiedGmt time.Time `gorm:"column:post_modified_gmt" db:"post_modified_gmt" json:"post_modified_gmt" form:"post_modified_gmt"` - PostContentFiltered string `gorm:"column:post_content_filtered" db:"post_content_filtered" json:"post_content_filtered" form:"post_content_filtered"` - PostParent uint64 `gorm:"column:post_parent" db:"post_parent" json:"post_parent" form:"post_parent"` - Guid string `gorm:"column:guid" db:"guid" json:"guid" form:"guid"` - MenuOrder int `gorm:"column:menu_order" db:"menu_order" json:"menu_order" form:"menu_order"` - PostType string `gorm:"column:post_type" db:"post_type" json:"post_type" form:"post_type"` - PostMimeType string `gorm:"column:post_mime_type" db:"post_mime_type" json:"post_mime_type" form:"post_mime_type"` - CommentCount int64 `gorm:"column:comment_count" db:"comment_count" json:"comment_count" form:"comment_count"` - User *user `table:"wp_users user" foreignKey:"ID" local:"post_author" relation:"hasOne"` - PostMeta *[]models.PostMeta `table:"wp_postmeta meta" foreignKey:"post_id" local:"ID" relation:"hasMany"` + Id uint64 `gorm:"column:ID" db:"ID" json:"ID" form:"ID"` + PostAuthor uint64 `gorm:"column:post_author" db:"post_author" json:"post_author" form:"post_author"` + PostDate time.Time `gorm:"column:post_date" db:"post_date" json:"post_date" form:"post_date"` + PostDateGmt time.Time `gorm:"column:post_date_gmt" db:"post_date_gmt" json:"post_date_gmt" form:"post_date_gmt"` + PostContent string `gorm:"column:post_content" db:"post_content" json:"post_content" form:"post_content"` + PostTitle string `gorm:"column:post_title" db:"post_title" json:"post_title" form:"post_title"` + PostExcerpt string `gorm:"column:post_excerpt" db:"post_excerpt" json:"post_excerpt" form:"post_excerpt"` + PostStatus string `gorm:"column:post_status" db:"post_status" json:"post_status" form:"post_status"` + CommentStatus string `gorm:"column:comment_status" db:"comment_status" json:"comment_status" form:"comment_status"` + PingStatus string `gorm:"column:ping_status" db:"ping_status" json:"ping_status" form:"ping_status"` + PostPassword string `gorm:"column:post_password" db:"post_password" json:"post_password" form:"post_password"` + PostName string `gorm:"column:post_name" db:"post_name" json:"post_name" form:"post_name"` + ToPing string `gorm:"column:to_ping" db:"to_ping" json:"to_ping" form:"to_ping"` + Pinged string `gorm:"column:pinged" db:"pinged" json:"pinged" form:"pinged"` + PostModified time.Time `gorm:"column:post_modified" db:"post_modified" json:"post_modified" form:"post_modified"` + PostModifiedGmt time.Time `gorm:"column:post_modified_gmt" db:"post_modified_gmt" json:"post_modified_gmt" form:"post_modified_gmt"` + PostContentFiltered string `gorm:"column:post_content_filtered" db:"post_content_filtered" json:"post_content_filtered" form:"post_content_filtered"` + PostParent uint64 `gorm:"column:post_parent" db:"post_parent" json:"post_parent" form:"post_parent"` + Guid string `gorm:"column:guid" db:"guid" json:"guid" form:"guid"` + MenuOrder int `gorm:"column:menu_order" db:"menu_order" json:"menu_order" form:"menu_order"` + PostType string `gorm:"column:post_type" db:"post_type" json:"post_type" form:"post_type"` + PostMimeType string `gorm:"column:post_mime_type" db:"post_mime_type" json:"post_mime_type" form:"post_mime_type"` + CommentCount int64 `gorm:"column:comment_count" db:"comment_count" json:"comment_count" form:"comment_count"` + User *user + PostMeta *[]models.PostMeta +} + +func PostAuthor() (func(any) []any, func(posts, users any) error, any, Relationship) { + var u user + return func(a any) []any { + return []any{a.(*post).PostAuthor} + }, func(posts, users any) error { + u := users.(*user) + postss, ok := posts.(*post) + if !ok { + postsss, ok := posts.(*[]post) + if !ok { + return errors.New("无法识别post的类型") + } + for i := 0; i < len(*postsss); i++ { + (*postsss)[i].User = u + } + } + postss.User = u + return nil + + }, &u, Relationship{ + RelationType: "hasOne", + Table: "wp_users user", + ForeignKey: "ID", + Local: "post_author", + } +} +func PostMetas() (func(any) []any, func(posts, users any) error, any, Relationship) { + var u []models.PostMeta + return func(a any) []any { + return []any{a.(*post).Id} + }, func(posts, meta any) error { + postss := posts.(*post) + u := meta.(*[]models.PostMeta) + postss.PostMeta = u + return nil + }, &u, Relationship{ + RelationType: "hasMany", + Table: "wp_postmeta meta", + ForeignKey: "post_id", + Local: "ID", + } } type TermRelationships struct { @@ -339,17 +383,15 @@ func TestGets2(t *testing.T) { t.Run("hasOne", func(t *testing.T) { { q := Conditions( - Where(SqlBuilder{{"id = 190"}}), - With("user", Conditions( + Where(SqlBuilder{{"posts.id = 190"}}), + WithCtx(&ctx), + WithFn(true, true, Conditions( Fields("ID,user_login,user_pass"), - )), + ), PostAuthor), Fields("posts.*"), From("wp_posts posts"), - With("meta", Conditions( - WithJoin(true), - )), + WithFn(false, true, nil, PostMetas), ) - ctx = context.WithValue(ctx, "ancestorsQueryCondition", q) got, err := Gets[post](ctx, q) _ = got if err != nil { @@ -357,6 +399,26 @@ func TestGets2(t *testing.T) { } } }) + t.Run("hasOne", func(t *testing.T) { + { + q := Conditions( + Where(SqlBuilder{{"posts.id", "in", ""}}), + In([]any{190, 2978}), + WithCtx(&ctx), + WithFn(true, true, Conditions( + Fields("ID,user_login,user_pass"), + ), PostAuthor), + Fields("posts.*"), + From("wp_posts posts"), + WithFn(false, false, nil, PostMetas), + ) + got, err := Finds[post](ctx, q) + _ = got + if err != nil { + t.Errorf("err:%v", err) + } + } + }) } func TestFirstOne(t *testing.T) { diff --git a/model/querycondition.go b/model/querycondition.go index 6fd3615..90a07bd 100644 --- a/model/querycondition.go +++ b/model/querycondition.go @@ -27,12 +27,16 @@ func FindFromDB[T Model](db dbQuery, ctx context.Context, q *QueryCondition) (r func finds[T Model](db dbQuery, ctx context.Context, q *QueryCondition) (r []T, err error) { setTable[T](q) - sq, args, err := BuildQuerySql(q) - if err != nil { + if len(q.RelationFn) < 1 { + sq, args, er := BuildQuerySql(q) + if err != nil { + err = er + return + } + err = db.Select(ctx, &r, sq, args...) return } - err = db.Select(ctx, &r, sq, args...) - + err = parseRelation(true, db, ctx, &r, q) return } @@ -304,7 +308,7 @@ func GetsFromDB[T Model](db dbQuery, ctx context.Context, q *QueryCondition) (T, func gets[T Model](db dbQuery, ctx context.Context, q *QueryCondition) (r T, err error) { setTable[T](q) - if len(q.Relation) < 1 { + if len(q.RelationFn) < 1 { s, args, er := BuildQuerySql(q) if er != nil { err = er diff --git a/model/relation.go b/model/relation.go index f150c77..556d063 100644 --- a/model/relation.go +++ b/model/relation.go @@ -2,9 +2,9 @@ package model import ( "context" + "database/sql" "fmt" "github.com/fthvgb1/wp-go/helper" - "reflect" "strings" ) @@ -14,89 +14,73 @@ func setTable[T Model](q *QueryCondition) { } } +type Relationship struct { + RelationType string + Table string + ForeignKey string + Local string + On string +} + func Relation(db dbQuery, ctx context.Context, r any, q *QueryCondition) ([]func(), []func() error) { var fn []func() var fns []func() error - t := reflect.TypeOf(r).Elem() - v := reflect.ValueOf(r).Elem() - for tableTag, relation := range q.Relation { - if tableTag == "" { - continue - } - tableTag := tableTag - relation := relation - for i := 0; i < t.NumField(); i++ { - i := i - tag := t.Field(i).Tag - table, ok := tag.Lookup("table") - if !ok || table == "" { - continue - } - tables := strings.Split(table, " ") - if tables[len(tables)-1] != tableTag { - continue - } - foreignKey := tag.Get("foreignKey") - if foreignKey == "" { - continue - } - localKey := tag.Get("local") - if localKey == "" { - continue - } - if relation == nil { - relation = &QueryCondition{ - Fields: "*", - } - } - relation.From = table - id := "" - j := 0 - for ; j < t.NumField(); j++ { - vvv, ok := t.Field(j).Tag.Lookup("db") - if ok && vvv == tag.Get("local") { - break - } - } - if relation.WithJoin { + for _, f := range q.RelationFn { + getVal, isJoin, qq, ff := f() + idFn, assignment, rr, ship := ff() + if isJoin { + fn = append(fn, func() { + tables := strings.Split(ship.Table, " ") from := strings.Split(q.From, " ") - fn = append(fn, func() { - qq := helper.GetContextVal(ctx, "ancestorsQueryCondition", q) - qq.Join = append(q.Join, SqlBuilder{ - {"left join", table, fmt.Sprintf("%s.%s=%s.%s", tables[len(tables)-1], foreignKey, from[len(from)-1], localKey)}, - }...) - }) - } - fns = append(fns, func() error { - { - var w any = relation.Where - if w == nil { - w = SqlBuilder{} - } - ww, ok := w.(SqlBuilder) - if ok { - id = fmt.Sprintf("%v", v.Field(j).Interface()) - ww = append(ww, SqlBuilder{{ - foreignKey, "=", id, "int", - }}...) - relation.Where = ww - } + on := "" + if ship.On != "" { + on = fmt.Sprintf("and %s", on) } - var err error - vv := reflect.New(v.Field(i).Type().Elem()).Interface() - switch tag.Get("relation") { - case "hasOne": - err = parseRelation(false, db, ctx, vv, relation) - case "hasMany": - err = parseRelation(true, db, ctx, vv, relation) - } - if err != nil { - return err - } - v.Field(i).Set(reflect.ValueOf(vv)) - return nil + qq := helper.GetContextVal(ctx, "ancestorsQueryCondition", q) + qq.Join = append(qq.Join, []string{ + "left join", ship.Table, fmt.Sprintf("%s.%s=%s.%s %s", tables[len(tables)-1], ship.ForeignKey, from[len(from)-1], ship.Local, on)}) }) } + if !getVal { + continue + } + fns = append(fns, func() error { + var err error + { + if qq == nil { + qq = &QueryCondition{ + Fields: "*", + } + } + var w any = qq.Where + if w == nil { + w = SqlBuilder{} + } + ww, ok := w.(SqlBuilder) + if ok { + ww = append(ww, SqlBuilder{{ + ship.ForeignKey, "in", "", + }}...) + qq.In = [][]any{idFn(r)} + qq.Where = ww + } + if qq.From == "" { + qq.From = ship.Table + } + } + // todo finds的情况 + switch ship.RelationType { + case "hasOne": + err = parseRelation(false, db, ctx, rr, qq) + case "hasMany": + err = parseRelation(true, db, ctx, rr, qq) + } + if err != nil && err != sql.ErrNoRows { + return err + } + err = assignment(r, rr) + return err + }) } return fn, fns }