diff --git a/model/query_test.go b/model/query_test.go index 213e24c..a691c34 100644 --- a/model/query_test.go +++ b/model/query_test.go @@ -40,6 +40,9 @@ type post struct { User *user Ships *[]TermRelationships PostMeta *[]models.PostMeta + TermTaxonomy *[]TermTaxonomy + Terms *[]models.Terms + CommentMetas *[]CommentMeta } type TermRelationships struct { diff --git a/model/relation.go b/model/relation.go index 029dbe1..0414e78 100644 --- a/model/relation.go +++ b/model/relation.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper/slice" + str "github.com/fthvgb1/wp-go/helper/strings" "golang.org/x/exp/constraints" "strings" ) @@ -36,25 +37,79 @@ type Relationship struct { ForeignKey string Local string On string + Middle *Relationship +} + +func parseBeforeJoin(qq *QueryCondition, ship Relationship) { + var fromTable, foreignKey, local string + if ship.Middle != nil { + parseBeforeJoin(qq, *ship.Middle) + local = ship.Local + fromTable = ship.Middle.Table + } else { + fromTable = qq.From + } + foreignKey = ship.ForeignKey + local = ship.Local + tables := strings.Split(ship.Table, " ") + from := strings.Split(fromTable, " ") + on := "" + if ship.On != "" { + on = fmt.Sprintf("and %s", on) + } + qq.Join = append(qq.Join, []string{ + "left join", ship.Table, + fmt.Sprintf("%s.%s=%s.%s %s", + tables[len(tables)-1], foreignKey, from[len(from)-1], local, on, + )}) + +} + +func parseAfterJoin(fromTable string, ids [][]any, qq *QueryCondition, ship Relationship) bool { + tables := strings.Split(ship.Middle.Table, " ") + from := strings.Split(fromTable, " ") + on := "" + if ship.On != "" { + on = fmt.Sprintf("and %s", on) + } + foreignKey := ship.ForeignKey + local := ship.Local + qq.Join = append(qq.Join, []string{ + "left join", ship.Middle.Table, + fmt.Sprintf("%s.%s=%s.%s %s", + tables[len(tables)-1], foreignKey, from[len(from)-1], local, on, + ), + }) + if ship.Middle != nil && ship.Middle.Middle != nil { + return parseAfterJoin(tables[len(tables)-1], ids, qq, *ship.Middle) + } else { + from := strings.Split(qq.From, " ") + ww, ok := qq.Where.(SqlBuilder) + if ok { + ww = append(ww, []string{fmt.Sprintf("%s.%s", + tables[len(tables)-1], ship.Middle.ForeignKey), "in", ""}, + ) + qq.Where = ww + } + if qq.Fields == "" || qq.Fields == "*" { + qq.Fields = str.Join(from[len(from)-1], ".", "*", ",", tables[len(tables)-1], ".", ship.Middle.ForeignKey) + } + qq.In = ids + return ship.Middle.RelationType == HasMany + } } func Relation(isPlural bool, db dbQuery, ctx context.Context, r any, q *QueryCondition) ([]func(), []func() error) { var beforeFn []func() var afterFn []func() error + qx := helper.GetContextVal(ctx, "ancestorsQueryCondition", q) + for _, f := range q.RelationFn { getVal, isJoin, qq, relationship := f() idFn, assignmentFn, rr, rrs, ship := relationship() if isJoin { beforeFn = append(beforeFn, func() { - tables := strings.Split(ship.Table, " ") - from := strings.Split(q.From, " ") - on := "" - if ship.On != "" { - on = fmt.Sprintf("and %s", on) - } - 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)}) + parseBeforeJoin(qx, ship) }) } if !getVal { @@ -71,21 +126,26 @@ func Relation(isPlural bool, db dbQuery, ctx context.Context, r any, q *QueryCon 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{ids} - qq.Where = ww - } if qq.From == "" { qq.From = ship.Table } + var w any = qq.Where + if w == nil { + qq.Where = SqlBuilder{} + } + ww, ok := qq.Where.(SqlBuilder) + in := [][]any{ids} + if ok { + if ship.Middle != nil { + isPlural = parseAfterJoin(qq.From, in, qq, ship) + } else { + ww = append(ww, SqlBuilder{{ + ship.ForeignKey, "in", "", + }}...) + qq.In = in + qq.Where = ww + } + } err = ParseRelation(isPlural || ship.RelationType == HasMany, db, ctx, helper.Or(isPlural, rrs, rr), qq) if err != nil { if err == sql.ErrNoRows { diff --git a/model/relation_test.go b/model/relation_test.go index f2df4f6..dd51587 100644 --- a/model/relation_test.go +++ b/model/relation_test.go @@ -2,6 +2,7 @@ package model import ( "github.com/fthvgb1/wp-go/app/pkg/models" + "github.com/fthvgb1/wp-go/helper/slice" "testing" ) @@ -15,6 +16,13 @@ type TermTaxonomy struct { Term *models.Terms } +type CommentMeta struct { + MetaId uint64 `db:"meta_id"` + CommentId uint64 `db:"comment_id"` + MetaKey string `db:"meta_key"` + MetaValue string `db:"meta_value"` +} + var termMyHasOneTerm = RelationHasOne(func(m *TermTaxonomy) uint64 { return m.TermTaxonomyId }, func(p *models.Terms) uint64 { @@ -117,6 +125,71 @@ func PostMetas() (func(any) []any, func(any, any), any, any, Relationship) { } } +var postHaveManyTerms = RelationHasMany(func(m *post) uint64 { + return m.Id +}, func(p *struct { + ObjectId uint64 `db:"object_id"` + models.Terms +}) uint64 { + return p.ObjectId +}, func(m *post, i *[]struct { + ObjectId uint64 `db:"object_id"` + models.Terms +}) { + v := slice.Map(*i, func(t struct { + ObjectId uint64 `db:"object_id"` + models.Terms + }) models.Terms { + return t.Terms + }) + m.Terms = &v +}, Relationship{ + RelationType: HasOne, + Table: "wp_terms", + ForeignKey: "term_id", + Local: "term_id", + Middle: &Relationship{ + RelationType: HasOne, + Table: "wp_term_taxonomy taxonomy", + ForeignKey: "term_taxonomy_id", + Local: "term_taxonomy_id", + Middle: &Relationship{ + RelationType: HasMany, + Table: "wp_term_relationships", + ForeignKey: "object_id", + Local: "ID", + }, + }, +}) + +var postHaveManyCommentMetas = func() RelationFn { + type metas struct { + CommentPostID uint64 `db:"comment_post_ID"` + CommentMeta + } + return RelationHasMany(func(m *post) uint64 { + return m.Id + }, func(p *metas) uint64 { + return p.CommentPostID + }, func(m *post, i *[]metas) { + v := slice.Map(*i, func(t metas) CommentMeta { + return t.CommentMeta + }) + m.CommentMetas = &v + }, Relationship{ + RelationType: HasOne, + Table: "wp_commentmeta", + ForeignKey: "comment_id", + Local: "comment_ID", + Middle: &Relationship{ + RelationType: HasMany, + Table: "wp_comments comments", + ForeignKey: "comment_post_ID", + Local: "ID", + }, + }) +}() + func Meta2() RelationFn { return RelationHasMany(postId, metasPostId, func(m *post, i *[]models.PostMeta) { m.PostMeta = i @@ -156,7 +229,7 @@ func TestGets2(t *testing.T) { WithFn(true, false, nil, termMyHasOneTerm), ), shipHasManyTermMy), ), postHasManyShip), - //WithFn(true, false, nil, term), + WithFn(true, false, nil, postHaveManyTerms), ) got, err := Gets[post](ctx, q) _ = got @@ -169,7 +242,7 @@ func TestGets2(t *testing.T) { { q := Conditions( Where(SqlBuilder{{"posts.id", "in", ""}}), - In([]any{190, 3022, 291}), + In([]any{190, 3022, 291, 2858}), WithCtx(&ctx), WithFn(true, false, Conditions( Fields("ID,user_login,user_pass"), @@ -177,12 +250,13 @@ func TestGets2(t *testing.T) { Fields("posts.*"), From("wp_posts posts"), WithFn(true, false, nil, Meta2()), - WithFn(true, false, Conditions( + /*WithFn(true, false, Conditions( WithFn(true, false, Conditions( WithFn(true, false, nil, termMyHasOneTerm), ), shipHasManyTermMy), - ), postHasManyShip), - //WithFn(true, false, nil, term), + ), postHasManyShip),*/ + WithFn(true, false, nil, postHaveManyTerms), + WithFn(true, false, nil, postHaveManyCommentMetas), ) got, err := Finds[post](ctx, q) _ = got