type complexity measurement improvement + misc corrections

This commit is contained in:
Laurent Le Houerou 2024-08-14 11:30:54 +04:00
parent cf28f6c8df
commit 17b24f2a16
7 changed files with 146 additions and 49 deletions

View File

@ -110,7 +110,7 @@ func NewFootball(c *graphql.Client) *Football {
c, c,
"players", "players",
[]string{"football"}, []string{"football"},
), ).WithMaxPageSize(100),
Season: graphql.NewQuery[Season, SeasonParams]( Season: graphql.NewQuery[Season, SeasonParams](
c, c,
"season", "season",

View File

@ -4,8 +4,14 @@ import "git.lehouerou.net/laurent/sorare/graphql"
type Rivals struct { type Rivals struct {
c *graphql.Client c *graphql.Client
PastGames *graphql.PaginatedQuery[RivalsGame, graphql.EmptyParams]
} }
func NewRivals(c *graphql.Client) *Rivals { func NewRivals(c *graphql.Client) *Rivals {
return &Rivals{c: c} return &Rivals{
c: c,
PastGames: graphql.NewPaginatedQuery[RivalsGame, graphql.EmptyParams](c, "pastGamesPaginated", []string{"football", "rivals"}).WithOverrideComplexity(107),
}
} }

32
football/rivals_game.go Normal file
View File

@ -0,0 +1,32 @@
package football
import (
"git.lehouerou.net/laurent/sorare/graphql"
"git.lehouerou.net/laurent/sorare/types"
)
type RivalsDraftableObject struct {
Id graphql.Id `graphql:"id"`
CapValue float64 `graphql:"capValue"`
Player struct {
Slug string `graphql:"slug"`
ActiveClub struct {
Slug string `graphql:"slug"`
}
ActiveNationalTeam struct {
Slug string `graphql:"slug"`
}
} `graphql:"player"`
Position types.Position `graphql:"position"`
}
type RivalsGame struct {
Id graphql.Id `graphql:"id"`
Slug string `graphql:"slug"`
Cap int `graphql:"cap"`
DraftablePlayers []RivalsDraftableObject `graphql:"draftablePlayers"`
FormationKnown bool `graphql:"formationKnown"`
Game struct {
Id graphql.Id `graphql:"id"`
} `graphql:"game"`
}

View File

@ -22,28 +22,28 @@ func NewSo5(c *graphql.Client) *So5 {
So5Fixture: graphql.NewQuery[So5Fixture, So5FixtureParams]( So5Fixture: graphql.NewQuery[So5Fixture, So5FixtureParams](
c, c,
"so5Fixture", "so5Fixture",
[]string{"football", "so5"}, []string{"so5"},
), ),
So5Fixtures: graphql.NewPaginatedQuery[So5Fixture, So5FixturesParams]( So5Fixtures: graphql.NewPaginatedQuery[So5Fixture, So5FixturesParams](
c, c,
"so5Fixtures", "so5Fixtures",
[]string{"football", "so5"}, []string{"so5"},
), ),
So5Score: graphql.NewQuery[So5Score, graphql.IdParams]( So5Score: graphql.NewQuery[So5Score, graphql.IdParams](
c, c,
"so5Score", "so5Score",
[]string{"football", "so5"}, []string{"so5"},
), ),
So5Leaderboard: graphql.NewQuery[So5Leaderboard, graphql.SlugParams]( So5Leaderboard: graphql.NewQuery[So5Leaderboard, graphql.SlugParams](
c, c,
"so5Leaderboard", "so5Leaderboard",
[]string{"football", "so5"}, []string{"so5"},
), ),
UpcomingLeaderboards: graphql.NewQuery[[]So5Leaderboard, UpcomingLeaderboardsParams]( UpcomingLeaderboards: graphql.NewQuery[[]So5Leaderboard, UpcomingLeaderboardsParams](
c, c,
"upcomingLeaderboards", "upcomingLeaderboards",
[]string{"football", "so5"}, []string{"so5"},
), ),
} }
} }

View File

@ -3,6 +3,7 @@ package graphql
import ( import (
"context" "context"
"net/http" "net/http"
"reflect"
"sync" "sync"
"time" "time"
@ -60,12 +61,11 @@ func (c *Client) SetApiKey(apiKey string) {
c.apiKey = apiKey c.apiKey = apiKey
} }
func (c *Client) MaxComplexity() int { func (c *Client) MaxQueryComplexity() int {
if c.authenticated { if c.apiKey != "" || c.authenticated {
return MaxAuthenticatedQueryComplexity return MaxAuthenticatedQueryComplexity
} else {
return MaxAnonymousQueryComplexity
} }
return MaxAnonymousQueryComplexity
} }
func (c *Client) ConstructRawQuery( func (c *Client) ConstructRawQuery(
@ -140,3 +140,52 @@ func (c *Client) Mutate(
defer c.lock.Unlock() defer c.lock.Unlock()
return c.gql.Mutate(ctx, q, variables, options...) return c.gql.Mutate(ctx, q, variables, options...)
} }
func GetTypeComplexity(t reflect.Type) int {
var checkStruct func(t reflect.Type, complexity *int)
checkStruct = func(t reflect.Type, complexity *int) {
if t.Kind() != reflect.Struct {
return
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag.Get("graphql")
if field.Type.Kind() == reflect.Struct {
*complexity++
if len(tag) > 6 && tag[:6] == "... on" {
*complexity--
}
checkStruct(field.Type, complexity)
continue
}
if field.Type.Kind() == reflect.Slice {
if isSimpleType(field.Type.Elem()) {
*complexity++
} else {
*complexity++
tmpcomplexity := 0
checkStruct(field.Type.Elem(), &tmpcomplexity)
*complexity += tmpcomplexity * 10
}
continue
}
if tag != "" && tag != "__typename" {
*complexity++
}
}
}
complexity := 0
checkStruct(t, &complexity)
return complexity
}
func isSimpleType(t reflect.Type) bool {
switch t.Kind() {
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.String:
return true
default:
return false
}
}

View File

@ -12,6 +12,8 @@ type PaginatedQuery[ResultType any, Params any] struct {
additionalPayloadParams map[string]interface{} additionalPayloadParams map[string]interface{}
additionalQueryParams map[string]interface{} additionalQueryParams map[string]interface{}
overrideComplexity int
} }
func NewPaginatedQuery[ResultType any, Params any]( func NewPaginatedQuery[ResultType any, Params any](
@ -25,6 +27,7 @@ func NewPaginatedQuery[ResultType any, Params any](
containerLayers: containerLayers, containerLayers: containerLayers,
additionalPayloadParams: make(map[string]interface{}), additionalPayloadParams: make(map[string]interface{}),
additionalQueryParams: make(map[string]interface{}), additionalQueryParams: make(map[string]interface{}),
overrideComplexity: 0,
} }
} }
@ -44,6 +47,11 @@ func (pq *PaginatedQuery[ResultType, Params]) WithQueryParam(
return pq return pq
} }
func (pq *PaginatedQuery[ResultType, Params]) WithOverrideComplexity(complexity int) *PaginatedQuery[ResultType, Params] {
pq.overrideComplexity = complexity
return pq
}
type PaginatedQueryGetOptions struct { type PaginatedQueryGetOptions struct {
Limit int Limit int
} }
@ -68,17 +76,26 @@ func (pq *PaginatedQuery[ResultType, Params]) Get(
var res []ResultType var res []ResultType
after := "" after := ""
var noop ResultType var noop ResultType
pageSize := (pq.c.MaxComplexity() - 9) / (GetComplexity(reflect.TypeOf(noop)) + 1) complexity := pq.overrideComplexity
if complexity == 0 {
complexity = GetTypeComplexity(reflect.TypeOf(noop))
}
maxPageSize := (pq.c.MaxQueryComplexity() - 7 - len(pq.containerLayers)) / complexity
for { for {
pageSize := maxPageSize
if opts.Limit > 0 {
remaining := opts.Limit - len(res)
if remaining < pageSize {
pageSize = remaining
}
}
page, pi, err := pq.getPage(ctx, params, after, pageSize) page, pi, err := pq.getPage(ctx, params, after, pageSize)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, item := range page { res = append(res, page...)
res = append(res, item)
if opts.Limit > 0 && len(res) >= opts.Limit { if opts.Limit > 0 && len(res) >= opts.Limit {
return res, nil return res[:opts.Limit], nil
}
} }
if !pi.HasNextPage { if !pi.HasNextPage {
break break
@ -122,35 +139,3 @@ func (pq *PaginatedQuery[ResultType, Params]) getPage(
} }
return res.Nodes, res.PageInfo, nil return res.Nodes, res.PageInfo, nil
} }
func GetComplexity(t reflect.Type) int {
var checkStruct func(t reflect.Type, complexity *int)
checkStruct = func(t reflect.Type, complexity *int) {
if t.Kind() != reflect.Struct {
return
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Type.Kind() == reflect.Struct {
*complexity++
tag := field.Tag.Get("graphql")
if len(tag) > 6 && tag[:6] == "... on" {
*complexity--
}
checkStruct(field.Type, complexity)
continue
}
if field.Type.Kind() == reflect.Slice {
*complexity++
tmpcomplexity := 0
checkStruct(field.Type.Elem(), &tmpcomplexity)
*complexity += tmpcomplexity * 10
continue
}
*complexity++
}
}
complexity := 0
checkStruct(t, &complexity)
return complexity
}

View File

@ -3,6 +3,7 @@ package graphql
import ( import (
"context" "context"
"fmt" "fmt"
"reflect"
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -15,6 +16,8 @@ type Query[ReturnType any, Params any] struct {
additionalPayloadParams map[string]interface{} additionalPayloadParams map[string]interface{}
additionalQueryParams map[string]interface{} additionalQueryParams map[string]interface{}
maxPageSize int
} }
func NewQuery[ReturnType any, Params any]( func NewQuery[ReturnType any, Params any](
@ -29,6 +32,7 @@ func NewQuery[ReturnType any, Params any](
additionalPayloadParams: make(map[string]interface{}), additionalPayloadParams: make(map[string]interface{}),
additionalQueryParams: make(map[string]interface{}), additionalQueryParams: make(map[string]interface{}),
maxPageSize: 0,
} }
} }
@ -48,6 +52,27 @@ func (r *Query[ReturnType, Params]) WithQueryParam(
return r return r
} }
func (r *Query[ReturnType, Params]) WithMaxPageSize(maxPageSize int) *Query[ReturnType, Params] {
r.maxPageSize = maxPageSize
return r
}
func (r *Query[ReturnType, Params]) GetPageSize() int {
t := reflect.TypeOf((*ReturnType)(nil)).Elem()
if t.Kind() == reflect.Slice {
t = t.Elem()
}
complexity := GetTypeComplexity(t)
maxComplexity := r.c.MaxQueryComplexity()
layers := len(r.containerLayers)
computedPageSize := (maxComplexity - layers - 1) / complexity
if r.maxPageSize > 0 && r.maxPageSize < computedPageSize {
return r.maxPageSize
}
return computedPageSize
}
func (r *Query[ReturnType, Params]) Get(ctx context.Context, params Params) (ReturnType, error) { func (r *Query[ReturnType, Params]) Get(ctx context.Context, params Params) (ReturnType, error) {
paramsMap := convertParamsToMap(params) paramsMap := convertParamsToMap(params)