graphql 2.0.17 → 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 (43) 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/graphql_ext.bundle +0 -0
  10. data/lib/graphql/language/lexer.rb +216 -1505
  11. data/lib/graphql/language/lexer.ri +744 -0
  12. data/lib/graphql/language/parser.rb +9 -9
  13. data/lib/graphql/language/parser.y +9 -9
  14. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  15. data/lib/graphql/query/context.rb +45 -11
  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 +23 -27
  23. data/lib/graphql/schema/warden.rb +8 -1
  24. data/lib/graphql/schema.rb +34 -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/version.rb +1 -1
  41. data/lib/graphql.rb +7 -8
  42. metadata +14 -3
  43. 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: 7f43155bcb78f5842e409f8c001b7d031578604d2e7d962457cc7fd92e1d68d4
4
+ data.tar.gz: e86aa4c3071f63d605f9e2e3833ba967282f906c3ff0e1252db71b063fe69cd9
5
5
  SHA512:
6
- metadata.gz: 1a15e0e17fd99b2c24c0a66f40f54c9909fc56c70d97fba2833bcb4934af65c06c5834b847386caa92100c4bb329c8fb27b799f5628f29cf89e6ff04946c013c
7
- data.tar.gz: 92eac476af42ec7222b371ebd87b81b78873a3240fe83ac994b0c603b990f84a082b05dead69d59dff48a58c5762d1d26028e13f57ec55481f86e4b67c92f70b
6
+ metadata.gz: 79c16409d45a11f1bba6623e1d97877984bfe10577ab5241ecd3ebe515fefb6cd42940f336640c60cf19f94c61d902692f26b0497699e4ca9e912f2532fa6168
7
+ data.tar.gz: a9f5e954d7988a440c083cd0afbb6f5ff72ab77d30d4fab708f9f501d2ce8af49b13fde571e50788c9ba79fd943d38e4c207e4ac9ddccd93c80d2c0f9ef8f997
@@ -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}`
Binary file