graphql 2.0.21 → 2.0.23

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.

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