graphql 1.11.4 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +5 -5
  3. data/lib/generators/graphql/object_generator.rb +2 -0
  4. data/lib/generators/graphql/relay_generator.rb +63 -0
  5. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  6. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  7. data/lib/generators/graphql/templates/node_type.erb +9 -0
  8. data/lib/generators/graphql/templates/object.erb +1 -1
  9. data/lib/generators/graphql/templates/query_type.erb +1 -3
  10. data/lib/generators/graphql/templates/schema.erb +8 -35
  11. data/lib/generators/graphql/templates/union.erb +1 -1
  12. data/lib/graphql.rb +55 -4
  13. data/lib/graphql/analysis/analyze_query.rb +7 -0
  14. data/lib/graphql/analysis/ast.rb +11 -2
  15. data/lib/graphql/analysis/ast/visitor.rb +9 -1
  16. data/lib/graphql/argument.rb +3 -3
  17. data/lib/graphql/backtrace.rb +28 -19
  18. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  19. data/lib/graphql/backtrace/table.rb +22 -2
  20. data/lib/graphql/backtrace/tracer.rb +40 -8
  21. data/lib/graphql/backwards_compatibility.rb +1 -0
  22. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  23. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  24. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  25. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  26. data/lib/graphql/dataloader.rb +198 -0
  27. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  28. data/lib/graphql/dataloader/request.rb +24 -0
  29. data/lib/graphql/dataloader/request_all.rb +22 -0
  30. data/lib/graphql/dataloader/source.rb +93 -0
  31. data/lib/graphql/define/assign_global_id_field.rb +2 -2
  32. data/lib/graphql/define/instance_definable.rb +32 -2
  33. data/lib/graphql/define/type_definer.rb +5 -5
  34. data/lib/graphql/deprecated_dsl.rb +5 -0
  35. data/lib/graphql/enum_type.rb +2 -0
  36. data/lib/graphql/execution/errors.rb +4 -0
  37. data/lib/graphql/execution/execute.rb +7 -0
  38. data/lib/graphql/execution/interpreter.rb +20 -6
  39. data/lib/graphql/execution/interpreter/arguments.rb +57 -5
  40. data/lib/graphql/execution/interpreter/arguments_cache.rb +8 -0
  41. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  42. data/lib/graphql/execution/interpreter/runtime.rb +236 -120
  43. data/lib/graphql/execution/multiplex.rb +20 -6
  44. data/lib/graphql/function.rb +4 -0
  45. data/lib/graphql/input_object_type.rb +2 -0
  46. data/lib/graphql/integer_decoding_error.rb +17 -0
  47. data/lib/graphql/interface_type.rb +3 -1
  48. data/lib/graphql/introspection.rb +96 -0
  49. data/lib/graphql/introspection/field_type.rb +7 -3
  50. data/lib/graphql/introspection/input_value_type.rb +6 -0
  51. data/lib/graphql/introspection/introspection_query.rb +6 -92
  52. data/lib/graphql/introspection/type_type.rb +7 -3
  53. data/lib/graphql/invalid_null_error.rb +1 -1
  54. data/lib/graphql/language/block_string.rb +24 -5
  55. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  56. data/lib/graphql/language/lexer.rb +7 -3
  57. data/lib/graphql/language/lexer.rl +7 -3
  58. data/lib/graphql/language/nodes.rb +1 -1
  59. data/lib/graphql/language/parser.rb +107 -103
  60. data/lib/graphql/language/parser.y +4 -0
  61. data/lib/graphql/language/sanitized_printer.rb +59 -26
  62. data/lib/graphql/name_validator.rb +6 -7
  63. data/lib/graphql/object_type.rb +2 -0
  64. data/lib/graphql/pagination/connection.rb +5 -1
  65. data/lib/graphql/pagination/connections.rb +15 -17
  66. data/lib/graphql/query.rb +8 -3
  67. data/lib/graphql/query/context.rb +18 -3
  68. data/lib/graphql/query/serial_execution.rb +1 -0
  69. data/lib/graphql/query/validation_pipeline.rb +1 -1
  70. data/lib/graphql/relay/array_connection.rb +2 -2
  71. data/lib/graphql/relay/base_connection.rb +7 -0
  72. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  73. data/lib/graphql/relay/connection_type.rb +1 -1
  74. data/lib/graphql/relay/mutation.rb +1 -0
  75. data/lib/graphql/relay/node.rb +3 -0
  76. data/lib/graphql/relay/range_add.rb +14 -5
  77. data/lib/graphql/relay/type_extensions.rb +2 -0
  78. data/lib/graphql/scalar_type.rb +2 -0
  79. data/lib/graphql/schema.rb +104 -39
  80. data/lib/graphql/schema/argument.rb +74 -5
  81. data/lib/graphql/schema/build_from_definition.rb +203 -86
  82. data/lib/graphql/schema/default_type_error.rb +2 -0
  83. data/lib/graphql/schema/directive.rb +76 -0
  84. data/lib/graphql/schema/directive/deprecated.rb +1 -1
  85. data/lib/graphql/schema/directive/flagged.rb +57 -0
  86. data/lib/graphql/schema/enum.rb +3 -0
  87. data/lib/graphql/schema/enum_value.rb +12 -6
  88. data/lib/graphql/schema/field.rb +59 -24
  89. data/lib/graphql/schema/field/connection_extension.rb +10 -8
  90. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  91. data/lib/graphql/schema/input_object.rb +38 -25
  92. data/lib/graphql/schema/interface.rb +2 -1
  93. data/lib/graphql/schema/late_bound_type.rb +2 -2
  94. data/lib/graphql/schema/loader.rb +1 -0
  95. data/lib/graphql/schema/member.rb +4 -0
  96. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  97. data/lib/graphql/schema/member/build_type.rb +17 -7
  98. data/lib/graphql/schema/member/has_arguments.rb +70 -51
  99. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  100. data/lib/graphql/schema/member/has_directives.rb +98 -0
  101. data/lib/graphql/schema/member/has_fields.rb +2 -2
  102. data/lib/graphql/schema/member/has_validators.rb +31 -0
  103. data/lib/graphql/schema/member/type_system_helpers.rb +3 -3
  104. data/lib/graphql/schema/object.rb +11 -0
  105. data/lib/graphql/schema/printer.rb +5 -4
  106. data/lib/graphql/schema/relay_classic_mutation.rb +4 -2
  107. data/lib/graphql/schema/resolver.rb +7 -0
  108. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  109. data/lib/graphql/schema/subscription.rb +19 -1
  110. data/lib/graphql/schema/timeout.rb +29 -15
  111. data/lib/graphql/schema/timeout_middleware.rb +2 -0
  112. data/lib/graphql/schema/unique_within_type.rb +1 -2
  113. data/lib/graphql/schema/validation.rb +10 -0
  114. data/lib/graphql/schema/validator.rb +163 -0
  115. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  116. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  117. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  118. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  119. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  120. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  121. data/lib/graphql/static_validation.rb +1 -0
  122. data/lib/graphql/static_validation/all_rules.rb +1 -0
  123. data/lib/graphql/static_validation/rules/fields_will_merge.rb +25 -17
  124. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  125. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  126. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  127. data/lib/graphql/static_validation/validator.rb +33 -7
  128. data/lib/graphql/subscriptions.rb +18 -23
  129. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +21 -7
  130. data/lib/graphql/tracing.rb +2 -2
  131. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  132. data/lib/graphql/tracing/platform_tracing.rb +4 -2
  133. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  134. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  135. data/lib/graphql/types/int.rb +9 -2
  136. data/lib/graphql/types/relay.rb +11 -3
  137. data/lib/graphql/types/relay/base_connection.rb +2 -91
  138. data/lib/graphql/types/relay/base_edge.rb +2 -34
  139. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  140. data/lib/graphql/types/relay/default_relay.rb +27 -0
  141. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  142. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  143. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  144. data/lib/graphql/types/relay/node.rb +2 -4
  145. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  146. data/lib/graphql/types/relay/node_field.rb +1 -19
  147. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  148. data/lib/graphql/types/relay/page_info.rb +2 -14
  149. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  150. data/lib/graphql/types/string.rb +7 -1
  151. data/lib/graphql/unauthorized_error.rb +1 -1
  152. data/lib/graphql/union_type.rb +2 -0
  153. data/lib/graphql/upgrader/member.rb +1 -0
  154. data/lib/graphql/upgrader/schema.rb +1 -0
  155. data/lib/graphql/version.rb +1 -1
  156. data/readme.md +1 -1
  157. metadata +49 -6
  158. data/lib/graphql/types/relay/base_field.rb +0 -22
  159. data/lib/graphql/types/relay/base_interface.rb +0 -29
  160. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -13,13 +13,6 @@ module GraphQL
13
13
  @object
14
14
  end
15
15
  end
16
-
17
- # Allows to return "raw" value from the resolver
18
- module HandlesRawValue
19
- def raw_value(obj)
20
- RawValue.new(obj)
21
- end
22
- end
23
16
  end
24
17
  end
25
18
  end
@@ -19,8 +19,10 @@ module GraphQL
19
19
 
20
20
  def initialize(query:, response:)
21
21
  @query = query
22
+ @dataloader = query.multiplex.dataloader
22
23
  @schema = query.schema
23
24
  @context = query.context
25
+ @multiplex_context = query.multiplex.context
24
26
  @interpreter_context = @context.namespace(:interpreter)
25
27
  @response = response
26
28
  @dead_paths = {}
@@ -43,26 +45,63 @@ module GraphQL
43
45
  # might be stored up in lazies.
44
46
  # @return [void]
45
47
  def run_eager
46
-
47
48
  root_operation = query.selected_operation
48
49
  root_op_type = root_operation.operation_type || "query"
49
50
  root_type = schema.root_type_for_operation(root_op_type)
50
51
  path = []
51
- @interpreter_context[:current_object] = query.root_value
52
- @interpreter_context[:current_path] = path
52
+ set_all_interpreter_context(query.root_value, nil, nil, path)
53
53
  object_proxy = authorized_new(root_type, query.root_value, context, path)
54
54
  object_proxy = schema.sync_lazy(object_proxy)
55
55
  if object_proxy.nil?
56
56
  # Root .authorized? returned false.
57
57
  write_in_response(path, nil)
58
- nil
59
58
  else
60
- evaluate_selections(path, context.scoped_context, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
61
- nil
59
+ # Prepare this runtime state to be encapsulated in a Fiber
60
+ @progress_path = path
61
+ @progress_scoped_context = context.scoped_context
62
+ @progress_object = object_proxy
63
+ @progress_object_type = root_type
64
+ @progress_index = nil
65
+ @progress_is_eager_selection = root_op_type == "mutation"
66
+ @progress_selections = gather_selections(object_proxy, root_type, root_operation.selections)
67
+
68
+ # Make the first fiber which will begin execution
69
+ enqueue_selections_fiber
62
70
  end
71
+ delete_interpreter_context(:current_path)
72
+ delete_interpreter_context(:current_field)
73
+ delete_interpreter_context(:current_object)
74
+ delete_interpreter_context(:current_arguments)
75
+ nil
76
+ end
77
+
78
+ # Use `@dataloader` to enqueue a fiber that will pick up from the current point.
79
+ # @return [void]
80
+ def enqueue_selections_fiber
81
+ # Read these into local variables so that later assignments don't affect the block below.
82
+ path = @progress_path
83
+ scoped_context = @progress_scoped_context
84
+ owner_object = @progress_object
85
+ owner_type = @progress_object_type
86
+ idx = @progress_index
87
+ is_eager_selection = @progress_is_eager_selection
88
+ gathered_selections = @progress_selections
89
+
90
+ @dataloader.enqueue {
91
+ evaluate_selections(
92
+ path,
93
+ scoped_context,
94
+ owner_object,
95
+ owner_type,
96
+ is_eager_selection: is_eager_selection,
97
+ after: idx,
98
+ gathered_selections: gathered_selections,
99
+ )
100
+ }
101
+ nil
63
102
  end
64
103
 
65
- def gather_selections(owner_object, owner_type, selections, selections_by_name)
104
+ def gather_selections(owner_object, owner_type, selections, selections_by_name = {})
66
105
  selections.each do |node|
67
106
  # Skip gathering this if the directive says so
68
107
  if !directives_include?(node, owner_object, owner_type)
@@ -114,139 +153,186 @@ module GraphQL
114
153
  raise "Invariant: unexpected selection class: #{node.class}"
115
154
  end
116
155
  end
156
+ selections_by_name
117
157
  end
118
158
 
119
- def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil)
120
- @interpreter_context[:current_object] = owner_object
121
- @interpreter_context[:current_path] = path
122
- selections_by_name = {}
123
- gather_selections(owner_object, owner_type, selections, selections_by_name)
124
- selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
125
- # As a performance optimization, the hash key will be a `Node` if
126
- # there's only one selection of the field. But if there are multiple
127
- # selections of the field, it will be an Array of nodes
128
- if field_ast_nodes_or_ast_node.is_a?(Array)
129
- field_ast_nodes = field_ast_nodes_or_ast_node
130
- ast_node = field_ast_nodes.first
131
- else
132
- field_ast_nodes = nil
133
- ast_node = field_ast_nodes_or_ast_node
159
+ NO_ARGS = {}.freeze
160
+
161
+ # @return [void]
162
+ def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection:, gathered_selections:, after:)
163
+ set_all_interpreter_context(owner_object, nil, nil, path)
164
+
165
+ @progress_path = path
166
+ @progress_scoped_context = scoped_context
167
+ @progress_object = owner_object
168
+ @progress_object_type = owner_type
169
+ @progress_index = nil
170
+ @progress_is_eager_selection = is_eager_selection
171
+ @progress_selections = gathered_selections
172
+
173
+ # Track `idx` manually to avoid an allocation on this hot path
174
+ idx = 0
175
+ gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
176
+ prev_idx = idx
177
+ idx += 1
178
+ # TODO: this is how a `progress` resumes where this left off.
179
+ # Is there a better way to seek in the hash?
180
+ # I think we could also use the array of keys; it supports seeking just fine.
181
+ if after && prev_idx <= after
182
+ next
134
183
  end
135
- field_name = ast_node.name
136
- field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
137
- is_introspection = false
138
- if field_defn.nil?
139
- field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
140
- is_introspection = true
141
- entry_point_field
142
- elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
143
- is_introspection = true
144
- dynamic_field
145
- else
146
- raise "Invariant: no field for #{owner_type}.#{field_name}"
147
- end
184
+ @progress_index = prev_idx
185
+ # This is how the current runtime gives itself to `dataloader`
186
+ # so that the dataloader can enqueue another fiber to resume if needed.
187
+ @dataloader.current_runtime = self
188
+ evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection)
189
+ # The dataloader knows if ^^ that selection halted and later selections were executed in another fiber.
190
+ # If that's the case, then don't continue execution here.
191
+ if @dataloader.yielded?(path)
192
+ break
148
193
  end
149
- return_type = field_defn.type
194
+ end
195
+ nil
196
+ end
150
197
 
151
- next_path = path.dup
152
- next_path << result_name
153
- next_path.freeze
198
+ attr_reader :progress_path
154
199
 
155
- # This seems janky, but we need to know
156
- # the field's return type at this path in order
157
- # to propagate `null`
158
- set_type_at_path(next_path, return_type)
159
- # Set this before calling `run_with_directives`, so that the directive can have the latest path
160
- @interpreter_context[:current_path] = next_path
161
- @interpreter_context[:current_field] = field_defn
200
+ # @return [void]
201
+ def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field)
202
+ # As a performance optimization, the hash key will be a `Node` if
203
+ # there's only one selection of the field. But if there are multiple
204
+ # selections of the field, it will be an Array of nodes
205
+ if field_ast_nodes_or_ast_node.is_a?(Array)
206
+ field_ast_nodes = field_ast_nodes_or_ast_node
207
+ ast_node = field_ast_nodes.first
208
+ else
209
+ field_ast_nodes = nil
210
+ ast_node = field_ast_nodes_or_ast_node
211
+ end
212
+ field_name = ast_node.name
213
+ field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
214
+ is_introspection = false
215
+ if field_defn.nil?
216
+ field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
217
+ is_introspection = true
218
+ entry_point_field
219
+ elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
220
+ is_introspection = true
221
+ dynamic_field
222
+ else
223
+ raise "Invariant: no field for #{owner_type}.#{field_name}"
224
+ end
225
+ end
226
+ return_type = field_defn.type
162
227
 
163
- context.scoped_context = scoped_context
164
- object = owner_object
228
+ next_path = path.dup
229
+ next_path << result_name
230
+ next_path.freeze
165
231
 
166
- if is_introspection
167
- object = authorized_new(field_defn.owner, object, context, next_path)
168
- end
232
+ # This seems janky, but we need to know
233
+ # the field's return type at this path in order
234
+ # to propagate `null`
235
+ set_type_at_path(next_path, return_type)
236
+ # Set this before calling `run_with_directives`, so that the directive can have the latest path
237
+ set_all_interpreter_context(nil, field_defn, nil, next_path)
169
238
 
170
- begin
171
- kwarg_arguments = arguments(object, field_defn, ast_node)
172
- rescue GraphQL::ExecutionError => e
173
- continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node)
174
- next
175
- end
239
+ context.scoped_context = scoped_context
240
+ object = owner_object
176
241
 
177
- after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments|
178
- if resolved_arguments.is_a? GraphQL::ExecutionError
179
- continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
180
- next
181
- end
242
+ if is_introspection
243
+ object = authorized_new(field_defn.owner, object, context, next_path)
244
+ end
245
+
246
+ begin
247
+ kwarg_arguments = arguments(object, field_defn, ast_node)
248
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => e
249
+ continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node)
250
+ return
251
+ end
182
252
 
183
- kwarg_arguments = resolved_arguments.keyword_arguments
253
+ after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments|
254
+ if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
255
+ continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
256
+ next
257
+ end
184
258
 
259
+ kwarg_arguments = if resolved_arguments.empty? && field_defn.extras.empty?
260
+ # We can avoid allocating the `{ Symbol => Object }` hash in this case
261
+ NO_ARGS
262
+ else
263
+ # Bundle up the extras, then make a new arguments instance
264
+ # that includes the extras, too.
265
+ extra_args = {}
185
266
  field_defn.extras.each do |extra|
186
267
  case extra
187
268
  when :ast_node
188
- kwarg_arguments[:ast_node] = ast_node
269
+ extra_args[:ast_node] = ast_node
189
270
  when :execution_errors
190
- kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
271
+ extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
191
272
  when :path
192
- kwarg_arguments[:path] = next_path
273
+ extra_args[:path] = next_path
193
274
  when :lookahead
194
275
  if !field_ast_nodes
195
276
  field_ast_nodes = [ast_node]
196
277
  end
197
- kwarg_arguments[:lookahead] = Execution::Lookahead.new(
278
+
279
+ extra_args[:lookahead] = Execution::Lookahead.new(
198
280
  query: query,
199
281
  ast_nodes: field_ast_nodes,
200
282
  field: field_defn,
201
283
  )
202
284
  when :argument_details
203
- kwarg_arguments[:argument_details] = resolved_arguments
285
+ # Use this flag to tell Interpreter::Arguments to add itself
286
+ # to the keyword args hash _before_ freezing everything.
287
+ extra_args[:argument_details] = :__arguments_add_self
204
288
  else
205
- kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
289
+ extra_args[extra] = field_defn.fetch_extra(extra, context)
206
290
  end
207
291
  end
292
+ resolved_arguments = resolved_arguments.merge_extras(extra_args)
293
+ resolved_arguments.keyword_arguments
294
+ end
208
295
 
209
- @interpreter_context[:current_arguments] = kwarg_arguments
296
+ set_all_interpreter_context(nil, nil, kwarg_arguments, nil)
210
297
 
211
- # Optimize for the case that field is selected only once
212
- if field_ast_nodes.nil? || field_ast_nodes.size == 1
213
- next_selections = ast_node.selections
214
- else
215
- next_selections = []
216
- field_ast_nodes.each { |f| next_selections.concat(f.selections) }
217
- end
298
+ # Optimize for the case that field is selected only once
299
+ if field_ast_nodes.nil? || field_ast_nodes.size == 1
300
+ next_selections = ast_node.selections
301
+ else
302
+ next_selections = []
303
+ field_ast_nodes.each { |f| next_selections.concat(f.selections) }
304
+ end
218
305
 
219
- field_result = resolve_with_directives(object, ast_node) do
220
- # Actually call the field resolver and capture the result
221
- app_result = begin
222
- query.with_error_handling do
223
- query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
224
- field_defn.resolve(object, kwarg_arguments, context)
225
- end
306
+ field_result = resolve_with_directives(object, ast_node) do
307
+ # Actually call the field resolver and capture the result
308
+ app_result = begin
309
+ query.with_error_handling do
310
+ query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments}) do
311
+ field_defn.resolve(object, kwarg_arguments, context)
226
312
  end
227
- rescue GraphQL::ExecutionError => err
228
- err
229
313
  end
230
- after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
231
- continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
232
- if RawValue === continue_value
233
- # Write raw value directly to the response without resolving nested objects
234
- write_in_response(next_path, continue_value.resolve)
235
- elsif HALT != continue_value
236
- continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
237
- end
314
+ rescue GraphQL::ExecutionError => err
315
+ err
316
+ end
317
+ after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
318
+ continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
319
+ if RawValue === continue_value
320
+ # Write raw value directly to the response without resolving nested objects
321
+ write_in_response(next_path, continue_value.resolve)
322
+ elsif HALT != continue_value
323
+ continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
238
324
  end
239
325
  end
326
+ end
240
327
 
241
- # If this field is a root mutation field, immediately resolve
242
- # all of its child fields before moving on to the next root mutation field.
243
- # (Subselections of this mutation will still be resolved level-by-level.)
244
- if root_operation_type == "mutation"
245
- Interpreter::Resolve.resolve_all([field_result])
246
- else
247
- field_result
248
- end
328
+ # If this field is a root mutation field, immediately resolve
329
+ # all of its child fields before moving on to the next root mutation field.
330
+ # (Subselections of this mutation will still be resolved level-by-level.)
331
+ if is_eager_field
332
+ Interpreter::Resolve.resolve_all([field_result])
249
333
  end
334
+
335
+ nil
250
336
  end
251
337
  end
252
338
 
@@ -307,7 +393,7 @@ module GraphQL
307
393
  resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
308
394
  resolved_value ||= value
309
395
 
310
- after_lazy(resolved_type_or_lazy, owner: current_type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
396
+ after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
311
397
  possible_types = query.possible_types(current_type)
312
398
 
313
399
  if !possible_types.include?(resolved_type)
@@ -327,12 +413,13 @@ module GraphQL
327
413
  rescue GraphQL::ExecutionError => err
328
414
  err
329
415
  end
330
- after_lazy(object_proxy, owner: current_type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object|
416
+ after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object|
331
417
  continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node)
332
418
  if HALT != continue_value
333
419
  response_hash = {}
334
420
  write_in_response(path, response_hash)
335
- evaluate_selections(path, context.scoped_context, continue_value, current_type, next_selections)
421
+ gathered_selections = gather_selections(continue_value, current_type, next_selections)
422
+ evaluate_selections(path, context.scoped_context, continue_value, current_type, is_eager_selection: false, gathered_selections: gathered_selections, after: nil)
336
423
  response_hash
337
424
  end
338
425
  end
@@ -350,7 +437,7 @@ module GraphQL
350
437
  idx += 1
351
438
  set_type_at_path(next_path, inner_type)
352
439
  # This will update `response_list` with the lazy
353
- after_lazy(inner_value, owner: inner_type, path: next_path, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
440
+ after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
354
441
  continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node)
355
442
  if HALT != continue_value
356
443
  continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
@@ -412,30 +499,39 @@ module GraphQL
412
499
  true
413
500
  end
414
501
 
502
+ def set_all_interpreter_context(object, field, arguments, path)
503
+ if object
504
+ @context[:current_object] = @interpreter_context[:current_object] = object
505
+ end
506
+ if field
507
+ @context[:current_field] = @interpreter_context[:current_field] = field
508
+ end
509
+ if arguments
510
+ @context[:current_arguments] = @interpreter_context[:current_arguments] = arguments
511
+ end
512
+ if path
513
+ @context[:current_path] = @interpreter_context[:current_path] = path
514
+ end
515
+ end
516
+
415
517
  # @param obj [Object] Some user-returned value that may want to be batched
416
518
  # @param path [Array<String>]
417
519
  # @param field [GraphQL::Schema::Field]
418
520
  # @param eager [Boolean] Set to `true` for mutation root fields only
419
521
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
420
522
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
421
- def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false, trace: true, &block)
422
- @interpreter_context[:current_object] = owner_object
423
- @interpreter_context[:current_arguments] = arguments
424
- @interpreter_context[:current_path] = path
425
- @interpreter_context[:current_field] = field
523
+ def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, eager: false, trace: true, &block)
524
+ set_all_interpreter_context(owner_object, field, arguments, path)
426
525
  if schema.lazy?(lazy_obj)
427
526
  lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
428
- @interpreter_context[:current_path] = path
429
- @interpreter_context[:current_field] = field
430
- @interpreter_context[:current_object] = owner_object
431
- @interpreter_context[:current_arguments] = arguments
527
+ set_all_interpreter_context(owner_object, field, arguments, path)
432
528
  context.scoped_context = scoped_context
433
529
  # Wrap the execution of _this_ method with tracing,
434
530
  # but don't wrap the continuation below
435
531
  inner_obj = begin
436
532
  query.with_error_handling do
437
533
  if trace
438
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
534
+ query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
439
535
  schema.sync_lazy(lazy_obj)
440
536
  end
441
537
  else
@@ -445,7 +541,7 @@ module GraphQL
445
541
  rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
446
542
  err
447
543
  end
448
- after_lazy(inner_obj, owner: owner, field: field, path: path, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager, trace: trace, &block)
544
+ after_lazy(inner_obj, owner: owner, field: field, path: path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager, trace: trace, &block)
449
545
  end
450
546
 
451
547
  if eager
@@ -460,9 +556,7 @@ module GraphQL
460
556
  end
461
557
 
462
558
  def arguments(graphql_object, arg_owner, ast_node)
463
- # Don't cache arguments if field extras or extensions are requested since they can mutate the argument data structure
464
- if arg_owner.arguments_statically_coercible? &&
465
- (!arg_owner.is_a?(GraphQL::Schema::Field) || (arg_owner.extras.empty? && arg_owner.extensions.empty?))
559
+ if arg_owner.arguments_statically_coercible?
466
560
  query.arguments_for(ast_node, arg_owner)
467
561
  else
468
562
  # The arguments must be prepared in the context of the given object
@@ -503,6 +597,16 @@ module GraphQL
503
597
  end
504
598
  end
505
599
 
600
+ def value_at(path)
601
+ i = 0
602
+ value = @response.final_value
603
+ while value && (part = path[i])
604
+ value = value[part]
605
+ i += 1
606
+ end
607
+ value
608
+ end
609
+
506
610
  # To propagate nulls, we have to know what the field type was
507
611
  # at previous parts of the response.
508
612
  # This hash matches the response
@@ -539,6 +643,18 @@ module GraphQL
539
643
  res && res[:__dead]
540
644
  end
541
645
 
646
+ # Set this pair in the Query context, but also in the interpeter namespace,
647
+ # for compatibility.
648
+ def set_interpreter_context(key, value)
649
+ @interpreter_context[key] = value
650
+ @context[key] = value
651
+ end
652
+
653
+ def delete_interpreter_context(key)
654
+ @interpreter_context.delete(key)
655
+ @context.delete(key)
656
+ end
657
+
542
658
  def resolve_type(type, value, path)
543
659
  trace_payload = { context: context, type: type, object: value, path: path }
544
660
  resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do