graphql 2.5.11 → 2.5.19

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 (43) 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/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
  5. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
  6. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
  7. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
  8. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
  9. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
  10. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
  11. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
  12. data/lib/graphql/dashboard.rb +5 -2
  13. data/lib/graphql/dataloader/async_dataloader.rb +22 -11
  14. data/lib/graphql/dataloader/null_dataloader.rb +44 -10
  15. data/lib/graphql/dataloader.rb +75 -23
  16. data/lib/graphql/date_encoding_error.rb +1 -1
  17. data/lib/graphql/execution/interpreter/resolve.rb +7 -13
  18. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
  19. data/lib/graphql/execution/interpreter/runtime.rb +21 -16
  20. data/lib/graphql/execution/interpreter.rb +2 -13
  21. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  22. data/lib/graphql/language.rb +21 -12
  23. data/lib/graphql/schema/argument.rb +7 -0
  24. data/lib/graphql/schema/build_from_definition.rb +3 -1
  25. data/lib/graphql/schema/directive.rb +22 -4
  26. data/lib/graphql/schema/field.rb +6 -47
  27. data/lib/graphql/schema/member/has_arguments.rb +43 -14
  28. data/lib/graphql/schema/member/has_fields.rb +76 -4
  29. data/lib/graphql/schema/validator/required_validator.rb +33 -2
  30. data/lib/graphql/schema/visibility.rb +2 -2
  31. data/lib/graphql/schema.rb +20 -3
  32. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +2 -2
  33. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  34. data/lib/graphql/testing/helpers.rb +12 -9
  35. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  36. data/lib/graphql/testing.rb +1 -0
  37. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  38. data/lib/graphql/tracing/detailed_trace.rb +70 -7
  39. data/lib/graphql/tracing/perfetto_trace.rb +208 -78
  40. data/lib/graphql/tracing/sentry_trace.rb +3 -1
  41. data/lib/graphql/version.rb +1 -1
  42. data/lib/graphql.rb +5 -2
  43. metadata +7 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c2ef756861a779063236ee9f8745d30ae3494138674239c8f638556d7f5105e
4
- data.tar.gz: 23592e61f76acb0be2a4c60d953a829e8f1d46534633513ac44797a5d3386d2a
3
+ metadata.gz: 3a94d75c592000805bf9fd18507343c0b3428f1cf1d9a1828edc921828fd2735
4
+ data.tar.gz: 2984583eb702965d43e45f58f1ac29b6a782b8ecf02b4ee1953b5c5bbb6ec872
5
5
  SHA512:
6
- metadata.gz: 5c8db1d976343d569aa1fec1cee02de2c72947e0937fcd9917f8f6e078204efd373d244954e9e03b8dc7fd20066dcf7533bf4b52c706dba0f5640b60ae0465f0
7
- data.tar.gz: 38422c543b159cc7243ac4dcc9139ed95b2583cab7942c60e4c557b89d6a4ffaa8fb1df94286fa3fbe4b918f5c116364d5fbe5c2afa611ba9d46d7ac56c18e61
6
+ metadata.gz: 743bd603f22bb38bdff984c75b37eea32d460aeafd50617fc52dc9c4b93fe1d3265df196b728432a7b2d6494a6ba20c47b658ee84b387377037ea173844c654b
7
+ data.tar.gz: 12354210f6208443a5ec0dc931606440de29bfe0bdb7c6468e7faf9bc013a67671eab62fcc3bd88405d1ffb840a5c0e04be337d9a6707d511a2046d315722f63
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+ require 'rails/generators/active_record'
3
+
4
+ module Graphql
5
+ module Generators
6
+ class DetailedTraceGenerator < ::Rails::Generators::Base
7
+ include ::Rails::Generators::Migration
8
+ desc "Install GraphQL::Tracing::DetailedTrace for your schema"
9
+ source_root File.expand_path('../templates', __FILE__)
10
+
11
+ class_option :redis,
12
+ type: :boolean,
13
+ default: false,
14
+ desc: "Use Redis for persistence instead of ActiveRecord"
15
+
16
+ def self.next_migration_number(dirname)
17
+ ::ActiveRecord::Generators::Base.next_migration_number(dirname)
18
+ end
19
+
20
+ def install_detailed_traces
21
+
22
+ schema_glob = File.expand_path("app/graphql/*_schema.rb", destination_root)
23
+ schema_file = Dir.glob(schema_glob).first
24
+ if !schema_file
25
+ raise ArgumentError, "Failed to find schema definition file (checked: #{schema_glob.inspect})"
26
+ end
27
+ schema_file_match = /( *)class ([A-Za-z:]+) < GraphQL::Schema/.match(File.read(schema_file))
28
+ schema_name = schema_file_match[2]
29
+ indent = schema_file_match[1] + " "
30
+
31
+ if !options.redis?
32
+ migration_template 'create_graphql_detailed_traces.erb', 'db/migrate/create_graphql_detailed_traces.rb'
33
+ end
34
+
35
+ log :add_detailed_traces_plugin
36
+ sentinel = /< GraphQL::Schema\s*\n/m
37
+ code = <<-RUBY
38
+ #{indent}use GraphQL::Tracing::DetailedTrace#{options.redis? ? ", redis: raise(\"TODO: pass a connection to a persistent redis database\")" : ""}, limit: 50
39
+
40
+ #{indent}# When this returns true, DetailedTrace will trace the query
41
+ #{indent}# Could use `query.context`, `query.selected_operation_name`, `query.query_string` here
42
+ #{indent}# Could call out to Flipper, etc
43
+ #{indent}def self.detailed_trace?(query)
44
+ #{indent} rand <= 0.000_1 # one in ten thousand
45
+ #{indent}end
46
+
47
+ RUBY
48
+
49
+ in_root do
50
+ inject_into_file schema_file, code, after: sentinel, force: false
51
+ end
52
+
53
+ routes_source = File.read(File.expand_path("config/routes.rb", destination_root))
54
+ already_has_dashboard = routes_source.include?("GraphQL::Dashboard") ||
55
+ routes_source.include?("Schema.dashboard") ||
56
+ routes_source.include?("GraphQL::Pro::Routes::Lazy")
57
+
58
+ if (!already_has_dashboard || behavior == :revoke)
59
+ log :route, "GraphQL::Dashboard"
60
+ shell.mute do
61
+ route <<~RUBY
62
+ # TODO: add authorization to this route and expose it in production
63
+ # See https://graphql-ruby.org/pro/dashboard.html#authorizing-the-dashboard
64
+ if Rails.env.development?
65
+ mount GraphQL::Dashboard, at: "/graphql/dashboard", schema: #{schema_name.inspect}
66
+ end
67
+
68
+ RUBY
69
+ end
70
+
71
+ gem("google-protobuf")
72
+
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,10 @@
1
+ class CreateGraphqlDetailedTraces < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :graphql_detailed_traces, force: true do |t|
4
+ t.bigint :begin_ms, null: false
5
+ t.float :duration_ms, null: false
6
+ t.binary :trace_data, null: false
7
+ t.string :operation_name, null: false
8
+ end
9
+ end
10
+ end
@@ -20,4 +20,5 @@
20
20
  <div class="col-auto">
21
21
  <%= link_to "Back", graphql_dashboard.operation_store_clients_path, class: "btn btn-outline-secondary" %>
22
22
  </div>
23
+ </div>
23
24
  <% end %>
@@ -3,10 +3,10 @@
3
3
  <div class="col">
4
4
  <h1>Edit <%= @client.name %></h1>
5
5
  </div>
6
- <div>
6
+ </div>
7
7
  <%= render partial: "graphql/dashboard/operation_store/clients/form" %>
8
8
 
9
- <hr class="mt-5"/>
9
+ <hr class="mt-5">
10
10
  <div class="row mt-5">
11
11
  <div class="col">
12
12
  <div class="alert alert-danger">
@@ -33,7 +33,7 @@
33
33
  <td><%= link_to(client.name, graphql_dashboard.edit_operation_store_client_path(name: client.name)) %></td>
34
34
  <td>
35
35
  <%= link_to(graphql_dashboard.operation_store_client_operations_path(client_name: client.name)) do %>
36
- <%= client.operations_count %><% if client.archived_operations_count > 0 %> <span class="muted">(<%=client.archived_operations_count%> archived)</span><% end %>
36
+ <%= client.operations_count %><% if client.archived_operations_count > 0 %> <span class="muted">(<%= client.archived_operations_count %> archived)</span><% end %>
37
37
  <% end %>
38
38
  </td>
39
39
  <td><%= client.created_at %></td>
@@ -3,5 +3,5 @@
3
3
  <div class="col">
4
4
  <h1>New Client</h1>
5
5
  </div>
6
- <div>
6
+ </div>
7
7
  <%= render partial: "graphql/dashboard/operation_store/clients/form" %>
@@ -10,7 +10,7 @@
10
10
  <form method="GET" action="<%= graphql_dashboard.operation_store_index_entries_path %>" style="margin-left: auto; margin-top:-5px;">
11
11
  <div class="input-group">
12
12
  <%= text_field_tag "q", @search_term, class: "form-control", placeholder: "Find types, fields, arguments, or enum values" %>
13
- <input type="submit" value="Search" class="btn btn-outline-primary btn-sm"/>
13
+ <input type="submit" value="Search" class="btn btn-outline-primary btn-sm">
14
14
  </div>
15
15
  </form>
16
16
  </div>
@@ -2,7 +2,7 @@
2
2
  <% if @operation.nil? %>
3
3
  <div class="row">
4
4
  <div class="col">
5
- <p>No stored operation found for <code><%= params[:digest] %></code>
5
+ <p>No stored operation found for <code><%= params[:digest] %></code></p>
6
6
  </div>
7
7
  </div>
8
8
  <% else %>
@@ -9,7 +9,7 @@
9
9
  <div class="col">
10
10
  <p>Last triggered: <%= @topic_last_triggered_at || "none" %></p>
11
11
  <p><%= pluralize(@subscriptions_count, "Subscription") %></p>
12
- <div>
12
+ </div>
13
13
  </div>
14
14
 
15
15
  <div class="row">
@@ -1,15 +1,15 @@
1
1
  <!doctype html>
2
2
  <html lang="en" class="h-100" >
3
3
  <head>
4
- <link rel="icon" type="image/png" href="<%= graphql_dashboard.static_path("icon.png") %>" />
4
+ <link rel="icon" type="image/png" href="<%= graphql_dashboard.static_path("icon.png") %>">
5
5
  <meta charset="utf-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
7
  <title>GraphQL Dashboard <%= content_for?(:title) ? " · #{content_for(:title)}" : "" %> </title>
8
- <%= stylesheet_link_tag graphql_dashboard.static_path("bootstrap-5.3.3.min.css") %>
9
- <%= stylesheet_link_tag graphql_dashboard.static_path("charts.min.css") %>
10
- <%= stylesheet_link_tag graphql_dashboard.static_path("dashboard.css") %>
11
- <%= javascript_include_tag graphql_dashboard.static_path("bootstrap-5.3.3.min.js") %>
12
- <%= javascript_include_tag graphql_dashboard.static_path("dashboard.js") %>
8
+ <link rel="stylesheet" href="<%= graphql_dashboard.static_path("bootstrap-5.3.3.min.css") %>" media="screen" />
9
+ <link rel="stylesheet" href="<%= graphql_dashboard.static_path("charts.min.css") %>" media="screen" />
10
+ <link rel="stylesheet" href="<%= graphql_dashboard.static_path("dashboard.css") %>" media="screen" />
11
+ <script src="<%= graphql_dashboard.static_path("bootstrap-5.3.3.min.js") %>"></script>
12
+ <script src="<%= graphql_dashboard.static_path("dashboard.js") %>"></script>
13
13
  <%= csrf_meta_tags %>
14
14
  </head>
15
15
  <body class="h-100 d-flex flex-column">
@@ -20,7 +20,7 @@
20
20
  <nav class="navbar navbar-expand-lg bg-body-tertiary">
21
21
  <div class="container-fluid">
22
22
  <%= link_to graphql_dashboard.root_path, class: "navbar-brand" do %>
23
- <img id="header-icon" src="<%= graphql_dashboard.static_path("header-icon.png") %>" alt="GraphQL-Ruby" />
23
+ <img id="header-icon" src="<%= graphql_dashboard.static_path("header-icon.png") %>" alt="GraphQL-Ruby">
24
24
  <% end %>
25
25
  <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
26
26
  <span class="navbar-toggler-icon"></span>
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require 'rails/engine'
3
+ require 'action_controller'
3
4
  module Graphql
4
5
  # `GraphQL::Dashboard` is a `Rails::Engine`-based dashboard for viewing metadata about your GraphQL schema.
5
6
  #
@@ -34,7 +35,7 @@ module Graphql
34
35
  class Dashboard < Rails::Engine
35
36
  engine_name "graphql_dashboard"
36
37
  isolate_namespace(Graphql::Dashboard)
37
- routes.draw do
38
+ routes do
38
39
  root "landings#show"
39
40
  resources :statics, only: :show, constraints: { id: /[0-9A-Za-z\-.]+/ }
40
41
 
@@ -76,6 +77,8 @@ module Graphql
76
77
  resources :subscriptions, only: [:show], constraints: { id: /[a-zA-Z0-9\-]+/ }
77
78
  post "/subscriptions/clear_all", to: "subscriptions#clear_all", as: :clear_all
78
79
  end
80
+
81
+ ApplicationController.include(Dashboard.routes.url_helpers)
79
82
  end
80
83
 
81
84
  class ApplicationController < ActionController::Base
@@ -117,7 +120,7 @@ module Graphql
117
120
  end
118
121
 
119
122
  class StaticsController < ApplicationController
120
- skip_after_action :verify_same_origin_request
123
+ skip_forgery_protection
121
124
  # Use an explicit list of files to avoid any chance of reading other files from disk
122
125
  STATICS = {}
123
126
 
@@ -14,7 +14,7 @@ module GraphQL
14
14
  nil
15
15
  end
16
16
 
17
- def run
17
+ def run(trace_query_lazy: nil)
18
18
  trace = Fiber[:__graphql_current_multiplex]&.current_trace
19
19
  jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
20
20
  job_fibers = []
@@ -29,16 +29,7 @@ module GraphQL
29
29
  first_pass = false
30
30
  fiber_vars = get_fiber_variables
31
31
 
32
- while (f = (job_fibers.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size) < jobs_fiber_limit) && spawn_job_fiber(trace))))
33
- if f.alive?
34
- finished = run_fiber(f)
35
- if !finished
36
- next_job_fibers << f
37
- end
38
- end
39
- end
40
- job_fibers.concat(next_job_fibers)
41
- next_job_fibers.clear
32
+ run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace)
42
33
 
43
34
  Sync do |root_task|
44
35
  set_fiber_variables(fiber_vars)
@@ -54,6 +45,13 @@ module GraphQL
54
45
  next_source_tasks.clear
55
46
  end
56
47
  end
48
+
49
+ if !@lazies_at_depth.empty?
50
+ with_trace_query_lazy(trace_query_lazy) do
51
+ run_next_pending_lazies(job_fibers, trace)
52
+ run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace)
53
+ end
54
+ end
57
55
  end
58
56
  trace&.end_dataloader(self)
59
57
  end
@@ -69,6 +67,19 @@ module GraphQL
69
67
 
70
68
  private
71
69
 
70
+ def run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace)
71
+ while (f = (job_fibers.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size) < jobs_fiber_limit) && spawn_job_fiber(trace))))
72
+ if f.alive?
73
+ finished = run_fiber(f)
74
+ if !finished
75
+ next_job_fibers << f
76
+ end
77
+ end
78
+ end
79
+ job_fibers.concat(next_job_fibers)
80
+ next_job_fibers.clear
81
+ end
82
+
72
83
  def spawn_source_task(parent_task, condition, trace)
73
84
  pending_sources = nil
74
85
  @source_cache.each_value do |source_by_batch_params|
@@ -2,24 +2,58 @@
2
2
 
3
3
  module GraphQL
4
4
  class Dataloader
5
- # The default implementation of dataloading -- all no-ops.
5
+ # GraphQL-Ruby uses this when Dataloader isn't enabled.
6
6
  #
7
- # The Dataloader interface isn't public, but it enables
8
- # simple internal code while adding the option to add Dataloader.
7
+ # It runs execution code inline and gathers lazy objects (eg. Promises)
8
+ # and resolves them during {#run}.
9
9
  class NullDataloader < Dataloader
10
- # These are all no-ops because code was
11
- # executed synchronously.
10
+ def initialize(*)
11
+ @lazies_at_depth = Hash.new { |h,k| h[k] = [] }
12
+ end
13
+
14
+ def freeze
15
+ @lazies_at_depth.default_proc = nil
16
+ @lazies_at_depth.freeze
17
+ super
18
+ end
19
+
20
+ def run(trace_query_lazy: nil)
21
+ with_trace_query_lazy(trace_query_lazy) do
22
+ while !@lazies_at_depth.empty?
23
+ smallest_depth = nil
24
+ @lazies_at_depth.each_key do |depth_key|
25
+ smallest_depth ||= depth_key
26
+ if depth_key < smallest_depth
27
+ smallest_depth = depth_key
28
+ end
29
+ end
30
+
31
+ if smallest_depth
32
+ lazies = @lazies_at_depth.delete(smallest_depth)
33
+ lazies.each(&:value) # resolve these Lazy instances
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ def run_isolated
40
+ new_dl = self.class.new
41
+ res = nil
42
+ new_dl.append_job {
43
+ res = yield
44
+ }
45
+ new_dl.run
46
+ res
47
+ end
12
48
 
13
- def initialize(*); end
14
- def run; end
15
- def run_isolated; yield; end
16
49
  def clear_cache; end
50
+
17
51
  def yield(_source)
18
52
  raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources."
19
53
  end
20
54
 
21
- def append_job
22
- yield
55
+ def append_job(callable = nil)
56
+ callable ? callable.call : yield
23
57
  nil
24
58
  end
25
59
 
@@ -64,6 +64,7 @@ module GraphQL
64
64
  @nonblocking = nonblocking
65
65
  end
66
66
  @fiber_limit = fiber_limit
67
+ @lazies_at_depth = Hash.new { |h, k| h[k] = [] }
67
68
  end
68
69
 
69
70
  # @return [Integer, nil]
@@ -140,10 +141,10 @@ module GraphQL
140
141
  end
141
142
 
142
143
  # @api private Nothing to see here
143
- def append_job(&job)
144
+ def append_job(callable = nil, &job)
144
145
  # Given a block, queue it up to be worked through when `#run` is called.
145
- # (If the dataloader is already running, than a Fiber will pick this up later.)
146
- @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)
147
148
  nil
148
149
  end
149
150
 
@@ -160,6 +161,10 @@ module GraphQL
160
161
  def run_isolated
161
162
  prev_queue = @pending_jobs
162
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.
163
168
  @source_cache.each do |source_class, batched_sources|
164
169
  batched_sources.each do |batch_args, batched_source_instance|
165
170
  if batched_source_instance.pending?
@@ -179,6 +184,7 @@ module GraphQL
179
184
  res
180
185
  ensure
181
186
  @pending_jobs = prev_queue
187
+ @lazies_at_depth = prev_lazies_at_depth
182
188
  prev_pending_keys.each do |source_instance, pending|
183
189
  pending.each do |key, value|
184
190
  if !source_instance.results.key?(key)
@@ -188,7 +194,8 @@ module GraphQL
188
194
  end
189
195
  end
190
196
 
191
- def run
197
+ # @param trace_query_lazy [nil, Execution::Multiplex]
198
+ def run(trace_query_lazy: nil)
192
199
  trace = Fiber[:__graphql_current_multiplex]&.current_trace
193
200
  jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
194
201
  job_fibers = []
@@ -201,26 +208,13 @@ module GraphQL
201
208
  while first_pass || !job_fibers.empty?
202
209
  first_pass = false
203
210
 
204
- while (f = (job_fibers.shift || (((next_job_fibers.size + job_fibers.size) < jobs_fiber_limit) && spawn_job_fiber(trace))))
205
- if f.alive?
206
- finished = run_fiber(f)
207
- if !finished
208
- next_job_fibers << f
209
- end
210
- end
211
- end
212
- join_queues(job_fibers, next_job_fibers)
213
-
214
- while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
215
- 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)))
216
- if f.alive?
217
- finished = run_fiber(f)
218
- if !finished
219
- next_source_fibers << f
220
- end
221
- 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)
222
217
  end
223
- join_queues(source_fibers, next_source_fibers)
224
218
  end
225
219
  end
226
220
 
@@ -248,6 +242,11 @@ module GraphQL
248
242
  f.resume
249
243
  end
250
244
 
245
+ # @api private
246
+ def lazy_at_depth(depth, lazy)
247
+ @lazies_at_depth[depth] << lazy
248
+ end
249
+
251
250
  def spawn_fiber
252
251
  fiber_vars = get_fiber_variables
253
252
  Fiber.new(blocking: !@nonblocking) {
@@ -275,6 +274,59 @@ module GraphQL
275
274
 
276
275
  private
277
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
+
278
330
  def calculate_fiber_limit
279
331
  total_fiber_limit = @fiber_limit || Float::INFINITY
280
332
  if total_fiber_limit < 4
@@ -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
@@ -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
@@ -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.
@@ -42,6 +42,14 @@ module GraphQL
42
42
  end
43
43
  end
44
44
 
45
+ def depth
46
+ @depth ||= if @graphql_parent
47
+ @graphql_parent.depth + 1
48
+ else
49
+ 1
50
+ end
51
+ end
52
+
45
53
  attr_accessor :graphql_dead
46
54
  attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent,
47
55
  :graphql_application_value, :graphql_result_type, :graphql_selections, :graphql_is_eager, :ast_node, :graphql_arguments, :graphql_field
@@ -157,6 +165,11 @@ module GraphQL
157
165
  end
158
166
  end
159
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
160
173
  end
161
174
 
162
175
  class GraphQLResultArray