Skip to content

Commit 99f2b32

Browse files
authored
fix: merge inline fragment and field selections together (#1240)
This change fixes normalization for cases like the following: query fragmentsWithFields { dog { extras { ... on DogExtra { string } } extras { ... on DogExtra { string string1 } } } } I have marged two different visitors and their tests into a single visitor. EnterSelectionSets from two stages were merged into one, but with the following differences: * Internal loop was optimized, * Early return happens only when something is merged.
1 parent d12ab8d commit 99f2b32

11 files changed

+477
-240
lines changed

v2/pkg/ast/ast_selection.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -241,11 +241,3 @@ func (d *Document) SelectionSetFieldNames(set int) (fieldNames []string) {
241241
}
242242
return
243243
}
244-
245-
func (d *Document) SelectionIsFieldSelection(ref int) bool {
246-
return d.Selections[ref].Kind == SelectionKindField
247-
}
248-
249-
func (d *Document) SelectionIsInlineFragmentSelection(ref int) bool {
250-
return d.Selections[ref].Kind == SelectionKindInlineFragment
251-
}

v2/pkg/astnormalization/abstract_field_normalizer.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ func NewAbstractFieldNormalizer(operation *ast.Document, definition *ast.Documen
3131

3232
mergeInlineFragmentSelections(&walker)
3333
inlineSelectionsFromInlineFragments(&walker)
34-
mergeFieldSelections(&walker)
3534
deduplicateFields(&walker)
3635

3736
return normalizer

v2/pkg/astnormalization/astnormalization.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Package astnormalization helps to transform parsed GraphQL AST's into a easier t
33
44
# Example
55
6-
This examples shows how the normalization package helps "simplifying" a GraphQL AST.
6+
This example shows how the normalization package helps "simplify" a GraphQL AST.
77
88
Input:
99
@@ -76,7 +76,7 @@ import (
7676
)
7777

7878
// NormalizeOperation creates a default Normalizer and applies all rules to a given AST
79-
// In case you're using OperationNormalizer in a hot path you shouldn't be using this function.
79+
// In case you're using OperationNormalizer in a hot path, you shouldn't be using this function.
8080
// Create a new OperationNormalizer using NewNormalizer() instead and re-use it.
8181
func NormalizeOperation(operation, definition *ast.Document, report *operationreport.Report) {
8282
normalizer := NewNormalizer(false, false)
@@ -193,11 +193,11 @@ func WithNormalizeDefinition() Option {
193193
func (o *OperationNormalizer) setupOperationWalkers() {
194194
o.operationWalkers = make([]walkerStage, 0, 9)
195195

196-
// NOTE: normalization rules for variables relies on the fact that
197-
// we will visit only single operation, so it is important to remove non-matching operations
196+
// NOTE: normalization rules for variables rely on the fact that
197+
// we will visit only a single operation, so it is important to remove non-matching operations
198198
if o.options.removeNotMatchingOperationDefinitions {
199199
removeNotMatchingOperationDefinitionsWalker := astvisitor.NewWalker(2)
200-
// this rule do not walk deep into ast, so separate walk not expensive,
200+
// This rule does not walk deep into ast, so a separate walk is not expensive,
201201
// but we could not mix this walk with other rules, because they need to go deep
202202
o.removeOperationDefinitionsVisitor = removeOperationDefinitions(&removeNotMatchingOperationDefinitionsWalker)
203203

@@ -212,7 +212,6 @@ func (o *OperationNormalizer) setupOperationWalkers() {
212212
directiveIncludeSkip(&directivesIncludeSkip)
213213

214214
cleanup := astvisitor.NewWalker(8)
215-
mergeFieldSelections(&cleanup)
216215
deduplicateFields(&cleanup)
217216
if o.options.removeUnusedVariables {
218217
del := deleteUnusedVariables(&cleanup)
@@ -272,7 +271,7 @@ func (o *OperationNormalizer) setupOperationWalkers() {
272271
}
273272

274273
o.operationWalkers = append(o.operationWalkers, walkerStage{
275-
name: "mergeFieldSelections, deduplicateFields, deleteUnusedVariables",
274+
name: "deduplicateFields, deleteUnusedVariables",
276275
walker: &cleanup,
277276
})
278277

@@ -332,7 +331,7 @@ func (o *OperationNormalizer) NormalizeNamedOperation(operation, definition *ast
332331
}
333332

334333
// NOTE: debug code - do not remove
335-
// printed, _ := astprinter.PrintStringIndent(operation, definition, " ")
334+
// printed, _ := astprinter.PrintStringIndent(operation, " ")
336335
// fmt.Println("\n\nNormalizeOperation stage:", o.operationWalkers[i].name)
337336
// fmt.Println(printed)
338337
// fmt.Println("variables:", string(operation.Input.Variables))

v2/pkg/astnormalization/astnormalization_test.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,176 @@ func TestNormalizeOperation(t *testing.T) {
197197
}`, ``, ``)
198198
})
199199

200+
// The following 3 tests are to test the combined logic of inline fragment selection merging
201+
// and a field selection merging. When applied separately, they could not merge the field and
202+
// fragments.
203+
t.Run("combined inline fragment fields merging 1", func(t *testing.T) {
204+
run(t, testDefinition, `
205+
query q {
206+
pet {
207+
... on Dog {
208+
... on Pet {
209+
...DogName
210+
...DogExtraString
211+
}
212+
}
213+
}
214+
pet {
215+
... on Dog {
216+
...DogBarkVolume
217+
...DogExtraString
218+
}
219+
}
220+
}
221+
fragment DogName on Pet { ... on Dog { name } }
222+
fragment DogBarkVolume on Pet { ... on Dog { barkVolume } }
223+
fragment DogExtraString on Dog {
224+
...DogName
225+
extras {
226+
... on DogExtra {
227+
string
228+
}
229+
}
230+
} `, `
231+
query q {
232+
pet {
233+
... on Dog {
234+
name
235+
extras {
236+
string
237+
}
238+
barkVolume
239+
}
240+
}
241+
}`, ``, ``)
242+
})
243+
244+
t.Run("combined inline fragment fields merging 2", func(t *testing.T) {
245+
run(t, testDefinition, `
246+
query q {
247+
pet {
248+
...DogExtraString
249+
}
250+
pet {
251+
... on Dog {
252+
extras {
253+
... on DogExtra {
254+
string1
255+
}
256+
}
257+
}
258+
}
259+
}
260+
fragment DogExtraString on Dog {
261+
extras {
262+
... on DogExtra {
263+
string
264+
}
265+
... on Pet {
266+
__typename
267+
}
268+
}
269+
} `, `
270+
query q {
271+
pet {
272+
... on Dog {
273+
extras {
274+
string
275+
... on Pet {
276+
__typename
277+
}
278+
string1
279+
}
280+
}
281+
}
282+
}`, ``, ``)
283+
})
284+
285+
t.Run("combined inline fragment fields merging 3", func(t *testing.T) {
286+
run(t, ` schema { query: Query }
287+
scalar ID
288+
scalar String
289+
290+
type Query {
291+
requestForQuote(id: ID!): Request
292+
}
293+
type Request { draftQuotePackage: QuotePackage }
294+
type QuotePackage { requestedParts: Parts }
295+
type Parts { edges: [PartEdge] }
296+
type PartEdge { quote: Quote }
297+
type Quote { quoteRevision: QuoteRevisionUnion }
298+
union QuoteRevisionUnion = QuoteRevision | Node
299+
type QuoteRevision { partBreakdown: PartBreakdown }
300+
type Node { name: String! }
301+
type PartBreakdown {
302+
breakdownType: String!
303+
cost: String!
304+
} `, `
305+
query SimplePartQuoteEditorQuery($requestForQuoteId: ID!) {
306+
requestForQuote(id: $requestForQuoteId) {
307+
draftQuotePackage {
308+
...useBreakdownTypeFilter_quotePackage
309+
requestedParts {
310+
edges {
311+
quote {
312+
quoteRevision {
313+
... on QuoteRevision {
314+
partBreakdown {
315+
cost
316+
}
317+
}
318+
}
319+
}
320+
}
321+
}
322+
}
323+
}
324+
}
325+
326+
fragment useBreakdownTypeFilter_quotePackage on QuotePackage {
327+
requestedParts {
328+
edges {
329+
quote {
330+
quoteRevision {
331+
... on QuoteRevision {
332+
partBreakdown {
333+
breakdownType
334+
}
335+
}
336+
... on Node {
337+
__isNode: __typename
338+
}
339+
}
340+
}
341+
}
342+
}
343+
}
344+
`, `
345+
query SimplePartQuoteEditorQuery($requestForQuoteId: ID!) {
346+
requestForQuote(id: $requestForQuoteId) {
347+
draftQuotePackage {
348+
requestedParts {
349+
edges {
350+
quote {
351+
quoteRevision {
352+
... on QuoteRevision {
353+
partBreakdown {
354+
breakdownType
355+
cost
356+
}
357+
}
358+
... on Node {
359+
__isNode: __typename
360+
}
361+
}
362+
}
363+
}
364+
}
365+
}
366+
}
367+
}`, `123`, `123`)
368+
})
369+
200370
t.Run("fragments", func(t *testing.T) {
201371
run(t, variablesExtractionDefinition, `
202372
mutation HttpBinPost{

v2/pkg/astnormalization/field_deduplication.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor"
66
)
77

8+
// deduplicateFields registers a visitor to remove duplicate fields in a GraphQL operation.
89
func deduplicateFields(walker *astvisitor.Walker) {
910
visitor := deduplicateFieldsVisitor{
1011
Walker: walker,

v2/pkg/astnormalization/field_selection_merging.go

Lines changed: 0 additions & 95 deletions
This file was deleted.

0 commit comments

Comments
 (0)