Skip to content

Commit befea5a

Browse files
committed
Replace validation visitor stacks with single-slot variables
Eliminates array push/pop operations per validation pass by replacing array-based stacks with single-slot variables that use the Ruby call stack for save/restore. Pre-allocates path array indexed by depth counter.
1 parent 680b1d7 commit befea5a

File tree

7 files changed

+93
-75
lines changed

7 files changed

+93
-75
lines changed

lib/graphql/static_validation/base_visitor.rb

Lines changed: 84 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,29 @@ module GraphQL
33
module StaticValidation
44
class BaseVisitor < GraphQL::Language::StaticVisitor
55
def initialize(document, context)
6-
@path = []
7-
@object_types = []
8-
@directives = []
9-
@field_definitions = []
10-
@argument_definitions = []
11-
@directive_definitions = []
6+
@path = Array.new(32) # pre-allocated, indexed by @path_depth
7+
@path_depth = 0
8+
@current_object_type = nil
9+
@parent_object_type = nil
10+
@current_field_definition = nil
11+
@current_argument_definition = nil
12+
@parent_argument_definition = nil
13+
@current_directive_definition = nil
1214
@context = context
1315
@types = context.query.types
1416
@schema = context.schema
17+
@inline_fragment_paths = {}
18+
@field_unwrapped_types = {}.compare_by_identity
1519
super(document)
1620
end
1721

1822
attr_reader :context
1923

20-
# @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
21-
attr_reader :object_types
24+
attr_reader :current_object_type, :parent_object_type
2225

2326
# @return [Array<String>] The nesting of the current position in the AST
2427
def path
25-
@path.dup
28+
@path[0, @path_depth]
2629
end
2730

2831
# Build a class to visit the AST and perform validation,
@@ -55,135 +58,150 @@ def self.including_rules(rules)
5558
module ContextMethods
5659
def on_operation_definition(node, parent)
5760
object_type = @schema.root_type_for_operation(node.operation_type)
58-
push_type(object_type)
59-
@path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
61+
prev_parent_ot = @parent_object_type
62+
@parent_object_type = @current_object_type
63+
@current_object_type = object_type
64+
@path[@path_depth] = "#{node.operation_type}#{node.name ? " #{node.name}" : ""}"; @path_depth += 1
6065
super
61-
@object_types.pop
62-
@path.pop
66+
@current_object_type = @parent_object_type
67+
@parent_object_type = prev_parent_ot
68+
@path_depth -= 1
6369
end
6470

6571
def on_fragment_definition(node, parent)
66-
on_fragment_with_type(node) do
67-
@path.push("fragment #{node.name}")
68-
super
72+
object_type = if node.type
73+
@types.type(node.type.name)
74+
else
75+
@current_object_type
6976
end
77+
prev_parent_ot = @parent_object_type
78+
@parent_object_type = @current_object_type
79+
@current_object_type = object_type
80+
@path[@path_depth] = "fragment #{node.name}"; @path_depth += 1
81+
super
82+
@current_object_type = @parent_object_type
83+
@parent_object_type = prev_parent_ot
84+
@path_depth -= 1
7085
end
7186

87+
INLINE_FRAGMENT_NO_TYPE = "..."
88+
7289
def on_inline_fragment(node, parent)
73-
on_fragment_with_type(node) do
74-
@path.push("...#{node.type ? " on #{node.type.to_query_string}" : ""}")
75-
super
90+
if node.type
91+
object_type = @types.type(node.type.name)
92+
@path[@path_depth] = @inline_fragment_paths[node.type.name] ||= -"... on #{node.type.to_query_string}"; @path_depth += 1
93+
else
94+
object_type = @current_object_type
95+
@path[@path_depth] = INLINE_FRAGMENT_NO_TYPE; @path_depth += 1
7696
end
97+
prev_parent_ot = @parent_object_type
98+
@parent_object_type = @current_object_type
99+
@current_object_type = object_type
100+
super
101+
@current_object_type = @parent_object_type
102+
@parent_object_type = prev_parent_ot
103+
@path_depth -= 1
77104
end
78105

79106
def on_field(node, parent)
80-
parent_type = @object_types.last
107+
parent_type = @current_object_type
81108
field_definition = @types.field(parent_type, node.name)
82-
@field_definitions.push(field_definition)
83-
if !field_definition.nil?
84-
next_object_type = field_definition.type.unwrap
85-
push_type(next_object_type)
109+
prev_field_definition = @current_field_definition
110+
@current_field_definition = field_definition
111+
prev_parent_ot = @parent_object_type
112+
@parent_object_type = @current_object_type
113+
if field_definition
114+
@current_object_type = @field_unwrapped_types[field_definition] ||= field_definition.type.unwrap
86115
else
87-
push_type(nil)
116+
@current_object_type = nil
88117
end
89-
@path.push(node.alias || node.name)
118+
@path[@path_depth] = node.alias || node.name; @path_depth += 1
90119
super
91-
@field_definitions.pop
92-
@object_types.pop
93-
@path.pop
120+
@current_field_definition = prev_field_definition
121+
@current_object_type = @parent_object_type
122+
@parent_object_type = prev_parent_ot
123+
@path_depth -= 1
94124
end
95125

96126
def on_directive(node, parent)
97127
directive_defn = @context.schema_directives[node.name]
98-
@directive_definitions.push(directive_defn)
128+
prev_directive_definition = @current_directive_definition
129+
@current_directive_definition = directive_defn
99130
super
100-
@directive_definitions.pop
131+
@current_directive_definition = prev_directive_definition
101132
end
102133

103134
def on_argument(node, parent)
104-
argument_defn = if (arg = @argument_definitions.last)
135+
argument_defn = if (arg = @current_argument_definition)
105136
arg_type = arg.type.unwrap
106137
if arg_type.kind.input_object?
107138
@types.argument(arg_type, node.name)
108139
else
109140
nil
110141
end
111-
elsif (directive_defn = @directive_definitions.last)
142+
elsif (directive_defn = @current_directive_definition)
112143
@types.argument(directive_defn, node.name)
113-
elsif (field_defn = @field_definitions.last)
144+
elsif (field_defn = @current_field_definition)
114145
@types.argument(field_defn, node.name)
115146
else
116147
nil
117148
end
118149

119-
@argument_definitions.push(argument_defn)
120-
@path.push(node.name)
150+
prev_parent = @parent_argument_definition
151+
@parent_argument_definition = @current_argument_definition
152+
@current_argument_definition = argument_defn
153+
@path[@path_depth] = node.name; @path_depth += 1
121154
super
122-
@argument_definitions.pop
123-
@path.pop
155+
@current_argument_definition = @parent_argument_definition
156+
@parent_argument_definition = prev_parent
157+
@path_depth -= 1
124158
end
125159

126160
def on_fragment_spread(node, parent)
127-
@path.push("... #{node.name}")
161+
@path[@path_depth] = "... #{node.name}"; @path_depth += 1
128162
super
129-
@path.pop
163+
@path_depth -= 1
130164
end
131165

132166
def on_input_object(node, parent)
133-
arg_defn = @argument_definitions.last
167+
arg_defn = @current_argument_definition
134168
if arg_defn && arg_defn.type.list?
135-
@path.push(parent.children.index(node))
169+
@path[@path_depth] = parent.children.index(node); @path_depth += 1
136170
super
137-
@path.pop
171+
@path_depth -= 1
138172
else
139173
super
140174
end
141175
end
142176

143177
# @return [GraphQL::BaseType] The current object type
144178
def type_definition
145-
@object_types.last
179+
@current_object_type
146180
end
147181

148182
# @return [GraphQL::BaseType] The type which the current type came from
149183
def parent_type_definition
150-
@object_types[-2]
184+
@parent_object_type
151185
end
152186

153187
# @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
154188
def field_definition
155-
@field_definitions.last
189+
@current_field_definition
156190
end
157191

158192
# @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
159193
def directive_definition
160-
@directive_definitions.last
194+
@current_directive_definition
161195
end
162196

163197
# @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
164198
def argument_definition
165-
# Don't get the _last_ one because that's the current one.
166-
# Get the second-to-last one, which is the parent of the current one.
167-
@argument_definitions[-2]
199+
# Return the parent argument definition (not the current one).
200+
@parent_argument_definition
168201
end
169202

170203
private
171204

172-
def on_fragment_with_type(node)
173-
object_type = if node.type
174-
@types.type(node.type.name)
175-
else
176-
@object_types.last
177-
end
178-
push_type(object_type)
179-
yield(node)
180-
@object_types.pop
181-
@path.pop
182-
end
183-
184-
def push_type(t)
185-
@object_types.push(t)
186-
end
187205
end
188206

189207
private
@@ -192,7 +210,7 @@ def add_error(error, path: nil)
192210
if @context.too_many_errors?
193211
throw :too_many_validation_errors
194212
end
195-
error.path ||= (path || @path.dup)
213+
error.path ||= (path || @path[0, @path_depth])
196214
context.errors << error
197215
end
198216

lib/graphql/static_validation/rules/argument_literals_are_compatible.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def on_argument(node, parent)
1212
return
1313
end
1414

15-
if @context.schema.error_bubbling || context.errors.none? { |err| err.path.take(@path.size) == @path }
15+
if @context.schema.error_bubbling || context.errors.none? { |err| err.path.take(@path_depth) == @path[0, @path_depth] }
1616
parent_defn = parent_definition(parent)
1717

1818
if parent_defn && (arg_defn = @types.argument(parent_defn, node.name))

lib/graphql/static_validation/rules/fields_are_defined_on_type.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module GraphQL
33
module StaticValidation
44
module FieldsAreDefinedOnType
55
def on_field(node, parent)
6-
parent_type = @object_types[-2]
6+
parent_type = @parent_object_type
77
field = context.query.types.field(parent_type, node.name)
88

99
if field.nil?

lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ module FieldsHaveAppropriateSelections
77
include GraphQL::StaticValidation::Error::ErrorHelper
88

99
def on_field(node, parent)
10-
field_defn = field_definition
11-
if validate_field_selections(node, field_defn.type.unwrap)
10+
if validate_field_selections(node, @current_object_type)
1211
super
1312
end
1413
end

lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ def initialize(*)
88
end
99

1010
def on_inline_fragment(node, parent)
11-
fragment_parent = context.object_types[-2]
12-
fragment_child = context.object_types.last
11+
fragment_parent = @parent_object_type
12+
fragment_child = @current_object_type
1313
if fragment_child
1414
validate_fragment_in_scope(fragment_parent, fragment_child, node, context, context.path)
1515
end
1616
super
1717
end
1818

1919
def on_fragment_spread(node, parent)
20-
fragment_parent = context.object_types.last
20+
fragment_parent = @current_object_type
2121
@spreads_to_validate << FragmentSpread.new(node: node, parent_type: fragment_parent, path: context.path)
2222
super
2323
end

lib/graphql/static_validation/rules/required_arguments_are_present.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module GraphQL
33
module StaticValidation
44
module RequiredArgumentsArePresent
55
def on_field(node, _parent)
6-
assert_required_args(node, field_definition)
6+
assert_required_args(node, @current_field_definition)
77
super
88
end
99

lib/graphql/static_validation/validation_context.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ def initialize(query, visitor_class, max_errors)
3232
# TODO stop using def_delegators because of Array allocations
3333
def_delegators :@visitor,
3434
:path, :type_definition, :field_definition, :argument_definition,
35-
:parent_type_definition, :directive_definition, :object_types, :dependencies
35+
:parent_type_definition, :directive_definition,
36+
:current_object_type, :parent_object_type, :dependencies
3637

3738
def on_dependency_resolve(&handler)
3839
@on_dependency_resolve_handlers << handler

0 commit comments

Comments
 (0)