Skip to content

Commit f82681f

Browse files
devsergiyjensneuse
andauthored
fix: do not merge batched items with 1 item as a single item fetch (#658)
Co-authored-by: Jens Neuse <[email protected]>
1 parent 00eb593 commit f82681f

File tree

2 files changed

+275
-2
lines changed

2 files changed

+275
-2
lines changed

v2/pkg/engine/resolve/resolve_federation_test.go

+272
Original file line numberDiff line numberDiff line change
@@ -1956,6 +1956,278 @@ func TestResolveGraphQLResponse_Federation(t *testing.T) {
19561956
}, 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"}}]}]}}`
19571957
}))
19581958

1959+
t.Run("nested batching single root result", testFn(true, func(t *testing.T, ctrl *gomock.Controller) (node *GraphQLResponse, ctx Context, expectedOutput string) {
1960+
1961+
productsService := mockedDS(t, ctrl,
1962+
`{"method":"POST","url":"http://products","body":{"query":"query{topProducts{name __typename upc}}"}}`,
1963+
`{"topProducts":[{"name":"Table","__typename":"Product","upc":"1"}]}`)
1964+
1965+
reviewsService := mockedDS(t, ctrl,
1966+
`{"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"}]}}}`,
1967+
`{"_entities":[{"__typename":"Product","reviews":[{"body":"Love Table!","author":{"__typename":"User","id":"1"}}]}]}`)
1968+
1969+
stockService := mockedDS(t, ctrl,
1970+
`{"method":"POST","url":"http://stock","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on Product {stock}}}","variables":{"representations":[{"__typename":"Product","upc":"1"}]}}}`,
1971+
`{"_entities":[{"stock":8}]}`)
1972+
1973+
usersService := mockedDS(t, ctrl,
1974+
`{"method":"POST","url":"http://users","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {name}}}","variables":{"representations":[{"__typename":"User","id":"1"}]}}}`,
1975+
`{"_entities":[{"name":"user-1"}]}`)
1976+
1977+
return &GraphQLResponse{
1978+
Data: &Object{
1979+
Fetch: &SingleFetch{
1980+
InputTemplate: InputTemplate{
1981+
Segments: []TemplateSegment{
1982+
{
1983+
Data: []byte(`{"method":"POST","url":"http://products","body":{"query":"query{topProducts{name __typename upc}}"}}`),
1984+
SegmentType: StaticSegmentType,
1985+
},
1986+
},
1987+
},
1988+
FetchConfiguration: FetchConfiguration{
1989+
DataSource: productsService,
1990+
PostProcessing: PostProcessingConfiguration{
1991+
SelectResponseDataPath: []string{"data"},
1992+
},
1993+
},
1994+
},
1995+
Fields: []*Field{
1996+
{
1997+
Name: []byte("topProducts"),
1998+
Value: &Array{
1999+
Path: []string{"topProducts"},
2000+
Item: &Object{
2001+
Fetch: &ParallelFetch{
2002+
Fetches: []Fetch{
2003+
&BatchEntityFetch{
2004+
Input: BatchInput{
2005+
Header: InputTemplate{
2006+
Segments: []TemplateSegment{
2007+
{
2008+
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":[`),
2009+
SegmentType: StaticSegmentType,
2010+
},
2011+
},
2012+
},
2013+
Items: []InputTemplate{
2014+
{
2015+
Segments: []TemplateSegment{
2016+
{
2017+
SegmentType: VariableSegmentType,
2018+
VariableKind: ResolvableObjectVariableKind,
2019+
Renderer: NewGraphQLVariableResolveRenderer(&Object{
2020+
Fields: []*Field{
2021+
{
2022+
Name: []byte("__typename"),
2023+
Value: &String{
2024+
Path: []string{"__typename"},
2025+
},
2026+
},
2027+
{
2028+
Name: []byte("upc"),
2029+
Value: &String{
2030+
Path: []string{"upc"},
2031+
},
2032+
},
2033+
},
2034+
}),
2035+
},
2036+
},
2037+
},
2038+
},
2039+
Separator: InputTemplate{
2040+
Segments: []TemplateSegment{
2041+
{
2042+
Data: []byte(`,`),
2043+
SegmentType: StaticSegmentType,
2044+
},
2045+
},
2046+
},
2047+
Footer: InputTemplate{
2048+
Segments: []TemplateSegment{
2049+
{
2050+
Data: []byte(`]}}}`),
2051+
SegmentType: StaticSegmentType,
2052+
},
2053+
},
2054+
},
2055+
},
2056+
DataSource: reviewsService,
2057+
PostProcessing: PostProcessingConfiguration{
2058+
SelectResponseDataPath: []string{"data", "_entities"},
2059+
},
2060+
},
2061+
&BatchEntityFetch{
2062+
Input: BatchInput{
2063+
Header: InputTemplate{
2064+
Segments: []TemplateSegment{
2065+
{
2066+
Data: []byte(`{"method":"POST","url":"http://stock","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on Product {stock}}}","variables":{"representations":[`),
2067+
SegmentType: StaticSegmentType,
2068+
},
2069+
},
2070+
},
2071+
Items: []InputTemplate{
2072+
{
2073+
Segments: []TemplateSegment{
2074+
{
2075+
SegmentType: VariableSegmentType,
2076+
VariableKind: ResolvableObjectVariableKind,
2077+
Renderer: NewGraphQLVariableResolveRenderer(&Object{
2078+
Fields: []*Field{
2079+
{
2080+
Name: []byte("__typename"),
2081+
Value: &String{
2082+
Path: []string{"__typename"},
2083+
},
2084+
},
2085+
{
2086+
Name: []byte("upc"),
2087+
Value: &String{
2088+
Path: []string{"upc"},
2089+
},
2090+
},
2091+
},
2092+
}),
2093+
},
2094+
},
2095+
},
2096+
},
2097+
Separator: InputTemplate{
2098+
Segments: []TemplateSegment{
2099+
{
2100+
Data: []byte(`,`),
2101+
SegmentType: StaticSegmentType,
2102+
},
2103+
},
2104+
},
2105+
Footer: InputTemplate{
2106+
Segments: []TemplateSegment{
2107+
{
2108+
Data: []byte(`]}}}`),
2109+
SegmentType: StaticSegmentType,
2110+
},
2111+
},
2112+
},
2113+
},
2114+
DataSource: stockService,
2115+
PostProcessing: PostProcessingConfiguration{
2116+
SelectResponseDataPath: []string{"data", "_entities"},
2117+
},
2118+
},
2119+
},
2120+
},
2121+
Fields: []*Field{
2122+
{
2123+
Name: []byte("name"),
2124+
Value: &String{
2125+
Path: []string{"name"},
2126+
},
2127+
},
2128+
{
2129+
Name: []byte("stock"),
2130+
Value: &Integer{
2131+
Path: []string{"stock"},
2132+
},
2133+
},
2134+
{
2135+
Name: []byte("reviews"),
2136+
Value: &Array{
2137+
Path: []string{"reviews"},
2138+
Item: &Object{
2139+
Fields: []*Field{
2140+
{
2141+
Name: []byte("body"),
2142+
Value: &String{
2143+
Path: []string{"body"},
2144+
},
2145+
},
2146+
{
2147+
Name: []byte("author"),
2148+
Value: &Object{
2149+
Path: []string{"author"},
2150+
Fetch: &BatchEntityFetch{
2151+
Input: BatchInput{
2152+
Header: InputTemplate{
2153+
Segments: []TemplateSegment{
2154+
{
2155+
Data: []byte(`{"method":"POST","url":"http://users","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {name}}}","variables":{"representations":[`),
2156+
SegmentType: StaticSegmentType,
2157+
},
2158+
},
2159+
},
2160+
Items: []InputTemplate{
2161+
{
2162+
Segments: []TemplateSegment{
2163+
{
2164+
SegmentType: VariableSegmentType,
2165+
VariableKind: ResolvableObjectVariableKind,
2166+
Renderer: NewGraphQLVariableResolveRenderer(&Object{
2167+
Fields: []*Field{
2168+
{
2169+
Name: []byte("__typename"),
2170+
Value: &String{
2171+
Path: []string{"__typename"},
2172+
},
2173+
},
2174+
{
2175+
Name: []byte("id"),
2176+
Value: &String{
2177+
Path: []string{"id"},
2178+
},
2179+
},
2180+
},
2181+
}),
2182+
},
2183+
},
2184+
},
2185+
},
2186+
Separator: InputTemplate{
2187+
Segments: []TemplateSegment{
2188+
{
2189+
Data: []byte(`,`),
2190+
SegmentType: StaticSegmentType,
2191+
},
2192+
},
2193+
},
2194+
Footer: InputTemplate{
2195+
Segments: []TemplateSegment{
2196+
{
2197+
Data: []byte(`]}}}`),
2198+
SegmentType: StaticSegmentType,
2199+
},
2200+
},
2201+
},
2202+
},
2203+
DataSource: usersService,
2204+
PostProcessing: PostProcessingConfiguration{
2205+
SelectResponseDataPath: []string{"data", "_entities"},
2206+
},
2207+
},
2208+
Fields: []*Field{
2209+
{
2210+
Name: []byte("name"),
2211+
Value: &String{
2212+
Path: []string{"name"},
2213+
},
2214+
},
2215+
},
2216+
},
2217+
},
2218+
},
2219+
},
2220+
},
2221+
},
2222+
},
2223+
},
2224+
},
2225+
},
2226+
},
2227+
},
2228+
}, Context{ctx: context.Background(), Variables: nil}, `{"data":{"topProducts":[{"name":"Table","stock":8,"reviews":[{"body":"Love Table!","author":{"name":"user-1"}}]}]}}`
2229+
}))
2230+
19592231
t.Run("nested batching of direct array children", testFn(true, func(t *testing.T, ctrl *gomock.Controller) (node *GraphQLResponse, ctx Context, expectedOutput string) {
19602232

19612233
accountsService := mockedDS(t, ctrl,

v2/pkg/engine/resolve/v2load.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import (
1010
"sync"
1111

1212
"github.com/pkg/errors"
13+
"golang.org/x/sync/errgroup"
14+
1315
"github.com/wundergraph/graphql-go-tools/v2/pkg/astjson"
1416
"github.com/wundergraph/graphql-go-tools/v2/pkg/pool"
15-
"golang.org/x/sync/errgroup"
1617
)
1718

1819
type V2Loader struct {
@@ -325,7 +326,7 @@ func (l *V2Loader) mergeResult(res *result, items []int) error {
325326
l.data.RootNode = node
326327
return nil
327328
}
328-
if len(items) == 1 {
329+
if len(items) == 1 && res.batchStats == nil {
329330
l.data.MergeNodesWithPath(items[0], node, res.postProcessing.MergePath)
330331
return nil
331332
}

0 commit comments

Comments
 (0)