graphql 2.0.17.2 → 2.0.21

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/ast.rb +2 -2
  3. data/lib/graphql/backtrace/trace.rb +96 -0
  4. data/lib/graphql/backtrace/tracer.rb +1 -1
  5. data/lib/graphql/backtrace.rb +6 -1
  6. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  7. data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -3
  8. data/lib/graphql/execution/interpreter/resolve.rb +19 -0
  9. data/lib/graphql/execution/interpreter/runtime.rb +254 -211
  10. data/lib/graphql/execution/interpreter.rb +9 -14
  11. data/lib/graphql/execution/lazy.rb +2 -4
  12. data/lib/graphql/execution/multiplex.rb +2 -1
  13. data/lib/graphql/filter.rb +7 -2
  14. data/lib/graphql/language/document_from_schema_definition.rb +25 -9
  15. data/lib/graphql/language/lexer.rb +216 -1505
  16. data/lib/graphql/language/nodes.rb +27 -9
  17. data/lib/graphql/language/parser.rb +509 -491
  18. data/lib/graphql/language/parser.y +43 -38
  19. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  20. data/lib/graphql/pagination/connection.rb +5 -5
  21. data/lib/graphql/query/context.rb +62 -31
  22. data/lib/graphql/query/null_context.rb +1 -1
  23. data/lib/graphql/query.rb +22 -5
  24. data/lib/graphql/schema/argument.rb +7 -9
  25. data/lib/graphql/schema/build_from_definition.rb +15 -3
  26. data/lib/graphql/schema/enum_value.rb +2 -5
  27. data/lib/graphql/schema/field.rb +44 -31
  28. data/lib/graphql/schema/field_extension.rb +1 -4
  29. data/lib/graphql/schema/find_inherited_value.rb +2 -7
  30. data/lib/graphql/schema/member/base_dsl_methods.rb +13 -11
  31. data/lib/graphql/schema/member/has_arguments.rb +1 -1
  32. data/lib/graphql/schema/member/has_ast_node.rb +12 -0
  33. data/lib/graphql/schema/member/has_deprecation_reason.rb +3 -4
  34. data/lib/graphql/schema/member/has_directives.rb +15 -10
  35. data/lib/graphql/schema/member/has_fields.rb +87 -37
  36. data/lib/graphql/schema/member/has_validators.rb +2 -2
  37. data/lib/graphql/schema/member/relay_shortcuts.rb +19 -0
  38. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  39. data/lib/graphql/schema/object.rb +2 -4
  40. data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
  41. data/lib/graphql/schema/resolver.rb +4 -4
  42. data/lib/graphql/schema/timeout.rb +24 -28
  43. data/lib/graphql/schema/validator.rb +1 -1
  44. data/lib/graphql/schema/warden.rb +11 -2
  45. data/lib/graphql/schema.rb +72 -1
  46. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -4
  47. data/lib/graphql/static_validation/rules/fields_will_merge.rb +2 -2
  48. data/lib/graphql/static_validation/validator.rb +1 -1
  49. data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
  50. data/lib/graphql/tracing/appoptics_trace.rb +231 -0
  51. data/lib/graphql/tracing/appsignal_trace.rb +77 -0
  52. data/lib/graphql/tracing/data_dog_trace.rb +148 -0
  53. data/lib/graphql/tracing/legacy_trace.rb +65 -0
  54. data/lib/graphql/tracing/new_relic_trace.rb +75 -0
  55. data/lib/graphql/tracing/notifications_trace.rb +42 -0
  56. data/lib/graphql/tracing/platform_trace.rb +109 -0
  57. data/lib/graphql/tracing/platform_tracing.rb +15 -3
  58. data/lib/graphql/tracing/prometheus_trace.rb +89 -0
  59. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +1 -1
  60. data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
  61. data/lib/graphql/tracing/scout_trace.rb +72 -0
  62. data/lib/graphql/tracing/statsd_trace.rb +56 -0
  63. data/lib/graphql/tracing/trace.rb +75 -0
  64. data/lib/graphql/tracing.rb +16 -39
  65. data/lib/graphql/type_kinds.rb +6 -3
  66. data/lib/graphql/types/relay/base_connection.rb +1 -1
  67. data/lib/graphql/types/relay/connection_behaviors.rb +24 -2
  68. data/lib/graphql/types/relay/edge_behaviors.rb +16 -2
  69. data/lib/graphql/types/relay/node_behaviors.rb +7 -1
  70. data/lib/graphql/types/relay/page_info_behaviors.rb +7 -2
  71. data/lib/graphql/types/relay.rb +0 -1
  72. data/lib/graphql/version.rb +1 -1
  73. data/lib/graphql.rb +16 -9
  74. metadata +33 -8
  75. data/lib/graphql/language/lexer.rl +0 -280
  76. data/lib/graphql/types/relay/default_relay.rb +0 -27
@@ -8,6 +8,18 @@ module GraphQL
8
8
  #
9
9
  # @api private
10
10
  class Runtime
11
+ class CurrentState
12
+ def initialize
13
+ @current_object = nil
14
+ @current_field = nil
15
+ @current_arguments = nil
16
+ @current_result_name = nil
17
+ @current_result = nil
18
+ end
19
+
20
+ attr_accessor :current_result, :current_result_name,
21
+ :current_arguments, :current_field, :current_object
22
+ end
11
23
 
12
24
  module GraphQLResult
13
25
  def initialize(result_name, parent_result)
@@ -20,6 +32,15 @@ module GraphQL
20
32
  @graphql_metadata = nil
21
33
  end
22
34
 
35
+ def path
36
+ @path ||= build_path([])
37
+ end
38
+
39
+ def build_path(path_array)
40
+ graphql_result_name && path_array.unshift(graphql_result_name)
41
+ @graphql_parent ? @graphql_parent.build_path(path_array) : path_array
42
+ end
43
+
23
44
  attr_accessor :graphql_dead
24
45
  attr_reader :graphql_parent, :graphql_result_name
25
46
 
@@ -45,7 +66,7 @@ module GraphQL
45
66
 
46
67
  attr_accessor :graphql_merged_into
47
68
 
48
- def []=(key, value)
69
+ def set_leaf(key, value)
49
70
  # This is a hack.
50
71
  # Basically, this object is merged into the root-level result at some point.
51
72
  # But the problem is, some lazies are created whose closures retain reference to _this_
@@ -55,23 +76,27 @@ module GraphQL
55
76
  # In order to return a proper partial result (eg, for a directive), we have to update this object, too.
56
77
  # Yowza.
57
78
  if (t = @graphql_merged_into)
58
- t[key] = value
79
+ t.set_leaf(key, value)
59
80
  end
60
81
 
61
- if value.respond_to?(:graphql_result_data)
62
- @graphql_result_data[key] = value.graphql_result_data
63
- # If we encounter some part of this response that requires metadata tracking,
64
- # then create the metadata hash if necessary. It will be kept up-to-date after this.
65
- (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
66
- else
67
- @graphql_result_data[key] = value
68
- # keep this up-to-date if it's been initialized
69
- @graphql_metadata && @graphql_metadata[key] = value
70
- end
82
+ @graphql_result_data[key] = value
83
+ # keep this up-to-date if it's been initialized
84
+ @graphql_metadata && @graphql_metadata[key] = value
71
85
 
72
86
  value
73
87
  end
74
88
 
89
+ def set_child_result(key, value)
90
+ if (t = @graphql_merged_into)
91
+ t.set_child_result(key, value)
92
+ end
93
+ @graphql_result_data[key] = value.graphql_result_data
94
+ # If we encounter some part of this response that requires metadata tracking,
95
+ # then create the metadata hash if necessary. It will be kept up-to-date after this.
96
+ (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
97
+ value
98
+ end
99
+
75
100
  def delete(key)
76
101
  @graphql_metadata && @graphql_metadata.delete(key)
77
102
  @graphql_result_data.delete(key)
@@ -92,6 +117,29 @@ module GraphQL
92
117
  def [](k)
93
118
  (@graphql_metadata || @graphql_result_data)[k]
94
119
  end
120
+
121
+ def merge_into(into_result)
122
+ self.each do |key, value|
123
+ case value
124
+ when GraphQLResultHash
125
+ next_into = into_result[key]
126
+ if next_into
127
+ value.merge_into(next_into)
128
+ else
129
+ into_result.set_child_result(key, value)
130
+ end
131
+ when GraphQLResultArray
132
+ # There's no special handling of arrays because currently, there's no way to split the execution
133
+ # of a list over several concurrent flows.
134
+ next_result.set_child_result(key, value)
135
+ else
136
+ # We have to assume that, since this passed the `fields_will_merge` selection,
137
+ # that the old and new values are the same.
138
+ into_result.set_leaf(key, value)
139
+ end
140
+ end
141
+ @graphql_merged_into = into_result
142
+ end
95
143
  end
96
144
 
97
145
  class GraphQLResultArray
@@ -114,19 +162,25 @@ module GraphQL
114
162
  @graphql_result_data.delete_at(delete_at_index)
115
163
  end
116
164
 
117
- def []=(idx, value)
165
+ def set_leaf(idx, value)
118
166
  if @skip_indices
119
167
  offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
120
168
  idx -= offset_by
121
169
  end
122
- if value.respond_to?(:graphql_result_data)
123
- @graphql_result_data[idx] = value.graphql_result_data
124
- (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
125
- else
126
- @graphql_result_data[idx] = value
127
- @graphql_metadata && @graphql_metadata[idx] = value
128
- end
170
+ @graphql_result_data[idx] = value
171
+ @graphql_metadata && @graphql_metadata[idx] = value
172
+ value
173
+ end
129
174
 
175
+ def set_child_result(idx, value)
176
+ if @skip_indices
177
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
178
+ idx -= offset_by
179
+ end
180
+ @graphql_result_data[idx] = value.graphql_result_data
181
+ # If we encounter some part of this response that requires metadata tracking,
182
+ # then create the metadata hash if necessary. It will be kept up-to-date after this.
183
+ (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
130
184
  value
131
185
  end
132
186
 
@@ -148,18 +202,10 @@ module GraphQL
148
202
  # @return [GraphQL::Query::Context]
149
203
  attr_reader :context
150
204
 
151
- def thread_info
152
- info = Thread.current[:__graphql_runtime_info]
153
- if !info
154
- new_ti = {}
155
- info = Thread.current[:__graphql_runtime_info] = new_ti
156
- end
157
- info
158
- end
159
-
160
- def initialize(query:)
205
+ def initialize(query:, lazies_at_depth:)
161
206
  @query = query
162
207
  @dataloader = query.multiplex.dataloader
208
+ @lazies_at_depth = lazies_at_depth
163
209
  @schema = query.schema
164
210
  @context = query.context
165
211
  @multiplex_context = query.multiplex.context
@@ -208,8 +254,9 @@ module GraphQL
208
254
  root_operation = query.selected_operation
209
255
  root_op_type = root_operation.operation_type || "query"
210
256
  root_type = schema.root_type_for_operation(root_op_type)
211
- path = []
212
- set_all_interpreter_context(query.root_value, nil, nil, path)
257
+ st = get_current_runtime_state
258
+ st.current_object = query.root_value
259
+ st.current_result = @response
213
260
  object_proxy = authorized_new(root_type, query.root_value, context)
214
261
  object_proxy = schema.sync_lazy(object_proxy)
215
262
 
@@ -236,10 +283,12 @@ module GraphQL
236
283
  end
237
284
 
238
285
  @dataloader.append_job {
239
- set_all_interpreter_context(query.root_value, nil, nil, path)
286
+ st = get_current_runtime_state
287
+ st.current_object = query.root_value
288
+ st.current_result = selection_response
289
+
240
290
  call_method_on_directives(:resolve, object_proxy, selections.graphql_directives) do
241
291
  evaluate_selections(
242
- path,
243
292
  object_proxy,
244
293
  root_type,
245
294
  root_op_type == "mutation",
@@ -253,32 +302,7 @@ module GraphQL
253
302
  end
254
303
  end
255
304
  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)
260
- nil
261
- end
262
-
263
- # @return [void]
264
- def deep_merge_selection_result(from_result, into_result)
265
- from_result.each do |key, value|
266
- if !into_result.key?(key)
267
- into_result[key] = value
268
- else
269
- case value
270
- when GraphQLResultHash
271
- deep_merge_selection_result(value, into_result[key])
272
- else
273
- # We have to assume that, since this passed the `fields_will_merge` selection,
274
- # that the old and new values are the same.
275
- # There's no special handling of arrays because currently, there's no way to split the execution
276
- # of a list over several concurrent flows.
277
- into_result[key] = value
278
- end
279
- end
280
- end
281
- from_result.graphql_merged_into = into_result
305
+ delete_all_interpreter_context
282
306
  nil
283
307
  end
284
308
 
@@ -355,22 +379,25 @@ module GraphQL
355
379
  selections_to_run || selections_by_name
356
380
  end
357
381
 
358
- NO_ARGS = {}.freeze
382
+ NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
359
383
 
360
384
  # @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)
385
+ def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
386
+ st = get_current_runtime_state
387
+ st.current_object = owner_object
388
+ st.current_result_name = nil
389
+ st.current_result = selections_result
363
390
 
364
391
  finished_jobs = 0
365
392
  enqueued_jobs = gathered_selections.size
366
393
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
367
394
  @dataloader.append_job {
368
395
  evaluate_selection(
369
- path, result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
396
+ result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
370
397
  )
371
398
  finished_jobs += 1
372
399
  if target_result && finished_jobs == enqueued_jobs
373
- deep_merge_selection_result(selections_result, target_result)
400
+ selections_result.merge_into(target_result)
374
401
  end
375
402
  }
376
403
  end
@@ -378,10 +405,8 @@ module GraphQL
378
405
  selections_result
379
406
  end
380
407
 
381
- attr_reader :progress_path
382
-
383
408
  # @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
409
+ def evaluate_selection(result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
385
410
  return if dead_result?(selections_result)
386
411
  # As a performance optimization, the hash key will be a `Node` if
387
412
  # there's only one selection of the field. But if there are multiple
@@ -412,17 +437,19 @@ module GraphQL
412
437
 
413
438
  return_type = field_defn.type
414
439
 
415
- next_path = path + [result_name]
416
- next_path.freeze
417
-
418
440
  # This seems janky, but we need to know
419
441
  # the field's return type at this path in order
420
442
  # to propagate `null`
421
- if return_type.non_null?
443
+ return_type_non_null = return_type.non_null?
444
+ if return_type_non_null
422
445
  (selections_result.graphql_non_null_field_names ||= []).push(result_name)
423
446
  end
424
447
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
425
- set_all_interpreter_context(nil, field_defn, nil, next_path)
448
+ st = get_current_runtime_state
449
+ st.current_field = field_defn
450
+ st.current_result = selections_result
451
+ st.current_result_name = result_name
452
+
426
453
  object = owner_object
427
454
 
428
455
  if is_introspection
@@ -432,25 +459,34 @@ module GraphQL
432
459
  total_args_count = field_defn.arguments(context).size
433
460
  if total_args_count == 0
434
461
  resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
435
- evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type)
462
+ if field_defn.extras.size == 0
463
+ evaluate_selection_with_resolved_keyword_args(
464
+ NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null
465
+ )
466
+ else
467
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
468
+ end
436
469
  else
437
- # TODO remove all arguments(...) usages?
438
470
  @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
439
- evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type)
471
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
440
472
  end
441
473
  end
442
474
  end
443
475
 
444
- def evaluate_selection_with_args(arguments, field_defn, 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|
476
+ def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null) # rubocop:disable Metrics/ParameterLists
477
+ after_lazy(arguments, owner: owner_type, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
446
478
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
447
- continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
479
+ continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
448
480
  next
449
481
  end
450
482
 
451
- kwarg_arguments = if resolved_arguments.empty? && field_defn.extras.empty?
452
- # We can avoid allocating the `{ Symbol => Object }` hash in this case
453
- NO_ARGS
483
+ kwarg_arguments = if field_defn.extras.empty?
484
+ if resolved_arguments.empty?
485
+ # We can avoid allocating the `{ Symbol => Object }` hash in this case
486
+ NO_ARGS
487
+ else
488
+ resolved_arguments.keyword_arguments
489
+ end
454
490
  else
455
491
  # Bundle up the extras, then make a new arguments instance
456
492
  # that includes the extras, too.
@@ -460,9 +496,9 @@ module GraphQL
460
496
  when :ast_node
461
497
  extra_args[:ast_node] = ast_node
462
498
  when :execution_errors
463
- extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
499
+ extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, current_path)
464
500
  when :path
465
- extra_args[:path] = next_path
501
+ extra_args[:path] = current_path
466
502
  when :lookahead
467
503
  if !field_ast_nodes
468
504
  field_ast_nodes = [ast_node]
@@ -489,61 +525,70 @@ module GraphQL
489
525
  resolved_arguments.keyword_arguments
490
526
  end
491
527
 
492
- set_all_interpreter_context(nil, nil, resolved_arguments, nil)
528
+ evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null)
529
+ end
530
+ end
493
531
 
494
- # Optimize for the case that field is selected only once
495
- if field_ast_nodes.nil? || field_ast_nodes.size == 1
496
- next_selections = ast_node.selections
497
- directives = ast_node.directives
498
- else
499
- next_selections = []
500
- directives = []
501
- field_ast_nodes.each { |f|
502
- next_selections.concat(f.selections)
503
- directives.concat(f.directives)
504
- }
505
- end
506
-
507
- field_result = call_method_on_directives(:resolve, object, directives) do
508
- # Actually call the field resolver and capture the result
509
- app_result = begin
510
- query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments}) do
511
- field_defn.resolve(object, kwarg_arguments, context)
512
- end
513
- rescue GraphQL::ExecutionError => err
514
- err
515
- rescue StandardError => err
516
- begin
517
- query.handle_or_reraise(err)
518
- rescue GraphQL::ExecutionError => ex_err
519
- ex_err
520
- end
532
+ def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null) # rubocop:disable Metrics/ParameterLists
533
+ st = get_current_runtime_state
534
+ st.current_field = field_defn
535
+ st.current_object = object
536
+ st.current_arguments = resolved_arguments
537
+ st.current_result_name = result_name
538
+ st.current_result = selection_result
539
+ # Optimize for the case that field is selected only once
540
+ if field_ast_nodes.nil? || field_ast_nodes.size == 1
541
+ next_selections = ast_node.selections
542
+ directives = ast_node.directives
543
+ else
544
+ next_selections = []
545
+ directives = []
546
+ field_ast_nodes.each { |f|
547
+ next_selections.concat(f.selections)
548
+ directives.concat(f.directives)
549
+ }
550
+ end
551
+
552
+ field_result = call_method_on_directives(:resolve, object, directives) do
553
+ # Actually call the field resolver and capture the result
554
+ app_result = begin
555
+ query.current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
556
+ field_defn.resolve(object, kwarg_arguments, context)
521
557
  end
522
- after_lazy(app_result, owner: owner_type, field: field_defn, 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)
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)
526
- end
558
+ rescue GraphQL::ExecutionError => err
559
+ err
560
+ rescue StandardError => err
561
+ begin
562
+ query.handle_or_reraise(err)
563
+ rescue GraphQL::ExecutionError => ex_err
564
+ ex_err
527
565
  end
528
566
  end
529
-
530
- # If this field is a root mutation field, immediately resolve
531
- # all of its child fields before moving on to the next root mutation field.
532
- # (Subselections of this mutation will still be resolved level-by-level.)
533
- if is_eager_field
534
- Interpreter::Resolve.resolve_all([field_result], @dataloader)
535
- else
536
- # Return this from `after_lazy` because it might be another lazy that needs to be resolved
537
- field_result
567
+ after_lazy(app_result, owner: owner_type, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
568
+ continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
569
+ if HALT != continue_value
570
+ continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
571
+ end
538
572
  end
539
573
  end
574
+
575
+ # If this field is a root mutation field, immediately resolve
576
+ # all of its child fields before moving on to the next root mutation field.
577
+ # (Subselections of this mutation will still be resolved level-by-level.)
578
+ if is_eager_field
579
+ Interpreter::Resolve.resolve_all([field_result], @dataloader)
580
+ else
581
+ # Return this from `after_lazy` because it might be another lazy that needs to be resolved
582
+ field_result
583
+ end
540
584
  end
541
585
 
586
+
542
587
  def dead_result?(selection_result)
543
- selection_result.graphql_dead || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
588
+ selection_result.graphql_dead # || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
544
589
  end
545
590
 
546
- def set_result(selection_result, result_name, value)
591
+ def set_result(selection_result, result_name, value, is_child_result)
547
592
  if !dead_result?(selection_result)
548
593
  if value.nil? &&
549
594
  ( # there are two conditions under which `nil` is not allowed in the response:
@@ -563,11 +608,13 @@ module GraphQL
563
608
  if parent.nil? # This is a top-level result hash
564
609
  @response = nil
565
610
  else
566
- set_result(parent, name_in_parent, nil)
611
+ set_result(parent, name_in_parent, nil, false)
567
612
  set_graphql_dead(selection_result)
568
613
  end
614
+ elsif is_child_result
615
+ selection_result.set_child_result(result_name, value)
569
616
  else
570
- selection_result[result_name] = value
617
+ selection_result.set_leaf(result_name, value)
571
618
  end
572
619
  end
573
620
  end
@@ -587,18 +634,29 @@ module GraphQL
587
634
  end
588
635
  end
589
636
 
637
+ def current_path
638
+ st = get_current_runtime_state
639
+ result = st.current_result
640
+ path = result && result.path
641
+ if path && (rn = st.current_result_name)
642
+ path = path.dup
643
+ path.push(rn)
644
+ end
645
+ path
646
+ end
647
+
590
648
  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
649
+ def continue_value(value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
592
650
  case value
593
651
  when nil
594
652
  if is_non_null
595
- set_result(selection_result, result_name, nil) do
653
+ set_result(selection_result, result_name, nil, false) do
596
654
  # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
597
655
  err = parent_type::InvalidNullError.new(parent_type, field, value)
598
656
  schema.type_error(err, context)
599
657
  end
600
658
  else
601
- set_result(selection_result, result_name, nil)
659
+ set_result(selection_result, result_name, nil, false)
602
660
  end
603
661
  HALT
604
662
  when GraphQL::Error
@@ -607,11 +665,11 @@ module GraphQL
607
665
  # every time.
608
666
  if value.is_a?(GraphQL::ExecutionError)
609
667
  if selection_result.nil? || !dead_result?(selection_result)
610
- value.path ||= path
668
+ value.path ||= current_path
611
669
  value.ast_node ||= ast_node
612
670
  context.errors << value
613
671
  if selection_result
614
- set_result(selection_result, result_name, nil)
672
+ set_result(selection_result, result_name, nil, false)
615
673
  end
616
674
  end
617
675
  HALT
@@ -624,7 +682,7 @@ module GraphQL
624
682
  rescue GraphQL::ExecutionError => err
625
683
  err
626
684
  end
627
- continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
685
+ continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
628
686
  elsif value.is_a?(GraphQL::UnauthorizedError)
629
687
  # this hook might raise & crash, or it might return
630
688
  # a replacement value
@@ -633,7 +691,7 @@ module GraphQL
633
691
  rescue GraphQL::ExecutionError => err
634
692
  err
635
693
  end
636
- continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
694
+ continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
637
695
  elsif GraphQL::Execution::SKIP == value
638
696
  # It's possible a lazy was already written here
639
697
  case selection_result
@@ -659,15 +717,15 @@ module GraphQL
659
717
  if selection_result.nil? || !dead_result?(selection_result)
660
718
  value.each_with_index do |error, index|
661
719
  error.ast_node ||= ast_node
662
- error.path ||= path + (list_type_at_all ? [index] : [])
720
+ error.path ||= current_path + (list_type_at_all ? [index] : [])
663
721
  context.errors << error
664
722
  end
665
723
  if selection_result
666
724
  if list_type_at_all
667
725
  result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
668
- set_result(selection_result, result_name, result_without_errors)
726
+ set_result(selection_result, result_name, result_without_errors, false)
669
727
  else
670
- set_result(selection_result, result_name, nil)
728
+ set_result(selection_result, result_name, nil, false)
671
729
  end
672
730
  end
673
731
  end
@@ -677,7 +735,7 @@ module GraphQL
677
735
  end
678
736
  when GraphQL::Execution::Interpreter::RawValue
679
737
  # Write raw value directly to the response without resolving nested objects
680
- set_result(selection_result, result_name, value.resolve)
738
+ set_result(selection_result, result_name, value.resolve, false)
681
739
  HALT
682
740
  else
683
741
  value
@@ -692,7 +750,7 @@ module GraphQL
692
750
  # Location information from `path` and `ast_node`.
693
751
  #
694
752
  # @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
753
+ 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
754
  if current_type.non_null?
697
755
  current_type = current_type.of_type
698
756
  is_non_null = true
@@ -705,11 +763,11 @@ module GraphQL
705
763
  rescue StandardError => err
706
764
  schema.handle_or_reraise(context, err)
707
765
  end
708
- set_result(selection_result, result_name, r)
766
+ set_result(selection_result, result_name, r, false)
709
767
  r
710
768
  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|
769
+ resolved_type_or_lazy = resolve_type(current_type, value)
770
+ 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
771
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
714
772
  resolved_type, resolved_value = resolved_type_result
715
773
  else
@@ -723,10 +781,10 @@ module GraphQL
723
781
  err_class = current_type::UnresolvedTypeError
724
782
  type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
725
783
  schema.type_error(type_error, context)
726
- set_result(selection_result, result_name, nil)
784
+ set_result(selection_result, result_name, nil, false)
727
785
  nil
728
786
  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)
787
+ continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
730
788
  end
731
789
  end
732
790
  when "OBJECT"
@@ -735,11 +793,11 @@ module GraphQL
735
793
  rescue GraphQL::ExecutionError => err
736
794
  err
737
795
  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)
796
+ 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|
797
+ continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
740
798
  if HALT != continue_value
741
799
  response_hash = GraphQLResultHash.new(result_name, selection_result)
742
- set_result(selection_result, result_name, response_hash)
800
+ set_result(selection_result, result_name, response_hash, true)
743
801
  gathered_selections = gather_selections(continue_value, current_type, next_selections)
744
802
  # There are two possibilities for `gathered_selections`:
745
803
  # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
@@ -757,10 +815,15 @@ module GraphQL
757
815
  this_result = response_hash
758
816
  final_result = nil
759
817
  end
760
- set_all_interpreter_context(continue_value, nil, nil, path) # reset this mutable state
818
+ # reset this mutable state
819
+ # Unset `result_name` here because it's already included in the new response hash
820
+ st = get_current_runtime_state
821
+ st.current_object = continue_value
822
+ st.current_result_name = nil
823
+ st.current_result = this_result
824
+
761
825
  call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
762
826
  evaluate_selections(
763
- path,
764
827
  continue_value,
765
828
  current_type,
766
829
  false,
@@ -778,43 +841,30 @@ module GraphQL
778
841
  inner_type = current_type.of_type
779
842
  # This is true for objects, unions, and interfaces
780
843
  use_dataloader_job = !inner_type.unwrap.kind.input?
844
+ inner_type_non_null = inner_type.non_null?
781
845
  response_list = GraphQLResultArray.new(result_name, selection_result)
782
- response_list.graphql_non_null_list_items = inner_type.non_null?
783
- set_result(selection_result, result_name, response_list)
784
- result_was_set = false
846
+ response_list.graphql_non_null_list_items = inner_type_non_null
847
+ set_result(selection_result, result_name, response_list, true)
785
848
  idx = 0
786
849
  list_value = begin
787
850
  value.each do |inner_value|
788
- 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
851
  this_idx = idx
796
- next_path.freeze
797
852
  idx += 1
798
853
  if use_dataloader_job
799
854
  @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)
855
+ resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
801
856
  end
802
857
  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)
858
+ resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
804
859
  end
805
860
  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
861
 
812
862
  response_list
813
863
  rescue NoMethodError => err
814
864
  # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
815
865
  if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
816
866
  # 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)
867
+ raise ListResultFailedError.new(value: value, field: field, path: current_path)
818
868
  else
819
869
  # This was some other NoMethodError -- let it bubble to reveal the real error.
820
870
  raise
@@ -829,20 +879,22 @@ module GraphQL
829
879
  end
830
880
  end
831
881
 
832
- continue_value(path, list_value, owner_type, field, inner_type.non_null?, ast_node, result_name, selection_result)
882
+ continue_value(list_value, owner_type, field, inner_type.non_null?, ast_node, result_name, selection_result)
833
883
  else
834
884
  raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
835
885
  end
836
886
  end
837
887
 
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)
888
+ def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) # rubocop:disable Metrics/ParameterLists
889
+ st = get_current_runtime_state
890
+ st.current_result_name = this_idx
891
+ st.current_result = response_list
840
892
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
841
893
  # 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)
894
+ after_lazy(inner_value, owner: inner_type, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
895
+ continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
844
896
  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)
897
+ continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
846
898
  end
847
899
  end
848
900
  end
@@ -861,7 +913,6 @@ module GraphQL
861
913
  dir_defn = @schema_directives.fetch(dir_node.name)
862
914
  raw_dir_args = arguments(nil, dir_defn, dir_node)
863
915
  dir_args = continue_value(
864
- @context[:current_path], # path
865
916
  raw_dir_args, # value
866
917
  dir_defn, # parent_type
867
918
  nil, # field
@@ -893,37 +944,30 @@ module GraphQL
893
944
  true
894
945
  end
895
946
 
896
- def set_all_interpreter_context(object, field, arguments, path)
897
- ti = thread_info
898
- if object
899
- ti[:current_object] = object
900
- end
901
- if field
902
- ti[:current_field] = field
903
- end
904
- if arguments
905
- ti[:current_arguments] = arguments
906
- end
907
- if path
908
- ti[:current_path] = path
909
- end
947
+ def get_current_runtime_state
948
+ Thread.current[:__graphql_runtime_info] ||= CurrentState.new
910
949
  end
911
950
 
912
951
  # @param obj [Object] Some user-returned value that may want to be batched
913
- # @param path [Array<String>]
914
952
  # @param field [GraphQL::Schema::Field]
915
953
  # @param eager [Boolean] Set to `true` for mutation root fields only
916
954
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
917
955
  # @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)
956
+ def after_lazy(lazy_obj, owner:, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
919
957
  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)
958
+ orig_result = result
959
+ lazy = GraphQL::Execution::Lazy.new(field: field) do
960
+ st = get_current_runtime_state
961
+ st.current_object = owner_object
962
+ st.current_field = field
963
+ st.current_arguments = arguments
964
+ st.current_result_name = result_name
965
+ st.current_result = orig_result
922
966
  # Wrap the execution of _this_ method with tracing,
923
967
  # but don't wrap the continuation below
924
968
  inner_obj = begin
925
969
  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
970
+ query.current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
927
971
  schema.sync_lazy(lazy_obj)
928
972
  end
929
973
  else
@@ -944,11 +988,17 @@ module GraphQL
944
988
  if eager
945
989
  lazy.value
946
990
  else
947
- set_result(result, result_name, lazy)
991
+ set_result(result, result_name, lazy, false)
992
+ current_depth = 0
993
+ while result
994
+ current_depth += 1
995
+ result = result.graphql_parent
996
+ end
997
+ @lazies_at_depth[current_depth] << lazy
948
998
  lazy
949
999
  end
950
1000
  else
951
- set_all_interpreter_context(owner_object, field, arguments, path)
1001
+ # Don't need to reset state here because it _wasn't_ lazy.
952
1002
  yield(lazy_obj)
953
1003
  end
954
1004
  end
@@ -962,25 +1012,18 @@ module GraphQL
962
1012
  end
963
1013
  end
964
1014
 
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)
1015
+ def delete_all_interpreter_context
1016
+ Thread.current[:__graphql_runtime_info] = nil
973
1017
  end
974
1018
 
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
1019
+ def resolve_type(type, value)
1020
+ resolved_type, resolved_value = query.current_trace.resolve_type(query: query, type: type, object: value) do
978
1021
  query.resolve_type(type, value)
979
1022
  end
980
1023
 
981
1024
  if lazy?(resolved_type)
982
1025
  GraphQL::Execution::Lazy.new do
983
- query.trace("resolve_type_lazy", trace_payload) do
1026
+ query.current_trace.resolve_type_lazy(query: query, type: type, object: value) do
984
1027
  schema.sync_lazy(resolved_type)
985
1028
  end
986
1029
  end