graphql 2.6.2 → 2.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/generators/graphql/field_extractor.rb +17 -1
- data/lib/graphql/analysis/query_complexity.rb +31 -15
- data/lib/graphql/backtrace/table.rb +10 -1
- data/lib/graphql/current.rb +7 -1
- data/lib/graphql/dataloader/async_dataloader.rb +310 -60
- data/lib/graphql/dataloader/source.rb +18 -10
- data/lib/graphql/dataloader.rb +1 -1
- data/lib/graphql/execution/directive_checks.rb +2 -0
- data/lib/graphql/execution/field_resolve_step.rb +84 -18
- data/lib/graphql/execution/finalize.rb +10 -9
- data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
- data/lib/graphql/execution/interpreter/runtime.rb +0 -6
- data/lib/graphql/execution/load_argument_step.rb +6 -0
- data/lib/graphql/execution/prepare_object_step.rb +14 -4
- data/lib/graphql/execution/runner.rb +7 -1
- data/lib/graphql/execution/selections_step.rb +6 -2
- data/lib/graphql/execution_error.rb +4 -0
- data/lib/graphql/language.rb +8 -2
- data/lib/graphql/schema/argument.rb +1 -1
- data/lib/graphql/schema/interface.rb +1 -1
- data/lib/graphql/schema/introspection_system.rb +6 -21
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
- data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
- data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
- data/lib/graphql/schema/validator/format_validator.rb +3 -3
- data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
- data/lib/graphql/schema/validator/length_validator.rb +6 -6
- data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
- data/lib/graphql/schema/validator/required_validator.rb +6 -4
- data/lib/graphql/schema/validator.rb +9 -0
- data/lib/graphql/schema/visibility/profile.rb +6 -4
- data/lib/graphql/schema/visibility.rb +30 -22
- data/lib/graphql/schema.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8ece536c0702c8789a3a5c79de82af4bd2ff806a3209d0ac4da88fc968629d19
|
|
4
|
+
data.tar.gz: 0063f1ea552e7782029083eb027138a0725af89a65265dc643469f2227184134
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ee406b8ad164004af45d98e3b5dc70d69396f008a80cc68ef83a4bff212698c0a181644dc2b513e8bd1e71d2baf7d1ba2901ad05f497199556b171a6dfb11ab
|
|
7
|
+
data.tar.gz: 2ffd4e01742bd3b0feb207a316b1935d1edff05be8b28afb1c42e5ba47b4df916df802ee15617bb30bd45cdf6fd4f7a59c22b5d73cbabb3d8ee6658f3246d8ef
|
|
@@ -6,7 +6,23 @@ module Graphql
|
|
|
6
6
|
module FieldExtractor
|
|
7
7
|
def fields
|
|
8
8
|
columns = []
|
|
9
|
-
|
|
9
|
+
if (model_columns = klass&.columns)
|
|
10
|
+
filter = if defined?(ActiveSupport::ParameterFilter)
|
|
11
|
+
fp = if defined?(Rails) && Rails.application && (app_config = Rails.application.config.filter_parameters).present? && !app_config.empty?
|
|
12
|
+
app_config
|
|
13
|
+
elsif ActiveSupport.respond_to?(:filter_parameters)
|
|
14
|
+
ActiveSupport.filter_parameters
|
|
15
|
+
else
|
|
16
|
+
[]
|
|
17
|
+
end
|
|
18
|
+
ActiveSupport::ParameterFilter.new(fp, mask: nil)
|
|
19
|
+
else
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
columns += model_columns
|
|
23
|
+
.select { |c| filter ? filter.filter_param(c.name, c.name) : true }
|
|
24
|
+
.map { |c| generate_column_string(c) }
|
|
25
|
+
end
|
|
10
26
|
columns + custom_fields
|
|
11
27
|
end
|
|
12
28
|
|
|
@@ -9,6 +9,8 @@ module GraphQL
|
|
|
9
9
|
super
|
|
10
10
|
@skip_introspection_fields = !query.schema.max_complexity_count_introspection_fields
|
|
11
11
|
@complexities_on_type_by_query = {}
|
|
12
|
+
@intersect_cache = Hash.new { |h, k| h[k] = {}.compare_by_identity }.compare_by_identity
|
|
13
|
+
@possible_types_cache = {}.compare_by_identity
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
# Override this method to use the complexity result
|
|
@@ -27,14 +29,14 @@ module GraphQL
|
|
|
27
29
|
future_complexity
|
|
28
30
|
end
|
|
29
31
|
when nil
|
|
30
|
-
subject.logger.warn <<~
|
|
32
|
+
subject.logger.warn <<~MESSAGE
|
|
31
33
|
GraphQL-Ruby's complexity cost system is getting some "breaking fixes" in a future version. See the migration notes at https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#complexity_cost_calculation_mode_for-class_method
|
|
32
34
|
|
|
33
35
|
To opt into the future behavior, configure your schema (#{subject.schema.name ? subject.schema.name : subject.schema.ancestors}) with:
|
|
34
36
|
|
|
35
37
|
complexity_cost_calculation_mode(:future) # or `:legacy`, `:compare`
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
MESSAGE
|
|
38
40
|
max_possible_complexity(mode: :legacy)
|
|
39
41
|
else
|
|
40
42
|
raise ArgumentError, "Expected `:future`, `:legacy`, `:compare`, or `nil` from `#{query.schema}.complexity_cost_calculation_mode_for` but got: #{query.schema.complexity_cost_calculation_mode.inspect}"
|
|
@@ -159,8 +161,22 @@ module GraphQL
|
|
|
159
161
|
|
|
160
162
|
def types_intersect?(query, a, b)
|
|
161
163
|
return true if a == b
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
|
|
165
|
+
if a.object_id < b.object_id
|
|
166
|
+
first_cache = @intersect_cache[a]
|
|
167
|
+
second_key = b
|
|
168
|
+
else
|
|
169
|
+
first_cache = @intersect_cache[b]
|
|
170
|
+
second_key = a
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
if first_cache.key?(second_key)
|
|
174
|
+
first_cache[second_key]
|
|
175
|
+
else
|
|
176
|
+
a_types = @possible_types_cache[a] ||= query.types.possible_types(a).to_set
|
|
177
|
+
b_types = @possible_types_cache[b] ||= query.types.possible_types(b).to_set
|
|
178
|
+
first_cache[second_key] = a_types.intersect?(b_types)
|
|
179
|
+
end
|
|
164
180
|
end
|
|
165
181
|
|
|
166
182
|
# A hook which is called whenever a field's max complexity is calculated.
|
|
@@ -175,18 +191,16 @@ module GraphQL
|
|
|
175
191
|
# @param inner_selections [Array<Hash<String, ScopedTypeComplexity>>] Field selections for a scope
|
|
176
192
|
# @return [Integer] Total complexity value for all these selections in the parent scope
|
|
177
193
|
def merged_max_complexity(query, inner_selections)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
194
|
+
child_scopes_by_key = {}
|
|
195
|
+
inner_selections.each do |inner_selection|
|
|
196
|
+
inner_selection.each do |k, v|
|
|
197
|
+
scopes = child_scopes_by_key[k] ||= []
|
|
198
|
+
scopes << v
|
|
199
|
+
end
|
|
182
200
|
end
|
|
183
|
-
|
|
184
201
|
# Add up the total cost for each unique field name's coalesced selections
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
# all keys come with at least one scope.
|
|
188
|
-
child_scopes = inner_selections.filter_map { _1[field_key] }
|
|
189
|
-
|
|
202
|
+
total = 0
|
|
203
|
+
child_scopes_by_key.each do |field_key, child_scopes|
|
|
190
204
|
# Compute maximum possible cost of child selections;
|
|
191
205
|
# composites merge their maximums, while leaf scopes are always zero.
|
|
192
206
|
# FieldsWillMerge validation assures all scopes are uniformly composite or leaf.
|
|
@@ -214,8 +228,10 @@ module GraphQL
|
|
|
214
228
|
child_complexity: maximum_children_cost,
|
|
215
229
|
)
|
|
216
230
|
|
|
217
|
-
total
|
|
231
|
+
total += maximum_cost
|
|
218
232
|
end
|
|
233
|
+
|
|
234
|
+
total
|
|
219
235
|
end
|
|
220
236
|
|
|
221
237
|
def legacy_merged_max_complexity(query, inner_selections)
|
|
@@ -90,7 +90,16 @@ module GraphQL
|
|
|
90
90
|
|
|
91
91
|
if ast_node
|
|
92
92
|
field_defn = query.get_field(result.graphql_result_type, ast_node.name)
|
|
93
|
-
args =
|
|
93
|
+
args = begin
|
|
94
|
+
if (cached_args = query.arguments_cache.cached_arguments_for(ast_node, field_defn))
|
|
95
|
+
cached_args.to_h
|
|
96
|
+
else
|
|
97
|
+
EmptyObjects::EMPTY_HASH
|
|
98
|
+
end
|
|
99
|
+
rescue StandardError => err
|
|
100
|
+
"Failed to load arguments, #{err.class}: #{err.message}"
|
|
101
|
+
end
|
|
102
|
+
|
|
94
103
|
field_path = field_defn.path
|
|
95
104
|
if ast_node.alias
|
|
96
105
|
field_path += " as #{ast_node.alias}"
|
data/lib/graphql/current.rb
CHANGED
|
@@ -41,7 +41,13 @@ module GraphQL
|
|
|
41
41
|
# @see GraphQL::Field#path for a string identifying this field
|
|
42
42
|
# @return [GraphQL::Field, nil] The currently-running field, if there is one.
|
|
43
43
|
def self.field
|
|
44
|
-
Fiber[:__graphql_runtime_info]
|
|
44
|
+
if (interpreter_info = Fiber[:__graphql_runtime_info])
|
|
45
|
+
interpreter_info.values&.first&.current_field
|
|
46
|
+
elsif (field = Fiber[:__graphql_current_field])
|
|
47
|
+
field
|
|
48
|
+
else
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
45
51
|
end
|
|
46
52
|
|
|
47
53
|
# @return [Class, nil] The currently-running {Dataloader::Source} class, if there is one.
|
|
@@ -1,86 +1,324 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
module GraphQL
|
|
3
4
|
class Dataloader
|
|
4
5
|
class AsyncDataloader < Dataloader
|
|
6
|
+
def self.use(...)
|
|
7
|
+
if !Async::Task.method_defined?(:cancel)
|
|
8
|
+
Async::Task.alias_method(:cancel, :stop)
|
|
9
|
+
end
|
|
10
|
+
if !Async::Task.method_defined?(:graphql_async_dataloader_run)
|
|
11
|
+
Async::Task.attr_accessor(:graphql_async_dataloader_run)
|
|
12
|
+
Async::Task.attr_accessor(:graphql_async_dataloader_condition)
|
|
13
|
+
end
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(...)
|
|
18
|
+
super
|
|
19
|
+
create_pending_run
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def create_pending_run
|
|
23
|
+
jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
|
|
24
|
+
@pending_run = Run.new(total_fiber_limit, jobs_fiber_limit)
|
|
25
|
+
end
|
|
26
|
+
|
|
5
27
|
def yield(source = Fiber[:__graphql_current_dataloader_source])
|
|
6
|
-
|
|
28
|
+
task = Async::Task.current
|
|
29
|
+
run = task.graphql_async_dataloader_run
|
|
30
|
+
trace = run.trace
|
|
7
31
|
trace&.dataloader_fiber_yield(source)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
end
|
|
32
|
+
run.finished_tasks.push(task)
|
|
33
|
+
condition = task.graphql_async_dataloader_condition
|
|
34
|
+
condition.wait
|
|
35
|
+
run.started_tasks.push(task)
|
|
13
36
|
trace&.dataloader_fiber_resume(source)
|
|
14
37
|
nil
|
|
15
38
|
end
|
|
16
39
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
source_tasks = []
|
|
23
|
-
next_source_tasks = []
|
|
24
|
-
first_pass = true
|
|
25
|
-
sources_condition = Async::Condition.new
|
|
26
|
-
manager = spawn_fiber do
|
|
27
|
-
trace&.begin_dataloader(self)
|
|
28
|
-
while first_pass || !job_fibers.empty?
|
|
29
|
-
first_pass = false
|
|
30
|
-
fiber_vars = get_fiber_variables
|
|
40
|
+
class Run
|
|
41
|
+
def initialize(total_fiber_limit, jobs_fiber_limit)
|
|
42
|
+
@root_task = nil
|
|
43
|
+
@trace = nil
|
|
44
|
+
@jobs = []
|
|
31
45
|
|
|
32
|
-
|
|
46
|
+
@total_fiber_limit = total_fiber_limit
|
|
47
|
+
@jobs_fiber_limit = jobs_fiber_limit
|
|
48
|
+
@lazies_at_depth = Hash.new { |h, k| h[k] = [] }
|
|
33
49
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
@finished_tasks = nil
|
|
51
|
+
@started_tasks = nil
|
|
52
|
+
@started_count_task = nil
|
|
53
|
+
@finished_count_task = nil
|
|
54
|
+
@finished_all_tasks = nil
|
|
55
|
+
@finished_first_pass = nil
|
|
56
|
+
|
|
57
|
+
@snoozed_jobs_condition = Async::Condition.new
|
|
58
|
+
@snoozed_sources_condition = Async::Condition.new
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
attr_accessor :trace, :root_task
|
|
62
|
+
|
|
63
|
+
attr_reader :jobs, :lazies_at_depth, :jobs_fiber_limit, :total_fiber_limit, :finished_tasks, :started_tasks, :snoozed_jobs_condition, :snoozed_sources_condition
|
|
64
|
+
|
|
65
|
+
def jobs_bandwidth?
|
|
66
|
+
running_count < jobs_fiber_limit
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def allowed_sources_tasks
|
|
70
|
+
within_limit = total_fiber_limit - running_count
|
|
71
|
+
if within_limit < 1
|
|
72
|
+
1
|
|
73
|
+
else
|
|
74
|
+
within_limit
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def close_queues
|
|
79
|
+
@finished_tasks.close
|
|
80
|
+
@finished_count_task.cancel
|
|
81
|
+
|
|
82
|
+
@started_tasks.close
|
|
83
|
+
@started_count_task.cancel
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def running_count
|
|
87
|
+
@snoozed_jobs_condition.instance_variable_get(:@ready).num_waiting +
|
|
88
|
+
@snoozed_sources_condition.instance_variable_get(:@ready).num_waiting +
|
|
89
|
+
@started_count +
|
|
90
|
+
@started_tasks.size -
|
|
91
|
+
@finished_count
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def wait_for_queues
|
|
95
|
+
if !@finished_first_pass.resolved?
|
|
96
|
+
@finished_first_pass.resolve(true)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
@finished_all_tasks.wait
|
|
100
|
+
@finished_all_tasks = Async::Promise.new
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def new_queues
|
|
104
|
+
@finished_tasks = Async::Queue.new
|
|
105
|
+
@finished_count = 0
|
|
106
|
+
@started_tasks = Async::Queue.new
|
|
107
|
+
@started_count = 0
|
|
108
|
+
@finished_first_pass = Async::Promise.new
|
|
109
|
+
@finished_all_tasks = Async::Promise.new
|
|
110
|
+
|
|
111
|
+
@started_count_task = @root_task.async do |task|
|
|
112
|
+
@finished_first_pass.wait
|
|
113
|
+
while task = @started_tasks.wait
|
|
114
|
+
@started_count += 1
|
|
115
|
+
if task.status == :initialized # could also be resumed after waiting
|
|
116
|
+
task.run
|
|
46
117
|
end
|
|
47
118
|
end
|
|
119
|
+
end
|
|
48
120
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
121
|
+
@finished_count_task = @root_task.async do |task|
|
|
122
|
+
while t_or_err = @finished_tasks.wait
|
|
123
|
+
if t_or_err.is_a?(StandardError)
|
|
124
|
+
@finished_all_tasks.reject(t_or_err)
|
|
125
|
+
else
|
|
126
|
+
@finished_count += 1
|
|
127
|
+
if @finished_count == @started_count
|
|
128
|
+
@finished_all_tasks.resolve(true)
|
|
129
|
+
end
|
|
53
130
|
end
|
|
54
131
|
end
|
|
55
132
|
end
|
|
56
|
-
trace&.end_dataloader(self)
|
|
57
133
|
end
|
|
58
134
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
raise "Invariant: Manager didn't terminate successfully: #{manager}"
|
|
135
|
+
def running?
|
|
136
|
+
@snoozed_jobs_condition.waiting? || @snoozed_sources_condition.waiting?
|
|
62
137
|
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def append_job(callable = nil, &block)
|
|
141
|
+
active_run.jobs.push(callable || block)
|
|
142
|
+
nil
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def lazy_at_depth(depth, lazy)
|
|
146
|
+
active_run.lazies_at_depth[depth] << lazy
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def active_run
|
|
150
|
+
@pending_run || Async::Task.current?&.graphql_async_dataloader_run || raise(GraphQL::Error, "No available Run to append to, GraphQL-Ruby bug")
|
|
151
|
+
end
|
|
63
152
|
|
|
153
|
+
def run_isolated
|
|
154
|
+
previous_run = Async::Task.current?&.graphql_async_dataloader_run
|
|
155
|
+
prev_pending_keys = {}
|
|
156
|
+
# Clear pending loads but keep already-cached records
|
|
157
|
+
# in case they are useful to the given block.
|
|
158
|
+
@source_cache.each do |source_class, batched_sources|
|
|
159
|
+
batched_sources.each do |batch_args, batched_source_instance|
|
|
160
|
+
if batched_source_instance.pending?
|
|
161
|
+
prev_pending_keys[batched_source_instance] = batched_source_instance.pending.dup
|
|
162
|
+
batched_source_instance.pending.clear
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
res = nil
|
|
168
|
+
create_pending_run
|
|
169
|
+
@pending_run.jobs << -> { res = yield }
|
|
170
|
+
run
|
|
171
|
+
res
|
|
172
|
+
ensure
|
|
173
|
+
if previous_run
|
|
174
|
+
Async::Task.current.graphql_async_dataloader_run = previous_run
|
|
175
|
+
# clear the one created in #run:
|
|
176
|
+
@pending_run = nil
|
|
177
|
+
end
|
|
178
|
+
prev_pending_keys.each do |source_instance, pending|
|
|
179
|
+
pending.each do |key, value|
|
|
180
|
+
if !source_instance.results.key?(key)
|
|
181
|
+
source_instance.pending[key] = value
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def run(trace_query_lazy: nil)
|
|
188
|
+
trace = Fiber[:__graphql_current_multiplex]&.current_trace
|
|
189
|
+
run = @pending_run || Async::Task.current?&.graphql_async_dataloader_run || raise(GraphQL::Error, "No available Run, GraphQL-Ruby internal bug")
|
|
190
|
+
@pending_run = nil
|
|
191
|
+
run.trace = trace
|
|
192
|
+
first_pass = true
|
|
193
|
+
trace&.begin_dataloader(self)
|
|
194
|
+
fiber_vars = get_fiber_variables
|
|
195
|
+
raised_error = nil
|
|
196
|
+
jobs = run.jobs
|
|
197
|
+
Sync do |_maybe_new_task|
|
|
198
|
+
# Make sure there's a new task instance to hold `.graphql_...` state:
|
|
199
|
+
task = Async::Task.new do |root_task|
|
|
200
|
+
run.root_task = root_task
|
|
201
|
+
root_task.graphql_async_dataloader_run = run
|
|
202
|
+
set_fiber_variables(fiber_vars)
|
|
203
|
+
|
|
204
|
+
while first_pass || run.running? || !jobs.empty?
|
|
205
|
+
first_pass = false
|
|
206
|
+
run_pending_steps(run)
|
|
207
|
+
run_sources(run)
|
|
208
|
+
|
|
209
|
+
if !run.lazies_at_depth.empty?
|
|
210
|
+
with_trace_query_lazy(trace_query_lazy) do
|
|
211
|
+
run_next_pending_lazies(run)
|
|
212
|
+
run_pending_steps(run)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
rescue StandardError => err
|
|
217
|
+
raised_error = err
|
|
218
|
+
root_task.cancel
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
task.run
|
|
222
|
+
task.wait
|
|
223
|
+
end
|
|
224
|
+
create_pending_run
|
|
225
|
+
if raised_error
|
|
226
|
+
raise raised_error
|
|
227
|
+
end
|
|
228
|
+
trace&.end_dataloader(self)
|
|
64
229
|
rescue UncaughtThrowError => e
|
|
65
230
|
throw e.tag, e.value
|
|
66
231
|
end
|
|
67
232
|
|
|
68
233
|
private
|
|
69
234
|
|
|
70
|
-
def run_pending_steps(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
235
|
+
def run_pending_steps(run)
|
|
236
|
+
run.new_queues
|
|
237
|
+
|
|
238
|
+
if (unsnoozed = run.snoozed_jobs_condition.waiting?)
|
|
239
|
+
run.snoozed_jobs_condition.signal
|
|
240
|
+
end
|
|
241
|
+
pending_jobs = run.jobs
|
|
242
|
+
while (!pending_jobs.empty? && (has_limit = run.jobs_bandwidth?)) || (unsnoozed)
|
|
243
|
+
unsnoozed = false
|
|
244
|
+
if has_limit
|
|
245
|
+
spawn_job_task(run)
|
|
246
|
+
end
|
|
247
|
+
run.wait_for_queues
|
|
248
|
+
end
|
|
249
|
+
ensure
|
|
250
|
+
run.close_queues
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def spawn_job_task(run)
|
|
254
|
+
pending_jobs = run.jobs
|
|
255
|
+
if !pending_jobs.empty?
|
|
256
|
+
fiber_vars = get_fiber_variables
|
|
257
|
+
new_task = Async::Task.new(run.root_task) do |task|
|
|
258
|
+
run.trace&.dataloader_spawn_execution_fiber(pending_jobs)
|
|
259
|
+
task.graphql_async_dataloader_run = run
|
|
260
|
+
task.graphql_async_dataloader_condition = run.snoozed_jobs_condition
|
|
261
|
+
set_fiber_variables(fiber_vars)
|
|
262
|
+
while job = pending_jobs.shift
|
|
263
|
+
job.call
|
|
76
264
|
end
|
|
265
|
+
ensure
|
|
266
|
+
cleanup_fiber
|
|
267
|
+
run.finished_tasks.push($! || task)
|
|
268
|
+
run.trace&.dataloader_fiber_exit
|
|
77
269
|
end
|
|
270
|
+
run.started_tasks.push(new_task)
|
|
271
|
+
new_task
|
|
78
272
|
end
|
|
79
|
-
job_fibers.concat(next_job_fibers)
|
|
80
|
-
next_job_fibers.clear
|
|
81
273
|
end
|
|
82
274
|
|
|
83
|
-
def
|
|
275
|
+
def run_sources(run)
|
|
276
|
+
run.new_queues
|
|
277
|
+
|
|
278
|
+
if (unsnoozed = run.snoozed_sources_condition.waiting?)
|
|
279
|
+
run.snoozed_sources_condition.signal
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
allowed_tasks = run.allowed_sources_tasks
|
|
283
|
+
while (has_pending = @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) } ) || unsnoozed
|
|
284
|
+
unsnoozed = false
|
|
285
|
+
if has_pending
|
|
286
|
+
spawn_source_task(run, allowed_tasks)
|
|
287
|
+
end
|
|
288
|
+
run.wait_for_queues
|
|
289
|
+
end
|
|
290
|
+
ensure
|
|
291
|
+
run.close_queues
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
#### TODO DRY Had to duplicate to remove spawn_job_fiber
|
|
295
|
+
def run_next_pending_lazies(run)
|
|
296
|
+
smallest_depth = nil
|
|
297
|
+
run.lazies_at_depth.each_key do |depth_key|
|
|
298
|
+
smallest_depth ||= depth_key
|
|
299
|
+
if depth_key < smallest_depth
|
|
300
|
+
smallest_depth = depth_key
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
if smallest_depth
|
|
305
|
+
lazies = run.lazies_at_depth.delete(smallest_depth)
|
|
306
|
+
if !lazies.empty?
|
|
307
|
+
begin
|
|
308
|
+
run.new_queues
|
|
309
|
+
lazies.each_with_index do |l, idx|
|
|
310
|
+
append_job { l.value }
|
|
311
|
+
end
|
|
312
|
+
spawn_job_task(run) # Todo what was the last `true` condition?
|
|
313
|
+
run.wait_for_queues
|
|
314
|
+
ensure
|
|
315
|
+
run.close_queues
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def spawn_source_task(run, num_tasks)
|
|
84
322
|
pending_sources = nil
|
|
85
323
|
@source_cache.each_value do |source_by_batch_params|
|
|
86
324
|
source_by_batch_params.each_value do |source|
|
|
@@ -92,18 +330,30 @@ module GraphQL
|
|
|
92
330
|
end
|
|
93
331
|
|
|
94
332
|
if pending_sources
|
|
333
|
+
if num_tasks == Float::INFINITY
|
|
334
|
+
num_tasks = pending_sources.size
|
|
335
|
+
end
|
|
95
336
|
fiber_vars = get_fiber_variables
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
trace&.
|
|
102
|
-
|
|
103
|
-
|
|
337
|
+
trace = run.trace
|
|
338
|
+
num_tasks.times do
|
|
339
|
+
new_task = Async::Task.new(run.root_task) do |task|
|
|
340
|
+
task.graphql_async_dataloader_run = run
|
|
341
|
+
task.graphql_async_dataloader_condition = run.snoozed_sources_condition
|
|
342
|
+
trace&.dataloader_spawn_source_fiber(pending_sources)
|
|
343
|
+
set_fiber_variables(fiber_vars)
|
|
344
|
+
while (source = pending_sources.shift)
|
|
345
|
+
trace&.begin_dataloader_source(source)
|
|
346
|
+
source.run_pending_keys
|
|
347
|
+
trace&.end_dataloader_source(source)
|
|
348
|
+
end
|
|
349
|
+
nil
|
|
350
|
+
ensure
|
|
351
|
+
run.finished_tasks.push($! || task)
|
|
352
|
+
cleanup_fiber
|
|
353
|
+
trace&.dataloader_fiber_exit
|
|
104
354
|
end
|
|
105
|
-
|
|
106
|
-
|
|
355
|
+
run.started_tasks.push(new_task)
|
|
356
|
+
new_task
|
|
107
357
|
end
|
|
108
358
|
end
|
|
109
359
|
end
|
|
@@ -89,7 +89,8 @@ module GraphQL
|
|
|
89
89
|
sync(pending_keys)
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
-
result_keys.map { |k| result_for(k) }
|
|
92
|
+
result_keys.map! { |k| result_for(k) }
|
|
93
|
+
result_keys
|
|
93
94
|
end
|
|
94
95
|
|
|
95
96
|
# Subclasses must implement this method to return a value for each of `keys`
|
|
@@ -138,22 +139,25 @@ module GraphQL
|
|
|
138
139
|
# @api private
|
|
139
140
|
# @return [void]
|
|
140
141
|
def run_pending_keys
|
|
141
|
-
|
|
142
|
-
@fetching.each_key { |k| @pending.delete(k) }
|
|
143
|
-
end
|
|
142
|
+
@fetching.each_key { |k| @pending.delete(k) }
|
|
144
143
|
return if @pending.empty?
|
|
145
144
|
fetch_h = @pending
|
|
146
|
-
@pending = {}
|
|
147
145
|
@fetching.merge!(fetch_h)
|
|
146
|
+
@pending = {}
|
|
148
147
|
results = fetch(fetch_h.values)
|
|
149
|
-
|
|
148
|
+
idx = 0
|
|
149
|
+
|
|
150
|
+
fetch_h.each_key do |key|
|
|
150
151
|
@results[key] = results[idx]
|
|
152
|
+
@fetching.delete(key)
|
|
153
|
+
idx += 1
|
|
151
154
|
end
|
|
152
155
|
nil
|
|
153
156
|
rescue StandardError => error
|
|
154
|
-
fetch_h.each_key { |key|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
fetch_h.each_key { |key|
|
|
158
|
+
@results[key] = error
|
|
159
|
+
@fetching.delete(key)
|
|
160
|
+
}
|
|
157
161
|
end
|
|
158
162
|
|
|
159
163
|
# These arguments are given to `dataloader.with(source_class, ...)`. The object
|
|
@@ -171,7 +175,11 @@ module GraphQL
|
|
|
171
175
|
# @param batch_kwargs [Hash]
|
|
172
176
|
# @return [Object]
|
|
173
177
|
def self.batch_key_for(*batch_args, **batch_kwargs)
|
|
174
|
-
|
|
178
|
+
if batch_kwargs.any? # rubocop:disable Development/NoneWithoutBlockCop
|
|
179
|
+
[*batch_args, **batch_kwargs]
|
|
180
|
+
else
|
|
181
|
+
batch_args
|
|
182
|
+
end
|
|
175
183
|
end
|
|
176
184
|
|
|
177
185
|
# Clear any already-loaded objects for this source
|
data/lib/graphql/dataloader.rb
CHANGED
|
@@ -58,7 +58,7 @@ module GraphQL
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def initialize(nonblocking: self.class.default_nonblocking, fiber_limit: self.class.default_fiber_limit)
|
|
61
|
-
@source_cache = Hash.new { |h, k| h[k] = {} }
|
|
61
|
+
@source_cache = Hash.new { |h, k| h[k] = {} }.compare_by_identity
|
|
62
62
|
@pending_jobs = []
|
|
63
63
|
if !nonblocking.nil?
|
|
64
64
|
@nonblocking = nonblocking
|
|
@@ -18,11 +18,13 @@ module GraphQL
|
|
|
18
18
|
case name
|
|
19
19
|
when SKIP
|
|
20
20
|
args = query.arguments_for(directive_ast_node, directive_defn)
|
|
21
|
+
next if args.is_a?(GraphQL::ExecutionError)
|
|
21
22
|
if args[:if] == true
|
|
22
23
|
return false
|
|
23
24
|
end
|
|
24
25
|
when INCLUDE
|
|
25
26
|
args = query.arguments_for(directive_ast_node, directive_defn)
|
|
27
|
+
next if args.is_a?(GraphQL::ExecutionError)
|
|
26
28
|
if args[:if] == false
|
|
27
29
|
return false
|
|
28
30
|
end
|