graphql 1.11.10 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +5 -5
  3. data/lib/generators/graphql/relay_generator.rb +63 -0
  4. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  5. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  6. data/lib/generators/graphql/templates/node_type.erb +9 -0
  7. data/lib/generators/graphql/templates/object.erb +1 -1
  8. data/lib/generators/graphql/templates/query_type.erb +1 -3
  9. data/lib/generators/graphql/templates/schema.erb +8 -35
  10. data/lib/graphql/analysis/analyze_query.rb +7 -0
  11. data/lib/graphql/analysis/ast/visitor.rb +9 -1
  12. data/lib/graphql/analysis/ast.rb +11 -2
  13. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  14. data/lib/graphql/backtrace/table.rb +22 -2
  15. data/lib/graphql/backtrace/tracer.rb +40 -9
  16. data/lib/graphql/backtrace.rb +28 -19
  17. data/lib/graphql/backwards_compatibility.rb +1 -0
  18. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  19. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  20. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  21. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  22. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  23. data/lib/graphql/dataloader/request.rb +24 -0
  24. data/lib/graphql/dataloader/request_all.rb +22 -0
  25. data/lib/graphql/dataloader/source.rb +93 -0
  26. data/lib/graphql/dataloader.rb +197 -0
  27. data/lib/graphql/define/assign_global_id_field.rb +1 -1
  28. data/lib/graphql/define/instance_definable.rb +32 -2
  29. data/lib/graphql/define/type_definer.rb +5 -5
  30. data/lib/graphql/deprecated_dsl.rb +5 -0
  31. data/lib/graphql/enum_type.rb +2 -0
  32. data/lib/graphql/execution/errors.rb +4 -0
  33. data/lib/graphql/execution/execute.rb +7 -0
  34. data/lib/graphql/execution/interpreter/arguments.rb +51 -14
  35. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  36. data/lib/graphql/execution/interpreter/runtime.rb +210 -124
  37. data/lib/graphql/execution/interpreter.rb +10 -6
  38. data/lib/graphql/execution/multiplex.rb +20 -6
  39. data/lib/graphql/function.rb +4 -0
  40. data/lib/graphql/input_object_type.rb +2 -0
  41. data/lib/graphql/interface_type.rb +3 -1
  42. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  43. data/lib/graphql/object_type.rb +2 -0
  44. data/lib/graphql/pagination/connection.rb +5 -1
  45. data/lib/graphql/pagination/connections.rb +6 -16
  46. data/lib/graphql/query/context.rb +4 -0
  47. data/lib/graphql/query/serial_execution.rb +1 -0
  48. data/lib/graphql/query/validation_pipeline.rb +1 -1
  49. data/lib/graphql/query.rb +2 -0
  50. data/lib/graphql/relay/base_connection.rb +7 -0
  51. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  52. data/lib/graphql/relay/connection_type.rb +1 -1
  53. data/lib/graphql/relay/mutation.rb +1 -0
  54. data/lib/graphql/relay/node.rb +3 -0
  55. data/lib/graphql/relay/type_extensions.rb +2 -0
  56. data/lib/graphql/scalar_type.rb +2 -0
  57. data/lib/graphql/schema/argument.rb +25 -7
  58. data/lib/graphql/schema/build_from_definition.rb +139 -51
  59. data/lib/graphql/schema/directive/flagged.rb +57 -0
  60. data/lib/graphql/schema/directive.rb +76 -0
  61. data/lib/graphql/schema/enum.rb +3 -0
  62. data/lib/graphql/schema/enum_value.rb +12 -6
  63. data/lib/graphql/schema/field/connection_extension.rb +3 -2
  64. data/lib/graphql/schema/field.rb +28 -9
  65. data/lib/graphql/schema/input_object.rb +33 -22
  66. data/lib/graphql/schema/interface.rb +1 -0
  67. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  68. data/lib/graphql/schema/member/build_type.rb +3 -3
  69. data/lib/graphql/schema/member/has_arguments.rb +24 -6
  70. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  71. data/lib/graphql/schema/member/has_directives.rb +98 -0
  72. data/lib/graphql/schema/member/has_validators.rb +31 -0
  73. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  74. data/lib/graphql/schema/member.rb +4 -0
  75. data/lib/graphql/schema/object.rb +11 -0
  76. data/lib/graphql/schema/printer.rb +5 -4
  77. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  78. data/lib/graphql/schema/resolver.rb +7 -0
  79. data/lib/graphql/schema/subscription.rb +19 -1
  80. data/lib/graphql/schema/timeout_middleware.rb +2 -0
  81. data/lib/graphql/schema/validation.rb +2 -0
  82. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  83. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  84. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  85. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  86. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  87. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  88. data/lib/graphql/schema/validator.rb +163 -0
  89. data/lib/graphql/schema.rb +72 -49
  90. data/lib/graphql/static_validation/base_visitor.rb +0 -3
  91. data/lib/graphql/static_validation/rules/fields_will_merge.rb +4 -4
  92. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  93. data/lib/graphql/static_validation/validation_context.rb +1 -6
  94. data/lib/graphql/static_validation/validator.rb +12 -14
  95. data/lib/graphql/subscriptions.rb +17 -20
  96. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  97. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  98. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  99. data/lib/graphql/tracing.rb +2 -2
  100. data/lib/graphql/types/relay/base_connection.rb +2 -92
  101. data/lib/graphql/types/relay/base_edge.rb +2 -35
  102. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  103. data/lib/graphql/types/relay/default_relay.rb +27 -0
  104. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  105. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  106. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  107. data/lib/graphql/types/relay/node.rb +2 -4
  108. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  109. data/lib/graphql/types/relay/node_field.rb +1 -19
  110. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  111. data/lib/graphql/types/relay/page_info.rb +2 -14
  112. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  113. data/lib/graphql/types/relay.rb +11 -3
  114. data/lib/graphql/union_type.rb +2 -0
  115. data/lib/graphql/upgrader/member.rb +1 -0
  116. data/lib/graphql/upgrader/schema.rb +1 -0
  117. data/lib/graphql/version.rb +1 -1
  118. data/lib/graphql.rb +38 -4
  119. metadata +31 -6
  120. data/lib/graphql/types/relay/base_field.rb +0 -22
  121. data/lib/graphql/types/relay/base_interface.rb +0 -29
  122. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -19,8 +19,10 @@ module GraphQL
19
19
 
20
20
  def initialize(query:, response:)
21
21
  @query = query
22
+ @dataloader = query.multiplex.dataloader
22
23
  @schema = query.schema
23
24
  @context = query.context
25
+ @multiplex_context = query.multiplex.context
24
26
  @interpreter_context = @context.namespace(:interpreter)
25
27
  @response = response
26
28
  @dead_paths = {}
@@ -54,7 +56,17 @@ module GraphQL
54
56
  # Root .authorized? returned false.
55
57
  write_in_response(path, nil)
56
58
  else
57
- evaluate_selections(path, context.scoped_context, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
59
+ # Prepare this runtime state to be encapsulated in a Fiber
60
+ @progress_path = path
61
+ @progress_scoped_context = context.scoped_context
62
+ @progress_object = object_proxy
63
+ @progress_object_type = root_type
64
+ @progress_index = nil
65
+ @progress_is_eager_selection = root_op_type == "mutation"
66
+ @progress_selections = gather_selections(object_proxy, root_type, root_operation.selections)
67
+
68
+ # Make the first fiber which will begin execution
69
+ enqueue_selections_fiber
58
70
  end
59
71
  delete_interpreter_context(:current_path)
60
72
  delete_interpreter_context(:current_field)
@@ -63,7 +75,33 @@ module GraphQL
63
75
  nil
64
76
  end
65
77
 
66
- def gather_selections(owner_object, owner_type, selections, selections_by_name)
78
+ # Use `@dataloader` to enqueue a fiber that will pick up from the current point.
79
+ # @return [void]
80
+ def enqueue_selections_fiber
81
+ # Read these into local variables so that later assignments don't affect the block below.
82
+ path = @progress_path
83
+ scoped_context = @progress_scoped_context
84
+ owner_object = @progress_object
85
+ owner_type = @progress_object_type
86
+ idx = @progress_index
87
+ is_eager_selection = @progress_is_eager_selection
88
+ gathered_selections = @progress_selections
89
+
90
+ @dataloader.enqueue {
91
+ evaluate_selections(
92
+ path,
93
+ scoped_context,
94
+ owner_object,
95
+ owner_type,
96
+ is_eager_selection: is_eager_selection,
97
+ after: idx,
98
+ gathered_selections: gathered_selections,
99
+ )
100
+ }
101
+ nil
102
+ end
103
+
104
+ def gather_selections(owner_object, owner_type, selections, selections_by_name = {})
67
105
  selections.each do |node|
68
106
  # Skip gathering this if the directive says so
69
107
  if !directives_include?(node, owner_object, owner_type)
@@ -115,145 +153,184 @@ module GraphQL
115
153
  raise "Invariant: unexpected selection class: #{node.class}"
116
154
  end
117
155
  end
156
+ selections_by_name
118
157
  end
119
158
 
120
159
  NO_ARGS = {}.freeze
121
160
 
122
- def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil)
161
+ # @return [void]
162
+ def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection:, gathered_selections:, after:)
123
163
  set_all_interpreter_context(owner_object, nil, nil, path)
124
- selections_by_name = {}
125
- gather_selections(owner_object, owner_type, selections, selections_by_name)
126
- selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
127
- # As a performance optimization, the hash key will be a `Node` if
128
- # there's only one selection of the field. But if there are multiple
129
- # selections of the field, it will be an Array of nodes
130
- if field_ast_nodes_or_ast_node.is_a?(Array)
131
- field_ast_nodes = field_ast_nodes_or_ast_node
132
- ast_node = field_ast_nodes.first
133
- else
134
- field_ast_nodes = nil
135
- ast_node = field_ast_nodes_or_ast_node
164
+
165
+ @progress_path = path
166
+ @progress_scoped_context = scoped_context
167
+ @progress_object = owner_object
168
+ @progress_object_type = owner_type
169
+ @progress_index = nil
170
+ @progress_is_eager_selection = is_eager_selection
171
+ @progress_selections = gathered_selections
172
+
173
+ # Track `idx` manually to avoid an allocation on this hot path
174
+ idx = 0
175
+ gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
176
+ prev_idx = idx
177
+ idx += 1
178
+ # TODO: this is how a `progress` resumes where this left off.
179
+ # Is there a better way to seek in the hash?
180
+ # I think we could also use the array of keys; it supports seeking just fine.
181
+ if after && prev_idx <= after
182
+ next
136
183
  end
137
- field_name = ast_node.name
138
- field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
139
- is_introspection = false
140
- if field_defn.nil?
141
- field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
142
- is_introspection = true
143
- entry_point_field
144
- elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
145
- is_introspection = true
146
- dynamic_field
147
- else
148
- raise "Invariant: no field for #{owner_type}.#{field_name}"
149
- end
184
+ @progress_index = prev_idx
185
+ # This is how the current runtime gives itself to `dataloader`
186
+ # so that the dataloader can enqueue another fiber to resume if needed.
187
+ @dataloader.current_runtime = self
188
+ evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection)
189
+ # The dataloader knows if ^^ that selection halted and later selections were executed in another fiber.
190
+ # If that's the case, then don't continue execution here.
191
+ if @dataloader.yielded?
192
+ break
150
193
  end
151
- return_type = field_defn.type
194
+ end
195
+ nil
196
+ end
152
197
 
153
- next_path = path.dup
154
- next_path << result_name
155
- next_path.freeze
198
+ # @return [void]
199
+ def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field)
200
+ # As a performance optimization, the hash key will be a `Node` if
201
+ # there's only one selection of the field. But if there are multiple
202
+ # selections of the field, it will be an Array of nodes
203
+ if field_ast_nodes_or_ast_node.is_a?(Array)
204
+ field_ast_nodes = field_ast_nodes_or_ast_node
205
+ ast_node = field_ast_nodes.first
206
+ else
207
+ field_ast_nodes = nil
208
+ ast_node = field_ast_nodes_or_ast_node
209
+ end
210
+ field_name = ast_node.name
211
+ field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
212
+ is_introspection = false
213
+ if field_defn.nil?
214
+ field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
215
+ is_introspection = true
216
+ entry_point_field
217
+ elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
218
+ is_introspection = true
219
+ dynamic_field
220
+ else
221
+ raise "Invariant: no field for #{owner_type}.#{field_name}"
222
+ end
223
+ end
224
+ return_type = field_defn.type
156
225
 
157
- # This seems janky, but we need to know
158
- # the field's return type at this path in order
159
- # to propagate `null`
160
- set_type_at_path(next_path, return_type)
161
- # Set this before calling `run_with_directives`, so that the directive can have the latest path
162
- set_all_interpreter_context(nil, field_defn, nil, next_path)
226
+ next_path = path.dup
227
+ next_path << result_name
228
+ next_path.freeze
163
229
 
164
- context.scoped_context = scoped_context
165
- object = owner_object
230
+ # This seems janky, but we need to know
231
+ # the field's return type at this path in order
232
+ # to propagate `null`
233
+ set_type_at_path(next_path, return_type)
234
+ # Set this before calling `run_with_directives`, so that the directive can have the latest path
235
+ set_all_interpreter_context(nil, field_defn, nil, next_path)
166
236
 
167
- if is_introspection
168
- object = authorized_new(field_defn.owner, object, context, next_path)
169
- end
237
+ context.scoped_context = scoped_context
238
+ object = owner_object
170
239
 
171
- begin
172
- kwarg_arguments = arguments(object, field_defn, ast_node)
173
- rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => e
174
- continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node)
240
+ if is_introspection
241
+ object = authorized_new(field_defn.owner, object, context, next_path)
242
+ end
243
+
244
+ begin
245
+ kwarg_arguments = arguments(object, field_defn, ast_node)
246
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => e
247
+ continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node)
248
+ return
249
+ end
250
+
251
+ after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments|
252
+ if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
253
+ continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
175
254
  next
176
255
  end
177
256
 
178
- after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments|
179
- case resolved_arguments
180
- when GraphQL::ExecutionError, GraphQL::UnauthorizedError
181
- continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
182
- next
183
- end
184
-
185
- if resolved_arguments.empty? && field_defn.extras.empty?
186
- # We can avoid allocating the `{ Symbol => Object }` hash in this case
187
- kwarg_arguments = NO_ARGS
188
- else
189
- kwarg_arguments = resolved_arguments.keyword_arguments
190
-
191
- field_defn.extras.each do |extra|
192
- case extra
193
- when :ast_node
194
- kwarg_arguments[:ast_node] = ast_node
195
- when :execution_errors
196
- kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
197
- when :path
198
- kwarg_arguments[:path] = next_path
199
- when :lookahead
200
- if !field_ast_nodes
201
- field_ast_nodes = [ast_node]
202
- end
203
- kwarg_arguments[:lookahead] = Execution::Lookahead.new(
204
- query: query,
205
- ast_nodes: field_ast_nodes,
206
- field: field_defn,
207
- )
208
- when :argument_details
209
- kwarg_arguments[:argument_details] = resolved_arguments
210
- else
211
- kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
257
+ kwarg_arguments = if resolved_arguments.empty? && field_defn.extras.empty?
258
+ # We can avoid allocating the `{ Symbol => Object }` hash in this case
259
+ NO_ARGS
260
+ else
261
+ # Bundle up the extras, then make a new arguments instance
262
+ # that includes the extras, too.
263
+ extra_args = {}
264
+ field_defn.extras.each do |extra|
265
+ case extra
266
+ when :ast_node
267
+ extra_args[:ast_node] = ast_node
268
+ when :execution_errors
269
+ extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
270
+ when :path
271
+ extra_args[:path] = next_path
272
+ when :lookahead
273
+ if !field_ast_nodes
274
+ field_ast_nodes = [ast_node]
212
275
  end
276
+
277
+ extra_args[:lookahead] = Execution::Lookahead.new(
278
+ query: query,
279
+ ast_nodes: field_ast_nodes,
280
+ field: field_defn,
281
+ )
282
+ when :argument_details
283
+ # Use this flag to tell Interpreter::Arguments to add itself
284
+ # to the keyword args hash _before_ freezing everything.
285
+ extra_args[:argument_details] = :__arguments_add_self
286
+ else
287
+ extra_args[extra] = field_defn.fetch_extra(extra, context)
213
288
  end
214
289
  end
290
+ resolved_arguments = resolved_arguments.merge_extras(extra_args)
291
+ resolved_arguments.keyword_arguments
292
+ end
215
293
 
216
- set_all_interpreter_context(nil, nil, kwarg_arguments, nil)
294
+ set_all_interpreter_context(nil, nil, kwarg_arguments, nil)
217
295
 
218
- # Optimize for the case that field is selected only once
219
- if field_ast_nodes.nil? || field_ast_nodes.size == 1
220
- next_selections = ast_node.selections
221
- else
222
- next_selections = []
223
- field_ast_nodes.each { |f| next_selections.concat(f.selections) }
224
- end
296
+ # Optimize for the case that field is selected only once
297
+ if field_ast_nodes.nil? || field_ast_nodes.size == 1
298
+ next_selections = ast_node.selections
299
+ else
300
+ next_selections = []
301
+ field_ast_nodes.each { |f| next_selections.concat(f.selections) }
302
+ end
225
303
 
226
- field_result = resolve_with_directives(object, ast_node) do
227
- # Actually call the field resolver and capture the result
228
- app_result = begin
229
- query.with_error_handling do
230
- query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
231
- field_defn.resolve(object, kwarg_arguments, context)
232
- end
304
+ field_result = resolve_with_directives(object, ast_node) do
305
+ # Actually call the field resolver and capture the result
306
+ app_result = begin
307
+ query.with_error_handling do
308
+ query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments}) do
309
+ field_defn.resolve(object, kwarg_arguments, context)
233
310
  end
234
- rescue GraphQL::ExecutionError => err
235
- err
236
311
  end
237
- after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
238
- continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
239
- if RawValue === continue_value
240
- # Write raw value directly to the response without resolving nested objects
241
- write_in_response(next_path, continue_value.resolve)
242
- elsif HALT != continue_value
243
- continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
244
- end
312
+ rescue GraphQL::ExecutionError => err
313
+ err
314
+ end
315
+ after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
316
+ continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
317
+ if RawValue === continue_value
318
+ # Write raw value directly to the response without resolving nested objects
319
+ write_in_response(next_path, continue_value.resolve)
320
+ elsif HALT != continue_value
321
+ continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
245
322
  end
246
323
  end
324
+ end
247
325
 
248
- # If this field is a root mutation field, immediately resolve
249
- # all of its child fields before moving on to the next root mutation field.
250
- # (Subselections of this mutation will still be resolved level-by-level.)
251
- if root_operation_type == "mutation"
252
- Interpreter::Resolve.resolve_all([field_result])
253
- else
254
- field_result
255
- end
326
+ # If this field is a root mutation field, immediately resolve
327
+ # all of its child fields before moving on to the next root mutation field.
328
+ # (Subselections of this mutation will still be resolved level-by-level.)
329
+ if is_eager_field
330
+ Interpreter::Resolve.resolve_all([field_result])
256
331
  end
332
+
333
+ nil
257
334
  end
258
335
  end
259
336
 
@@ -314,7 +391,7 @@ module GraphQL
314
391
  resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
315
392
  resolved_value ||= value
316
393
 
317
- after_lazy(resolved_type_or_lazy, owner: current_type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
394
+ after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
318
395
  possible_types = query.possible_types(current_type)
319
396
 
320
397
  if !possible_types.include?(resolved_type)
@@ -334,12 +411,13 @@ module GraphQL
334
411
  rescue GraphQL::ExecutionError => err
335
412
  err
336
413
  end
337
- after_lazy(object_proxy, owner: current_type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object|
414
+ after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object|
338
415
  continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node)
339
416
  if HALT != continue_value
340
417
  response_hash = {}
341
418
  write_in_response(path, response_hash)
342
- evaluate_selections(path, context.scoped_context, continue_value, current_type, next_selections)
419
+ gathered_selections = gather_selections(continue_value, current_type, next_selections)
420
+ evaluate_selections(path, context.scoped_context, continue_value, current_type, is_eager_selection: false, gathered_selections: gathered_selections, after: nil)
343
421
  response_hash
344
422
  end
345
423
  end
@@ -357,7 +435,7 @@ module GraphQL
357
435
  idx += 1
358
436
  set_type_at_path(next_path, inner_type)
359
437
  # This will update `response_list` with the lazy
360
- after_lazy(inner_value, owner: inner_type, path: next_path, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
438
+ after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
361
439
  continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node)
362
440
  if HALT != continue_value
363
441
  continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
@@ -440,7 +518,7 @@ module GraphQL
440
518
  # @param eager [Boolean] Set to `true` for mutation root fields only
441
519
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
442
520
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
443
- def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false, trace: true, &block)
521
+ def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, eager: false, trace: true, &block)
444
522
  set_all_interpreter_context(owner_object, field, arguments, path)
445
523
  if schema.lazy?(lazy_obj)
446
524
  lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
@@ -451,7 +529,7 @@ module GraphQL
451
529
  inner_obj = begin
452
530
  query.with_error_handling do
453
531
  if trace
454
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
532
+ query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
455
533
  schema.sync_lazy(lazy_obj)
456
534
  end
457
535
  else
@@ -461,7 +539,7 @@ module GraphQL
461
539
  rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
462
540
  err
463
541
  end
464
- after_lazy(inner_obj, owner: owner, field: field, path: path, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager, trace: trace, &block)
542
+ after_lazy(inner_obj, owner: owner, field: field, path: path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager, trace: trace, &block)
465
543
  end
466
544
 
467
545
  if eager
@@ -476,9 +554,7 @@ module GraphQL
476
554
  end
477
555
 
478
556
  def arguments(graphql_object, arg_owner, ast_node)
479
- # Don't cache arguments if field extras or extensions are requested since they can mutate the argument data structure
480
- if arg_owner.arguments_statically_coercible? &&
481
- (!arg_owner.is_a?(GraphQL::Schema::Field) || (arg_owner.extras.empty? && arg_owner.extensions.empty?))
557
+ if arg_owner.arguments_statically_coercible?
482
558
  query.arguments_for(ast_node, arg_owner)
483
559
  else
484
560
  # The arguments must be prepared in the context of the given object
@@ -519,6 +595,16 @@ module GraphQL
519
595
  end
520
596
  end
521
597
 
598
+ def value_at(path)
599
+ i = 0
600
+ value = @response.final_value
601
+ while value && (part = path[i])
602
+ value = value[part]
603
+ i += 1
604
+ end
605
+ value
606
+ end
607
+
522
608
  # To propagate nulls, we have to know what the field type was
523
609
  # at previous parts of the response.
524
610
  # This hash matches the response
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require "fiber"
2
3
  require "graphql/execution/interpreter/argument_value"
3
4
  require "graphql/execution/interpreter/arguments"
4
5
  require "graphql/execution/interpreter/arguments_cache"
@@ -22,12 +23,15 @@ module GraphQL
22
23
  end
23
24
 
24
25
  def self.use(schema_class)
25
- schema_class.interpreter = true
26
- schema_class.query_execution_strategy(GraphQL::Execution::Interpreter)
27
- schema_class.mutation_execution_strategy(GraphQL::Execution::Interpreter)
28
- schema_class.subscription_execution_strategy(GraphQL::Execution::Interpreter)
29
- schema_class.add_subscription_extension_if_necessary
30
- GraphQL::Schema::Object.include(HandlesRawValue)
26
+ if schema_class.interpreter?
27
+ definition_line = caller(2, 1).first
28
+ warn("GraphQL::Execution::Interpreter is now the default; remove `use GraphQL::Execution::Interpreter` from the schema definition (#{definition_line})")
29
+ else
30
+ schema_class.query_execution_strategy(self)
31
+ schema_class.mutation_execution_strategy(self)
32
+ schema_class.subscription_execution_strategy(self)
33
+ schema_class.add_subscription_extension_if_necessary
34
+ end
31
35
  end
32
36
 
33
37
  def self.begin_multiplex(multiplex)
@@ -29,11 +29,13 @@ module GraphQL
29
29
 
30
30
  include Tracing::Traceable
31
31
 
32
- attr_reader :context, :queries, :schema, :max_complexity
32
+ attr_reader :context, :queries, :schema, :max_complexity, :dataloader
33
33
  def initialize(schema:, queries:, context:, max_complexity:)
34
34
  @schema = schema
35
35
  @queries = queries
36
+ @queries.each { |q| q.multiplex = self }
36
37
  @context = context
38
+ @context[:dataloader] = @dataloader = @schema.dataloader_class.new(context)
37
39
  @tracers = schema.tracers + (context[:tracers] || [])
38
40
  # Support `context: {backtrace: true}`
39
41
  if context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer)
@@ -79,20 +81,30 @@ module GraphQL
79
81
  multiplex.schema.query_execution_strategy.begin_multiplex(multiplex)
80
82
  queries = multiplex.queries
81
83
  # Do as much eager evaluation of the query as possible
82
- results = queries.map do |query|
83
- begin_query(query, multiplex)
84
+ results = []
85
+ queries.each_with_index do |query, idx|
86
+ multiplex.dataloader.enqueue {
87
+ results[idx] = begin_query(query, multiplex)
88
+ }
84
89
  end
85
90
 
91
+ multiplex.dataloader.run
92
+
86
93
  # Then, work through lazy results in a breadth-first way
87
- multiplex.schema.query_execution_strategy.finish_multiplex(results, multiplex)
94
+ multiplex.dataloader.enqueue {
95
+ multiplex.schema.query_execution_strategy.finish_multiplex(results, multiplex)
96
+ }
97
+ multiplex.dataloader.run
88
98
 
89
99
  # Then, find all errors and assign the result to the query object
90
- results.each_with_index.map do |data_result, idx|
100
+ results.each_with_index do |data_result, idx|
91
101
  query = queries[idx]
92
102
  finish_query(data_result, query, multiplex)
93
103
  # Get the Query::Result, not the Hash
94
- query.result
104
+ results[idx] = query.result
95
105
  end
106
+
107
+ results
96
108
  rescue Exception
97
109
  # TODO rescue at a higher level so it will catch errors in analysis, too
98
110
  # Assign values here so that the query's `@executed` becomes true
@@ -144,6 +156,8 @@ module GraphQL
144
156
 
145
157
  # use the old `query_execution_strategy` etc to run this query
146
158
  def run_one_legacy(schema, query)
159
+ warn "Multiplex.run_one_legacy will be removed from GraphQL-Ruby 2.0, upgrade to the Interpreter to avoid this deprecated codepath: https://graphql-ruby.org/queries/interpreter.html"
160
+
147
161
  query.result_values = if !query.valid?
148
162
  all_errors = query.validation_errors + query.analysis_errors + query.context.errors
149
163
  if all_errors.any?
@@ -2,6 +2,10 @@
2
2
  module GraphQL
3
3
  # @api deprecated
4
4
  class Function
5
+ def self.inherited(subclass)
6
+ warn "GraphQL::Function (used for #{subclass}) will be removed from GraphQL-Ruby 2.0, please upgrade to resolvers: https://graphql-ruby.org/fields/resolvers.html"
7
+ end
8
+
5
9
  # @return [Hash<String => GraphQL::Argument>] Arguments, keyed by name
6
10
  def arguments
7
11
  self.class.arguments
@@ -2,6 +2,8 @@
2
2
  module GraphQL
3
3
  # @api deprecated
4
4
  class InputObjectType < GraphQL::BaseType
5
+ extend Define::InstanceDefinable::DeprecatedDefine
6
+
5
7
  accepts_definitions(
6
8
  :arguments, :mutation,
7
9
  input_field: GraphQL::Define::AssignArgument,
@@ -2,10 +2,12 @@
2
2
  module GraphQL
3
3
  # @api deprecated
4
4
  class InterfaceType < GraphQL::BaseType
5
+ extend Define::InstanceDefinable::DeprecatedDefine
6
+
5
7
  accepts_definitions :fields, :orphan_types, :resolve_type, field: GraphQL::Define::AssignObjectField
6
8
 
7
9
  attr_accessor :fields, :orphan_types, :resolve_type_proc
8
- attr_writer :type_membership_class
10
+ attr_writer :type_membership_class
9
11
  ensure_defined :fields, :orphan_types, :resolve_type_proc, :resolve_type
10
12
 
11
13
  def initialize