lib/graphql/execution/execute.rb in graphql-1.6.8 vs lib/graphql/execution/execute.rb in graphql-1.7.0

- old
+ new

@@ -11,108 +11,130 @@ # Just a singleton for implementing {Query::Context#skip} # @api private SKIP = Skip.new # @api private - PROPAGATE_NULL = Object.new + class PropagateNull + end + # @api private + PROPAGATE_NULL = PropagateNull.new def execute(ast_operation, root_type, query) - result = resolve_selection( - query.root_value, - root_type, - query.irep_selection, - query.context, - mutation: query.mutation? - ) - - GraphQL::Execution::Lazy.resolve(result) - - result.to_h + result = resolve_root_selection(query) + lazy_resolve_root_selection(result, {query: query}) + GraphQL::Execution::Flatten.call(query.context) end # @api private module ExecutionFunctions module_function - def resolve_selection(object, current_type, selection, query_ctx, mutation: false ) - selection_result = SelectionResult.new + def resolve_root_selection(query) + GraphQL::Tracing.trace("execute_query", query: query) do + operation = query.selected_operation + op_type = operation.operation_type + root_type = query.root_type_for_operation(op_type) + resolve_selection( + query.root_value, + root_type, + query.context, + mutation: query.mutation? + ) + end + end - selection.typed_children[current_type].each do |name, subselection| - field_result = resolve_field( - selection_result, - subselection, - current_type, - subselection.definition, - object, - query_ctx + def lazy_resolve_root_selection(result, query: nil, queries: nil) + if query.nil? && queries.length == 1 + query = queries[0] + end + + GraphQL::Tracing.trace("execute_query_lazy", {queries: queries, query: query}) do + GraphQL::Execution::Lazy.resolve(result) + end + end + + def resolve_selection(object, current_type, current_ctx, mutation: false ) + # Assign this _before_ resolving the children + # so that when a child propagates null, the selection result is + # ready for it. + current_ctx.value = {} + + selections_on_type = current_ctx.irep_node.typed_children[current_type] + + selections_on_type.each do |name, child_irep_node| + field_ctx = current_ctx.spawn_child( + key: name, + object: object, + irep_node: child_irep_node, ) + field_result = GraphQL::Tracing.trace("execute_field", { context: field_ctx }) do + resolve_field( + object, + field_ctx + ) + end + if field_result.is_a?(Skip) next end if mutation - GraphQL::Execution::Lazy.resolve(field_result) + GraphQL::Execution::Lazy.resolve(field_ctx) end - selection_result.set(name, field_result) # If the last subselection caused a null to propagate to _this_ selection, # then we may as well quit executing fields because they # won't be in the response - if selection_result.invalid_null? + if current_ctx.invalid_null? break + else + current_ctx.value[name] = field_ctx end end - selection_result + current_ctx.value end - def resolve_field(owner, selection, parent_type, field, object, query_ctx) - query = query_ctx.query - field_ctx = query_ctx.spawn( - parent_type: parent_type, - field: field, - key: selection.name, - selection: selection, - ) + def resolve_field(object, field_ctx) + query = field_ctx.query + irep_node = field_ctx.irep_node + parent_type = irep_node.owner_type + field = field_ctx.field raw_value = begin - arguments = query.arguments_for(selection, field) - query_ctx.schema.middleware.invoke([parent_type, object, field, arguments, field_ctx]) + arguments = query.arguments_for(irep_node, field) + field_ctx.schema.middleware.invoke([parent_type, object, field, arguments, field_ctx]) rescue GraphQL::ExecutionError => err err end - result = if query.schema.lazy?(raw_value) - field.prepare_lazy(raw_value, arguments, field_ctx).then { |inner_value| - continue_resolve_field(owner, selection, parent_type, field, inner_value, field_ctx) + # If the returned object is lazy (unfinished), + # assign the lazy object to `.value=` so we can resolve it later. + # When we resolve it later, reassign it to `.value=` so that + # the finished value replaces the unfinished one. + # + # If the returned object is finished, continue to coerce + # and resolve child fields + if query.schema.lazy?(raw_value) + field_ctx.value = field.prepare_lazy(raw_value, arguments, field_ctx).then { |inner_value| + field_ctx.value = continue_resolve_field(inner_value, field_ctx) } elsif raw_value.is_a?(GraphQL::Execution::Lazy) # It came from a connection resolve, assume it was already instrumented - raw_value.then { |inner_value| - continue_resolve_field(owner, selection, parent_type, field, inner_value, field_ctx) + field_ctx.value = raw_value.then { |inner_value| + field_ctx.value = continue_resolve_field(inner_value, field_ctx) } else - continue_resolve_field(owner, selection, parent_type, field, raw_value, field_ctx) + field_ctx.value = continue_resolve_field(raw_value, field_ctx) end - - case result - when PROPAGATE_NULL, GraphQL::Execution::Lazy, SelectionResult - FieldResult.new( - owner: owner, - type: field.type, - value: result, - ) - else - result - end end - def continue_resolve_field(owner, selection, parent_type, field, raw_value, field_ctx) - if owner.invalid_null? - return + def continue_resolve_field(raw_value, field_ctx) + if field_ctx.parent.invalid_null? + return nil end query = field_ctx.query case raw_value when GraphQL::ExecutionError @@ -129,23 +151,22 @@ end end end resolve_value( - owner, - parent_type, - field, - field.type, raw_value, - selection, + field_ctx.type, field_ctx, ) end - def resolve_value(owner, parent_type, field_defn, field_type, value, selection, field_ctx) + def resolve_value(value, field_type, field_ctx) + field_defn = field_ctx.field + if value.nil? if field_type.kind.non_null? + parent_type = field_ctx.irep_node.owner_type type_error = GraphQL::InvalidNullError.new(parent_type, field_defn, value) field_ctx.schema.type_error(type_error, field_ctx) PROPAGATE_NULL else nil @@ -155,77 +176,65 @@ PROPAGATE_NULL else nil end elsif value.is_a?(Skip) - value + field_ctx.value = value else case field_type.kind - when GraphQL::TypeKinds::SCALAR + when GraphQL::TypeKinds::SCALAR, GraphQL::TypeKinds::ENUM field_type.coerce_result(value, field_ctx) - when GraphQL::TypeKinds::ENUM - field_type.coerce_result(value, field_ctx) when GraphQL::TypeKinds::LIST inner_type = field_type.of_type i = 0 result = [] + field_ctx.value = result + value.each do |inner_value| - inner_ctx = field_ctx.spawn( + inner_ctx = field_ctx.spawn_child( key: i, - selection: selection, - parent_type: parent_type, - field: field_defn, + object: inner_value, + irep_node: field_ctx.irep_node, ) inner_result = resolve_value( - owner, - parent_type, - field_defn, - inner_type, inner_value, - selection, + inner_type, inner_ctx, ) - result << GraphQL::Execution::FieldResult.new(type: inner_type, owner: owner, value: inner_result) + inner_ctx.value = inner_result + result << inner_ctx i += 1 end result when GraphQL::TypeKinds::NON_NULL - wrapped_type = field_type.of_type + inner_type = field_type.of_type resolve_value( - owner, - parent_type, - field_defn, - wrapped_type, value, - selection, + inner_type, field_ctx, ) when GraphQL::TypeKinds::OBJECT resolve_selection( value, field_type, - selection, field_ctx ) when GraphQL::TypeKinds::UNION, GraphQL::TypeKinds::INTERFACE query = field_ctx.query resolved_type = field_type.resolve_type(value, field_ctx) possible_types = query.possible_types(field_type) if !possible_types.include?(resolved_type) + parent_type = field_ctx.irep_node.owner_type type_error = GraphQL::UnresolvedTypeError.new(value, field_defn, parent_type, resolved_type, possible_types) field_ctx.schema.type_error(type_error, field_ctx) PROPAGATE_NULL else resolve_value( - owner, - parent_type, - field_defn, - resolved_type, value, - selection, + resolved_type, field_ctx, ) end else raise("Unknown type kind: #{field_type.kind}")