graphql 2.0.21 → 2.0.32

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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/mutation_delete_generator.rb +1 -1
  3. data/lib/generators/graphql/mutation_update_generator.rb +1 -1
  4. data/lib/generators/graphql/relay.rb +18 -1
  5. data/lib/graphql/backtrace.rb +0 -4
  6. data/lib/graphql/dataloader/source.rb +69 -45
  7. data/lib/graphql/dataloader.rb +4 -4
  8. data/lib/graphql/execution/interpreter/arguments_cache.rb +31 -30
  9. data/lib/graphql/execution/interpreter/runtime.rb +122 -101
  10. data/lib/graphql/execution/interpreter.rb +1 -2
  11. data/lib/graphql/execution/lookahead.rb +1 -1
  12. data/lib/graphql/filter.rb +2 -1
  13. data/lib/graphql/introspection/entry_points.rb +1 -1
  14. data/lib/graphql/language/document_from_schema_definition.rb +16 -9
  15. data/lib/graphql/language/lexer.rb +89 -57
  16. data/lib/graphql/language/nodes.rb +3 -0
  17. data/lib/graphql/language/parser.rb +706 -691
  18. data/lib/graphql/language/parser.y +1 -0
  19. data/lib/graphql/language/printer.rb +28 -14
  20. data/lib/graphql/language/visitor.rb +64 -61
  21. data/lib/graphql/query/context.rb +16 -7
  22. data/lib/graphql/query/null_context.rb +8 -18
  23. data/lib/graphql/query/validation_pipeline.rb +2 -1
  24. data/lib/graphql/query.rb +37 -12
  25. data/lib/graphql/schema/addition.rb +38 -12
  26. data/lib/graphql/schema/always_visible.rb +10 -0
  27. data/lib/graphql/schema/argument.rb +8 -10
  28. data/lib/graphql/schema/build_from_definition.rb +8 -7
  29. data/lib/graphql/schema/directive.rb +1 -1
  30. data/lib/graphql/schema/enum_value.rb +2 -2
  31. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  32. data/lib/graphql/schema/field.rb +26 -15
  33. data/lib/graphql/schema/input_object.rb +9 -7
  34. data/lib/graphql/schema/interface.rb +5 -1
  35. data/lib/graphql/schema/introspection_system.rb +1 -1
  36. data/lib/graphql/schema/member/build_type.rb +10 -2
  37. data/lib/graphql/schema/member/has_arguments.rb +9 -7
  38. data/lib/graphql/schema/member/has_directives.rb +1 -1
  39. data/lib/graphql/schema/member/has_fields.rb +1 -1
  40. data/lib/graphql/schema/member/has_interfaces.rb +1 -1
  41. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  42. data/lib/graphql/schema/object.rb +6 -1
  43. data/lib/graphql/schema/printer.rb +3 -1
  44. data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
  45. data/lib/graphql/schema/resolver.rb +12 -10
  46. data/lib/graphql/schema/timeout.rb +1 -1
  47. data/lib/graphql/schema/validator.rb +1 -1
  48. data/lib/graphql/schema/warden.rb +37 -4
  49. data/lib/graphql/schema.rb +92 -23
  50. data/lib/graphql/tracing/appoptics_trace.rb +35 -11
  51. data/lib/graphql/tracing/appsignal_trace.rb +4 -0
  52. data/lib/graphql/tracing/data_dog_trace.rb +89 -50
  53. data/lib/graphql/tracing/data_dog_tracing.rb +7 -21
  54. data/lib/graphql/tracing/legacy_trace.rb +5 -1
  55. data/lib/graphql/tracing/notifications_trace.rb +7 -0
  56. data/lib/graphql/tracing/platform_trace.rb +26 -12
  57. data/lib/graphql/tracing/prometheus_trace.rb +4 -0
  58. data/lib/graphql/tracing/scout_trace.rb +3 -0
  59. data/lib/graphql/tracing/statsd_trace.rb +4 -0
  60. data/lib/graphql/tracing.rb +1 -0
  61. data/lib/graphql/types/relay/connection_behaviors.rb +1 -1
  62. data/lib/graphql/types/relay/edge_behaviors.rb +1 -1
  63. data/lib/graphql/version.rb +1 -1
  64. data/readme.md +1 -1
  65. metadata +36 -24
@@ -22,12 +22,13 @@ module GraphQL
22
22
  end
23
23
 
24
24
  module GraphQLResult
25
- def initialize(result_name, parent_result)
25
+ def initialize(result_name, parent_result, is_non_null_in_parent)
26
26
  @graphql_parent = parent_result
27
27
  if parent_result && parent_result.graphql_dead
28
28
  @graphql_dead = true
29
29
  end
30
30
  @graphql_result_name = result_name
31
+ @graphql_is_non_null_in_parent = is_non_null_in_parent
31
32
  # Jump through some hoops to avoid creating this duplicate storage if at all possible.
32
33
  @graphql_metadata = nil
33
34
  end
@@ -42,22 +43,14 @@ module GraphQL
42
43
  end
43
44
 
44
45
  attr_accessor :graphql_dead
45
- attr_reader :graphql_parent, :graphql_result_name
46
-
47
- # Although these are used by only one of the Result classes,
48
- # it's handy to have the methods implemented on both (even though they just return `nil`)
49
- # because it makes it easy to check if anything is assigned.
50
- # @return [nil, Array<String>]
51
- attr_accessor :graphql_non_null_field_names
52
- # @return [nil, true]
53
- attr_accessor :graphql_non_null_list_items
46
+ attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent
54
47
 
55
48
  # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
56
49
  attr_accessor :graphql_result_data
57
50
  end
58
51
 
59
52
  class GraphQLResultHash
60
- def initialize(_result_name, _parent_result)
53
+ def initialize(_result_name, _parent_result, _is_non_null_in_parent)
61
54
  super
62
55
  @graphql_result_data = {}
63
56
  end
@@ -131,7 +124,7 @@ module GraphQL
131
124
  when GraphQLResultArray
132
125
  # There's no special handling of arrays because currently, there's no way to split the execution
133
126
  # of a list over several concurrent flows.
134
- next_result.set_child_result(key, value)
127
+ into_result.set_child_result(key, value)
135
128
  else
136
129
  # We have to assume that, since this passed the `fields_will_merge` selection,
137
130
  # that the old and new values are the same.
@@ -145,7 +138,7 @@ module GraphQL
145
138
  class GraphQLResultArray
146
139
  include GraphQLResult
147
140
 
148
- def initialize(_result_name, _parent_result)
141
+ def initialize(_result_name, _parent_result, _is_non_null_in_parent)
149
142
  super
150
143
  @graphql_result_data = []
151
144
  end
@@ -189,10 +182,6 @@ module GraphQL
189
182
  end
190
183
  end
191
184
 
192
- class GraphQLSelectionSet < Hash
193
- attr_accessor :graphql_directives
194
- end
195
-
196
185
  # @return [GraphQL::Query]
197
186
  attr_reader :query
198
187
 
@@ -204,14 +193,12 @@ module GraphQL
204
193
 
205
194
  def initialize(query:, lazies_at_depth:)
206
195
  @query = query
196
+ @current_trace = query.current_trace
207
197
  @dataloader = query.multiplex.dataloader
208
198
  @lazies_at_depth = lazies_at_depth
209
199
  @schema = query.schema
210
200
  @context = query.context
211
- @multiplex_context = query.multiplex.context
212
- # Start this off empty:
213
- Thread.current[:__graphql_runtime_info] = nil
214
- @response = GraphQLResultHash.new(nil, nil)
201
+ @response = GraphQLResultHash.new(nil, nil, false)
215
202
  # Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
216
203
  @runtime_directive_names = []
217
204
  noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
@@ -225,8 +212,11 @@ module GraphQL
225
212
  # Which assumes that MyObject.get_field("myField") will return the same field
226
213
  # during the lifetime of a query
227
214
  @fields_cache = Hash.new { |h, k| h[k] = {} }
215
+ # this can by by-identity since owners are the same object, but not the sub-hash, which uses strings.
216
+ @fields_cache.compare_by_identity
228
217
  # { Class => Boolean }
229
218
  @lazy_cache = {}
219
+ @lazy_cache.compare_by_identity
230
220
  end
231
221
 
232
222
  def final_result
@@ -257,15 +247,15 @@ module GraphQL
257
247
  st = get_current_runtime_state
258
248
  st.current_object = query.root_value
259
249
  st.current_result = @response
260
- object_proxy = authorized_new(root_type, query.root_value, context)
261
- object_proxy = schema.sync_lazy(object_proxy)
250
+ runtime_object = root_type.wrap(query.root_value, context)
251
+ runtime_object = schema.sync_lazy(runtime_object)
262
252
 
263
- if object_proxy.nil?
253
+ if runtime_object.nil?
264
254
  # Root .authorized? returned false.
265
255
  @response = nil
266
256
  else
267
- call_method_on_directives(:resolve, object_proxy, root_operation.directives) do # execute query level directives
268
- gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
257
+ call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
258
+ gathered_selections = gather_selections(runtime_object, root_type, root_operation.selections)
269
259
  # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
270
260
  # require isolation during execution (because of runtime directives). In that case,
271
261
  # make a new, isolated result hash for writing the result into. (That isolated response
@@ -275,7 +265,7 @@ module GraphQL
275
265
  # directly evaluated and the results can be written right into the main response hash.
276
266
  tap_or_each(gathered_selections) do |selections, is_selection_array|
277
267
  if is_selection_array
278
- selection_response = GraphQLResultHash.new(nil, nil)
268
+ selection_response = GraphQLResultHash.new(nil, nil, false)
279
269
  final_response = @response
280
270
  else
281
271
  selection_response = @response
@@ -286,10 +276,13 @@ module GraphQL
286
276
  st = get_current_runtime_state
287
277
  st.current_object = query.root_value
288
278
  st.current_result = selection_response
289
-
290
- call_method_on_directives(:resolve, object_proxy, selections.graphql_directives) do
279
+ # This is a less-frequent case; use a fast check since it's often not there.
280
+ if (directives = selections[:graphql_directives])
281
+ selections.delete(:graphql_directives)
282
+ end
283
+ call_method_on_directives(:resolve, runtime_object, directives) do
291
284
  evaluate_selections(
292
- object_proxy,
285
+ runtime_object,
293
286
  root_type,
294
287
  root_op_type == "mutation",
295
288
  selections,
@@ -306,7 +299,7 @@ module GraphQL
306
299
  nil
307
300
  end
308
301
 
309
- def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new)
302
+ def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
310
303
  selections.each do |node|
311
304
  # Skip gathering this if the directive says so
312
305
  if !directives_include?(node, owner_object, owner_type)
@@ -332,8 +325,8 @@ module GraphQL
332
325
  else
333
326
  # This is an InlineFragment or a FragmentSpread
334
327
  if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
335
- next_selections = GraphQLSelectionSet.new
336
- next_selections.graphql_directives = node.directives
328
+ next_selections = {}
329
+ next_selections[:graphql_directives] = node.directives
337
330
  if selections_to_run
338
331
  selections_to_run << next_selections
339
332
  else
@@ -350,25 +343,26 @@ module GraphQL
350
343
  if node.type
351
344
  type_defn = schema.get_type(node.type.name, context)
352
345
 
353
- # Faster than .map{}.include?()
354
- query.warden.possible_types(type_defn).each do |t|
355
- if t == owner_type
356
- gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
357
- break
346
+ if query.warden.possible_types(type_defn).include?(owner_type)
347
+ result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
348
+ if !result.equal?(next_selections)
349
+ selections_to_run = result
358
350
  end
359
351
  end
360
352
  else
361
353
  # it's an untyped fragment, definitely continue
362
- gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
354
+ result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
355
+ if !result.equal?(next_selections)
356
+ selections_to_run = result
357
+ end
363
358
  end
364
359
  when GraphQL::Language::Nodes::FragmentSpread
365
360
  fragment_def = query.fragments[node.name]
366
361
  type_defn = query.get_type(fragment_def.type.name)
367
- possible_types = query.warden.possible_types(type_defn)
368
- possible_types.each do |t|
369
- if t == owner_type
370
- gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
371
- break
362
+ if query.warden.possible_types(type_defn).include?(owner_type)
363
+ result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
364
+ if !result.equal?(next_selections)
365
+ selections_to_run = result
372
366
  end
373
367
  end
374
368
  else
@@ -400,6 +394,12 @@ module GraphQL
400
394
  selections_result.merge_into(target_result)
401
395
  end
402
396
  }
397
+ # Field resolution may pause the fiber,
398
+ # so it wouldn't get to the `Resolve` call that happens below.
399
+ # So instead trigger a run from this outer context.
400
+ if is_eager_selection
401
+ @dataloader.run
402
+ end
403
403
  end
404
404
 
405
405
  selections_result
@@ -441,19 +441,14 @@ module GraphQL
441
441
  # the field's return type at this path in order
442
442
  # to propagate `null`
443
443
  return_type_non_null = return_type.non_null?
444
- if return_type_non_null
445
- (selections_result.graphql_non_null_field_names ||= []).push(result_name)
446
- end
447
444
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
448
445
  st = get_current_runtime_state
449
446
  st.current_field = field_defn
450
447
  st.current_result = selections_result
451
448
  st.current_result_name = result_name
452
449
 
453
- object = owner_object
454
-
455
450
  if is_introspection
456
- object = authorized_new(field_defn.owner, object, context)
451
+ owner_object = field_defn.owner.wrap(owner_object, context)
457
452
  end
458
453
 
459
454
  total_args_count = field_defn.arguments(context).size
@@ -461,20 +456,20 @@ module GraphQL
461
456
  resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
462
457
  if field_defn.extras.size == 0
463
458
  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
459
+ NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null
465
460
  )
466
461
  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)
462
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
468
463
  end
469
464
  else
470
- @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
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)
465
+ @query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
466
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
472
467
  end
473
468
  end
474
469
  end
475
470
 
476
471
  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|
472
+ after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
478
473
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
479
474
  continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
480
475
  next
@@ -552,7 +547,7 @@ module GraphQL
552
547
  field_result = call_method_on_directives(:resolve, object, directives) do
553
548
  # Actually call the field resolver and capture the result
554
549
  app_result = begin
555
- query.current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
550
+ @current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
556
551
  field_defn.resolve(object, kwarg_arguments, context)
557
552
  end
558
553
  rescue GraphQL::ExecutionError => err
@@ -564,7 +559,7 @@ module GraphQL
564
559
  ex_err
565
560
  end
566
561
  end
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|
562
+ after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
568
563
  continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
569
564
  if HALT != continue_value
570
565
  continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
@@ -588,13 +583,9 @@ module GraphQL
588
583
  selection_result.graphql_dead # || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
589
584
  end
590
585
 
591
- def set_result(selection_result, result_name, value, is_child_result)
586
+ def set_result(selection_result, result_name, value, is_child_result, is_non_null)
592
587
  if !dead_result?(selection_result)
593
- if value.nil? &&
594
- ( # there are two conditions under which `nil` is not allowed in the response:
595
- (selection_result.graphql_non_null_list_items) || # this value would be written into a list that doesn't allow nils
596
- ((nn = selection_result.graphql_non_null_field_names) && nn.include?(result_name)) # this value would be written into a field that doesn't allow nils
597
- )
588
+ if value.nil? && is_non_null
598
589
  # This is an invalid nil that should be propagated
599
590
  # One caller of this method passes a block,
600
591
  # namely when application code returns a `nil` to GraphQL and it doesn't belong there.
@@ -604,11 +595,12 @@ module GraphQL
604
595
  # TODO the code is trying to tell me something.
605
596
  yield if block_given?
606
597
  parent = selection_result.graphql_parent
607
- name_in_parent = selection_result.graphql_result_name
608
598
  if parent.nil? # This is a top-level result hash
609
599
  @response = nil
610
600
  else
611
- set_result(parent, name_in_parent, nil, false)
601
+ name_in_parent = selection_result.graphql_result_name
602
+ is_non_null_in_parent = selection_result.graphql_is_non_null_in_parent
603
+ set_result(parent, name_in_parent, nil, false, is_non_null_in_parent)
612
604
  set_graphql_dead(selection_result)
613
605
  end
614
606
  elsif is_child_result
@@ -650,13 +642,13 @@ module GraphQL
650
642
  case value
651
643
  when nil
652
644
  if is_non_null
653
- set_result(selection_result, result_name, nil, false) do
645
+ set_result(selection_result, result_name, nil, false, is_non_null) do
654
646
  # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
655
647
  err = parent_type::InvalidNullError.new(parent_type, field, value)
656
648
  schema.type_error(err, context)
657
649
  end
658
650
  else
659
- set_result(selection_result, result_name, nil, false)
651
+ set_result(selection_result, result_name, nil, false, is_non_null)
660
652
  end
661
653
  HALT
662
654
  when GraphQL::Error
@@ -669,7 +661,7 @@ module GraphQL
669
661
  value.ast_node ||= ast_node
670
662
  context.errors << value
671
663
  if selection_result
672
- set_result(selection_result, result_name, nil, false)
664
+ set_result(selection_result, result_name, nil, false, is_non_null)
673
665
  end
674
666
  end
675
667
  HALT
@@ -723,9 +715,9 @@ module GraphQL
723
715
  if selection_result
724
716
  if list_type_at_all
725
717
  result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
726
- set_result(selection_result, result_name, result_without_errors, false)
718
+ set_result(selection_result, result_name, result_without_errors, false, is_non_null)
727
719
  else
728
- set_result(selection_result, result_name, nil, false)
720
+ set_result(selection_result, result_name, nil, false, is_non_null)
729
721
  end
730
722
  end
731
723
  end
@@ -735,7 +727,7 @@ module GraphQL
735
727
  end
736
728
  when GraphQL::Execution::Interpreter::RawValue
737
729
  # Write raw value directly to the response without resolving nested objects
738
- set_result(selection_result, result_name, value.resolve, false)
730
+ set_result(selection_result, result_name, value.resolve, false, is_non_null)
739
731
  HALT
740
732
  else
741
733
  value
@@ -763,11 +755,11 @@ module GraphQL
763
755
  rescue StandardError => err
764
756
  schema.handle_or_reraise(context, err)
765
757
  end
766
- set_result(selection_result, result_name, r, false)
758
+ set_result(selection_result, result_name, r, false, is_non_null)
767
759
  r
768
760
  when "UNION", "INTERFACE"
769
761
  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|
762
+ after_lazy(resolved_type_or_lazy, 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|
771
763
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
772
764
  resolved_type, resolved_value = resolved_type_result
773
765
  else
@@ -781,7 +773,7 @@ module GraphQL
781
773
  err_class = current_type::UnresolvedTypeError
782
774
  type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
783
775
  schema.type_error(type_error, context)
784
- set_result(selection_result, result_name, nil, false)
776
+ set_result(selection_result, result_name, nil, false, is_non_null)
785
777
  nil
786
778
  else
787
779
  continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
@@ -789,15 +781,15 @@ module GraphQL
789
781
  end
790
782
  when "OBJECT"
791
783
  object_proxy = begin
792
- authorized_new(current_type, value, context)
784
+ current_type.wrap(value, context)
793
785
  rescue GraphQL::ExecutionError => err
794
786
  err
795
787
  end
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|
788
+ after_lazy(object_proxy, 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
789
  continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
798
790
  if HALT != continue_value
799
- response_hash = GraphQLResultHash.new(result_name, selection_result)
800
- set_result(selection_result, result_name, response_hash, true)
791
+ response_hash = GraphQLResultHash.new(result_name, selection_result, is_non_null)
792
+ set_result(selection_result, result_name, response_hash, true, is_non_null)
801
793
  gathered_selections = gather_selections(continue_value, current_type, next_selections)
802
794
  # There are two possibilities for `gathered_selections`:
803
795
  # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
@@ -809,7 +801,7 @@ module GraphQL
809
801
  # (Technically, it's possible that one of those entries _doesn't_ require isolation.)
810
802
  tap_or_each(gathered_selections) do |selections, is_selection_array|
811
803
  if is_selection_array
812
- this_result = GraphQLResultHash.new(result_name, selection_result)
804
+ this_result = GraphQLResultHash.new(result_name, selection_result, is_non_null)
813
805
  final_result = response_hash
814
806
  else
815
807
  this_result = response_hash
@@ -822,7 +814,11 @@ module GraphQL
822
814
  st.current_result_name = nil
823
815
  st.current_result = this_result
824
816
 
825
- call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
817
+ # This is a less-frequent case; use a fast check since it's often not there.
818
+ if (directives = selections[:graphql_directives])
819
+ selections.delete(:graphql_directives)
820
+ end
821
+ call_method_on_directives(:resolve, continue_value, directives) do
826
822
  evaluate_selections(
827
823
  continue_value,
828
824
  current_type,
@@ -842,12 +838,12 @@ module GraphQL
842
838
  # This is true for objects, unions, and interfaces
843
839
  use_dataloader_job = !inner_type.unwrap.kind.input?
844
840
  inner_type_non_null = inner_type.non_null?
845
- response_list = GraphQLResultArray.new(result_name, selection_result)
846
- response_list.graphql_non_null_list_items = inner_type_non_null
847
- set_result(selection_result, result_name, response_list, true)
848
- idx = 0
841
+ response_list = GraphQLResultArray.new(result_name, selection_result, is_non_null)
842
+ set_result(selection_result, result_name, response_list, true, is_non_null)
843
+ idx = nil
849
844
  list_value = begin
850
845
  value.each do |inner_value|
846
+ idx ||= 0
851
847
  this_idx = idx
852
848
  idx += 1
853
849
  if use_dataloader_job
@@ -878,8 +874,9 @@ module GraphQL
878
874
  ex_err
879
875
  end
880
876
  end
881
-
882
- continue_value(list_value, owner_type, field, inner_type.non_null?, ast_node, result_name, selection_result)
877
+ # Detect whether this error came while calling `.each` (before `idx` is set) or while running list *items* (after `idx` is set)
878
+ error_is_non_null = idx.nil? ? is_non_null : inner_type.non_null?
879
+ continue_value(list_value, owner_type, field, error_is_non_null, ast_node, result_name, selection_result)
883
880
  else
884
881
  raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
885
882
  end
@@ -891,7 +888,7 @@ module GraphQL
891
888
  st.current_result = response_list
892
889
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
893
890
  # This will update `response_list` with the lazy
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|
891
+ after_lazy(inner_value, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
895
892
  continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
896
893
  if HALT != continue_value
897
894
  continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
@@ -945,7 +942,25 @@ module GraphQL
945
942
  end
946
943
 
947
944
  def get_current_runtime_state
948
- Thread.current[:__graphql_runtime_info] ||= CurrentState.new
945
+ current_state = Thread.current[:__graphql_runtime_info] ||= begin
946
+ per_query_state = {}
947
+ per_query_state.compare_by_identity
948
+ per_query_state
949
+ end
950
+
951
+ current_state[@query] ||= CurrentState.new
952
+ end
953
+
954
+ def minimal_after_lazy(value, &block)
955
+ if lazy?(value)
956
+ GraphQL::Execution::Lazy.new do
957
+ result = @schema.sync_lazy(value)
958
+ # The returned result might also be lazy, so check it, too
959
+ minimal_after_lazy(result, &block)
960
+ end
961
+ else
962
+ yield(value)
963
+ end
949
964
  end
950
965
 
951
966
  # @param obj [Object] Some user-returned value that may want to be batched
@@ -953,7 +968,7 @@ module GraphQL
953
968
  # @param eager [Boolean] Set to `true` for mutation root fields only
954
969
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
955
970
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
956
- def after_lazy(lazy_obj, owner:, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
971
+ def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
957
972
  if lazy?(lazy_obj)
958
973
  orig_result = result
959
974
  lazy = GraphQL::Execution::Lazy.new(field: field) do
@@ -967,7 +982,7 @@ module GraphQL
967
982
  # but don't wrap the continuation below
968
983
  inner_obj = begin
969
984
  if trace
970
- query.current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
985
+ @current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
971
986
  schema.sync_lazy(lazy_obj)
972
987
  end
973
988
  else
@@ -988,7 +1003,7 @@ module GraphQL
988
1003
  if eager
989
1004
  lazy.value
990
1005
  else
991
- set_result(result, result_name, lazy, false)
1006
+ set_result(result, result_name, lazy, false, false) # is_non_null is irrelevant here
992
1007
  current_depth = 0
993
1008
  while result
994
1009
  current_depth += 1
@@ -1013,17 +1028,24 @@ module GraphQL
1013
1028
  end
1014
1029
 
1015
1030
  def delete_all_interpreter_context
1016
- Thread.current[:__graphql_runtime_info] = nil
1031
+ per_query_state = Thread.current[:__graphql_runtime_info]
1032
+ if per_query_state
1033
+ per_query_state.delete(@query)
1034
+ if per_query_state.size == 0
1035
+ Thread.current[:__graphql_runtime_info] = nil
1036
+ end
1037
+ end
1038
+ nil
1017
1039
  end
1018
1040
 
1019
1041
  def resolve_type(type, value)
1020
- resolved_type, resolved_value = query.current_trace.resolve_type(query: query, type: type, object: value) do
1042
+ resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do
1021
1043
  query.resolve_type(type, value)
1022
1044
  end
1023
1045
 
1024
1046
  if lazy?(resolved_type)
1025
1047
  GraphQL::Execution::Lazy.new do
1026
- query.current_trace.resolve_type_lazy(query: query, type: type, object: value) do
1048
+ @current_trace.resolve_type_lazy(query: query, type: type, object: value) do
1027
1049
  schema.sync_lazy(resolved_type)
1028
1050
  end
1029
1051
  end
@@ -1032,14 +1054,13 @@ module GraphQL
1032
1054
  end
1033
1055
  end
1034
1056
 
1035
- def authorized_new(type, value, context)
1036
- type.authorized_new(value, context)
1037
- end
1038
-
1039
1057
  def lazy?(object)
1040
- @lazy_cache.fetch(object.class) {
1041
- @lazy_cache[object.class] = @schema.lazy?(object)
1042
- }
1058
+ obj_class = object.class
1059
+ is_lazy = @lazy_cache[obj_class]
1060
+ if is_lazy.nil?
1061
+ is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object)
1062
+ end
1063
+ is_lazy
1043
1064
  end
1044
1065
  end
1045
1066
  end
@@ -87,7 +87,6 @@ module GraphQL
87
87
 
88
88
  # Then, work through lazy results in a breadth-first way
89
89
  multiplex.dataloader.append_job {
90
- tracer = multiplex
91
90
  query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil
92
91
  queries = multiplex ? multiplex.queries : [query]
93
92
  final_values = queries.map do |query|
@@ -96,7 +95,7 @@ module GraphQL
96
95
  runtime ? runtime.final_result : nil
97
96
  end
98
97
  final_values.compact!
99
- tracer.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
98
+ multiplex.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
100
99
  Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader)
101
100
  end
102
101
  queries.each do |query|
@@ -55,7 +55,7 @@ module GraphQL
55
55
  @arguments
56
56
  else
57
57
  @arguments = if @field
58
- @query.schema.after_lazy(@query.arguments_for(@ast_nodes.first, @field)) do |args|
58
+ @query.after_lazy(@query.arguments_for(@ast_nodes.first, @field)) do |args|
59
59
  args.is_a?(Execution::Interpreter::Arguments) ? args.keyword_arguments : args
60
60
  end
61
61
  else
@@ -6,7 +6,8 @@ module GraphQL
6
6
  class Filter
7
7
  def initialize(only: nil, except: nil, silence_deprecation_warning: false)
8
8
  if !silence_deprecation_warning
9
- GraphQL::Deprecation.warn("GraphQL::Filter is deprecated and will be removed in v2.1.0. Implement `visible?` on your schema members instead (https://graphql-ruby.org/authorization/visibility.html).")
9
+ line = caller(2, 10).find { |l| !l.include?("lib/graphql") }
10
+ GraphQL::Deprecation.warn("GraphQL::Filter, `only:`, `except:`, and `.merge_filters` are deprecated and will be removed in v2.1.0. Implement `visible?` on your schema members instead (https://graphql-ruby.org/authorization/visibility.html).\n #{line}")
10
11
  end
11
12
  @only = only
12
13
  @except = except
@@ -11,7 +11,7 @@ module GraphQL
11
11
  # Apply wrapping manually since this field isn't wrapped by instrumentation
12
12
  schema = @context.query.schema
13
13
  schema_type = schema.introspection_system.types["__Schema"]
14
- schema_type.authorized_new(schema, @context)
14
+ schema_type.wrap(schema, @context)
15
15
  end
16
16
 
17
17
  def __type(name:)
@@ -24,17 +24,24 @@ module GraphQL
24
24
  @include_built_in_directives = include_built_in_directives
25
25
  @include_one_of = false
26
26
 
27
- filter = GraphQL::Filter.new(only: only, except: except, silence_deprecation_warning: true)
28
- if @schema.respond_to?(:visible?)
29
- filter = filter.merge(only: @schema.method(:visible?))
27
+ schema_context = schema.context_class.new(query: nil, object: nil, schema: schema, values: context)
28
+
29
+ @warden = if only || except
30
+ filter = GraphQL::Filter
31
+ .new(only: only, except: except)
32
+ .merge(only: @schema.method(:visible?))
33
+ GraphQL::Schema::Warden.new(
34
+ filter,
35
+ schema: @schema,
36
+ context: schema_context,
37
+ )
38
+ else
39
+ @schema.warden_class.new(
40
+ schema: @schema,
41
+ context: schema_context,
42
+ )
30
43
  end
31
44
 
32
- schema_context = schema.context_class.new(query: nil, object: nil, schema: schema, values: context)
33
- @warden = GraphQL::Schema::Warden.new(
34
- filter,
35
- schema: @schema,
36
- context: schema_context,
37
- )
38
45
  schema_context.warden = @warden
39
46
  end
40
47