Skip to content

Commit e8035a8

Browse files
authored
Fix performance degradation on handling conflict fields (#212)
Contributed by Kohei Morita Replicates graphql/graphql-js@f94b511
1 parent 4aeece0 commit e8035a8

File tree

3 files changed

+75
-12
lines changed

3 files changed

+75
-12
lines changed

src/graphql/validation/rules/overlapping_fields_can_be_merged.py

+31-12
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@
1010
FragmentDefinitionNode,
1111
FragmentSpreadNode,
1212
InlineFragmentNode,
13-
ObjectFieldNode,
14-
ObjectValueNode,
1513
SelectionSetNode,
14+
ValueNode,
1615
print_ast,
1716
)
1817
from ...type import (
@@ -558,7 +557,7 @@ def find_conflict(
558557
)
559558

560559
# Two field calls must have the same arguments.
561-
if stringify_arguments(node1) != stringify_arguments(node2):
560+
if not same_arguments(node1, node2):
562561
return (response_name, "they have differing arguments"), [node1], [node2]
563562

564563
directives1 = node1.directives
@@ -598,14 +597,34 @@ def find_conflict(
598597
return None # no conflict
599598

600599

601-
def stringify_arguments(field_node: Union[FieldNode, DirectiveNode]) -> str:
602-
input_object_with_args = ObjectValueNode(
603-
fields=tuple(
604-
ObjectFieldNode(name=arg_node.name, value=arg_node.value)
605-
for arg_node in field_node.arguments
606-
)
607-
)
608-
return print_ast(sort_value_node(input_object_with_args))
600+
def same_arguments(
601+
node1: Union[FieldNode, DirectiveNode], node2: Union[FieldNode, DirectiveNode]
602+
) -> bool:
603+
args1 = node1.arguments
604+
args2 = node2.arguments
605+
606+
if args1 is None or len(args1) == 0:
607+
return args2 is None or len(args2) == 0
608+
609+
if args2 is None or len(args2) == 0:
610+
return False
611+
612+
if len(args1) != len(args2):
613+
return False
614+
615+
values2 = {arg.name.value: arg.value for arg in args2}
616+
617+
for arg1 in args1:
618+
value1 = arg1.value
619+
value2 = values2.get(arg1.name.value)
620+
if value2 is None or stringify_value(value1) != stringify_value(value2):
621+
return False
622+
623+
return True
624+
625+
626+
def stringify_value(value: ValueNode) -> str:
627+
return print_ast(sort_value_node(value))
609628

610629

611630
def get_stream_directive(
@@ -627,7 +646,7 @@ def same_streams(
627646
return True
628647
if stream1 and stream2:
629648
# check if both fields have equivalent streams
630-
return stringify_arguments(stream1) == stringify_arguments(stream2)
649+
return same_arguments(stream1, stream2)
631650
# fields have a mix of stream and no stream
632651
return False
633652

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from graphql import (
2+
GraphQLField,
3+
GraphQLObjectType,
4+
GraphQLSchema,
5+
GraphQLString,
6+
graphql_sync,
7+
)
8+
9+
10+
schema = GraphQLSchema(
11+
query=GraphQLObjectType(
12+
name="Query",
13+
fields={
14+
"hello": GraphQLField(
15+
GraphQLString,
16+
resolve=lambda obj, info: "world",
17+
)
18+
},
19+
)
20+
)
21+
source = "query {{ {fields} }}".format(fields="hello " * 250)
22+
23+
24+
def test_many_repeated_fields(benchmark):
25+
print(source)
26+
result = benchmark(lambda: graphql_sync(schema, source))
27+
assert not result.errors

tests/validation/test_overlapping_fields_can_be_merged.py

+17
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,23 @@ def different_stream_directive_second_missing_args():
166166
],
167167
)
168168

169+
def different_stream_directive_extra_argument():
170+
assert_errors(
171+
"""
172+
fragment conflictingArgs on Dog {
173+
name @stream(label: "streamLabel", initialCount: 1)
174+
name @stream(label: "streamLabel", initialCount: 1, extraArg: true)
175+
}""",
176+
[
177+
{
178+
"message": "Fields 'name' conflict because they have differing"
179+
" stream directives. Use different aliases on the fields"
180+
" to fetch both if this was intentional.",
181+
"locations": [(3, 15), (4, 15)],
182+
}
183+
],
184+
)
185+
169186
def mix_of_stream_and_no_stream():
170187
assert_errors(
171188
"""

0 commit comments

Comments
 (0)