graphql 2.0.14 → 2.0.32

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.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/mutation_delete_generator.rb +1 -1
  3. data/lib/generators/graphql/mutation_update_generator.rb +1 -1
  4. data/lib/generators/graphql/relay.rb +18 -1
  5. data/lib/graphql/analysis/ast/visitor.rb +42 -35
  6. data/lib/graphql/analysis/ast.rb +2 -2
  7. data/lib/graphql/backtrace/table.rb +2 -2
  8. data/lib/graphql/backtrace/trace.rb +96 -0
  9. data/lib/graphql/backtrace/tracer.rb +1 -1
  10. data/lib/graphql/backtrace.rb +2 -1
  11. data/lib/graphql/dataloader/source.rb +69 -45
  12. data/lib/graphql/dataloader.rb +8 -5
  13. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  14. data/lib/graphql/execution/interpreter/arguments_cache.rb +33 -33
  15. data/lib/graphql/execution/interpreter/resolve.rb +19 -0
  16. data/lib/graphql/execution/interpreter/runtime.rb +355 -268
  17. data/lib/graphql/execution/interpreter.rb +19 -15
  18. data/lib/graphql/execution/lazy.rb +6 -12
  19. data/lib/graphql/execution/lookahead.rb +16 -5
  20. data/lib/graphql/execution/multiplex.rb +2 -1
  21. data/lib/graphql/filter.rb +8 -2
  22. data/lib/graphql/introspection/directive_type.rb +2 -2
  23. data/lib/graphql/introspection/entry_points.rb +1 -1
  24. data/lib/graphql/introspection/field_type.rb +1 -1
  25. data/lib/graphql/introspection/schema_type.rb +2 -2
  26. data/lib/graphql/introspection/type_type.rb +5 -5
  27. data/lib/graphql/introspection.rb +1 -1
  28. data/lib/graphql/language/document_from_schema_definition.rb +58 -35
  29. data/lib/graphql/language/lexer.rb +248 -1505
  30. data/lib/graphql/language/nodes.rb +69 -40
  31. data/lib/graphql/language/parser.rb +775 -742
  32. data/lib/graphql/language/parser.y +44 -38
  33. data/lib/graphql/language/printer.rb +48 -25
  34. data/lib/graphql/language/visitor.rb +192 -81
  35. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  36. data/lib/graphql/pagination/connection.rb +5 -5
  37. data/lib/graphql/query/context.rb +93 -27
  38. data/lib/graphql/query/null_context.rb +8 -18
  39. data/lib/graphql/query/validation_pipeline.rb +2 -1
  40. data/lib/graphql/query.rb +55 -13
  41. data/lib/graphql/rake_task.rb +28 -1
  42. data/lib/graphql/schema/addition.rb +38 -12
  43. data/lib/graphql/schema/always_visible.rb +10 -0
  44. data/lib/graphql/schema/argument.rb +15 -23
  45. data/lib/graphql/schema/build_from_definition.rb +54 -25
  46. data/lib/graphql/schema/directive/transform.rb +1 -1
  47. data/lib/graphql/schema/directive.rb +12 -2
  48. data/lib/graphql/schema/enum.rb +24 -17
  49. data/lib/graphql/schema/enum_value.rb +3 -4
  50. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  51. data/lib/graphql/schema/field.rb +95 -73
  52. data/lib/graphql/schema/field_extension.rb +1 -4
  53. data/lib/graphql/schema/find_inherited_value.rb +2 -7
  54. data/lib/graphql/schema/input_object.rb +9 -7
  55. data/lib/graphql/schema/interface.rb +5 -11
  56. data/lib/graphql/schema/introspection_system.rb +1 -1
  57. data/lib/graphql/schema/late_bound_type.rb +2 -0
  58. data/lib/graphql/schema/member/base_dsl_methods.rb +17 -14
  59. data/lib/graphql/schema/member/build_type.rb +11 -3
  60. data/lib/graphql/schema/member/has_arguments.rb +114 -65
  61. data/lib/graphql/schema/member/has_ast_node.rb +12 -0
  62. data/lib/graphql/schema/member/has_deprecation_reason.rb +3 -4
  63. data/lib/graphql/schema/member/has_directives.rb +81 -61
  64. data/lib/graphql/schema/member/has_fields.rb +95 -38
  65. data/lib/graphql/schema/member/has_interfaces.rb +49 -8
  66. data/lib/graphql/schema/member/has_validators.rb +32 -6
  67. data/lib/graphql/schema/member/relay_shortcuts.rb +19 -0
  68. data/lib/graphql/schema/member/type_system_helpers.rb +17 -0
  69. data/lib/graphql/schema/object.rb +8 -5
  70. data/lib/graphql/schema/printer.rb +3 -1
  71. data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
  72. data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
  73. data/lib/graphql/schema/resolver.rb +16 -14
  74. data/lib/graphql/schema/timeout.rb +25 -29
  75. data/lib/graphql/schema/type_membership.rb +3 -0
  76. data/lib/graphql/schema/union.rb +10 -1
  77. data/lib/graphql/schema/validator.rb +2 -2
  78. data/lib/graphql/schema/warden.rb +64 -7
  79. data/lib/graphql/schema.rb +171 -28
  80. data/lib/graphql/static_validation/definition_dependencies.rb +7 -1
  81. data/lib/graphql/static_validation/literal_validator.rb +15 -1
  82. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -4
  83. data/lib/graphql/static_validation/rules/fields_will_merge.rb +2 -2
  84. data/lib/graphql/static_validation/validator.rb +1 -1
  85. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +7 -1
  86. data/lib/graphql/subscriptions/event.rb +2 -7
  87. data/lib/graphql/subscriptions.rb +5 -0
  88. data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
  89. data/lib/graphql/tracing/appoptics_trace.rb +255 -0
  90. data/lib/graphql/tracing/appsignal_trace.rb +81 -0
  91. data/lib/graphql/tracing/data_dog_trace.rb +187 -0
  92. data/lib/graphql/tracing/data_dog_tracing.rb +7 -21
  93. data/lib/graphql/tracing/legacy_trace.rb +69 -0
  94. data/lib/graphql/tracing/new_relic_trace.rb +75 -0
  95. data/lib/graphql/tracing/notifications_trace.rb +49 -0
  96. data/lib/graphql/tracing/platform_trace.rb +123 -0
  97. data/lib/graphql/tracing/platform_tracing.rb +15 -3
  98. data/lib/graphql/tracing/prometheus_trace.rb +93 -0
  99. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +1 -1
  100. data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
  101. data/lib/graphql/tracing/scout_trace.rb +75 -0
  102. data/lib/graphql/tracing/statsd_trace.rb +60 -0
  103. data/lib/graphql/tracing/trace.rb +75 -0
  104. data/lib/graphql/tracing.rb +17 -39
  105. data/lib/graphql/type_kinds.rb +6 -3
  106. data/lib/graphql/types/relay/base_connection.rb +1 -1
  107. data/lib/graphql/types/relay/connection_behaviors.rb +28 -6
  108. data/lib/graphql/types/relay/edge_behaviors.rb +16 -5
  109. data/lib/graphql/types/relay/node_behaviors.rb +8 -2
  110. data/lib/graphql/types/relay/page_info_behaviors.rb +7 -2
  111. data/lib/graphql/types/relay.rb +0 -1
  112. data/lib/graphql/types/string.rb +1 -1
  113. data/lib/graphql/version.rb +1 -1
  114. data/lib/graphql.rb +16 -9
  115. data/readme.md +1 -1
  116. metadata +66 -29
  117. data/lib/graphql/language/lexer.rl +0 -280
  118. data/lib/graphql/types/relay/default_relay.rb +0 -21
@@ -8,35 +8,49 @@ module GraphQL
8
8
  #
9
9
  # @api private
10
10
  class Runtime
11
+ class CurrentState
12
+ def initialize
13
+ @current_object = nil
14
+ @current_field = nil
15
+ @current_arguments = nil
16
+ @current_result_name = nil
17
+ @current_result = nil
18
+ end
19
+
20
+ attr_accessor :current_result, :current_result_name,
21
+ :current_arguments, :current_field, :current_object
22
+ end
11
23
 
12
24
  module GraphQLResult
13
- def initialize(result_name, parent_result)
25
+ def initialize(result_name, parent_result, is_non_null_in_parent)
14
26
  @graphql_parent = parent_result
15
27
  if parent_result && parent_result.graphql_dead
16
28
  @graphql_dead = true
17
29
  end
18
30
  @graphql_result_name = result_name
31
+ @graphql_is_non_null_in_parent = is_non_null_in_parent
19
32
  # Jump through some hoops to avoid creating this duplicate storage if at all possible.
20
33
  @graphql_metadata = nil
21
34
  end
22
35
 
23
- attr_accessor :graphql_dead
24
- attr_reader :graphql_parent, :graphql_result_name
36
+ def path
37
+ @path ||= build_path([])
38
+ end
25
39
 
26
- # Although these are used by only one of the Result classes,
27
- # it's handy to have the methods implemented on both (even though they just return `nil`)
28
- # because it makes it easy to check if anything is assigned.
29
- # @return [nil, Array<String>]
30
- attr_accessor :graphql_non_null_field_names
31
- # @return [nil, true]
32
- attr_accessor :graphql_non_null_list_items
40
+ def build_path(path_array)
41
+ graphql_result_name && path_array.unshift(graphql_result_name)
42
+ @graphql_parent ? @graphql_parent.build_path(path_array) : path_array
43
+ end
44
+
45
+ attr_accessor :graphql_dead
46
+ attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent
33
47
 
34
48
  # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
35
49
  attr_accessor :graphql_result_data
36
50
  end
37
51
 
38
52
  class GraphQLResultHash
39
- def initialize(_result_name, _parent_result)
53
+ def initialize(_result_name, _parent_result, _is_non_null_in_parent)
40
54
  super
41
55
  @graphql_result_data = {}
42
56
  end
@@ -45,7 +59,7 @@ module GraphQL
45
59
 
46
60
  attr_accessor :graphql_merged_into
47
61
 
48
- def []=(key, value)
62
+ def set_leaf(key, value)
49
63
  # This is a hack.
50
64
  # Basically, this object is merged into the root-level result at some point.
51
65
  # But the problem is, some lazies are created whose closures retain reference to _this_
@@ -55,20 +69,24 @@ module GraphQL
55
69
  # In order to return a proper partial result (eg, for a directive), we have to update this object, too.
56
70
  # Yowza.
57
71
  if (t = @graphql_merged_into)
58
- t[key] = value
72
+ t.set_leaf(key, value)
59
73
  end
60
74
 
61
- if value.respond_to?(:graphql_result_data)
62
- @graphql_result_data[key] = value.graphql_result_data
63
- # If we encounter some part of this response that requires metadata tracking,
64
- # then create the metadata hash if necessary. It will be kept up-to-date after this.
65
- (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
66
- else
67
- @graphql_result_data[key] = value
68
- # keep this up-to-date if it's been initialized
69
- @graphql_metadata && @graphql_metadata[key] = value
70
- end
75
+ @graphql_result_data[key] = value
76
+ # keep this up-to-date if it's been initialized
77
+ @graphql_metadata && @graphql_metadata[key] = value
78
+
79
+ value
80
+ end
71
81
 
82
+ def set_child_result(key, value)
83
+ if (t = @graphql_merged_into)
84
+ t.set_child_result(key, value)
85
+ end
86
+ @graphql_result_data[key] = value.graphql_result_data
87
+ # If we encounter some part of this response that requires metadata tracking,
88
+ # then create the metadata hash if necessary. It will be kept up-to-date after this.
89
+ (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
72
90
  value
73
91
  end
74
92
 
@@ -92,12 +110,35 @@ module GraphQL
92
110
  def [](k)
93
111
  (@graphql_metadata || @graphql_result_data)[k]
94
112
  end
113
+
114
+ def merge_into(into_result)
115
+ self.each do |key, value|
116
+ case value
117
+ when GraphQLResultHash
118
+ next_into = into_result[key]
119
+ if next_into
120
+ value.merge_into(next_into)
121
+ else
122
+ into_result.set_child_result(key, value)
123
+ end
124
+ when GraphQLResultArray
125
+ # There's no special handling of arrays because currently, there's no way to split the execution
126
+ # of a list over several concurrent flows.
127
+ into_result.set_child_result(key, value)
128
+ else
129
+ # We have to assume that, since this passed the `fields_will_merge` selection,
130
+ # that the old and new values are the same.
131
+ into_result.set_leaf(key, value)
132
+ end
133
+ end
134
+ @graphql_merged_into = into_result
135
+ end
95
136
  end
96
137
 
97
138
  class GraphQLResultArray
98
139
  include GraphQLResult
99
140
 
100
- def initialize(_result_name, _parent_result)
141
+ def initialize(_result_name, _parent_result, _is_non_null_in_parent)
101
142
  super
102
143
  @graphql_result_data = []
103
144
  end
@@ -114,19 +155,25 @@ module GraphQL
114
155
  @graphql_result_data.delete_at(delete_at_index)
115
156
  end
116
157
 
117
- def []=(idx, value)
158
+ def set_leaf(idx, value)
118
159
  if @skip_indices
119
160
  offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
120
161
  idx -= offset_by
121
162
  end
122
- if value.respond_to?(:graphql_result_data)
123
- @graphql_result_data[idx] = value.graphql_result_data
124
- (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
125
- else
126
- @graphql_result_data[idx] = value
127
- @graphql_metadata && @graphql_metadata[idx] = value
128
- end
163
+ @graphql_result_data[idx] = value
164
+ @graphql_metadata && @graphql_metadata[idx] = value
165
+ value
166
+ end
129
167
 
168
+ def set_child_result(idx, value)
169
+ if @skip_indices
170
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
171
+ idx -= offset_by
172
+ end
173
+ @graphql_result_data[idx] = value.graphql_result_data
174
+ # If we encounter some part of this response that requires metadata tracking,
175
+ # then create the metadata hash if necessary. It will be kept up-to-date after this.
176
+ (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
130
177
  value
131
178
  end
132
179
 
@@ -135,10 +182,6 @@ module GraphQL
135
182
  end
136
183
  end
137
184
 
138
- class GraphQLSelectionSet < Hash
139
- attr_accessor :graphql_directives
140
- end
141
-
142
185
  # @return [GraphQL::Query]
143
186
  attr_reader :query
144
187
 
@@ -148,14 +191,14 @@ module GraphQL
148
191
  # @return [GraphQL::Query::Context]
149
192
  attr_reader :context
150
193
 
151
- def initialize(query:)
194
+ def initialize(query:, lazies_at_depth:)
152
195
  @query = query
196
+ @current_trace = query.current_trace
153
197
  @dataloader = query.multiplex.dataloader
198
+ @lazies_at_depth = lazies_at_depth
154
199
  @schema = query.schema
155
200
  @context = query.context
156
- @multiplex_context = query.multiplex.context
157
- @interpreter_context = @context.namespace(:interpreter)
158
- @response = GraphQLResultHash.new(nil, nil)
201
+ @response = GraphQLResultHash.new(nil, nil, false)
159
202
  # Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
160
203
  @runtime_directive_names = []
161
204
  noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
@@ -169,8 +212,11 @@ module GraphQL
169
212
  # Which assumes that MyObject.get_field("myField") will return the same field
170
213
  # during the lifetime of a query
171
214
  @fields_cache = Hash.new { |h, k| h[k] = {} }
215
+ # this can by by-identity since owners are the same object, but not the sub-hash, which uses strings.
216
+ @fields_cache.compare_by_identity
172
217
  # { Class => Boolean }
173
218
  @lazy_cache = {}
219
+ @lazy_cache.compare_by_identity
174
220
  end
175
221
 
176
222
  def final_result
@@ -198,17 +244,18 @@ module GraphQL
198
244
  root_operation = query.selected_operation
199
245
  root_op_type = root_operation.operation_type || "query"
200
246
  root_type = schema.root_type_for_operation(root_op_type)
201
- path = []
202
- set_all_interpreter_context(query.root_value, nil, nil, path)
203
- object_proxy = authorized_new(root_type, query.root_value, context)
204
- object_proxy = schema.sync_lazy(object_proxy)
247
+ st = get_current_runtime_state
248
+ st.current_object = query.root_value
249
+ st.current_result = @response
250
+ runtime_object = root_type.wrap(query.root_value, context)
251
+ runtime_object = schema.sync_lazy(runtime_object)
205
252
 
206
- if object_proxy.nil?
253
+ if runtime_object.nil?
207
254
  # Root .authorized? returned false.
208
255
  @response = nil
209
256
  else
210
- call_method_on_directives(:resolve, object_proxy, root_operation.directives) do # execute query level directives
211
- gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
257
+ call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
258
+ gathered_selections = gather_selections(runtime_object, root_type, root_operation.selections)
212
259
  # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
213
260
  # require isolation during execution (because of runtime directives). In that case,
214
261
  # make a new, isolated result hash for writing the result into. (That isolated response
@@ -218,7 +265,7 @@ module GraphQL
218
265
  # directly evaluated and the results can be written right into the main response hash.
219
266
  tap_or_each(gathered_selections) do |selections, is_selection_array|
220
267
  if is_selection_array
221
- selection_response = GraphQLResultHash.new(nil, nil)
268
+ selection_response = GraphQLResultHash.new(nil, nil, false)
222
269
  final_response = @response
223
270
  else
224
271
  selection_response = @response
@@ -226,11 +273,16 @@ module GraphQL
226
273
  end
227
274
 
228
275
  @dataloader.append_job {
229
- set_all_interpreter_context(query.root_value, nil, nil, path)
230
- call_method_on_directives(:resolve, object_proxy, selections.graphql_directives) do
276
+ st = get_current_runtime_state
277
+ st.current_object = query.root_value
278
+ st.current_result = selection_response
279
+ # This is a less-frequent case; use a fast check since it's often not there.
280
+ if (directives = selections[:graphql_directives])
281
+ selections.delete(:graphql_directives)
282
+ end
283
+ call_method_on_directives(:resolve, runtime_object, directives) do
231
284
  evaluate_selections(
232
- path,
233
- object_proxy,
285
+ runtime_object,
234
286
  root_type,
235
287
  root_op_type == "mutation",
236
288
  selections,
@@ -243,36 +295,11 @@ module GraphQL
243
295
  end
244
296
  end
245
297
  end
246
- delete_interpreter_context(:current_path)
247
- delete_interpreter_context(:current_field)
248
- delete_interpreter_context(:current_object)
249
- delete_interpreter_context(:current_arguments)
298
+ delete_all_interpreter_context
250
299
  nil
251
300
  end
252
301
 
253
- # @return [void]
254
- def deep_merge_selection_result(from_result, into_result)
255
- from_result.each do |key, value|
256
- if !into_result.key?(key)
257
- into_result[key] = value
258
- else
259
- case value
260
- when GraphQLResultHash
261
- deep_merge_selection_result(value, into_result[key])
262
- else
263
- # We have to assume that, since this passed the `fields_will_merge` selection,
264
- # that the old and new values are the same.
265
- # There's no special handling of arrays because currently, there's no way to split the execution
266
- # of a list over several concurrent flows.
267
- into_result[key] = value
268
- end
269
- end
270
- end
271
- from_result.graphql_merged_into = into_result
272
- nil
273
- end
274
-
275
- def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new)
302
+ def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
276
303
  selections.each do |node|
277
304
  # Skip gathering this if the directive says so
278
305
  if !directives_include?(node, owner_object, owner_type)
@@ -298,8 +325,8 @@ module GraphQL
298
325
  else
299
326
  # This is an InlineFragment or a FragmentSpread
300
327
  if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
301
- next_selections = GraphQLSelectionSet.new
302
- next_selections.graphql_directives = node.directives
328
+ next_selections = {}
329
+ next_selections[:graphql_directives] = node.directives
303
330
  if selections_to_run
304
331
  selections_to_run << next_selections
305
332
  else
@@ -316,25 +343,26 @@ module GraphQL
316
343
  if node.type
317
344
  type_defn = schema.get_type(node.type.name, context)
318
345
 
319
- # Faster than .map{}.include?()
320
- query.warden.possible_types(type_defn).each do |t|
321
- if t == owner_type
322
- gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
323
- break
346
+ if query.warden.possible_types(type_defn).include?(owner_type)
347
+ result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
348
+ if !result.equal?(next_selections)
349
+ selections_to_run = result
324
350
  end
325
351
  end
326
352
  else
327
353
  # it's an untyped fragment, definitely continue
328
- gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
354
+ result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
355
+ if !result.equal?(next_selections)
356
+ selections_to_run = result
357
+ end
329
358
  end
330
359
  when GraphQL::Language::Nodes::FragmentSpread
331
360
  fragment_def = query.fragments[node.name]
332
361
  type_defn = query.get_type(fragment_def.type.name)
333
- possible_types = query.warden.possible_types(type_defn)
334
- possible_types.each do |t|
335
- if t == owner_type
336
- gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
337
- break
362
+ if query.warden.possible_types(type_defn).include?(owner_type)
363
+ result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
364
+ if !result.equal?(next_selections)
365
+ selections_to_run = result
338
366
  end
339
367
  end
340
368
  else
@@ -345,33 +373,40 @@ module GraphQL
345
373
  selections_to_run || selections_by_name
346
374
  end
347
375
 
348
- NO_ARGS = {}.freeze
376
+ NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
349
377
 
350
378
  # @return [void]
351
- def evaluate_selections(path, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
352
- set_all_interpreter_context(owner_object, nil, nil, path)
379
+ def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
380
+ st = get_current_runtime_state
381
+ st.current_object = owner_object
382
+ st.current_result_name = nil
383
+ st.current_result = selections_result
353
384
 
354
385
  finished_jobs = 0
355
386
  enqueued_jobs = gathered_selections.size
356
387
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
357
388
  @dataloader.append_job {
358
389
  evaluate_selection(
359
- path, result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
390
+ result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
360
391
  )
361
392
  finished_jobs += 1
362
393
  if target_result && finished_jobs == enqueued_jobs
363
- deep_merge_selection_result(selections_result, target_result)
394
+ selections_result.merge_into(target_result)
364
395
  end
365
396
  }
397
+ # Field resolution may pause the fiber,
398
+ # so it wouldn't get to the `Resolve` call that happens below.
399
+ # So instead trigger a run from this outer context.
400
+ if is_eager_selection
401
+ @dataloader.run
402
+ end
366
403
  end
367
404
 
368
405
  selections_result
369
406
  end
370
407
 
371
- attr_reader :progress_path
372
-
373
408
  # @return [void]
374
- def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
409
+ 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
375
410
  return if dead_result?(selections_result)
376
411
  # As a performance optimization, the hash key will be a `Node` if
377
412
  # there's only one selection of the field. But if there are multiple
@@ -399,48 +434,54 @@ module GraphQL
399
434
  raise "Invariant: no field for #{owner_type}.#{field_name}"
400
435
  end
401
436
  end
402
- return_type = field_defn.type
403
437
 
404
- next_path = path + [result_name]
405
- next_path.freeze
438
+ return_type = field_defn.type
406
439
 
407
440
  # This seems janky, but we need to know
408
441
  # the field's return type at this path in order
409
442
  # to propagate `null`
410
- if return_type.non_null?
411
- (selections_result.graphql_non_null_field_names ||= []).push(result_name)
412
- end
443
+ return_type_non_null = return_type.non_null?
413
444
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
414
- set_all_interpreter_context(nil, field_defn, nil, next_path)
415
- object = owner_object
445
+ st = get_current_runtime_state
446
+ st.current_field = field_defn
447
+ st.current_result = selections_result
448
+ st.current_result_name = result_name
416
449
 
417
450
  if is_introspection
418
- object = authorized_new(field_defn.owner, object, context)
451
+ owner_object = field_defn.owner.wrap(owner_object, context)
419
452
  end
420
453
 
421
454
  total_args_count = field_defn.arguments(context).size
422
455
  if total_args_count == 0
423
456
  resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
424
- evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
457
+ if field_defn.extras.size == 0
458
+ evaluate_selection_with_resolved_keyword_args(
459
+ 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
460
+ )
461
+ else
462
+ 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, return_type_non_null)
463
+ end
425
464
  else
426
- # TODO remove all arguments(...) usages?
427
- @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
428
- evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
465
+ @query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
466
+ 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, return_type_non_null)
429
467
  end
430
468
  end
431
469
  end
432
470
 
433
- def evaluate_selection_with_args(arguments, field_defn, next_path, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object) # rubocop:disable Metrics/ParameterLists
434
- return_type = field_defn.type
435
- after_lazy(arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
471
+ 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, return_type_non_null) # rubocop:disable Metrics/ParameterLists
472
+ 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|
436
473
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
437
- continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
474
+ continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
438
475
  next
439
476
  end
440
477
 
441
- kwarg_arguments = if resolved_arguments.empty? && field_defn.extras.empty?
442
- # We can avoid allocating the `{ Symbol => Object }` hash in this case
443
- NO_ARGS
478
+ kwarg_arguments = if field_defn.extras.empty?
479
+ if resolved_arguments.empty?
480
+ # We can avoid allocating the `{ Symbol => Object }` hash in this case
481
+ NO_ARGS
482
+ else
483
+ resolved_arguments.keyword_arguments
484
+ end
444
485
  else
445
486
  # Bundle up the extras, then make a new arguments instance
446
487
  # that includes the extras, too.
@@ -450,9 +491,9 @@ module GraphQL
450
491
  when :ast_node
451
492
  extra_args[:ast_node] = ast_node
452
493
  when :execution_errors
453
- extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
494
+ extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, current_path)
454
495
  when :path
455
- extra_args[:path] = next_path
496
+ extra_args[:path] = current_path
456
497
  when :lookahead
457
498
  if !field_ast_nodes
458
499
  field_ast_nodes = [ast_node]
@@ -479,67 +520,72 @@ module GraphQL
479
520
  resolved_arguments.keyword_arguments
480
521
  end
481
522
 
482
- set_all_interpreter_context(nil, nil, resolved_arguments, nil)
523
+ 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)
524
+ end
525
+ end
483
526
 
484
- # Optimize for the case that field is selected only once
485
- if field_ast_nodes.nil? || field_ast_nodes.size == 1
486
- next_selections = ast_node.selections
487
- directives = ast_node.directives
488
- else
489
- next_selections = []
490
- directives = []
491
- field_ast_nodes.each { |f|
492
- next_selections.concat(f.selections)
493
- directives.concat(f.directives)
494
- }
495
- end
496
-
497
- field_result = call_method_on_directives(:resolve, object, directives) do
498
- # Actually call the field resolver and capture the result
499
- app_result = begin
500
- 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
501
- field_defn.resolve(object, kwarg_arguments, context)
502
- end
503
- rescue GraphQL::ExecutionError => err
504
- err
505
- rescue StandardError => err
506
- begin
507
- query.handle_or_reraise(err)
508
- rescue GraphQL::ExecutionError => ex_err
509
- ex_err
510
- end
527
+ 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
528
+ st = get_current_runtime_state
529
+ st.current_field = field_defn
530
+ st.current_object = object
531
+ st.current_arguments = resolved_arguments
532
+ st.current_result_name = result_name
533
+ st.current_result = selection_result
534
+ # Optimize for the case that field is selected only once
535
+ if field_ast_nodes.nil? || field_ast_nodes.size == 1
536
+ next_selections = ast_node.selections
537
+ directives = ast_node.directives
538
+ else
539
+ next_selections = []
540
+ directives = []
541
+ field_ast_nodes.each { |f|
542
+ next_selections.concat(f.selections)
543
+ directives.concat(f.directives)
544
+ }
545
+ end
546
+
547
+ field_result = call_method_on_directives(:resolve, object, directives) do
548
+ # Actually call the field resolver and capture the result
549
+ app_result = begin
550
+ @current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
551
+ field_defn.resolve(object, kwarg_arguments, context)
511
552
  end
512
- after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
513
- continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
514
- if HALT != continue_value
515
- continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
516
- end
553
+ rescue GraphQL::ExecutionError => err
554
+ err
555
+ rescue StandardError => err
556
+ begin
557
+ query.handle_or_reraise(err)
558
+ rescue GraphQL::ExecutionError => ex_err
559
+ ex_err
517
560
  end
518
561
  end
519
-
520
- # If this field is a root mutation field, immediately resolve
521
- # all of its child fields before moving on to the next root mutation field.
522
- # (Subselections of this mutation will still be resolved level-by-level.)
523
- if is_eager_field
524
- Interpreter::Resolve.resolve_all([field_result], @dataloader)
525
- else
526
- # Return this from `after_lazy` because it might be another lazy that needs to be resolved
527
- field_result
562
+ 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|
563
+ continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
564
+ if HALT != continue_value
565
+ continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
566
+ end
528
567
  end
529
568
  end
569
+
570
+ # If this field is a root mutation field, immediately resolve
571
+ # all of its child fields before moving on to the next root mutation field.
572
+ # (Subselections of this mutation will still be resolved level-by-level.)
573
+ if is_eager_field
574
+ Interpreter::Resolve.resolve_all([field_result], @dataloader)
575
+ else
576
+ # Return this from `after_lazy` because it might be another lazy that needs to be resolved
577
+ field_result
578
+ end
530
579
  end
531
580
 
581
+
532
582
  def dead_result?(selection_result)
533
- selection_result.graphql_dead || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
583
+ selection_result.graphql_dead # || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
534
584
  end
535
585
 
536
- def set_result(selection_result, result_name, value)
586
+ def set_result(selection_result, result_name, value, is_child_result, is_non_null)
537
587
  if !dead_result?(selection_result)
538
- if value.nil? &&
539
- ( # there are two conditions under which `nil` is not allowed in the response:
540
- (selection_result.graphql_non_null_list_items) || # this value would be written into a list that doesn't allow nils
541
- ((nn = selection_result.graphql_non_null_field_names) && nn.include?(result_name)) # this value would be written into a field that doesn't allow nils
542
- )
588
+ if value.nil? && is_non_null
543
589
  # This is an invalid nil that should be propagated
544
590
  # One caller of this method passes a block,
545
591
  # namely when application code returns a `nil` to GraphQL and it doesn't belong there.
@@ -549,15 +595,18 @@ module GraphQL
549
595
  # TODO the code is trying to tell me something.
550
596
  yield if block_given?
551
597
  parent = selection_result.graphql_parent
552
- name_in_parent = selection_result.graphql_result_name
553
598
  if parent.nil? # This is a top-level result hash
554
599
  @response = nil
555
600
  else
556
- set_result(parent, name_in_parent, nil)
601
+ name_in_parent = selection_result.graphql_result_name
602
+ is_non_null_in_parent = selection_result.graphql_is_non_null_in_parent
603
+ set_result(parent, name_in_parent, nil, false, is_non_null_in_parent)
557
604
  set_graphql_dead(selection_result)
558
605
  end
606
+ elsif is_child_result
607
+ selection_result.set_child_result(result_name, value)
559
608
  else
560
- selection_result[result_name] = value
609
+ selection_result.set_leaf(result_name, value)
561
610
  end
562
611
  end
563
612
  end
@@ -577,18 +626,29 @@ module GraphQL
577
626
  end
578
627
  end
579
628
 
629
+ def current_path
630
+ st = get_current_runtime_state
631
+ result = st.current_result
632
+ path = result && result.path
633
+ if path && (rn = st.current_result_name)
634
+ path = path.dup
635
+ path.push(rn)
636
+ end
637
+ path
638
+ end
639
+
580
640
  HALT = Object.new
581
- def continue_value(path, value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
641
+ def continue_value(value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
582
642
  case value
583
643
  when nil
584
644
  if is_non_null
585
- set_result(selection_result, result_name, nil) do
645
+ set_result(selection_result, result_name, nil, false, is_non_null) do
586
646
  # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
587
647
  err = parent_type::InvalidNullError.new(parent_type, field, value)
588
648
  schema.type_error(err, context)
589
649
  end
590
650
  else
591
- set_result(selection_result, result_name, nil)
651
+ set_result(selection_result, result_name, nil, false, is_non_null)
592
652
  end
593
653
  HALT
594
654
  when GraphQL::Error
@@ -597,14 +657,24 @@ module GraphQL
597
657
  # every time.
598
658
  if value.is_a?(GraphQL::ExecutionError)
599
659
  if selection_result.nil? || !dead_result?(selection_result)
600
- value.path ||= path
660
+ value.path ||= current_path
601
661
  value.ast_node ||= ast_node
602
662
  context.errors << value
603
663
  if selection_result
604
- set_result(selection_result, result_name, nil)
664
+ set_result(selection_result, result_name, nil, false, is_non_null)
605
665
  end
606
666
  end
607
667
  HALT
668
+ elsif value.is_a?(GraphQL::UnauthorizedFieldError)
669
+ value.field ||= field
670
+ # this hook might raise & crash, or it might return
671
+ # a replacement value
672
+ next_value = begin
673
+ schema.unauthorized_field(value)
674
+ rescue GraphQL::ExecutionError => err
675
+ err
676
+ end
677
+ continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
608
678
  elsif value.is_a?(GraphQL::UnauthorizedError)
609
679
  # this hook might raise & crash, or it might return
610
680
  # a replacement value
@@ -613,7 +683,7 @@ module GraphQL
613
683
  rescue GraphQL::ExecutionError => err
614
684
  err
615
685
  end
616
- continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
686
+ continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
617
687
  elsif GraphQL::Execution::SKIP == value
618
688
  # It's possible a lazy was already written here
619
689
  case selection_result
@@ -639,15 +709,15 @@ module GraphQL
639
709
  if selection_result.nil? || !dead_result?(selection_result)
640
710
  value.each_with_index do |error, index|
641
711
  error.ast_node ||= ast_node
642
- error.path ||= path + (list_type_at_all ? [index] : [])
712
+ error.path ||= current_path + (list_type_at_all ? [index] : [])
643
713
  context.errors << error
644
714
  end
645
715
  if selection_result
646
716
  if list_type_at_all
647
717
  result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
648
- set_result(selection_result, result_name, result_without_errors)
718
+ set_result(selection_result, result_name, result_without_errors, false, is_non_null)
649
719
  else
650
- set_result(selection_result, result_name, nil)
720
+ set_result(selection_result, result_name, nil, false, is_non_null)
651
721
  end
652
722
  end
653
723
  end
@@ -657,7 +727,7 @@ module GraphQL
657
727
  end
658
728
  when GraphQL::Execution::Interpreter::RawValue
659
729
  # Write raw value directly to the response without resolving nested objects
660
- set_result(selection_result, result_name, value.resolve)
730
+ set_result(selection_result, result_name, value.resolve, false, is_non_null)
661
731
  HALT
662
732
  else
663
733
  value
@@ -672,7 +742,7 @@ module GraphQL
672
742
  # Location information from `path` and `ast_node`.
673
743
  #
674
744
  # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
675
- def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
745
+ def continue_field(value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
676
746
  if current_type.non_null?
677
747
  current_type = current_type.of_type
678
748
  is_non_null = true
@@ -680,12 +750,16 @@ module GraphQL
680
750
 
681
751
  case current_type.kind.name
682
752
  when "SCALAR", "ENUM"
683
- r = current_type.coerce_result(value, context)
684
- set_result(selection_result, result_name, r)
753
+ r = begin
754
+ current_type.coerce_result(value, context)
755
+ rescue StandardError => err
756
+ schema.handle_or_reraise(context, err)
757
+ end
758
+ set_result(selection_result, result_name, r, false, is_non_null)
685
759
  r
686
760
  when "UNION", "INTERFACE"
687
- resolved_type_or_lazy = resolve_type(current_type, value, path)
688
- after_lazy(resolved_type_or_lazy, owner: current_type, path: path, 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|
761
+ resolved_type_or_lazy = resolve_type(current_type, value)
762
+ 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|
689
763
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
690
764
  resolved_type, resolved_value = resolved_type_result
691
765
  else
@@ -699,23 +773,23 @@ module GraphQL
699
773
  err_class = current_type::UnresolvedTypeError
700
774
  type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
701
775
  schema.type_error(type_error, context)
702
- set_result(selection_result, result_name, nil)
776
+ set_result(selection_result, result_name, nil, false, is_non_null)
703
777
  nil
704
778
  else
705
- continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
779
+ continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
706
780
  end
707
781
  end
708
782
  when "OBJECT"
709
783
  object_proxy = begin
710
- authorized_new(current_type, value, context)
784
+ current_type.wrap(value, context)
711
785
  rescue GraphQL::ExecutionError => err
712
786
  err
713
787
  end
714
- after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
715
- continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
788
+ 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|
789
+ continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
716
790
  if HALT != continue_value
717
- response_hash = GraphQLResultHash.new(result_name, selection_result)
718
- set_result(selection_result, result_name, response_hash)
791
+ response_hash = GraphQLResultHash.new(result_name, selection_result, is_non_null)
792
+ set_result(selection_result, result_name, response_hash, true, is_non_null)
719
793
  gathered_selections = gather_selections(continue_value, current_type, next_selections)
720
794
  # There are two possibilities for `gathered_selections`:
721
795
  # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
@@ -727,16 +801,25 @@ module GraphQL
727
801
  # (Technically, it's possible that one of those entries _doesn't_ require isolation.)
728
802
  tap_or_each(gathered_selections) do |selections, is_selection_array|
729
803
  if is_selection_array
730
- this_result = GraphQLResultHash.new(result_name, selection_result)
804
+ this_result = GraphQLResultHash.new(result_name, selection_result, is_non_null)
731
805
  final_result = response_hash
732
806
  else
733
807
  this_result = response_hash
734
808
  final_result = nil
735
809
  end
736
- set_all_interpreter_context(continue_value, nil, nil, path) # reset this mutable state
737
- call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
810
+ # reset this mutable state
811
+ # Unset `result_name` here because it's already included in the new response hash
812
+ st = get_current_runtime_state
813
+ st.current_object = continue_value
814
+ st.current_result_name = nil
815
+ st.current_result = this_result
816
+
817
+ # This is a less-frequent case; use a fast check since it's often not there.
818
+ if (directives = selections[:graphql_directives])
819
+ selections.delete(:graphql_directives)
820
+ end
821
+ call_method_on_directives(:resolve, continue_value, directives) do
738
822
  evaluate_selections(
739
- path,
740
823
  continue_value,
741
824
  current_type,
742
825
  false,
@@ -754,43 +837,30 @@ module GraphQL
754
837
  inner_type = current_type.of_type
755
838
  # This is true for objects, unions, and interfaces
756
839
  use_dataloader_job = !inner_type.unwrap.kind.input?
757
- response_list = GraphQLResultArray.new(result_name, selection_result)
758
- response_list.graphql_non_null_list_items = inner_type.non_null?
759
- set_result(selection_result, result_name, response_list)
760
- result_was_set = false
761
- idx = 0
840
+ inner_type_non_null = inner_type.non_null?
841
+ response_list = GraphQLResultArray.new(result_name, selection_result, is_non_null)
842
+ set_result(selection_result, result_name, response_list, true, is_non_null)
843
+ idx = nil
762
844
  list_value = begin
763
845
  value.each do |inner_value|
764
- break if dead_result?(response_list)
765
- if !result_was_set
766
- # Don't set the result unless `.each` is successful
767
- set_result(selection_result, result_name, response_list)
768
- result_was_set = true
769
- end
770
- next_path = path + [idx]
846
+ idx ||= 0
771
847
  this_idx = idx
772
- next_path.freeze
773
848
  idx += 1
774
849
  if use_dataloader_job
775
850
  @dataloader.append_job do
776
- resolve_list_item(inner_value, inner_type, next_path, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
851
+ 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)
777
852
  end
778
853
  else
779
- resolve_list_item(inner_value, inner_type, next_path, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
854
+ 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)
780
855
  end
781
856
  end
782
- # Maybe the list was empty and the block was never called.
783
- if !result_was_set
784
- set_result(selection_result, result_name, response_list)
785
- result_was_set = true
786
- end
787
857
 
788
858
  response_list
789
859
  rescue NoMethodError => err
790
860
  # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
791
861
  if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
792
862
  # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
793
- raise ListResultFailedError.new(value: value, field: field, path: path)
863
+ raise ListResultFailedError.new(value: value, field: field, path: current_path)
794
864
  else
795
865
  # This was some other NoMethodError -- let it bubble to reveal the real error.
796
866
  raise
@@ -804,21 +874,24 @@ module GraphQL
804
874
  ex_err
805
875
  end
806
876
  end
807
-
808
- continue_value(path, list_value, owner_type, field, inner_type.non_null?, ast_node, result_name, selection_result)
877
+ # Detect whether this error came while calling `.each` (before `idx` is set) or while running list *items* (after `idx` is set)
878
+ error_is_non_null = idx.nil? ? is_non_null : inner_type.non_null?
879
+ continue_value(list_value, owner_type, field, error_is_non_null, ast_node, result_name, selection_result)
809
880
  else
810
881
  raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
811
882
  end
812
883
  end
813
884
 
814
- def resolve_list_item(inner_value, inner_type, next_path, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) # rubocop:disable Metrics/ParameterLists
815
- set_all_interpreter_context(nil, nil, nil, next_path)
885
+ 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) # rubocop:disable Metrics/ParameterLists
886
+ st = get_current_runtime_state
887
+ st.current_result_name = this_idx
888
+ st.current_result = response_list
816
889
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
817
890
  # This will update `response_list` with the lazy
818
- after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
819
- continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
891
+ 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|
892
+ continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
820
893
  if HALT != continue_value
821
- continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
894
+ continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
822
895
  end
823
896
  end
824
897
  end
@@ -837,7 +910,6 @@ module GraphQL
837
910
  dir_defn = @schema_directives.fetch(dir_node.name)
838
911
  raw_dir_args = arguments(nil, dir_defn, dir_node)
839
912
  dir_args = continue_value(
840
- @context[:current_path], # path
841
913
  raw_dir_args, # value
842
914
  dir_defn, # parent_type
843
915
  nil, # field
@@ -869,36 +941,48 @@ module GraphQL
869
941
  true
870
942
  end
871
943
 
872
- def set_all_interpreter_context(object, field, arguments, path)
873
- if object
874
- @context[:current_object] = @interpreter_context[:current_object] = object
875
- end
876
- if field
877
- @context[:current_field] = @interpreter_context[:current_field] = field
944
+ def get_current_runtime_state
945
+ current_state = Thread.current[:__graphql_runtime_info] ||= begin
946
+ per_query_state = {}
947
+ per_query_state.compare_by_identity
948
+ per_query_state
878
949
  end
879
- if arguments
880
- @context[:current_arguments] = @interpreter_context[:current_arguments] = arguments
881
- end
882
- if path
883
- @context[:current_path] = @interpreter_context[:current_path] = path
950
+
951
+ current_state[@query] ||= CurrentState.new
952
+ end
953
+
954
+ def minimal_after_lazy(value, &block)
955
+ if lazy?(value)
956
+ GraphQL::Execution::Lazy.new do
957
+ result = @schema.sync_lazy(value)
958
+ # The returned result might also be lazy, so check it, too
959
+ minimal_after_lazy(result, &block)
960
+ end
961
+ else
962
+ yield(value)
884
963
  end
885
964
  end
886
965
 
887
966
  # @param obj [Object] Some user-returned value that may want to be batched
888
- # @param path [Array<String>]
889
967
  # @param field [GraphQL::Schema::Field]
890
968
  # @param eager [Boolean] Set to `true` for mutation root fields only
891
969
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
892
970
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
893
- def after_lazy(lazy_obj, owner:, field:, path:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
971
+ def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
894
972
  if lazy?(lazy_obj)
895
- lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
896
- set_all_interpreter_context(owner_object, field, arguments, path)
973
+ orig_result = result
974
+ lazy = GraphQL::Execution::Lazy.new(field: field) do
975
+ st = get_current_runtime_state
976
+ st.current_object = owner_object
977
+ st.current_field = field
978
+ st.current_arguments = arguments
979
+ st.current_result_name = result_name
980
+ st.current_result = orig_result
897
981
  # Wrap the execution of _this_ method with tracing,
898
982
  # but don't wrap the continuation below
899
983
  inner_obj = begin
900
984
  if trace
901
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
985
+ @current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
902
986
  schema.sync_lazy(lazy_obj)
903
987
  end
904
988
  else
@@ -919,11 +1003,17 @@ module GraphQL
919
1003
  if eager
920
1004
  lazy.value
921
1005
  else
922
- set_result(result, result_name, lazy)
1006
+ set_result(result, result_name, lazy, false, false) # is_non_null is irrelevant here
1007
+ current_depth = 0
1008
+ while result
1009
+ current_depth += 1
1010
+ result = result.graphql_parent
1011
+ end
1012
+ @lazies_at_depth[current_depth] << lazy
923
1013
  lazy
924
1014
  end
925
1015
  else
926
- set_all_interpreter_context(owner_object, field, arguments, path)
1016
+ # Don't need to reset state here because it _wasn't_ lazy.
927
1017
  yield(lazy_obj)
928
1018
  end
929
1019
  end
@@ -937,27 +1027,25 @@ module GraphQL
937
1027
  end
938
1028
  end
939
1029
 
940
- # Set this pair in the Query context, but also in the interpeter namespace,
941
- # for compatibility.
942
- def set_interpreter_context(key, value)
943
- @interpreter_context[key] = value
944
- @context[key] = value
945
- end
946
-
947
- def delete_interpreter_context(key)
948
- @interpreter_context.delete(key)
949
- @context.delete(key)
1030
+ def delete_all_interpreter_context
1031
+ per_query_state = Thread.current[:__graphql_runtime_info]
1032
+ if per_query_state
1033
+ per_query_state.delete(@query)
1034
+ if per_query_state.size == 0
1035
+ Thread.current[:__graphql_runtime_info] = nil
1036
+ end
1037
+ end
1038
+ nil
950
1039
  end
951
1040
 
952
- def resolve_type(type, value, path)
953
- trace_payload = { context: context, type: type, object: value, path: path }
954
- resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do
1041
+ def resolve_type(type, value)
1042
+ resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do
955
1043
  query.resolve_type(type, value)
956
1044
  end
957
1045
 
958
1046
  if lazy?(resolved_type)
959
1047
  GraphQL::Execution::Lazy.new do
960
- query.trace("resolve_type_lazy", trace_payload) do
1048
+ @current_trace.resolve_type_lazy(query: query, type: type, object: value) do
961
1049
  schema.sync_lazy(resolved_type)
962
1050
  end
963
1051
  end
@@ -966,14 +1054,13 @@ module GraphQL
966
1054
  end
967
1055
  end
968
1056
 
969
- def authorized_new(type, value, context)
970
- type.authorized_new(value, context)
971
- end
972
-
973
1057
  def lazy?(object)
974
- @lazy_cache.fetch(object.class) {
975
- @lazy_cache[object.class] = @schema.lazy?(object)
976
- }
1058
+ obj_class = object.class
1059
+ is_lazy = @lazy_cache[obj_class]
1060
+ if is_lazy.nil?
1061
+ is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object)
1062
+ end
1063
+ is_lazy
977
1064
  end
978
1065
  end
979
1066
  end