Skip to content

Commit 7c3e769

Browse files
committed
[Validation] Performance improvements
Related GraphQL-js commit graphql/graphql-js@0bc9088
1 parent 5dc00fb commit 7c3e769

9 files changed

+49
-27
lines changed

graphql/core/validation/context.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
1-
from ..language.ast import FragmentDefinition
1+
from ..language.ast import FragmentDefinition, FragmentSpread
22

33

44
class ValidationContext(object):
5-
__slots__ = '_schema', '_ast', '_type_info', '_fragments'
5+
__slots__ = '_schema', '_ast', '_type_info', '_fragments', '_fragment_spreads'
66

77
def __init__(self, schema, ast, type_info):
88
self._schema = schema
99
self._ast = ast
1010
self._type_info = type_info
1111
self._fragments = None
12+
self._fragment_spreads = {}
1213

1314
def get_schema(self):
1415
return self._schema
1516

17+
def get_fragment_spreads(self, node):
18+
spreads = self._fragment_spreads.get(node)
19+
if not spreads:
20+
spreads = []
21+
self.gather_spreads(spreads, node.selection_set)
22+
self._fragment_spreads[node] = spreads
23+
return spreads
24+
1625
def get_ast(self):
1726
return self._ast
1827

@@ -42,3 +51,11 @@ def get_directive(self):
4251

4352
def get_argument(self):
4453
return self._type_info.get_argument()
54+
55+
@classmethod
56+
def gather_spreads(cls, spreads, node):
57+
for selection in node.selections:
58+
if isinstance(selection, FragmentSpread):
59+
spreads.append(selection)
60+
elif selection.selection_set:
61+
cls.gather_spreads(spreads, selection.selection_set)

graphql/core/validation/rules/arguments_of_correct_type.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def enter_Argument(self, node, key, parent, path, ancestors):
1515
print_ast(node.value), errors),
1616
[node.value]
1717
)
18+
return False
1819

1920
@staticmethod
2021
def bad_value_message(arg_name, type, value, verbose_errors):

graphql/core/validation/rules/default_values_of_correct_type.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ def enter_VariableDefinition(self, node, key, parent, path, ancestors):
2424
self.bad_value_for_default_arg_message(name, type, print_ast(default_value), errors),
2525
[default_value]
2626
)
27+
return False
28+
29+
def enter_SelectionSet(self, node, key, parent, path, ancestors):
30+
return False
31+
32+
def enter_FragmentDefinition(self, node, key, parent, path, ancestors):
33+
return False
2734

2835
@staticmethod
2936
def default_for_non_null_arg_message(var_name, type, guess_type):

graphql/core/validation/rules/no_fragment_cycles.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ def detect_cycle_recursive(self, fragment):
3030
fragment_name = fragment.name.value
3131
self.visited_frags.add(fragment_name)
3232

33-
spread_nodes = []
34-
self.gather_spreads(spread_nodes, fragment.selection_set)
33+
spread_nodes = self.context.get_fragment_spreads(fragment)
3534
if not spread_nodes:
3635
return
3736

@@ -64,11 +63,3 @@ def detect_cycle_recursive(self, fragment):
6463
def cycle_error_message(fragment_name, spread_names):
6564
via = ' via {}'.format(', '.join(spread_names)) if spread_names else ''
6665
return 'Cannot spread fragment "{}" within itself{}.'.format(fragment_name, via)
67-
68-
@classmethod
69-
def gather_spreads(cls, spreads, node):
70-
for selection in node.selections:
71-
if isinstance(selection, ast.FragmentSpread):
72-
spreads.append(selection)
73-
elif selection.selection_set:
74-
cls.gather_spreads(spreads, selection.selection_set)

graphql/core/validation/rules/no_unused_fragments.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,30 @@ class NoUnusedFragments(ValidationRule):
77

88
def __init__(self, context):
99
super(NoUnusedFragments, self).__init__(context)
10-
self.fragment_definitions = []
1110
self.spreads_within_operation = []
12-
self.fragment_adjacencies = {}
13-
self.spread_names = set()
11+
self.fragment_definitions = []
1412

1513
def enter_OperationDefinition(self, node, key, parent, path, ancestors):
16-
self.spread_names = set()
17-
self.spreads_within_operation.append(self.spread_names)
14+
self.spreads_within_operation.append(self.context.get_fragment_spreads(node))
15+
return False
1816

1917
def enter_FragmentDefinition(self, node, key, parent, path, ancestors):
2018
self.fragment_definitions.append(node)
21-
self.spread_names = set()
22-
self.fragment_adjacencies[node.name.value] = self.spread_names
23-
24-
def enter_FragmentSpread(self, node, key, parent, path, ancestors):
25-
self.spread_names.add(node.name.value)
19+
return False
2620

2721
def leave_Document(self, node, key, parent, path, ancestors):
2822
fragment_names_used = set()
2923

3024
def reduce_spread_fragments(spreads):
31-
for fragment_name in spreads:
32-
if fragment_name in fragment_names_used:
25+
for spread in spreads:
26+
frag_name = spread.name.value
27+
if frag_name in fragment_names_used:
3328
continue
3429

35-
fragment_names_used.add(fragment_name)
36-
if fragment_name in self.fragment_adjacencies:
37-
reduce_spread_fragments(self.fragment_adjacencies[fragment_name])
30+
fragment_names_used.add(frag_name)
31+
fragment = self.context.get_fragment(frag_name)
32+
if fragment:
33+
reduce_spread_fragments(self.context.get_fragment_spreads(fragment))
3834

3935
for spreads in self.spreads_within_operation:
4036
reduce_spread_fragments(spreads)

graphql/core/validation/rules/unique_argument_names.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def enter_Argument(self, node, key, parent, path, ancestors):
2525
)
2626

2727
self.known_arg_names[arg_name] = node.name
28+
return False
2829

2930
@staticmethod
3031
def duplicate_arg_message(field):

graphql/core/validation/rules/unique_fragment_names.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ def __init__(self, context):
99
super(UniqueFragmentNames, self).__init__(context)
1010
self.known_fragment_names = {}
1111

12+
def enter_OperationDefinition(self, node, key, parent, path, ancestors):
13+
return False
14+
1215
def enter_FragmentDefinition(self, node, key, parent, path, ancestors):
1316
fragment_name = node.name.value
1417
if fragment_name in self.known_fragment_names:
@@ -18,6 +21,7 @@ def enter_FragmentDefinition(self, node, key, parent, path, ancestors):
1821
)
1922

2023
self.known_fragment_names[fragment_name] = node.name
24+
return False
2125

2226
@staticmethod
2327
def duplicate_fragment_name_message(field):

graphql/core/validation/rules/unique_input_field_names.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def enter_ObjectField(self, node, key, parent, path, ancestors):
2626
)
2727

2828
self.known_names[field_name] = node.name
29+
return False
2930

3031
@staticmethod
3132
def duplicate_input_field_message(field_name):

graphql/core/validation/rules/unique_operation_names.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ def enter_OperationDefinition(self, node, key, parent, path, ancestors):
2121
)
2222

2323
self.known_operation_names[operation_name.value] = operation_name
24+
return False
25+
26+
def enter_FragmentDefinition(self, node, key, parent, path, ancestors):
27+
return False
2428

2529
@staticmethod
2630
def duplicate_operation_name_message(operation_name):

0 commit comments

Comments
 (0)