graphql 2.1.0 → 2.1.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  3. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  4. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  5. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  6. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  7. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  8. data/lib/generators/graphql/templates/base_field.erb +2 -0
  9. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  10. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  11. data/lib/generators/graphql/templates/base_object.erb +2 -0
  12. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  13. data/lib/generators/graphql/templates/base_union.erb +2 -0
  14. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  15. data/lib/generators/graphql/templates/loader.erb +2 -0
  16. data/lib/generators/graphql/templates/mutation.erb +2 -0
  17. data/lib/generators/graphql/templates/node_type.erb +2 -0
  18. data/lib/generators/graphql/templates/query_type.erb +2 -0
  19. data/lib/generators/graphql/templates/schema.erb +2 -0
  20. data/lib/graphql/analysis/ast/analyzer.rb +7 -0
  21. data/lib/graphql/analysis/ast/visitor.rb +2 -2
  22. data/lib/graphql/analysis/ast.rb +15 -11
  23. data/lib/graphql/dataloader/source.rb +7 -0
  24. data/lib/graphql/dataloader.rb +9 -0
  25. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +170 -0
  26. data/lib/graphql/execution/interpreter/runtime.rb +90 -251
  27. data/lib/graphql/execution/interpreter.rb +0 -6
  28. data/lib/graphql/execution/lookahead.rb +1 -1
  29. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  30. data/lib/graphql/introspection/entry_points.rb +2 -2
  31. data/lib/graphql/language/block_string.rb +28 -16
  32. data/lib/graphql/language/definition_slice.rb +1 -1
  33. data/lib/graphql/language/document_from_schema_definition.rb +30 -19
  34. data/lib/graphql/language/nodes.rb +1 -1
  35. data/lib/graphql/language/printer.rb +88 -27
  36. data/lib/graphql/language/sanitized_printer.rb +6 -1
  37. data/lib/graphql/language/static_visitor.rb +167 -0
  38. data/lib/graphql/language/visitor.rb +2 -0
  39. data/lib/graphql/language.rb +1 -0
  40. data/lib/graphql/query/context/scoped_context.rb +101 -0
  41. data/lib/graphql/query/context.rb +32 -98
  42. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  43. data/lib/graphql/schema/field/connection_extension.rb +1 -15
  44. data/lib/graphql/schema/field.rb +6 -3
  45. data/lib/graphql/schema/has_single_input_argument.rb +156 -0
  46. data/lib/graphql/schema/introspection_system.rb +2 -0
  47. data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
  48. data/lib/graphql/schema/member/has_arguments.rb +14 -2
  49. data/lib/graphql/schema/member/has_fields.rb +4 -1
  50. data/lib/graphql/schema/member/has_interfaces.rb +21 -7
  51. data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
  52. data/lib/graphql/schema/resolver.rb +4 -0
  53. data/lib/graphql/schema/scalar.rb +3 -3
  54. data/lib/graphql/schema/warden.rb +20 -3
  55. data/lib/graphql/schema.rb +19 -2
  56. data/lib/graphql/static_validation/all_rules.rb +1 -1
  57. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  58. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  59. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  60. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  61. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  62. data/lib/graphql/static_validation/validation_context.rb +5 -2
  63. data/lib/graphql/tracing/appoptics_trace.rb +2 -2
  64. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  65. data/lib/graphql/version.rb +1 -1
  66. data/lib/graphql.rb +1 -1
  67. metadata +7 -2
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require "graphql/execution/interpreter/runtime/graphql_result"
2
3
 
3
4
  module GraphQL
4
5
  module Execution
@@ -22,167 +23,6 @@ module GraphQL
22
23
  :current_arguments, :current_field, :current_object, :was_authorized_by_scope_items
23
24
  end
24
25
 
25
- module GraphQLResult
26
- def initialize(result_name, parent_result, is_non_null_in_parent)
27
- @graphql_parent = parent_result
28
- if parent_result && parent_result.graphql_dead
29
- @graphql_dead = true
30
- end
31
- @graphql_result_name = result_name
32
- @graphql_is_non_null_in_parent = is_non_null_in_parent
33
- # Jump through some hoops to avoid creating this duplicate storage if at all possible.
34
- @graphql_metadata = nil
35
- end
36
-
37
- def path
38
- @path ||= build_path([])
39
- end
40
-
41
- def build_path(path_array)
42
- graphql_result_name && path_array.unshift(graphql_result_name)
43
- @graphql_parent ? @graphql_parent.build_path(path_array) : path_array
44
- end
45
-
46
- attr_accessor :graphql_dead
47
- attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent
48
-
49
- # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
50
- attr_accessor :graphql_result_data
51
- end
52
-
53
- class GraphQLResultHash
54
- def initialize(_result_name, _parent_result, _is_non_null_in_parent)
55
- super
56
- @graphql_result_data = {}
57
- end
58
-
59
- include GraphQLResult
60
-
61
- attr_accessor :graphql_merged_into
62
-
63
- def set_leaf(key, value)
64
- # This is a hack.
65
- # Basically, this object is merged into the root-level result at some point.
66
- # But the problem is, some lazies are created whose closures retain reference to _this_
67
- # object. When those lazies are resolved, they cause an update to this object.
68
- #
69
- # In order to return a proper top-level result, we have to update that top-level result object.
70
- # In order to return a proper partial result (eg, for a directive), we have to update this object, too.
71
- # Yowza.
72
- if (t = @graphql_merged_into)
73
- t.set_leaf(key, value)
74
- end
75
-
76
- @graphql_result_data[key] = value
77
- # keep this up-to-date if it's been initialized
78
- @graphql_metadata && @graphql_metadata[key] = value
79
-
80
- value
81
- end
82
-
83
- def set_child_result(key, value)
84
- if (t = @graphql_merged_into)
85
- t.set_child_result(key, value)
86
- end
87
- @graphql_result_data[key] = value.graphql_result_data
88
- # If we encounter some part of this response that requires metadata tracking,
89
- # then create the metadata hash if necessary. It will be kept up-to-date after this.
90
- (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
91
- value
92
- end
93
-
94
- def delete(key)
95
- @graphql_metadata && @graphql_metadata.delete(key)
96
- @graphql_result_data.delete(key)
97
- end
98
-
99
- def each
100
- (@graphql_metadata || @graphql_result_data).each { |k, v| yield(k, v) }
101
- end
102
-
103
- def values
104
- (@graphql_metadata || @graphql_result_data).values
105
- end
106
-
107
- def key?(k)
108
- @graphql_result_data.key?(k)
109
- end
110
-
111
- def [](k)
112
- (@graphql_metadata || @graphql_result_data)[k]
113
- end
114
-
115
- def merge_into(into_result)
116
- self.each do |key, value|
117
- case value
118
- when GraphQLResultHash
119
- next_into = into_result[key]
120
- if next_into
121
- value.merge_into(next_into)
122
- else
123
- into_result.set_child_result(key, value)
124
- end
125
- when GraphQLResultArray
126
- # There's no special handling of arrays because currently, there's no way to split the execution
127
- # of a list over several concurrent flows.
128
- next_result.set_child_result(key, value)
129
- else
130
- # We have to assume that, since this passed the `fields_will_merge` selection,
131
- # that the old and new values are the same.
132
- into_result.set_leaf(key, value)
133
- end
134
- end
135
- @graphql_merged_into = into_result
136
- end
137
- end
138
-
139
- class GraphQLResultArray
140
- include GraphQLResult
141
-
142
- def initialize(_result_name, _parent_result, _is_non_null_in_parent)
143
- super
144
- @graphql_result_data = []
145
- end
146
-
147
- def graphql_skip_at(index)
148
- # Mark this index as dead. It's tricky because some indices may already be storing
149
- # `Lazy`s. So the runtime is still holding indexes _before_ skipping,
150
- # this object has to coordinate incoming writes to account for any already-skipped indices.
151
- @skip_indices ||= []
152
- @skip_indices << index
153
- offset_by = @skip_indices.count { |skipped_idx| skipped_idx < index}
154
- delete_at_index = index - offset_by
155
- @graphql_metadata && @graphql_metadata.delete_at(delete_at_index)
156
- @graphql_result_data.delete_at(delete_at_index)
157
- end
158
-
159
- def set_leaf(idx, value)
160
- if @skip_indices
161
- offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
162
- idx -= offset_by
163
- end
164
- @graphql_result_data[idx] = value
165
- @graphql_metadata && @graphql_metadata[idx] = value
166
- value
167
- end
168
-
169
- def set_child_result(idx, value)
170
- if @skip_indices
171
- offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
172
- idx -= offset_by
173
- end
174
- @graphql_result_data[idx] = value.graphql_result_data
175
- # If we encounter some part of this response that requires metadata tracking,
176
- # then create the metadata hash if necessary. It will be kept up-to-date after this.
177
- (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
178
- value
179
- end
180
-
181
- def values
182
- (@graphql_metadata || @graphql_result_data)
183
- end
184
- end
185
-
186
26
  # @return [GraphQL::Query]
187
27
  attr_reader :query
188
28
 
@@ -209,15 +49,16 @@ module GraphQL
209
49
  @runtime_directive_names << name
210
50
  end
211
51
  end
212
- # A cache of { Class => { String => Schema::Field } }
213
- # Which assumes that MyObject.get_field("myField") will return the same field
214
- # during the lifetime of a query
215
- @fields_cache = Hash.new { |h, k| h[k] = {} }
216
- # this can by by-identity since owners are the same object, but not the sub-hash, which uses strings.
217
- @fields_cache.compare_by_identity
218
52
  # { Class => Boolean }
219
53
  @lazy_cache = {}
220
54
  @lazy_cache.compare_by_identity
55
+
56
+ @gathered_selections_cache = Hash.new { |h, k|
57
+ cache = {}
58
+ cache.compare_by_identity
59
+ h[k] = cache
60
+ }
61
+ @gathered_selections_cache.compare_by_identity
221
62
  end
222
63
 
223
64
  def final_result
@@ -257,7 +98,7 @@ module GraphQL
257
98
  @response = nil
258
99
  else
259
100
  call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
260
- gathered_selections = gather_selections(runtime_object, root_type, root_operation.selections)
101
+ gathered_selections = gather_selections(runtime_object, root_type, nil, root_operation.selections)
261
102
  # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
262
103
  # require isolation during execution (because of runtime directives). In that case,
263
104
  # make a new, isolated result hash for writing the result into. (That isolated response
@@ -277,6 +118,7 @@ module GraphQL
277
118
  @dataloader.append_job {
278
119
  st = get_current_runtime_state
279
120
  st.current_object = query.root_value
121
+ st.current_result_name = nil
280
122
  st.current_result = selection_response
281
123
  # This is a less-frequent case; use a fast check since it's often not there.
282
124
  if (directives = selections[:graphql_directives])
@@ -291,20 +133,28 @@ module GraphQL
291
133
  selection_response,
292
134
  final_response,
293
135
  nil,
136
+ st,
294
137
  )
295
138
  end
296
139
  }
297
140
  end
298
141
  end
299
142
  end
300
- delete_all_interpreter_context
301
143
  nil
302
144
  end
303
145
 
304
- def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
146
+ def gather_selections(owner_object, owner_type, ast_node_for_caching, selections, selections_to_run = nil, selections_by_name = nil)
147
+ if ast_node_for_caching && (cached_selections = @gathered_selections_cache[ast_node_for_caching][owner_type])
148
+ return cached_selections
149
+ end
150
+ selections_by_name ||= {} # allocate this default here so we check the cache first
151
+
152
+ should_cache = true
153
+
305
154
  selections.each do |node|
306
155
  # Skip gathering this if the directive says so
307
156
  if !directives_include?(node, owner_object, owner_type)
157
+ should_cache = false
308
158
  next
309
159
  end
310
160
 
@@ -329,6 +179,7 @@ module GraphQL
329
179
  if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
330
180
  next_selections = {}
331
181
  next_selections[:graphql_directives] = node.directives
182
+ should_cache = false
332
183
  if selections_to_run
333
184
  selections_to_run << next_selections
334
185
  else
@@ -346,41 +197,41 @@ module GraphQL
346
197
  type_defn = schema.get_type(node.type.name, context)
347
198
 
348
199
  if query.warden.possible_types(type_defn).include?(owner_type)
349
- gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
200
+ gather_selections(owner_object, owner_type, nil, node.selections, selections_to_run, next_selections)
350
201
  end
351
202
  else
352
203
  # it's an untyped fragment, definitely continue
353
- gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
204
+ gather_selections(owner_object, owner_type, nil, node.selections, selections_to_run, next_selections)
354
205
  end
355
206
  when GraphQL::Language::Nodes::FragmentSpread
356
207
  fragment_def = query.fragments[node.name]
357
208
  type_defn = query.get_type(fragment_def.type.name)
358
209
  if query.warden.possible_types(type_defn).include?(owner_type)
359
- gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
210
+ gather_selections(owner_object, owner_type, nil, fragment_def.selections, selections_to_run, next_selections)
360
211
  end
361
212
  else
362
213
  raise "Invariant: unexpected selection class: #{node.class}"
363
214
  end
364
215
  end
365
216
  end
366
- selections_to_run || selections_by_name
217
+ result = selections_to_run || selections_by_name
218
+ if should_cache
219
+ @gathered_selections_cache[ast_node_for_caching][owner_type] = result
220
+ end
221
+ result
367
222
  end
368
223
 
369
224
  NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
370
225
 
371
226
  # @return [void]
372
- def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
373
- st = get_current_runtime_state
374
- st.current_object = owner_object
375
- st.current_result_name = nil
376
- st.current_result = selections_result
377
-
227
+ def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object, runtime_state) # rubocop:disable Metrics/ParameterLists
378
228
  finished_jobs = 0
379
229
  enqueued_jobs = gathered_selections.size
380
230
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
381
231
  @dataloader.append_job {
232
+ runtime_state = get_current_runtime_state
382
233
  evaluate_selection(
383
- result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
234
+ result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object, runtime_state
384
235
  )
385
236
  finished_jobs += 1
386
237
  if target_result && finished_jobs == enqueued_jobs
@@ -391,7 +242,9 @@ module GraphQL
391
242
  # so it wouldn't get to the `Resolve` call that happens below.
392
243
  # So instead trigger a run from this outer context.
393
244
  if is_eager_selection
245
+ @dataloader.clear_cache
394
246
  @dataloader.run
247
+ @dataloader.clear_cache
395
248
  end
396
249
  end
397
250
 
@@ -399,7 +252,7 @@ module GraphQL
399
252
  end
400
253
 
401
254
  # @return [void]
402
- def evaluate_selection(result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
255
+ def evaluate_selection(result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_field, selections_result, parent_object, runtime_state) # rubocop:disable Metrics/ParameterLists
403
256
  return if dead_result?(selections_result)
404
257
  # As a performance optimization, the hash key will be a `Node` if
405
258
  # there's only one selection of the field. But if there are multiple
@@ -412,51 +265,37 @@ module GraphQL
412
265
  ast_node = field_ast_nodes_or_ast_node
413
266
  end
414
267
  field_name = ast_node.name
415
- # This can't use `query.get_field` because it gets confused on introspection below if `field_defn` isn't `nil`,
416
- # because of how `is_introspection` is used to call `.authorized_new` later on.
417
- field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name, @context)
418
- is_introspection = false
419
- if field_defn.nil?
420
- field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
421
- is_introspection = true
422
- entry_point_field
423
- elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
424
- is_introspection = true
425
- dynamic_field
426
- else
427
- raise "Invariant: no field for #{owner_type}.#{field_name}"
428
- end
429
- end
268
+ field_defn = query.warden.get_field(owner_type, field_name)
430
269
 
431
270
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
432
- st = get_current_runtime_state
433
- st.current_field = field_defn
434
- st.current_result = selections_result
435
- st.current_result_name = result_name
271
+ runtime_state.current_field = field_defn
272
+ runtime_state.current_result = selections_result
273
+ runtime_state.current_result_name = result_name
436
274
 
437
- if is_introspection
275
+ if field_defn.dynamic_introspection
438
276
  owner_object = field_defn.owner.wrap(owner_object, context)
439
277
  end
278
+
440
279
  return_type = field_defn.type
441
- total_args_count = field_defn.arguments(context).size
442
- if total_args_count == 0
280
+ if !field_defn.any_arguments?
443
281
  resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
444
282
  if field_defn.extras.size == 0
445
283
  evaluate_selection_with_resolved_keyword_args(
446
- NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type.non_null?
284
+ NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type.non_null?, runtime_state
447
285
  )
448
286
  else
449
- evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type)
287
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, runtime_state)
450
288
  end
451
289
  else
452
290
  @query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
453
- evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type)
291
+ runtime_state = get_current_runtime_state # This might be in a different fiber
292
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, runtime_state)
454
293
  end
455
294
  end
456
295
  end
457
296
 
458
- def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type) # rubocop:disable Metrics/ParameterLists
459
- after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
297
+ def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, runtime_state) # rubocop:disable Metrics/ParameterLists
298
+ after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_arguments, runtime_state|
460
299
  return_type_non_null = return_type.non_null?
461
300
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
462
301
  continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
@@ -508,17 +347,16 @@ module GraphQL
508
347
  resolved_arguments.keyword_arguments
509
348
  end
510
349
 
511
- evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null)
350
+ evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null, runtime_state)
512
351
  end
513
352
  end
514
353
 
515
- def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null) # rubocop:disable Metrics/ParameterLists
516
- st = get_current_runtime_state
517
- st.current_field = field_defn
518
- st.current_object = object
519
- st.current_arguments = resolved_arguments
520
- st.current_result_name = result_name
521
- st.current_result = selection_result
354
+ def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null, runtime_state) # rubocop:disable Metrics/ParameterLists
355
+ runtime_state.current_field = field_defn
356
+ runtime_state.current_object = object
357
+ runtime_state.current_arguments = resolved_arguments
358
+ runtime_state.current_result_name = result_name
359
+ runtime_state.current_result = selection_result
522
360
  # Optimize for the case that field is selected only once
523
361
  if field_ast_nodes.nil? || field_ast_nodes.size == 1
524
362
  next_selections = ast_node.selections
@@ -547,13 +385,12 @@ module GraphQL
547
385
  ex_err
548
386
  end
549
387
  end
550
- after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
388
+ after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_result, runtime_state|
551
389
  continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
552
390
  if HALT != continue_value
553
- st = get_current_runtime_state
554
- was_scoped = st.was_authorized_by_scope_items
555
- st.was_authorized_by_scope_items = nil
556
- continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result, was_scoped)
391
+ was_scoped = runtime_state.was_authorized_by_scope_items
392
+ runtime_state.was_authorized_by_scope_items = nil
393
+ continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result, was_scoped, runtime_state)
557
394
  end
558
395
  end
559
396
  end
@@ -733,7 +570,7 @@ module GraphQL
733
570
  # Location information from `path` and `ast_node`.
734
571
  #
735
572
  # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
736
- def continue_field(value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped) # rubocop:disable Metrics/ParameterLists
573
+ def continue_field(value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists
737
574
  if current_type.non_null?
738
575
  current_type = current_type.of_type
739
576
  is_non_null = true
@@ -750,7 +587,7 @@ module GraphQL
750
587
  r
751
588
  when "UNION", "INTERFACE"
752
589
  resolved_type_or_lazy = resolve_type(current_type, value)
753
- 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) do |resolved_type_result|
590
+ 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|
754
591
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
755
592
  resolved_type, resolved_value = resolved_type_result
756
593
  else
@@ -767,7 +604,7 @@ module GraphQL
767
604
  set_result(selection_result, result_name, nil, false, is_non_null)
768
605
  nil
769
606
  else
770
- continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped)
607
+ continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped, runtime_state)
771
608
  end
772
609
  end
773
610
  when "OBJECT"
@@ -776,12 +613,13 @@ module GraphQL
776
613
  rescue GraphQL::ExecutionError => err
777
614
  err
778
615
  end
779
- after_lazy(object_proxy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
616
+ after_lazy(object_proxy, 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 |inner_object, runtime_state|
780
617
  continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
781
618
  if HALT != continue_value
782
619
  response_hash = GraphQLResultHash.new(result_name, selection_result, is_non_null)
783
620
  set_result(selection_result, result_name, response_hash, true, is_non_null)
784
- gathered_selections = gather_selections(continue_value, current_type, next_selections)
621
+
622
+ gathered_selections = gather_selections(continue_value, current_type, ast_node, next_selections)
785
623
  # There are two possibilities for `gathered_selections`:
786
624
  # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
787
625
  # This case is handled below, and the result can be written right into the main `response_hash` above.
@@ -800,10 +638,9 @@ module GraphQL
800
638
  end
801
639
  # reset this mutable state
802
640
  # Unset `result_name` here because it's already included in the new response hash
803
- st = get_current_runtime_state
804
- st.current_object = continue_value
805
- st.current_result_name = nil
806
- st.current_result = this_result
641
+ runtime_state.current_object = continue_value
642
+ runtime_state.current_result_name = nil
643
+ runtime_state.current_result = this_result
807
644
  # This is a less-frequent case; use a fast check since it's often not there.
808
645
  if (directives = selections[:graphql_directives])
809
646
  selections.delete(:graphql_directives)
@@ -817,6 +654,7 @@ module GraphQL
817
654
  this_result,
818
655
  final_result,
819
656
  owner_object.object,
657
+ runtime_state,
820
658
  )
821
659
  this_result
822
660
  end
@@ -838,10 +676,10 @@ module GraphQL
838
676
  idx += 1
839
677
  if use_dataloader_job
840
678
  @dataloader.append_job do
841
- resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type, was_scoped)
679
+ resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type, was_scoped, runtime_state)
842
680
  end
843
681
  else
844
- resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type, was_scoped)
682
+ resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type, was_scoped, runtime_state)
845
683
  end
846
684
  end
847
685
 
@@ -872,16 +710,15 @@ module GraphQL
872
710
  end
873
711
  end
874
712
 
875
- def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type, was_scoped) # rubocop:disable Metrics/ParameterLists
876
- st = get_current_runtime_state
877
- st.current_result_name = this_idx
878
- st.current_result = response_list
713
+ def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists
714
+ runtime_state.current_result_name = this_idx
715
+ runtime_state.current_result = response_list
879
716
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
880
717
  # This will update `response_list` with the lazy
881
- after_lazy(inner_value, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
718
+ after_lazy(inner_value, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list, runtime_state: runtime_state) do |inner_inner_value, runtime_state|
882
719
  continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
883
720
  if HALT != continue_value
884
- continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list, was_scoped)
721
+ continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list, was_scoped, runtime_state)
885
722
  end
886
723
  end
887
724
  end
@@ -958,19 +795,21 @@ module GraphQL
958
795
  # @param eager [Boolean] Set to `true` for mutation root fields only
959
796
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
960
797
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
961
- def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
798
+ def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, runtime_state:, trace: true, &block)
962
799
  if lazy?(lazy_obj)
963
800
  orig_result = result
964
- st = get_current_runtime_state
965
- was_authorized_by_scope_items = st.was_authorized_by_scope_items
801
+ was_authorized_by_scope_items = runtime_state.was_authorized_by_scope_items
966
802
  lazy = GraphQL::Execution::Lazy.new(field: field) do
967
- st = get_current_runtime_state
968
- st.current_object = owner_object
969
- st.current_field = field
970
- st.current_arguments = arguments
971
- st.current_result_name = result_name
972
- st.current_result = orig_result
973
- st.was_authorized_by_scope_items = was_authorized_by_scope_items
803
+ # This block might be called in a new fiber;
804
+ # In that case, this will initialize a new state
805
+ # to avoid conflicting with the parent fiber.
806
+ runtime_state = get_current_runtime_state
807
+ runtime_state.current_object = owner_object
808
+ runtime_state.current_field = field
809
+ runtime_state.current_arguments = arguments
810
+ runtime_state.current_result_name = result_name
811
+ runtime_state.current_result = orig_result
812
+ runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items
974
813
  # Wrap the execution of _this_ method with tracing,
975
814
  # but don't wrap the continuation below
976
815
  inner_obj = begin
@@ -990,7 +829,7 @@ module GraphQL
990
829
  ex_err
991
830
  end
992
831
  end
993
- yield(inner_obj)
832
+ yield(inner_obj, runtime_state)
994
833
  end
995
834
 
996
835
  if eager
@@ -1007,7 +846,7 @@ module GraphQL
1007
846
  end
1008
847
  else
1009
848
  # Don't need to reset state here because it _wasn't_ lazy.
1010
- yield(lazy_obj)
849
+ yield(lazy_obj, runtime_state)
1011
850
  end
1012
851
  end
1013
852
 
@@ -98,12 +98,6 @@ module GraphQL
98
98
  multiplex.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
99
99
  Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader)
100
100
  end
101
- queries.each do |query|
102
- runtime = query.context.namespace(:interpreter_runtime)[:runtime]
103
- if runtime
104
- runtime.delete_all_interpreter_context
105
- end
106
- end
107
101
  }
108
102
  multiplex.dataloader.run
109
103
 
@@ -102,7 +102,7 @@ module GraphQL
102
102
  @query.warden
103
103
  .possible_types(selected_type)
104
104
  .map { |t| @query.warden.fields(t) }
105
- .flatten
105
+ .tap(&:flatten!)
106
106
  end
107
107
 
108
108
  if (match_by_orig_name = all_fields.find { |f| f.original_name == field_name })
@@ -2,7 +2,7 @@
2
2
  module GraphQL
3
3
  module Introspection
4
4
  class DynamicFields < Introspection::BaseObject
5
- field :__typename, String, "The name of this type", null: false
5
+ field :__typename, String, "The name of this type", null: false, dynamic_introspection: true
6
6
 
7
7
  def __typename
8
8
  object.class.graphql_name
@@ -2,8 +2,8 @@
2
2
  module GraphQL
3
3
  module Introspection
4
4
  class EntryPoints < Introspection::BaseObject
5
- field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false
6
- field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system" do
5
+ field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false, dynamic_introspection: true
6
+ field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", dynamic_introspection: true do
7
7
  argument :name, String
8
8
  end
9
9