graphql 1.11.11 → 1.12.0

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.

Files changed (119) 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/language/nodes.rb +0 -5
  44. data/lib/graphql/language/visitor.rb +0 -1
  45. data/lib/graphql/object_type.rb +2 -0
  46. data/lib/graphql/pagination/connection.rb +5 -1
  47. data/lib/graphql/pagination/connections.rb +6 -16
  48. data/lib/graphql/query/context.rb +4 -0
  49. data/lib/graphql/query/serial_execution.rb +1 -0
  50. data/lib/graphql/query.rb +2 -0
  51. data/lib/graphql/relay/base_connection.rb +7 -0
  52. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  53. data/lib/graphql/relay/connection_type.rb +1 -1
  54. data/lib/graphql/relay/mutation.rb +1 -0
  55. data/lib/graphql/relay/node.rb +3 -0
  56. data/lib/graphql/relay/type_extensions.rb +2 -0
  57. data/lib/graphql/scalar_type.rb +2 -0
  58. data/lib/graphql/schema/argument.rb +30 -10
  59. data/lib/graphql/schema/build_from_definition.rb +145 -58
  60. data/lib/graphql/schema/directive/flagged.rb +57 -0
  61. data/lib/graphql/schema/directive.rb +76 -0
  62. data/lib/graphql/schema/enum.rb +3 -0
  63. data/lib/graphql/schema/enum_value.rb +13 -7
  64. data/lib/graphql/schema/field/connection_extension.rb +3 -2
  65. data/lib/graphql/schema/field.rb +28 -10
  66. data/lib/graphql/schema/input_object.rb +36 -28
  67. data/lib/graphql/schema/interface.rb +1 -0
  68. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  69. data/lib/graphql/schema/member/build_type.rb +3 -3
  70. data/lib/graphql/schema/member/has_arguments.rb +24 -6
  71. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  72. data/lib/graphql/schema/member/has_directives.rb +98 -0
  73. data/lib/graphql/schema/member/has_validators.rb +31 -0
  74. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  75. data/lib/graphql/schema/member.rb +4 -0
  76. data/lib/graphql/schema/object.rb +11 -0
  77. data/lib/graphql/schema/printer.rb +5 -4
  78. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  79. data/lib/graphql/schema/resolver.rb +7 -0
  80. data/lib/graphql/schema/subscription.rb +19 -1
  81. data/lib/graphql/schema/timeout_middleware.rb +2 -0
  82. data/lib/graphql/schema/validation.rb +2 -0
  83. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  84. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  85. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  86. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  87. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  88. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  89. data/lib/graphql/schema/validator.rb +163 -0
  90. data/lib/graphql/schema.rb +61 -23
  91. data/lib/graphql/static_validation/validator.rb +2 -0
  92. data/lib/graphql/subscriptions.rb +17 -20
  93. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  94. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  95. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  96. data/lib/graphql/tracing.rb +2 -2
  97. data/lib/graphql/types/relay/base_connection.rb +2 -92
  98. data/lib/graphql/types/relay/base_edge.rb +2 -35
  99. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  100. data/lib/graphql/types/relay/default_relay.rb +27 -0
  101. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  102. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  103. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  104. data/lib/graphql/types/relay/node.rb +2 -4
  105. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  106. data/lib/graphql/types/relay/node_field.rb +1 -19
  107. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  108. data/lib/graphql/types/relay/page_info.rb +2 -14
  109. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  110. data/lib/graphql/types/relay.rb +11 -3
  111. data/lib/graphql/union_type.rb +2 -0
  112. data/lib/graphql/upgrader/member.rb +1 -0
  113. data/lib/graphql/upgrader/schema.rb +1 -0
  114. data/lib/graphql/version.rb +1 -1
  115. data/lib/graphql.rb +38 -4
  116. metadata +34 -9
  117. data/lib/graphql/types/relay/base_field.rb +0 -22
  118. data/lib/graphql/types/relay/base_interface.rb +0 -29
  119. 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