wp-go/model/relation.go

302 lines
7.5 KiB
Go
Raw Permalink Normal View History

2023-05-17 14:22:31 +00:00
package model
import (
"context"
2023-05-19 15:14:58 +00:00
"database/sql"
2023-12-03 14:42:44 +00:00
"errors"
2023-05-17 14:22:31 +00:00
"fmt"
2023-05-18 14:27:28 +00:00
"github.com/fthvgb1/wp-go/helper"
2023-05-20 17:38:19 +00:00
"github.com/fthvgb1/wp-go/helper/slice"
2023-05-24 12:41:24 +00:00
str "github.com/fthvgb1/wp-go/helper/strings"
2023-05-20 17:38:19 +00:00
"golang.org/x/exp/constraints"
2023-05-17 14:22:31 +00:00
"strings"
)
func setTable[T Model](q *QueryCondition) {
if q.From == "" {
q.From = Table[T]()
}
}
2023-05-21 13:30:00 +00:00
const (
HasOne = "hasOne"
HasMany = "hasMany"
)
// Relationship join table
//
2023-12-03 14:42:44 +00:00
// # RelationType HasOne| HasMany
2023-05-21 13:30:00 +00:00
//
// eg: hasOne, post has a user. ForeignKey is user's id , Local is post's userId field
//
// eg: hasMany, post has many comments,ForeignKey is comment's postId field, Local is post's id field
//
// On is additional join on conditions
2023-05-19 15:14:58 +00:00
type Relationship struct {
RelationType string
Table string
ForeignKey string
Local string
On string
2023-05-24 12:41:24 +00:00
Middle *Relationship
}
func parseBeforeJoin(qq *QueryCondition, ship Relationship) {
var fromTable, foreignKey, local string
if ship.Middle != nil {
parseBeforeJoin(qq, *ship.Middle)
local = ship.Local
2023-05-27 06:42:14 +00:00
fromTable = ship.Middle.Table
2023-05-24 12:41:24 +00:00
} else {
fromTable = qq.From
}
2023-05-24 13:33:30 +00:00
foreignKey = ship.ForeignKey
local = ship.Local
2023-05-24 12:41:24 +00:00
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,
)})
}
2023-05-27 06:42:14 +00:00
func parseAfterJoin(fromTable string, ids [][]any, qq *QueryCondition, ship Relationship) bool {
2023-05-24 12:41:24 +00:00
tables := strings.Split(ship.Middle.Table, " ")
2023-05-27 06:42:14 +00:00
from := strings.Split(fromTable, " ")
2023-05-24 12:41:24 +00:00
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,
),
})
2023-05-27 06:42:14 +00:00
if ship.Middle != nil && ship.Middle.Middle != nil {
2023-05-30 15:49:05 +00:00
r := parseAfterJoin(tables[len(tables)-1], ids, qq, *ship.Middle)
return ship.RelationType == HasMany || r
2023-05-24 12:41:24 +00:00
} else {
2023-05-27 06:42:14 +00:00
from := strings.Split(qq.From, " ")
2023-05-24 12:41:24 +00:00
ww, ok := qq.Where.(SqlBuilder)
if ok {
ww = append(ww, []string{fmt.Sprintf("%s.%s",
2023-05-27 06:42:14 +00:00
tables[len(tables)-1], ship.Middle.ForeignKey), "in", ""},
2023-05-24 12:41:24 +00:00
)
qq.Where = ww
2023-05-30 12:02:52 +00:00
} else {
aw, ok := helper.IsImplements[AndWhere](qq.Where)
if ok {
vv := aw.AndWhere(fmt.Sprintf("%s.%s",
tables[len(tables)-1], ship.Middle.ForeignKey), "in", strings.Join(slice.DecompressBy(ids, func(t any) (string, bool) {
return fmt.Sprintf("%v", t), true
}), ","), "int")
wa, ok := helper.IsImplements[ParseWhere](vv)
if ok {
qq.Where = wa
}
}
2023-05-24 12:41:24 +00:00
}
if qq.Fields == "" || qq.Fields == "*" {
2023-05-27 06:42:14 +00:00
qq.Fields = str.Join(from[len(from)-1], ".", "*", ",", tables[len(tables)-1], ".", ship.Middle.ForeignKey)
2023-05-24 12:41:24 +00:00
}
qq.In = ids
2023-05-30 15:49:05 +00:00
return ship.RelationType == HasMany || ship.Middle.RelationType == HasMany
2023-05-24 12:41:24 +00:00
}
2023-05-19 15:14:58 +00:00
}
2023-05-21 13:30:00 +00:00
func Relation(isPlural bool, db dbQuery, ctx context.Context, r any, q *QueryCondition) ([]func(), []func() error) {
2023-05-21 11:53:37 +00:00
var beforeFn []func()
var afterFn []func() error
2023-05-24 12:41:24 +00:00
qx := helper.GetContextVal(ctx, "ancestorsQueryCondition", q)
2023-05-19 15:14:58 +00:00
for _, f := range q.RelationFn {
2023-05-21 11:53:37 +00:00
getVal, isJoin, qq, relationship := f()
2023-12-03 14:42:44 +00:00
idFn, assignmentFn, varFn, ship := relationship()
2023-05-19 15:14:58 +00:00
if isJoin {
2023-05-21 11:53:37 +00:00
beforeFn = append(beforeFn, func() {
2023-05-24 12:41:24 +00:00
parseBeforeJoin(qx, ship)
2023-05-19 15:14:58 +00:00
})
}
if !getVal {
2023-05-17 14:22:31 +00:00
continue
}
2023-05-21 11:53:37 +00:00
afterFn = append(afterFn, func() error {
2023-05-20 17:38:19 +00:00
ids := idFn(r)
if len(ids) < 1 {
return nil
}
2023-05-19 15:14:58 +00:00
var err error
2023-05-21 11:53:37 +00:00
if qq == nil {
qq = &QueryCondition{
Fields: "*",
2023-05-19 15:14:58 +00:00
}
}
2023-05-24 12:41:24 +00:00
if qq.From == "" {
qq.From = ship.Table
}
2023-05-21 11:53:37 +00:00
var w any = qq.Where
if w == nil {
2023-05-24 12:41:24 +00:00
qq.Where = SqlBuilder{}
2023-05-19 15:14:58 +00:00
}
2023-05-24 12:41:24 +00:00
in := [][]any{ids}
2023-05-30 12:02:52 +00:00
if ship.Middle != nil {
isPlural = parseAfterJoin(qq.From, in, qq, ship)
} else {
ww, ok := qq.Where.(SqlBuilder)
if ok {
2023-05-24 12:41:24 +00:00
ww = append(ww, SqlBuilder{{
ship.ForeignKey, "in", "",
}}...)
qq.Where = ww
2023-05-30 12:02:52 +00:00
} else {
aw, ok := helper.IsImplements[AndWhere](qq.Where)
if ok {
ww := aw.AndWhere(ship.ForeignKey, "in", strings.Join(slice.Map(ids, func(t any) string {
return fmt.Sprintf("%v", t)
}), ","), "int")
qq.Where = ww
}
2023-05-24 12:41:24 +00:00
}
2023-05-30 12:02:52 +00:00
qq.In = in
2023-05-21 11:53:37 +00:00
}
2023-12-03 14:42:44 +00:00
err = ParseRelation(isPlural || ship.RelationType == HasMany, db, ctx, varFn(isPlural), qq)
2023-05-21 11:53:37 +00:00
if err != nil {
2023-12-03 14:42:44 +00:00
if errors.Is(err, sql.ErrNoRows) {
2023-05-21 11:53:37 +00:00
err = nil
} else {
return err
}
}
2023-12-03 14:42:44 +00:00
assignmentFn(r, varFn(isPlural))
2023-05-19 15:14:58 +00:00
return err
})
2023-05-17 14:22:31 +00:00
}
2023-05-21 11:53:37 +00:00
return beforeFn, afterFn
2023-05-17 14:22:31 +00:00
}
2023-05-20 17:38:19 +00:00
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)
})))
}
}
2023-05-21 11:53:37 +00:00
// SetHasOne mIdFn is main , pIdFn is part
//
// eg: post has a user. mIdFn is post's userId, iddFn is user's id
func SetHasOne[T, V any, K comparable](assignmentFn func(*T, *V), mIdFn func(*T) K, pIdFn func(*V) K) func(any, any) {
return func(m, p any) {
2023-05-20 17:38:19 +00:00
one, ok := m.(*T)
if ok {
2023-05-21 11:53:37 +00:00
assignmentFn(one, p.(*V))
2023-05-20 17:38:19 +00:00
return
}
2023-05-21 11:53:37 +00:00
mSlice := m.(*[]T)
pSLice := p.(*[]V)
mm := slice.SimpleToMap(*pSLice, func(v V) K {
return pIdFn(&v)
2023-05-20 17:38:19 +00:00
})
2023-05-21 11:53:37 +00:00
for i := 0; i < len(*mSlice); i++ {
m := &(*mSlice)[i]
id := mIdFn(m)
p, ok := mm[id]
2023-05-20 17:38:19 +00:00
if ok {
2023-05-21 11:53:37 +00:00
assignmentFn(m, &p)
2023-05-20 17:38:19 +00:00
}
}
}
}
2023-05-21 11:53:37 +00:00
// SetHasMany
// eg: post has many comments,pIdFn is comment's postId, mIdFn is post's id
func SetHasMany[T, V any, K comparable](assignmentFn func(*T, *[]V), pIdFn func(*T) K, mIdFn func(*V) K) func(any, any) {
return func(m, p any) {
2023-05-20 17:38:19 +00:00
one, ok := m.(*T)
if ok {
2023-05-21 11:53:37 +00:00
assignmentFn(one, p.(*[]V))
2023-05-20 17:38:19 +00:00
return
}
r := m.(*[]T)
2023-05-21 11:53:37 +00:00
vv := p.(*[]V)
2023-05-20 17:38:19 +00:00
mm := slice.GroupBy(*vv, func(t V) (K, V) {
2023-05-21 11:53:37 +00:00
return mIdFn(&t), t
2023-05-20 17:38:19 +00:00
})
for i := 0; i < len(*r); i++ {
2023-05-21 11:53:37 +00:00
m := &(*r)[i]
id := pIdFn(m)
p, ok := mm[id]
2023-05-20 17:38:19 +00:00
if ok {
2023-05-21 11:53:37 +00:00
assignmentFn(m, &p)
2023-05-20 17:38:19 +00:00
}
}
}
}
2023-05-21 13:30:00 +00:00
// RelationHasOne
// eg: post has a user. fId is post's userId, pId is user's id
2023-05-24 13:58:30 +00:00
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 {
2023-05-21 13:30:00 +00:00
idFn := GetWithID(fId)
setFn := SetHasOne(setVal, fId, pId)
2023-12-03 14:42:44 +00:00
return func() (func(any) []any, func(any, any), func(bool) any, Relationship) {
return idFn, setFn, func(isPlural bool) any {
if isPlural {
var ss []P
return &ss
}
var s P
return &s
}, r
2023-05-20 17:38:19 +00:00
}
}
2023-05-21 13:30:00 +00:00
// RelationHasMany
2023-05-24 13:58:30 +00:00
// eg: post has many comments,mId is post's id, pId is comment's postId
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 {
2023-05-21 13:30:00 +00:00
idFn := GetWithID(mId)
setFn := SetHasMany(setVal, mId, pId)
2023-12-03 14:42:44 +00:00
return func() (func(any) []any, func(any, any), func(bool) any, Relationship) {
return idFn, setFn, func(_ bool) any {
var ss []P
return &ss
}, r
2023-05-21 13:30:00 +00:00
}
}
func AddRelationFn(getVal, join bool, q *QueryCondition, r RelationFn) func() (bool, bool, *QueryCondition, RelationFn) {
return func() (bool, bool, *QueryCondition, RelationFn) {
return getVal, join, q, r
2023-05-20 17:38:19 +00:00
}
}
2023-06-09 14:28:23 +00:00
func withOther(db dbQuery, ctx context.Context, r any, q *QueryCondition) error {
_, after := Relation(true, db, ctx, r, q)
for _, fn := range after {
err := fn()
if err != nil {
return err
}
}
return nil
}
func DBWithOther(db dbQuery, ctx context.Context, r any, q *QueryCondition) error {
return withOther(db, ctx, r, q)
}
func WithOther(ctx context.Context, r any, q *QueryCondition) error {
return withOther(globalBb, ctx, r, q)
}