graphql 2.0.19 → 2.0.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/backtrace/trace.rb +96 -0
  3. data/lib/graphql/backtrace.rb +6 -1
  4. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  5. data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -3
  6. data/lib/graphql/execution/interpreter/runtime.rb +206 -171
  7. data/lib/graphql/execution/interpreter.rb +1 -1
  8. data/lib/graphql/filter.rb +7 -2
  9. data/lib/graphql/language/document_from_schema_definition.rb +25 -9
  10. data/lib/graphql/language/nodes.rb +25 -7
  11. data/lib/graphql/language/parser.rb +476 -459
  12. data/lib/graphql/language/parser.y +6 -2
  13. data/lib/graphql/pagination/connection.rb +5 -5
  14. data/lib/graphql/query/context.rb +13 -12
  15. data/lib/graphql/query/null_context.rb +1 -1
  16. data/lib/graphql/query.rb +9 -5
  17. data/lib/graphql/schema/argument.rb +7 -9
  18. data/lib/graphql/schema/build_from_definition.rb +15 -3
  19. data/lib/graphql/schema/enum_value.rb +2 -5
  20. data/lib/graphql/schema/field.rb +16 -13
  21. data/lib/graphql/schema/field_extension.rb +1 -4
  22. data/lib/graphql/schema/find_inherited_value.rb +2 -7
  23. data/lib/graphql/schema/member/base_dsl_methods.rb +13 -11
  24. data/lib/graphql/schema/member/has_arguments.rb +1 -1
  25. data/lib/graphql/schema/member/has_ast_node.rb +12 -0
  26. data/lib/graphql/schema/member/has_directives.rb +15 -10
  27. data/lib/graphql/schema/member/has_fields.rb +81 -36
  28. data/lib/graphql/schema/member/has_validators.rb +2 -2
  29. data/lib/graphql/schema/member/relay_shortcuts.rb +19 -0
  30. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  31. data/lib/graphql/schema/resolver.rb +4 -4
  32. data/lib/graphql/schema/validator.rb +1 -1
  33. data/lib/graphql/schema/warden.rb +3 -1
  34. data/lib/graphql/schema.rb +41 -16
  35. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -4
  36. data/lib/graphql/static_validation/rules/fields_will_merge.rb +2 -2
  37. data/lib/graphql/tracing/appsignal_trace.rb +13 -2
  38. data/lib/graphql/tracing/legacy_trace.rb +65 -0
  39. data/lib/graphql/tracing/notifications_trace.rb +2 -1
  40. data/lib/graphql/tracing/platform_trace.rb +21 -19
  41. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +1 -1
  42. data/lib/graphql/tracing/trace.rb +75 -0
  43. data/lib/graphql/tracing.rb +3 -123
  44. data/lib/graphql/types/relay/connection_behaviors.rb +24 -2
  45. data/lib/graphql/types/relay/edge_behaviors.rb +16 -2
  46. data/lib/graphql/types/relay/node_behaviors.rb +7 -1
  47. data/lib/graphql/types/relay/page_info_behaviors.rb +7 -2
  48. data/lib/graphql/types/relay.rb +0 -1
  49. data/lib/graphql/version.rb +1 -1
  50. data/lib/graphql.rb +11 -7
  51. metadata +5 -4
  52. data/lib/graphql/language/lexer.ri +0 -744
  53. data/lib/graphql/types/relay/default_relay.rb +0 -27
@@ -8,6 +8,18 @@ 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
25
  def initialize(result_name, parent_result)
@@ -54,7 +66,7 @@ module GraphQL
54
66
 
55
67
  attr_accessor :graphql_merged_into
56
68
 
57
- def []=(key, value)
69
+ def set_leaf(key, value)
58
70
  # This is a hack.
59
71
  # Basically, this object is merged into the root-level result at some point.
60
72
  # But the problem is, some lazies are created whose closures retain reference to _this_
@@ -64,23 +76,27 @@ module GraphQL
64
76
  # In order to return a proper partial result (eg, for a directive), we have to update this object, too.
65
77
  # Yowza.
66
78
  if (t = @graphql_merged_into)
67
- t[key] = value
79
+ t.set_leaf(key, value)
68
80
  end
69
81
 
70
- if value.respond_to?(:graphql_result_data)
71
- @graphql_result_data[key] = value.graphql_result_data
72
- # If we encounter some part of this response that requires metadata tracking,
73
- # then create the metadata hash if necessary. It will be kept up-to-date after this.
74
- (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
75
- else
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
- end
82
+ @graphql_result_data[key] = value
83
+ # keep this up-to-date if it's been initialized
84
+ @graphql_metadata && @graphql_metadata[key] = value
80
85
 
81
86
  value
82
87
  end
83
88
 
89
+ def set_child_result(key, value)
90
+ if (t = @graphql_merged_into)
91
+ t.set_child_result(key, value)
92
+ end
93
+ @graphql_result_data[key] = value.graphql_result_data
94
+ # If we encounter some part of this response that requires metadata tracking,
95
+ # then create the metadata hash if necessary. It will be kept up-to-date after this.
96
+ (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
97
+ value
98
+ end
99
+
84
100
  def delete(key)
85
101
  @graphql_metadata && @graphql_metadata.delete(key)
86
102
  @graphql_result_data.delete(key)
@@ -101,6 +117,29 @@ module GraphQL
101
117
  def [](k)
102
118
  (@graphql_metadata || @graphql_result_data)[k]
103
119
  end
120
+
121
+ def merge_into(into_result)
122
+ self.each do |key, value|
123
+ case value
124
+ when GraphQLResultHash
125
+ next_into = into_result[key]
126
+ if next_into
127
+ value.merge_into(next_into)
128
+ else
129
+ into_result.set_child_result(key, value)
130
+ end
131
+ when GraphQLResultArray
132
+ # There's no special handling of arrays because currently, there's no way to split the execution
133
+ # of a list over several concurrent flows.
134
+ next_result.set_child_result(key, value)
135
+ else
136
+ # We have to assume that, since this passed the `fields_will_merge` selection,
137
+ # that the old and new values are the same.
138
+ into_result.set_leaf(key, value)
139
+ end
140
+ end
141
+ @graphql_merged_into = into_result
142
+ end
104
143
  end
105
144
 
106
145
  class GraphQLResultArray
@@ -123,19 +162,25 @@ module GraphQL
123
162
  @graphql_result_data.delete_at(delete_at_index)
124
163
  end
125
164
 
126
- def []=(idx, value)
165
+ def set_leaf(idx, value)
127
166
  if @skip_indices
128
167
  offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
129
168
  idx -= offset_by
130
169
  end
131
- if value.respond_to?(:graphql_result_data)
132
- @graphql_result_data[idx] = value.graphql_result_data
133
- (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
134
- else
135
- @graphql_result_data[idx] = value
136
- @graphql_metadata && @graphql_metadata[idx] = value
137
- end
170
+ @graphql_result_data[idx] = value
171
+ @graphql_metadata && @graphql_metadata[idx] = value
172
+ value
173
+ end
138
174
 
175
+ def set_child_result(idx, value)
176
+ if @skip_indices
177
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
178
+ idx -= offset_by
179
+ end
180
+ @graphql_result_data[idx] = value.graphql_result_data
181
+ # If we encounter some part of this response that requires metadata tracking,
182
+ # then create the metadata hash if necessary. It will be kept up-to-date after this.
183
+ (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
139
184
  value
140
185
  end
141
186
 
@@ -157,15 +202,6 @@ module GraphQL
157
202
  # @return [GraphQL::Query::Context]
158
203
  attr_reader :context
159
204
 
160
- def thread_info
161
- info = Thread.current[:__graphql_runtime_info]
162
- if !info
163
- new_ti = {}
164
- info = Thread.current[:__graphql_runtime_info] = new_ti
165
- end
166
- info
167
- end
168
-
169
205
  def initialize(query:, lazies_at_depth:)
170
206
  @query = query
171
207
  @dataloader = query.multiplex.dataloader
@@ -218,7 +254,9 @@ module GraphQL
218
254
  root_operation = query.selected_operation
219
255
  root_op_type = root_operation.operation_type || "query"
220
256
  root_type = schema.root_type_for_operation(root_op_type)
221
- set_all_interpreter_context(query.root_value, nil, nil, nil, @response)
257
+ st = get_current_runtime_state
258
+ st.current_object = query.root_value
259
+ st.current_result = @response
222
260
  object_proxy = authorized_new(root_type, query.root_value, context)
223
261
  object_proxy = schema.sync_lazy(object_proxy)
224
262
 
@@ -245,7 +283,10 @@ module GraphQL
245
283
  end
246
284
 
247
285
  @dataloader.append_job {
248
- set_all_interpreter_context(query.root_value, nil, nil, nil, selection_response)
286
+ st = get_current_runtime_state
287
+ st.current_object = query.root_value
288
+ st.current_result = selection_response
289
+
249
290
  call_method_on_directives(:resolve, object_proxy, selections.graphql_directives) do
250
291
  evaluate_selections(
251
292
  object_proxy,
@@ -265,28 +306,6 @@ module GraphQL
265
306
  nil
266
307
  end
267
308
 
268
- # @return [void]
269
- def deep_merge_selection_result(from_result, into_result)
270
- from_result.each do |key, value|
271
- if !into_result.key?(key)
272
- into_result[key] = value
273
- else
274
- case value
275
- when GraphQLResultHash
276
- deep_merge_selection_result(value, into_result[key])
277
- else
278
- # We have to assume that, since this passed the `fields_will_merge` selection,
279
- # that the old and new values are the same.
280
- # There's no special handling of arrays because currently, there's no way to split the execution
281
- # of a list over several concurrent flows.
282
- into_result[key] = value
283
- end
284
- end
285
- end
286
- from_result.graphql_merged_into = into_result
287
- nil
288
- end
289
-
290
309
  def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new)
291
310
  selections.each do |node|
292
311
  # Skip gathering this if the directive says so
@@ -360,11 +379,14 @@ module GraphQL
360
379
  selections_to_run || selections_by_name
361
380
  end
362
381
 
363
- NO_ARGS = {}.freeze
382
+ NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
364
383
 
365
384
  # @return [void]
366
385
  def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
367
- set_all_interpreter_context(owner_object, nil, nil, nil, selections_result)
386
+ st = get_current_runtime_state
387
+ st.current_object = owner_object
388
+ st.current_result_name = nil
389
+ st.current_result = selections_result
368
390
 
369
391
  finished_jobs = 0
370
392
  enqueued_jobs = gathered_selections.size
@@ -375,7 +397,7 @@ module GraphQL
375
397
  )
376
398
  finished_jobs += 1
377
399
  if target_result && finished_jobs == enqueued_jobs
378
- deep_merge_selection_result(selections_result, target_result)
400
+ selections_result.merge_into(target_result)
379
401
  end
380
402
  }
381
403
  end
@@ -418,11 +440,16 @@ module GraphQL
418
440
  # This seems janky, but we need to know
419
441
  # the field's return type at this path in order
420
442
  # to propagate `null`
421
- if return_type.non_null?
443
+ return_type_non_null = return_type.non_null?
444
+ if return_type_non_null
422
445
  (selections_result.graphql_non_null_field_names ||= []).push(result_name)
423
446
  end
424
447
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
425
- set_all_interpreter_context(nil, field_defn, nil, result_name, selections_result)
448
+ st = get_current_runtime_state
449
+ st.current_field = field_defn
450
+ st.current_result = selections_result
451
+ st.current_result_name = result_name
452
+
426
453
  object = owner_object
427
454
 
428
455
  if is_introspection
@@ -432,25 +459,34 @@ module GraphQL
432
459
  total_args_count = field_defn.arguments(context).size
433
460
  if total_args_count == 0
434
461
  resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
435
- evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type)
462
+ if field_defn.extras.size == 0
463
+ evaluate_selection_with_resolved_keyword_args(
464
+ NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null
465
+ )
466
+ else
467
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
468
+ end
436
469
  else
437
- # TODO remove all arguments(...) usages?
438
470
  @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
439
- evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type)
471
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
440
472
  end
441
473
  end
442
474
  end
443
475
 
444
- 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
476
+ 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
445
477
  after_lazy(arguments, owner: owner_type, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
446
478
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
447
- continue_value(resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
479
+ continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
448
480
  next
449
481
  end
450
482
 
451
- kwarg_arguments = if resolved_arguments.empty? && field_defn.extras.empty?
452
- # We can avoid allocating the `{ Symbol => Object }` hash in this case
453
- NO_ARGS
483
+ kwarg_arguments = if field_defn.extras.empty?
484
+ if resolved_arguments.empty?
485
+ # We can avoid allocating the `{ Symbol => Object }` hash in this case
486
+ NO_ARGS
487
+ else
488
+ resolved_arguments.keyword_arguments
489
+ end
454
490
  else
455
491
  # Bundle up the extras, then make a new arguments instance
456
492
  # that includes the extras, too.
@@ -489,61 +525,70 @@ module GraphQL
489
525
  resolved_arguments.keyword_arguments
490
526
  end
491
527
 
492
- set_all_interpreter_context(nil, nil, resolved_arguments, result_name, selection_result)
528
+ 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)
529
+ end
530
+ end
493
531
 
494
- # Optimize for the case that field is selected only once
495
- if field_ast_nodes.nil? || field_ast_nodes.size == 1
496
- next_selections = ast_node.selections
497
- directives = ast_node.directives
498
- else
499
- next_selections = []
500
- directives = []
501
- field_ast_nodes.each { |f|
502
- next_selections.concat(f.selections)
503
- directives.concat(f.directives)
504
- }
505
- end
506
-
507
- field_result = call_method_on_directives(:resolve, object, directives) do
508
- # Actually call the field resolver and capture the result
509
- app_result = begin
510
- query.current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
511
- field_defn.resolve(object, kwarg_arguments, context)
512
- end
513
- rescue GraphQL::ExecutionError => err
514
- err
515
- rescue StandardError => err
516
- begin
517
- query.handle_or_reraise(err)
518
- rescue GraphQL::ExecutionError => ex_err
519
- ex_err
520
- end
532
+ 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
533
+ st = get_current_runtime_state
534
+ st.current_field = field_defn
535
+ st.current_object = object
536
+ st.current_arguments = resolved_arguments
537
+ st.current_result_name = result_name
538
+ st.current_result = selection_result
539
+ # Optimize for the case that field is selected only once
540
+ if field_ast_nodes.nil? || field_ast_nodes.size == 1
541
+ next_selections = ast_node.selections
542
+ directives = ast_node.directives
543
+ else
544
+ next_selections = []
545
+ directives = []
546
+ field_ast_nodes.each { |f|
547
+ next_selections.concat(f.selections)
548
+ directives.concat(f.directives)
549
+ }
550
+ end
551
+
552
+ field_result = call_method_on_directives(:resolve, object, directives) do
553
+ # Actually call the field resolver and capture the result
554
+ app_result = begin
555
+ query.current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
556
+ field_defn.resolve(object, kwarg_arguments, context)
521
557
  end
522
- after_lazy(app_result, owner: owner_type, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
523
- continue_value = continue_value(inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
524
- if HALT != continue_value
525
- continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
526
- end
558
+ rescue GraphQL::ExecutionError => err
559
+ err
560
+ rescue StandardError => err
561
+ begin
562
+ query.handle_or_reraise(err)
563
+ rescue GraphQL::ExecutionError => ex_err
564
+ ex_err
527
565
  end
528
566
  end
529
-
530
- # If this field is a root mutation field, immediately resolve
531
- # all of its child fields before moving on to the next root mutation field.
532
- # (Subselections of this mutation will still be resolved level-by-level.)
533
- if is_eager_field
534
- Interpreter::Resolve.resolve_all([field_result], @dataloader)
535
- else
536
- # Return this from `after_lazy` because it might be another lazy that needs to be resolved
537
- field_result
567
+ after_lazy(app_result, owner: owner_type, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
568
+ continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
569
+ if HALT != continue_value
570
+ continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
571
+ end
538
572
  end
539
573
  end
574
+
575
+ # If this field is a root mutation field, immediately resolve
576
+ # all of its child fields before moving on to the next root mutation field.
577
+ # (Subselections of this mutation will still be resolved level-by-level.)
578
+ if is_eager_field
579
+ Interpreter::Resolve.resolve_all([field_result], @dataloader)
580
+ else
581
+ # Return this from `after_lazy` because it might be another lazy that needs to be resolved
582
+ field_result
583
+ end
540
584
  end
541
585
 
586
+
542
587
  def dead_result?(selection_result)
543
- selection_result.graphql_dead || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
588
+ selection_result.graphql_dead # || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
544
589
  end
545
590
 
546
- def set_result(selection_result, result_name, value)
591
+ def set_result(selection_result, result_name, value, is_child_result)
547
592
  if !dead_result?(selection_result)
548
593
  if value.nil? &&
549
594
  ( # there are two conditions under which `nil` is not allowed in the response:
@@ -563,11 +608,13 @@ module GraphQL
563
608
  if parent.nil? # This is a top-level result hash
564
609
  @response = nil
565
610
  else
566
- set_result(parent, name_in_parent, nil)
611
+ set_result(parent, name_in_parent, nil, false)
567
612
  set_graphql_dead(selection_result)
568
613
  end
614
+ elsif is_child_result
615
+ selection_result.set_child_result(result_name, value)
569
616
  else
570
- selection_result[result_name] = value
617
+ selection_result.set_leaf(result_name, value)
571
618
  end
572
619
  end
573
620
  end
@@ -588,39 +635,28 @@ module GraphQL
588
635
  end
589
636
 
590
637
  def current_path
591
- ti = thread_info
592
- path = ti &&
593
- (result = ti[:current_result]) &&
594
- (result.path)
595
- if path && (rn = ti[:current_result_name])
638
+ st = get_current_runtime_state
639
+ result = st.current_result
640
+ path = result && result.path
641
+ if path && (rn = st.current_result_name)
596
642
  path = path.dup
597
643
  path.push(rn)
598
644
  end
599
645
  path
600
646
  end
601
647
 
602
- def current_depth
603
- ti = thread_info
604
- depth = 1
605
- result = ti[:current_result]
606
- while (result = result.graphql_parent)
607
- depth += 1
608
- end
609
- depth
610
- end
611
-
612
648
  HALT = Object.new
613
649
  def continue_value(value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
614
650
  case value
615
651
  when nil
616
652
  if is_non_null
617
- set_result(selection_result, result_name, nil) do
653
+ set_result(selection_result, result_name, nil, false) do
618
654
  # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
619
655
  err = parent_type::InvalidNullError.new(parent_type, field, value)
620
656
  schema.type_error(err, context)
621
657
  end
622
658
  else
623
- set_result(selection_result, result_name, nil)
659
+ set_result(selection_result, result_name, nil, false)
624
660
  end
625
661
  HALT
626
662
  when GraphQL::Error
@@ -633,7 +669,7 @@ module GraphQL
633
669
  value.ast_node ||= ast_node
634
670
  context.errors << value
635
671
  if selection_result
636
- set_result(selection_result, result_name, nil)
672
+ set_result(selection_result, result_name, nil, false)
637
673
  end
638
674
  end
639
675
  HALT
@@ -687,9 +723,9 @@ module GraphQL
687
723
  if selection_result
688
724
  if list_type_at_all
689
725
  result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
690
- set_result(selection_result, result_name, result_without_errors)
726
+ set_result(selection_result, result_name, result_without_errors, false)
691
727
  else
692
- set_result(selection_result, result_name, nil)
728
+ set_result(selection_result, result_name, nil, false)
693
729
  end
694
730
  end
695
731
  end
@@ -699,7 +735,7 @@ module GraphQL
699
735
  end
700
736
  when GraphQL::Execution::Interpreter::RawValue
701
737
  # Write raw value directly to the response without resolving nested objects
702
- set_result(selection_result, result_name, value.resolve)
738
+ set_result(selection_result, result_name, value.resolve, false)
703
739
  HALT
704
740
  else
705
741
  value
@@ -727,7 +763,7 @@ module GraphQL
727
763
  rescue StandardError => err
728
764
  schema.handle_or_reraise(context, err)
729
765
  end
730
- set_result(selection_result, result_name, r)
766
+ set_result(selection_result, result_name, r, false)
731
767
  r
732
768
  when "UNION", "INTERFACE"
733
769
  resolved_type_or_lazy = resolve_type(current_type, value)
@@ -745,7 +781,7 @@ module GraphQL
745
781
  err_class = current_type::UnresolvedTypeError
746
782
  type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
747
783
  schema.type_error(type_error, context)
748
- set_result(selection_result, result_name, nil)
784
+ set_result(selection_result, result_name, nil, false)
749
785
  nil
750
786
  else
751
787
  continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
@@ -761,7 +797,7 @@ module GraphQL
761
797
  continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
762
798
  if HALT != continue_value
763
799
  response_hash = GraphQLResultHash.new(result_name, selection_result)
764
- set_result(selection_result, result_name, response_hash)
800
+ set_result(selection_result, result_name, response_hash, true)
765
801
  gathered_selections = gather_selections(continue_value, current_type, next_selections)
766
802
  # There are two possibilities for `gathered_selections`:
767
803
  # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
@@ -779,8 +815,13 @@ module GraphQL
779
815
  this_result = response_hash
780
816
  final_result = nil
781
817
  end
782
- # Don't pass `result_name` here because it's already included in the new response hash
783
- set_all_interpreter_context(continue_value, nil, nil, nil, this_result) # reset this mutable state
818
+ # reset this mutable state
819
+ # Unset `result_name` here because it's already included in the new response hash
820
+ st = get_current_runtime_state
821
+ st.current_object = continue_value
822
+ st.current_result_name = nil
823
+ st.current_result = this_result
824
+
784
825
  call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
785
826
  evaluate_selections(
786
827
  continue_value,
@@ -800,21 +841,21 @@ module GraphQL
800
841
  inner_type = current_type.of_type
801
842
  # This is true for objects, unions, and interfaces
802
843
  use_dataloader_job = !inner_type.unwrap.kind.input?
844
+ inner_type_non_null = inner_type.non_null?
803
845
  response_list = GraphQLResultArray.new(result_name, selection_result)
804
- response_list.graphql_non_null_list_items = inner_type.non_null?
805
- set_result(selection_result, result_name, response_list)
846
+ response_list.graphql_non_null_list_items = inner_type_non_null
847
+ set_result(selection_result, result_name, response_list, true)
806
848
  idx = 0
807
849
  list_value = begin
808
850
  value.each do |inner_value|
809
- break if dead_result?(response_list)
810
851
  this_idx = idx
811
852
  idx += 1
812
853
  if use_dataloader_job
813
854
  @dataloader.append_job do
814
- resolve_list_item(inner_value, inner_type, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
855
+ 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)
815
856
  end
816
857
  else
817
- resolve_list_item(inner_value, inner_type, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
858
+ 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)
818
859
  end
819
860
  end
820
861
 
@@ -844,12 +885,14 @@ module GraphQL
844
885
  end
845
886
  end
846
887
 
847
- def resolve_list_item(inner_value, inner_type, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) # rubocop:disable Metrics/ParameterLists
848
- set_all_interpreter_context(nil, nil, nil, this_idx, response_list)
888
+ 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
889
+ st = get_current_runtime_state
890
+ st.current_result_name = this_idx
891
+ st.current_result = response_list
849
892
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
850
893
  # This will update `response_list` with the lazy
851
894
  after_lazy(inner_value, owner: inner_type, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
852
- continue_value = continue_value(inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
895
+ continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
853
896
  if HALT != continue_value
854
897
  continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
855
898
  end
@@ -901,21 +944,8 @@ module GraphQL
901
944
  true
902
945
  end
903
946
 
904
- def set_all_interpreter_context(object, field, arguments, result_name, result)
905
- ti = thread_info
906
- if object
907
- ti[:current_object] = object
908
- end
909
- if field
910
- ti[:current_field] = field
911
- end
912
- if arguments
913
- ti[:current_arguments] = arguments
914
- end
915
- ti[:current_result_name] = result_name
916
- if result
917
- ti[:current_result] = result
918
- end
947
+ def get_current_runtime_state
948
+ Thread.current[:__graphql_runtime_info] ||= CurrentState.new
919
949
  end
920
950
 
921
951
  # @param obj [Object] Some user-returned value that may want to be batched
@@ -925,8 +955,14 @@ module GraphQL
925
955
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
926
956
  def after_lazy(lazy_obj, owner:, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
927
957
  if lazy?(lazy_obj)
958
+ orig_result = result
928
959
  lazy = GraphQL::Execution::Lazy.new(field: field) do
929
- set_all_interpreter_context(owner_object, field, arguments, result_name, result)
960
+ st = get_current_runtime_state
961
+ st.current_object = owner_object
962
+ st.current_field = field
963
+ st.current_arguments = arguments
964
+ st.current_result_name = result_name
965
+ st.current_result = orig_result
930
966
  # Wrap the execution of _this_ method with tracing,
931
967
  # but don't wrap the continuation below
932
968
  inner_obj = begin
@@ -952,12 +988,17 @@ module GraphQL
952
988
  if eager
953
989
  lazy.value
954
990
  else
955
- set_result(result, result_name, lazy)
991
+ set_result(result, result_name, lazy, false)
992
+ current_depth = 0
993
+ while result
994
+ current_depth += 1
995
+ result = result.graphql_parent
996
+ end
956
997
  @lazies_at_depth[current_depth] << lazy
957
998
  lazy
958
999
  end
959
1000
  else
960
- set_all_interpreter_context(owner_object, field, arguments, result_name, result)
1001
+ # Don't need to reset state here because it _wasn't_ lazy.
961
1002
  yield(lazy_obj)
962
1003
  end
963
1004
  end
@@ -972,13 +1013,7 @@ module GraphQL
972
1013
  end
973
1014
 
974
1015
  def delete_all_interpreter_context
975
- if (ti = thread_info)
976
- ti.delete(:current_result)
977
- ti.delete(:current_result_name)
978
- ti.delete(:current_field)
979
- ti.delete(:current_object)
980
- ti.delete(:current_arguments)
981
- end
1016
+ Thread.current[:__graphql_runtime_info] = nil
982
1017
  end
983
1018
 
984
1019
  def resolve_type(type, value)
@@ -14,7 +14,7 @@ module GraphQL
14
14
  class << self
15
15
  # Used internally to signal that the query shouldn't be executed
16
16
  # @api private
17
- NO_OPERATION = {}.freeze
17
+ NO_OPERATION = GraphQL::EmptyObjects::EMPTY_HASH
18
18
 
19
19
  # @param schema [GraphQL::Schema]
20
20
  # @param queries [Array<GraphQL::Query, Hash>]