graphql 2.4.5 → 2.5.21

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.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  4. data/lib/graphql/analysis/analyzer.rb +2 -1
  5. data/lib/graphql/analysis/query_complexity.rb +87 -7
  6. data/lib/graphql/analysis/visitor.rb +37 -40
  7. data/lib/graphql/analysis.rb +12 -9
  8. data/lib/graphql/autoload.rb +1 -0
  9. data/lib/graphql/backtrace/table.rb +118 -55
  10. data/lib/graphql/backtrace.rb +1 -19
  11. data/lib/graphql/current.rb +6 -1
  12. data/lib/graphql/dashboard/application_controller.rb +41 -0
  13. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  14. data/lib/graphql/dashboard/installable.rb +22 -0
  15. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  16. data/lib/graphql/dashboard/limiters.rb +93 -0
  17. data/lib/graphql/dashboard/operation_store.rb +199 -0
  18. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  19. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  20. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  21. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  22. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  23. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  24. data/lib/graphql/dashboard/statics/icon.png +0 -0
  25. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  26. data/lib/graphql/dashboard/subscriptions.rb +97 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  37. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  38. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  39. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  40. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  41. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  42. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  43. data/lib/graphql/dashboard.rb +96 -0
  44. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  45. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  46. data/lib/graphql/dataloader/async_dataloader.rb +38 -15
  47. data/lib/graphql/dataloader/null_dataloader.rb +55 -10
  48. data/lib/graphql/dataloader/source.rb +18 -6
  49. data/lib/graphql/dataloader.rb +110 -26
  50. data/lib/graphql/date_encoding_error.rb +1 -1
  51. data/lib/graphql/dig.rb +2 -1
  52. data/lib/graphql/execution/interpreter/resolve.rb +10 -16
  53. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +58 -5
  54. data/lib/graphql/execution/interpreter/runtime.rb +229 -93
  55. data/lib/graphql/execution/interpreter.rb +15 -24
  56. data/lib/graphql/execution/multiplex.rb +7 -6
  57. data/lib/graphql/execution/next/field_resolve_step.rb +690 -0
  58. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  59. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  60. data/lib/graphql/execution/next/runner.rb +389 -0
  61. data/lib/graphql/execution/next/selections_step.rb +37 -0
  62. data/lib/graphql/execution/next.rb +69 -0
  63. data/lib/graphql/execution.rb +1 -0
  64. data/lib/graphql/execution_error.rb +13 -10
  65. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  66. data/lib/graphql/introspection/directive_type.rb +7 -3
  67. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  68. data/lib/graphql/introspection/entry_points.rb +11 -3
  69. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  70. data/lib/graphql/introspection/field_type.rb +13 -5
  71. data/lib/graphql/introspection/input_value_type.rb +21 -13
  72. data/lib/graphql/introspection/type_type.rb +64 -28
  73. data/lib/graphql/invalid_name_error.rb +1 -1
  74. data/lib/graphql/invalid_null_error.rb +25 -16
  75. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  76. data/lib/graphql/language/lexer.rb +16 -5
  77. data/lib/graphql/language/nodes.rb +8 -1
  78. data/lib/graphql/language/parser.rb +16 -8
  79. data/lib/graphql/language/static_visitor.rb +37 -33
  80. data/lib/graphql/language/visitor.rb +59 -55
  81. data/lib/graphql/language.rb +21 -12
  82. data/lib/graphql/pagination/connection.rb +2 -0
  83. data/lib/graphql/pagination/connections.rb +32 -0
  84. data/lib/graphql/query/context.rb +6 -10
  85. data/lib/graphql/query/null_context.rb +9 -3
  86. data/lib/graphql/query/partial.rb +179 -0
  87. data/lib/graphql/query.rb +64 -64
  88. data/lib/graphql/railtie.rb +1 -1
  89. data/lib/graphql/schema/addition.rb +3 -1
  90. data/lib/graphql/schema/always_visible.rb +1 -0
  91. data/lib/graphql/schema/argument.rb +24 -8
  92. data/lib/graphql/schema/build_from_definition.rb +113 -54
  93. data/lib/graphql/schema/directive/flagged.rb +2 -0
  94. data/lib/graphql/schema/directive.rb +52 -2
  95. data/lib/graphql/schema/enum.rb +36 -1
  96. data/lib/graphql/schema/enum_value.rb +1 -1
  97. data/lib/graphql/schema/field/connection_extension.rb +15 -35
  98. data/lib/graphql/schema/field/scope_extension.rb +22 -13
  99. data/lib/graphql/schema/field.rb +101 -51
  100. data/lib/graphql/schema/field_extension.rb +33 -0
  101. data/lib/graphql/schema/input_object.rb +45 -38
  102. data/lib/graphql/schema/interface.rb +2 -1
  103. data/lib/graphql/schema/list.rb +1 -1
  104. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  105. data/lib/graphql/schema/member/has_arguments.rb +56 -19
  106. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  107. data/lib/graphql/schema/member/has_dataloader.rb +79 -0
  108. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  109. data/lib/graphql/schema/member/has_directives.rb +1 -1
  110. data/lib/graphql/schema/member/has_fields.rb +81 -5
  111. data/lib/graphql/schema/member/has_interfaces.rb +3 -3
  112. data/lib/graphql/schema/member/scoped.rb +1 -1
  113. data/lib/graphql/schema/member/type_system_helpers.rb +17 -3
  114. data/lib/graphql/schema/member.rb +6 -0
  115. data/lib/graphql/schema/object.rb +18 -8
  116. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  117. data/lib/graphql/schema/resolver.rb +52 -6
  118. data/lib/graphql/schema/scalar.rb +1 -6
  119. data/lib/graphql/schema/subscription.rb +50 -4
  120. data/lib/graphql/schema/timeout.rb +19 -2
  121. data/lib/graphql/schema/validator/required_validator.rb +71 -14
  122. data/lib/graphql/schema/visibility/migration.rb +3 -2
  123. data/lib/graphql/schema/visibility/profile.rb +115 -23
  124. data/lib/graphql/schema/visibility.rb +49 -32
  125. data/lib/graphql/schema/warden.rb +23 -2
  126. data/lib/graphql/schema.rb +333 -68
  127. data/lib/graphql/static_validation/all_rules.rb +2 -2
  128. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  129. data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -17
  130. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  131. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  132. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  133. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  134. data/lib/graphql/static_validation/validator.rb +6 -1
  135. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  136. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  137. data/lib/graphql/subscriptions/event.rb +12 -1
  138. data/lib/graphql/subscriptions/serialize.rb +1 -1
  139. data/lib/graphql/subscriptions.rb +1 -1
  140. data/lib/graphql/testing/helpers.rb +17 -11
  141. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  142. data/lib/graphql/testing.rb +1 -0
  143. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  144. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  145. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  146. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  147. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  148. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  149. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  150. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  151. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  152. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  153. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  154. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  155. data/lib/graphql/tracing/detailed_trace.rb +156 -0
  156. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  157. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  158. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  159. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  160. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  161. data/lib/graphql/tracing/notifications_trace.rb +184 -34
  162. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  163. data/lib/graphql/tracing/null_trace.rb +9 -0
  164. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  165. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  166. data/lib/graphql/tracing/perfetto_trace.rb +864 -0
  167. data/lib/graphql/tracing/platform_trace.rb +5 -0
  168. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  169. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  170. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  171. data/lib/graphql/tracing/scout_trace.rb +32 -55
  172. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  173. data/lib/graphql/tracing/sentry_trace.rb +64 -94
  174. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  175. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  176. data/lib/graphql/tracing/trace.rb +111 -1
  177. data/lib/graphql/tracing.rb +31 -30
  178. data/lib/graphql/type_kinds.rb +1 -0
  179. data/lib/graphql/types/relay/connection_behaviors.rb +9 -7
  180. data/lib/graphql/types/relay/edge_behaviors.rb +5 -4
  181. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  182. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  183. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  184. data/lib/graphql/unauthorized_error.rb +5 -1
  185. data/lib/graphql/version.rb +1 -1
  186. data/lib/graphql.rb +12 -31
  187. metadata +174 -11
  188. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  189. data/lib/graphql/backtrace/trace.rb +0 -93
  190. data/lib/graphql/backtrace/tracer.rb +0 -80
  191. data/lib/graphql/schema/null_mask.rb +0 -11
  192. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -21,7 +21,7 @@ module GraphQL
21
21
  def request(value)
22
22
  res_key = result_key_for(value)
23
23
  if !@results.key?(res_key)
24
- @pending[res_key] ||= value
24
+ @pending[res_key] ||= normalize_fetch_key(value)
25
25
  end
26
26
  Dataloader::Request.new(self, value)
27
27
  end
@@ -35,12 +35,24 @@ module GraphQL
35
35
  value
36
36
  end
37
37
 
38
+ # Implement this method if varying values given to {load} (etc) should be consolidated
39
+ # or normalized before being handed off to your {fetch} implementation.
40
+ #
41
+ # This is different than {result_key_for} because _that_ method handles unification inside Dataloader's cache,
42
+ # but this method changes the value passed into {fetch}.
43
+ #
44
+ # @param value [Object] The value passed to {load}, {load_all}, {request}, or {request_all}
45
+ # @return [Object] The value given to {fetch}
46
+ def normalize_fetch_key(value)
47
+ value
48
+ end
49
+
38
50
  # @return [Dataloader::Request] a pending request for a values from `keys`. Call `.load` on that object to wait for the results.
39
51
  def request_all(values)
40
52
  values.each do |v|
41
53
  res_key = result_key_for(v)
42
54
  if !@results.key?(res_key)
43
- @pending[res_key] ||= v
55
+ @pending[res_key] ||= normalize_fetch_key(v)
44
56
  end
45
57
  end
46
58
  Dataloader::RequestAll.new(self, values)
@@ -53,7 +65,7 @@ module GraphQL
53
65
  if @results.key?(result_key)
54
66
  result_for(result_key)
55
67
  else
56
- @pending[result_key] ||= value
68
+ @pending[result_key] ||= normalize_fetch_key(value)
57
69
  sync([result_key])
58
70
  result_for(result_key)
59
71
  end
@@ -68,7 +80,7 @@ module GraphQL
68
80
  k = result_key_for(v)
69
81
  result_keys << k
70
82
  if !@results.key?(k)
71
- @pending[k] ||= v
83
+ @pending[k] ||= normalize_fetch_key(v)
72
84
  pending_keys << k
73
85
  end
74
86
  }
@@ -93,14 +105,14 @@ module GraphQL
93
105
  # Then run the batch and update the cache.
94
106
  # @return [void]
95
107
  def sync(pending_result_keys)
96
- @dataloader.yield
108
+ @dataloader.yield(self)
97
109
  iterations = 0
98
110
  while pending_result_keys.any? { |key| !@results.key?(key) }
99
111
  iterations += 1
100
112
  if iterations > MAX_ITERATIONS
101
113
  raise "#{self.class}#sync tried #{MAX_ITERATIONS} times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency#{@dataloader.fiber_limit ? " or `fiber_limit: #{@dataloader.fiber_limit}` is set too low" : ""}."
102
114
  end
103
- @dataloader.yield
115
+ @dataloader.yield(self)
104
116
  end
105
117
  nil
106
118
  end
@@ -4,6 +4,8 @@ require "graphql/dataloader/null_dataloader"
4
4
  require "graphql/dataloader/request"
5
5
  require "graphql/dataloader/request_all"
6
6
  require "graphql/dataloader/source"
7
+ require "graphql/dataloader/active_record_association_source"
8
+ require "graphql/dataloader/active_record_source"
7
9
 
8
10
  module GraphQL
9
11
  # This plugin supports Fiber-based concurrency, along with {GraphQL::Dataloader::Source}.
@@ -62,6 +64,7 @@ module GraphQL
62
64
  @nonblocking = nonblocking
63
65
  end
64
66
  @fiber_limit = fiber_limit
67
+ @lazies_at_depth = Hash.new { |h, k| h[k] = [] }
65
68
  end
66
69
 
67
70
  # @return [Integer, nil]
@@ -129,16 +132,19 @@ module GraphQL
129
132
  # Dataloader will resume the fiber after the requested data has been loaded (by another Fiber).
130
133
  #
131
134
  # @return [void]
132
- def yield
135
+ def yield(source = Fiber[:__graphql_current_dataloader_source])
136
+ trace = Fiber[:__graphql_current_multiplex]&.current_trace
137
+ trace&.dataloader_fiber_yield(source)
133
138
  Fiber.yield
139
+ trace&.dataloader_fiber_resume(source)
134
140
  nil
135
141
  end
136
142
 
137
143
  # @api private Nothing to see here
138
- def append_job(&job)
144
+ def append_job(callable = nil, &job)
139
145
  # Given a block, queue it up to be worked through when `#run` is called.
140
- # (If the dataloader is already running, than a Fiber will pick this up later.)
141
- @pending_jobs.push(job)
146
+ # (If the dataloader is already running, then a Fiber will pick this up later.)
147
+ @pending_jobs.push(callable || job)
142
148
  nil
143
149
  end
144
150
 
@@ -155,6 +161,10 @@ module GraphQL
155
161
  def run_isolated
156
162
  prev_queue = @pending_jobs
157
163
  prev_pending_keys = {}
164
+ prev_lazies_at_depth = @lazies_at_depth
165
+ @lazies_at_depth = @lazies_at_depth.dup.clear
166
+ # Clear pending loads but keep already-cached records
167
+ # in case they are useful to the given block.
158
168
  @source_cache.each do |source_class, batched_sources|
159
169
  batched_sources.each do |batch_args, batched_source_instance|
160
170
  if batched_source_instance.pending?
@@ -174,6 +184,7 @@ module GraphQL
174
184
  res
175
185
  ensure
176
186
  @pending_jobs = prev_queue
187
+ @lazies_at_depth = prev_lazies_at_depth
177
188
  prev_pending_keys.each do |source_instance, pending|
178
189
  pending.each do |key, value|
179
190
  if !source_instance.results.key?(key)
@@ -183,7 +194,9 @@ module GraphQL
183
194
  end
184
195
  end
185
196
 
186
- def run
197
+ # @param trace_query_lazy [nil, Execution::Multiplex]
198
+ def run(trace_query_lazy: nil)
199
+ trace = Fiber[:__graphql_current_multiplex]&.current_trace
187
200
  jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
188
201
  job_fibers = []
189
202
  next_job_fibers = []
@@ -191,31 +204,21 @@ module GraphQL
191
204
  next_source_fibers = []
192
205
  first_pass = true
193
206
  manager = spawn_fiber do
207
+ trace&.begin_dataloader(self)
194
208
  while first_pass || !job_fibers.empty?
195
209
  first_pass = false
196
210
 
197
- while (f = (job_fibers.shift || (((next_job_fibers.size + job_fibers.size) < jobs_fiber_limit) && spawn_job_fiber)))
198
- if f.alive?
199
- finished = run_fiber(f)
200
- if !finished
201
- next_job_fibers << f
202
- end
203
- end
204
- end
205
- join_queues(job_fibers, next_job_fibers)
206
-
207
- while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
208
- while (f = source_fibers.shift || (((job_fibers.size + source_fibers.size + next_source_fibers.size + next_job_fibers.size) < total_fiber_limit) && spawn_source_fiber))
209
- if f.alive?
210
- finished = run_fiber(f)
211
- if !finished
212
- next_source_fibers << f
213
- end
214
- end
211
+ run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
212
+
213
+ if !@lazies_at_depth.empty?
214
+ with_trace_query_lazy(trace_query_lazy) do
215
+ run_next_pending_lazies(job_fibers, trace)
216
+ run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
215
217
  end
216
- join_queues(source_fibers, next_source_fibers)
217
218
  end
218
219
  end
220
+
221
+ trace&.end_dataloader(self)
219
222
  end
220
223
 
221
224
  run_fiber(manager)
@@ -230,6 +233,7 @@ module GraphQL
230
233
  if !source_fibers.empty?
231
234
  raise "Invariant: source fibers should have exited but #{source_fibers.size} remained"
232
235
  end
236
+
233
237
  rescue UncaughtThrowError => e
234
238
  throw e.tag, e.value
235
239
  end
@@ -238,6 +242,11 @@ module GraphQL
238
242
  f.resume
239
243
  end
240
244
 
245
+ # @api private
246
+ def lazy_at_depth(depth, lazy)
247
+ @lazies_at_depth[depth] << lazy
248
+ end
249
+
241
250
  def spawn_fiber
242
251
  fiber_vars = get_fiber_variables
243
252
  Fiber.new(blocking: !@nonblocking) {
@@ -247,8 +256,77 @@ module GraphQL
247
256
  }
248
257
  end
249
258
 
259
+ # Pre-warm the Dataloader cache with ActiveRecord objects which were loaded elsewhere.
260
+ # These will be used by {Dataloader::ActiveRecordSource}, {Dataloader::ActiveRecordAssociationSource} and their helper
261
+ # methods, `dataload_record` and `dataload_association`.
262
+ # @param records [Array<ActiveRecord::Base>] Already-loaded records to warm the cache with
263
+ # @param index_by [Symbol] The attribute to use as the cache key. (Should match `find_by:` when using {ActiveRecordSource})
264
+ # @return [void]
265
+ def merge_records(records, index_by: :id)
266
+ records_by_class = Hash.new { |h, k| h[k] = {} }
267
+ records.each do |r|
268
+ records_by_class[r.class][r.public_send(index_by)] = r
269
+ end
270
+ records_by_class.each do |r_class, records|
271
+ with(ActiveRecordSource, r_class).merge(records)
272
+ end
273
+ end
274
+
250
275
  private
251
276
 
277
+ def run_next_pending_lazies(job_fibers, trace)
278
+ smallest_depth = nil
279
+ @lazies_at_depth.each_key do |depth_key|
280
+ smallest_depth ||= depth_key
281
+ if depth_key < smallest_depth
282
+ smallest_depth = depth_key
283
+ end
284
+ end
285
+
286
+ if smallest_depth
287
+ lazies = @lazies_at_depth.delete(smallest_depth)
288
+ if !lazies.empty?
289
+ lazies.each_with_index do |l, idx|
290
+ append_job { l.value }
291
+ end
292
+ job_fibers.unshift(spawn_job_fiber(trace))
293
+ end
294
+ end
295
+ end
296
+
297
+ def run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
298
+ while (f = (job_fibers.shift || (((next_job_fibers.size + job_fibers.size) < jobs_fiber_limit) && spawn_job_fiber(trace))))
299
+ if f.alive?
300
+ finished = run_fiber(f)
301
+ if !finished
302
+ next_job_fibers << f
303
+ end
304
+ end
305
+ end
306
+ join_queues(job_fibers, next_job_fibers)
307
+
308
+ while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
309
+ while (f = source_fibers.shift || (((job_fibers.size + source_fibers.size + next_source_fibers.size + next_job_fibers.size) < total_fiber_limit) && spawn_source_fiber(trace)))
310
+ if f.alive?
311
+ finished = run_fiber(f)
312
+ if !finished
313
+ next_source_fibers << f
314
+ end
315
+ end
316
+ end
317
+ join_queues(source_fibers, next_source_fibers)
318
+ end
319
+ end
320
+
321
+ def with_trace_query_lazy(multiplex_or_nil, &block)
322
+ if (multiplex = multiplex_or_nil)
323
+ query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil
324
+ multiplex.current_trace.execute_query_lazy(query: query, multiplex: multiplex, &block)
325
+ else
326
+ yield
327
+ end
328
+ end
329
+
252
330
  def calculate_fiber_limit
253
331
  total_fiber_limit = @fiber_limit || Float::INFINITY
254
332
  if total_fiber_limit < 4
@@ -266,17 +344,19 @@ module GraphQL
266
344
  new_queue.clear
267
345
  end
268
346
 
269
- def spawn_job_fiber
347
+ def spawn_job_fiber(trace)
270
348
  if !@pending_jobs.empty?
271
349
  spawn_fiber do
350
+ trace&.dataloader_spawn_execution_fiber(@pending_jobs)
272
351
  while job = @pending_jobs.shift
273
352
  job.call
274
353
  end
354
+ trace&.dataloader_fiber_exit
275
355
  end
276
356
  end
277
357
  end
278
358
 
279
- def spawn_source_fiber
359
+ def spawn_source_fiber(trace)
280
360
  pending_sources = nil
281
361
  @source_cache.each_value do |source_by_batch_params|
282
362
  source_by_batch_params.each_value do |source|
@@ -289,10 +369,14 @@ module GraphQL
289
369
 
290
370
  if pending_sources
291
371
  spawn_fiber do
372
+ trace&.dataloader_spawn_source_fiber(pending_sources)
292
373
  pending_sources.each do |source|
293
374
  Fiber[:__graphql_current_dataloader_source] = source
375
+ trace&.begin_dataloader_source(source)
294
376
  source.run_pending_keys
377
+ trace&.end_dataloader_source(source)
295
378
  end
379
+ trace&.dataloader_fiber_exit
296
380
  end
297
381
  end
298
382
  end
@@ -10,7 +10,7 @@ module GraphQL
10
10
 
11
11
  def initialize(value)
12
12
  @date_value = value
13
- super("Date cannot be parsed: #{value}. \nDate must be be able to be parsed as a Ruby Date object.")
13
+ super("Date cannot be parsed: #{value}. \nDate must be able to be parsed as a Ruby Date object.")
14
14
  end
15
15
  end
16
16
  end
data/lib/graphql/dig.rb CHANGED
@@ -5,7 +5,8 @@ module GraphQL
5
5
  # so we can use some of the magic in Schema::InputObject and Interpreter::Arguments
6
6
  # to handle stringified/symbolized keys.
7
7
  #
8
- # @param args [Array<[String, Symbol>] Retrieves the value object corresponding to the each key objects repeatedly
8
+ # @param own_key [String, Symbol] A key to retrieve
9
+ # @param rest_keys [Array<[String, Symbol>] Retrieves the value object corresponding to the each key objects repeatedly
9
10
  # @return [Object]
10
11
  def dig(own_key, *rest_keys)
11
12
  val = self[own_key]
@@ -6,12 +6,17 @@ module GraphQL
6
6
  module Resolve
7
7
  # Continue field results in `results` until there's nothing else to continue.
8
8
  # @return [void]
9
+ # @deprecated Call `dataloader.run` instead
9
10
  def self.resolve_all(results, dataloader)
11
+ warn "#{self}.#{__method__} is deprecated; Use `dataloader.run` instead.#{caller(1, 5).map { |l| "\n #{l}"}.join}"
10
12
  dataloader.append_job { resolve(results, dataloader) }
11
13
  nil
12
14
  end
13
15
 
16
+ # @deprecated Call `dataloader.run` instead
14
17
  def self.resolve_each_depth(lazies_at_depth, dataloader)
18
+ warn "#{self}.#{__method__} is deprecated; Use `dataloader.run` instead.#{caller(1, 5).map { |l| "\n #{l}"}.join}"
19
+
15
20
  smallest_depth = nil
16
21
  lazies_at_depth.each_key do |depth_key|
17
22
  smallest_depth ||= depth_key
@@ -23,9 +28,9 @@ module GraphQL
23
28
  if smallest_depth
24
29
  lazies = lazies_at_depth.delete(smallest_depth)
25
30
  if !lazies.empty?
26
- dataloader.append_job {
27
- lazies.each(&:value) # resolve these Lazy instances
28
- }
31
+ lazies.each do |l|
32
+ dataloader.append_job { l.value }
33
+ end
29
34
  # Run lazies _and_ dataloader, see if more are enqueued
30
35
  dataloader.run
31
36
  resolve_each_depth(lazies_at_depth, dataloader)
@@ -34,20 +39,9 @@ module GraphQL
34
39
  nil
35
40
  end
36
41
 
37
- # After getting `results` back from an interpreter evaluation,
38
- # continue it until you get a response-ready Ruby value.
39
- #
40
- # `results` is one level of _depth_ of a query or multiplex.
41
- #
42
- # Resolve all lazy values in that depth before moving on
43
- # to the next level.
44
- #
45
- # It's assumed that the lazies will
46
- # return {Lazy} instances if there's more work to be done,
47
- # or return {Hash}/{Array} if the query should be continued.
48
- #
49
- # @return [void]
42
+ # @deprecated Call `dataloader.run` instead
50
43
  def self.resolve(results, dataloader)
44
+ warn "#{self}.#{__method__} is deprecated; Use `dataloader.run` instead.#{caller(1, 5).map { |l| "\n #{l}"}.join}"
51
45
  # There might be pending jobs here that _will_ write lazies
52
46
  # into the result hash. We should run them out, so we
53
47
  # can be sure that all lazies will be present in the result hashes.
@@ -5,7 +5,10 @@ module GraphQL
5
5
  class Interpreter
6
6
  class Runtime
7
7
  module GraphQLResult
8
- def initialize(result_name, result_type, application_value, parent_result, is_non_null_in_parent, selections, is_eager)
8
+ def initialize(result_name, result_type, application_value, parent_result, is_non_null_in_parent, selections, is_eager, ast_node, graphql_arguments, graphql_field) # rubocop:disable Metrics/ParameterLists
9
+ @ast_node = ast_node
10
+ @graphql_arguments = graphql_arguments
11
+ @graphql_field = graphql_field
9
12
  @graphql_parent = parent_result
10
13
  @graphql_application_value = application_value
11
14
  @graphql_result_type = result_type
@@ -18,31 +21,52 @@ module GraphQL
18
21
  @graphql_metadata = nil
19
22
  @graphql_selections = selections
20
23
  @graphql_is_eager = is_eager
24
+ @base_path = nil
21
25
  end
22
26
 
27
+ # TODO test full path in Partial
28
+ attr_writer :base_path
29
+
23
30
  def path
24
31
  @path ||= build_path([])
25
32
  end
26
33
 
27
34
  def build_path(path_array)
28
35
  graphql_result_name && path_array.unshift(graphql_result_name)
29
- @graphql_parent ? @graphql_parent.build_path(path_array) : path_array
36
+ if @graphql_parent
37
+ @graphql_parent.build_path(path_array)
38
+ elsif @base_path
39
+ @base_path + path_array
40
+ else
41
+ path_array
42
+ end
43
+ end
44
+
45
+ def depth
46
+ @depth ||= if @graphql_parent
47
+ @graphql_parent.depth + 1
48
+ else
49
+ 1
50
+ end
30
51
  end
31
52
 
32
53
  attr_accessor :graphql_dead
33
54
  attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent,
34
- :graphql_application_value, :graphql_result_type, :graphql_selections, :graphql_is_eager
55
+ :graphql_application_value, :graphql_result_type, :graphql_selections, :graphql_is_eager, :ast_node, :graphql_arguments, :graphql_field
35
56
 
36
57
  # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
37
58
  attr_accessor :graphql_result_data
38
59
  end
39
60
 
40
61
  class GraphQLResultHash
41
- def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager)
62
+ def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager, _ast_node, _graphql_arguments, graphql_field) # rubocop:disable Metrics/ParameterLists
42
63
  super
43
64
  @graphql_result_data = {}
65
+ @ordered_result_keys = nil
44
66
  end
45
67
 
68
+ attr_accessor :ordered_result_keys
69
+
46
70
  include GraphQLResult
47
71
 
48
72
  attr_accessor :graphql_merged_into
@@ -60,7 +84,13 @@ module GraphQL
60
84
  t.set_leaf(key, value)
61
85
  end
62
86
 
87
+ before_size = @graphql_result_data.size
63
88
  @graphql_result_data[key] = value
89
+ after_size = @graphql_result_data.size
90
+ if after_size > before_size && @ordered_result_keys[before_size] != key
91
+ fix_result_order
92
+ end
93
+
64
94
  # keep this up-to-date if it's been initialized
65
95
  @graphql_metadata && @graphql_metadata[key] = value
66
96
 
@@ -71,7 +101,13 @@ module GraphQL
71
101
  if (t = @graphql_merged_into)
72
102
  t.set_child_result(key, value)
73
103
  end
104
+ before_size = @graphql_result_data.size
74
105
  @graphql_result_data[key] = value.graphql_result_data
106
+ after_size = @graphql_result_data.size
107
+ if after_size > before_size && @ordered_result_keys[before_size] != key
108
+ fix_result_order
109
+ end
110
+
75
111
  # If we encounter some part of this response that requires metadata tracking,
76
112
  # then create the metadata hash if necessary. It will be kept up-to-date after this.
77
113
  (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
@@ -121,12 +157,25 @@ module GraphQL
121
157
  end
122
158
  @graphql_merged_into = into_result
123
159
  end
160
+
161
+ def fix_result_order
162
+ @ordered_result_keys.each do |k|
163
+ if @graphql_result_data.key?(k)
164
+ @graphql_result_data[k] = @graphql_result_data.delete(k)
165
+ end
166
+ end
167
+ end
168
+
169
+ # hook for breadth-first implementations to signal when collecting results.
170
+ def collect_result(result_name, result_value)
171
+ false
172
+ end
124
173
  end
125
174
 
126
175
  class GraphQLResultArray
127
176
  include GraphQLResult
128
177
 
129
- def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager)
178
+ def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager, _ast_node, _graphql_arguments, graphql_field) # rubocop:disable Metrics/ParameterLists
130
179
  super
131
180
  @graphql_result_data = []
132
181
  end
@@ -168,6 +217,10 @@ module GraphQL
168
217
  def values
169
218
  (@graphql_metadata || @graphql_result_data)
170
219
  end
220
+
221
+ def [](idx)
222
+ (@graphql_metadata || @graphql_result_data)[idx]
223
+ end
171
224
  end
172
225
  end
173
226
  end