MM-43143: Dataloaders for roles (#19936)

We use a dataloader to optimize roles loading.

https://mattermost.atlassian.net/browse/MM-43143

```release-note
NONE
```

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Agniva De Sarker
2022-04-08 15:32:35 +05:30
коммит произвёл GitHub
родитель ba2f116ed1
Коммит 56dfcddff5
23 изменённых файлов: 1210 добавлений и 15 удалений

Просмотреть файл

@@ -9,6 +9,7 @@ import (
"encoding/json"
"net/http"
"github.com/graph-gophers/dataloader/v6"
graphql "github.com/graph-gophers/graphql-go"
gqlerrors "github.com/graph-gophers/graphql-go/errors"
"github.com/mattermost/mattermost-server/v6/model"
@@ -58,7 +59,12 @@ func (api *API) InitGraphQL() error {
}
// Unique type to hold our context.
type ctxKey struct{}
type ctxKey int
const (
webCtx ctxKey = 0
rolesLoaderCtx ctxKey = 1
)
func (api *API) graphQL(c *Context, w http.ResponseWriter, r *http.Request) {
var response *graphql.Response
@@ -90,7 +96,10 @@ func (api *API) graphQL(c *Context, w http.ResponseWriter, r *http.Request) {
// Populate the context with required info.
reqCtx := r.Context()
reqCtx = context.WithValue(reqCtx, ctxKey{}, c)
reqCtx = context.WithValue(reqCtx, webCtx, c)
rolesLoader := dataloader.NewBatchedLoader(graphQLRolesLoader, dataloader.WithBatchCapacity(200))
reqCtx = context.WithValue(reqCtx, rolesLoaderCtx, rolesLoader)
response = api.schema.Exec(reqCtx,
params.Query,

Просмотреть файл

@@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"github.com/graph-gophers/dataloader/v6"
"github.com/mattermost/mattermost-server/v6/app"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/web"
@@ -278,9 +279,18 @@ func (*resolver) ChannelMembers(ctx context.Context, args struct {
// Kind of an anti-pattern, but there are lots of methods attached to *web.Context
// so we use it for now.
func getCtx(ctx context.Context) (*web.Context, error) {
c, ok := ctx.Value(ctxKey{}).(*web.Context)
c, ok := ctx.Value(webCtx).(*web.Context)
if !ok {
return nil, errors.New("no web.Context found in context")
}
return c, nil
}
// getRolesLoader returns the roles loader out of the context.
func getRolesLoader(ctx context.Context) (*dataloader.Loader, error) {
l, ok := ctx.Value(rolesLoaderCtx).(*dataloader.Loader)
if !ok {
return nil, errors.New("no dataloader.Loader found in context")
}
return l, nil
}

Просмотреть файл

@@ -9,6 +9,7 @@ import (
"fmt"
"strings"
"github.com/graph-gophers/dataloader/v6"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/web"
)
@@ -65,12 +66,24 @@ func (cm *channelMember) Channel(ctx context.Context) (*channel, error) {
}
func (cm *channelMember) Roles_(ctx context.Context) ([]*model.Role, error) {
c, err := getCtx(ctx)
loader, err := getRolesLoader(ctx)
if err != nil {
return nil, err
}
return getGraphQLRoles(c, strings.Fields(cm.Roles))
thunk := loader.LoadMany(ctx, dataloader.NewKeysFromStrings(strings.Fields(cm.Roles)))
results, errs := thunk()
// All errors are the same. We just return the first one.
if len(errs) > 0 && errs[0] != nil {
return nil, err
}
roles := make([]*model.Role, len(results))
for i, res := range results {
roles[i] = res.(*model.Role)
}
return roles, nil
}
func (cm *channelMember) Cursor() *string {
@@ -79,6 +92,28 @@ func (cm *channelMember) Cursor() *string {
return model.NewString(encoded)
}
func graphQLRolesLoader(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
stringKeys := keys.Keys()
result := make([]*dataloader.Result, len(stringKeys))
c, err := getCtx(ctx)
if err != nil {
result[0] = &dataloader.Result{Error: err}
return result
}
roles, err := getGraphQLRoles(c, stringKeys)
if err != nil {
result[0] = &dataloader.Result{Error: err}
return result
}
for i, role := range roles {
result[i] = &dataloader.Result{Data: role}
}
return result
}
func getGraphQLRoles(c *web.Context, roleNames []string) ([]*model.Role, error) {
cleanedRoleNames, valid := model.CleanRoleNames(roleNames)
if !valid {
@@ -91,6 +126,17 @@ func getGraphQLRoles(c *web.Context, roleNames []string) ([]*model.Role, error)
return nil, appErr
}
// The roles need to be in the exact same order as the input slice.
tmp := make(map[string]*model.Role)
for _, r := range roles {
tmp[r.Name] = r
}
// We reuse the same slice and just rewrite the roles.
for i, roleName := range roleNames {
roles[i] = tmp[roleName]
}
return roles, nil
}

Просмотреть файл

@@ -7,6 +7,7 @@ import (
"context"
"strings"
"github.com/graph-gophers/dataloader/v6"
"github.com/mattermost/mattermost-server/v6/model"
)
@@ -60,10 +61,22 @@ func (tm *teamMember) SidebarCategories(ctx context.Context) ([]*model.SidebarCa
// match with api4.getRolesByNames
func (tm *teamMember) Roles_(ctx context.Context) ([]*model.Role, error) {
c, err := getCtx(ctx)
loader, err := getRolesLoader(ctx)
if err != nil {
return nil, err
}
return getGraphQLRoles(c, strings.Fields(tm.Roles))
thunk := loader.LoadMany(ctx, dataloader.NewKeysFromStrings(strings.Fields(tm.Roles)))
results, errs := thunk()
// All errors are the same. We just return the first one.
if len(errs) > 0 && errs[0] != nil {
return nil, err
}
roles := make([]*model.Role, len(results))
for i, res := range results {
roles[i] = res.(*model.Role)
}
return roles, nil
}

Просмотреть файл

@@ -143,3 +143,85 @@ func TestGraphQLChannelsLeft(t *testing.T) {
assert.Len(t, q.ChannelsLeft, 0)
})
}
func TestGraphQLRolesLoader(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
th := Setup(t).InitBasic()
defer th.TearDown()
var q struct {
User struct {
ID string `json:"id"`
Roles []struct {
ID string `json:"id"`
Name string `json:"Name"`
} `json:"roles"`
} `json:"user"`
ChannelMembers []struct {
MsgCount float64 `json:"msgCount"`
Roles []struct {
ID string `json:"id"`
Name string `json:"Name"`
} `json:"roles"`
} `json:"channelMembers"`
TeamMembers []struct {
SchemeUser bool `json:"schemeUser"`
Roles []struct {
ID string `json:"id"`
Name string `json:"Name"`
} `json:"roles"`
}
}
input := graphQLInput{
OperationName: "channelMembers",
Query: `
query channelMembers {
user(id: "me") {
id
username
roles {
id
name
}
}
channelMembers(userId: "me") {
msgCount
roles {
id
name
}
}
teamMembers(userId: "me") {
schemeUser
roles {
id
name
}
}
}
`,
}
resp, err := th.MakeGraphQLRequest(&input)
require.NoError(t, err)
require.Len(t, resp.Errors, 0)
require.NoError(t, json.Unmarshal(resp.Data, &q))
require.Len(t, q.User.Roles, 1)
assert.Equal(t, "system_user", q.User.Roles[0].Name)
require.Len(t, q.ChannelMembers, 5)
for _, cm := range q.ChannelMembers {
require.Len(t, cm.Roles, 1)
assert.Equal(t, "channel_user", cm.Roles[0].Name)
}
require.Len(t, q.TeamMembers, 1)
for _, tm := range q.TeamMembers {
require.Len(t, tm.Roles, 1)
assert.Equal(t, "team_user", tm.Roles[0].Name)
}
}

Просмотреть файл

@@ -7,6 +7,7 @@ import (
"context"
"net/http"
"github.com/graph-gophers/dataloader/v6"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/web"
)
@@ -66,17 +67,29 @@ func getGraphQLUser(ctx context.Context, id string) (*user, error) {
// match with api4.getRolesByNames
func (u *user) Roles(ctx context.Context) ([]*model.Role, error) {
c, err := getCtx(ctx)
if err != nil {
return nil, err
}
roleNames := u.GetRoles()
if len(roleNames) == 0 {
return nil, nil
}
return getGraphQLRoles(c, roleNames)
loader, err := getRolesLoader(ctx)
if err != nil {
return nil, err
}
thunk := loader.LoadMany(ctx, dataloader.NewKeysFromStrings(roleNames))
results, errs := thunk()
// All errors are the same. We just return the first one.
if len(errs) > 0 && errs[0] != nil {
return nil, err
}
roles := make([]*model.Role, len(results))
for i, res := range results {
roles[i] = res.(*model.Role)
}
return roles, nil
}
// match with api4.getPreferences

2
go.mod
Просмотреть файл

@@ -41,6 +41,7 @@ require (
github.com/gorilla/mux v1.8.0
github.com/gorilla/schema v1.2.0
github.com/gorilla/websocket v1.5.0
github.com/graph-gophers/dataloader/v6 v6.0.0
github.com/graph-gophers/graphql-go v1.3.0
github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
@@ -51,7 +52,6 @@ require (
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.4.3
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/memberlist v0.3.1
github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba

3
go.sum
Просмотреть файл

@@ -715,6 +715,8 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/graph-gophers/dataloader/v6 v6.0.0 h1:qBpmq3B8PIQesoh0EJXKGfw+ulMUb+KFl4IZOe9ScWg=
github.com/graph-gophers/dataloader/v6 v6.0.0/go.mod h1:J15OZSnOoZgMkijpbZcwCmglIDYqlUiTEE1xLPbyqZM=
github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0=
github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
@@ -1180,6 +1182,7 @@ github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=

1
vendor/github.com/graph-gophers/dataloader/v6/.gitignore сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1 @@
vendor/

14
vendor/github.com/graph-gophers/dataloader/v6/.travis.yml сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,14 @@
language: go
go:
- 1.15
- 1.14
install:
- go mod install
script:
- go test -v -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)

21
vendor/github.com/graph-gophers/dataloader/v6/LICENSE сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Nick Randall
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

99
vendor/github.com/graph-gophers/dataloader/v6/MIGRATE.md сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,99 @@
## Upgrade from v1 to v2
The only difference between v1 and v2 is that we added use of [context](https://golang.org/pkg/context).
```diff
- loader.Load(key string) Thunk
+ loader.Load(ctx context.Context, key string) Thunk
- loader.LoadMany(keys []string) ThunkMany
+ loader.LoadMany(ctx context.Context, keys []string) ThunkMany
```
```diff
- type BatchFunc func([]string) []*Result
+ type BatchFunc func(context.Context, []string) []*Result
```
## Upgrade from v2 to v3
```diff
// dataloader.Interface as added context.Context to methods
- loader.Prime(key string, value interface{}) Interface
+ loader.Prime(ctx context.Context, key string, value interface{}) Interface
- loader.Clear(key string) Interface
+ loader.Clear(ctx context.Context, key string) Interface
```
```diff
// cache interface as added context.Context to methods
type Cache interface {
- Get(string) (Thunk, bool)
+ Get(context.Context, string) (Thunk, bool)
- Set(string, Thunk)
+ Set(context.Context, string, Thunk)
- Delete(string) bool
+ Delete(context.Context, string) bool
Clear()
}
```
## Upgrade from v3 to v4
```diff
// dataloader.Interface as now allows interace{} as key rather than string
- loader.Load(context.Context, key string) Thunk
+ loader.Load(ctx context.Context, key interface{}) Thunk
- loader.LoadMany(context.Context, key []string) ThunkMany
+ loader.LoadMany(ctx context.Context, keys []interface{}) ThunkMany
- loader.Prime(context.Context, key string, value interface{}) Interface
+ loader.Prime(ctx context.Context, key interface{}, value interface{}) Interface
- loader.Clear(context.Context, key string) Interface
+ loader.Clear(ctx context.Context, key interface{}) Interface
```
```diff
// cache interface now allows interface{} as key instead of string
type Cache interface {
- Get(context.Context, string) (Thunk, bool)
+ Get(context.Context, interface{}) (Thunk, bool)
- Set(context.Context, string, Thunk)
+ Set(context.Context, interface{}, Thunk)
- Delete(context.Context, string) bool
+ Delete(context.Context, interface{}) bool
Clear()
}
```
## Upgrade from v4 to v5
```diff
// dataloader.Interface as now allows interace{} as key rather than string
- loader.Load(context.Context, key interface{}) Thunk
+ loader.Load(ctx context.Context, key Key) Thunk
- loader.LoadMany(context.Context, key []interface{}) ThunkMany
+ loader.LoadMany(ctx context.Context, keys Keys) ThunkMany
- loader.Prime(context.Context, key interface{}, value interface{}) Interface
+ loader.Prime(ctx context.Context, key Key, value interface{}) Interface
- loader.Clear(context.Context, key interface{}) Interface
+ loader.Clear(ctx context.Context, key Key) Interface
```
```diff
// cache interface now allows interface{} as key instead of string
type Cache interface {
- Get(context.Context, interface{}) (Thunk, bool)
+ Get(context.Context, Key) (Thunk, bool)
- Set(context.Context, interface{}, Thunk)
+ Set(context.Context, Key, Thunk)
- Delete(context.Context, interface{}) bool
+ Delete(context.Context, Key) bool
Clear()
}
```
## Upgrade from v5 to v6
We add major version release because we switched to using Go Modules from dep,
and drop build tags for older versions of Go (1.9).
The preferred import method includes the major version tag.
```go
import "github.com/graph-gophers/dataloader/v6"
```

48
vendor/github.com/graph-gophers/dataloader/v6/README.md сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,48 @@
# DataLoader
[![GoDoc](https://godoc.org/gopkg.in/graph-gophers/dataloader.v3?status.svg)](https://godoc.org/github.com/graph-gophers/dataloader)
[![Build Status](https://travis-ci.org/graph-gophers/dataloader.svg?branch=master)](https://travis-ci.org/graph-gophers/dataloader)
This is an implementation of [Facebook's DataLoader](https://github.com/facebook/dataloader) in Golang.
## Install
`go get -u github.com/graph-gophers/dataloader`
## Usage
```go
// setup batch function
batchFn := func(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
var results []*dataloader.Result
// do some async work to get data for specified keys
// append to this list resolved values
return results
}
// create Loader with an in-memory cache
loader := dataloader.NewBatchedLoader(batchFn)
/**
* Use loader
*
* A thunk is a function returned from a function that is a
* closure over a value (in this case an interface value and error).
* When called, it will block until the value is resolved.
*/
thunk := loader.Load(context.TODO(), dataloader.StringKey("key1")) // StringKey is a convenience method that make wraps string to implement `Key` interface
result, err := thunk()
if err != nil {
// handle data error
}
log.Printf("value: %#v", result)
```
### Don't need/want to use context?
You're welcome to install the v1 version of this library.
## Cache
This implementation contains a very basic cache that is intended only to be used for short lived DataLoaders (i.e. DataLoaders that ony exsist for the life of an http request). You may use your own implementation if you want.
> it also has a `NoCache` type that implements the cache interface but all methods are noop. If you do not wish to cache anything.
## Examples
There are a few basic examples in the example folder.

80
vendor/github.com/graph-gophers/dataloader/v6/TRACE.md сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,80 @@
# Adding a new trace backend.
If you whant to add a new tracing backend all you need to do is implement the
`Tracer` interface and pass it as an option to the dataloader on initialization.
As an example, this is how you could implement it to an OpenCensus backend.
```go
package main
import (
"context"
"strings"
exp "go.opencensus.io/examples/exporter"
"github.com/nicksrandall/dataloader"
"go.opencensus.io/trace"
)
// OpenCensusTracer Tracer implements a tracer that can be used with the Open Tracing standard.
type OpenCensusTracer struct{}
// TraceLoad will trace a call to dataloader.LoadMany with Open Tracing
func (OpenCensusTracer) TraceLoad(ctx context.Context, key dataloader.Key) (context.Context, dataloader.TraceLoadFinishFunc) {
cCtx, cSpan := trace.StartSpan(ctx, "Dataloader: load")
cSpan.AddAttributes(
trace.StringAttribute("dataloader.key", key.String()),
)
return cCtx, func(thunk dataloader.Thunk) {
// TODO: is there anything we should do with the results?
cSpan.End()
}
}
// TraceLoadMany will trace a call to dataloader.LoadMany with Open Tracing
func (OpenCensusTracer) TraceLoadMany(ctx context.Context, keys dataloader.Keys) (context.Context, dataloader.TraceLoadManyFinishFunc) {
cCtx, cSpan := trace.StartSpan(ctx, "Dataloader: loadmany")
cSpan.AddAttributes(
trace.StringAttribute("dataloader.keys", strings.Join(keys.Keys(), ",")),
)
return cCtx, func(thunk dataloader.ThunkMany) {
// TODO: is there anything we should do with the results?
cSpan.End()
}
}
// TraceBatch will trace a call to dataloader.LoadMany with Open Tracing
func (OpenCensusTracer) TraceBatch(ctx context.Context, keys dataloader.Keys) (context.Context, dataloader.TraceBatchFinishFunc) {
cCtx, cSpan := trace.StartSpan(ctx, "Dataloader: batch")
cSpan.AddAttributes(
trace.StringAttribute("dataloader.keys", strings.Join(keys.Keys(), ",")),
)
return cCtx, func(results []*dataloader.Result) {
// TODO: is there anything we should do with the results?
cSpan.End()
}
}
func batchFunc(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
// ...loader logic goes here
}
func main(){
//initialize an example exporter that just logs to the console
trace.ApplyConfig(trace.Config{
DefaultSampler: trace.AlwaysSample(),
})
trace.RegisterExporter(&exp.PrintExporter{})
// initialize the dataloader with your new tracer backend
loader := dataloader.NewBatchedLoader(batchFunc, dataloader.WithTracer(OpenCensusTracer{}))
// initialize a context since it's not receiving one from anywhere else.
ctx, span := trace.StartSpan(context.TODO(), "Span Name")
defer span.End()
// request from the dataloader as usual
value, err := loader.Load(ctx, dataloader.StringKey(SomeID))()
// ...
}
```
Don't forget to initialize the exporters of your choice and register it with `trace.RegisterExporter(&exporterInstance)`.

28
vendor/github.com/graph-gophers/dataloader/v6/cache.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,28 @@
package dataloader
import "context"
// The Cache interface. If a custom cache is provided, it must implement this interface.
type Cache interface {
Get(context.Context, Key) (Thunk, bool)
Set(context.Context, Key, Thunk)
Delete(context.Context, Key) bool
Clear()
}
// NoCache implements Cache interface where all methods are noops.
// This is useful for when you don't want to cache items but still
// want to use a data loader
type NoCache struct{}
// Get is a NOOP
func (c *NoCache) Get(context.Context, Key) (Thunk, bool) { return nil, false }
// Set is a NOOP
func (c *NoCache) Set(context.Context, Key, Thunk) { return }
// Delete is a NOOP
func (c *NoCache) Delete(context.Context, Key) bool { return false }
// Clear is a NOOP
func (c *NoCache) Clear() { return }

26
vendor/github.com/graph-gophers/dataloader/v6/codecov.yml сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,26 @@
codecov:
notify:
require_ci_to_pass: true
comment:
behavior: default
layout: header, diff
require_changes: false
coverage:
precision: 2
range:
- 70.0
- 100.0
round: down
status:
changes: false
patch: true
project: true
parsers:
gcov:
branch_detection:
conditional: true
loop: true
macro: false
method: false
javascript:
enable_partials: false

492
vendor/github.com/graph-gophers/dataloader/v6/dataloader.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,492 @@
// Package dataloader is an implimentation of facebook's dataloader in go.
// See https://github.com/facebook/dataloader for more information
package dataloader
import (
"context"
"fmt"
"log"
"runtime"
"sync"
"time"
)
// Interface is a `DataLoader` Interface which defines a public API for loading data from a particular
// data back-end with unique keys such as the `id` column of a SQL table or
// document name in a MongoDB database, given a batch loading function.
//
// Each `DataLoader` instance should contain a unique memoized cache. Use caution when
// used in long-lived applications or those which serve many users with
// different access permissions and consider creating a new instance per
// web request.
type Interface interface {
Load(context.Context, Key) Thunk
LoadMany(context.Context, Keys) ThunkMany
Clear(context.Context, Key) Interface
ClearAll() Interface
Prime(ctx context.Context, key Key, value interface{}) Interface
}
// BatchFunc is a function, which when given a slice of keys (string), returns a slice of `results`.
// It's important that the length of the input keys matches the length of the output results.
//
// The keys passed to this function are guaranteed to be unique
type BatchFunc func(context.Context, Keys) []*Result
// Result is the data structure that a BatchFunc returns.
// It contains the resolved data, and any errors that may have occurred while fetching the data.
type Result struct {
Data interface{}
Error error
}
// ResultMany is used by the LoadMany method.
// It contains a list of resolved data and a list of errors.
// The lengths of the data list and error list will match, and elements at each index correspond to each other.
type ResultMany struct {
Data []interface{}
Error []error
}
// Loader implements the dataloader.Interface.
type Loader struct {
// the batch function to be used by this loader
batchFn BatchFunc
// the maximum batch size. Set to 0 if you want it to be unbounded.
batchCap int
// the internal cache. This packages contains a basic cache implementation but any custom cache
// implementation could be used as long as it implements the `Cache` interface.
cacheLock sync.Mutex
cache Cache
// should we clear the cache on each batch?
// this would allow batching but no long term caching
clearCacheOnBatch bool
// count of queued up items
count int
// the maximum input queue size. Set to 0 if you want it to be unbounded.
inputCap int
// the amount of time to wait before triggering a batch
wait time.Duration
// lock to protect the batching operations
batchLock sync.Mutex
// current batcher
curBatcher *batcher
// used to close the sleeper of the current batcher
endSleeper chan bool
// used by tests to prevent logs
silent bool
// can be set to trace calls to dataloader
tracer Tracer
}
// Thunk is a function that will block until the value (*Result) it contains is resolved.
// After the value it contains is resolved, this function will return the result.
// This function can be called many times, much like a Promise is other languages.
// The value will only need to be resolved once so subsequent calls will return immediately.
type Thunk func() (interface{}, error)
// ThunkMany is much like the Thunk func type but it contains a list of results.
type ThunkMany func() ([]interface{}, []error)
// type used to on input channel
type batchRequest struct {
key Key
channel chan *Result
}
// Option allows for configuration of Loader fields.
type Option func(*Loader)
// WithCache sets the BatchedLoader cache. Defaults to InMemoryCache if a Cache is not set.
func WithCache(c Cache) Option {
return func(l *Loader) {
l.cache = c
}
}
// WithBatchCapacity sets the batch capacity. Default is 0 (unbounded).
func WithBatchCapacity(c int) Option {
return func(l *Loader) {
l.batchCap = c
}
}
// WithInputCapacity sets the input capacity. Default is 1000.
func WithInputCapacity(c int) Option {
return func(l *Loader) {
l.inputCap = c
}
}
// WithWait sets the amount of time to wait before triggering a batch.
// Default duration is 16 milliseconds.
func WithWait(d time.Duration) Option {
return func(l *Loader) {
l.wait = d
}
}
// WithClearCacheOnBatch allows batching of items but no long term caching.
// It accomplishes this by clearing the cache after each batch operation.
func WithClearCacheOnBatch() Option {
return func(l *Loader) {
l.cacheLock.Lock()
l.clearCacheOnBatch = true
l.cacheLock.Unlock()
}
}
// withSilentLogger turns of log messages. It's used by the tests
func withSilentLogger() Option {
return func(l *Loader) {
l.silent = true
}
}
// WithTracer allows tracing of calls to Load and LoadMany
func WithTracer(tracer Tracer) Option {
return func(l *Loader) {
l.tracer = tracer
}
}
// WithOpenTracingTracer allows tracing of calls to Load and LoadMany
func WithOpenTracingTracer() Option {
return WithTracer(&OpenTracingTracer{})
}
// NewBatchedLoader constructs a new Loader with given options.
func NewBatchedLoader(batchFn BatchFunc, opts ...Option) *Loader {
loader := &Loader{
batchFn: batchFn,
inputCap: 1000,
wait: 16 * time.Millisecond,
}
// Apply options
for _, apply := range opts {
apply(loader)
}
// Set defaults
if loader.cache == nil {
loader.cache = NewCache()
}
if loader.tracer == nil {
loader.tracer = &NoopTracer{}
}
return loader
}
// Load load/resolves the given key, returning a channel that will contain the value and error
func (l *Loader) Load(originalContext context.Context, key Key) Thunk {
ctx, finish := l.tracer.TraceLoad(originalContext, key)
c := make(chan *Result, 1)
var result struct {
mu sync.RWMutex
value *Result
}
// lock to prevent duplicate keys coming in before item has been added to cache.
l.cacheLock.Lock()
if v, ok := l.cache.Get(ctx, key); ok {
defer finish(v)
defer l.cacheLock.Unlock()
return v
}
thunk := func() (interface{}, error) {
result.mu.RLock()
resultNotSet := result.value == nil
result.mu.RUnlock()
if resultNotSet {
result.mu.Lock()
if v, ok := <-c; ok {
result.value = v
}
result.mu.Unlock()
}
result.mu.RLock()
defer result.mu.RUnlock()
return result.value.Data, result.value.Error
}
defer finish(thunk)
l.cache.Set(ctx, key, thunk)
l.cacheLock.Unlock()
// this is sent to batch fn. It contains the key and the channel to return the
// the result on
req := &batchRequest{key, c}
l.batchLock.Lock()
// start the batch window if it hasn't already started.
if l.curBatcher == nil {
l.curBatcher = l.newBatcher(l.silent, l.tracer)
// start the current batcher batch function
go l.curBatcher.batch(originalContext)
// start a sleeper for the current batcher
l.endSleeper = make(chan bool)
go l.sleeper(l.curBatcher, l.endSleeper)
}
l.curBatcher.input <- req
// if we need to keep track of the count (max batch), then do so.
if l.batchCap > 0 {
l.count++
// if we hit our limit, force the batch to start
if l.count == l.batchCap {
// end the batcher synchronously here because another call to Load
// may concurrently happen and needs to go to a new batcher.
l.curBatcher.end()
// end the sleeper for the current batcher.
// this is to stop the goroutine without waiting for the
// sleeper timeout.
close(l.endSleeper)
l.reset()
}
}
l.batchLock.Unlock()
return thunk
}
// LoadMany loads mulitiple keys, returning a thunk (type: ThunkMany) that will resolve the keys passed in.
func (l *Loader) LoadMany(originalContext context.Context, keys Keys) ThunkMany {
ctx, finish := l.tracer.TraceLoadMany(originalContext, keys)
var (
length = len(keys)
data = make([]interface{}, length)
errors = make([]error, length)
c = make(chan *ResultMany, 1)
wg sync.WaitGroup
)
resolve := func(ctx context.Context, i int) {
defer wg.Done()
thunk := l.Load(ctx, keys[i])
result, err := thunk()
data[i] = result
errors[i] = err
}
wg.Add(length)
for i := range keys {
go resolve(ctx, i)
}
go func() {
wg.Wait()
// errs is nil unless there exists a non-nil error.
// This prevents dataloader from returning a slice of all-nil errors.
var errs []error
for _, e := range errors {
if e != nil {
errs = errors
break
}
}
c <- &ResultMany{Data: data, Error: errs}
close(c)
}()
var result struct {
mu sync.RWMutex
value *ResultMany
}
thunkMany := func() ([]interface{}, []error) {
result.mu.RLock()
resultNotSet := result.value == nil
result.mu.RUnlock()
if resultNotSet {
result.mu.Lock()
if v, ok := <-c; ok {
result.value = v
}
result.mu.Unlock()
}
result.mu.RLock()
defer result.mu.RUnlock()
return result.value.Data, result.value.Error
}
defer finish(thunkMany)
return thunkMany
}
// Clear clears the value at `key` from the cache, it it exsits. Returs self for method chaining
func (l *Loader) Clear(ctx context.Context, key Key) Interface {
l.cacheLock.Lock()
l.cache.Delete(ctx, key)
l.cacheLock.Unlock()
return l
}
// ClearAll clears the entire cache. To be used when some event results in unknown invalidations.
// Returns self for method chaining.
func (l *Loader) ClearAll() Interface {
l.cacheLock.Lock()
l.cache.Clear()
l.cacheLock.Unlock()
return l
}
// Prime adds the provided key and value to the cache. If the key already exists, no change is made.
// Returns self for method chaining
func (l *Loader) Prime(ctx context.Context, key Key, value interface{}) Interface {
if _, ok := l.cache.Get(ctx, key); !ok {
thunk := func() (interface{}, error) {
return value, nil
}
l.cache.Set(ctx, key, thunk)
}
return l
}
func (l *Loader) reset() {
l.count = 0
l.curBatcher = nil
if l.clearCacheOnBatch {
l.cache.Clear()
}
}
type batcher struct {
input chan *batchRequest
batchFn BatchFunc
finished bool
silent bool
tracer Tracer
}
// newBatcher returns a batcher for the current requests
// all the batcher methods must be protected by a global batchLock
func (l *Loader) newBatcher(silent bool, tracer Tracer) *batcher {
return &batcher{
input: make(chan *batchRequest, l.inputCap),
batchFn: l.batchFn,
silent: silent,
tracer: tracer,
}
}
// stop receiving input and process batch function
func (b *batcher) end() {
if !b.finished {
close(b.input)
b.finished = true
}
}
// execute the batch of all items in queue
func (b *batcher) batch(originalContext context.Context) {
var (
keys = make(Keys, 0)
reqs = make([]*batchRequest, 0)
items = make([]*Result, 0)
panicErr interface{}
)
for item := range b.input {
keys = append(keys, item.key)
reqs = append(reqs, item)
}
ctx, finish := b.tracer.TraceBatch(originalContext, keys)
defer finish(items)
func() {
defer func() {
if r := recover(); r != nil {
panicErr = r
if b.silent {
return
}
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
log.Printf("Dataloader: Panic received in batch function: %v\n%s", panicErr, buf)
}
}()
items = b.batchFn(ctx, keys)
}()
if panicErr != nil {
for _, req := range reqs {
req.channel <- &Result{Error: fmt.Errorf("Panic received in batch function: %v", panicErr)}
close(req.channel)
}
return
}
if len(items) != len(keys) {
err := &Result{Error: fmt.Errorf(`
The batch function supplied did not return an array of responses
the same length as the array of keys.
Keys:
%v
Values:
%v
`, keys, items)}
for _, req := range reqs {
req.channel <- err
close(req.channel)
}
return
}
for i, req := range reqs {
req.channel <- items[i]
close(req.channel)
}
}
// wait the appropriate amount of time for the provided batcher
func (l *Loader) sleeper(b *batcher, close chan bool) {
select {
// used by batch to close early. usually triggered by max batch size
case <-close:
return
// this will move this goroutine to the back of the callstack?
case <-time.After(l.wait):
}
// reset
// this is protected by the batchLock to avoid closing the batcher input
// channel while Load is inserting a request
l.batchLock.Lock()
b.end()
// We can end here also if the batcher has already been closed and a
// new one has been created. So reset the loader state only if the batcher
// is the current one
if l.curBatcher == b {
l.reset()
}
l.batchLock.Unlock()
}

10
vendor/github.com/graph-gophers/dataloader/v6/go.mod сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,10 @@
module github.com/graph-gophers/dataloader/v6
go 1.15
require (
github.com/hashicorp/golang-lru v0.5.4
github.com/opentracing/opentracing-go v1.2.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/stretchr/testify v1.6.1 // indirect
)

18
vendor/github.com/graph-gophers/dataloader/v6/go.sum сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,18 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

63
vendor/github.com/graph-gophers/dataloader/v6/in_memory_cache.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,63 @@
package dataloader
import (
"context"
"sync"
)
// InMemoryCache is an in memory implementation of Cache interface.
// This simple implementation is well suited for
// a "per-request" dataloader (i.e. one that only lives
// for the life of an http request) but it's not well suited
// for long lived cached items.
type InMemoryCache struct {
items map[string]Thunk
mu sync.RWMutex
}
// NewCache constructs a new InMemoryCache
func NewCache() *InMemoryCache {
items := make(map[string]Thunk)
return &InMemoryCache{
items: items,
}
}
// Set sets the `value` at `key` in the cache
func (c *InMemoryCache) Set(_ context.Context, key Key, value Thunk) {
c.mu.Lock()
c.items[key.String()] = value
c.mu.Unlock()
}
// Get gets the value at `key` if it exsits, returns value (or nil) and bool
// indicating of value was found
func (c *InMemoryCache) Get(_ context.Context, key Key) (Thunk, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
item, found := c.items[key.String()]
if !found {
return nil, false
}
return item, true
}
// Delete deletes item at `key` from cache
func (c *InMemoryCache) Delete(ctx context.Context, key Key) bool {
if _, found := c.Get(ctx, key); found {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.items, key.String())
return true
}
return false
}
// Clear clears the entire cache
func (c *InMemoryCache) Clear() {
c.mu.Lock()
c.items = map[string]Thunk{}
c.mu.Unlock()
}

39
vendor/github.com/graph-gophers/dataloader/v6/key.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,39 @@
package dataloader
// Key is the interface that all keys need to implement
type Key interface {
// String returns a guaranteed unique string that can be used to identify an object
String() string
// Raw returns the raw, underlaying value of the key
Raw() interface{}
}
// Keys wraps a slice of Key types to provide some convenience methods.
type Keys []Key
// Keys returns the list of strings. One for each "Key" in the list
func (l Keys) Keys() []string {
list := make([]string, len(l))
for i := range l {
list[i] = l[i].String()
}
return list
}
// StringKey implements the Key interface for a string
type StringKey string
// String is an identity method. Used to implement String interface
func (k StringKey) String() string { return string(k) }
// Raw is an identity method. Used to implement Key Raw
func (k StringKey) Raw() interface{} { return k }
// NewKeysFromStrings converts a `[]strings` to a `Keys` ([]Key)
func NewKeysFromStrings(strings []string) Keys {
list := make(Keys, len(strings))
for i := range strings {
list[i] = StringKey(strings[i])
}
return list
}

78
vendor/github.com/graph-gophers/dataloader/v6/trace.go сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,78 @@
package dataloader
import (
"context"
opentracing "github.com/opentracing/opentracing-go"
)
type TraceLoadFinishFunc func(Thunk)
type TraceLoadManyFinishFunc func(ThunkMany)
type TraceBatchFinishFunc func([]*Result)
// Tracer is an interface that may be used to implement tracing.
type Tracer interface {
// TraceLoad will trace the calls to Load
TraceLoad(ctx context.Context, key Key) (context.Context, TraceLoadFinishFunc)
// TraceLoadMany will trace the calls to LoadMany
TraceLoadMany(ctx context.Context, keys Keys) (context.Context, TraceLoadManyFinishFunc)
// TraceBatch will trace data loader batches
TraceBatch(ctx context.Context, keys Keys) (context.Context, TraceBatchFinishFunc)
}
// OpenTracing Tracer implements a tracer that can be used with the Open Tracing standard.
type OpenTracingTracer struct{}
// TraceLoad will trace a call to dataloader.LoadMany with Open Tracing
func (OpenTracingTracer) TraceLoad(ctx context.Context, key Key) (context.Context, TraceLoadFinishFunc) {
span, spanCtx := opentracing.StartSpanFromContext(ctx, "Dataloader: load")
span.SetTag("dataloader.key", key.String())
return spanCtx, func(thunk Thunk) {
// TODO: is there anything we should do with the results?
span.Finish()
}
}
// TraceLoadMany will trace a call to dataloader.LoadMany with Open Tracing
func (OpenTracingTracer) TraceLoadMany(ctx context.Context, keys Keys) (context.Context, TraceLoadManyFinishFunc) {
span, spanCtx := opentracing.StartSpanFromContext(ctx, "Dataloader: loadmany")
span.SetTag("dataloader.keys", keys.Keys())
return spanCtx, func(thunk ThunkMany) {
// TODO: is there anything we should do with the results?
span.Finish()
}
}
// TraceBatch will trace a call to dataloader.LoadMany with Open Tracing
func (OpenTracingTracer) TraceBatch(ctx context.Context, keys Keys) (context.Context, TraceBatchFinishFunc) {
span, spanCtx := opentracing.StartSpanFromContext(ctx, "Dataloader: batch")
span.SetTag("dataloader.keys", keys.Keys())
return spanCtx, func(results []*Result) {
// TODO: is there anything we should do with the results?
span.Finish()
}
}
// NoopTracer is the default (noop) tracer
type NoopTracer struct{}
// TraceLoad is a noop function
func (NoopTracer) TraceLoad(ctx context.Context, key Key) (context.Context, TraceLoadFinishFunc) {
return ctx, func(Thunk) {}
}
// TraceLoadMany is a noop function
func (NoopTracer) TraceLoadMany(ctx context.Context, keys Keys) (context.Context, TraceLoadManyFinishFunc) {
return ctx, func(ThunkMany) {}
}
// TraceBatch is a noop function
func (NoopTracer) TraceBatch(ctx context.Context, keys Keys) (context.Context, TraceBatchFinishFunc) {
return ctx, func(result []*Result) {}
}

4
vendor/modules.txt поставляемый
Просмотреть файл

@@ -276,6 +276,9 @@ github.com/gorilla/schema
# github.com/gorilla/websocket v1.5.0
## explicit
github.com/gorilla/websocket
# github.com/graph-gophers/dataloader/v6 v6.0.0
## explicit
github.com/graph-gophers/dataloader/v6
# github.com/graph-gophers/graphql-go v1.3.0
## explicit
github.com/graph-gophers/graphql-go
@@ -322,7 +325,6 @@ github.com/hashicorp/go-plugin/internal/plugin
## explicit
github.com/hashicorp/go-sockaddr
# github.com/hashicorp/golang-lru v0.5.4
## explicit
github.com/hashicorp/golang-lru
github.com/hashicorp/golang-lru/simplelru
# github.com/hashicorp/memberlist v0.3.1