graphql 2.0.21 → 2.0.23

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43f1cc3ef36a54a31f8afa03ee3109b34f046e26c427acfb80599a2dd245cf14
4
- data.tar.gz: 4e4a15c766650e36004175de15cfcb9f67c6b4e7d24e2f91712a6ad3f8681453
3
+ metadata.gz: 5aade8e5f88eb3dd8e8d6148aa7e8e00b0a351422b090e02a911158f425f1905
4
+ data.tar.gz: 1ee1e4529f0b1f68596ce73f2b171624637999f0a1d6fc03f377c46fc7756a78
5
5
  SHA512:
6
- metadata.gz: 908f3790bcc773bb6c7fe3db1f5e2ccabf361ece7b462b80fc4e59911167eabb8baf544739f31ac425579bca61b56b46937ac60e7a54a03827228016457388de
7
- data.tar.gz: 62fb9dc90ab55aff66d3d1621fc28b18c0c9599e2f8a7f8521d372a6f549c85ac0a2e50a0d43cb77bde728cb44f85b178bb37388c6bd3d2aed5f8e91723a79ef
6
+ metadata.gz: 8b9cd861e35c529674bdacb6e1a34864bca6852c030022215ecc028d2b36b20da120be4ee45bb5ac5fb906b856e78dc4cfbbcf55609ad47fc47e6d68422a26b1
7
+ data.tar.gz: b4afc463a0c9058396f274c9224f5a6b0bca73fefc68c1e6f10e53eea24b95b89ef6d9ddd3e22929d90be69b7c46bd81e6ed134ddab2550cbfc4408aee29b8ef
@@ -55,9 +55,5 @@ module GraphQL
55
55
  @parent_frame = parent_frame
56
56
  end
57
57
  end
58
-
59
- class DefaultBacktraceTrace < GraphQL::Tracing::Trace
60
- include GraphQL::Backtrace::Trace
61
- end
62
58
  end
63
59
  end
@@ -7,49 +7,50 @@ module GraphQL
7
7
  def initialize(query)
8
8
  @query = query
9
9
  @dataloader = query.context.dataloader
10
- @storage = Hash.new do |h, ast_node|
11
- h[ast_node] = Hash.new do |h2, arg_owner|
12
- h2[arg_owner] = Hash.new do |h3, parent_object|
13
- dataload_for(ast_node, arg_owner, parent_object) do |kwarg_arguments|
14
- h3[parent_object] = @query.schema.after_lazy(kwarg_arguments) do |resolved_args|
15
- h3[parent_object] = resolved_args
16
- end
17
- end
18
-
19
- if !h3.key?(parent_object)
20
- # TODO should i bother putting anything here?
21
- h3[parent_object] = NO_ARGUMENTS
22
- else
23
- h3[parent_object]
24
- end
10
+ @storage = Hash.new do |h, argument_owner|
11
+ args_by_parent = if argument_owner.arguments_statically_coercible?
12
+ shared_values_cache = {}
13
+ Hash.new do |h2, ignored_parent_object|
14
+ h2[ignored_parent_object] = shared_values_cache
15
+ end
16
+ else
17
+ Hash.new do |h2, parent_object|
18
+ args_by_node = {}
19
+ args_by_node.compare_by_identity
20
+ h2[parent_object] = args_by_node
25
21
  end
26
22
  end
23
+ args_by_parent.compare_by_identity
24
+ h[argument_owner] = args_by_parent
27
25
  end
26
+ @storage.compare_by_identity
28
27
  end
29
28
 
30
29
  def fetch(ast_node, argument_owner, parent_object)
31
- # If any jobs were enqueued, run them now,
32
- # since this might have been called outside of execution.
33
- # (The jobs are responsible for updating `result` in-place.)
34
- if !@storage.key?(ast_node) || !@storage[ast_node].key?(argument_owner)
35
- @dataloader.run_isolated do
36
- @storage[ast_node][argument_owner][parent_object]
30
+ # This runs eagerly if no block is given
31
+ @storage[argument_owner][parent_object][ast_node] ||= begin
32
+ args_hash = self.class.prepare_args_hash(@query, ast_node)
33
+ kwarg_arguments = argument_owner.coerce_arguments(parent_object, args_hash, @query.context)
34
+ @query.after_lazy(kwarg_arguments) do |resolved_args|
35
+ @storage[argument_owner][parent_object][ast_node] = resolved_args
37
36
  end
38
37
  end
39
- # Ack, the _hash_ is updated, but the key is eventually
40
- # overridden with an immutable arguments instance.
41
- # The first call queues up the job,
42
- # then this call fetches the result.
43
- # TODO this should be better, find a solution
44
- # that works with merging the runtime.rb code
45
- @storage[ast_node][argument_owner][parent_object]
38
+
46
39
  end
47
40
 
48
41
  # @yield [Interpreter::Arguments, Lazy<Interpreter::Arguments>] The finally-loaded arguments
49
42
  def dataload_for(ast_node, argument_owner, parent_object, &block)
50
43
  # First, normalize all AST or Ruby values to a plain Ruby hash
51
- args_hash = self.class.prepare_args_hash(@query, ast_node)
52
- argument_owner.coerce_arguments(parent_object, args_hash, @query.context, &block)
44
+ arg_storage = @storage[argument_owner][parent_object]
45
+ if (args = arg_storage[ast_node])
46
+ yield(args)
47
+ else
48
+ args_hash = self.class.prepare_args_hash(@query, ast_node)
49
+ argument_owner.coerce_arguments(parent_object, args_hash, @query.context) do |resolved_args|
50
+ arg_storage[ast_node] = resolved_args
51
+ yield(resolved_args)
52
+ end
53
+ end
53
54
  nil
54
55
  end
55
56
 
@@ -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
@@ -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
@@ -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,8 +276,11 @@ 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, object_proxy, directives) do
291
284
  evaluate_selections(
292
285
  object_proxy,
293
286
  root_type,
@@ -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,12 +343,8 @@ 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
358
- end
346
+ if query.warden.possible_types(type_defn).include?(owner_type)
347
+ gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
359
348
  end
360
349
  else
361
350
  # it's an untyped fragment, definitely continue
@@ -364,12 +353,8 @@ module GraphQL
364
353
  when GraphQL::Language::Nodes::FragmentSpread
365
354
  fragment_def = query.fragments[node.name]
366
355
  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
372
- end
356
+ if query.warden.possible_types(type_defn).include?(owner_type)
357
+ gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
373
358
  end
374
359
  else
375
360
  raise "Invariant: unexpected selection class: #{node.class}"
@@ -400,6 +385,12 @@ module GraphQL
400
385
  selections_result.merge_into(target_result)
401
386
  end
402
387
  }
388
+ # Field resolution may pause the fiber,
389
+ # so it wouldn't get to the `Resolve` call that happens below.
390
+ # So instead trigger a run from this outer context.
391
+ if is_eager_selection
392
+ @dataloader.run
393
+ end
403
394
  end
404
395
 
405
396
  selections_result
@@ -441,9 +432,6 @@ module GraphQL
441
432
  # the field's return type at this path in order
442
433
  # to propagate `null`
443
434
  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
435
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
448
436
  st = get_current_runtime_state
449
437
  st.current_field = field_defn
@@ -474,7 +462,7 @@ module GraphQL
474
462
  end
475
463
 
476
464
  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|
465
+ 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
466
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
479
467
  continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
480
468
  next
@@ -552,7 +540,7 @@ module GraphQL
552
540
  field_result = call_method_on_directives(:resolve, object, directives) do
553
541
  # Actually call the field resolver and capture the result
554
542
  app_result = begin
555
- query.current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
543
+ @current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
556
544
  field_defn.resolve(object, kwarg_arguments, context)
557
545
  end
558
546
  rescue GraphQL::ExecutionError => err
@@ -564,7 +552,7 @@ module GraphQL
564
552
  ex_err
565
553
  end
566
554
  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|
555
+ 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
556
  continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
569
557
  if HALT != continue_value
570
558
  continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
@@ -588,13 +576,9 @@ module GraphQL
588
576
  selection_result.graphql_dead # || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
589
577
  end
590
578
 
591
- def set_result(selection_result, result_name, value, is_child_result)
579
+ def set_result(selection_result, result_name, value, is_child_result, is_non_null)
592
580
  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
- )
581
+ if value.nil? && is_non_null
598
582
  # This is an invalid nil that should be propagated
599
583
  # One caller of this method passes a block,
600
584
  # namely when application code returns a `nil` to GraphQL and it doesn't belong there.
@@ -604,11 +588,12 @@ module GraphQL
604
588
  # TODO the code is trying to tell me something.
605
589
  yield if block_given?
606
590
  parent = selection_result.graphql_parent
607
- name_in_parent = selection_result.graphql_result_name
608
591
  if parent.nil? # This is a top-level result hash
609
592
  @response = nil
610
593
  else
611
- set_result(parent, name_in_parent, nil, false)
594
+ name_in_parent = selection_result.graphql_result_name
595
+ is_non_null_in_parent = selection_result.graphql_is_non_null_in_parent
596
+ set_result(parent, name_in_parent, nil, false, is_non_null_in_parent)
612
597
  set_graphql_dead(selection_result)
613
598
  end
614
599
  elsif is_child_result
@@ -650,13 +635,13 @@ module GraphQL
650
635
  case value
651
636
  when nil
652
637
  if is_non_null
653
- set_result(selection_result, result_name, nil, false) do
638
+ set_result(selection_result, result_name, nil, false, is_non_null) do
654
639
  # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
655
640
  err = parent_type::InvalidNullError.new(parent_type, field, value)
656
641
  schema.type_error(err, context)
657
642
  end
658
643
  else
659
- set_result(selection_result, result_name, nil, false)
644
+ set_result(selection_result, result_name, nil, false, is_non_null)
660
645
  end
661
646
  HALT
662
647
  when GraphQL::Error
@@ -669,7 +654,7 @@ module GraphQL
669
654
  value.ast_node ||= ast_node
670
655
  context.errors << value
671
656
  if selection_result
672
- set_result(selection_result, result_name, nil, false)
657
+ set_result(selection_result, result_name, nil, false, is_non_null)
673
658
  end
674
659
  end
675
660
  HALT
@@ -723,9 +708,9 @@ module GraphQL
723
708
  if selection_result
724
709
  if list_type_at_all
725
710
  result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
726
- set_result(selection_result, result_name, result_without_errors, false)
711
+ set_result(selection_result, result_name, result_without_errors, false, is_non_null)
727
712
  else
728
- set_result(selection_result, result_name, nil, false)
713
+ set_result(selection_result, result_name, nil, false, is_non_null)
729
714
  end
730
715
  end
731
716
  end
@@ -735,7 +720,7 @@ module GraphQL
735
720
  end
736
721
  when GraphQL::Execution::Interpreter::RawValue
737
722
  # Write raw value directly to the response without resolving nested objects
738
- set_result(selection_result, result_name, value.resolve, false)
723
+ set_result(selection_result, result_name, value.resolve, false, is_non_null)
739
724
  HALT
740
725
  else
741
726
  value
@@ -763,11 +748,11 @@ module GraphQL
763
748
  rescue StandardError => err
764
749
  schema.handle_or_reraise(context, err)
765
750
  end
766
- set_result(selection_result, result_name, r, false)
751
+ set_result(selection_result, result_name, r, false, is_non_null)
767
752
  r
768
753
  when "UNION", "INTERFACE"
769
754
  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|
755
+ 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
756
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
772
757
  resolved_type, resolved_value = resolved_type_result
773
758
  else
@@ -781,7 +766,7 @@ module GraphQL
781
766
  err_class = current_type::UnresolvedTypeError
782
767
  type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
783
768
  schema.type_error(type_error, context)
784
- set_result(selection_result, result_name, nil, false)
769
+ set_result(selection_result, result_name, nil, false, is_non_null)
785
770
  nil
786
771
  else
787
772
  continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
@@ -793,11 +778,11 @@ module GraphQL
793
778
  rescue GraphQL::ExecutionError => err
794
779
  err
795
780
  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|
781
+ 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
782
  continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
798
783
  if HALT != continue_value
799
- response_hash = GraphQLResultHash.new(result_name, selection_result)
800
- set_result(selection_result, result_name, response_hash, true)
784
+ response_hash = GraphQLResultHash.new(result_name, selection_result, is_non_null)
785
+ set_result(selection_result, result_name, response_hash, true, is_non_null)
801
786
  gathered_selections = gather_selections(continue_value, current_type, next_selections)
802
787
  # There are two possibilities for `gathered_selections`:
803
788
  # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
@@ -809,7 +794,7 @@ module GraphQL
809
794
  # (Technically, it's possible that one of those entries _doesn't_ require isolation.)
810
795
  tap_or_each(gathered_selections) do |selections, is_selection_array|
811
796
  if is_selection_array
812
- this_result = GraphQLResultHash.new(result_name, selection_result)
797
+ this_result = GraphQLResultHash.new(result_name, selection_result, is_non_null)
813
798
  final_result = response_hash
814
799
  else
815
800
  this_result = response_hash
@@ -822,7 +807,11 @@ module GraphQL
822
807
  st.current_result_name = nil
823
808
  st.current_result = this_result
824
809
 
825
- call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
810
+ # This is a less-frequent case; use a fast check since it's often not there.
811
+ if (directives = selections[:graphql_directives])
812
+ selections.delete(:graphql_directives)
813
+ end
814
+ call_method_on_directives(:resolve, continue_value, directives) do
826
815
  evaluate_selections(
827
816
  continue_value,
828
817
  current_type,
@@ -842,12 +831,12 @@ module GraphQL
842
831
  # This is true for objects, unions, and interfaces
843
832
  use_dataloader_job = !inner_type.unwrap.kind.input?
844
833
  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
834
+ response_list = GraphQLResultArray.new(result_name, selection_result, is_non_null)
835
+ set_result(selection_result, result_name, response_list, true, is_non_null)
836
+ idx = nil
849
837
  list_value = begin
850
838
  value.each do |inner_value|
839
+ idx ||= 0
851
840
  this_idx = idx
852
841
  idx += 1
853
842
  if use_dataloader_job
@@ -878,8 +867,9 @@ module GraphQL
878
867
  ex_err
879
868
  end
880
869
  end
881
-
882
- continue_value(list_value, owner_type, field, inner_type.non_null?, ast_node, result_name, selection_result)
870
+ # Detect whether this error came while calling `.each` (before `idx` is set) or while running list *items* (after `idx` is set)
871
+ error_is_non_null = idx.nil? ? is_non_null : inner_type.non_null?
872
+ continue_value(list_value, owner_type, field, error_is_non_null, ast_node, result_name, selection_result)
883
873
  else
884
874
  raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
885
875
  end
@@ -891,7 +881,7 @@ module GraphQL
891
881
  st.current_result = response_list
892
882
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
893
883
  # 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|
884
+ 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
885
  continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
896
886
  if HALT != continue_value
897
887
  continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
@@ -945,7 +935,25 @@ module GraphQL
945
935
  end
946
936
 
947
937
  def get_current_runtime_state
948
- Thread.current[:__graphql_runtime_info] ||= CurrentState.new
938
+ current_state = Thread.current[:__graphql_runtime_info] ||= begin
939
+ per_query_state = {}
940
+ per_query_state.compare_by_identity
941
+ per_query_state
942
+ end
943
+
944
+ current_state[@query] ||= CurrentState.new
945
+ end
946
+
947
+ def minimal_after_lazy(value, &block)
948
+ if lazy?(value)
949
+ GraphQL::Execution::Lazy.new do
950
+ result = @schema.sync_lazy(value)
951
+ # The returned result might also be lazy, so check it, too
952
+ minimal_after_lazy(result, &block)
953
+ end
954
+ else
955
+ yield(value)
956
+ end
949
957
  end
950
958
 
951
959
  # @param obj [Object] Some user-returned value that may want to be batched
@@ -953,7 +961,7 @@ module GraphQL
953
961
  # @param eager [Boolean] Set to `true` for mutation root fields only
954
962
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
955
963
  # @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)
964
+ def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
957
965
  if lazy?(lazy_obj)
958
966
  orig_result = result
959
967
  lazy = GraphQL::Execution::Lazy.new(field: field) do
@@ -967,7 +975,7 @@ module GraphQL
967
975
  # but don't wrap the continuation below
968
976
  inner_obj = begin
969
977
  if trace
970
- query.current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
978
+ @current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
971
979
  schema.sync_lazy(lazy_obj)
972
980
  end
973
981
  else
@@ -988,7 +996,7 @@ module GraphQL
988
996
  if eager
989
997
  lazy.value
990
998
  else
991
- set_result(result, result_name, lazy, false)
999
+ set_result(result, result_name, lazy, false, false) # is_non_null is irrelevant here
992
1000
  current_depth = 0
993
1001
  while result
994
1002
  current_depth += 1
@@ -1013,17 +1021,24 @@ module GraphQL
1013
1021
  end
1014
1022
 
1015
1023
  def delete_all_interpreter_context
1016
- Thread.current[:__graphql_runtime_info] = nil
1024
+ per_query_state = Thread.current[:__graphql_runtime_info]
1025
+ if per_query_state
1026
+ per_query_state.delete(@query)
1027
+ if per_query_state.size == 0
1028
+ Thread.current[:__graphql_runtime_info] = nil
1029
+ end
1030
+ end
1031
+ nil
1017
1032
  end
1018
1033
 
1019
1034
  def resolve_type(type, value)
1020
- resolved_type, resolved_value = query.current_trace.resolve_type(query: query, type: type, object: value) do
1035
+ resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do
1021
1036
  query.resolve_type(type, value)
1022
1037
  end
1023
1038
 
1024
1039
  if lazy?(resolved_type)
1025
1040
  GraphQL::Execution::Lazy.new do
1026
- query.current_trace.resolve_type_lazy(query: query, type: type, object: value) do
1041
+ @current_trace.resolve_type_lazy(query: query, type: type, object: value) do
1027
1042
  schema.sync_lazy(resolved_type)
1028
1043
  end
1029
1044
  end
@@ -1037,9 +1052,12 @@ module GraphQL
1037
1052
  end
1038
1053
 
1039
1054
  def lazy?(object)
1040
- @lazy_cache.fetch(object.class) {
1041
- @lazy_cache[object.class] = @schema.lazy?(object)
1042
- }
1055
+ obj_class = object.class
1056
+ is_lazy = @lazy_cache[obj_class]
1057
+ if is_lazy.nil?
1058
+ is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object)
1059
+ end
1060
+ is_lazy
1043
1061
  end
1044
1062
  end
1045
1063
  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
@@ -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