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 +4 -4
- data/lib/generators/graphql/field_extractor.rb +17 -1
- data/lib/graphql/analysis/query_complexity.rb +2 -2
- data/lib/graphql/dataloader/async_dataloader.rb +275 -69
- data/lib/graphql/dataloader/source.rb +32 -19
- data/lib/graphql/dataloader.rb +52 -36
- data/lib/graphql/execution/field_resolve_step.rb +71 -18
- data/lib/graphql/execution/finalize.rb +7 -8
- data/lib/graphql/execution/interpreter/runtime.rb +1 -8
- data/lib/graphql/execution/prepare_object_step.rb +8 -4
- data/lib/graphql/execution/runner.rb +8 -2
- data/lib/graphql/execution/selections_step.rb +6 -2
- data/lib/graphql/execution_error.rb +4 -0
- data/lib/graphql/query/variables.rb +1 -1
- data/lib/graphql/schema/directive.rb +3 -0
- data/lib/graphql/schema/resolver.rb +1 -1
- data/lib/graphql/schema/wrapper.rb +4 -0
- data/lib/graphql/schema.rb +3 -3
- data/lib/graphql/version.rb +1 -1
- metadata +2 -58
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3366525e188ac8b81b4dc7bdc8e34bb1839153fbec23d8e2a5faf07c02b56aa6
|
|
4
|
+
data.tar.gz: 414d8f17cee8d40adf8c5923427e83d187efdbd08871c7b9d8bbd4fa13b0e550
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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 <<~
|
|
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
|
-
|
|
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
|
-
|
|
31
|
+
task = Async::Task.current
|
|
32
|
+
run = task.graphql_async_dataloader_run
|
|
33
|
+
trace = run.trace
|
|
7
34
|
trace&.dataloader_fiber_yield(source)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
80
|
-
|
|
235
|
+
trace&.end_dataloader(self)
|
|
236
|
+
rescue UncaughtThrowError => e
|
|
237
|
+
throw e.tag, e.value
|
|
81
238
|
end
|
|
82
239
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
trace&.
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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|
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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`.
|
data/lib/graphql/dataloader.rb
CHANGED
|
@@ -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" &&
|
|
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
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
216
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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? ||
|
|
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
|
|
360
|
-
pending_sources =
|
|
361
|
-
@
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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, @
|
|
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, @
|
|
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
|
|
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 =
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
41
|
-
|
|
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[
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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] =
|
|
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] =
|
|
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] =
|
|
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] =
|
|
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 =
|
|
285
|
-
add_finalizer(query,
|
|
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 =
|
|
86
|
+
is_authed = false
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
|
data/lib/graphql/schema.rb
CHANGED
|
@@ -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(:
|
|
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
|
data/lib/graphql/version.rb
CHANGED
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.
|
|
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-
|
|
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
|