graphql 2.0.20 → 2.0.22
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.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/graphql/backtrace/trace.rb +96 -0
- data/lib/graphql/backtrace.rb +6 -1
- data/lib/graphql/execution/interpreter/arguments.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +33 -33
- data/lib/graphql/execution/interpreter/runtime.rb +274 -209
- data/lib/graphql/execution/interpreter.rb +2 -3
- data/lib/graphql/execution/lookahead.rb +1 -1
- data/lib/graphql/filter.rb +8 -2
- data/lib/graphql/language/document_from_schema_definition.rb +37 -17
- data/lib/graphql/language/lexer.rb +5 -3
- data/lib/graphql/language/nodes.rb +2 -2
- data/lib/graphql/language/parser.rb +475 -458
- data/lib/graphql/language/parser.y +5 -1
- data/lib/graphql/pagination/connection.rb +5 -5
- data/lib/graphql/query/context.rb +22 -12
- data/lib/graphql/query/null_context.rb +4 -1
- data/lib/graphql/query.rb +25 -11
- data/lib/graphql/schema/argument.rb +12 -14
- data/lib/graphql/schema/build_from_definition.rb +15 -3
- data/lib/graphql/schema/enum_value.rb +2 -5
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +17 -16
- data/lib/graphql/schema/field_extension.rb +1 -4
- data/lib/graphql/schema/find_inherited_value.rb +2 -7
- data/lib/graphql/schema/input_object.rb +1 -1
- data/lib/graphql/schema/member/has_arguments.rb +10 -8
- data/lib/graphql/schema/member/has_directives.rb +4 -6
- data/lib/graphql/schema/member/has_fields.rb +80 -36
- data/lib/graphql/schema/member/has_validators.rb +2 -2
- data/lib/graphql/schema/object.rb +1 -1
- data/lib/graphql/schema/printer.rb +3 -1
- data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
- data/lib/graphql/schema/resolver.rb +8 -8
- data/lib/graphql/schema/validator.rb +1 -1
- data/lib/graphql/schema/warden.rb +11 -3
- data/lib/graphql/schema.rb +41 -12
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -4
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +2 -2
- data/lib/graphql/tracing/appsignal_trace.rb +6 -0
- data/lib/graphql/tracing/legacy_trace.rb +65 -0
- data/lib/graphql/tracing/notifications_trace.rb +5 -1
- data/lib/graphql/tracing/platform_trace.rb +21 -19
- data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +1 -1
- data/lib/graphql/tracing/trace.rb +75 -0
- data/lib/graphql/tracing.rb +4 -123
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +4 -0
- data/readme.md +1 -1
- metadata +6 -3
@@ -8,14 +8,27 @@ module GraphQL
|
|
8
8
|
#
|
9
9
|
# @api private
|
10
10
|
class Runtime
|
11
|
+
class CurrentState
|
12
|
+
def initialize
|
13
|
+
@current_object = nil
|
14
|
+
@current_field = nil
|
15
|
+
@current_arguments = nil
|
16
|
+
@current_result_name = nil
|
17
|
+
@current_result = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :current_result, :current_result_name,
|
21
|
+
:current_arguments, :current_field, :current_object
|
22
|
+
end
|
11
23
|
|
12
24
|
module GraphQLResult
|
13
|
-
def initialize(result_name, parent_result)
|
25
|
+
def initialize(result_name, parent_result, is_non_null_in_parent)
|
14
26
|
@graphql_parent = parent_result
|
15
27
|
if parent_result && parent_result.graphql_dead
|
16
28
|
@graphql_dead = true
|
17
29
|
end
|
18
30
|
@graphql_result_name = result_name
|
31
|
+
@graphql_is_non_null_in_parent = is_non_null_in_parent
|
19
32
|
# Jump through some hoops to avoid creating this duplicate storage if at all possible.
|
20
33
|
@graphql_metadata = nil
|
21
34
|
end
|
@@ -30,22 +43,14 @@ module GraphQL
|
|
30
43
|
end
|
31
44
|
|
32
45
|
attr_accessor :graphql_dead
|
33
|
-
attr_reader :graphql_parent, :graphql_result_name
|
34
|
-
|
35
|
-
# Although these are used by only one of the Result classes,
|
36
|
-
# it's handy to have the methods implemented on both (even though they just return `nil`)
|
37
|
-
# because it makes it easy to check if anything is assigned.
|
38
|
-
# @return [nil, Array<String>]
|
39
|
-
attr_accessor :graphql_non_null_field_names
|
40
|
-
# @return [nil, true]
|
41
|
-
attr_accessor :graphql_non_null_list_items
|
46
|
+
attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent
|
42
47
|
|
43
48
|
# @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
|
44
49
|
attr_accessor :graphql_result_data
|
45
50
|
end
|
46
51
|
|
47
52
|
class GraphQLResultHash
|
48
|
-
def initialize(_result_name, _parent_result)
|
53
|
+
def initialize(_result_name, _parent_result, _is_non_null_in_parent)
|
49
54
|
super
|
50
55
|
@graphql_result_data = {}
|
51
56
|
end
|
@@ -54,7 +59,7 @@ module GraphQL
|
|
54
59
|
|
55
60
|
attr_accessor :graphql_merged_into
|
56
61
|
|
57
|
-
def
|
62
|
+
def set_leaf(key, value)
|
58
63
|
# This is a hack.
|
59
64
|
# Basically, this object is merged into the root-level result at some point.
|
60
65
|
# But the problem is, some lazies are created whose closures retain reference to _this_
|
@@ -64,20 +69,24 @@ module GraphQL
|
|
64
69
|
# In order to return a proper partial result (eg, for a directive), we have to update this object, too.
|
65
70
|
# Yowza.
|
66
71
|
if (t = @graphql_merged_into)
|
67
|
-
t
|
72
|
+
t.set_leaf(key, value)
|
68
73
|
end
|
69
74
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
@graphql_result_data[key] = value
|
77
|
-
# keep this up-to-date if it's been initialized
|
78
|
-
@graphql_metadata && @graphql_metadata[key] = value
|
79
|
-
end
|
75
|
+
@graphql_result_data[key] = value
|
76
|
+
# keep this up-to-date if it's been initialized
|
77
|
+
@graphql_metadata && @graphql_metadata[key] = value
|
78
|
+
|
79
|
+
value
|
80
|
+
end
|
80
81
|
|
82
|
+
def set_child_result(key, value)
|
83
|
+
if (t = @graphql_merged_into)
|
84
|
+
t.set_child_result(key, value)
|
85
|
+
end
|
86
|
+
@graphql_result_data[key] = value.graphql_result_data
|
87
|
+
# If we encounter some part of this response that requires metadata tracking,
|
88
|
+
# then create the metadata hash if necessary. It will be kept up-to-date after this.
|
89
|
+
(@graphql_metadata ||= @graphql_result_data.dup)[key] = value
|
81
90
|
value
|
82
91
|
end
|
83
92
|
|
@@ -101,12 +110,35 @@ module GraphQL
|
|
101
110
|
def [](k)
|
102
111
|
(@graphql_metadata || @graphql_result_data)[k]
|
103
112
|
end
|
113
|
+
|
114
|
+
def merge_into(into_result)
|
115
|
+
self.each do |key, value|
|
116
|
+
case value
|
117
|
+
when GraphQLResultHash
|
118
|
+
next_into = into_result[key]
|
119
|
+
if next_into
|
120
|
+
value.merge_into(next_into)
|
121
|
+
else
|
122
|
+
into_result.set_child_result(key, value)
|
123
|
+
end
|
124
|
+
when GraphQLResultArray
|
125
|
+
# There's no special handling of arrays because currently, there's no way to split the execution
|
126
|
+
# of a list over several concurrent flows.
|
127
|
+
next_result.set_child_result(key, value)
|
128
|
+
else
|
129
|
+
# We have to assume that, since this passed the `fields_will_merge` selection,
|
130
|
+
# that the old and new values are the same.
|
131
|
+
into_result.set_leaf(key, value)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
@graphql_merged_into = into_result
|
135
|
+
end
|
104
136
|
end
|
105
137
|
|
106
138
|
class GraphQLResultArray
|
107
139
|
include GraphQLResult
|
108
140
|
|
109
|
-
def initialize(_result_name, _parent_result)
|
141
|
+
def initialize(_result_name, _parent_result, _is_non_null_in_parent)
|
110
142
|
super
|
111
143
|
@graphql_result_data = []
|
112
144
|
end
|
@@ -123,19 +155,25 @@ module GraphQL
|
|
123
155
|
@graphql_result_data.delete_at(delete_at_index)
|
124
156
|
end
|
125
157
|
|
126
|
-
def
|
158
|
+
def set_leaf(idx, value)
|
127
159
|
if @skip_indices
|
128
160
|
offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
|
129
161
|
idx -= offset_by
|
130
162
|
end
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
@graphql_result_data[idx] = value
|
136
|
-
@graphql_metadata && @graphql_metadata[idx] = value
|
137
|
-
end
|
163
|
+
@graphql_result_data[idx] = value
|
164
|
+
@graphql_metadata && @graphql_metadata[idx] = value
|
165
|
+
value
|
166
|
+
end
|
138
167
|
|
168
|
+
def set_child_result(idx, value)
|
169
|
+
if @skip_indices
|
170
|
+
offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
|
171
|
+
idx -= offset_by
|
172
|
+
end
|
173
|
+
@graphql_result_data[idx] = value.graphql_result_data
|
174
|
+
# If we encounter some part of this response that requires metadata tracking,
|
175
|
+
# then create the metadata hash if necessary. It will be kept up-to-date after this.
|
176
|
+
(@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
|
139
177
|
value
|
140
178
|
end
|
141
179
|
|
@@ -144,10 +182,6 @@ module GraphQL
|
|
144
182
|
end
|
145
183
|
end
|
146
184
|
|
147
|
-
class GraphQLSelectionSet < Hash
|
148
|
-
attr_accessor :graphql_directives
|
149
|
-
end
|
150
|
-
|
151
185
|
# @return [GraphQL::Query]
|
152
186
|
attr_reader :query
|
153
187
|
|
@@ -157,25 +191,14 @@ module GraphQL
|
|
157
191
|
# @return [GraphQL::Query::Context]
|
158
192
|
attr_reader :context
|
159
193
|
|
160
|
-
def thread_info
|
161
|
-
info = Thread.current[:__graphql_runtime_info]
|
162
|
-
if !info
|
163
|
-
new_ti = {}
|
164
|
-
info = Thread.current[:__graphql_runtime_info] = new_ti
|
165
|
-
end
|
166
|
-
info
|
167
|
-
end
|
168
|
-
|
169
194
|
def initialize(query:, lazies_at_depth:)
|
170
195
|
@query = query
|
196
|
+
@current_trace = query.current_trace
|
171
197
|
@dataloader = query.multiplex.dataloader
|
172
198
|
@lazies_at_depth = lazies_at_depth
|
173
199
|
@schema = query.schema
|
174
200
|
@context = query.context
|
175
|
-
@
|
176
|
-
# Start this off empty:
|
177
|
-
Thread.current[:__graphql_runtime_info] = nil
|
178
|
-
@response = GraphQLResultHash.new(nil, nil)
|
201
|
+
@response = GraphQLResultHash.new(nil, nil, false)
|
179
202
|
# Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
|
180
203
|
@runtime_directive_names = []
|
181
204
|
noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
|
@@ -189,8 +212,11 @@ module GraphQL
|
|
189
212
|
# Which assumes that MyObject.get_field("myField") will return the same field
|
190
213
|
# during the lifetime of a query
|
191
214
|
@fields_cache = Hash.new { |h, k| h[k] = {} }
|
215
|
+
# this can by by-identity since owners are the same object, but not the sub-hash, which uses strings.
|
216
|
+
@fields_cache.compare_by_identity
|
192
217
|
# { Class => Boolean }
|
193
218
|
@lazy_cache = {}
|
219
|
+
@lazy_cache.compare_by_identity
|
194
220
|
end
|
195
221
|
|
196
222
|
def final_result
|
@@ -218,7 +244,9 @@ module GraphQL
|
|
218
244
|
root_operation = query.selected_operation
|
219
245
|
root_op_type = root_operation.operation_type || "query"
|
220
246
|
root_type = schema.root_type_for_operation(root_op_type)
|
221
|
-
|
247
|
+
st = get_current_runtime_state
|
248
|
+
st.current_object = query.root_value
|
249
|
+
st.current_result = @response
|
222
250
|
object_proxy = authorized_new(root_type, query.root_value, context)
|
223
251
|
object_proxy = schema.sync_lazy(object_proxy)
|
224
252
|
|
@@ -237,7 +265,7 @@ module GraphQL
|
|
237
265
|
# directly evaluated and the results can be written right into the main response hash.
|
238
266
|
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
239
267
|
if is_selection_array
|
240
|
-
selection_response = GraphQLResultHash.new(nil, nil)
|
268
|
+
selection_response = GraphQLResultHash.new(nil, nil, false)
|
241
269
|
final_response = @response
|
242
270
|
else
|
243
271
|
selection_response = @response
|
@@ -245,8 +273,14 @@ module GraphQL
|
|
245
273
|
end
|
246
274
|
|
247
275
|
@dataloader.append_job {
|
248
|
-
|
249
|
-
|
276
|
+
st = get_current_runtime_state
|
277
|
+
st.current_object = query.root_value
|
278
|
+
st.current_result = selection_response
|
279
|
+
# This is a less-frequent case; use a fast check since it's often not there.
|
280
|
+
if (directives = selections[:graphql_directives])
|
281
|
+
selections.delete(:graphql_directives)
|
282
|
+
end
|
283
|
+
call_method_on_directives(:resolve, object_proxy, directives) do
|
250
284
|
evaluate_selections(
|
251
285
|
object_proxy,
|
252
286
|
root_type,
|
@@ -265,29 +299,7 @@ module GraphQL
|
|
265
299
|
nil
|
266
300
|
end
|
267
301
|
|
268
|
-
|
269
|
-
def deep_merge_selection_result(from_result, into_result)
|
270
|
-
from_result.each do |key, value|
|
271
|
-
if !into_result.key?(key)
|
272
|
-
into_result[key] = value
|
273
|
-
else
|
274
|
-
case value
|
275
|
-
when GraphQLResultHash
|
276
|
-
deep_merge_selection_result(value, into_result[key])
|
277
|
-
else
|
278
|
-
# We have to assume that, since this passed the `fields_will_merge` selection,
|
279
|
-
# that the old and new values are the same.
|
280
|
-
# There's no special handling of arrays because currently, there's no way to split the execution
|
281
|
-
# of a list over several concurrent flows.
|
282
|
-
into_result[key] = value
|
283
|
-
end
|
284
|
-
end
|
285
|
-
end
|
286
|
-
from_result.graphql_merged_into = into_result
|
287
|
-
nil
|
288
|
-
end
|
289
|
-
|
290
|
-
def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new)
|
302
|
+
def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
|
291
303
|
selections.each do |node|
|
292
304
|
# Skip gathering this if the directive says so
|
293
305
|
if !directives_include?(node, owner_object, owner_type)
|
@@ -313,8 +325,8 @@ module GraphQL
|
|
313
325
|
else
|
314
326
|
# This is an InlineFragment or a FragmentSpread
|
315
327
|
if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
|
316
|
-
next_selections =
|
317
|
-
next_selections
|
328
|
+
next_selections = {}
|
329
|
+
next_selections[:graphql_directives] = node.directives
|
318
330
|
if selections_to_run
|
319
331
|
selections_to_run << next_selections
|
320
332
|
else
|
@@ -360,11 +372,14 @@ module GraphQL
|
|
360
372
|
selections_to_run || selections_by_name
|
361
373
|
end
|
362
374
|
|
363
|
-
NO_ARGS =
|
375
|
+
NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
|
364
376
|
|
365
377
|
# @return [void]
|
366
378
|
def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
|
367
|
-
|
379
|
+
st = get_current_runtime_state
|
380
|
+
st.current_object = owner_object
|
381
|
+
st.current_result_name = nil
|
382
|
+
st.current_result = selections_result
|
368
383
|
|
369
384
|
finished_jobs = 0
|
370
385
|
enqueued_jobs = gathered_selections.size
|
@@ -375,9 +390,15 @@ module GraphQL
|
|
375
390
|
)
|
376
391
|
finished_jobs += 1
|
377
392
|
if target_result && finished_jobs == enqueued_jobs
|
378
|
-
|
393
|
+
selections_result.merge_into(target_result)
|
379
394
|
end
|
380
395
|
}
|
396
|
+
# Field resolution may pause the fiber,
|
397
|
+
# so it wouldn't get to the `Resolve` call that happens below.
|
398
|
+
# So instead trigger a run from this outer context.
|
399
|
+
if is_eager_selection
|
400
|
+
@dataloader.run
|
401
|
+
end
|
381
402
|
end
|
382
403
|
|
383
404
|
selections_result
|
@@ -418,11 +439,13 @@ module GraphQL
|
|
418
439
|
# This seems janky, but we need to know
|
419
440
|
# the field's return type at this path in order
|
420
441
|
# to propagate `null`
|
421
|
-
|
422
|
-
(selections_result.graphql_non_null_field_names ||= []).push(result_name)
|
423
|
-
end
|
442
|
+
return_type_non_null = return_type.non_null?
|
424
443
|
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
425
|
-
|
444
|
+
st = get_current_runtime_state
|
445
|
+
st.current_field = field_defn
|
446
|
+
st.current_result = selections_result
|
447
|
+
st.current_result_name = result_name
|
448
|
+
|
426
449
|
object = owner_object
|
427
450
|
|
428
451
|
if is_introspection
|
@@ -432,25 +455,34 @@ module GraphQL
|
|
432
455
|
total_args_count = field_defn.arguments(context).size
|
433
456
|
if total_args_count == 0
|
434
457
|
resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
|
435
|
-
|
458
|
+
if field_defn.extras.size == 0
|
459
|
+
evaluate_selection_with_resolved_keyword_args(
|
460
|
+
NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null
|
461
|
+
)
|
462
|
+
else
|
463
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
|
464
|
+
end
|
436
465
|
else
|
437
|
-
# TODO remove all arguments(...) usages?
|
438
466
|
@query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
|
439
|
-
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type)
|
467
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
|
440
468
|
end
|
441
469
|
end
|
442
470
|
end
|
443
471
|
|
444
|
-
def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type) # rubocop:disable Metrics/ParameterLists
|
445
|
-
after_lazy(arguments,
|
472
|
+
def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null) # rubocop:disable Metrics/ParameterLists
|
473
|
+
after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
|
446
474
|
if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
|
447
|
-
continue_value(resolved_arguments, owner_type, field_defn,
|
475
|
+
continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
|
448
476
|
next
|
449
477
|
end
|
450
478
|
|
451
|
-
kwarg_arguments = if
|
452
|
-
|
453
|
-
|
479
|
+
kwarg_arguments = if field_defn.extras.empty?
|
480
|
+
if resolved_arguments.empty?
|
481
|
+
# We can avoid allocating the `{ Symbol => Object }` hash in this case
|
482
|
+
NO_ARGS
|
483
|
+
else
|
484
|
+
resolved_arguments.keyword_arguments
|
485
|
+
end
|
454
486
|
else
|
455
487
|
# Bundle up the extras, then make a new arguments instance
|
456
488
|
# that includes the extras, too.
|
@@ -489,67 +521,72 @@ module GraphQL
|
|
489
521
|
resolved_arguments.keyword_arguments
|
490
522
|
end
|
491
523
|
|
492
|
-
|
524
|
+
evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null)
|
525
|
+
end
|
526
|
+
end
|
493
527
|
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
ex_err
|
520
|
-
end
|
528
|
+
def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null) # rubocop:disable Metrics/ParameterLists
|
529
|
+
st = get_current_runtime_state
|
530
|
+
st.current_field = field_defn
|
531
|
+
st.current_object = object
|
532
|
+
st.current_arguments = resolved_arguments
|
533
|
+
st.current_result_name = result_name
|
534
|
+
st.current_result = selection_result
|
535
|
+
# Optimize for the case that field is selected only once
|
536
|
+
if field_ast_nodes.nil? || field_ast_nodes.size == 1
|
537
|
+
next_selections = ast_node.selections
|
538
|
+
directives = ast_node.directives
|
539
|
+
else
|
540
|
+
next_selections = []
|
541
|
+
directives = []
|
542
|
+
field_ast_nodes.each { |f|
|
543
|
+
next_selections.concat(f.selections)
|
544
|
+
directives.concat(f.directives)
|
545
|
+
}
|
546
|
+
end
|
547
|
+
|
548
|
+
field_result = call_method_on_directives(:resolve, object, directives) do
|
549
|
+
# Actually call the field resolver and capture the result
|
550
|
+
app_result = begin
|
551
|
+
@current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
|
552
|
+
field_defn.resolve(object, kwarg_arguments, context)
|
521
553
|
end
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
554
|
+
rescue GraphQL::ExecutionError => err
|
555
|
+
err
|
556
|
+
rescue StandardError => err
|
557
|
+
begin
|
558
|
+
query.handle_or_reraise(err)
|
559
|
+
rescue GraphQL::ExecutionError => ex_err
|
560
|
+
ex_err
|
527
561
|
end
|
528
562
|
end
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
Interpreter::Resolve.resolve_all([field_result], @dataloader)
|
535
|
-
else
|
536
|
-
# Return this from `after_lazy` because it might be another lazy that needs to be resolved
|
537
|
-
field_result
|
563
|
+
after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
|
564
|
+
continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
|
565
|
+
if HALT != continue_value
|
566
|
+
continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
|
567
|
+
end
|
538
568
|
end
|
539
569
|
end
|
570
|
+
|
571
|
+
# If this field is a root mutation field, immediately resolve
|
572
|
+
# all of its child fields before moving on to the next root mutation field.
|
573
|
+
# (Subselections of this mutation will still be resolved level-by-level.)
|
574
|
+
if is_eager_field
|
575
|
+
Interpreter::Resolve.resolve_all([field_result], @dataloader)
|
576
|
+
else
|
577
|
+
# Return this from `after_lazy` because it might be another lazy that needs to be resolved
|
578
|
+
field_result
|
579
|
+
end
|
540
580
|
end
|
541
581
|
|
582
|
+
|
542
583
|
def dead_result?(selection_result)
|
543
|
-
selection_result.graphql_dead || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
|
584
|
+
selection_result.graphql_dead # || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
|
544
585
|
end
|
545
586
|
|
546
|
-
def set_result(selection_result, result_name, value)
|
587
|
+
def set_result(selection_result, result_name, value, is_child_result, is_non_null)
|
547
588
|
if !dead_result?(selection_result)
|
548
|
-
if value.nil? &&
|
549
|
-
( # there are two conditions under which `nil` is not allowed in the response:
|
550
|
-
(selection_result.graphql_non_null_list_items) || # this value would be written into a list that doesn't allow nils
|
551
|
-
((nn = selection_result.graphql_non_null_field_names) && nn.include?(result_name)) # this value would be written into a field that doesn't allow nils
|
552
|
-
)
|
589
|
+
if value.nil? && is_non_null
|
553
590
|
# This is an invalid nil that should be propagated
|
554
591
|
# One caller of this method passes a block,
|
555
592
|
# namely when application code returns a `nil` to GraphQL and it doesn't belong there.
|
@@ -559,15 +596,18 @@ module GraphQL
|
|
559
596
|
# TODO the code is trying to tell me something.
|
560
597
|
yield if block_given?
|
561
598
|
parent = selection_result.graphql_parent
|
562
|
-
name_in_parent = selection_result.graphql_result_name
|
563
599
|
if parent.nil? # This is a top-level result hash
|
564
600
|
@response = nil
|
565
601
|
else
|
566
|
-
|
602
|
+
name_in_parent = selection_result.graphql_result_name
|
603
|
+
is_non_null_in_parent = selection_result.graphql_is_non_null_in_parent
|
604
|
+
set_result(parent, name_in_parent, nil, false, is_non_null_in_parent)
|
567
605
|
set_graphql_dead(selection_result)
|
568
606
|
end
|
607
|
+
elsif is_child_result
|
608
|
+
selection_result.set_child_result(result_name, value)
|
569
609
|
else
|
570
|
-
selection_result
|
610
|
+
selection_result.set_leaf(result_name, value)
|
571
611
|
end
|
572
612
|
end
|
573
613
|
end
|
@@ -588,11 +628,10 @@ module GraphQL
|
|
588
628
|
end
|
589
629
|
|
590
630
|
def current_path
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
if path && (rn = ti[:current_result_name])
|
631
|
+
st = get_current_runtime_state
|
632
|
+
result = st.current_result
|
633
|
+
path = result && result.path
|
634
|
+
if path && (rn = st.current_result_name)
|
596
635
|
path = path.dup
|
597
636
|
path.push(rn)
|
598
637
|
end
|
@@ -604,13 +643,13 @@ module GraphQL
|
|
604
643
|
case value
|
605
644
|
when nil
|
606
645
|
if is_non_null
|
607
|
-
set_result(selection_result, result_name, nil) do
|
646
|
+
set_result(selection_result, result_name, nil, false, is_non_null) do
|
608
647
|
# This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
|
609
648
|
err = parent_type::InvalidNullError.new(parent_type, field, value)
|
610
649
|
schema.type_error(err, context)
|
611
650
|
end
|
612
651
|
else
|
613
|
-
set_result(selection_result, result_name, nil)
|
652
|
+
set_result(selection_result, result_name, nil, false, is_non_null)
|
614
653
|
end
|
615
654
|
HALT
|
616
655
|
when GraphQL::Error
|
@@ -623,7 +662,7 @@ module GraphQL
|
|
623
662
|
value.ast_node ||= ast_node
|
624
663
|
context.errors << value
|
625
664
|
if selection_result
|
626
|
-
set_result(selection_result, result_name, nil)
|
665
|
+
set_result(selection_result, result_name, nil, false, is_non_null)
|
627
666
|
end
|
628
667
|
end
|
629
668
|
HALT
|
@@ -677,9 +716,9 @@ module GraphQL
|
|
677
716
|
if selection_result
|
678
717
|
if list_type_at_all
|
679
718
|
result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
|
680
|
-
set_result(selection_result, result_name, result_without_errors)
|
719
|
+
set_result(selection_result, result_name, result_without_errors, false, is_non_null)
|
681
720
|
else
|
682
|
-
set_result(selection_result, result_name, nil)
|
721
|
+
set_result(selection_result, result_name, nil, false, is_non_null)
|
683
722
|
end
|
684
723
|
end
|
685
724
|
end
|
@@ -689,7 +728,7 @@ module GraphQL
|
|
689
728
|
end
|
690
729
|
when GraphQL::Execution::Interpreter::RawValue
|
691
730
|
# Write raw value directly to the response without resolving nested objects
|
692
|
-
set_result(selection_result, result_name, value.resolve)
|
731
|
+
set_result(selection_result, result_name, value.resolve, false, is_non_null)
|
693
732
|
HALT
|
694
733
|
else
|
695
734
|
value
|
@@ -717,11 +756,11 @@ module GraphQL
|
|
717
756
|
rescue StandardError => err
|
718
757
|
schema.handle_or_reraise(context, err)
|
719
758
|
end
|
720
|
-
set_result(selection_result, result_name, r)
|
759
|
+
set_result(selection_result, result_name, r, false, is_non_null)
|
721
760
|
r
|
722
761
|
when "UNION", "INTERFACE"
|
723
762
|
resolved_type_or_lazy = resolve_type(current_type, value)
|
724
|
-
after_lazy(resolved_type_or_lazy,
|
763
|
+
after_lazy(resolved_type_or_lazy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type_result|
|
725
764
|
if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
|
726
765
|
resolved_type, resolved_value = resolved_type_result
|
727
766
|
else
|
@@ -735,7 +774,7 @@ module GraphQL
|
|
735
774
|
err_class = current_type::UnresolvedTypeError
|
736
775
|
type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
|
737
776
|
schema.type_error(type_error, context)
|
738
|
-
set_result(selection_result, result_name, nil)
|
777
|
+
set_result(selection_result, result_name, nil, false, is_non_null)
|
739
778
|
nil
|
740
779
|
else
|
741
780
|
continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
|
@@ -747,11 +786,11 @@ module GraphQL
|
|
747
786
|
rescue GraphQL::ExecutionError => err
|
748
787
|
err
|
749
788
|
end
|
750
|
-
after_lazy(object_proxy,
|
789
|
+
after_lazy(object_proxy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
|
751
790
|
continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
|
752
791
|
if HALT != continue_value
|
753
|
-
response_hash = GraphQLResultHash.new(result_name, selection_result)
|
754
|
-
set_result(selection_result, result_name, response_hash)
|
792
|
+
response_hash = GraphQLResultHash.new(result_name, selection_result, is_non_null)
|
793
|
+
set_result(selection_result, result_name, response_hash, true, is_non_null)
|
755
794
|
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
756
795
|
# There are two possibilities for `gathered_selections`:
|
757
796
|
# 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
|
@@ -763,15 +802,24 @@ module GraphQL
|
|
763
802
|
# (Technically, it's possible that one of those entries _doesn't_ require isolation.)
|
764
803
|
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
765
804
|
if is_selection_array
|
766
|
-
this_result = GraphQLResultHash.new(result_name, selection_result)
|
805
|
+
this_result = GraphQLResultHash.new(result_name, selection_result, is_non_null)
|
767
806
|
final_result = response_hash
|
768
807
|
else
|
769
808
|
this_result = response_hash
|
770
809
|
final_result = nil
|
771
810
|
end
|
772
|
-
#
|
773
|
-
|
774
|
-
|
811
|
+
# reset this mutable state
|
812
|
+
# Unset `result_name` here because it's already included in the new response hash
|
813
|
+
st = get_current_runtime_state
|
814
|
+
st.current_object = continue_value
|
815
|
+
st.current_result_name = nil
|
816
|
+
st.current_result = this_result
|
817
|
+
|
818
|
+
# This is a less-frequent case; use a fast check since it's often not there.
|
819
|
+
if (directives = selections[:graphql_directives])
|
820
|
+
selections.delete(:graphql_directives)
|
821
|
+
end
|
822
|
+
call_method_on_directives(:resolve, continue_value, directives) do
|
775
823
|
evaluate_selections(
|
776
824
|
continue_value,
|
777
825
|
current_type,
|
@@ -790,21 +838,21 @@ module GraphQL
|
|
790
838
|
inner_type = current_type.of_type
|
791
839
|
# This is true for objects, unions, and interfaces
|
792
840
|
use_dataloader_job = !inner_type.unwrap.kind.input?
|
793
|
-
|
794
|
-
response_list
|
795
|
-
set_result(selection_result, result_name, response_list)
|
796
|
-
idx =
|
841
|
+
inner_type_non_null = inner_type.non_null?
|
842
|
+
response_list = GraphQLResultArray.new(result_name, selection_result, is_non_null)
|
843
|
+
set_result(selection_result, result_name, response_list, true, is_non_null)
|
844
|
+
idx = nil
|
797
845
|
list_value = begin
|
798
846
|
value.each do |inner_value|
|
799
|
-
|
847
|
+
idx ||= 0
|
800
848
|
this_idx = idx
|
801
849
|
idx += 1
|
802
850
|
if use_dataloader_job
|
803
851
|
@dataloader.append_job do
|
804
|
-
resolve_list_item(inner_value, inner_type, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
|
852
|
+
resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
|
805
853
|
end
|
806
854
|
else
|
807
|
-
resolve_list_item(inner_value, inner_type, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
|
855
|
+
resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
|
808
856
|
end
|
809
857
|
end
|
810
858
|
|
@@ -827,19 +875,22 @@ module GraphQL
|
|
827
875
|
ex_err
|
828
876
|
end
|
829
877
|
end
|
830
|
-
|
831
|
-
|
878
|
+
# Detect whether this error came while calling `.each` (before `idx` is set) or while running list *items* (after `idx` is set)
|
879
|
+
error_is_non_null = idx.nil? ? is_non_null : inner_type.non_null?
|
880
|
+
continue_value(list_value, owner_type, field, error_is_non_null, ast_node, result_name, selection_result)
|
832
881
|
else
|
833
882
|
raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
|
834
883
|
end
|
835
884
|
end
|
836
885
|
|
837
|
-
def resolve_list_item(inner_value, inner_type, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) # rubocop:disable Metrics/ParameterLists
|
838
|
-
|
886
|
+
def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) # rubocop:disable Metrics/ParameterLists
|
887
|
+
st = get_current_runtime_state
|
888
|
+
st.current_result_name = this_idx
|
889
|
+
st.current_result = response_list
|
839
890
|
call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
|
840
891
|
# This will update `response_list` with the lazy
|
841
|
-
after_lazy(inner_value,
|
842
|
-
continue_value = continue_value(inner_inner_value, owner_type, field,
|
892
|
+
after_lazy(inner_value, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
|
893
|
+
continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
|
843
894
|
if HALT != continue_value
|
844
895
|
continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
|
845
896
|
end
|
@@ -891,20 +942,25 @@ module GraphQL
|
|
891
942
|
true
|
892
943
|
end
|
893
944
|
|
894
|
-
def
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
if field
|
900
|
-
ti[:current_field] = field
|
901
|
-
end
|
902
|
-
if arguments
|
903
|
-
ti[:current_arguments] = arguments
|
945
|
+
def get_current_runtime_state
|
946
|
+
current_state = Thread.current[:__graphql_runtime_info] ||= begin
|
947
|
+
per_query_state = {}
|
948
|
+
per_query_state.compare_by_identity
|
949
|
+
per_query_state
|
904
950
|
end
|
905
|
-
|
906
|
-
|
907
|
-
|
951
|
+
|
952
|
+
current_state[@query] ||= CurrentState.new
|
953
|
+
end
|
954
|
+
|
955
|
+
def minimal_after_lazy(value, &block)
|
956
|
+
if lazy?(value)
|
957
|
+
GraphQL::Execution::Lazy.new do
|
958
|
+
result = @schema.sync_lazy(value)
|
959
|
+
# The returned result might also be lazy, so check it, too
|
960
|
+
minimal_after_lazy(result, &block)
|
961
|
+
end
|
962
|
+
else
|
963
|
+
yield(value)
|
908
964
|
end
|
909
965
|
end
|
910
966
|
|
@@ -913,16 +969,21 @@ module GraphQL
|
|
913
969
|
# @param eager [Boolean] Set to `true` for mutation root fields only
|
914
970
|
# @param trace [Boolean] If `false`, don't wrap this with field tracing
|
915
971
|
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
916
|
-
def after_lazy(lazy_obj,
|
972
|
+
def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
|
917
973
|
if lazy?(lazy_obj)
|
918
974
|
orig_result = result
|
919
975
|
lazy = GraphQL::Execution::Lazy.new(field: field) do
|
920
|
-
|
976
|
+
st = get_current_runtime_state
|
977
|
+
st.current_object = owner_object
|
978
|
+
st.current_field = field
|
979
|
+
st.current_arguments = arguments
|
980
|
+
st.current_result_name = result_name
|
981
|
+
st.current_result = orig_result
|
921
982
|
# Wrap the execution of _this_ method with tracing,
|
922
983
|
# but don't wrap the continuation below
|
923
984
|
inner_obj = begin
|
924
985
|
if trace
|
925
|
-
|
986
|
+
@current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
|
926
987
|
schema.sync_lazy(lazy_obj)
|
927
988
|
end
|
928
989
|
else
|
@@ -943,7 +1004,7 @@ module GraphQL
|
|
943
1004
|
if eager
|
944
1005
|
lazy.value
|
945
1006
|
else
|
946
|
-
set_result(result, result_name, lazy)
|
1007
|
+
set_result(result, result_name, lazy, false, false) # is_non_null is irrelevant here
|
947
1008
|
current_depth = 0
|
948
1009
|
while result
|
949
1010
|
current_depth += 1
|
@@ -953,7 +1014,7 @@ module GraphQL
|
|
953
1014
|
lazy
|
954
1015
|
end
|
955
1016
|
else
|
956
|
-
|
1017
|
+
# Don't need to reset state here because it _wasn't_ lazy.
|
957
1018
|
yield(lazy_obj)
|
958
1019
|
end
|
959
1020
|
end
|
@@ -968,23 +1029,24 @@ module GraphQL
|
|
968
1029
|
end
|
969
1030
|
|
970
1031
|
def delete_all_interpreter_context
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
1032
|
+
per_query_state = Thread.current[:__graphql_runtime_info]
|
1033
|
+
if per_query_state
|
1034
|
+
per_query_state.delete(@query)
|
1035
|
+
if per_query_state.size == 0
|
1036
|
+
Thread.current[:__graphql_runtime_info] = nil
|
1037
|
+
end
|
977
1038
|
end
|
1039
|
+
nil
|
978
1040
|
end
|
979
1041
|
|
980
1042
|
def resolve_type(type, value)
|
981
|
-
resolved_type, resolved_value =
|
1043
|
+
resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do
|
982
1044
|
query.resolve_type(type, value)
|
983
1045
|
end
|
984
1046
|
|
985
1047
|
if lazy?(resolved_type)
|
986
1048
|
GraphQL::Execution::Lazy.new do
|
987
|
-
|
1049
|
+
@current_trace.resolve_type_lazy(query: query, type: type, object: value) do
|
988
1050
|
schema.sync_lazy(resolved_type)
|
989
1051
|
end
|
990
1052
|
end
|
@@ -998,9 +1060,12 @@ module GraphQL
|
|
998
1060
|
end
|
999
1061
|
|
1000
1062
|
def lazy?(object)
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1063
|
+
obj_class = object.class
|
1064
|
+
is_lazy = @lazy_cache[obj_class]
|
1065
|
+
if is_lazy.nil?
|
1066
|
+
is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object)
|
1067
|
+
end
|
1068
|
+
is_lazy
|
1004
1069
|
end
|
1005
1070
|
end
|
1006
1071
|
end
|