graphql 2.3.4 → 2.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  3. data/lib/graphql/analysis/analyzer.rb +89 -0
  4. data/lib/graphql/analysis/field_usage.rb +82 -0
  5. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  6. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  7. data/lib/graphql/analysis/query_complexity.rb +183 -0
  8. data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
  9. data/lib/graphql/analysis/visitor.rb +282 -0
  10. data/lib/graphql/analysis.rb +92 -1
  11. data/lib/graphql/dataloader/async_dataloader.rb +2 -0
  12. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  13. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +7 -4
  14. data/lib/graphql/execution/interpreter/runtime.rb +40 -59
  15. data/lib/graphql/execution/interpreter.rb +2 -2
  16. data/lib/graphql/language/nodes.rb +17 -22
  17. data/lib/graphql/language/parser.rb +54 -13
  18. data/lib/graphql/query/validation_pipeline.rb +2 -2
  19. data/lib/graphql/query.rb +1 -1
  20. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  21. data/lib/graphql/schema/addition.rb +21 -11
  22. data/lib/graphql/schema/argument.rb +19 -5
  23. data/lib/graphql/schema/directive.rb +2 -0
  24. data/lib/graphql/schema/field.rb +8 -0
  25. data/lib/graphql/schema/has_single_input_argument.rb +1 -0
  26. data/lib/graphql/schema/input_object.rb +1 -0
  27. data/lib/graphql/schema/introspection_system.rb +2 -2
  28. data/lib/graphql/schema/late_bound_type.rb +4 -0
  29. data/lib/graphql/schema/list.rb +2 -2
  30. data/lib/graphql/schema/member/has_arguments.rb +2 -35
  31. data/lib/graphql/schema/member/has_directives.rb +1 -1
  32. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  33. data/lib/graphql/schema/member/type_system_helpers.rb +1 -2
  34. data/lib/graphql/schema/resolver.rb +1 -0
  35. data/lib/graphql/schema/warden.rb +2 -3
  36. data/lib/graphql/schema.rb +20 -20
  37. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  38. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  39. data/lib/graphql/subscriptions/broadcast_analyzer.rb +1 -1
  40. data/lib/graphql/subscriptions.rb +1 -1
  41. data/lib/graphql/type_kinds.rb +1 -1
  42. data/lib/graphql/version.rb +1 -1
  43. data/lib/graphql.rb +0 -8
  44. metadata +10 -11
  45. data/lib/graphql/analysis/ast/analyzer.rb +0 -91
  46. data/lib/graphql/analysis/ast/field_usage.rb +0 -84
  47. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  48. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  49. data/lib/graphql/analysis/ast/query_complexity.rb +0 -185
  50. data/lib/graphql/analysis/ast/visitor.rb +0 -284
  51. data/lib/graphql/analysis/ast.rb +0 -94
@@ -65,16 +65,6 @@ module GraphQL
65
65
  "#<#{self.class.name} response=#{@response.inspect}>"
66
66
  end
67
67
 
68
- def tap_or_each(obj_or_array)
69
- if obj_or_array.is_a?(Array)
70
- obj_or_array.each do |item|
71
- yield(item, true)
72
- end
73
- else
74
- yield(obj_or_array, false)
75
- end
76
- end
77
-
78
68
  # This _begins_ the execution. Some deferred work
79
69
  # might be stored up in lazies.
80
70
  # @return [void]
@@ -84,7 +74,8 @@ module GraphQL
84
74
  root_type = schema.root_type_for_operation(root_op_type)
85
75
  runtime_object = root_type.wrap(query.root_value, context)
86
76
  runtime_object = schema.sync_lazy(runtime_object)
87
- @response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false)
77
+ is_eager = root_op_type == "mutation"
78
+ @response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, root_operation.selections, is_eager)
88
79
  st = get_current_runtime_state
89
80
  st.current_result = @response
90
81
 
@@ -93,17 +84,9 @@ module GraphQL
93
84
  @response = nil
94
85
  else
95
86
  call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
96
- gathered_selections = gather_selections(runtime_object, root_type, root_operation.selections)
97
- # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
98
- # require isolation during execution (because of runtime directives). In that case,
99
- # make a new, isolated result hash for writing the result into. (That isolated response
100
- # is eventually merged back into the main response)
101
- #
102
- # Otherwise, `gathered_selections` is a hash of selections which can be
103
- # directly evaluated and the results can be written right into the main response hash.
104
- tap_or_each(gathered_selections) do |selections, is_selection_array|
87
+ each_gathered_selections(@response) do |selections, is_selection_array|
105
88
  if is_selection_array
106
- selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false)
89
+ selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, selections, is_eager)
107
90
  final_response = @response
108
91
  else
109
92
  selection_response = @response
@@ -112,12 +95,10 @@ module GraphQL
112
95
 
113
96
  @dataloader.append_job {
114
97
  evaluate_selections(
115
- root_op_type == "mutation",
116
98
  selections,
117
99
  selection_response,
118
100
  final_response,
119
101
  nil,
120
- nil,
121
102
  )
122
103
  }
123
104
  end
@@ -126,6 +107,17 @@ module GraphQL
126
107
  nil
127
108
  end
128
109
 
110
+ def each_gathered_selections(response_hash)
111
+ gathered_selections = gather_selections(response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections)
112
+ if gathered_selections.is_a?(Array)
113
+ gathered_selections.each do |item|
114
+ yield(item, true)
115
+ end
116
+ else
117
+ yield(gathered_selections, false)
118
+ end
119
+ end
120
+
129
121
  def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
130
122
  selections.each do |node|
131
123
  # Skip gathering this if the directive says so
@@ -138,7 +130,7 @@ module GraphQL
138
130
  selections = selections_by_name[response_key]
139
131
  # if there was already a selection of this field,
140
132
  # use an array to hold all selections,
141
- # otherise, use the single node to represent the selection
133
+ # otherwise, use the single node to represent the selection
142
134
  if selections
143
135
  # This field was already selected at least once,
144
136
  # add this node to the list of selections
@@ -203,7 +195,7 @@ module GraphQL
203
195
  NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
204
196
 
205
197
  # @return [void]
206
- def evaluate_selections(is_eager_selection, gathered_selections, selections_result, target_result, parent_object, runtime_state) # rubocop:disable Metrics/ParameterLists
198
+ def evaluate_selections(gathered_selections, selections_result, target_result, runtime_state) # rubocop:disable Metrics/ParameterLists
207
199
  runtime_state ||= get_current_runtime_state
208
200
  runtime_state.current_result_name = nil
209
201
  runtime_state.current_result = selections_result
@@ -218,7 +210,7 @@ module GraphQL
218
210
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
219
211
  @dataloader.append_job {
220
212
  evaluate_selection(
221
- result_name, field_ast_nodes_or_ast_node, is_eager_selection, selections_result, parent_object
213
+ result_name, field_ast_nodes_or_ast_node, selections_result
222
214
  )
223
215
  finished_jobs += 1
224
216
  if target_result && finished_jobs == enqueued_jobs
@@ -228,7 +220,7 @@ module GraphQL
228
220
  # Field resolution may pause the fiber,
229
221
  # so it wouldn't get to the `Resolve` call that happens below.
230
222
  # So instead trigger a run from this outer context.
231
- if is_eager_selection
223
+ if selections_result.graphql_is_eager
232
224
  @dataloader.clear_cache
233
225
  @dataloader.run
234
226
  @dataloader.clear_cache
@@ -239,7 +231,7 @@ module GraphQL
239
231
  end
240
232
 
241
233
  # @return [void]
242
- def evaluate_selection(result_name, field_ast_nodes_or_ast_node, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
234
+ def evaluate_selection(result_name, field_ast_nodes_or_ast_node, selections_result) # rubocop:disable Metrics/ParameterLists
243
235
  return if selections_result.graphql_dead
244
236
  # As a performance optimization, the hash key will be a `Node` if
245
237
  # there's only one selection of the field. But if there are multiple
@@ -266,28 +258,27 @@ module GraphQL
266
258
  owner_object = field_defn.owner.wrap(owner_object, context)
267
259
  end
268
260
 
269
- return_type = field_defn.type
270
261
  if !field_defn.any_arguments?
271
262
  resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
272
263
  if field_defn.extras.size == 0
273
264
  evaluate_selection_with_resolved_keyword_args(
274
- NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type.non_null?, runtime_state
265
+ NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state
275
266
  )
276
267
  else
277
- evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, runtime_state)
268
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state)
278
269
  end
279
270
  else
280
271
  @query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
281
272
  runtime_state = get_current_runtime_state # This might be in a different fiber
282
- evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, runtime_state)
273
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state)
283
274
  end
284
275
  end
285
276
  end
286
277
 
287
- def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, object, is_eager_field, result_name, selection_result, parent_object, return_type, runtime_state) # rubocop:disable Metrics/ParameterLists
278
+ def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) # rubocop:disable Metrics/ParameterLists
288
279
  after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_arguments, runtime_state|
289
- return_type_non_null = return_type.non_null?
290
280
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
281
+ return_type_non_null = field_defn.type.non_null?
291
282
  continue_value(resolved_arguments, field_defn, return_type_non_null, ast_node, result_name, selection_result)
292
283
  next
293
284
  end
@@ -326,7 +317,8 @@ module GraphQL
326
317
  # to the keyword args hash _before_ freezing everything.
327
318
  extra_args[:argument_details] = :__arguments_add_self
328
319
  when :parent
329
- extra_args[:parent] = parent_object
320
+ parent_result = selection_result.graphql_parent
321
+ extra_args[:parent] = parent_result&.graphql_application_value&.object
330
322
  else
331
323
  extra_args[extra] = field_defn.fetch_extra(extra, context)
332
324
  end
@@ -337,11 +329,11 @@ module GraphQL
337
329
  resolved_arguments.keyword_arguments
338
330
  end
339
331
 
340
- evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null, runtime_state)
332
+ evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state)
341
333
  end
342
334
  end
343
335
 
344
- def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null, runtime_state) # rubocop:disable Metrics/ParameterLists
336
+ def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) # rubocop:disable Metrics/ParameterLists
345
337
  runtime_state.current_field = field_defn
346
338
  runtime_state.current_arguments = resolved_arguments
347
339
  runtime_state.current_result_name = result_name
@@ -384,7 +376,8 @@ module GraphQL
384
376
  end
385
377
  after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_result, runtime_state|
386
378
  owner_type = selection_result.graphql_result_type
387
- continue_value = continue_value(inner_result, field_defn, return_type_non_null, ast_node, result_name, selection_result)
379
+ return_type = field_defn.type
380
+ continue_value = continue_value(inner_result, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
388
381
  if HALT != continue_value
389
382
  was_scoped = runtime_state.was_authorized_by_scope_items
390
383
  runtime_state.was_authorized_by_scope_items = nil
@@ -395,7 +388,7 @@ module GraphQL
395
388
  # If this field is a root mutation field, immediately resolve
396
389
  # all of its child fields before moving on to the next root mutation field.
397
390
  # (Subselections of this mutation will still be resolved level-by-level.)
398
- if is_eager_field
391
+ if selection_result.graphql_is_eager
399
392
  Interpreter::Resolve.resolve_all([field_result], @dataloader)
400
393
  end
401
394
  end
@@ -607,21 +600,11 @@ module GraphQL
607
600
  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, runtime_state: runtime_state) do |inner_object, runtime_state|
608
601
  continue_value = continue_value(inner_object, field, is_non_null, ast_node, result_name, selection_result)
609
602
  if HALT != continue_value
610
- response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null)
603
+ response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false)
611
604
  set_result(selection_result, result_name, response_hash, true, is_non_null)
612
-
613
- gathered_selections = gather_selections(continue_value, current_type, next_selections)
614
- # There are two possibilities for `gathered_selections`:
615
- # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
616
- # This case is handled below, and the result can be written right into the main `response_hash` above.
617
- # In this case, `gathered_selections` is a hash of selections.
618
- # 2. Some selections of this object have runtime directives that may or may not modify execution.
619
- # That part of the selection is evaluated in an isolated way, writing into a sub-response object which is
620
- # eventually merged into the final response. In this case, `gathered_selections` is an array of things to run in isolation.
621
- # (Technically, it's possible that one of those entries _doesn't_ require isolation.)
622
- tap_or_each(gathered_selections) do |selections, is_selection_array|
605
+ each_gathered_selections(response_hash) do |selections, is_selection_array|
623
606
  if is_selection_array
624
- this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null)
607
+ this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false)
625
608
  final_result = response_hash
626
609
  else
627
610
  this_result = response_hash
@@ -629,11 +612,9 @@ module GraphQL
629
612
  end
630
613
 
631
614
  evaluate_selections(
632
- false,
633
615
  selections,
634
616
  this_result,
635
617
  final_result,
636
- owner_object.object,
637
618
  runtime_state,
638
619
  )
639
620
  end
@@ -644,7 +625,7 @@ module GraphQL
644
625
  # This is true for objects, unions, and interfaces
645
626
  use_dataloader_job = !inner_type.unwrap.kind.input?
646
627
  inner_type_non_null = inner_type.non_null?
647
- response_list = GraphQLResultArray.new(result_name, current_type, response_list, selection_result, is_non_null)
628
+ response_list = GraphQLResultArray.new(result_name, current_type, owner_object, selection_result, is_non_null, next_selections, false)
648
629
  set_result(selection_result, result_name, response_list, true, is_non_null)
649
630
  idx = nil
650
631
  list_value = begin
@@ -654,10 +635,10 @@ module GraphQL
654
635
  idx += 1
655
636
  if use_dataloader_job
656
637
  @dataloader.append_job do
657
- resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type, was_scoped, runtime_state)
638
+ resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state)
658
639
  end
659
640
  else
660
- resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type, was_scoped, runtime_state)
641
+ resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state)
661
642
  end
662
643
  end
663
644
 
@@ -688,7 +669,7 @@ module GraphQL
688
669
  end
689
670
  end
690
671
 
691
- def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists
672
+ def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists
692
673
  runtime_state.current_result_name = this_idx
693
674
  runtime_state.current_result = response_list
694
675
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
@@ -696,7 +677,7 @@ module GraphQL
696
677
  after_lazy(inner_value, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list, runtime_state: runtime_state) do |inner_inner_value, runtime_state|
697
678
  continue_value = continue_value(inner_inner_value, field, inner_type_non_null, ast_node, this_idx, response_list)
698
679
  if HALT != continue_value
699
- continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list, was_scoped, runtime_state)
680
+ continue_field(continue_value, owner_type, field, inner_type, ast_node, response_list.graphql_selections, false, owner_object, arguments, this_idx, response_list, was_scoped, runtime_state)
700
681
  end
701
682
  end
702
683
  end
@@ -20,7 +20,7 @@ module GraphQL
20
20
  # @param queries [Array<GraphQL::Query, Hash>]
21
21
  # @param context [Hash]
22
22
  # @param max_complexity [Integer, nil]
23
- # @return [Array<Hash>] One result per query
23
+ # @return [Array<GraphQL::Query::Result>] One result per query
24
24
  def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
25
25
  queries = query_options.map do |opts|
26
26
  case opts
@@ -40,7 +40,7 @@ module GraphQL
40
40
  lazies_at_depth = Hash.new { |h, k| h[k] = [] }
41
41
  multiplex_analyzers = schema.multiplex_analyzers
42
42
  if multiplex.max_complexity
43
- multiplex_analyzers += [GraphQL::Analysis::AST::MaxQueryComplexity]
43
+ multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
44
44
  end
45
45
 
46
46
  schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
@@ -274,6 +274,8 @@ module GraphQL
274
274
  ]
275
275
 
276
276
  def generate_initialize
277
+ return if method_defined?(:marshal_load, false) # checking for `:initialize` doesn't work right
278
+
277
279
  scalar_method_names = @scalar_methods
278
280
  # TODO: These probably should be scalar methods, but `types` returns an array
279
281
  [:types, :description].each do |extra_method|
@@ -392,16 +394,6 @@ module GraphQL
392
394
 
393
395
  # A single selection in a GraphQL query.
394
396
  class Field < AbstractNode
395
- scalar_methods :name, :alias
396
- children_methods({
397
- arguments: GraphQL::Language::Nodes::Argument,
398
- selections: GraphQL::Language::Nodes::Field,
399
- directives: GraphQL::Language::Nodes::Directive,
400
- })
401
-
402
- # @!attribute selections
403
- # @return [Array<Nodes::Field>] Selections on this object (or empty array if this is a scalar field)
404
-
405
397
  def initialize(name: nil, arguments: NONE, directives: NONE, selections: NONE, field_alias: nil, line: nil, col: nil, pos: nil, filename: nil, source: nil)
406
398
  @name = name
407
399
  @arguments = arguments || NONE
@@ -428,24 +420,19 @@ module GraphQL
428
420
  @line, @col, @filename, @name, @arguments, @directives, @selections, @alias = values
429
421
  end
430
422
 
431
- # Override this because default is `:fields`
432
- self.children_method_name = :selections
433
- end
434
-
435
- # A reusable fragment, defined at document-level.
436
- class FragmentDefinition < AbstractNode
437
- scalar_methods :name, :type
423
+ scalar_methods :name, :alias
438
424
  children_methods({
425
+ arguments: GraphQL::Language::Nodes::Argument,
439
426
  selections: GraphQL::Language::Nodes::Field,
440
427
  directives: GraphQL::Language::Nodes::Directive,
441
428
  })
442
429
 
443
- self.children_method_name = :definitions
444
- # @!attribute name
445
- # @return [String] the identifier for this fragment, which may be applied with `...#{name}`
430
+ # Override this because default is `:fields`
431
+ self.children_method_name = :selections
432
+ end
446
433
 
447
- # @!attribute type
448
- # @return [String] the type condition for this fragment (name of type which it may apply to)
434
+ # A reusable fragment, defined at document-level.
435
+ class FragmentDefinition < AbstractNode
449
436
  def initialize(name: nil, type: nil, directives: NONE, selections: NONE, filename: nil, pos: nil, source: nil, line: nil, col: nil)
450
437
  @name = name
451
438
  @type = type
@@ -469,6 +456,14 @@ module GraphQL
469
456
  def marshal_load(values)
470
457
  @line, @col, @filename, @name, @type, @directives, @selections = values
471
458
  end
459
+
460
+ scalar_methods :name, :type
461
+ children_methods({
462
+ selections: GraphQL::Language::Nodes::Field,
463
+ directives: GraphQL::Language::Nodes::Directive,
464
+ })
465
+
466
+ self.children_method_name = :definitions
472
467
  end
473
468
 
474
469
  # Application of a named fragment in a selection
@@ -379,7 +379,12 @@ module GraphQL
379
379
  v_loc = pos
380
380
  description = if at?(:STRING); string_value; end
381
381
  defn_loc = pos
382
- enum_value = parse_enum_name
382
+ # Any identifier, but not true, false, or null
383
+ enum_value = if at?(:TRUE) || at?(:FALSE) || at?(:NULL)
384
+ expect_token(:IDENTIFIER)
385
+ else
386
+ parse_name
387
+ end
383
388
  v_directives = parse_directives
384
389
  list << EnumValueDefinition.new(pos: v_loc, definition_pos: defn_loc, description: description, name: enum_value, directives: v_directives, filename: @filename, source: self)
385
390
  end
@@ -615,9 +620,6 @@ module GraphQL
615
620
  when :ON
616
621
  advance_token
617
622
  "on"
618
- when :DIRECTIVE
619
- advance_token
620
- "directive"
621
623
  when :EXTEND
622
624
  advance_token
623
625
  "extend"
@@ -634,15 +636,6 @@ module GraphQL
634
636
  end
635
637
  end
636
638
 
637
- # Any identifier, but not true, false, or null
638
- def parse_enum_name
639
- if at?(:TRUE) || at?(:FALSE) || at?(:NULL)
640
- expect_token(:IDENTIFIER)
641
- else
642
- parse_name
643
- end
644
- end
645
-
646
639
  def parse_type_name
647
640
  TypeName.new(pos: pos, name: parse_name, filename: @filename, source: self)
648
641
  end
@@ -733,6 +726,54 @@ module GraphQL
733
726
  loc = pos
734
727
  advance_token
735
728
  VariableIdentifier.new(pos: loc, name: parse_name, filename: @filename, source: self)
729
+ when :SCHEMA
730
+ advance_token
731
+ Nodes::Enum.new(pos: pos, name: "schema", filename: @filename, source: self)
732
+ when :SCALAR
733
+ advance_token
734
+ Nodes::Enum.new(pos: pos, name: "scalar", filename: @filename, source: self)
735
+ when :IMPLEMENTS
736
+ advance_token
737
+ Nodes::Enum.new(pos: pos, name: "implements", filename: @filename, source: self)
738
+ when :INTERFACE
739
+ advance_token
740
+ Nodes::Enum.new(pos: pos, name: "interface", filename: @filename, source: self)
741
+ when :UNION
742
+ advance_token
743
+ Nodes::Enum.new(pos: pos, name: "union", filename: @filename, source: self)
744
+ when :ENUM
745
+ advance_token
746
+ Nodes::Enum.new(pos: pos, name: "enum", filename: @filename, source: self)
747
+ when :INPUT
748
+ advance_token
749
+ Nodes::Enum.new(pos: pos, name: "input", filename: @filename, source: self)
750
+ when :DIRECTIVE
751
+ advance_token
752
+ Nodes::Enum.new(pos: pos, name: "directive", filename: @filename, source: self)
753
+ when :TYPE
754
+ advance_token
755
+ Nodes::Enum.new(pos: pos, name: "type", filename: @filename, source: self)
756
+ when :QUERY
757
+ advance_token
758
+ Nodes::Enum.new(pos: pos, name: "query", filename: @filename, source: self)
759
+ when :MUTATION
760
+ advance_token
761
+ Nodes::Enum.new(pos: pos, name: "mutation", filename: @filename, source: self)
762
+ when :SUBSCRIPTION
763
+ advance_token
764
+ Nodes::Enum.new(pos: pos, name: "subscription", filename: @filename, source: self)
765
+ when :FRAGMENT
766
+ advance_token
767
+ Nodes::Enum.new(pos: pos, name: "fragment", filename: @filename, source: self)
768
+ when :REPEATABLE
769
+ advance_token
770
+ Nodes::Enum.new(pos: pos, name: "repeatable", filename: @filename, source: self)
771
+ when :ON
772
+ advance_token
773
+ Nodes::Enum.new(pos: pos, name: "on", filename: @filename, source: self)
774
+ when :EXTEND
775
+ advance_token
776
+ Nodes::Enum.new(pos: pos, name: "extend", filename: @filename, source: self)
736
777
  else
737
778
  expect_token(:VALUE)
738
779
  end
@@ -100,10 +100,10 @@ module GraphQL
100
100
  # Depending on the analysis engine, we must use different analyzers
101
101
  # remove this once everything has switched over to AST analyzers
102
102
  if max_depth
103
- qa << GraphQL::Analysis::AST::MaxQueryDepth
103
+ qa << GraphQL::Analysis::MaxQueryDepth
104
104
  end
105
105
  if max_complexity
106
- qa << GraphQL::Analysis::AST::MaxQueryComplexity
106
+ qa << GraphQL::Analysis::MaxQueryComplexity
107
107
  end
108
108
  qa
109
109
  else
data/lib/graphql/query.rb CHANGED
@@ -222,7 +222,7 @@ module GraphQL
222
222
  end
223
223
 
224
224
  # Get the result for this query, executing it once
225
- # @return [Hash] A GraphQL response, with `"data"` and/or `"errors"` keys
225
+ # @return [GraphQL::Query::Result] A Hash-like GraphQL response, with `"data"` and/or `"errors"` keys
226
226
  def result
227
227
  if !@executed
228
228
  Execution::Interpreter.run_all(@schema, [self], context: @context)
@@ -9,7 +9,7 @@ module GraphQL
9
9
 
10
10
  # Return the source of `send_node`, but without the keyword argument represented by `pair_node`
11
11
  def source_without_keyword_argument(send_node, pair_node)
12
- # work back to the preceeding comma
12
+ # work back to the preceding comma
13
13
  first_pos = pair_node.location.expression.begin_pos
14
14
  end_pos = pair_node.location.expression.end_pos
15
15
  node_source = send_node.source_range.source
@@ -12,7 +12,7 @@ module GraphQL
12
12
  @possible_types = {}
13
13
  @types = {}
14
14
  @union_memberships = {}
15
- @references = Hash.new { |h, k| h[k] = [] }
15
+ @references = Hash.new { |h, k| h[k] = Set.new }
16
16
  @arguments_with_default_values = []
17
17
  add_type_and_traverse(new_types)
18
18
  end
@@ -20,7 +20,7 @@ module GraphQL
20
20
  private
21
21
 
22
22
  def references_to(thing, from:)
23
- @references[thing] << from
23
+ @references[thing].add(from)
24
24
  end
25
25
 
26
26
  def get_type(name)
@@ -95,7 +95,7 @@ module GraphQL
95
95
  # It's a union with possible_types
96
96
  # Replace the item by class name
97
97
  owner.assign_type_membership_object_type(type)
98
- @possible_types[owner.graphql_name] = owner.possible_types
98
+ @possible_types[owner] = owner.possible_types
99
99
  elsif type.kind.interface? && (owner.kind.object? || owner.kind.interface?)
100
100
  new_interfaces = []
101
101
  owner.interfaces.each do |int_t|
@@ -110,7 +110,7 @@ module GraphQL
110
110
  end
111
111
  owner.implements(*new_interfaces)
112
112
  new_interfaces.each do |int|
113
- pt = @possible_types[int.graphql_name] ||= []
113
+ pt = @possible_types[int] ||= []
114
114
  if !pt.include?(owner) && owner.is_a?(Class)
115
115
  pt << owner
116
116
  end
@@ -126,6 +126,7 @@ module GraphQL
126
126
  @types[type.graphql_name] = type
127
127
  when GraphQL::Schema::Field, GraphQL::Schema::Argument
128
128
  orig_type = owner.type
129
+ unwrapped_t = type
129
130
  # Apply list/non-null wrapper as needed
130
131
  if orig_type.respond_to?(:of_type)
131
132
  transforms = []
@@ -142,6 +143,7 @@ module GraphQL
142
143
  transforms.reverse_each { |t| type = type.public_send(t) }
143
144
  end
144
145
  owner.type = type
146
+ references_to(unwrapped_t, from: owner)
145
147
  else
146
148
  raise "Unexpected update: #{owner.inspect} #{type.inspect}"
147
149
  end
@@ -164,7 +166,9 @@ module GraphQL
164
166
  @directives << type
165
167
  type.all_argument_definitions.each do |arg|
166
168
  arg_type = arg.type.unwrap
167
- references_to(arg_type, from: arg)
169
+ if !arg_type.is_a?(GraphQL::Schema::LateBoundType)
170
+ references_to(arg_type, from: arg)
171
+ end
168
172
  path.push(arg.graphql_name)
169
173
  add_type(arg_type, owner: arg, late_types: late_types, path: path)
170
174
  path.pop
@@ -187,14 +191,18 @@ module GraphQL
187
191
  type.all_field_definitions.each do |field|
188
192
  name = field.graphql_name
189
193
  field_type = field.type.unwrap
190
- references_to(field_type, from: field)
194
+ if !field_type.is_a?(GraphQL::Schema::LateBoundType)
195
+ references_to(field_type, from: field)
196
+ end
191
197
  path.push(name)
192
198
  add_type(field_type, owner: field, late_types: late_types, path: path)
193
199
  add_directives_from(field)
194
200
  field.all_argument_definitions.each do |arg|
195
201
  add_directives_from(arg)
196
202
  arg_type = arg.type.unwrap
197
- references_to(arg_type, from: arg)
203
+ if !arg_type.is_a?(GraphQL::Schema::LateBoundType)
204
+ references_to(arg_type, from: arg)
205
+ end
198
206
  path.push(arg.graphql_name)
199
207
  add_type(arg_type, owner: arg, late_types: late_types, path: path)
200
208
  path.pop
@@ -209,7 +217,9 @@ module GraphQL
209
217
  type.all_argument_definitions.each do |arg|
210
218
  add_directives_from(arg)
211
219
  arg_type = arg.type.unwrap
212
- references_to(arg_type, from: arg)
220
+ if !arg_type.is_a?(GraphQL::Schema::LateBoundType)
221
+ references_to(arg_type, from: arg)
222
+ end
213
223
  path.push(arg.graphql_name)
214
224
  add_type(arg_type, owner: arg, late_types: late_types, path: path)
215
225
  path.pop
@@ -219,7 +229,7 @@ module GraphQL
219
229
  end
220
230
  end
221
231
  if type.kind.union?
222
- @possible_types[type.graphql_name] = type.all_possible_types
232
+ @possible_types[type] = type.all_possible_types
223
233
  path.push("possible_types")
224
234
  type.all_possible_types.each do |t|
225
235
  add_type(t, owner: type, late_types: late_types, path: path)
@@ -234,7 +244,7 @@ module GraphQL
234
244
  path.pop
235
245
  end
236
246
  if type.kind.object?
237
- possible_types_for_this_name = @possible_types[type.graphql_name] ||= []
247
+ possible_types_for_this_name = @possible_types[type] ||= []
238
248
  possible_types_for_this_name << type
239
249
  end
240
250
 
@@ -246,7 +256,7 @@ module GraphQL
246
256
  interface_type = interface_type_membership.abstract_type
247
257
  # We can get these now; we'll have to get late-bound types later
248
258
  if interface_type.is_a?(Module) && type.is_a?(Class)
249
- implementers = @possible_types[interface_type.graphql_name] ||= []
259
+ implementers = @possible_types[interface_type] ||= []
250
260
  implementers << type
251
261
  end
252
262
  when String, Schema::LateBoundType
@@ -312,10 +312,15 @@ module GraphQL
312
312
  context.query.after_lazy(custom_loaded_value) do |custom_value|
313
313
  if loads
314
314
  if type.list?
315
- loaded_values = custom_value.each_with_index.map { |custom_val, idx|
316
- id = coerced_value[idx]
317
- load_method_owner.authorize_application_object(self, id, context, custom_val)
318
- }
315
+ loaded_values = []
316
+ context.dataloader.run_isolated do
317
+ custom_value.each_with_index.map { |custom_val, idx|
318
+ id = coerced_value[idx]
319
+ context.dataloader.append_job do
320
+ loaded_values[idx] = load_method_owner.authorize_application_object(self, id, context, custom_val)
321
+ end
322
+ }
323
+ end
319
324
  context.schema.after_any_lazies(loaded_values, &:itself)
320
325
  else
321
326
  load_method_owner.authorize_application_object(self, coerced_value, context, custom_loaded_value)
@@ -326,7 +331,16 @@ module GraphQL
326
331
  end
327
332
  elsif loads
328
333
  if type.list?
329
- loaded_values = coerced_value.map { |val| load_method_owner.load_and_authorize_application_object(self, val, context) }
334
+ loaded_values = []
335
+ # We want to run these list items all together,
336
+ # but we also need to wait for the result so we can return it :S
337
+ context.dataloader.run_isolated do
338
+ coerced_value.each_with_index { |val, idx|
339
+ context.dataloader.append_job do
340
+ loaded_values[idx] = load_method_owner.load_and_authorize_application_object(self, val, context)
341
+ end
342
+ }
343
+ end
330
344
  context.schema.after_any_lazies(loaded_values, &:itself)
331
345
  else
332
346
  load_method_owner.load_and_authorize_application_object(self, coerced_value, context)
@@ -188,6 +188,8 @@ module GraphQL
188
188
  assert_has_location(SCALAR)
189
189
  elsif @owner < GraphQL::Schema
190
190
  assert_has_location(SCHEMA)
191
+ elsif @owner < GraphQL::Schema::Resolver
192
+ assert_has_location(FIELD_DEFINITION)
191
193
  else
192
194
  raise "Unexpected directive owner class: #{@owner}"
193
195
  end