Skip to content

Commit

Permalink
fix: do not merge batched items with 1 item as a single item fetch (#658
Browse files Browse the repository at this point in the history
)

Co-authored-by: Jens Neuse <jens.neuse@gmx.de>
  • Loading branch information
devsergiy and jensneuse authored Nov 6, 2023
1 parent 00eb593 commit f82681f
Show file tree
Hide file tree
Showing 2 changed files with 275 additions and 2 deletions.
272 changes: 272 additions & 0 deletions v2/pkg/engine/resolve/resolve_federation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1956,6 +1956,278 @@ func TestResolveGraphQLResponse_Federation(t *testing.T) {
}, Context{ctx: context.Background(), Variables: nil}, `{"data":{"topProducts":[{"name":"Table","stock":8,"reviews":[{"body":"Love Table!","author":{"name":"user-1"}},{"body":"Prefer other Table.","author":{"name":"user-2"}}]},{"name":"Couch","stock":2,"reviews":[{"body":"Couch Too expensive.","author":{"name":"user-1"}}]},{"name":"Chair","stock":5,"reviews":[{"body":"Chair Could be better.","author":{"name":"user-2"}}]}]}}`
}))

t.Run("nested batching single root result", testFn(true, func(t *testing.T, ctrl *gomock.Controller) (node *GraphQLResponse, ctx Context, expectedOutput string) {

productsService := mockedDS(t, ctrl,
`{"method":"POST","url":"http://products","body":{"query":"query{topProducts{name __typename upc}}"}}`,
`{"topProducts":[{"name":"Table","__typename":"Product","upc":"1"}]}`)

reviewsService := mockedDS(t, ctrl,
`{"method":"POST","url":"http://reviews","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on Product {reviews {body author {__typename id}}}}}","variables":{"representations":[{"__typename":"Product","upc":"1"}]}}}`,
`{"_entities":[{"__typename":"Product","reviews":[{"body":"Love Table!","author":{"__typename":"User","id":"1"}}]}]}`)

stockService := mockedDS(t, ctrl,
`{"method":"POST","url":"http://stock","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on Product {stock}}}","variables":{"representations":[{"__typename":"Product","upc":"1"}]}}}`,
`{"_entities":[{"stock":8}]}`)

usersService := mockedDS(t, ctrl,
`{"method":"POST","url":"http://users","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {name}}}","variables":{"representations":[{"__typename":"User","id":"1"}]}}}`,
`{"_entities":[{"name":"user-1"}]}`)

return &GraphQLResponse{
Data: &Object{
Fetch: &SingleFetch{
InputTemplate: InputTemplate{
Segments: []TemplateSegment{
{
Data: []byte(`{"method":"POST","url":"http://products","body":{"query":"query{topProducts{name __typename upc}}"}}`),
SegmentType: StaticSegmentType,
},
},
},
FetchConfiguration: FetchConfiguration{
DataSource: productsService,
PostProcessing: PostProcessingConfiguration{
SelectResponseDataPath: []string{"data"},
},
},
},
Fields: []*Field{
{
Name: []byte("topProducts"),
Value: &Array{
Path: []string{"topProducts"},
Item: &Object{
Fetch: &ParallelFetch{
Fetches: []Fetch{
&BatchEntityFetch{
Input: BatchInput{
Header: InputTemplate{
Segments: []TemplateSegment{
{
Data: []byte(`{"method":"POST","url":"http://reviews","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on Product {reviews {body author {__typename id}}}}}","variables":{"representations":[`),
SegmentType: StaticSegmentType,
},
},
},
Items: []InputTemplate{
{
Segments: []TemplateSegment{
{
SegmentType: VariableSegmentType,
VariableKind: ResolvableObjectVariableKind,
Renderer: NewGraphQLVariableResolveRenderer(&Object{
Fields: []*Field{
{
Name: []byte("__typename"),
Value: &String{
Path: []string{"__typename"},
},
},
{
Name: []byte("upc"),
Value: &String{
Path: []string{"upc"},
},
},
},
}),
},
},
},
},
Separator: InputTemplate{
Segments: []TemplateSegment{
{
Data: []byte(`,`),
SegmentType: StaticSegmentType,
},
},
},
Footer: InputTemplate{
Segments: []TemplateSegment{
{
Data: []byte(`]}}}`),
SegmentType: StaticSegmentType,
},
},
},
},
DataSource: reviewsService,
PostProcessing: PostProcessingConfiguration{
SelectResponseDataPath: []string{"data", "_entities"},
},
},
&BatchEntityFetch{
Input: BatchInput{
Header: InputTemplate{
Segments: []TemplateSegment{
{
Data: []byte(`{"method":"POST","url":"http://stock","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on Product {stock}}}","variables":{"representations":[`),
SegmentType: StaticSegmentType,
},
},
},
Items: []InputTemplate{
{
Segments: []TemplateSegment{
{
SegmentType: VariableSegmentType,
VariableKind: ResolvableObjectVariableKind,
Renderer: NewGraphQLVariableResolveRenderer(&Object{
Fields: []*Field{
{
Name: []byte("__typename"),
Value: &String{
Path: []string{"__typename"},
},
},
{
Name: []byte("upc"),
Value: &String{
Path: []string{"upc"},
},
},
},
}),
},
},
},
},
Separator: InputTemplate{
Segments: []TemplateSegment{
{
Data: []byte(`,`),
SegmentType: StaticSegmentType,
},
},
},
Footer: InputTemplate{
Segments: []TemplateSegment{
{
Data: []byte(`]}}}`),
SegmentType: StaticSegmentType,
},
},
},
},
DataSource: stockService,
PostProcessing: PostProcessingConfiguration{
SelectResponseDataPath: []string{"data", "_entities"},
},
},
},
},
Fields: []*Field{
{
Name: []byte("name"),
Value: &String{
Path: []string{"name"},
},
},
{
Name: []byte("stock"),
Value: &Integer{
Path: []string{"stock"},
},
},
{
Name: []byte("reviews"),
Value: &Array{
Path: []string{"reviews"},
Item: &Object{
Fields: []*Field{
{
Name: []byte("body"),
Value: &String{
Path: []string{"body"},
},
},
{
Name: []byte("author"),
Value: &Object{
Path: []string{"author"},
Fetch: &BatchEntityFetch{
Input: BatchInput{
Header: InputTemplate{
Segments: []TemplateSegment{
{
Data: []byte(`{"method":"POST","url":"http://users","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {name}}}","variables":{"representations":[`),
SegmentType: StaticSegmentType,
},
},
},
Items: []InputTemplate{
{
Segments: []TemplateSegment{
{
SegmentType: VariableSegmentType,
VariableKind: ResolvableObjectVariableKind,
Renderer: NewGraphQLVariableResolveRenderer(&Object{
Fields: []*Field{
{
Name: []byte("__typename"),
Value: &String{
Path: []string{"__typename"},
},
},
{
Name: []byte("id"),
Value: &String{
Path: []string{"id"},
},
},
},
}),
},
},
},
},
Separator: InputTemplate{
Segments: []TemplateSegment{
{
Data: []byte(`,`),
SegmentType: StaticSegmentType,
},
},
},
Footer: InputTemplate{
Segments: []TemplateSegment{
{
Data: []byte(`]}}}`),
SegmentType: StaticSegmentType,
},
},
},
},
DataSource: usersService,
PostProcessing: PostProcessingConfiguration{
SelectResponseDataPath: []string{"data", "_entities"},
},
},
Fields: []*Field{
{
Name: []byte("name"),
Value: &String{
Path: []string{"name"},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
}, Context{ctx: context.Background(), Variables: nil}, `{"data":{"topProducts":[{"name":"Table","stock":8,"reviews":[{"body":"Love Table!","author":{"name":"user-1"}}]}]}}`
}))

t.Run("nested batching of direct array children", testFn(true, func(t *testing.T, ctrl *gomock.Controller) (node *GraphQLResponse, ctx Context, expectedOutput string) {

accountsService := mockedDS(t, ctrl,
Expand Down
5 changes: 3 additions & 2 deletions v2/pkg/engine/resolve/v2load.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import (
"sync"

"github.com/pkg/errors"
"golang.org/x/sync/errgroup"

"github.com/wundergraph/graphql-go-tools/v2/pkg/astjson"
"github.com/wundergraph/graphql-go-tools/v2/pkg/pool"
"golang.org/x/sync/errgroup"
)

type V2Loader struct {
Expand Down Expand Up @@ -325,7 +326,7 @@ func (l *V2Loader) mergeResult(res *result, items []int) error {
l.data.RootNode = node
return nil
}
if len(items) == 1 {
if len(items) == 1 && res.batchStats == nil {
l.data.MergeNodesWithPath(items[0], node, res.postProcessing.MergePath)
return nil
}
Expand Down

0 comments on commit f82681f

Please sign in to comment.