优化 hasone/many不用反射

This commit is contained in:
xing 2023-05-19 23:14:58 +08:00
parent 6caf07b575
commit 4242d850ff
4 changed files with 183 additions and 133 deletions

View File

@ -1,5 +1,7 @@
package model package model
import "context"
type QueryCondition struct { type QueryCondition struct {
Where ParseWhere Where ParseWhere
From string From string
@ -11,8 +13,7 @@ type QueryCondition struct {
Limit int Limit int
Offset int Offset int
In [][]any In [][]any
Relation map[string]*QueryCondition RelationFn []func() (bool, bool, *QueryCondition, func() (func(any) []any, func(any, any) error, any, Relationship))
WithJoin bool
} }
func Conditions(fns ...Condition) *QueryCondition { 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) { return func(c *QueryCondition) {
if c.Relation == nil { *ctx = context.WithValue(*ctx, "ancestorsQueryCondition", c)
c.Relation = map[string]*QueryCondition{}
}
c.Relation[tableTag] = q
} }
} }
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) { 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
})
} }
} }

View File

@ -3,6 +3,7 @@ package model
import ( import (
"context" "context"
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/safety"
@ -37,8 +38,51 @@ type post struct {
PostType string `gorm:"column:post_type" db:"post_type" json:"post_type" form:"post_type"` 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"` 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"` 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"` User *user
PostMeta *[]models.PostMeta `table:"wp_postmeta meta" foreignKey:"post_id" local:"ID" relation:"hasMany"` 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 { type TermRelationships struct {
@ -339,17 +383,15 @@ func TestGets2(t *testing.T) {
t.Run("hasOne", func(t *testing.T) { t.Run("hasOne", func(t *testing.T) {
{ {
q := Conditions( q := Conditions(
Where(SqlBuilder{{"id = 190"}}), Where(SqlBuilder{{"posts.id = 190"}}),
With("user", Conditions( WithCtx(&ctx),
WithFn(true, true, Conditions(
Fields("ID,user_login,user_pass"), Fields("ID,user_login,user_pass"),
)), ), PostAuthor),
Fields("posts.*"), Fields("posts.*"),
From("wp_posts posts"), From("wp_posts posts"),
With("meta", Conditions( WithFn(false, true, nil, PostMetas),
WithJoin(true),
)),
) )
ctx = context.WithValue(ctx, "ancestorsQueryCondition", q)
got, err := Gets[post](ctx, q) got, err := Gets[post](ctx, q)
_ = got _ = got
if err != nil { 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) { func TestFirstOne(t *testing.T) {

View File

@ -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) { func finds[T Model](db dbQuery, ctx context.Context, q *QueryCondition) (r []T, err error) {
setTable[T](q) setTable[T](q)
sq, args, err := BuildQuerySql(q) if len(q.RelationFn) < 1 {
sq, args, er := BuildQuerySql(q)
if err != nil { if err != nil {
err = er
return return
} }
err = db.Select(ctx, &r, sq, args...) err = db.Select(ctx, &r, sq, args...)
return
}
err = parseRelation(true, db, ctx, &r, q)
return 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) { func gets[T Model](db dbQuery, ctx context.Context, q *QueryCondition) (r T, err error) {
setTable[T](q) setTable[T](q)
if len(q.Relation) < 1 { if len(q.RelationFn) < 1 {
s, args, er := BuildQuerySql(q) s, args, er := BuildQuerySql(q)
if er != nil { if er != nil {
err = er err = er

View File

@ -2,9 +2,9 @@ package model
import ( import (
"context" "context"
"database/sql"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
"reflect"
"strings" "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) { func Relation(db dbQuery, ctx context.Context, r any, q *QueryCondition) ([]func(), []func() error) {
var fn []func() var fn []func()
var fns []func() error var fns []func() error
t := reflect.TypeOf(r).Elem() for _, f := range q.RelationFn {
v := reflect.ValueOf(r).Elem() getVal, isJoin, qq, ff := f()
for tableTag, relation := range q.Relation { idFn, assignment, rr, ship := ff()
if tableTag == "" { if isJoin {
fn = append(fn, 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)})
})
}
if !getVal {
continue continue
} }
tableTag := tableTag fns = append(fns, func() error {
relation := relation var err error
for i := 0; i < t.NumField(); i++ { {
i := i if qq == nil {
tag := t.Field(i).Tag qq = &QueryCondition{
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: "*", Fields: "*",
} }
} }
relation.From = table var w any = qq.Where
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 {
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 { if w == nil {
w = SqlBuilder{} w = SqlBuilder{}
} }
ww, ok := w.(SqlBuilder) ww, ok := w.(SqlBuilder)
if ok { if ok {
id = fmt.Sprintf("%v", v.Field(j).Interface())
ww = append(ww, SqlBuilder{{ ww = append(ww, SqlBuilder{{
foreignKey, "=", id, "int", ship.ForeignKey, "in", "",
}}...) }}...)
relation.Where = ww qq.In = [][]any{idFn(r)}
qq.Where = ww
}
if qq.From == "" {
qq.From = ship.Table
} }
} }
var err error // todo finds的情况
vv := reflect.New(v.Field(i).Type().Elem()).Interface() switch ship.RelationType {
switch tag.Get("relation") {
case "hasOne": case "hasOne":
err = parseRelation(false, db, ctx, vv, relation) err = parseRelation(false, db, ctx, rr, qq)
case "hasMany": case "hasMany":
err = parseRelation(true, db, ctx, vv, relation) err = parseRelation(true, db, ctx, rr, qq)
} }
if err != nil { if err != nil && err != sql.ErrNoRows {
return err return err
} }
v.Field(i).Set(reflect.ValueOf(vv)) err = assignment(r, rr)
return nil return err
}) })
} }
}
return fn, fns return fn, fns
} }