Skip to content

Commit fdbfd2f

Browse files
committed
Move #run into Result classes
1 parent 9d5b6da commit fdbfd2f

2 files changed

Lines changed: 155 additions & 225 deletions

File tree

lib/graphql/execution/interpreter/runtime.rb

Lines changed: 20 additions & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
# Next thoughts
66
#
77
# - `continue_field` is probably a step of its own -- that method can somehow be factored out
8-
# - UNION/INTERFACE should initialize the ResultHash that the resolved object type will eventually use.
9-
# That would simplify the method call a lot. And then it could add a new step itself.
108
# - It seems like Dataloader/Lazy will fit in at the queue level, so the flow would be:
119
# - Run jobs from queue
1210
# - Then, run dataloader/lazies
@@ -45,6 +43,8 @@ def current_object
4543
# @return [GraphQL::Query::Context]
4644
attr_reader :context
4745

46+
attr_reader :dataloader
47+
4848
def initialize(query:, lazies_at_depth:)
4949
@query = query
5050
@current_trace = query.current_trace
@@ -78,115 +78,6 @@ def inspect
7878
"#<#{self.class.name} response=#{@response.inspect}>"
7979
end
8080

81-
class ObjectStep
82-
def initialize(runtime, response, runtime_state)
83-
@runtime = runtime
84-
@response = response
85-
@runtime_state = runtime_state
86-
end
87-
88-
def run
89-
@runtime.each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys|
90-
@response.ordered_result_keys ||= ordered_result_keys
91-
if is_selection_array
92-
this_result = GraphQLResultHash.new(
93-
@response.graphql_response_name,
94-
@response.graphql_result_type,
95-
@response.graphql_application_value,
96-
@response.graphql_parent,
97-
@response.graphql_is_non_null_in_parent,
98-
selections,
99-
false,
100-
@response.ast_node,
101-
@response.graphql_arguments,
102-
@response.graphql_field)
103-
this_result.ordered_result_keys = ordered_result_keys
104-
final_result = @response
105-
else
106-
this_result = @response
107-
final_result = nil
108-
end
109-
@runtime.evaluate_selections(
110-
selections,
111-
this_result,
112-
final_result,
113-
nil,
114-
)
115-
end
116-
end
117-
end
118-
119-
class ListStep
120-
def initialize(runtime, runtime_state, response_list, list_object, was_scoped)
121-
@runtime = runtime
122-
@runtime_state = runtime_state
123-
@response_list = response_list
124-
@list_object = list_object
125-
@was_scoped = was_scoped
126-
end
127-
128-
def run
129-
current_type = @response_list.graphql_result_type
130-
inner_type = current_type.of_type
131-
# This is true for objects, unions, and interfaces
132-
# use_dataloader_job = !inner_type.unwrap.kind.input?
133-
inner_type_non_null = inner_type.non_null?
134-
idx = nil
135-
list_value = begin
136-
begin
137-
@list_object.each do |inner_value|
138-
idx ||= 0
139-
this_idx = idx
140-
idx += 1
141-
# TODO if use_dataloader_job ... ??
142-
# Better would be to extract a ListValueStep
143-
@runtime.resolve_list_item(
144-
inner_value,
145-
inner_type,
146-
inner_type_non_null,
147-
@response_list.ast_node,
148-
@response_list.graphql_field,
149-
@response_list.graphql_application_value,
150-
@response_list.graphql_arguments,
151-
this_idx,
152-
@response_list,
153-
@was_scoped,
154-
@runtime_state
155-
)
156-
end
157-
158-
@response_list
159-
rescue NoMethodError => err
160-
# Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
161-
if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
162-
# This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
163-
raise ListResultFailedError.new(value: @list_object, field: @response_list.graphql_field, path: @runtime.current_path)
164-
else
165-
# This was some other NoMethodError -- let it bubble to reveal the real error.
166-
raise
167-
end
168-
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
169-
ex_err
170-
rescue StandardError => err
171-
begin
172-
@runtime.query.handle_or_reraise(err)
173-
rescue GraphQL::ExecutionError => ex_err
174-
ex_err
175-
end
176-
end
177-
rescue StandardError => err
178-
begin
179-
@runtime.query.handle_or_reraise(err)
180-
rescue GraphQL::ExecutionError => ex_err
181-
ex_err
182-
end
183-
end
184-
# Detect whether this error came while calling `.each` (before `idx` is set) or while running list *items* (after `idx` is set)
185-
error_is_non_null = idx.nil? ? is_non_null : inner_type.non_null?
186-
@runtime.continue_value(list_value, @response_list.graphql_field, error_is_non_null, @response_list.ast_node, @response_list.graphql_result_name, @response_list.graphql_parent)
187-
end
188-
end
189-
19081
class DirectivesStep
19182
def initialize(runtime, object, ast_node, next_step)
19283
@runtime = runtime
@@ -197,7 +88,7 @@ def initialize(runtime, object, ast_node, next_step)
19788

19889
def run
19990
@runtime.call_method_on_directives(:resolve, @object, @ast_node.directives) do
200-
next_step.call
91+
@runtime.run_queue << @next_step
20192
end
20293
end
20394
end
@@ -234,14 +125,14 @@ def run
234125

235126
possible_types = @runtime.query.types.possible_types(current_type)
236127
if !possible_types.include?(resolved_type)
237-
parent_type = @field.owner_type
128+
field = @response_hash.graphql_field
129+
parent_type = field.owner_type
238130
err_class = current_type::UnresolvedTypeError
239131
type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
240-
@runtime.schema.type_error(type_error, context)
132+
@runtime.schema.type_error(type_error, @runtime.context)
241133
@runtime.set_result(selection_result, result_name, nil, false, is_non_null)
242134
nil
243135
else
244-
# TODO create the response_hash ahead of time which contains all this metadata
245136
@runtime.continue_field(resolved_value, @response_hash.graphql_field, resolved_type, @response_hash.ast_node, @response_hash.graphql_selections, @response_hash.graphql_is_non_null_in_parent, @response_hash.graphql_arguments, @response_hash.graphql_result_name, @response_hash.graphql_parent, @was_scoped, @runtime_state)
246137
end
247138
end
@@ -275,15 +166,14 @@ def run_eager
275166
if object_proxy.nil?
276167
@response = nil
277168
else
278-
@response = GraphQLResultHash.new(nil, root_type, object_proxy, nil, false, selections, is_eager, ast_node, nil, nil)
169+
@response = GraphQLResultHash.new(self, nil, root_type, object_proxy, nil, false, selections, is_eager, ast_node, nil, nil)
279170
@response.base_path = base_path
280171
runtime_state.current_result = @response
281-
obj_step = ObjectStep.new(self, @response, nil)
282172
if !ast_node.directives.empty?
283173
dir_step = DirectivesStep.new(self, object, ast_node, obj_step)
284174
@run_queue << dir_step
285175
else
286-
@run_queue << obj_step
176+
@run_queue << @response
287177
end
288178
end
289179
when "LIST"
@@ -304,9 +194,9 @@ def run_eager
304194
end
305195
@response = selection_result[result_name]
306196
else
307-
@response = GraphQLResultArray.new(nil, root_type, nil, nil, false, selections, false, ast_node, nil, nil)
197+
@response = GraphQLResultArray.new(self, nil, root_type, object, nil, false, selections, false, ast_node, nil, nil)
308198
@response.base_path = base_path
309-
@run_queue << ListStep.new(self, runtime_state, @response, object, false)
199+
@run_queue << @response
310200
end
311201
when "SCALAR", "ENUM"
312202
result_name = ast_node.alias || ast_node.name
@@ -329,27 +219,6 @@ def run_eager
329219
@response.base_path = base_path
330220

331221
@run_queue << ResolveTypeStep.new(self, @response, false)
332-
333-
# resolved_type, _resolved_obj = resolve_type(root_type, object)
334-
# resolved_type = schema.sync_lazy(resolved_type)
335-
# object_proxy = resolved_type.wrap(object, context)
336-
# object_proxy = schema.sync_lazy(object_proxy)
337-
338-
# each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys|
339-
# @response.ordered_result_keys ||= ordered_result_keys
340-
# if is_selection_array == true
341-
# raise "This isn't supported yet"
342-
# end
343-
344-
# @dataloader.append_job {
345-
# evaluate_selections(
346-
# selections,
347-
# @response,
348-
# nil,
349-
# runtime_state,
350-
# )
351-
# }
352-
# end
353222
else
354223
raise "Invariant: unsupported type kind for partial execution: #{root_type.kind.inspect} (#{root_type})"
355224
end
@@ -451,51 +320,7 @@ def gather_selections(owner_object, owner_type, selections, selections_to_run, s
451320

452321
# @return [void]
453322
def evaluate_selections(gathered_selections, selections_result, target_result, runtime_state) # rubocop:disable Metrics/ParameterLists
454-
runtime_state ||= get_current_runtime_state
455-
runtime_state.current_result_name = nil
456-
runtime_state.current_result = selections_result
457-
# This is a less-frequent case; use a fast check since it's often not there.
458-
if (directives = gathered_selections[:graphql_directives])
459-
gathered_selections.delete(:graphql_directives)
460-
end
461323

462-
call_method_on_directives(:resolve, selections_result.graphql_application_value, directives) do
463-
finished_jobs = 0
464-
enqueued_jobs = gathered_selections.size
465-
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
466-
# Field resolution may pause the fiber,
467-
# so it wouldn't get to the `Resolve` call that happens below.
468-
# So instead trigger a run from this outer context.
469-
if selections_result.graphql_is_eager
470-
@dataloader.clear_cache
471-
@dataloader.run_isolated {
472-
evaluate_selection(
473-
result_name, field_ast_nodes_or_ast_node, selections_result
474-
)
475-
finished_jobs += 1
476-
if finished_jobs == enqueued_jobs
477-
if target_result
478-
selections_result.merge_into(target_result)
479-
end
480-
end
481-
@dataloader.clear_cache
482-
}
483-
else
484-
@dataloader.append_job {
485-
evaluate_selection(
486-
result_name, field_ast_nodes_or_ast_node, selections_result
487-
)
488-
finished_jobs += 1
489-
if finished_jobs == enqueued_jobs
490-
if target_result
491-
selections_result.merge_into(target_result)
492-
end
493-
end
494-
}
495-
end
496-
end
497-
selections_result
498-
end
499324
end
500325

501326
# @return [void]
@@ -586,7 +411,11 @@ def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_node
586411
extra_args[:argument_details] = :__arguments_add_self
587412
when :parent
588413
parent_result = selection_result.graphql_parent
589-
extra_args[:parent] = parent_result&.graphql_application_value&.object
414+
if parent_result.is_a?(GraphQL::Execution::Interpreter::Runtime::GraphQLResultArray)
415+
parent_result = parent_result.graphql_parent
416+
end
417+
parent_value = parent_result&.graphql_application_value&.object
418+
extra_args[:parent] = parent_value
590419
else
591420
extra_args[extra] = field_defn.fetch_extra(extra, context)
592421
end
@@ -844,39 +673,8 @@ def continue_field(value, field, current_type, ast_node, next_selections, is_non
844673
set_result(selection_result, result_name, r, false, is_non_null)
845674
r
846675
when "UNION", "INTERFACE"
847-
response_hash = GraphQLResultHash.new(result_name, current_type, value, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
676+
response_hash = GraphQLResultHash.new(self, result_name, current_type, value, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
848677
@run_queue << ResolveTypeStep.new(self, response_hash, was_scoped)
849-
# resolved_type_or_lazy = begin
850-
# resolve_type(current_type, value)
851-
# rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
852-
# return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
853-
# rescue StandardError => err
854-
# begin
855-
# query.handle_or_reraise(err)
856-
# rescue GraphQL::ExecutionError => ex_err
857-
# return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
858-
# end
859-
# end
860-
# after_lazy(resolved_type_or_lazy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_type_result, runtime_state|
861-
# if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
862-
# resolved_type, resolved_value = resolved_type_result
863-
# else
864-
# resolved_type = resolved_type_result
865-
# resolved_value = value
866-
# end
867-
868-
# possible_types = query.types.possible_types(current_type)
869-
# if !possible_types.include?(resolved_type)
870-
# parent_type = field.owner_type
871-
# err_class = current_type::UnresolvedTypeError
872-
# type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
873-
# schema.type_error(type_error, context)
874-
# set_result(selection_result, result_name, nil, false, is_non_null)
875-
# nil
876-
# else
877-
# continue_field(resolved_value, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped, runtime_state)
878-
# end
879-
# end
880678
when "OBJECT"
881679
object_proxy = begin
882680
was_scoped ? current_type.wrap_scoped(value, context) : current_type.wrap(value, context)
@@ -886,15 +684,15 @@ def continue_field(value, field, current_type, ast_node, next_selections, is_non
886684
after_lazy(object_proxy, ast_node: ast_node, field: field, owner_object: selection_result.graphql_application_value, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_object, runtime_state|
887685
continue_value = continue_value(inner_object, field, is_non_null, ast_node, result_name, selection_result)
888686
if HALT != continue_value
889-
response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
687+
response_hash = GraphQLResultHash.new(self, result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
890688
set_result(selection_result, result_name, response_hash, true, is_non_null)
891-
@run_queue << ObjectStep.new(self, response_hash, runtime_state)
689+
@run_queue << response_hash
892690
end
893691
end
894692
when "LIST"
895-
response_list = GraphQLResultArray.new(result_name, current_type, selection_result.graphql_application_value, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
693+
response_list = GraphQLResultArray.new(self, result_name, current_type, value, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
896694
set_result(selection_result, result_name, response_list, true, is_non_null)
897-
@run_queue << ListStep.new(self, runtime_state, response_list, value, was_scoped)
695+
@run_queue << response_list
898696
else
899697
raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
900698
end

0 commit comments

Comments
 (0)