graphql 2.4.9 → 2.4.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql/current.rb +5 -0
- data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
- data/lib/graphql/dataloader/active_record_source.rb +26 -0
- data/lib/graphql/dataloader/async_dataloader.rb +17 -5
- data/lib/graphql/dataloader/null_dataloader.rb +1 -1
- data/lib/graphql/dataloader/source.rb +2 -2
- data/lib/graphql/dataloader.rb +37 -5
- data/lib/graphql/execution/interpreter/runtime.rb +25 -6
- data/lib/graphql/execution/interpreter.rb +9 -1
- data/lib/graphql/language/parser.rb +1 -1
- data/lib/graphql/query.rb +8 -4
- data/lib/graphql/schema/enum.rb +1 -1
- data/lib/graphql/schema/interface.rb +1 -0
- data/lib/graphql/schema/loader.rb +1 -0
- data/lib/graphql/schema/member/has_dataloader.rb +56 -0
- data/lib/graphql/schema/member.rb +1 -0
- data/lib/graphql/schema/object.rb +17 -8
- data/lib/graphql/schema/resolver.rb +1 -5
- data/lib/graphql/schema/visibility/profile.rb +4 -4
- data/lib/graphql/schema/visibility.rb +14 -9
- data/lib/graphql/schema.rb +4 -4
- data/lib/graphql/static_validation/validator.rb +6 -1
- data/lib/graphql/tracing/appoptics_trace.rb +1 -1
- data/lib/graphql/tracing/new_relic_trace.rb +138 -41
- data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
- data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
- data/lib/graphql/tracing/perfetto_trace.rb +726 -0
- data/lib/graphql/tracing/trace.rb +124 -0
- data/lib/graphql/tracing.rb +1 -0
- data/lib/graphql/version.rb +1 -1
- metadata +36 -3
- data/lib/graphql/schema/null_mask.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e8174a7314a4dc73aa720915044a1b068a39eccb981ca7553b7f894b60e94bdb
|
4
|
+
data.tar.gz: 2bfb0e581036b944d2bd0f42b9ae580d990a848506b6ea20c9192c71c46e009d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5277ca8f41431b3854bbf85e64aa6c045701c803c18c927d21e2a89cdc2130a1c6bcb7c0e166fb3c7fcbdaac553f1bbf83bc00043ce8efd27b93dd8810b206a3
|
7
|
+
data.tar.gz: 0564e407251fad40e9a3cec275a172980407b97659a8db85cb177ab2b03c5244b320661096eb6e49b3df87897b39c66f727312bb310516341d962013d5899d02
|
data/lib/graphql/current.rb
CHANGED
@@ -48,5 +48,10 @@ module GraphQL
|
|
48
48
|
def self.dataloader_source_class
|
49
49
|
Fiber[:__graphql_current_dataloader_source]&.class
|
50
50
|
end
|
51
|
+
|
52
|
+
# @return [GraphQL::Dataloader::Source, nil] The currently-running source, if there is one
|
53
|
+
def self.dataloader_source
|
54
|
+
Fiber[:__graphql_current_dataloader_source]
|
55
|
+
end
|
51
56
|
end
|
52
57
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "graphql/dataloader/source"
|
3
|
+
require "graphql/dataloader/active_record_source"
|
4
|
+
|
5
|
+
module GraphQL
|
6
|
+
class Dataloader
|
7
|
+
class ActiveRecordAssociationSource < GraphQL::Dataloader::Source
|
8
|
+
RECORD_SOURCE_CLASS = ActiveRecordSource
|
9
|
+
|
10
|
+
def initialize(association, scope = nil)
|
11
|
+
@association = association
|
12
|
+
@scope = scope
|
13
|
+
end
|
14
|
+
|
15
|
+
def load(record)
|
16
|
+
if (assoc = record.association(@association)).loaded?
|
17
|
+
assoc.target
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def fetch(records)
|
24
|
+
record_classes = Set.new.compare_by_identity
|
25
|
+
associated_classes = Set.new.compare_by_identity
|
26
|
+
records.each do |record|
|
27
|
+
if record_classes.add?(record.class)
|
28
|
+
reflection = record.class.reflect_on_association(@association)
|
29
|
+
if !reflection.polymorphic? && reflection.klass
|
30
|
+
associated_classes.add(reflection.klass)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
available_records = []
|
36
|
+
associated_classes.each do |assoc_class|
|
37
|
+
already_loaded_records = dataloader.with(RECORD_SOURCE_CLASS, assoc_class).results.values
|
38
|
+
available_records.concat(already_loaded_records)
|
39
|
+
end
|
40
|
+
|
41
|
+
::ActiveRecord::Associations::Preloader.new(records: records, associations: @association, available_records: available_records, scope: @scope).call
|
42
|
+
|
43
|
+
loaded_associated_records = records.map { |r| r.public_send(@association) }
|
44
|
+
records_by_model = {}
|
45
|
+
loaded_associated_records.each do |record|
|
46
|
+
if record
|
47
|
+
updates = records_by_model[record.class] ||= {}
|
48
|
+
updates[record.id] = record
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if @scope.nil?
|
53
|
+
# Don't cache records loaded via scope because they might have reduced `SELECT`s
|
54
|
+
# Could check .select_values here?
|
55
|
+
records_by_model.each do |model_class, updates|
|
56
|
+
dataloader.with(RECORD_SOURCE_CLASS, model_class).merge(updates)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
loaded_associated_records
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "graphql/dataloader/source"
|
3
|
+
|
4
|
+
module GraphQL
|
5
|
+
class Dataloader
|
6
|
+
class ActiveRecordSource < GraphQL::Dataloader::Source
|
7
|
+
def initialize(model_class, find_by: model_class.primary_key)
|
8
|
+
@model_class = model_class
|
9
|
+
@find_by = find_by
|
10
|
+
@type_for_column = @model_class.type_for_attribute(@find_by)
|
11
|
+
end
|
12
|
+
|
13
|
+
def load(requested_key)
|
14
|
+
casted_key = @type_for_column.cast(requested_key)
|
15
|
+
super(casted_key)
|
16
|
+
end
|
17
|
+
|
18
|
+
def fetch(record_ids)
|
19
|
+
records = @model_class.where(@find_by => record_ids)
|
20
|
+
record_lookup = {}
|
21
|
+
records.each { |r| record_lookup[r.public_send(@find_by)] = r }
|
22
|
+
record_ids.map { |id| record_lookup[id] }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -2,16 +2,20 @@
|
|
2
2
|
module GraphQL
|
3
3
|
class Dataloader
|
4
4
|
class AsyncDataloader < Dataloader
|
5
|
-
def yield
|
5
|
+
def yield(source = Fiber[:__graphql_current_dataloader_source])
|
6
|
+
trace = Fiber[:__graphql_current_multiplex]&.current_trace
|
7
|
+
trace&.dataloader_fiber_yield(source)
|
6
8
|
if (condition = Fiber[:graphql_dataloader_next_tick])
|
7
9
|
condition.wait
|
8
10
|
else
|
9
11
|
Fiber.yield
|
10
12
|
end
|
13
|
+
trace&.dataloader_fiber_resume(source)
|
11
14
|
nil
|
12
15
|
end
|
13
16
|
|
14
17
|
def run
|
18
|
+
trace = Fiber[:__graphql_current_multiplex]&.current_trace
|
15
19
|
jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
|
16
20
|
job_fibers = []
|
17
21
|
next_job_fibers = []
|
@@ -20,11 +24,12 @@ module GraphQL
|
|
20
24
|
first_pass = true
|
21
25
|
sources_condition = Async::Condition.new
|
22
26
|
manager = spawn_fiber do
|
27
|
+
trace&.begin_dataloader(self)
|
23
28
|
while first_pass || !job_fibers.empty?
|
24
29
|
first_pass = false
|
25
30
|
fiber_vars = get_fiber_variables
|
26
31
|
|
27
|
-
while (f = (job_fibers.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size) < jobs_fiber_limit) && spawn_job_fiber)))
|
32
|
+
while (f = (job_fibers.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size) < jobs_fiber_limit) && spawn_job_fiber(trace))))
|
28
33
|
if f.alive?
|
29
34
|
finished = run_fiber(f)
|
30
35
|
if !finished
|
@@ -38,7 +43,7 @@ module GraphQL
|
|
38
43
|
Sync do |root_task|
|
39
44
|
set_fiber_variables(fiber_vars)
|
40
45
|
while !source_tasks.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
|
41
|
-
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))))
|
46
|
+
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))))
|
42
47
|
if task.alive?
|
43
48
|
root_task.yield # give the source task a chance to run
|
44
49
|
next_source_tasks << task
|
@@ -50,6 +55,7 @@ module GraphQL
|
|
50
55
|
end
|
51
56
|
end
|
52
57
|
end
|
58
|
+
trace&.end_dataloader(self)
|
53
59
|
end
|
54
60
|
|
55
61
|
manager.resume
|
@@ -63,7 +69,7 @@ module GraphQL
|
|
63
69
|
|
64
70
|
private
|
65
71
|
|
66
|
-
def spawn_source_task(parent_task, condition)
|
72
|
+
def spawn_source_task(parent_task, condition, trace)
|
67
73
|
pending_sources = nil
|
68
74
|
@source_cache.each_value do |source_by_batch_params|
|
69
75
|
source_by_batch_params.each_value do |source|
|
@@ -77,10 +83,16 @@ module GraphQL
|
|
77
83
|
if pending_sources
|
78
84
|
fiber_vars = get_fiber_variables
|
79
85
|
parent_task.async do
|
86
|
+
trace&.dataloader_spawn_source_fiber(pending_sources)
|
80
87
|
set_fiber_variables(fiber_vars)
|
81
88
|
Fiber[:graphql_dataloader_next_tick] = condition
|
82
|
-
pending_sources.each
|
89
|
+
pending_sources.each do |s|
|
90
|
+
trace&.begin_dataloader_source(s)
|
91
|
+
s.run_pending_keys
|
92
|
+
trace&.end_dataloader_source(s)
|
93
|
+
end
|
83
94
|
cleanup_fiber
|
95
|
+
trace&.dataloader_fiber_exit
|
84
96
|
end
|
85
97
|
end
|
86
98
|
end
|
@@ -11,7 +11,7 @@ module GraphQL
|
|
11
11
|
# executed synchronously.
|
12
12
|
def run; end
|
13
13
|
def run_isolated; yield; end
|
14
|
-
def yield
|
14
|
+
def yield(_source)
|
15
15
|
raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources."
|
16
16
|
end
|
17
17
|
|
@@ -93,14 +93,14 @@ module GraphQL
|
|
93
93
|
# Then run the batch and update the cache.
|
94
94
|
# @return [void]
|
95
95
|
def sync(pending_result_keys)
|
96
|
-
@dataloader.yield
|
96
|
+
@dataloader.yield(self)
|
97
97
|
iterations = 0
|
98
98
|
while pending_result_keys.any? { |key| !@results.key?(key) }
|
99
99
|
iterations += 1
|
100
100
|
if iterations > MAX_ITERATIONS
|
101
101
|
raise "#{self.class}#sync tried #{MAX_ITERATIONS} times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency#{@dataloader.fiber_limit ? " or `fiber_limit: #{@dataloader.fiber_limit}` is set too low" : ""}."
|
102
102
|
end
|
103
|
-
@dataloader.yield
|
103
|
+
@dataloader.yield(self)
|
104
104
|
end
|
105
105
|
nil
|
106
106
|
end
|
data/lib/graphql/dataloader.rb
CHANGED
@@ -4,6 +4,8 @@ require "graphql/dataloader/null_dataloader"
|
|
4
4
|
require "graphql/dataloader/request"
|
5
5
|
require "graphql/dataloader/request_all"
|
6
6
|
require "graphql/dataloader/source"
|
7
|
+
require "graphql/dataloader/active_record_association_source"
|
8
|
+
require "graphql/dataloader/active_record_source"
|
7
9
|
|
8
10
|
module GraphQL
|
9
11
|
# This plugin supports Fiber-based concurrency, along with {GraphQL::Dataloader::Source}.
|
@@ -129,8 +131,11 @@ module GraphQL
|
|
129
131
|
# Dataloader will resume the fiber after the requested data has been loaded (by another Fiber).
|
130
132
|
#
|
131
133
|
# @return [void]
|
132
|
-
def yield
|
134
|
+
def yield(source = Fiber[:__graphql_current_dataloader_source])
|
135
|
+
trace = Fiber[:__graphql_current_multiplex]&.current_trace
|
136
|
+
trace&.dataloader_fiber_yield(source)
|
133
137
|
Fiber.yield
|
138
|
+
trace&.dataloader_fiber_resume(source)
|
134
139
|
nil
|
135
140
|
end
|
136
141
|
|
@@ -184,6 +189,7 @@ module GraphQL
|
|
184
189
|
end
|
185
190
|
|
186
191
|
def run
|
192
|
+
trace = Fiber[:__graphql_current_multiplex]&.current_trace
|
187
193
|
jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
|
188
194
|
job_fibers = []
|
189
195
|
next_job_fibers = []
|
@@ -191,10 +197,11 @@ module GraphQL
|
|
191
197
|
next_source_fibers = []
|
192
198
|
first_pass = true
|
193
199
|
manager = spawn_fiber do
|
200
|
+
trace&.begin_dataloader(self)
|
194
201
|
while first_pass || !job_fibers.empty?
|
195
202
|
first_pass = false
|
196
203
|
|
197
|
-
while (f = (job_fibers.shift || (((next_job_fibers.size + job_fibers.size) < jobs_fiber_limit) && spawn_job_fiber)))
|
204
|
+
while (f = (job_fibers.shift || (((next_job_fibers.size + job_fibers.size) < jobs_fiber_limit) && spawn_job_fiber(trace))))
|
198
205
|
if f.alive?
|
199
206
|
finished = run_fiber(f)
|
200
207
|
if !finished
|
@@ -205,7 +212,7 @@ module GraphQL
|
|
205
212
|
join_queues(job_fibers, next_job_fibers)
|
206
213
|
|
207
214
|
while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
|
208
|
-
while (f = source_fibers.shift || (((job_fibers.size + source_fibers.size + next_source_fibers.size + next_job_fibers.size) < total_fiber_limit) && spawn_source_fiber))
|
215
|
+
while (f = source_fibers.shift || (((job_fibers.size + source_fibers.size + next_source_fibers.size + next_job_fibers.size) < total_fiber_limit) && spawn_source_fiber(trace)))
|
209
216
|
if f.alive?
|
210
217
|
finished = run_fiber(f)
|
211
218
|
if !finished
|
@@ -216,6 +223,8 @@ module GraphQL
|
|
216
223
|
join_queues(source_fibers, next_source_fibers)
|
217
224
|
end
|
218
225
|
end
|
226
|
+
|
227
|
+
trace&.end_dataloader(self)
|
219
228
|
end
|
220
229
|
|
221
230
|
run_fiber(manager)
|
@@ -230,6 +239,7 @@ module GraphQL
|
|
230
239
|
if !source_fibers.empty?
|
231
240
|
raise "Invariant: source fibers should have exited but #{source_fibers.size} remained"
|
232
241
|
end
|
242
|
+
|
233
243
|
rescue UncaughtThrowError => e
|
234
244
|
throw e.tag, e.value
|
235
245
|
end
|
@@ -247,6 +257,22 @@ module GraphQL
|
|
247
257
|
}
|
248
258
|
end
|
249
259
|
|
260
|
+
# Pre-warm the Dataloader cache with ActiveRecord objects which were loaded elsewhere.
|
261
|
+
# These will be used by {Dataloader::ActiveRecordSource}, {Dataloader::ActiveRecordAssociationSource} and their helper
|
262
|
+
# methods, `dataload_record` and `dataload_association`.
|
263
|
+
# @param records [Array<ActiveRecord::Base>] Already-loaded records to warm the cache with
|
264
|
+
# @param index_by [Symbol] The attribute to use as the cache key. (Should match `find_by:` when using {ActiveRecordSource})
|
265
|
+
# @return [void]
|
266
|
+
def merge_records(records, index_by: :id)
|
267
|
+
records_by_class = Hash.new { |h, k| h[k] = {} }
|
268
|
+
records.each do |r|
|
269
|
+
records_by_class[r.class][r.public_send(index_by)] = r
|
270
|
+
end
|
271
|
+
records_by_class.each do |r_class, records|
|
272
|
+
with(ActiveRecordSource, r_class).merge(records)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
250
276
|
private
|
251
277
|
|
252
278
|
def calculate_fiber_limit
|
@@ -266,17 +292,19 @@ module GraphQL
|
|
266
292
|
new_queue.clear
|
267
293
|
end
|
268
294
|
|
269
|
-
def spawn_job_fiber
|
295
|
+
def spawn_job_fiber(trace)
|
270
296
|
if !@pending_jobs.empty?
|
271
297
|
spawn_fiber do
|
298
|
+
trace&.dataloader_spawn_execution_fiber(@pending_jobs)
|
272
299
|
while job = @pending_jobs.shift
|
273
300
|
job.call
|
274
301
|
end
|
302
|
+
trace&.dataloader_fiber_exit
|
275
303
|
end
|
276
304
|
end
|
277
305
|
end
|
278
306
|
|
279
|
-
def spawn_source_fiber
|
307
|
+
def spawn_source_fiber(trace)
|
280
308
|
pending_sources = nil
|
281
309
|
@source_cache.each_value do |source_by_batch_params|
|
282
310
|
source_by_batch_params.each_value do |source|
|
@@ -289,10 +317,14 @@ module GraphQL
|
|
289
317
|
|
290
318
|
if pending_sources
|
291
319
|
spawn_fiber do
|
320
|
+
trace&.dataloader_spawn_source_fiber(pending_sources)
|
292
321
|
pending_sources.each do |source|
|
293
322
|
Fiber[:__graphql_current_dataloader_source] = source
|
323
|
+
trace&.begin_dataloader_source(source)
|
294
324
|
source.run_pending_keys
|
325
|
+
trace&.end_dataloader_source(source)
|
295
326
|
end
|
327
|
+
trace&.dataloader_fiber_exit
|
296
328
|
end
|
297
329
|
end
|
298
330
|
end
|
@@ -218,8 +218,10 @@ module GraphQL
|
|
218
218
|
result_name, field_ast_nodes_or_ast_node, selections_result
|
219
219
|
)
|
220
220
|
finished_jobs += 1
|
221
|
-
if
|
222
|
-
|
221
|
+
if finished_jobs == enqueued_jobs
|
222
|
+
if target_result
|
223
|
+
selections_result.merge_into(target_result)
|
224
|
+
end
|
223
225
|
end
|
224
226
|
@dataloader.clear_cache
|
225
227
|
}
|
@@ -229,8 +231,10 @@ module GraphQL
|
|
229
231
|
result_name, field_ast_nodes_or_ast_node, selections_result
|
230
232
|
)
|
231
233
|
finished_jobs += 1
|
232
|
-
if
|
233
|
-
|
234
|
+
if finished_jobs == enqueued_jobs
|
235
|
+
if target_result
|
236
|
+
selections_result.merge_into(target_result)
|
237
|
+
end
|
234
238
|
end
|
235
239
|
}
|
236
240
|
end
|
@@ -371,6 +375,7 @@ module GraphQL
|
|
371
375
|
end
|
372
376
|
# Actually call the field resolver and capture the result
|
373
377
|
app_result = begin
|
378
|
+
@current_trace.begin_execute_field(field_defn, object, kwarg_arguments, query)
|
374
379
|
@current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
|
375
380
|
field_defn.resolve(object, kwarg_arguments, context)
|
376
381
|
end
|
@@ -383,6 +388,7 @@ module GraphQL
|
|
383
388
|
ex_err
|
384
389
|
end
|
385
390
|
end
|
391
|
+
@current_trace.end_execute_field(field_defn, object, kwarg_arguments, query, app_result)
|
386
392
|
after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_result, runtime_state|
|
387
393
|
owner_type = selection_result.graphql_result_type
|
388
394
|
return_type = field_defn.type
|
@@ -391,6 +397,8 @@ module GraphQL
|
|
391
397
|
was_scoped = runtime_state.was_authorized_by_scope_items
|
392
398
|
runtime_state.was_authorized_by_scope_items = nil
|
393
399
|
continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result, was_scoped, runtime_state)
|
400
|
+
else
|
401
|
+
nil
|
394
402
|
end
|
395
403
|
end
|
396
404
|
end
|
@@ -781,8 +789,10 @@ module GraphQL
|
|
781
789
|
runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items
|
782
790
|
# Wrap the execution of _this_ method with tracing,
|
783
791
|
# but don't wrap the continuation below
|
792
|
+
result = nil
|
784
793
|
inner_obj = begin
|
785
|
-
if trace
|
794
|
+
result = if trace
|
795
|
+
@current_trace.begin_execute_field(field, owner_object, arguments, query)
|
786
796
|
@current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
|
787
797
|
schema.sync_lazy(lazy_obj)
|
788
798
|
end
|
@@ -797,6 +807,10 @@ module GraphQL
|
|
797
807
|
rescue GraphQL::ExecutionError => ex_err
|
798
808
|
ex_err
|
799
809
|
end
|
810
|
+
ensure
|
811
|
+
if trace
|
812
|
+
@current_trace.end_execute_field(field, owner_object, arguments, query, result)
|
813
|
+
end
|
800
814
|
end
|
801
815
|
yield(inner_obj, runtime_state)
|
802
816
|
end
|
@@ -840,14 +854,19 @@ module GraphQL
|
|
840
854
|
end
|
841
855
|
|
842
856
|
def resolve_type(type, value)
|
857
|
+
@current_trace.begin_resolve_type(type, value, context)
|
843
858
|
resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do
|
844
859
|
query.resolve_type(type, value)
|
845
860
|
end
|
861
|
+
@current_trace.end_resolve_type(type, value, context, resolved_type)
|
846
862
|
|
847
863
|
if lazy?(resolved_type)
|
848
864
|
GraphQL::Execution::Lazy.new do
|
865
|
+
@current_trace.begin_resolve_type(type, value, context)
|
849
866
|
@current_trace.resolve_type_lazy(query: query, type: type, object: value) do
|
850
|
-
schema.sync_lazy(resolved_type)
|
867
|
+
rt = schema.sync_lazy(resolved_type)
|
868
|
+
@current_trace.end_resolve_type(type, value, context, rt)
|
869
|
+
rt
|
851
870
|
end
|
852
871
|
end
|
853
872
|
else
|
@@ -33,9 +33,12 @@ module GraphQL
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
+
|
36
37
|
multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
|
37
38
|
Fiber[:__graphql_current_multiplex] = multiplex
|
38
|
-
multiplex.current_trace
|
39
|
+
trace = multiplex.current_trace
|
40
|
+
trace.begin_execute_multiplex(multiplex)
|
41
|
+
trace.execute_multiplex(multiplex: multiplex) do
|
39
42
|
schema = multiplex.schema
|
40
43
|
queries = multiplex.queries
|
41
44
|
lazies_at_depth = Hash.new { |h, k| h[k] = [] }
|
@@ -44,7 +47,10 @@ module GraphQL
|
|
44
47
|
multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
|
45
48
|
end
|
46
49
|
|
50
|
+
trace.begin_analyze_multiplex(multiplex, multiplex_analyzers)
|
47
51
|
schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
|
52
|
+
trace.end_analyze_multiplex(multiplex, multiplex_analyzers)
|
53
|
+
|
48
54
|
begin
|
49
55
|
# Since this is basically the batching context,
|
50
56
|
# share it for a whole multiplex
|
@@ -148,6 +154,8 @@ module GraphQL
|
|
148
154
|
}
|
149
155
|
end
|
150
156
|
end
|
157
|
+
ensure
|
158
|
+
trace&.end_execute_multiplex(multiplex)
|
151
159
|
end
|
152
160
|
end
|
153
161
|
|
@@ -161,7 +161,7 @@ module GraphQL
|
|
161
161
|
expect_token(:VAR_SIGN)
|
162
162
|
var_name = parse_name
|
163
163
|
expect_token(:COLON)
|
164
|
-
var_type = self.type
|
164
|
+
var_type = self.type || raise_parse_error("Missing type definition for variable: $#{var_name}")
|
165
165
|
default_value = if at?(:EQUALS)
|
166
166
|
advance_token
|
167
167
|
value
|
data/lib/graphql/query.rb
CHANGED
@@ -97,21 +97,22 @@ module GraphQL
|
|
97
97
|
# @param root_value [Object] the object used to resolve fields on the root type
|
98
98
|
# @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
|
99
99
|
# @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
|
100
|
-
# @param visibility_profile [Symbol]
|
100
|
+
# @param visibility_profile [Symbol] Another way to assign `context[:visibility_profile]`
|
101
101
|
def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, visibility_profile: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil, use_visibility_profile: nil)
|
102
102
|
# Even if `variables: nil` is passed, use an empty hash for simpler logic
|
103
103
|
variables ||= {}
|
104
104
|
@schema = schema
|
105
105
|
@context = schema.context_class.new(query: self, values: context)
|
106
|
+
if visibility_profile
|
107
|
+
@context[:visibility_profile] ||= visibility_profile
|
108
|
+
end
|
106
109
|
|
107
110
|
if use_visibility_profile.nil?
|
108
111
|
use_visibility_profile = warden ? false : schema.use_visibility_profile?
|
109
112
|
end
|
110
113
|
|
111
|
-
@visibility_profile = visibility_profile
|
112
|
-
|
113
114
|
if use_visibility_profile
|
114
|
-
@visibility_profile = @schema.visibility.profile_for(@context
|
115
|
+
@visibility_profile = @schema.visibility.profile_for(@context)
|
115
116
|
@warden = Schema::Warden::NullWarden.new(context: @context, schema: @schema)
|
116
117
|
else
|
117
118
|
@visibility_profile = nil
|
@@ -440,6 +441,7 @@ module GraphQL
|
|
440
441
|
@warden ||= @schema.warden_class.new(schema: @schema, context: @context)
|
441
442
|
parse_error = nil
|
442
443
|
@document ||= begin
|
444
|
+
current_trace.begin_parse(query_string)
|
443
445
|
if query_string
|
444
446
|
GraphQL.parse(query_string, trace: self.current_trace, max_tokens: @schema.max_query_string_tokens)
|
445
447
|
end
|
@@ -447,6 +449,8 @@ module GraphQL
|
|
447
449
|
parse_error = err
|
448
450
|
@schema.parse_error(err, @context)
|
449
451
|
nil
|
452
|
+
ensure
|
453
|
+
current_trace.end_parse(query_string)
|
450
454
|
end
|
451
455
|
|
452
456
|
@fragments = {}
|
data/lib/graphql/schema/enum.rb
CHANGED
@@ -13,6 +13,7 @@ module GraphQL
|
|
13
13
|
include GraphQL::Schema::Member::Scoped
|
14
14
|
include GraphQL::Schema::Member::HasAstNode
|
15
15
|
include GraphQL::Schema::Member::HasUnresolvedTypeError
|
16
|
+
include GraphQL::Schema::Member::HasDataloader
|
16
17
|
include GraphQL::Schema::Member::HasDirectives
|
17
18
|
include GraphQL::Schema::Member::HasInterfaces
|
18
19
|
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Member
|
6
|
+
module HasDataloader
|
7
|
+
# @return [GraphQL::Dataloader] The dataloader for the currently-running query
|
8
|
+
def dataloader
|
9
|
+
context.dataloader
|
10
|
+
end
|
11
|
+
|
12
|
+
# A shortcut method for loading a key from a source.
|
13
|
+
# Identical to `dataloader.with(source_class, *source_args).load(load_key)`
|
14
|
+
# @param source_class [Class<GraphQL::Dataloader::Source>]
|
15
|
+
# @param source_args [Array<Object>] Any extra parameters defined in `source_class`'s `initialize` method
|
16
|
+
# @param load_key [Object] The key to look up using `def fetch`
|
17
|
+
def dataload(source_class, *source_args, load_key)
|
18
|
+
dataloader.with(source_class, *source_args).load(load_key)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Find an object with ActiveRecord via {Dataloader::ActiveRecordSource}.
|
22
|
+
# @param model [Class<ActiveRecord::Base>]
|
23
|
+
# @param find_by_value [Object] Usually an `id`, might be another value if `find_by:` is also provided
|
24
|
+
# @param find_by [Symbol, String] A column name to look the record up by. (Defaults to the model's primary key.)
|
25
|
+
# @return [ActiveRecord::Base, nil]
|
26
|
+
def dataload_record(model, find_by_value, find_by: nil)
|
27
|
+
source = if find_by
|
28
|
+
dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by)
|
29
|
+
else
|
30
|
+
dataloader.with(Dataloader::ActiveRecordSource, model)
|
31
|
+
end
|
32
|
+
|
33
|
+
source.load(find_by_value)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Look up an associated record using a Rails association.
|
37
|
+
# @param association_name [Symbol] A `belongs_to` or `has_one` association. (If a `has_many` association is named here, it will be selected without pagination.)
|
38
|
+
# @param record [ActiveRecord::Base] The object that the association belongs to.
|
39
|
+
# @param scope [ActiveRecord::Relation] A scope to look up the associated record in
|
40
|
+
# @return [ActiveRecord::Base, nil] The associated record, if there is one
|
41
|
+
# @example Looking up a belongs_to on the current object
|
42
|
+
# dataload_association(:parent) # Equivalent to `object.parent`, but dataloaded
|
43
|
+
# @example Looking up an associated record on some other object
|
44
|
+
# dataload_association(:post, comment) # Equivalent to `comment.post`, but dataloaded
|
45
|
+
def dataload_association(record = object, association_name, scope: nil)
|
46
|
+
source = if scope
|
47
|
+
dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name, scope)
|
48
|
+
else
|
49
|
+
dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name)
|
50
|
+
end
|
51
|
+
source.load(record)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
require 'graphql/schema/member/base_dsl_methods'
|
3
3
|
require 'graphql/schema/member/graphql_type_names'
|
4
4
|
require 'graphql/schema/member/has_ast_node'
|
5
|
+
require 'graphql/schema/member/has_dataloader'
|
5
6
|
require 'graphql/schema/member/has_directives'
|
6
7
|
require 'graphql/schema/member/has_deprecation_reason'
|
7
8
|
require 'graphql/schema/member/has_interfaces'
|
@@ -7,6 +7,7 @@ module GraphQL
|
|
7
7
|
class Object < GraphQL::Schema::Member
|
8
8
|
extend GraphQL::Schema::Member::HasFields
|
9
9
|
extend GraphQL::Schema::Member::HasInterfaces
|
10
|
+
include Member::HasDataloader
|
10
11
|
|
11
12
|
# Raised when an Object doesn't have any field defined and hasn't explicitly opted out of this requirement
|
12
13
|
class FieldsAreRequiredError < GraphQL::Error
|
@@ -65,20 +66,28 @@ module GraphQL
|
|
65
66
|
# @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
|
66
67
|
# @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
|
67
68
|
def authorized_new(object, context)
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
69
|
+
context.query.current_trace.begin_authorized(self, object, context)
|
70
|
+
begin
|
71
|
+
maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do
|
72
|
+
begin
|
73
|
+
authorized?(object, context)
|
74
|
+
rescue GraphQL::UnauthorizedError => err
|
75
|
+
context.schema.unauthorized_object(err)
|
76
|
+
rescue StandardError => err
|
77
|
+
context.query.handle_or_reraise(err)
|
78
|
+
end
|
75
79
|
end
|
80
|
+
ensure
|
81
|
+
context.query.current_trace.end_authorized(self, object, context, maybe_lazy_auth_val)
|
76
82
|
end
|
77
83
|
|
78
84
|
auth_val = if context.schema.lazy?(maybe_lazy_auth_val)
|
79
85
|
GraphQL::Execution::Lazy.new do
|
86
|
+
context.query.current_trace.begin_authorized(self, object, context)
|
80
87
|
context.query.current_trace.authorized_lazy(query: context.query, type: self, object: object) do
|
81
|
-
context.schema.sync_lazy(maybe_lazy_auth_val)
|
88
|
+
res = context.schema.sync_lazy(maybe_lazy_auth_val)
|
89
|
+
context.query.current_trace.end_authorized(self, object, context, res)
|
90
|
+
res
|
82
91
|
end
|
83
92
|
end
|
84
93
|
else
|