graphql 2.1.0 → 2.1.1
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/generators/graphql/install/templates/base_mutation.erb +2 -0
- data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
- data/lib/generators/graphql/templates/base_argument.erb +2 -0
- data/lib/generators/graphql/templates/base_connection.erb +2 -0
- data/lib/generators/graphql/templates/base_edge.erb +2 -0
- data/lib/generators/graphql/templates/base_enum.erb +2 -0
- data/lib/generators/graphql/templates/base_field.erb +2 -0
- data/lib/generators/graphql/templates/base_input_object.erb +2 -0
- data/lib/generators/graphql/templates/base_interface.erb +2 -0
- data/lib/generators/graphql/templates/base_object.erb +2 -0
- data/lib/generators/graphql/templates/base_scalar.erb +2 -0
- data/lib/generators/graphql/templates/base_union.erb +2 -0
- data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
- data/lib/generators/graphql/templates/loader.erb +2 -0
- data/lib/generators/graphql/templates/mutation.erb +2 -0
- data/lib/generators/graphql/templates/node_type.erb +2 -0
- data/lib/generators/graphql/templates/query_type.erb +2 -0
- data/lib/generators/graphql/templates/schema.erb +2 -0
- data/lib/graphql/analysis/ast/analyzer.rb +7 -0
- data/lib/graphql/analysis/ast/visitor.rb +2 -2
- data/lib/graphql/analysis/ast.rb +15 -11
- data/lib/graphql/dataloader/source.rb +7 -0
- data/lib/graphql/dataloader.rb +9 -0
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +170 -0
- data/lib/graphql/execution/interpreter/runtime.rb +90 -251
- data/lib/graphql/execution/interpreter.rb +0 -6
- data/lib/graphql/execution/lookahead.rb +1 -1
- data/lib/graphql/introspection/dynamic_fields.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/language/block_string.rb +28 -16
- data/lib/graphql/language/definition_slice.rb +1 -1
- data/lib/graphql/language/document_from_schema_definition.rb +30 -19
- data/lib/graphql/language/nodes.rb +1 -1
- data/lib/graphql/language/printer.rb +88 -27
- data/lib/graphql/language/sanitized_printer.rb +6 -1
- data/lib/graphql/language/static_visitor.rb +167 -0
- data/lib/graphql/language/visitor.rb +2 -0
- data/lib/graphql/language.rb +1 -0
- data/lib/graphql/query/context/scoped_context.rb +101 -0
- data/lib/graphql/query/context.rb +32 -98
- data/lib/graphql/schema/directive/specified_by.rb +14 -0
- data/lib/graphql/schema/field/connection_extension.rb +1 -15
- data/lib/graphql/schema/field.rb +6 -3
- data/lib/graphql/schema/has_single_input_argument.rb +156 -0
- data/lib/graphql/schema/introspection_system.rb +2 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
- data/lib/graphql/schema/member/has_arguments.rb +14 -2
- data/lib/graphql/schema/member/has_fields.rb +4 -1
- data/lib/graphql/schema/member/has_interfaces.rb +21 -7
- data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
- data/lib/graphql/schema/resolver.rb +4 -0
- data/lib/graphql/schema/scalar.rb +3 -3
- data/lib/graphql/schema/warden.rb +20 -3
- data/lib/graphql/schema.rb +19 -2
- data/lib/graphql/static_validation/all_rules.rb +1 -1
- data/lib/graphql/static_validation/base_visitor.rb +1 -1
- data/lib/graphql/static_validation/literal_validator.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
- data/lib/graphql/static_validation/validation_context.rb +5 -2
- data/lib/graphql/tracing/appoptics_trace.rb +2 -2
- data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +1 -1
- metadata +7 -2
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require "graphql/execution/interpreter/runtime/graphql_result"
|
2
3
|
|
3
4
|
module GraphQL
|
4
5
|
module Execution
|
@@ -22,167 +23,6 @@ module GraphQL
|
|
22
23
|
:current_arguments, :current_field, :current_object, :was_authorized_by_scope_items
|
23
24
|
end
|
24
25
|
|
25
|
-
module GraphQLResult
|
26
|
-
def initialize(result_name, parent_result, is_non_null_in_parent)
|
27
|
-
@graphql_parent = parent_result
|
28
|
-
if parent_result && parent_result.graphql_dead
|
29
|
-
@graphql_dead = true
|
30
|
-
end
|
31
|
-
@graphql_result_name = result_name
|
32
|
-
@graphql_is_non_null_in_parent = is_non_null_in_parent
|
33
|
-
# Jump through some hoops to avoid creating this duplicate storage if at all possible.
|
34
|
-
@graphql_metadata = nil
|
35
|
-
end
|
36
|
-
|
37
|
-
def path
|
38
|
-
@path ||= build_path([])
|
39
|
-
end
|
40
|
-
|
41
|
-
def build_path(path_array)
|
42
|
-
graphql_result_name && path_array.unshift(graphql_result_name)
|
43
|
-
@graphql_parent ? @graphql_parent.build_path(path_array) : path_array
|
44
|
-
end
|
45
|
-
|
46
|
-
attr_accessor :graphql_dead
|
47
|
-
attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent
|
48
|
-
|
49
|
-
# @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
|
50
|
-
attr_accessor :graphql_result_data
|
51
|
-
end
|
52
|
-
|
53
|
-
class GraphQLResultHash
|
54
|
-
def initialize(_result_name, _parent_result, _is_non_null_in_parent)
|
55
|
-
super
|
56
|
-
@graphql_result_data = {}
|
57
|
-
end
|
58
|
-
|
59
|
-
include GraphQLResult
|
60
|
-
|
61
|
-
attr_accessor :graphql_merged_into
|
62
|
-
|
63
|
-
def set_leaf(key, value)
|
64
|
-
# This is a hack.
|
65
|
-
# Basically, this object is merged into the root-level result at some point.
|
66
|
-
# But the problem is, some lazies are created whose closures retain reference to _this_
|
67
|
-
# object. When those lazies are resolved, they cause an update to this object.
|
68
|
-
#
|
69
|
-
# In order to return a proper top-level result, we have to update that top-level result object.
|
70
|
-
# In order to return a proper partial result (eg, for a directive), we have to update this object, too.
|
71
|
-
# Yowza.
|
72
|
-
if (t = @graphql_merged_into)
|
73
|
-
t.set_leaf(key, value)
|
74
|
-
end
|
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
|
-
|
80
|
-
value
|
81
|
-
end
|
82
|
-
|
83
|
-
def set_child_result(key, value)
|
84
|
-
if (t = @graphql_merged_into)
|
85
|
-
t.set_child_result(key, value)
|
86
|
-
end
|
87
|
-
@graphql_result_data[key] = value.graphql_result_data
|
88
|
-
# If we encounter some part of this response that requires metadata tracking,
|
89
|
-
# then create the metadata hash if necessary. It will be kept up-to-date after this.
|
90
|
-
(@graphql_metadata ||= @graphql_result_data.dup)[key] = value
|
91
|
-
value
|
92
|
-
end
|
93
|
-
|
94
|
-
def delete(key)
|
95
|
-
@graphql_metadata && @graphql_metadata.delete(key)
|
96
|
-
@graphql_result_data.delete(key)
|
97
|
-
end
|
98
|
-
|
99
|
-
def each
|
100
|
-
(@graphql_metadata || @graphql_result_data).each { |k, v| yield(k, v) }
|
101
|
-
end
|
102
|
-
|
103
|
-
def values
|
104
|
-
(@graphql_metadata || @graphql_result_data).values
|
105
|
-
end
|
106
|
-
|
107
|
-
def key?(k)
|
108
|
-
@graphql_result_data.key?(k)
|
109
|
-
end
|
110
|
-
|
111
|
-
def [](k)
|
112
|
-
(@graphql_metadata || @graphql_result_data)[k]
|
113
|
-
end
|
114
|
-
|
115
|
-
def merge_into(into_result)
|
116
|
-
self.each do |key, value|
|
117
|
-
case value
|
118
|
-
when GraphQLResultHash
|
119
|
-
next_into = into_result[key]
|
120
|
-
if next_into
|
121
|
-
value.merge_into(next_into)
|
122
|
-
else
|
123
|
-
into_result.set_child_result(key, value)
|
124
|
-
end
|
125
|
-
when GraphQLResultArray
|
126
|
-
# There's no special handling of arrays because currently, there's no way to split the execution
|
127
|
-
# of a list over several concurrent flows.
|
128
|
-
next_result.set_child_result(key, value)
|
129
|
-
else
|
130
|
-
# We have to assume that, since this passed the `fields_will_merge` selection,
|
131
|
-
# that the old and new values are the same.
|
132
|
-
into_result.set_leaf(key, value)
|
133
|
-
end
|
134
|
-
end
|
135
|
-
@graphql_merged_into = into_result
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
class GraphQLResultArray
|
140
|
-
include GraphQLResult
|
141
|
-
|
142
|
-
def initialize(_result_name, _parent_result, _is_non_null_in_parent)
|
143
|
-
super
|
144
|
-
@graphql_result_data = []
|
145
|
-
end
|
146
|
-
|
147
|
-
def graphql_skip_at(index)
|
148
|
-
# Mark this index as dead. It's tricky because some indices may already be storing
|
149
|
-
# `Lazy`s. So the runtime is still holding indexes _before_ skipping,
|
150
|
-
# this object has to coordinate incoming writes to account for any already-skipped indices.
|
151
|
-
@skip_indices ||= []
|
152
|
-
@skip_indices << index
|
153
|
-
offset_by = @skip_indices.count { |skipped_idx| skipped_idx < index}
|
154
|
-
delete_at_index = index - offset_by
|
155
|
-
@graphql_metadata && @graphql_metadata.delete_at(delete_at_index)
|
156
|
-
@graphql_result_data.delete_at(delete_at_index)
|
157
|
-
end
|
158
|
-
|
159
|
-
def set_leaf(idx, value)
|
160
|
-
if @skip_indices
|
161
|
-
offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
|
162
|
-
idx -= offset_by
|
163
|
-
end
|
164
|
-
@graphql_result_data[idx] = value
|
165
|
-
@graphql_metadata && @graphql_metadata[idx] = value
|
166
|
-
value
|
167
|
-
end
|
168
|
-
|
169
|
-
def set_child_result(idx, value)
|
170
|
-
if @skip_indices
|
171
|
-
offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
|
172
|
-
idx -= offset_by
|
173
|
-
end
|
174
|
-
@graphql_result_data[idx] = value.graphql_result_data
|
175
|
-
# If we encounter some part of this response that requires metadata tracking,
|
176
|
-
# then create the metadata hash if necessary. It will be kept up-to-date after this.
|
177
|
-
(@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
|
178
|
-
value
|
179
|
-
end
|
180
|
-
|
181
|
-
def values
|
182
|
-
(@graphql_metadata || @graphql_result_data)
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
26
|
# @return [GraphQL::Query]
|
187
27
|
attr_reader :query
|
188
28
|
|
@@ -209,15 +49,16 @@ module GraphQL
|
|
209
49
|
@runtime_directive_names << name
|
210
50
|
end
|
211
51
|
end
|
212
|
-
# A cache of { Class => { String => Schema::Field } }
|
213
|
-
# Which assumes that MyObject.get_field("myField") will return the same field
|
214
|
-
# during the lifetime of a query
|
215
|
-
@fields_cache = Hash.new { |h, k| h[k] = {} }
|
216
|
-
# this can by by-identity since owners are the same object, but not the sub-hash, which uses strings.
|
217
|
-
@fields_cache.compare_by_identity
|
218
52
|
# { Class => Boolean }
|
219
53
|
@lazy_cache = {}
|
220
54
|
@lazy_cache.compare_by_identity
|
55
|
+
|
56
|
+
@gathered_selections_cache = Hash.new { |h, k|
|
57
|
+
cache = {}
|
58
|
+
cache.compare_by_identity
|
59
|
+
h[k] = cache
|
60
|
+
}
|
61
|
+
@gathered_selections_cache.compare_by_identity
|
221
62
|
end
|
222
63
|
|
223
64
|
def final_result
|
@@ -257,7 +98,7 @@ module GraphQL
|
|
257
98
|
@response = nil
|
258
99
|
else
|
259
100
|
call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
|
260
|
-
gathered_selections = gather_selections(runtime_object, root_type, root_operation.selections)
|
101
|
+
gathered_selections = gather_selections(runtime_object, root_type, nil, root_operation.selections)
|
261
102
|
# This is kind of a hack -- `gathered_selections` is an Array if any of the selections
|
262
103
|
# require isolation during execution (because of runtime directives). In that case,
|
263
104
|
# make a new, isolated result hash for writing the result into. (That isolated response
|
@@ -277,6 +118,7 @@ module GraphQL
|
|
277
118
|
@dataloader.append_job {
|
278
119
|
st = get_current_runtime_state
|
279
120
|
st.current_object = query.root_value
|
121
|
+
st.current_result_name = nil
|
280
122
|
st.current_result = selection_response
|
281
123
|
# This is a less-frequent case; use a fast check since it's often not there.
|
282
124
|
if (directives = selections[:graphql_directives])
|
@@ -291,20 +133,28 @@ module GraphQL
|
|
291
133
|
selection_response,
|
292
134
|
final_response,
|
293
135
|
nil,
|
136
|
+
st,
|
294
137
|
)
|
295
138
|
end
|
296
139
|
}
|
297
140
|
end
|
298
141
|
end
|
299
142
|
end
|
300
|
-
delete_all_interpreter_context
|
301
143
|
nil
|
302
144
|
end
|
303
145
|
|
304
|
-
def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name =
|
146
|
+
def gather_selections(owner_object, owner_type, ast_node_for_caching, selections, selections_to_run = nil, selections_by_name = nil)
|
147
|
+
if ast_node_for_caching && (cached_selections = @gathered_selections_cache[ast_node_for_caching][owner_type])
|
148
|
+
return cached_selections
|
149
|
+
end
|
150
|
+
selections_by_name ||= {} # allocate this default here so we check the cache first
|
151
|
+
|
152
|
+
should_cache = true
|
153
|
+
|
305
154
|
selections.each do |node|
|
306
155
|
# Skip gathering this if the directive says so
|
307
156
|
if !directives_include?(node, owner_object, owner_type)
|
157
|
+
should_cache = false
|
308
158
|
next
|
309
159
|
end
|
310
160
|
|
@@ -329,6 +179,7 @@ module GraphQL
|
|
329
179
|
if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
|
330
180
|
next_selections = {}
|
331
181
|
next_selections[:graphql_directives] = node.directives
|
182
|
+
should_cache = false
|
332
183
|
if selections_to_run
|
333
184
|
selections_to_run << next_selections
|
334
185
|
else
|
@@ -346,41 +197,41 @@ module GraphQL
|
|
346
197
|
type_defn = schema.get_type(node.type.name, context)
|
347
198
|
|
348
199
|
if query.warden.possible_types(type_defn).include?(owner_type)
|
349
|
-
gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
|
200
|
+
gather_selections(owner_object, owner_type, nil, node.selections, selections_to_run, next_selections)
|
350
201
|
end
|
351
202
|
else
|
352
203
|
# it's an untyped fragment, definitely continue
|
353
|
-
gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
|
204
|
+
gather_selections(owner_object, owner_type, nil, node.selections, selections_to_run, next_selections)
|
354
205
|
end
|
355
206
|
when GraphQL::Language::Nodes::FragmentSpread
|
356
207
|
fragment_def = query.fragments[node.name]
|
357
208
|
type_defn = query.get_type(fragment_def.type.name)
|
358
209
|
if query.warden.possible_types(type_defn).include?(owner_type)
|
359
|
-
gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
|
210
|
+
gather_selections(owner_object, owner_type, nil, fragment_def.selections, selections_to_run, next_selections)
|
360
211
|
end
|
361
212
|
else
|
362
213
|
raise "Invariant: unexpected selection class: #{node.class}"
|
363
214
|
end
|
364
215
|
end
|
365
216
|
end
|
366
|
-
selections_to_run || selections_by_name
|
217
|
+
result = selections_to_run || selections_by_name
|
218
|
+
if should_cache
|
219
|
+
@gathered_selections_cache[ast_node_for_caching][owner_type] = result
|
220
|
+
end
|
221
|
+
result
|
367
222
|
end
|
368
223
|
|
369
224
|
NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
|
370
225
|
|
371
226
|
# @return [void]
|
372
|
-
def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
|
373
|
-
st = get_current_runtime_state
|
374
|
-
st.current_object = owner_object
|
375
|
-
st.current_result_name = nil
|
376
|
-
st.current_result = selections_result
|
377
|
-
|
227
|
+
def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object, runtime_state) # rubocop:disable Metrics/ParameterLists
|
378
228
|
finished_jobs = 0
|
379
229
|
enqueued_jobs = gathered_selections.size
|
380
230
|
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
381
231
|
@dataloader.append_job {
|
232
|
+
runtime_state = get_current_runtime_state
|
382
233
|
evaluate_selection(
|
383
|
-
result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
|
234
|
+
result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object, runtime_state
|
384
235
|
)
|
385
236
|
finished_jobs += 1
|
386
237
|
if target_result && finished_jobs == enqueued_jobs
|
@@ -391,7 +242,9 @@ module GraphQL
|
|
391
242
|
# so it wouldn't get to the `Resolve` call that happens below.
|
392
243
|
# So instead trigger a run from this outer context.
|
393
244
|
if is_eager_selection
|
245
|
+
@dataloader.clear_cache
|
394
246
|
@dataloader.run
|
247
|
+
@dataloader.clear_cache
|
395
248
|
end
|
396
249
|
end
|
397
250
|
|
@@ -399,7 +252,7 @@ module GraphQL
|
|
399
252
|
end
|
400
253
|
|
401
254
|
# @return [void]
|
402
|
-
def evaluate_selection(result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
|
255
|
+
def evaluate_selection(result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_field, selections_result, parent_object, runtime_state) # rubocop:disable Metrics/ParameterLists
|
403
256
|
return if dead_result?(selections_result)
|
404
257
|
# As a performance optimization, the hash key will be a `Node` if
|
405
258
|
# there's only one selection of the field. But if there are multiple
|
@@ -412,51 +265,37 @@ module GraphQL
|
|
412
265
|
ast_node = field_ast_nodes_or_ast_node
|
413
266
|
end
|
414
267
|
field_name = ast_node.name
|
415
|
-
|
416
|
-
# because of how `is_introspection` is used to call `.authorized_new` later on.
|
417
|
-
field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name, @context)
|
418
|
-
is_introspection = false
|
419
|
-
if field_defn.nil?
|
420
|
-
field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
|
421
|
-
is_introspection = true
|
422
|
-
entry_point_field
|
423
|
-
elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
|
424
|
-
is_introspection = true
|
425
|
-
dynamic_field
|
426
|
-
else
|
427
|
-
raise "Invariant: no field for #{owner_type}.#{field_name}"
|
428
|
-
end
|
429
|
-
end
|
268
|
+
field_defn = query.warden.get_field(owner_type, field_name)
|
430
269
|
|
431
270
|
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
st.current_result_name = result_name
|
271
|
+
runtime_state.current_field = field_defn
|
272
|
+
runtime_state.current_result = selections_result
|
273
|
+
runtime_state.current_result_name = result_name
|
436
274
|
|
437
|
-
if
|
275
|
+
if field_defn.dynamic_introspection
|
438
276
|
owner_object = field_defn.owner.wrap(owner_object, context)
|
439
277
|
end
|
278
|
+
|
440
279
|
return_type = field_defn.type
|
441
|
-
|
442
|
-
if total_args_count == 0
|
280
|
+
if !field_defn.any_arguments?
|
443
281
|
resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
|
444
282
|
if field_defn.extras.size == 0
|
445
283
|
evaluate_selection_with_resolved_keyword_args(
|
446
|
-
NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type.non_null
|
284
|
+
NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type.non_null?, runtime_state
|
447
285
|
)
|
448
286
|
else
|
449
|
-
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type)
|
287
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, runtime_state)
|
450
288
|
end
|
451
289
|
else
|
452
290
|
@query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
|
453
|
-
|
291
|
+
runtime_state = get_current_runtime_state # This might be in a different fiber
|
292
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, runtime_state)
|
454
293
|
end
|
455
294
|
end
|
456
295
|
end
|
457
296
|
|
458
|
-
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
|
459
|
-
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|
|
297
|
+
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, runtime_state) # rubocop:disable Metrics/ParameterLists
|
298
|
+
after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_arguments, runtime_state|
|
460
299
|
return_type_non_null = return_type.non_null?
|
461
300
|
if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
|
462
301
|
continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
|
@@ -508,17 +347,16 @@ module GraphQL
|
|
508
347
|
resolved_arguments.keyword_arguments
|
509
348
|
end
|
510
349
|
|
511
|
-
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)
|
350
|
+
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, runtime_state)
|
512
351
|
end
|
513
352
|
end
|
514
353
|
|
515
|
-
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
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
st.current_result = selection_result
|
354
|
+
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, runtime_state) # rubocop:disable Metrics/ParameterLists
|
355
|
+
runtime_state.current_field = field_defn
|
356
|
+
runtime_state.current_object = object
|
357
|
+
runtime_state.current_arguments = resolved_arguments
|
358
|
+
runtime_state.current_result_name = result_name
|
359
|
+
runtime_state.current_result = selection_result
|
522
360
|
# Optimize for the case that field is selected only once
|
523
361
|
if field_ast_nodes.nil? || field_ast_nodes.size == 1
|
524
362
|
next_selections = ast_node.selections
|
@@ -547,13 +385,12 @@ module GraphQL
|
|
547
385
|
ex_err
|
548
386
|
end
|
549
387
|
end
|
550
|
-
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|
|
388
|
+
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|
|
551
389
|
continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
|
552
390
|
if HALT != continue_value
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result, was_scoped)
|
391
|
+
was_scoped = runtime_state.was_authorized_by_scope_items
|
392
|
+
runtime_state.was_authorized_by_scope_items = nil
|
393
|
+
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)
|
557
394
|
end
|
558
395
|
end
|
559
396
|
end
|
@@ -733,7 +570,7 @@ module GraphQL
|
|
733
570
|
# Location information from `path` and `ast_node`.
|
734
571
|
#
|
735
572
|
# @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
|
736
|
-
def continue_field(value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped) # rubocop:disable Metrics/ParameterLists
|
573
|
+
def continue_field(value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists
|
737
574
|
if current_type.non_null?
|
738
575
|
current_type = current_type.of_type
|
739
576
|
is_non_null = true
|
@@ -750,7 +587,7 @@ module GraphQL
|
|
750
587
|
r
|
751
588
|
when "UNION", "INTERFACE"
|
752
589
|
resolved_type_or_lazy = resolve_type(current_type, value)
|
753
|
-
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|
|
590
|
+
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, runtime_state: runtime_state) do |resolved_type_result, runtime_state|
|
754
591
|
if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
|
755
592
|
resolved_type, resolved_value = resolved_type_result
|
756
593
|
else
|
@@ -767,7 +604,7 @@ module GraphQL
|
|
767
604
|
set_result(selection_result, result_name, nil, false, is_non_null)
|
768
605
|
nil
|
769
606
|
else
|
770
|
-
continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped)
|
607
|
+
continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped, runtime_state)
|
771
608
|
end
|
772
609
|
end
|
773
610
|
when "OBJECT"
|
@@ -776,12 +613,13 @@ module GraphQL
|
|
776
613
|
rescue GraphQL::ExecutionError => err
|
777
614
|
err
|
778
615
|
end
|
779
|
-
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|
|
616
|
+
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, runtime_state: runtime_state) do |inner_object, runtime_state|
|
780
617
|
continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
|
781
618
|
if HALT != continue_value
|
782
619
|
response_hash = GraphQLResultHash.new(result_name, selection_result, is_non_null)
|
783
620
|
set_result(selection_result, result_name, response_hash, true, is_non_null)
|
784
|
-
|
621
|
+
|
622
|
+
gathered_selections = gather_selections(continue_value, current_type, ast_node, next_selections)
|
785
623
|
# There are two possibilities for `gathered_selections`:
|
786
624
|
# 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
|
787
625
|
# This case is handled below, and the result can be written right into the main `response_hash` above.
|
@@ -800,10 +638,9 @@ module GraphQL
|
|
800
638
|
end
|
801
639
|
# reset this mutable state
|
802
640
|
# Unset `result_name` here because it's already included in the new response hash
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
st.current_result = this_result
|
641
|
+
runtime_state.current_object = continue_value
|
642
|
+
runtime_state.current_result_name = nil
|
643
|
+
runtime_state.current_result = this_result
|
807
644
|
# This is a less-frequent case; use a fast check since it's often not there.
|
808
645
|
if (directives = selections[:graphql_directives])
|
809
646
|
selections.delete(:graphql_directives)
|
@@ -817,6 +654,7 @@ module GraphQL
|
|
817
654
|
this_result,
|
818
655
|
final_result,
|
819
656
|
owner_object.object,
|
657
|
+
runtime_state,
|
820
658
|
)
|
821
659
|
this_result
|
822
660
|
end
|
@@ -838,10 +676,10 @@ module GraphQL
|
|
838
676
|
idx += 1
|
839
677
|
if use_dataloader_job
|
840
678
|
@dataloader.append_job do
|
841
|
-
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, was_scoped)
|
679
|
+
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, was_scoped, runtime_state)
|
842
680
|
end
|
843
681
|
else
|
844
|
-
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, was_scoped)
|
682
|
+
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, was_scoped, runtime_state)
|
845
683
|
end
|
846
684
|
end
|
847
685
|
|
@@ -872,16 +710,15 @@ module GraphQL
|
|
872
710
|
end
|
873
711
|
end
|
874
712
|
|
875
|
-
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, was_scoped) # rubocop:disable Metrics/ParameterLists
|
876
|
-
|
877
|
-
|
878
|
-
st.current_result = response_list
|
713
|
+
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, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists
|
714
|
+
runtime_state.current_result_name = this_idx
|
715
|
+
runtime_state.current_result = response_list
|
879
716
|
call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
|
880
717
|
# This will update `response_list` with the lazy
|
881
|
-
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|
|
718
|
+
after_lazy(inner_value, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list, runtime_state: runtime_state) do |inner_inner_value, runtime_state|
|
882
719
|
continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
|
883
720
|
if HALT != continue_value
|
884
|
-
continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list, was_scoped)
|
721
|
+
continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list, was_scoped, runtime_state)
|
885
722
|
end
|
886
723
|
end
|
887
724
|
end
|
@@ -958,19 +795,21 @@ module GraphQL
|
|
958
795
|
# @param eager [Boolean] Set to `true` for mutation root fields only
|
959
796
|
# @param trace [Boolean] If `false`, don't wrap this with field tracing
|
960
797
|
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
961
|
-
def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
|
798
|
+
def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, runtime_state:, trace: true, &block)
|
962
799
|
if lazy?(lazy_obj)
|
963
800
|
orig_result = result
|
964
|
-
|
965
|
-
was_authorized_by_scope_items = st.was_authorized_by_scope_items
|
801
|
+
was_authorized_by_scope_items = runtime_state.was_authorized_by_scope_items
|
966
802
|
lazy = GraphQL::Execution::Lazy.new(field: field) do
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
803
|
+
# This block might be called in a new fiber;
|
804
|
+
# In that case, this will initialize a new state
|
805
|
+
# to avoid conflicting with the parent fiber.
|
806
|
+
runtime_state = get_current_runtime_state
|
807
|
+
runtime_state.current_object = owner_object
|
808
|
+
runtime_state.current_field = field
|
809
|
+
runtime_state.current_arguments = arguments
|
810
|
+
runtime_state.current_result_name = result_name
|
811
|
+
runtime_state.current_result = orig_result
|
812
|
+
runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items
|
974
813
|
# Wrap the execution of _this_ method with tracing,
|
975
814
|
# but don't wrap the continuation below
|
976
815
|
inner_obj = begin
|
@@ -990,7 +829,7 @@ module GraphQL
|
|
990
829
|
ex_err
|
991
830
|
end
|
992
831
|
end
|
993
|
-
yield(inner_obj)
|
832
|
+
yield(inner_obj, runtime_state)
|
994
833
|
end
|
995
834
|
|
996
835
|
if eager
|
@@ -1007,7 +846,7 @@ module GraphQL
|
|
1007
846
|
end
|
1008
847
|
else
|
1009
848
|
# Don't need to reset state here because it _wasn't_ lazy.
|
1010
|
-
yield(lazy_obj)
|
849
|
+
yield(lazy_obj, runtime_state)
|
1011
850
|
end
|
1012
851
|
end
|
1013
852
|
|
@@ -98,12 +98,6 @@ module GraphQL
|
|
98
98
|
multiplex.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
|
99
99
|
Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader)
|
100
100
|
end
|
101
|
-
queries.each do |query|
|
102
|
-
runtime = query.context.namespace(:interpreter_runtime)[:runtime]
|
103
|
-
if runtime
|
104
|
-
runtime.delete_all_interpreter_context
|
105
|
-
end
|
106
|
-
end
|
107
101
|
}
|
108
102
|
multiplex.dataloader.run
|
109
103
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
module GraphQL
|
3
3
|
module Introspection
|
4
4
|
class DynamicFields < Introspection::BaseObject
|
5
|
-
field :__typename, String, "The name of this type", null: false
|
5
|
+
field :__typename, String, "The name of this type", null: false, dynamic_introspection: true
|
6
6
|
|
7
7
|
def __typename
|
8
8
|
object.class.graphql_name
|
@@ -2,8 +2,8 @@
|
|
2
2
|
module GraphQL
|
3
3
|
module Introspection
|
4
4
|
class EntryPoints < Introspection::BaseObject
|
5
|
-
field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false
|
6
|
-
field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system" do
|
5
|
+
field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false, dynamic_introspection: true
|
6
|
+
field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", dynamic_introspection: true do
|
7
7
|
argument :name, String
|
8
8
|
end
|
9
9
|
|