graphql 2.6.3 → 2.6.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16abc9c2a5eda0da251dbe7e14433241bc7d038d2527926832ceb29336fb857a
4
- data.tar.gz: c89bd2a4b340ca30bfa7b823f51e4671972355d54bd56fdaed598c13a415c3b3
3
+ metadata.gz: 3366525e188ac8b81b4dc7bdc8e34bb1839153fbec23d8e2a5faf07c02b56aa6
4
+ data.tar.gz: 414d8f17cee8d40adf8c5923427e83d187efdbd08871c7b9d8bbd4fa13b0e550
5
5
  SHA512:
6
- metadata.gz: 6452d2a517c502b4a8934582d060885a5f6462de244a1f331433f1076dc57a879cdd081e147f70925064a99607e0794fcf6eadad7a3f4a9111a5ab979c552554
7
- data.tar.gz: b6a0219606d905fc885ab2192596d1acb1d92bce407defc751bfafb1a7199e9c32275c1d3202f3246251e72b80554698b1f54920f76178f1ddc81b41d628bacd
6
+ metadata.gz: f4423d53187dcf97e67ae1ba93cc9b5135c7db35cab04abc5533b96e97e3abd5d2c4bbbe683a64abc0117a022743a53df804d170e5fbd0fd7430a3987466531b
7
+ data.tar.gz: c6093a9a53e554aed992da96b80007bbe10df7ac9552c4a1dc0cce19ae8a3f33fd3e2cb0858c117c1e061c4b996a4af8a7b161b31b4f3806ff3a30a84dfaddb2
@@ -6,7 +6,23 @@ module Graphql
6
6
  module FieldExtractor
7
7
  def fields
8
8
  columns = []
9
- columns += (klass&.columns&.map { |c| generate_column_string(c) } || [])
9
+ if (model_columns = klass&.columns)
10
+ filter = if defined?(ActiveSupport::ParameterFilter)
11
+ fp = if defined?(Rails) && Rails.application && (app_config = Rails.application.config.filter_parameters).present? && !app_config.empty?
12
+ app_config
13
+ elsif ActiveSupport.respond_to?(:filter_parameters)
14
+ ActiveSupport.filter_parameters
15
+ else
16
+ []
17
+ end
18
+ ActiveSupport::ParameterFilter.new(fp, mask: nil)
19
+ else
20
+ nil
21
+ end
22
+ columns += model_columns
23
+ .select { |c| filter ? filter.filter_param(c.name, c.name) : true }
24
+ .map { |c| generate_column_string(c) }
25
+ end
10
26
  columns + custom_fields
11
27
  end
12
28
 
@@ -29,14 +29,14 @@ module GraphQL
29
29
  future_complexity
30
30
  end
31
31
  when nil
32
- subject.logger.warn <<~GRAPHQL
32
+ subject.logger.warn <<~MESSAGE
33
33
  GraphQL-Ruby's complexity cost system is getting some "breaking fixes" in a future version. See the migration notes at https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#complexity_cost_calculation_mode_for-class_method
34
34
 
35
35
  To opt into the future behavior, configure your schema (#{subject.schema.name ? subject.schema.name : subject.schema.ancestors}) with:
36
36
 
37
37
  complexity_cost_calculation_mode(:future) # or `:legacy`, `:compare`
38
38
 
39
- GRAPHQL
39
+ MESSAGE
40
40
  max_possible_complexity(mode: :legacy)
41
41
  else
42
42
  raise ArgumentError, "Expected `:future`, `:legacy`, `:compare`, or `nil` from `#{query.schema}.complexity_cost_calculation_mode_for` but got: #{query.schema.complexity_cost_calculation_mode.inspect}"
@@ -1,110 +1,316 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GraphQL
3
4
  class Dataloader
4
5
  class AsyncDataloader < Dataloader
6
+ def self.use(...)
7
+ if !Async::Task.method_defined?(:cancel)
8
+ Async::Task.alias_method(:cancel, :stop)
9
+ end
10
+ if !Async::Task.method_defined?(:graphql_async_dataloader_run)
11
+ Async::Task.attr_accessor(:graphql_async_dataloader_run)
12
+ Async::Task.attr_accessor(:graphql_async_dataloader_condition)
13
+ end
14
+ super
15
+ end
16
+
17
+ def initialize(...)
18
+ super
19
+ create_pending_run
20
+ end
21
+
22
+ # @api private
23
+ attr_reader :pending_sources
24
+
25
+ def create_pending_run
26
+ jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
27
+ @pending_run = Run.new(self, total_fiber_limit, jobs_fiber_limit)
28
+ end
29
+
5
30
  def yield(source = Fiber[:__graphql_current_dataloader_source])
6
- trace = Fiber[:__graphql_current_multiplex]&.current_trace
31
+ task = Async::Task.current
32
+ run = task.graphql_async_dataloader_run
33
+ trace = run.trace
7
34
  trace&.dataloader_fiber_yield(source)
8
- if (condition = Fiber[:graphql_dataloader_next_tick])
9
- condition.wait
10
- else
11
- Fiber.yield
12
- end
35
+ run.tasks_channel.push([:paused_task, task])
36
+ condition = task.graphql_async_dataloader_condition
37
+ condition.wait
38
+ run.tasks_channel.push([:resumed_task, task])
13
39
  trace&.dataloader_fiber_resume(source)
14
40
  nil
15
41
  end
16
42
 
17
- def run(trace_query_lazy: nil)
18
- trace = Fiber[:__graphql_current_multiplex]&.current_trace
19
- jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
20
- job_fibers = []
21
- next_job_fibers = []
22
- source_tasks = []
23
- next_source_tasks = []
24
- first_pass = true
25
- sources_condition = Async::Condition.new
26
- manager = spawn_fiber do
27
- trace&.begin_dataloader(self)
28
- while first_pass || !job_fibers.empty?
29
- first_pass = false
30
- fiber_vars = get_fiber_variables
31
-
32
- run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace)
33
-
34
- Sync do |root_task|
35
- set_fiber_variables(fiber_vars)
36
- while !source_tasks.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
37
- while (task = (source_tasks.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size + next_source_tasks.size) < total_fiber_limit) && spawn_source_task(root_task, sources_condition, trace))))
38
- if task.alive?
39
- root_task.yield # give the source task a chance to run
40
- next_source_tasks << task
43
+ class Run
44
+ def initialize(dataloader, total_fiber_limit, jobs_fiber_limit)
45
+ @dataloader = dataloader
46
+ @root_task = nil
47
+ @trace = nil
48
+ @jobs = []
49
+
50
+ @total_fiber_limit = total_fiber_limit
51
+ @jobs_fiber_limit = jobs_fiber_limit
52
+ @lazies_at_depth = Hash.new { |h, k| h[k] = [] }
53
+
54
+ @running_tasks = nil
55
+ @tasks_channel = nil
56
+ @tasks_channel_task = nil
57
+ @finished_all_tasks = nil
58
+
59
+ @snoozed_jobs_condition = Async::Condition.new
60
+ @snoozed_sources_condition = Async::Condition.new
61
+ end
62
+
63
+ attr_accessor :trace, :root_task
64
+
65
+ attr_reader :jobs, :lazies_at_depth, :jobs_fiber_limit, :snoozed_jobs_condition, :snoozed_sources_condition, :tasks_channel
66
+
67
+ def jobs_bandwidth?
68
+ running_count < @jobs_fiber_limit
69
+ end
70
+
71
+ def sources_bandwidth?
72
+ running_count < current_sources_fiber_limit
73
+ end
74
+
75
+ def close_queues
76
+ @tasks_channel.close
77
+ @tasks_channel_task.cancel
78
+ end
79
+
80
+ def wait_for_queues
81
+ @finished_all_tasks.wait
82
+ @finished_all_tasks = Async::Promise.new
83
+ end
84
+
85
+ def wait_for_no_running_tasks
86
+ @no_running_tasks.wait
87
+ @no_running_tasks = Async::Promise.new
88
+ end
89
+
90
+ def new_queues(mode)
91
+ @tasks_channel = Async::Queue.new(parent: @root_task)
92
+ @no_running_tasks = Async::Promise.new
93
+ @finished_all_tasks = Async::Promise.new
94
+ @running_tasks = []
95
+ @tasks_channel_task = @root_task.async do |_t|
96
+ while ((msg, data) = @tasks_channel.wait)
97
+ case msg
98
+ when :started_task
99
+ @running_tasks.push(data)
100
+ data.run
101
+ when :resumed_task
102
+ @running_tasks.push(data)
103
+ when :finished_task, :paused_task
104
+ @running_tasks.delete(data)
105
+ has_pending_work = mode == :jobs ? @jobs.any? : @dataloader.pending_sources.any?(&:pending?) # rubocop:disable Development/NoneWithoutBlockCop
106
+ if @running_tasks.empty?
107
+ @no_running_tasks.resolve(true)
108
+ has_bandwidth = mode == :jobs ? jobs_bandwidth? : sources_bandwidth?
109
+ if (!has_pending_work) || (!has_bandwidth)
110
+ @finished_all_tasks.resolve(true)
41
111
  end
42
112
  end
43
- sources_condition.signal
44
- source_tasks.concat(next_source_tasks)
45
- next_source_tasks.clear
113
+ when :task_error
114
+ @no_running_tasks.resolve(true)
115
+ @finished_all_tasks.reject(data)
116
+ else
117
+ raise ArgumentError, "Unknown tasks_channel action: #{msg.inspect}"
46
118
  end
47
119
  end
120
+ end
121
+ end
48
122
 
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
123
+ def running?
124
+ @snoozed_jobs_condition.waiting? || @snoozed_sources_condition.waiting?
125
+ end
126
+
127
+ def current_sources_fiber_limit
128
+ within_limit = @total_fiber_limit - running_count
129
+ if within_limit < 1
130
+ 1
131
+ else
132
+ within_limit
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+ def running_count
139
+ @snoozed_jobs_condition.instance_variable_get(:@ready).num_waiting +
140
+ @snoozed_sources_condition.instance_variable_get(:@ready).num_waiting +
141
+ (@running_tasks&.size || 0)
142
+ end
143
+ end
144
+
145
+ def append_job(callable = nil, &block)
146
+ active_run.jobs.push(callable || block)
147
+ nil
148
+ end
149
+
150
+ def lazy_at_depth(depth, lazy)
151
+ active_run.lazies_at_depth[depth] << lazy
152
+ end
153
+
154
+ def active_run
155
+ @pending_run || Async::Task.current?&.graphql_async_dataloader_run || raise(GraphQL::Error, "No available Run to append to, GraphQL-Ruby bug")
156
+ end
157
+
158
+ def run_isolated
159
+ previous_run = Async::Task.current?&.graphql_async_dataloader_run
160
+ prev_pending_keys = {}
161
+ # Clear pending loads but keep already-cached records
162
+ # in case they are useful to the given block.
163
+ @source_cache.each do |source_class, batched_sources|
164
+ batched_sources.each do |batch_args, batched_source_instance|
165
+ if batched_source_instance.pending?
166
+ prev_pending_keys[batched_source_instance] = batched_source_instance.pending.dup
167
+ batched_source_instance.pending.clear
54
168
  end
55
169
  end
56
- trace&.end_dataloader(self)
57
170
  end
58
171
 
59
- manager.resume
60
- if manager.alive?
61
- raise "Invariant: Manager didn't terminate successfully: #{manager}"
172
+ res = nil
173
+ create_pending_run
174
+ @pending_run.jobs << -> { res = yield }
175
+ run
176
+ res
177
+ ensure
178
+ if previous_run
179
+ Async::Task.current.graphql_async_dataloader_run = previous_run
180
+ # clear the one created in #run:
181
+ @pending_run = nil
62
182
  end
183
+ prev_pending_keys.each do |source_instance, pending|
184
+ pending.each do |key, value|
185
+ next if source_instance.results.key?(key)
63
186
 
64
- rescue UncaughtThrowError => e
65
- throw e.tag, e.value
187
+ queue_pending_source(source_instance) if source_instance.pending.empty?
188
+ source_instance.pending[key] = value
189
+ end
190
+ end
66
191
  end
67
192
 
68
- private
193
+ def run(trace_query_lazy: nil)
194
+ trace = Fiber[:__graphql_current_multiplex]&.current_trace
195
+ run = @pending_run || Async::Task.current?&.graphql_async_dataloader_run || raise(GraphQL::Error, "No available Run, GraphQL-Ruby internal bug")
196
+ @pending_run = nil
197
+ run.trace = trace
198
+ first_pass = true
199
+ trace&.begin_dataloader(self)
200
+ fiber_vars = get_fiber_variables
201
+ raised_error = nil
202
+ jobs = run.jobs
203
+ Sync do |_maybe_new_task|
204
+ # Make sure there's a new task instance to hold `.graphql_...` state:
205
+ task = Async::Task.new do |root_task|
206
+ run.root_task = root_task
207
+ root_task.graphql_async_dataloader_run = run
208
+ set_fiber_variables(fiber_vars)
209
+
210
+ while first_pass || run.running? || !jobs.empty?
211
+ first_pass = false
212
+ run_queue(run, run.snoozed_jobs_condition, :jobs)
213
+ run_queue(run, run.snoozed_sources_condition, :sources)
69
214
 
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
215
+ if !run.lazies_at_depth.empty?
216
+ with_trace_query_lazy(trace_query_lazy) do
217
+ if enqueue_next_pending_lazies(run.lazies_at_depth)
218
+ run_queue(run, run.snoozed_jobs_condition, :jobs)
219
+ end
220
+ end
221
+ end
76
222
  end
223
+ rescue StandardError => err
224
+ raised_error = err
225
+ root_task.cancel
77
226
  end
227
+
228
+ task.run
229
+ task.wait
230
+ end
231
+ create_pending_run
232
+ if raised_error
233
+ raise raised_error
78
234
  end
79
- job_fibers.concat(next_job_fibers)
80
- next_job_fibers.clear
235
+ trace&.end_dataloader(self)
236
+ rescue UncaughtThrowError => e
237
+ throw e.tag, e.value
81
238
  end
82
239
 
83
- def spawn_source_task(parent_task, condition, trace)
84
- pending_sources = nil
85
- @source_cache.each_value do |source_by_batch_params|
86
- source_by_batch_params.each_value do |source|
87
- if source.pending?
88
- pending_sources ||= []
89
- pending_sources << source
240
+ private
241
+
242
+ def run_queue(run, condition, mode)
243
+ should_wait_for_all_tasks = false
244
+
245
+ if (unsnoozed = condition.waiting?)
246
+ should_wait_for_all_tasks = true
247
+ run.new_queues(mode)
248
+ condition.signal
249
+ end
250
+
251
+ while (pending_work = (mode == :jobs) ? (!run.jobs.empty? && run.jobs_bandwidth? ? run.jobs : nil) : (drain_pending_sources)) || unsnoozed
252
+ unsnoozed = false
253
+ if pending_work
254
+ if should_wait_for_all_tasks == false
255
+ should_wait_for_all_tasks = true
256
+ run.new_queues(mode)
257
+ end
258
+ num_tasks = mode == :sources ? run.current_sources_fiber_limit : 1
259
+ if num_tasks > pending_work.size
260
+ num_tasks = pending_work.size
90
261
  end
262
+ spawn_tasks(run, mode, condition, pending_work, num_tasks)
91
263
  end
264
+
265
+ run.wait_for_no_running_tasks
266
+ end
267
+
268
+ if should_wait_for_all_tasks
269
+ run.wait_for_queues
270
+ end
271
+ ensure
272
+ if should_wait_for_all_tasks
273
+ run.close_queues
92
274
  end
275
+ end
93
276
 
94
- if pending_sources
95
- fiber_vars = get_fiber_variables
96
- parent_task.async do
97
- trace&.dataloader_spawn_source_fiber(pending_sources)
277
+ # Use a separate method for this so that the outer loop's reassignment of `pending_work`
278
+ # doesn't affect already-running tasks which (would) close over that variable
279
+ def spawn_tasks(run, mode, condition, pending_work, num_tasks)
280
+ fiber_vars = get_fiber_variables
281
+ trace = run.trace
282
+ num_tasks.times do
283
+ new_task = Async::Task.new(run.root_task) do |task|
284
+ task.graphql_async_dataloader_run = run
285
+ task.graphql_async_dataloader_condition = condition
98
286
  set_fiber_variables(fiber_vars)
99
- Fiber[:graphql_dataloader_next_tick] = condition
100
- pending_sources.each do |s|
101
- trace&.begin_dataloader_source(s)
102
- s.run_pending_keys
103
- trace&.end_dataloader_source(s)
287
+ case mode
288
+ when :jobs
289
+ trace&.dataloader_spawn_execution_fiber(pending_work)
290
+ while job = pending_work.shift
291
+ job.call
292
+ end
293
+ when :sources
294
+ trace&.dataloader_spawn_source_fiber(pending_work)
295
+ while (source = pending_work.shift)
296
+ Fiber[:__graphql_current_dataloader_source] = source
297
+ trace&.begin_dataloader_source(source)
298
+ source.run_pending_keys
299
+ trace&.end_dataloader_source(source)
300
+ end
301
+ else
302
+ raise ArgumentError, "Unknown mode: #{mode.inspect}"
104
303
  end
304
+ nil
305
+ rescue StandardError => err
306
+ run.tasks_channel.push([:task_error, err])
307
+ else
308
+ run.tasks_channel.push([:finished_task, task])
309
+ ensure
105
310
  cleanup_fiber
106
311
  trace&.dataloader_fiber_exit
107
312
  end
313
+ run.tasks_channel.push([:started_task, new_task])
108
314
  end
109
315
  end
110
316
  end
@@ -20,9 +20,7 @@ module GraphQL
20
20
  # @return [Dataloader::Request] a pending request for a value from `key`. Call `.load` on that object to wait for the result.
21
21
  def request(value)
22
22
  res_key = result_key_for(value)
23
- if !@results.key?(res_key)
24
- @pending[res_key] ||= normalize_fetch_key(value)
25
- end
23
+ add_pending_key(res_key, value)
26
24
  Dataloader::Request.new(self, value)
27
25
  end
28
26
 
@@ -51,9 +49,7 @@ module GraphQL
51
49
  def request_all(values)
52
50
  values.each do |v|
53
51
  res_key = result_key_for(v)
54
- if !@results.key?(res_key)
55
- @pending[res_key] ||= normalize_fetch_key(v)
56
- end
52
+ add_pending_key(res_key, v)
57
53
  end
58
54
  Dataloader::RequestAll.new(self, values)
59
55
  end
@@ -65,7 +61,7 @@ module GraphQL
65
61
  if @results.key?(result_key)
66
62
  result_for(result_key)
67
63
  else
68
- @pending[result_key] ||= normalize_fetch_key(value)
64
+ add_pending_key(result_key, value)
69
65
  sync([result_key])
70
66
  result_for(result_key)
71
67
  end
@@ -79,8 +75,7 @@ module GraphQL
79
75
  values.each { |v|
80
76
  k = result_key_for(v)
81
77
  result_keys << k
82
- if !@results.key?(k)
83
- @pending[k] ||= normalize_fetch_key(v)
78
+ if add_pending_key(k, v)
84
79
  pending_keys << k
85
80
  end
86
81
  }
@@ -89,7 +84,8 @@ module GraphQL
89
84
  sync(pending_keys)
90
85
  end
91
86
 
92
- result_keys.map { |k| result_for(k) }
87
+ result_keys.map! { |k| result_for(k) }
88
+ result_keys
93
89
  end
94
90
 
95
91
  # Subclasses must implement this method to return a value for each of `keys`
@@ -105,6 +101,7 @@ module GraphQL
105
101
  # Then run the batch and update the cache.
106
102
  # @return [void]
107
103
  def sync(pending_result_keys)
104
+ @dataloader.queue_pending_source(self) if pending?
108
105
  @dataloader.yield(self)
109
106
  iterations = 0
110
107
  while pending_result_keys.any? { |key| !@results.key?(key) }
@@ -138,22 +135,25 @@ module GraphQL
138
135
  # @api private
139
136
  # @return [void]
140
137
  def run_pending_keys
141
- if !@fetching.empty?
142
- @fetching.each_key { |k| @pending.delete(k) }
143
- end
138
+ @fetching.each_key { |k| @pending.delete(k) }
144
139
  return if @pending.empty?
145
140
  fetch_h = @pending
146
- @pending = {}
147
141
  @fetching.merge!(fetch_h)
142
+ @pending = {}
148
143
  results = fetch(fetch_h.values)
149
- fetch_h.each_with_index do |(key, _value), idx|
144
+ idx = 0
145
+
146
+ fetch_h.each_key do |key|
150
147
  @results[key] = results[idx]
148
+ @fetching.delete(key)
149
+ idx += 1
151
150
  end
152
151
  nil
153
152
  rescue StandardError => error
154
- fetch_h.each_key { |key| @results[key] = error }
155
- ensure
156
- fetch_h && fetch_h.each_key { |k| @fetching.delete(k) }
153
+ fetch_h.each_key { |key|
154
+ @results[key] = error
155
+ @fetching.delete(key)
156
+ }
157
157
  end
158
158
 
159
159
  # These arguments are given to `dataloader.with(source_class, ...)`. The object
@@ -171,7 +171,11 @@ module GraphQL
171
171
  # @param batch_kwargs [Hash]
172
172
  # @return [Object]
173
173
  def self.batch_key_for(*batch_args, **batch_kwargs)
174
- [*batch_args, **batch_kwargs]
174
+ if batch_kwargs.any? # rubocop:disable Development/NoneWithoutBlockCop
175
+ [*batch_args, **batch_kwargs]
176
+ else
177
+ batch_args
178
+ end
175
179
  end
176
180
 
177
181
  # Clear any already-loaded objects for this source
@@ -185,6 +189,15 @@ module GraphQL
185
189
 
186
190
  private
187
191
 
192
+ def add_pending_key(result_key, value)
193
+ return false if @results.key?(result_key)
194
+
195
+ was_empty = @pending.empty?
196
+ @pending[result_key] ||= normalize_fetch_key(value)
197
+ @dataloader.queue_pending_source(self) if was_empty
198
+ true
199
+ end
200
+
188
201
  # Reads and returns the result for the key from the internal cache, or raises an error if the result was an error
189
202
  # @param key [Object] key passed to {#load} or {#load_all}
190
203
  # @return [Object] The result from {#fetch} for `key`.
@@ -58,7 +58,9 @@ module GraphQL
58
58
  end
59
59
 
60
60
  def initialize(nonblocking: self.class.default_nonblocking, fiber_limit: self.class.default_fiber_limit)
61
- @source_cache = Hash.new { |h, k| h[k] = {} }
61
+ @source_cache = Hash.new { |h, k| h[k] = {} }.compare_by_identity
62
+ @pending_source_set = Set.new.compare_by_identity
63
+ @pending_sources = []
62
64
  @pending_jobs = []
63
65
  if !nonblocking.nil?
64
66
  @nonblocking = nonblocking
@@ -108,7 +110,7 @@ module GraphQL
108
110
  # @param batch_parameters [Array<Object>]
109
111
  # @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`,
110
112
  # and cached for the lifetime of this {Multiplex}.
111
- if (RUBY_ENGINE == "ruby" && RUBY_ENGINE < "3") || RUBY_ENGINE == "truffleruby" # truffle-ruby wasn't doing well with the implementation below
113
+ if (RUBY_ENGINE == "ruby" && RUBY_VERSION < "3") || RUBY_ENGINE == "truffleruby" # truffle-ruby wasn't doing well with the implementation below
112
114
  def with(source_class, *batch_args)
113
115
  batch_key = source_class.batch_key_for(*batch_args)
114
116
  @source_cache[source_class][batch_key] ||= begin
@@ -148,6 +150,14 @@ module GraphQL
148
150
  nil
149
151
  end
150
152
 
153
+ # @api private
154
+ def queue_pending_source(source)
155
+ if @pending_source_set.add?(source)
156
+ @pending_sources << source
157
+ end
158
+ nil
159
+ end
160
+
151
161
  # Clear any already-loaded objects from {Source} caches
152
162
  # @return [void]
153
163
  def clear_cache
@@ -187,9 +197,10 @@ module GraphQL
187
197
  @lazies_at_depth = prev_lazies_at_depth
188
198
  prev_pending_keys.each do |source_instance, pending|
189
199
  pending.each do |key, value|
190
- if !source_instance.results.key?(key)
191
- source_instance.pending[key] = value
192
- end
200
+ next if source_instance.results.key?(key)
201
+
202
+ queue_pending_source(source_instance) if source_instance.pending.empty?
203
+ source_instance.pending[key] = value
193
204
  end
194
205
  end
195
206
  end
@@ -212,8 +223,10 @@ module GraphQL
212
223
 
213
224
  if !@lazies_at_depth.empty?
214
225
  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)
226
+ if enqueue_next_pending_lazies(@lazies_at_depth)
227
+ job_fibers.unshift(spawn_job_fiber(trace))
228
+ run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
229
+ end
217
230
  end
218
231
  end
219
232
  end
@@ -274,24 +287,19 @@ module GraphQL
274
287
 
275
288
  private
276
289
 
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
290
+ # Returns true if anything was actually enqueued
291
+ def enqueue_next_pending_lazies(lazies_at_depth)
292
+ smallest_depth = lazies_at_depth.each_key.min
293
+ return false if smallest_depth.nil?
285
294
 
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
295
+ lazies = lazies_at_depth.delete(smallest_depth)
296
+ return false if lazies.empty?
297
+
298
+ lazies.each do |lazy|
299
+ append_job { lazy.value }
294
300
  end
301
+
302
+ true
295
303
  end
296
304
 
297
305
  def run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
@@ -305,7 +313,7 @@ module GraphQL
305
313
  end
306
314
  join_queues(job_fibers, next_job_fibers)
307
315
 
308
- while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
316
+ while (!source_fibers.empty? || !@pending_sources.empty?)
309
317
  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
318
  if f.alive?
311
319
  finished = run_fiber(f)
@@ -356,21 +364,29 @@ module GraphQL
356
364
  end
357
365
  end
358
366
 
359
- def spawn_source_fiber(trace)
360
- pending_sources = nil
361
- @source_cache.each_value do |source_by_batch_params|
362
- source_by_batch_params.each_value do |source|
363
- if source.pending?
364
- pending_sources ||= []
365
- pending_sources << source
366
- end
367
- end
367
+ def drain_pending_sources
368
+ pending_sources = @pending_sources
369
+ @pending_sources = []
370
+ @pending_source_set.clear
371
+
372
+ pending_sources.select!(&:pending?)
373
+ pending_sources.empty? ? nil : pending_sources
374
+ end
375
+
376
+ def dequeue_pending_source
377
+ while (source = @pending_sources.shift)
378
+ @pending_source_set.delete(source)
379
+ return source if source.pending?
368
380
  end
381
+ end
369
382
 
370
- if pending_sources
383
+ def spawn_source_fiber(trace)
384
+ if !@pending_sources.empty?
371
385
  spawn_fiber do
372
- trace&.dataloader_spawn_source_fiber(pending_sources)
373
- pending_sources.each do |source|
386
+ trace&.dataloader_spawn_source_fiber(@pending_sources)
387
+ # This will find sources which were enqueued during `#fetch`:
388
+ while (source = dequeue_pending_source)
389
+ next if !source.pending?
374
390
  Fiber[:__graphql_current_dataloader_source] = source
375
391
  trace&.begin_dataloader_source(source)
376
392
  source.run_pending_keys
@@ -49,11 +49,12 @@ module GraphQL
49
49
  end
50
50
 
51
51
  def value
52
+ return nil if @selections_step.killed
52
53
  query = @selections_step.query
53
54
  set_current_field
54
- query.current_trace.begin_execute_field(@field_definition, @arguments, @field_results, query)
55
+ query.current_trace.begin_execute_field(@field_definition, @field_results, @arguments, query)
55
56
  sync(@field_results)
56
- query.current_trace.end_execute_field(@field_definition, @arguments, @field_results, query, @field_results)
57
+ query.current_trace.end_execute_field(@field_definition, @field_results, @arguments, query, @field_results)
57
58
  @runner.add_step(self)
58
59
  true
59
60
  ensure
@@ -79,7 +80,9 @@ module GraphQL
79
80
  end
80
81
 
81
82
  def call
83
+ return nil if @selections_step.killed
82
84
  set_current_field if @field_definition
85
+
83
86
  if @enqueued_authorization
84
87
  enqueue_next_steps
85
88
  elsif @finish_extension_idx
@@ -102,24 +105,64 @@ module GraphQL
102
105
  set_current_field(nil)
103
106
  end
104
107
 
105
- def add_graphql_error(err)
108
+ def add_graphql_error(result, key, err, return_type: @field_definition.type)
106
109
  err.path = path
107
110
  if err.ast_node.nil?
108
111
  err.ast_nodes = ast_nodes
109
112
  end
110
- @selections_step.query.context.add_error(err)
113
+ @runner.add_finalizer(@selections_step.query, result, key, err)
114
+ if !err.is_a?(GraphQL::Execution::Skip)
115
+ field_type = return_type
116
+ should_propagate_null = field_type.non_null?
117
+ while (should_propagate_null == false && field_type.kind.wraps?)
118
+ field_type = field_type.of_type
119
+ should_propagate_null = field_type.non_null?
120
+ end
121
+ if should_propagate_null
122
+ propagate_nulls
123
+ end
124
+ end
111
125
  err
112
126
  end
113
127
 
128
+ def propagate_nulls
129
+ propagating_null = true
130
+ highest_nulled_depth = path.size
131
+ highest_list_depth = nil
132
+ current_field_step = self
133
+ while current_field_step
134
+ return_type = current_field_step.field_definition.type
135
+ if propagating_null && return_type.non_null?
136
+ highest_nulled_depth = current_field_step.path.size
137
+ else
138
+ propagating_null = false
139
+ end
140
+
141
+ if return_type.list?
142
+ highest_list_depth = current_field_step.path.size
143
+ end
144
+
145
+ current_field_step = current_field_step.selections_step.field_resolve_step
146
+ end
147
+
148
+ if highest_list_depth.nil? || highest_nulled_depth <= highest_list_depth
149
+ kill_field_step = self
150
+ while kill_field_step && highest_nulled_depth <= kill_field_step.path.size
151
+ kill_field_step.selections_step.killed = true
152
+ kill_field_step = kill_field_step.selections_step.field_resolve_step
153
+ end
154
+ end
155
+ end
156
+
114
157
  def build_errors_result(errors, single_error)
115
158
  first_error = errors.nil? ? single_error : errors.pop
116
- @field_results = error_instance_array(@selections_step.objects.size, first_error)
159
+ @field_results = [first_error]
160
+ @results = [@selections_step.results.first]
117
161
  if errors
118
162
  errors.each do |e|
119
- add_graphql_error(e)
163
+ add_graphql_error(@results.first, key, e)
120
164
  end
121
165
  end
122
- @results ||= @selections_step.results
123
166
  build_results
124
167
  end
125
168
 
@@ -226,7 +269,7 @@ module GraphQL
226
269
  authorized_results << @results[i]
227
270
  end
228
271
  rescue GraphQL::ExecutionError => exec_err
229
- add_graphql_error(exec_err)
272
+ add_graphql_error(@results[i], key, exec_err)
230
273
  end
231
274
  end
232
275
  i += 1
@@ -244,7 +287,7 @@ module GraphQL
244
287
  @was_scoped = true
245
288
  end
246
289
 
247
- query.current_trace.begin_execute_field(@field_definition, @arguments, authorized_objects, query)
290
+ query.current_trace.begin_execute_field(@field_definition, authorized_objects, @arguments, query)
248
291
 
249
292
  if @runner.uses_runtime_directives
250
293
  if @ast_nodes.nil? || @ast_nodes.size == 1
@@ -319,7 +362,7 @@ module GraphQL
319
362
  @field_results = resolve_batch(authorized_objects, ctx, @arguments)
320
363
  end
321
364
 
322
- query.current_trace.end_execute_field(@field_definition, @arguments, authorized_objects, query, @field_results)
365
+ query.current_trace.end_execute_field(@field_definition, authorized_objects, @arguments, query, @field_results)
323
366
 
324
367
  if any_lazy_results?
325
368
  @runner.dataloader.lazy_at_depth(path.size, self)
@@ -331,12 +374,12 @@ module GraphQL
331
374
  end
332
375
  end
333
376
  rescue GraphQL::ExecutionError => err
334
- add_graphql_error(err)
377
+ build_errors_result(nil, err)
335
378
  rescue StandardError => stderr
336
379
  begin
337
380
  @selections_step.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: nil)
338
381
  rescue GraphQL::ExecutionError => err
339
- add_graphql_error(err)
382
+ add_graphql_error(@results[0], key, err)
340
383
  end
341
384
  end
342
385
 
@@ -458,13 +501,13 @@ module GraphQL
458
501
  end
459
502
 
460
503
  def finish_leaf_result(result_h, key, field_result, return_type, ctx)
461
- final_field_result = build_leaf_result(field_result, return_type, ctx, false)
504
+ final_field_result = build_leaf_result(result_h, key, field_result, return_type, ctx, false)
462
505
 
463
506
  @directive_finalizers&.each { |f| @runner.add_finalizer(ctx.query, result_h, key, f) }
464
507
  result_h[@key] = final_field_result
465
508
  end
466
509
 
467
- def build_leaf_result(field_result, return_type, ctx, is_from_array)
510
+ def build_leaf_result(result_h, result_key, field_result, return_type, ctx, is_from_array)
468
511
  if field_result.nil?
469
512
  if return_type.non_null?
470
513
  add_non_null_error(is_from_array)
@@ -473,7 +516,7 @@ module GraphQL
473
516
  end
474
517
  elsif field_result.is_a?(Finalizer)
475
518
  if field_result.is_a?(GraphQL::RuntimeError)
476
- add_graphql_error(field_result)
519
+ add_graphql_error(result_h, result_key, field_result, return_type: return_type)
477
520
  else
478
521
  field_result.path = path
479
522
  @runner.add_finalizer(ctx.query, result_h, key, field_result)
@@ -484,7 +527,11 @@ module GraphQL
484
527
  end
485
528
 
486
529
  inner_type = return_type.of_type
487
- field_result.map { |item| build_leaf_result(item, inner_type, ctx, true) }
530
+ result_a = Array.new(field_result.size)
531
+ field_result.each_with_index do |item, idx|
532
+ result_a[idx] = build_leaf_result(result_a, idx, item, inner_type, ctx, true)
533
+ end
534
+ result_a
488
535
  else
489
536
  return_type.coerce_result(field_result, ctx)
490
537
  end
@@ -526,6 +573,7 @@ module GraphQL
526
573
  query.current_trace.objects(obj_type, next_objects, ctx)
527
574
  @runner.add_step(SelectionsStep.new(
528
575
  path: path,
576
+ field_resolve_step: self,
529
577
  parent_type: obj_type,
530
578
  selections: @next_selections,
531
579
  objects: next_objects,
@@ -538,6 +586,7 @@ module GraphQL
538
586
  query.current_trace.objects(@static_type, @all_next_objects, ctx)
539
587
  @runner.add_step(SelectionsStep.new(
540
588
  path: path,
589
+ field_resolve_step: self,
541
590
  parent_type: @static_type,
542
591
  selections: @next_selections,
543
592
  objects: @all_next_objects,
@@ -558,7 +607,11 @@ module GraphQL
558
607
 
559
608
  def add_non_null_error(is_from_array)
560
609
  err = @parent_type::InvalidNullError.new(@parent_type, @field_definition, ast_nodes, is_from_array: is_from_array, path: path)
561
- @runner.schema.type_error(err, @selections_step.query.context)
610
+ nn_result = @runner.schema.type_error(err, @selections_step.query.context)
611
+ if nn_result.nil?
612
+ propagate_nulls
613
+ end
614
+ nn_result
562
615
  end
563
616
 
564
617
  def set_current_field(new_value = @field_definition)
@@ -576,7 +629,7 @@ module GraphQL
576
629
  end
577
630
  elsif field_result.is_a?(Finalizer)
578
631
  graphql_result[key] = if field_result.is_a?(GraphQL::RuntimeError)
579
- add_graphql_error(field_result)
632
+ add_graphql_error(graphql_result, key, field_result)
580
633
  else
581
634
  field_result.path = path
582
635
  @runner.add_finalizer(@selections_step.query, graphql_result, key, field_result)
@@ -33,14 +33,15 @@ module GraphQL
33
33
 
34
34
  targets.each_with_index do |target, idx|
35
35
  if target.is_a?(Hash)
36
- if target[key].equal?(err)
36
+ value_at_key = target[key]
37
+ if value_at_key.equal?(err)
37
38
  tf = @finalizers[target] ||= {}.compare_by_identity
38
39
  tf[key] = err
39
40
  @finalizers_count += 1
40
- elsif (arr = target[key]).is_a?(Array)
41
- arr.each_with_index do |el, idx|
41
+ elsif value_at_key.is_a?(Array)
42
+ value_at_key.each_with_index do |el, idx|
42
43
  if el.equal?(err)
43
- tf = @finalizers[arr] ||= {}.compare_by_identity
44
+ tf = @finalizers[value_at_key] ||= {}.compare_by_identity
44
45
  tf[idx] = err
45
46
  @finalizers_count += 1
46
47
  end
@@ -150,7 +151,7 @@ module GraphQL
150
151
  when Language::Nodes::InlineFragment
151
152
  static_type_at_result = @static_type_at[result_h]
152
153
  if static_type_at_result && (
153
- (t = ast_selection.type).nil? ||
154
+ (t = ast_selection.type).nil? ||
154
155
  @runner.type_condition_applies?(@query.context, static_type_at_result, t.name)
155
156
  )
156
157
  result_h = check_object_result(result_h, parent_type, ast_selection.selections)
@@ -170,9 +171,7 @@ module GraphQL
170
171
  end
171
172
 
172
173
  def check_list_result(result_arr, inner_type, ast_selections)
173
- inner_type_non_null = false
174
- if inner_type.non_null?
175
- inner_type_non_null = true
174
+ if (inner_type_non_null = inner_type.non_null?)
176
175
  inner_type = inner_type.of_type
177
176
  end
178
177
 
@@ -492,12 +492,6 @@ module GraphQL
492
492
  end
493
493
  end
494
494
  end
495
- # If this field is a root mutation field, immediately resolve
496
- # all of its child fields before moving on to the next root mutation field.
497
- # (Subselections of this mutation will still be resolved level-by-level.)
498
- if selection_result.graphql_is_eager
499
- @dataloader.run
500
- end
501
495
  end
502
496
 
503
497
  def set_result(selection_result, result_name, value, is_child_result, is_non_null)
@@ -771,8 +765,7 @@ module GraphQL
771
765
 
772
766
  response_list
773
767
  rescue NoMethodError => err
774
- # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
775
- if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
768
+ if err.name == :each && err.receiver == value
776
769
  # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
777
770
  raise ListResultFailedError.new(value: value, field: field, path: current_path)
778
771
  else
@@ -73,6 +73,10 @@ module GraphQL
73
73
  @field_resolve_step.set_current_field(nil)
74
74
  end
75
75
 
76
+ def add_field_error(err)
77
+ @field_resolve_step.add_graphql_error(@graphql_result, @key, err)
78
+ end
79
+
76
80
  def authorize
77
81
  if @field_resolve_step.was_scoped && !@resolved_type.reauthorize_scoped_objects
78
82
  @authorized_value = @object
@@ -96,13 +100,13 @@ module GraphQL
96
100
  create_result
97
101
  end
98
102
  rescue GraphQL::RuntimeError => err
99
- @graphql_result[@key] = @field_resolve_step.add_graphql_error(err)
103
+ @graphql_result[@key] = add_field_error(err)
100
104
  rescue StandardError => err
101
105
  query ||= @field_resolve_step.selections_step.query
102
106
  begin
103
107
  query.handle_or_reraise(err, field: @field_resolve_step.field_definition, arguments: @field_resolve_step.arguments, object: @object) # rubocop:disable Development/ContextIsPassedCop
104
108
  rescue GraphQL::RuntimeError => err
105
- @graphql_result[@key] = @field_resolve_step.add_graphql_error(err)
109
+ @graphql_result[@key] = add_field_error(err)
106
110
  end
107
111
  end
108
112
 
@@ -120,13 +124,13 @@ module GraphQL
120
124
  elsif @is_non_null
121
125
  @graphql_result[@key] = @field_resolve_step.add_non_null_error(@is_from_array)
122
126
  else
123
- @graphql_result[@key] = @field_resolve_step.add_graphql_error(@authorization_error)
127
+ @graphql_result[@key] = add_field_error(@authorization_error)
124
128
  end
125
129
  rescue GraphQL::RuntimeError => err
126
130
  if @is_non_null
127
131
  @graphql_result[@key] = @field_resolve_step.add_non_null_error(@is_from_array)
128
132
  else
129
- @graphql_result[@key] = @field_resolve_step.add_graphql_error(err)
133
+ @graphql_result[@key] = add_field_error(err)
130
134
  end
131
135
  end
132
136
  end
@@ -281,8 +281,8 @@ module GraphQL
281
281
  end
282
282
  result = dir_defn.resolve_operation(selected_operation, query, objects, dir_args, query.context)
283
283
  if result.is_a?(Finalizer)
284
- result.path = path
285
- add_finalizer(query, result, nil, data)
284
+ result.path = beginning_path
285
+ add_finalizer(query, data, nil, result)
286
286
  if result.is_a?(HaltExecution)
287
287
  continue_execution = false
288
288
  break
@@ -298,6 +298,7 @@ module GraphQL
298
298
  if query.query?
299
299
  isolated_steps[0] << SelectionsStep.new(
300
300
  parent_type: root_type,
301
+ field_resolve_step: nil,
301
302
  selections: selected_operation.selections,
302
303
  objects: objects,
303
304
  results: [data],
@@ -317,6 +318,7 @@ module GraphQL
317
318
  isolated_steps << [SelectionsStep.new(
318
319
  clobber: false, # `data` is being shared among several selections steps
319
320
  parent_type: root_type,
321
+ field_resolve_step: field_resolve_step,
320
322
  selections: field_resolve_step.ast_nodes || Array(field_resolve_step.ast_node),
321
323
  objects: objects,
322
324
  results: [data],
@@ -332,6 +334,7 @@ module GraphQL
332
334
  end
333
335
  isolated_steps[0] << SelectionsStep.new(
334
336
  parent_type: root_type,
337
+ field_resolve_step: nil,
335
338
  selections: selected_operation.selections,
336
339
  objects: objects,
337
340
  results: [data],
@@ -355,6 +358,7 @@ module GraphQL
355
358
  results << { "data" => data }
356
359
  isolated_steps[0] << SelectionsStep.new(
357
360
  parent_type: resolved_type,
361
+ field_resolve_step: nil,
358
362
  selections: selected_operation.selections,
359
363
  objects: objects,
360
364
  results: [data],
@@ -372,6 +376,7 @@ module GraphQL
372
376
  results << { "data" => list_result }
373
377
  isolated_steps[0] << SelectionsStep.new(
374
378
  parent_type: inner_type,
379
+ field_resolve_step: nil,
375
380
  selections: selected_operation.selections,
376
381
  objects: root_value,
377
382
  results: list_result,
@@ -420,6 +425,7 @@ module GraphQL
420
425
  selections = partial.ast_nodes
421
426
  dummy_ss = SelectionsStep.new(
422
427
  parent_type: nil,
428
+ field_resolve_step: nil,
423
429
  selections: selections,
424
430
  objects: nil,
425
431
  results: nil,
@@ -2,8 +2,9 @@
2
2
  module GraphQL
3
3
  module Execution
4
4
  class SelectionsStep
5
- def initialize(parent_type:, selections:, objects:, results:, runner:, query:, path:, clobber: true)
5
+ def initialize(parent_type:, field_resolve_step:, selections:, objects:, results:, runner:, query:, path:, clobber: true)
6
6
  @path = path
7
+ @field_resolve_step = field_resolve_step
7
8
  @parent_type = parent_type
8
9
  @selections = selections
9
10
  @runner = runner
@@ -12,10 +13,13 @@ module GraphQL
12
13
  @query = query
13
14
  @graphql_objects = nil
14
15
  @all_selections = nil
16
+ @killed = false
15
17
  @clobber = clobber
16
18
  end
17
19
 
18
- attr_reader :path, :query, :objects, :results
20
+ attr_reader :path, :query, :objects, :results, :field_resolve_step
21
+
22
+ attr_accessor :killed
19
23
 
20
24
  def graphql_objects
21
25
  @graphql_objects ||= @objects.map do |obj|
@@ -26,6 +26,10 @@ module GraphQL
26
26
  end
27
27
 
28
28
  def finalize_graphql_result(query, result_data, key)
29
+ add_this_error = !query.context.errors.any? { |e| e.eql?(self) }
30
+ if add_this_error
31
+ query.context.add_error(self)
32
+ end
29
33
  if ast_node.is_a?(GraphQL::Language::Nodes::Directive)
30
34
  # This is for backwards compatibility ... what does the spec say?
31
35
  result_data.delete(key)
@@ -19,7 +19,7 @@ module GraphQL
19
19
  @storage = ast_variables.each_with_object({}) do |ast_variable, memo|
20
20
  if schema.validate_max_errors && schema.validate_max_errors <= @errors.count
21
21
  add_max_errors_reached_message
22
- break
22
+ break memo
23
23
  end
24
24
  # Find the right value for this variable:
25
25
  # - First, use the value provided at runtime
@@ -168,6 +168,9 @@ module GraphQL
168
168
  # Let validation handle this
169
169
  value
170
170
  end
171
+ elsif arg_defn.default_value?
172
+ value = arg_defn.default_value
173
+ graphql_value = arg_type.coerce_isolated_result(value) unless value.nil?
171
174
  else
172
175
  value = graphql_value = nil
173
176
  end
@@ -83,7 +83,7 @@ module GraphQL
83
83
  is_authed, new_return_value = authorized?(**@prepared_arguments)
84
84
  rescue GraphQL::UnauthorizedError => err
85
85
  new_return_value = q.schema.unauthorized_object(err)
86
- is_authed = true # the error was handled
86
+ is_authed = false
87
87
  end
88
88
  end
89
89
 
@@ -25,6 +25,10 @@ module GraphQL
25
25
  def ==(other)
26
26
  self.class == other.class && of_type == other.of_type
27
27
  end
28
+
29
+ def deconstruct_keys(_keys)
30
+ { of_type: of_type }
31
+ end
28
32
  end
29
33
  end
30
34
  end
@@ -665,8 +665,8 @@ module GraphQL
665
665
  inherited_um = find_inherited_value(:union_memberships, EMPTY_HASH).fetch(type.graphql_name, EMPTY_ARRAY)
666
666
  own_um + inherited_um
667
667
  else
668
- joined_um = own_union_memberships.dup
669
- find_inherited_value(:union_memberhips, EMPTY_HASH).each do |k, v|
668
+ joined_um = own_union_memberships.transform_values(&:dup)
669
+ find_inherited_value(:union_memberships, EMPTY_HASH).each do |k, v|
670
670
  um = joined_um[k] ||= []
671
671
  um.concat(v)
672
672
  end
@@ -860,7 +860,7 @@ module GraphQL
860
860
  # @return [Array<GraphQL::StaticValidation::Error >]
861
861
  def validate(string_or_document, rules: nil, context: nil)
862
862
  doc = if string_or_document.is_a?(String)
863
- GraphQL.parse(string_or_document)
863
+ GraphQL.parse(string_or_document, max_tokens: max_query_string_tokens)
864
864
  else
865
865
  string_or_document
866
866
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.6.3"
3
+ VERSION = "2.6.6"
4
4
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.3
4
+ version: 2.6.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-05-26 00:00:00.000000000 Z
10
+ date: 2026-07-21 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64
@@ -261,34 +261,6 @@ dependencies:
261
261
  - - ">="
262
262
  - !ruby/object:Gem::Version
263
263
  version: '0'
264
- - !ruby/object:Gem::Dependency
265
- name: jekyll
266
- requirement: !ruby/object:Gem::Requirement
267
- requirements:
268
- - - ">="
269
- - !ruby/object:Gem::Version
270
- version: '0'
271
- type: :development
272
- prerelease: false
273
- version_requirements: !ruby/object:Gem::Requirement
274
- requirements:
275
- - - ">="
276
- - !ruby/object:Gem::Version
277
- version: '0'
278
- - !ruby/object:Gem::Dependency
279
- name: jekyll-sass-converter
280
- requirement: !ruby/object:Gem::Requirement
281
- requirements:
282
- - - "~>"
283
- - !ruby/object:Gem::Version
284
- version: '2.2'
285
- type: :development
286
- prerelease: false
287
- version_requirements: !ruby/object:Gem::Requirement
288
- requirements:
289
- - - "~>"
290
- - !ruby/object:Gem::Version
291
- version: '2.2'
292
264
  - !ruby/object:Gem::Dependency
293
265
  name: yard
294
266
  requirement: !ruby/object:Gem::Requirement
@@ -303,34 +275,6 @@ dependencies:
303
275
  - - ">="
304
276
  - !ruby/object:Gem::Version
305
277
  version: '0'
306
- - !ruby/object:Gem::Dependency
307
- name: jekyll-algolia
308
- requirement: !ruby/object:Gem::Requirement
309
- requirements:
310
- - - ">="
311
- - !ruby/object:Gem::Version
312
- version: '0'
313
- type: :development
314
- prerelease: false
315
- version_requirements: !ruby/object:Gem::Requirement
316
- requirements:
317
- - - ">="
318
- - !ruby/object:Gem::Version
319
- version: '0'
320
- - !ruby/object:Gem::Dependency
321
- name: jekyll-redirect-from
322
- requirement: !ruby/object:Gem::Requirement
323
- requirements:
324
- - - ">="
325
- - !ruby/object:Gem::Version
326
- version: '0'
327
- type: :development
328
- prerelease: false
329
- version_requirements: !ruby/object:Gem::Requirement
330
- requirements:
331
- - - ">="
332
- - !ruby/object:Gem::Version
333
- version: '0'
334
278
  - !ruby/object:Gem::Dependency
335
279
  name: m
336
280
  requirement: !ruby/object:Gem::Requirement