diff --git a/model/parse.go b/model/parse.go index 67ded99..fedf647 100644 --- a/model/parse.go +++ b/model/parse.go @@ -88,6 +88,7 @@ func (w SqlBuilder) ParseWhere(in *[][]any) (string, []any, error) { args = append(args, ss[1]) case 3, 4: w.parseWhereField(ss, &s) + s.WriteString(" ") s.WriteString(ss[1]) if w.parseIn(ss, &s, &c, &args, in) { s.WriteString(" and ") diff --git a/model/query_test.go b/model/query_test.go index ff633a6..f3fcced 100644 --- a/model/query_test.go +++ b/model/query_test.go @@ -39,6 +39,7 @@ type post struct { CommentCount int64 `gorm:"column:comment_count" db:"comment_count" json:"comment_count" form:"comment_count"` User *user PostMeta *[]models.PostMeta + TermTaxonomy *[]models.TermTaxonomy } type TermRelationships struct { diff --git a/model/relation.go b/model/relation.go index cca14dd..c00afff 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,88 @@ 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) + fromTable = ship.Middle.Table + foreignKey = ship.ForeignKey + local = ship.Local + } else { + fromTable = qq.From + if ship.RelationType == HasMany { + foreignKey = ship.Local + local = ship.ForeignKey + } else { + 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(ids [][]any, qq *QueryCondition, ship Relationship) bool { + tables := strings.Split(ship.Middle.Table, " ") + from := strings.Split(qq.From, " ") + on := "" + if ship.On != "" { + on = fmt.Sprintf("and %s", on) + } + foreignKey := ship.ForeignKey + local := ship.Local + if ship.RelationType == HasMany { + foreignKey = ship.Local + local = ship.ForeignKey + } + 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.Middle != nil { + return parseAfterJoin(ids, qq, *ship.Middle.Middle) + } else { + ww, ok := qq.Where.(SqlBuilder) + if ok { + ww = append(ww, []string{fmt.Sprintf("%s.%s", + tables[len(tables)-1], ship.Middle.Local), "in", ""}, + ) + qq.Where = ww + } + if qq.Fields == "" || qq.Fields == "*" { + qq.Fields = str.Join(from[len(from)-1], ".", "*") + } + 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 +135,27 @@ 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(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 { @@ -166,7 +236,9 @@ func SetHasMany[T, V any, K comparable](assignmentFn func(*T, *[]V), pIdFn func( // RelationHasOne // eg: post has a user. fId is post's userId, pId is user's id -func RelationHasOne[M, P any, I constraints.Integer | uint64](fId func(*M) I, pId func(*P) I, setVal func(*M, *P), r Relationship) RelationFn { +func RelationHasOne[M, P any, I constraints.Integer | constraints.Unsigned]( + fId func(*M) I, pId func(*P) I, setVal func(*M, *P), r Relationship) RelationFn { + idFn := GetWithID(fId) setFn := SetHasOne(setVal, fId, pId) return func() (func(any) []any, func(any, any), any, any, Relationship) { @@ -178,7 +250,9 @@ func RelationHasOne[M, P any, I constraints.Integer | uint64](fId func(*M) I, pI // RelationHasMany // eg: post has many comments,mId is comment's postId, pId is post's id -func RelationHasMany[M, P any, I constraints.Integer | uint64](mId func(*M) I, pId func(*P) I, setVal func(*M, *[]P), r Relationship) RelationFn { +func RelationHasMany[M, P any, I constraints.Integer | constraints.Unsigned]( + mId func(*M) I, pId func(*P) I, setVal func(*M, *[]P), r Relationship) RelationFn { + idFn := GetWithID(mId) setFn := SetHasMany(setVal, mId, pId) return func() (func(any) []any, func(any, any), any, any, Relationship) { diff --git a/model/relation_test.go b/model/relation_test.go index 618a683..8d11bc7 100644 --- a/model/relation_test.go +++ b/model/relation_test.go @@ -60,6 +60,25 @@ func PostMetas() (func(any) []any, func(any, any), any, any, Relationship) { } } +var term = RelationHasMany(func(m *post) uint64 { + return m.Id +}, func(p *models.TermTaxonomy) uint64 { + return p.TermTaxonomyId +}, func(m *post, i *[]models.TermTaxonomy) { + m.TermTaxonomy = i +}, 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: "ID", + Local: "object_id", + }, +}) + func Meta2() RelationFn { return RelationHasMany(postId, metasPostId, func(m *post, i *[]models.PostMeta) { m.PostMeta = i @@ -93,7 +112,8 @@ func TestGets2(t *testing.T) { ), PostAuthor2()), Fields("posts.*"), From("wp_posts posts"), - WithFn(true, true, nil, Meta2()), + WithFn(true, false, nil, Meta2()), + WithFn(true, false, nil, term), ) got, err := Gets[post](ctx, q) _ = got @@ -106,7 +126,7 @@ func TestGets2(t *testing.T) { { q := Conditions( Where(SqlBuilder{{"posts.id", "in", ""}}), - In([]any{190, 3022}), + In([]any{190, 3022, 291}), WithCtx(&ctx), WithFn(true, false, Conditions( Fields("ID,user_login,user_pass"), @@ -114,6 +134,7 @@ func TestGets2(t *testing.T) { Fields("posts.*"), From("wp_posts posts"), WithFn(true, false, nil, Meta2()), + WithFn(true, false, nil, term), ) got, err := Finds[post](ctx, q) _ = got