graphql 2.0.16 → 2.0.18

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/ast/visitor.rb +42 -35
  3. data/lib/graphql/analysis/ast.rb +2 -2
  4. data/lib/graphql/backtrace/tracer.rb +1 -1
  5. data/lib/graphql/execution/interpreter/resolve.rb +19 -0
  6. data/lib/graphql/execution/interpreter/runtime.rb +106 -88
  7. data/lib/graphql/execution/interpreter.rb +14 -9
  8. data/lib/graphql/execution/lazy.rb +6 -12
  9. data/lib/graphql/execution/multiplex.rb +2 -1
  10. data/lib/graphql/graphql_ext.bundle +0 -0
  11. data/lib/graphql/introspection/directive_type.rb +2 -2
  12. data/lib/graphql/introspection/field_type.rb +1 -1
  13. data/lib/graphql/introspection/schema_type.rb +2 -2
  14. data/lib/graphql/introspection/type_type.rb +5 -5
  15. data/lib/graphql/language/lexer.rb +216 -1505
  16. data/lib/graphql/language/lexer.ri +744 -0
  17. data/lib/graphql/language/nodes.rb +39 -31
  18. data/lib/graphql/language/parser.rb +9 -9
  19. data/lib/graphql/language/parser.y +9 -9
  20. data/lib/graphql/language/visitor.rb +191 -83
  21. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  22. data/lib/graphql/query/context.rb +45 -11
  23. data/lib/graphql/query.rb +15 -2
  24. data/lib/graphql/schema/argument.rb +0 -4
  25. data/lib/graphql/schema/directive.rb +12 -2
  26. data/lib/graphql/schema/enum.rb +24 -17
  27. data/lib/graphql/schema/enum_value.rb +5 -3
  28. data/lib/graphql/schema/field.rb +53 -45
  29. data/lib/graphql/schema/interface.rb +0 -10
  30. data/lib/graphql/schema/late_bound_type.rb +2 -0
  31. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -14
  32. data/lib/graphql/schema/member/has_arguments.rb +104 -57
  33. data/lib/graphql/schema/member/has_deprecation_reason.rb +3 -4
  34. data/lib/graphql/schema/member/has_fields.rb +14 -2
  35. data/lib/graphql/schema/member/has_interfaces.rb +49 -8
  36. data/lib/graphql/schema/member/has_validators.rb +31 -5
  37. data/lib/graphql/schema/member/type_system_helpers.rb +17 -0
  38. data/lib/graphql/schema/object.rb +2 -4
  39. data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
  40. data/lib/graphql/schema/timeout.rb +23 -27
  41. data/lib/graphql/schema/warden.rb +26 -4
  42. data/lib/graphql/schema.rb +37 -19
  43. data/lib/graphql/static_validation/literal_validator.rb +15 -1
  44. data/lib/graphql/static_validation/validator.rb +1 -1
  45. data/lib/graphql/subscriptions/event.rb +2 -7
  46. data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
  47. data/lib/graphql/tracing/appoptics_trace.rb +231 -0
  48. data/lib/graphql/tracing/appsignal_trace.rb +66 -0
  49. data/lib/graphql/tracing/data_dog_trace.rb +148 -0
  50. data/lib/graphql/tracing/new_relic_trace.rb +75 -0
  51. data/lib/graphql/tracing/notifications_trace.rb +41 -0
  52. data/lib/graphql/tracing/platform_trace.rb +107 -0
  53. data/lib/graphql/tracing/platform_tracing.rb +15 -3
  54. data/lib/graphql/tracing/prometheus_trace.rb +89 -0
  55. data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
  56. data/lib/graphql/tracing/scout_trace.rb +72 -0
  57. data/lib/graphql/tracing/statsd_trace.rb +56 -0
  58. data/lib/graphql/tracing.rb +136 -39
  59. data/lib/graphql/type_kinds.rb +6 -3
  60. data/lib/graphql/types/relay/connection_behaviors.rb +0 -4
  61. data/lib/graphql/types/relay/edge_behaviors.rb +0 -4
  62. data/lib/graphql/types/string.rb +1 -1
  63. data/lib/graphql/version.rb +1 -1
  64. data/lib/graphql.rb +7 -8
  65. metadata +15 -4
  66. data/lib/graphql/language/lexer.rl +0 -280
@@ -20,6 +20,15 @@ module GraphQL
20
20
  @graphql_metadata = nil
21
21
  end
22
22
 
23
+ def path
24
+ @path ||= build_path([])
25
+ end
26
+
27
+ def build_path(path_array)
28
+ graphql_result_name && path_array.unshift(graphql_result_name)
29
+ @graphql_parent ? @graphql_parent.build_path(path_array) : path_array
30
+ end
31
+
23
32
  attr_accessor :graphql_dead
24
33
  attr_reader :graphql_parent, :graphql_result_name
25
34
 
@@ -157,9 +166,10 @@ module GraphQL
157
166
  info
158
167
  end
159
168
 
160
- def initialize(query:)
169
+ def initialize(query:, lazies_at_depth:)
161
170
  @query = query
162
171
  @dataloader = query.multiplex.dataloader
172
+ @lazies_at_depth = lazies_at_depth
163
173
  @schema = query.schema
164
174
  @context = query.context
165
175
  @multiplex_context = query.multiplex.context
@@ -208,8 +218,7 @@ module GraphQL
208
218
  root_operation = query.selected_operation
209
219
  root_op_type = root_operation.operation_type || "query"
210
220
  root_type = schema.root_type_for_operation(root_op_type)
211
- path = []
212
- set_all_interpreter_context(query.root_value, nil, nil, path)
221
+ set_all_interpreter_context(query.root_value, nil, nil, nil, @response)
213
222
  object_proxy = authorized_new(root_type, query.root_value, context)
214
223
  object_proxy = schema.sync_lazy(object_proxy)
215
224
 
@@ -236,10 +245,9 @@ module GraphQL
236
245
  end
237
246
 
238
247
  @dataloader.append_job {
239
- set_all_interpreter_context(query.root_value, nil, nil, path)
248
+ set_all_interpreter_context(query.root_value, nil, nil, nil, selection_response)
240
249
  call_method_on_directives(:resolve, object_proxy, selections.graphql_directives) do
241
250
  evaluate_selections(
242
- path,
243
251
  object_proxy,
244
252
  root_type,
245
253
  root_op_type == "mutation",
@@ -253,10 +261,7 @@ module GraphQL
253
261
  end
254
262
  end
255
263
  end
256
- delete_interpreter_context(:current_path)
257
- delete_interpreter_context(:current_field)
258
- delete_interpreter_context(:current_object)
259
- delete_interpreter_context(:current_arguments)
264
+ delete_all_interpreter_context
260
265
  nil
261
266
  end
262
267
 
@@ -358,15 +363,15 @@ module GraphQL
358
363
  NO_ARGS = {}.freeze
359
364
 
360
365
  # @return [void]
361
- def evaluate_selections(path, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
362
- set_all_interpreter_context(owner_object, nil, nil, path)
366
+ 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)
363
368
 
364
369
  finished_jobs = 0
365
370
  enqueued_jobs = gathered_selections.size
366
371
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
367
372
  @dataloader.append_job {
368
373
  evaluate_selection(
369
- path, result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
374
+ result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
370
375
  )
371
376
  finished_jobs += 1
372
377
  if target_result && finished_jobs == enqueued_jobs
@@ -378,10 +383,8 @@ module GraphQL
378
383
  selections_result
379
384
  end
380
385
 
381
- attr_reader :progress_path
382
-
383
386
  # @return [void]
384
- 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
387
+ 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
385
388
  return if dead_result?(selections_result)
386
389
  # As a performance optimization, the hash key will be a `Node` if
387
390
  # there's only one selection of the field. But if there are multiple
@@ -409,10 +412,8 @@ module GraphQL
409
412
  raise "Invariant: no field for #{owner_type}.#{field_name}"
410
413
  end
411
414
  end
412
- return_type = field_defn.type
413
415
 
414
- next_path = path + [result_name]
415
- next_path.freeze
416
+ return_type = field_defn.type
416
417
 
417
418
  # This seems janky, but we need to know
418
419
  # the field's return type at this path in order
@@ -421,7 +422,7 @@ module GraphQL
421
422
  (selections_result.graphql_non_null_field_names ||= []).push(result_name)
422
423
  end
423
424
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
424
- set_all_interpreter_context(nil, field_defn, nil, next_path)
425
+ set_all_interpreter_context(nil, field_defn, nil, result_name, selections_result)
425
426
  object = owner_object
426
427
 
427
428
  if is_introspection
@@ -431,20 +432,19 @@ module GraphQL
431
432
  total_args_count = field_defn.arguments(context).size
432
433
  if total_args_count == 0
433
434
  resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
434
- 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)
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)
435
436
  else
436
437
  # TODO remove all arguments(...) usages?
437
438
  @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
438
- 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)
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)
439
440
  end
440
441
  end
441
442
  end
442
443
 
443
- 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
444
- return_type = field_defn.type
445
- 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|
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
445
+ 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
446
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
447
- continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
447
+ continue_value(resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
448
448
  next
449
449
  end
450
450
 
@@ -460,9 +460,9 @@ module GraphQL
460
460
  when :ast_node
461
461
  extra_args[:ast_node] = ast_node
462
462
  when :execution_errors
463
- extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
463
+ extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, current_path)
464
464
  when :path
465
- extra_args[:path] = next_path
465
+ extra_args[:path] = current_path
466
466
  when :lookahead
467
467
  if !field_ast_nodes
468
468
  field_ast_nodes = [ast_node]
@@ -489,7 +489,7 @@ module GraphQL
489
489
  resolved_arguments.keyword_arguments
490
490
  end
491
491
 
492
- set_all_interpreter_context(nil, nil, resolved_arguments, nil)
492
+ set_all_interpreter_context(nil, nil, resolved_arguments, result_name, selection_result)
493
493
 
494
494
  # Optimize for the case that field is selected only once
495
495
  if field_ast_nodes.nil? || field_ast_nodes.size == 1
@@ -507,7 +507,7 @@ module GraphQL
507
507
  field_result = call_method_on_directives(:resolve, object, directives) do
508
508
  # Actually call the field resolver and capture the result
509
509
  app_result = begin
510
- 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
510
+ query.current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
511
511
  field_defn.resolve(object, kwarg_arguments, context)
512
512
  end
513
513
  rescue GraphQL::ExecutionError => err
@@ -519,10 +519,10 @@ module GraphQL
519
519
  ex_err
520
520
  end
521
521
  end
522
- 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|
523
- continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
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
524
  if HALT != continue_value
525
- continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
525
+ continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
526
526
  end
527
527
  end
528
528
  end
@@ -587,8 +587,30 @@ module GraphQL
587
587
  end
588
588
  end
589
589
 
590
+ 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])
596
+ path = path.dup
597
+ path.push(rn)
598
+ end
599
+ path
600
+ end
601
+
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
+
590
612
  HALT = Object.new
591
- def continue_value(path, value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
613
+ def continue_value(value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
592
614
  case value
593
615
  when nil
594
616
  if is_non_null
@@ -607,7 +629,7 @@ module GraphQL
607
629
  # every time.
608
630
  if value.is_a?(GraphQL::ExecutionError)
609
631
  if selection_result.nil? || !dead_result?(selection_result)
610
- value.path ||= path
632
+ value.path ||= current_path
611
633
  value.ast_node ||= ast_node
612
634
  context.errors << value
613
635
  if selection_result
@@ -615,6 +637,16 @@ module GraphQL
615
637
  end
616
638
  end
617
639
  HALT
640
+ elsif value.is_a?(GraphQL::UnauthorizedFieldError)
641
+ value.field ||= field
642
+ # this hook might raise & crash, or it might return
643
+ # a replacement value
644
+ next_value = begin
645
+ schema.unauthorized_field(value)
646
+ rescue GraphQL::ExecutionError => err
647
+ err
648
+ end
649
+ continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
618
650
  elsif value.is_a?(GraphQL::UnauthorizedError)
619
651
  # this hook might raise & crash, or it might return
620
652
  # a replacement value
@@ -623,7 +655,7 @@ module GraphQL
623
655
  rescue GraphQL::ExecutionError => err
624
656
  err
625
657
  end
626
- continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
658
+ continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
627
659
  elsif GraphQL::Execution::SKIP == value
628
660
  # It's possible a lazy was already written here
629
661
  case selection_result
@@ -649,7 +681,7 @@ module GraphQL
649
681
  if selection_result.nil? || !dead_result?(selection_result)
650
682
  value.each_with_index do |error, index|
651
683
  error.ast_node ||= ast_node
652
- error.path ||= path + (list_type_at_all ? [index] : [])
684
+ error.path ||= current_path + (list_type_at_all ? [index] : [])
653
685
  context.errors << error
654
686
  end
655
687
  if selection_result
@@ -682,7 +714,7 @@ module GraphQL
682
714
  # Location information from `path` and `ast_node`.
683
715
  #
684
716
  # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
685
- 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
717
+ 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
686
718
  if current_type.non_null?
687
719
  current_type = current_type.of_type
688
720
  is_non_null = true
@@ -698,8 +730,8 @@ module GraphQL
698
730
  set_result(selection_result, result_name, r)
699
731
  r
700
732
  when "UNION", "INTERFACE"
701
- resolved_type_or_lazy = resolve_type(current_type, value, path)
702
- 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|
733
+ resolved_type_or_lazy = resolve_type(current_type, value)
734
+ after_lazy(resolved_type_or_lazy, owner: current_type, 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|
703
735
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
704
736
  resolved_type, resolved_value = resolved_type_result
705
737
  else
@@ -716,7 +748,7 @@ module GraphQL
716
748
  set_result(selection_result, result_name, nil)
717
749
  nil
718
750
  else
719
- continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
751
+ continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
720
752
  end
721
753
  end
722
754
  when "OBJECT"
@@ -725,8 +757,8 @@ module GraphQL
725
757
  rescue GraphQL::ExecutionError => err
726
758
  err
727
759
  end
728
- 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|
729
- continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
760
+ after_lazy(object_proxy, owner: current_type, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
761
+ continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
730
762
  if HALT != continue_value
731
763
  response_hash = GraphQLResultHash.new(result_name, selection_result)
732
764
  set_result(selection_result, result_name, response_hash)
@@ -747,10 +779,10 @@ module GraphQL
747
779
  this_result = response_hash
748
780
  final_result = nil
749
781
  end
750
- set_all_interpreter_context(continue_value, nil, nil, path) # reset this mutable state
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
751
784
  call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
752
785
  evaluate_selections(
753
- path,
754
786
  continue_value,
755
787
  current_type,
756
788
  false,
@@ -771,40 +803,27 @@ module GraphQL
771
803
  response_list = GraphQLResultArray.new(result_name, selection_result)
772
804
  response_list.graphql_non_null_list_items = inner_type.non_null?
773
805
  set_result(selection_result, result_name, response_list)
774
- result_was_set = false
775
806
  idx = 0
776
807
  list_value = begin
777
808
  value.each do |inner_value|
778
809
  break if dead_result?(response_list)
779
- if !result_was_set
780
- # Don't set the result unless `.each` is successful
781
- set_result(selection_result, result_name, response_list)
782
- result_was_set = true
783
- end
784
- next_path = path + [idx]
785
810
  this_idx = idx
786
- next_path.freeze
787
811
  idx += 1
788
812
  if use_dataloader_job
789
813
  @dataloader.append_job do
790
- resolve_list_item(inner_value, inner_type, next_path, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
814
+ resolve_list_item(inner_value, inner_type, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
791
815
  end
792
816
  else
793
- resolve_list_item(inner_value, inner_type, next_path, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
817
+ resolve_list_item(inner_value, inner_type, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
794
818
  end
795
819
  end
796
- # Maybe the list was empty and the block was never called.
797
- if !result_was_set
798
- set_result(selection_result, result_name, response_list)
799
- result_was_set = true
800
- end
801
820
 
802
821
  response_list
803
822
  rescue NoMethodError => err
804
823
  # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
805
824
  if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
806
825
  # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
807
- raise ListResultFailedError.new(value: value, field: field, path: path)
826
+ raise ListResultFailedError.new(value: value, field: field, path: current_path)
808
827
  else
809
828
  # This was some other NoMethodError -- let it bubble to reveal the real error.
810
829
  raise
@@ -819,20 +838,20 @@ module GraphQL
819
838
  end
820
839
  end
821
840
 
822
- continue_value(path, list_value, owner_type, field, inner_type.non_null?, ast_node, result_name, selection_result)
841
+ continue_value(list_value, owner_type, field, inner_type.non_null?, ast_node, result_name, selection_result)
823
842
  else
824
843
  raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
825
844
  end
826
845
  end
827
846
 
828
- 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
829
- set_all_interpreter_context(nil, nil, nil, next_path)
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)
830
849
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
831
850
  # This will update `response_list` with the lazy
832
- 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|
833
- continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
851
+ 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)
834
853
  if HALT != continue_value
835
- continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
854
+ continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
836
855
  end
837
856
  end
838
857
  end
@@ -851,7 +870,6 @@ module GraphQL
851
870
  dir_defn = @schema_directives.fetch(dir_node.name)
852
871
  raw_dir_args = arguments(nil, dir_defn, dir_node)
853
872
  dir_args = continue_value(
854
- @context[:current_path], # path
855
873
  raw_dir_args, # value
856
874
  dir_defn, # parent_type
857
875
  nil, # field
@@ -883,7 +901,7 @@ module GraphQL
883
901
  true
884
902
  end
885
903
 
886
- def set_all_interpreter_context(object, field, arguments, path)
904
+ def set_all_interpreter_context(object, field, arguments, result_name, result)
887
905
  ti = thread_info
888
906
  if object
889
907
  ti[:current_object] = object
@@ -894,26 +912,26 @@ module GraphQL
894
912
  if arguments
895
913
  ti[:current_arguments] = arguments
896
914
  end
897
- if path
898
- ti[:current_path] = path
915
+ ti[:current_result_name] = result_name
916
+ if result
917
+ ti[:current_result] = result
899
918
  end
900
919
  end
901
920
 
902
921
  # @param obj [Object] Some user-returned value that may want to be batched
903
- # @param path [Array<String>]
904
922
  # @param field [GraphQL::Schema::Field]
905
923
  # @param eager [Boolean] Set to `true` for mutation root fields only
906
924
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
907
925
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
908
- def after_lazy(lazy_obj, owner:, field:, path:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
926
+ def after_lazy(lazy_obj, owner:, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
909
927
  if lazy?(lazy_obj)
910
- lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
911
- set_all_interpreter_context(owner_object, field, arguments, path)
928
+ lazy = GraphQL::Execution::Lazy.new(field: field) do
929
+ set_all_interpreter_context(owner_object, field, arguments, result_name, result)
912
930
  # Wrap the execution of _this_ method with tracing,
913
931
  # but don't wrap the continuation below
914
932
  inner_obj = begin
915
933
  if trace
916
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
934
+ query.current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
917
935
  schema.sync_lazy(lazy_obj)
918
936
  end
919
937
  else
@@ -935,10 +953,11 @@ module GraphQL
935
953
  lazy.value
936
954
  else
937
955
  set_result(result, result_name, lazy)
956
+ @lazies_at_depth[current_depth] << lazy
938
957
  lazy
939
958
  end
940
959
  else
941
- set_all_interpreter_context(owner_object, field, arguments, path)
960
+ set_all_interpreter_context(owner_object, field, arguments, result_name, result)
942
961
  yield(lazy_obj)
943
962
  end
944
963
  end
@@ -952,25 +971,24 @@ module GraphQL
952
971
  end
953
972
  end
954
973
 
955
- # Set this pair in the Query context, but also in the interpeter namespace,
956
- # for compatibility.
957
- def set_interpreter_context(key, value)
958
- thread_info[key] = value
959
- end
960
-
961
- def delete_interpreter_context(key)
962
- (ti = thread_info) && ti.delete(key)
974
+ 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
963
982
  end
964
983
 
965
- def resolve_type(type, value, path)
966
- trace_payload = { context: context, type: type, object: value, path: path }
967
- resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do
984
+ def resolve_type(type, value)
985
+ resolved_type, resolved_value = query.current_trace.resolve_type(query: query, type: type, object: value) do
968
986
  query.resolve_type(type, value)
969
987
  end
970
988
 
971
989
  if lazy?(resolved_type)
972
990
  GraphQL::Execution::Lazy.new do
973
- query.trace("resolve_type_lazy", trace_payload) do
991
+ query.current_trace.resolve_type_lazy(query: query, type: type, object: value) do
974
992
  schema.sync_lazy(resolved_type)
975
993
  end
976
994
  end
@@ -34,11 +34,12 @@ module GraphQL
34
34
  end
35
35
 
36
36
  multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
37
- multiplex.trace("execute_multiplex", { multiplex: multiplex }) do
37
+ multiplex.current_trace.execute_multiplex(multiplex: multiplex) do
38
38
  schema = multiplex.schema
39
39
  queries = multiplex.queries
40
40
  query_instrumenters = schema.instrumenters[:query]
41
41
  multiplex_instrumenters = schema.instrumenters[:multiplex]
42
+ lazies_at_depth = Hash.new { |h, k| h[k] = [] }
42
43
 
43
44
  # First, run multiplex instrumentation, then query instrumentation for each query
44
45
  call_hooks(multiplex_instrumenters, multiplex, :before_multiplex, :after_multiplex) do
@@ -67,10 +68,10 @@ module GraphQL
67
68
  # Although queries in a multiplex _share_ an Interpreter instance,
68
69
  # they also have another item of state, which is private to that query
69
70
  # in particular, assign it here:
70
- runtime = Runtime.new(query: query)
71
+ runtime = Runtime.new(query: query, lazies_at_depth: lazies_at_depth)
71
72
  query.context.namespace(:interpreter_runtime)[:runtime] = runtime
72
73
 
73
- query.trace("execute_query", {query: query}) do
74
+ query.current_trace.execute_query(query: query) do
74
75
  runtime.run_eager
75
76
  end
76
77
  rescue GraphQL::ExecutionError => err
@@ -95,16 +96,13 @@ module GraphQL
95
96
  runtime ? runtime.final_result : nil
96
97
  end
97
98
  final_values.compact!
98
- tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do
99
- Interpreter::Resolve.resolve_all(final_values, multiplex.dataloader)
99
+ tracer.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
100
+ Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader)
100
101
  end
101
102
  queries.each do |query|
102
103
  runtime = query.context.namespace(:interpreter_runtime)[:runtime]
103
104
  if runtime
104
- runtime.delete_interpreter_context(:current_path)
105
- runtime.delete_interpreter_context(:current_field)
106
- runtime.delete_interpreter_context(:current_object)
107
- runtime.delete_interpreter_context(:current_arguments)
105
+ runtime.delete_all_interpreter_context
108
106
  end
109
107
  end
110
108
  }
@@ -146,6 +144,13 @@ module GraphQL
146
144
  # Assign values here so that the query's `@executed` becomes true
147
145
  queries.map { |q| q.result_values ||= {} }
148
146
  raise
147
+ ensure
148
+ queries.map { |query|
149
+ runtime = query.context.namespace(:interpreter_runtime)[:runtime]
150
+ if runtime
151
+ runtime.delete_all_interpreter_context
152
+ end
153
+ }
149
154
  end
150
155
  end
151
156
  end
@@ -12,16 +12,14 @@ module GraphQL
12
12
  # - It has no error-catching functionality
13
13
  # @api private
14
14
  class Lazy
15
- attr_reader :path, :field
15
+ attr_reader :field
16
16
 
17
17
  # Create a {Lazy} which will get its inner value by calling the block
18
- # @param path [Array<String, Integer>]
19
18
  # @param field [GraphQL::Schema::Field]
20
19
  # @param get_value_func [Proc] a block to get the inner value (later)
21
- def initialize(path: nil, field: nil, &get_value_func)
20
+ def initialize(field: nil, &get_value_func)
22
21
  @get_value_func = get_value_func
23
22
  @resolved = false
24
- @path = path
25
23
  @field = field
26
24
  end
27
25
 
@@ -29,15 +27,11 @@ module GraphQL
29
27
  def value
30
28
  if !@resolved
31
29
  @resolved = true
32
- @value = begin
33
- v = @get_value_func.call
34
- if v.is_a?(Lazy)
35
- v = v.value
36
- end
37
- v
38
- rescue GraphQL::ExecutionError => err
39
- err
30
+ v = @get_value_func.call
31
+ if v.is_a?(Lazy)
32
+ v = v.value
40
33
  end
34
+ @value = v
41
35
  end
42
36
 
43
37
  # `SKIP` was made into a subclass of `GraphQL::Error` to improve runtime performance
@@ -25,13 +25,14 @@ module GraphQL
25
25
  class Multiplex
26
26
  include Tracing::Traceable
27
27
 
28
- attr_reader :context, :queries, :schema, :max_complexity, :dataloader
28
+ attr_reader :context, :queries, :schema, :max_complexity, :dataloader, :current_trace
29
29
 
30
30
  def initialize(schema:, queries:, context:, max_complexity:)
31
31
  @schema = schema
32
32
  @queries = queries
33
33
  @queries.each { |q| q.multiplex = self }
34
34
  @context = context
35
+ @current_trace = @context[:trace] || schema.new_trace(multiplex: self)
35
36
  @dataloader = @context[:dataloader] ||= @schema.dataloader_class.new
36
37
  @tracers = schema.tracers + (context[:tracers] || [])
37
38
  # Support `context: {backtrace: true}`
Binary file
@@ -11,8 +11,8 @@ module GraphQL
11
11
  "to the executor."
12
12
  field :name, String, null: false, method: :graphql_name
13
13
  field :description, String
14
- field :locations, [GraphQL::Schema::LateBoundType.new("__DirectiveLocation")], null: false
15
- field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false do
14
+ field :locations, [GraphQL::Schema::LateBoundType.new("__DirectiveLocation")], null: false, scope: false
15
+ field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false do
16
16
  argument :include_deprecated, Boolean, required: false, default_value: false
17
17
  end
18
18
  field :on_operation, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_operation?
@@ -7,7 +7,7 @@ module GraphQL
7
7
  "a name, potentially a list of arguments, and a return type."
8
8
  field :name, String, null: false
9
9
  field :description, String
10
- field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false do
10
+ field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false do
11
11
  argument :include_deprecated, Boolean, required: false, default_value: false
12
12
  end
13
13
  field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false
@@ -8,11 +8,11 @@ module GraphQL
8
8
  "available types and directives on the server, as well as the entry points for "\
9
9
  "query, mutation, and subscription operations."
10
10
 
11
- field :types, [GraphQL::Schema::LateBoundType.new("__Type")], "A list of all types supported by this server.", null: false
11
+ field :types, [GraphQL::Schema::LateBoundType.new("__Type")], "A list of all types supported by this server.", null: false, scope: false
12
12
  field :query_type, GraphQL::Schema::LateBoundType.new("__Type"), "The type that query operations will be rooted at.", null: false
13
13
  field :mutation_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server supports mutation, the type that mutation operations will be rooted at."
14
14
  field :subscription_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server support subscription, the type that subscription operations will be rooted at."
15
- field :directives, [GraphQL::Schema::LateBoundType.new("__Directive")], "A list of all directives supported by this server.", null: false
15
+ field :directives, [GraphQL::Schema::LateBoundType.new("__Directive")], "A list of all directives supported by this server.", null: false, scope: false
16
16
  field :description, String, resolver_method: :schema_description
17
17
 
18
18
  def schema_description