diff --git a/model/condition.go b/model/condition.go index 716d88a..c13fd47 100644 --- a/model/condition.go +++ b/model/condition.go @@ -13,9 +13,11 @@ type QueryCondition struct { Limit int Offset int In [][]any - RelationFn []func() (bool, bool, *QueryCondition, func() (func(any) []any, func(any, any) error, any, Relationship)) + RelationFn []func() (bool, bool, *QueryCondition, RelationFn) } +type RelationFn func() (func(any) []any, func(any, any), any, any, Relationship) + func Conditions(fns ...Condition) *QueryCondition { r := &QueryCondition{} for _, fn := range fns { @@ -94,9 +96,9 @@ func WithCtx(ctx *context.Context) Condition { } } -func WithFn(getVal, isJoin bool, q *QueryCondition, fn func() (func(any) []any, func(any, any) error, any, Relationship)) Condition { +func WithFn(getVal, isJoin bool, q *QueryCondition, fn func() (func(any) []any, func(any, any), any, any, Relationship)) Condition { return func(c *QueryCondition) { - c.RelationFn = append(c.RelationFn, func() (bool, bool, *QueryCondition, func() (func(any) []any, func(any, any) error, any, Relationship)) { + c.RelationFn = append(c.RelationFn, func() (bool, bool, *QueryCondition, RelationFn) { return getVal, isJoin, q, fn }) } diff --git a/model/query_test.go b/model/query_test.go index e51c6a0..ff633a6 100644 --- a/model/query_test.go +++ b/model/query_test.go @@ -3,7 +3,6 @@ package model import ( "context" "database/sql" - "errors" "fmt" "github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/safety" @@ -42,49 +41,6 @@ type post struct { 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 { ObjectID uint64 `db:"object_id"` TermTaxonomyId uint64 `db:"term_taxonomy_id"` @@ -379,48 +335,6 @@ func TestFindOneById(t *testing.T) { } } -func TestGets2(t *testing.T) { - t.Run("hasOne", func(t *testing.T) { - { - q := 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"), - WithFn(false, true, nil, PostMetas), - ) - got, err := Gets[post](ctx, q) - _ = got - if err != nil { - t.Errorf("err:%v", err) - } - } - }) - 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) { type args struct { where ParseWhere diff --git a/model/querycondition.go b/model/querycondition.go index 90a07bd..1a9118e 100644 --- a/model/querycondition.go +++ b/model/querycondition.go @@ -322,7 +322,7 @@ func gets[T Model](db dbQuery, ctx context.Context, q *QueryCondition) (r T, err } func parseRelation(isMultiple bool, db dbQuery, ctx context.Context, r any, q *QueryCondition) (err error) { - fn, fns := Relation(db, ctx, r, q) + fn, fns := Relation(isMultiple, db, ctx, r, q) for _, f := range fn { f() } diff --git a/model/relation.go b/model/relation.go index 556d063..dec55ff 100644 --- a/model/relation.go +++ b/model/relation.go @@ -5,6 +5,8 @@ import ( "database/sql" "fmt" "github.com/fthvgb1/wp-go/helper" + "github.com/fthvgb1/wp-go/helper/slice" + "golang.org/x/exp/constraints" "strings" ) @@ -22,12 +24,12 @@ type Relationship struct { On string } -func Relation(db dbQuery, ctx context.Context, r any, q *QueryCondition) ([]func(), []func() error) { +func Relation(isMultiple bool, db dbQuery, ctx context.Context, r any, q *QueryCondition) ([]func(), []func() error) { var fn []func() var fns []func() error for _, f := range q.RelationFn { getVal, isJoin, qq, ff := f() - idFn, assignment, rr, ship := ff() + idFn, assignment, rr, rrs, ship := ff() if isJoin { fn = append(fn, func() { tables := strings.Split(ship.Table, " ") @@ -45,6 +47,10 @@ func Relation(db dbQuery, ctx context.Context, r any, q *QueryCondition) ([]func continue } fns = append(fns, func() error { + ids := idFn(r) + if len(ids) < 1 { + return nil + } var err error { if qq == nil { @@ -61,26 +67,93 @@ func Relation(db dbQuery, ctx context.Context, r any, q *QueryCondition) ([]func ww = append(ww, SqlBuilder{{ ship.ForeignKey, "in", "", }}...) - qq.In = [][]any{idFn(r)} + qq.In = [][]any{ids} 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) - } + err = parseRelation(isMultiple || ship.RelationType == "hasMany", db, ctx, helper.Or(isMultiple, rrs, rr), qq) if err != nil && err != sql.ErrNoRows { return err } - err = assignment(r, rr) + assignment(r, helper.Or(isMultiple, rrs, rr)) + return err }) } return fn, fns } + +func GetWithID[T, V any](fn func(*T) V) func(any) []any { + return func(a any) []any { + one, ok := a.(*T) + if ok { + return []any{fn(one)} + } + return slice.ToAnySlice(slice.Unique(slice.Map(*a.(*[]T), func(t T) any { + return fn(&t) + }))) + } +} + +func SetHasOne[T, V any, K comparable](fn func(*T, *V), idFn func(*T) K, iddFn func(*V) K) func(any, any) { + return func(m, v any) { + one, ok := m.(*T) + if ok { + fn(one, v.(*V)) + return + } + r := m.(*[]T) + vv := v.(*[]V) + mm := slice.SimpleToMap(*vv, func(v V) K { + return iddFn(&v) + }) + for i := 0; i < len(*r); i++ { + val := &(*r)[i] + id := idFn(val) + v, ok := mm[id] + if ok { + fn(val, &v) + } + } + } +} + +func SetHasMany[T, V any, K comparable](fn func(*T, *[]V), idFn func(*T) K, iddFn func(*V) K) func(any, any) { + return func(m, v any) { + one, ok := m.(*T) + if ok { + fn(one, v.(*[]V)) + return + } + r := m.(*[]T) + vv := v.(*[]V) + mm := slice.GroupBy(*vv, func(t V) (K, V) { + return iddFn(&t), t + }) + for i := 0; i < len(*r); i++ { + val := &(*r)[i] + id := idFn(val) + v, ok := mm[id] + if ok { + fn(val, &v) + } + } + } +} + +func RelationHasOne[M, P any, I constraints.Integer | uint64](fId func(*M) I, pId func(*P) I, setVal func(*M, *P), r Relationship) RelationFn { + return func() (func(any) []any, func(any, any), any, any, Relationship) { + var s P + var ss []P + return GetWithID(fId), SetHasOne(setVal, fId, pId), &s, &ss, r + } +} +func RelationHasMany[M, P any, I constraints.Integer | uint64](mId func(*M) I, pId func(*P) I, setVal func(*M, *[]P), r Relationship) RelationFn { + return func() (func(any) []any, func(any, any), any, any, Relationship) { + var ss []P + return GetWithID(mId), SetHasMany(setVal, mId, pId), &ss, &ss, r + } +} diff --git a/model/relation_test.go b/model/relation_test.go new file mode 100644 index 0000000..bead917 --- /dev/null +++ b/model/relation_test.go @@ -0,0 +1,128 @@ +package model + +import ( + "github.com/fthvgb1/wp-go/app/pkg/models" + "testing" +) + +func postAuthorId(p *post) uint64 { + return p.PostAuthor +} + +func postId(p *post) uint64 { + return p.Id +} + +func userId(u *user) uint64 { + return u.Id +} + +func metaId(m *models.PostMeta) uint64 { + return m.MetaId +} +func metasPostId(m *models.PostMeta) uint64 { + return m.PostId +} + +func PostAuthor() (func(any) []any, func(any, any), any, any, Relationship) { + var u user + var uu []user + return GetWithID[post](func(t *post) uint64 { + return t.PostAuthor + }), + SetHasOne(func(p *post, u *user) { + p.User = u + }, func(t *post) uint64 { + return t.PostAuthor + }, func(u *user) uint64 { + return u.Id + }), + &u, &uu, + Relationship{ + RelationType: "hasOne", + Table: "wp_users user", + ForeignKey: "ID", + Local: "post_author", + } +} +func PostMetas() (func(any) []any, func(any, any), any, any, Relationship) { + var u []models.PostMeta + return GetWithID(func(t *post) any { + return t.Id + }), SetHasMany(func(t *post, v *[]models.PostMeta) { + t.PostMeta = v + }, func(t *post) uint64 { + return t.Id + }, func(m *models.PostMeta) uint64 { + return m.PostId + }), &u, &u, Relationship{ + RelationType: "hasMany", + Table: "wp_postmeta meta", + ForeignKey: "post_id", + Local: "ID", + } +} + +func Meta2() RelationFn { + return RelationHasMany(postId, metasPostId, func(m *post, i *[]models.PostMeta) { + m.PostMeta = i + }, Relationship{ + RelationType: "hasMany", + Table: "wp_postmeta meta", + ForeignKey: "post_id", + Local: "ID", + }) +} + +func PostAuthor2() RelationFn { + return RelationHasOne[post, user](postAuthorId, userId, func(p *post, u *user) { + p.User = u + }, Relationship{ + RelationType: "hasOne", + Table: "wp_users user", + ForeignKey: "ID", + Local: "post_author", + }) +} + +func TestGets2(t *testing.T) { + t.Run("one", func(t *testing.T) { + { + q := Conditions( + Where(SqlBuilder{{"posts.id = 190"}}), + WithCtx(&ctx), + WithFn(true, true, Conditions( + Fields("ID,user_login,user_pass"), + ), PostAuthor2()), + Fields("posts.*"), + From("wp_posts posts"), + WithFn(true, true, nil, Meta2()), + ) + got, err := Gets[post](ctx, q) + _ = got + if err != nil { + t.Errorf("err:%v", err) + } + } + }) + t.Run("many", func(t *testing.T) { + { + q := Conditions( + Where(SqlBuilder{{"posts.id", "in", ""}}), + In([]any{190, 3022}), + WithCtx(&ctx), + WithFn(true, false, Conditions( + Fields("ID,user_login,user_pass"), + ), PostAuthor2()), + Fields("posts.*"), + From("wp_posts posts"), + WithFn(true, false, nil, Meta2()), + ) + got, err := Finds[post](ctx, q) + _ = got + if err != nil { + t.Errorf("err:%v", err) + } + } + }) +}