graphql 2.0.17 → 2.0.19

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/ast.rb +2 -2
  3. data/lib/graphql/backtrace/tracer.rb +1 -1
  4. data/lib/graphql/execution/interpreter/resolve.rb +19 -0
  5. data/lib/graphql/execution/interpreter/runtime.rb +96 -88
  6. data/lib/graphql/execution/interpreter.rb +8 -13
  7. data/lib/graphql/execution/lazy.rb +2 -4
  8. data/lib/graphql/execution/multiplex.rb +2 -1
  9. data/lib/graphql/language/lexer.rb +216 -1505
  10. data/lib/graphql/language/lexer.ri +744 -0
  11. data/lib/graphql/language/nodes.rb +2 -2
  12. data/lib/graphql/language/parser.rb +39 -38
  13. data/lib/graphql/language/parser.y +38 -37
  14. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  15. data/lib/graphql/query/context.rb +57 -27
  16. data/lib/graphql/query.rb +15 -2
  17. data/lib/graphql/schema/field.rb +31 -19
  18. data/lib/graphql/schema/member/has_deprecation_reason.rb +3 -4
  19. data/lib/graphql/schema/member/has_fields.rb +6 -1
  20. data/lib/graphql/schema/object.rb +2 -4
  21. data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
  22. data/lib/graphql/schema/timeout.rb +24 -28
  23. data/lib/graphql/schema/warden.rb +8 -1
  24. data/lib/graphql/schema.rb +42 -0
  25. data/lib/graphql/static_validation/validator.rb +1 -1
  26. data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
  27. data/lib/graphql/tracing/appoptics_trace.rb +231 -0
  28. data/lib/graphql/tracing/appsignal_trace.rb +66 -0
  29. data/lib/graphql/tracing/data_dog_trace.rb +148 -0
  30. data/lib/graphql/tracing/new_relic_trace.rb +75 -0
  31. data/lib/graphql/tracing/notifications_trace.rb +41 -0
  32. data/lib/graphql/tracing/platform_trace.rb +107 -0
  33. data/lib/graphql/tracing/platform_tracing.rb +15 -3
  34. data/lib/graphql/tracing/prometheus_trace.rb +89 -0
  35. data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
  36. data/lib/graphql/tracing/scout_trace.rb +72 -0
  37. data/lib/graphql/tracing/statsd_trace.rb +56 -0
  38. data/lib/graphql/tracing.rb +136 -39
  39. data/lib/graphql/type_kinds.rb +6 -3
  40. data/lib/graphql/types/relay/base_connection.rb +1 -1
  41. data/lib/graphql/version.rb +1 -1
  42. data/lib/graphql.rb +10 -7
  43. metadata +31 -7
  44. data/lib/graphql/language/lexer.rl +0 -280
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f95232ddde41ec605d304a572989213869296dc3818f15844d86a13f5cfa8c4
4
- data.tar.gz: b4850a0d39ccca8d81f0d487e0311b51fba6f2bfe1151461ce39c732743799b5
3
+ metadata.gz: 0e3f276db828fe3908e759e87f8cc5d33ae761c3ea80bd53b92b16a971ef507a
4
+ data.tar.gz: fc59e806f45125da3065ed4d7758170f88231377664b9a130e0896d6b2790fa3
5
5
  SHA512:
6
- metadata.gz: 1a15e0e17fd99b2c24c0a66f40f54c9909fc56c70d97fba2833bcb4934af65c06c5834b847386caa92100c4bb329c8fb27b799f5628f29cf89e6ff04946c013c
7
- data.tar.gz: 92eac476af42ec7222b371ebd87b81b78873a3240fe83ac994b0c603b990f84a082b05dead69d59dff48a58c5762d1d26028e13f57ec55481f86e4b67c92f70b
6
+ metadata.gz: 580e967d89228c0174072f5857cb646ea7495ee5a00587249ffc3034ffe9b536f751c09b8112e443b0c93efcd868db01c584497364897d23c7e798ee3f496d64
7
+ data.tar.gz: 43db761134f0ea83311f6a8b62b3b34b9dccdc335869c9067f43dcc5b9fc7f4023b77b8bb70cf203435ac25711d16d0ce5aa620478d351950d0db9f2e7bee9c7
@@ -21,7 +21,7 @@ module GraphQL
21
21
  def analyze_multiplex(multiplex, analyzers)
22
22
  multiplex_analyzers = analyzers.map { |analyzer| analyzer.new(multiplex) }
23
23
 
24
- multiplex.trace("analyze_multiplex", { multiplex: multiplex }) do
24
+ multiplex.current_trace.analyze_multiplex(multiplex: multiplex) do
25
25
  query_results = multiplex.queries.map do |query|
26
26
  if query.valid?
27
27
  analyze_query(
@@ -48,7 +48,7 @@ module GraphQL
48
48
  # @param analyzers [Array<GraphQL::Analysis::AST::Analyzer>]
49
49
  # @return [Array<Any>] Results from those analyzers
50
50
  def analyze_query(query, analyzers, multiplex_analyzers: [])
51
- query.trace("analyze_query", { query: query }) do
51
+ query.current_trace.analyze_query(query: query) do
52
52
  query_analyzers = analyzers
53
53
  .map { |analyzer| analyzer.new(query) }
54
54
  .select { |analyzer| analyzer.analyze? }
@@ -25,7 +25,7 @@ module GraphQL
25
25
  when "execute_field", "execute_field_lazy"
26
26
  query = metadata[:query]
27
27
  multiplex = query.multiplex
28
- push_key = metadata[:path]
28
+ push_key = query.context[:current_path]
29
29
  parent_frame = multiplex.context[:graphql_backtrace_contexts][push_key[0..-2]]
30
30
 
31
31
  if parent_frame.is_a?(GraphQL::Query)
@@ -11,6 +11,25 @@ module GraphQL
11
11
  nil
12
12
  end
13
13
 
14
+ def self.resolve_each_depth(lazies_at_depth, dataloader)
15
+ depths = lazies_at_depth.keys
16
+ depths.sort!
17
+ next_depth = depths.first
18
+ if next_depth
19
+ lazies = lazies_at_depth[next_depth]
20
+ lazies_at_depth.delete(next_depth)
21
+ if lazies.any?
22
+ dataloader.append_job {
23
+ lazies.each(&:value) # resolve these Lazy instances
24
+ }
25
+ # Run lazies _and_ dataloader, see if more are enqueued
26
+ dataloader.run
27
+ resolve_each_depth(lazies_at_depth, dataloader)
28
+ end
29
+ end
30
+ nil
31
+ end
32
+
14
33
  # After getting `results` back from an interpreter evaluation,
15
34
  # continue it until you get a response-ready Ruby value.
16
35
  #
@@ -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
@@ -412,9 +415,6 @@ module GraphQL
412
415
 
413
416
  return_type = field_defn.type
414
417
 
415
- next_path = path + [result_name]
416
- next_path.freeze
417
-
418
418
  # This seems janky, but we need to know
419
419
  # the field's return type at this path in order
420
420
  # to propagate `null`
@@ -422,7 +422,7 @@ module GraphQL
422
422
  (selections_result.graphql_non_null_field_names ||= []).push(result_name)
423
423
  end
424
424
  # 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, next_path)
425
+ set_all_interpreter_context(nil, field_defn, nil, result_name, selections_result)
426
426
  object = owner_object
427
427
 
428
428
  if is_introspection
@@ -432,19 +432,19 @@ module GraphQL
432
432
  total_args_count = field_defn.arguments(context).size
433
433
  if total_args_count == 0
434
434
  resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
435
- 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, return_type)
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)
436
436
  else
437
437
  # TODO remove all arguments(...) usages?
438
438
  @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
439
- 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, return_type)
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)
440
440
  end
441
441
  end
442
442
  end
443
443
 
444
- 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, return_type) # rubocop:disable Metrics/ParameterLists
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
@@ -624,7 +646,7 @@ module GraphQL
624
646
  rescue GraphQL::ExecutionError => err
625
647
  err
626
648
  end
627
- continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
649
+ continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
628
650
  elsif value.is_a?(GraphQL::UnauthorizedError)
629
651
  # this hook might raise & crash, or it might return
630
652
  # a replacement value
@@ -633,7 +655,7 @@ module GraphQL
633
655
  rescue GraphQL::ExecutionError => err
634
656
  err
635
657
  end
636
- 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)
637
659
  elsif GraphQL::Execution::SKIP == value
638
660
  # It's possible a lazy was already written here
639
661
  case selection_result
@@ -659,7 +681,7 @@ module GraphQL
659
681
  if selection_result.nil? || !dead_result?(selection_result)
660
682
  value.each_with_index do |error, index|
661
683
  error.ast_node ||= ast_node
662
- error.path ||= path + (list_type_at_all ? [index] : [])
684
+ error.path ||= current_path + (list_type_at_all ? [index] : [])
663
685
  context.errors << error
664
686
  end
665
687
  if selection_result
@@ -692,7 +714,7 @@ module GraphQL
692
714
  # Location information from `path` and `ast_node`.
693
715
  #
694
716
  # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
695
- 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
696
718
  if current_type.non_null?
697
719
  current_type = current_type.of_type
698
720
  is_non_null = true
@@ -708,8 +730,8 @@ module GraphQL
708
730
  set_result(selection_result, result_name, r)
709
731
  r
710
732
  when "UNION", "INTERFACE"
711
- resolved_type_or_lazy = resolve_type(current_type, value, path)
712
- 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|
713
735
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
714
736
  resolved_type, resolved_value = resolved_type_result
715
737
  else
@@ -726,7 +748,7 @@ module GraphQL
726
748
  set_result(selection_result, result_name, nil)
727
749
  nil
728
750
  else
729
- 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)
730
752
  end
731
753
  end
732
754
  when "OBJECT"
@@ -735,8 +757,8 @@ module GraphQL
735
757
  rescue GraphQL::ExecutionError => err
736
758
  err
737
759
  end
738
- 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|
739
- 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)
740
762
  if HALT != continue_value
741
763
  response_hash = GraphQLResultHash.new(result_name, selection_result)
742
764
  set_result(selection_result, result_name, response_hash)
@@ -757,10 +779,10 @@ module GraphQL
757
779
  this_result = response_hash
758
780
  final_result = nil
759
781
  end
760
- 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
761
784
  call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
762
785
  evaluate_selections(
763
- path,
764
786
  continue_value,
765
787
  current_type,
766
788
  false,
@@ -781,40 +803,27 @@ module GraphQL
781
803
  response_list = GraphQLResultArray.new(result_name, selection_result)
782
804
  response_list.graphql_non_null_list_items = inner_type.non_null?
783
805
  set_result(selection_result, result_name, response_list)
784
- result_was_set = false
785
806
  idx = 0
786
807
  list_value = begin
787
808
  value.each do |inner_value|
788
809
  break if dead_result?(response_list)
789
- if !result_was_set
790
- # Don't set the result unless `.each` is successful
791
- set_result(selection_result, result_name, response_list)
792
- result_was_set = true
793
- end
794
- next_path = path + [idx]
795
810
  this_idx = idx
796
- next_path.freeze
797
811
  idx += 1
798
812
  if use_dataloader_job
799
813
  @dataloader.append_job do
800
- 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)
801
815
  end
802
816
  else
803
- 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)
804
818
  end
805
819
  end
806
- # Maybe the list was empty and the block was never called.
807
- if !result_was_set
808
- set_result(selection_result, result_name, response_list)
809
- result_was_set = true
810
- end
811
820
 
812
821
  response_list
813
822
  rescue NoMethodError => err
814
823
  # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
815
824
  if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
816
825
  # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
817
- raise ListResultFailedError.new(value: value, field: field, path: path)
826
+ raise ListResultFailedError.new(value: value, field: field, path: current_path)
818
827
  else
819
828
  # This was some other NoMethodError -- let it bubble to reveal the real error.
820
829
  raise
@@ -829,20 +838,20 @@ module GraphQL
829
838
  end
830
839
  end
831
840
 
832
- 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)
833
842
  else
834
843
  raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
835
844
  end
836
845
  end
837
846
 
838
- 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
839
- 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)
840
849
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
841
850
  # This will update `response_list` with the lazy
842
- 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|
843
- 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)
844
853
  if HALT != continue_value
845
- 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)
846
855
  end
847
856
  end
848
857
  end
@@ -861,7 +870,6 @@ module GraphQL
861
870
  dir_defn = @schema_directives.fetch(dir_node.name)
862
871
  raw_dir_args = arguments(nil, dir_defn, dir_node)
863
872
  dir_args = continue_value(
864
- @context[:current_path], # path
865
873
  raw_dir_args, # value
866
874
  dir_defn, # parent_type
867
875
  nil, # field
@@ -893,7 +901,7 @@ module GraphQL
893
901
  true
894
902
  end
895
903
 
896
- def set_all_interpreter_context(object, field, arguments, path)
904
+ def set_all_interpreter_context(object, field, arguments, result_name, result)
897
905
  ti = thread_info
898
906
  if object
899
907
  ti[:current_object] = object
@@ -904,26 +912,26 @@ module GraphQL
904
912
  if arguments
905
913
  ti[:current_arguments] = arguments
906
914
  end
907
- if path
908
- ti[:current_path] = path
915
+ ti[:current_result_name] = result_name
916
+ if result
917
+ ti[:current_result] = result
909
918
  end
910
919
  end
911
920
 
912
921
  # @param obj [Object] Some user-returned value that may want to be batched
913
- # @param path [Array<String>]
914
922
  # @param field [GraphQL::Schema::Field]
915
923
  # @param eager [Boolean] Set to `true` for mutation root fields only
916
924
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
917
925
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
918
- 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)
919
927
  if lazy?(lazy_obj)
920
- lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
921
- 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)
922
930
  # Wrap the execution of _this_ method with tracing,
923
931
  # but don't wrap the continuation below
924
932
  inner_obj = begin
925
933
  if trace
926
- 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
927
935
  schema.sync_lazy(lazy_obj)
928
936
  end
929
937
  else
@@ -945,10 +953,11 @@ module GraphQL
945
953
  lazy.value
946
954
  else
947
955
  set_result(result, result_name, lazy)
956
+ @lazies_at_depth[current_depth] << lazy
948
957
  lazy
949
958
  end
950
959
  else
951
- set_all_interpreter_context(owner_object, field, arguments, path)
960
+ set_all_interpreter_context(owner_object, field, arguments, result_name, result)
952
961
  yield(lazy_obj)
953
962
  end
954
963
  end
@@ -962,25 +971,24 @@ module GraphQL
962
971
  end
963
972
  end
964
973
 
965
- # Set this pair in the Query context, but also in the interpeter namespace,
966
- # for compatibility.
967
- def set_interpreter_context(key, value)
968
- thread_info[key] = value
969
- end
970
-
971
- def delete_interpreter_context(key)
972
- (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
973
982
  end
974
983
 
975
- def resolve_type(type, value, path)
976
- trace_payload = { context: context, type: type, object: value, path: path }
977
- 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
978
986
  query.resolve_type(type, value)
979
987
  end
980
988
 
981
989
  if lazy?(resolved_type)
982
990
  GraphQL::Execution::Lazy.new do
983
- query.trace("resolve_type_lazy", trace_payload) do
991
+ query.current_trace.resolve_type_lazy(query: query, type: type, object: value) do
984
992
  schema.sync_lazy(resolved_type)
985
993
  end
986
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
  }
@@ -150,10 +148,7 @@ module GraphQL
150
148
  queries.map { |query|
151
149
  runtime = query.context.namespace(:interpreter_runtime)[:runtime]
152
150
  if runtime
153
- runtime.delete_interpreter_context(:current_path)
154
- runtime.delete_interpreter_context(:current_field)
155
- runtime.delete_interpreter_context(:current_object)
156
- runtime.delete_interpreter_context(:current_arguments)
151
+ runtime.delete_all_interpreter_context
157
152
  end
158
153
  }
159
154
  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
 
@@ -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}`