graphql 2.2.1 → 2.2.2
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/analysis/ast/query_complexity.rb +80 -128
- data/lib/graphql/dataloader/source.rb +1 -1
- data/lib/graphql/dataloader.rb +19 -30
- data/lib/graphql/language/parser.rb +4 -2
- 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: a1caad3d02e9d64f06c086fd46c324a0df1fc66c35353aef62dd716fe7ea4b9c
|
4
|
+
data.tar.gz: 8c53383e4fe8b0e7d5199d42460e0a5920940ae27965a7223f5ef020b1c594cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae189d6de14b18dbee6781688a43d1f15cfb1418fa9ceb45ca099f577a5cd9339c6e16d8402e7b15ba62c64afbfbaed64f3d5d64b253a51597fb50d586d5a6dd
|
7
|
+
data.tar.gz: ab5860a939248086c5b8df11f0f638edce805110e75f51b068addce3e0956a9ffeda17836bf02962a943b137e4defccd48e3c18f836973f0c780c4ef225f7bd0
|
@@ -16,10 +16,11 @@ module GraphQL
|
|
16
16
|
max_possible_complexity
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
# ScopedTypeComplexity models a tree of GraphQL types mapped to inner selections, ie:
|
20
|
+
# Hash<GraphQL::BaseType, Hash<String, ScopedTypeComplexity>>
|
21
|
+
class ScopedTypeComplexity < Hash
|
22
|
+
# A proc for defaulting empty namespace requests as a new scope hash.
|
23
|
+
DEFAULT_PROC = ->(h, k) { h[k] = {} }
|
23
24
|
|
24
25
|
attr_reader :field_definition, :response_path, :query
|
25
26
|
|
@@ -27,32 +28,19 @@ module GraphQL
|
|
27
28
|
# @param field_definition [GraphQL::Field, GraphQL::Schema::Field] Used for getting the `.complexity` configuration
|
28
29
|
# @param query [GraphQL::Query] Used for `query.possible_types`
|
29
30
|
# @param response_path [Array<String>] The path to the response key for the field
|
31
|
+
# @return [Hash<GraphQL::BaseType, Hash<String, ScopedTypeComplexity>>]
|
30
32
|
def initialize(parent_type, field_definition, query, response_path)
|
33
|
+
super(&DEFAULT_PROC)
|
31
34
|
@parent_type = parent_type
|
32
35
|
@field_definition = field_definition
|
33
36
|
@query = query
|
34
37
|
@response_path = response_path
|
35
|
-
@scoped_children = nil
|
36
38
|
@nodes = []
|
37
39
|
end
|
38
40
|
|
39
41
|
# @return [Array<GraphQL::Language::Nodes::Field>]
|
40
42
|
attr_reader :nodes
|
41
43
|
|
42
|
-
# Returns true if this field has no selections, ie, it's a scalar.
|
43
|
-
# We need a quick way to check whether we should continue traversing.
|
44
|
-
def terminal?
|
45
|
-
@scoped_children.nil?
|
46
|
-
end
|
47
|
-
|
48
|
-
# This value is only calculated when asked for to avoid needless hash allocations.
|
49
|
-
# Also, if it's never asked for, we determine that this scope complexity
|
50
|
-
# is a scalar field ({#terminal?}).
|
51
|
-
# @return [Hash<Hash<Class => ScopedTypeComplexity>]
|
52
|
-
def scoped_children
|
53
|
-
@scoped_children ||= Hash.new(&HASH_CHILDREN)
|
54
|
-
end
|
55
|
-
|
56
44
|
def own_complexity(child_complexity)
|
57
45
|
@field_definition.calculate_complexity(query: @query, nodes: @nodes, child_complexity: child_complexity)
|
58
46
|
end
|
@@ -65,19 +53,14 @@ module GraphQL
|
|
65
53
|
return if visitor.skipping?
|
66
54
|
parent_type = visitor.parent_type_definition
|
67
55
|
field_key = node.alias || node.name
|
68
|
-
|
69
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
#
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
complexity = complexities_on_type.last.scoped_children[parent_type][field_key] ||= ScopedTypeComplexity.new(parent_type, visitor.field_definition, visitor.query, visitor.response_path)
|
78
|
-
complexity.nodes.push(node)
|
79
|
-
# Push it on the stack.
|
80
|
-
complexities_on_type.push(complexity)
|
56
|
+
|
57
|
+
# Find or create a complexity scope stack for this query.
|
58
|
+
scopes_stack = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query, visitor.response_path)]
|
59
|
+
|
60
|
+
# Find or create the complexity costing node for this field.
|
61
|
+
scope = scopes_stack.last[parent_type][field_key] ||= ScopedTypeComplexity.new(parent_type, visitor.field_definition, visitor.query, visitor.response_path)
|
62
|
+
scope.nodes.push(node)
|
63
|
+
scopes_stack.push(scope)
|
81
64
|
end
|
82
65
|
|
83
66
|
def on_leave_field(node, parent, visitor)
|
@@ -85,89 +68,61 @@ module GraphQL
|
|
85
68
|
# we'll visit them when we hit the spreads instead
|
86
69
|
return if visitor.visiting_fragment_definition?
|
87
70
|
return if visitor.skipping?
|
88
|
-
|
89
|
-
|
71
|
+
scopes_stack = @complexities_on_type_by_query[visitor.query]
|
72
|
+
scopes_stack.pop
|
90
73
|
end
|
91
74
|
|
92
75
|
private
|
93
76
|
|
94
77
|
# @return [Integer]
|
95
78
|
def max_possible_complexity
|
96
|
-
@complexities_on_type_by_query.reduce(0) do |total, (query,
|
97
|
-
|
98
|
-
# Use this entry point to calculate the total complexity
|
99
|
-
total_complexity_for_query = merged_max_complexity_for_scopes(query, [root_complexity.scoped_children])
|
100
|
-
total + total_complexity_for_query
|
79
|
+
@complexities_on_type_by_query.reduce(0) do |total, (query, scopes_stack)|
|
80
|
+
total + merged_max_complexity_for_scopes(query, [scopes_stack.first])
|
101
81
|
end
|
102
82
|
end
|
103
83
|
|
104
84
|
# @param query [GraphQL::Query] Used for `query.possible_types`
|
105
|
-
# @param
|
85
|
+
# @param scopes [Array<ScopedTypeComplexity>] Array of scoped type complexities
|
106
86
|
# @return [Integer]
|
107
|
-
def merged_max_complexity_for_scopes(query,
|
108
|
-
#
|
87
|
+
def merged_max_complexity_for_scopes(query, scopes)
|
88
|
+
# Aggregate a set of all possible scope types encountered (scope keys).
|
109
89
|
# Use a hash, but ignore the values; it's just a fast way to work with the keys.
|
110
|
-
|
111
|
-
|
112
|
-
all_scopes.merge!(h)
|
90
|
+
possible_scope_types = scopes.each_with_object({}) do |scope, memo|
|
91
|
+
memo.merge!(scope)
|
113
92
|
end
|
114
93
|
|
115
|
-
#
|
116
|
-
#
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
missing_concrete_types = query.possible_types(scope).select { |t| !all_scopes.key?(t) }
|
124
|
-
# This concrete type is possible _only_ as a member of the abstract type.
|
125
|
-
# So, attribute to it the complexity which belongs to the abstract type.
|
126
|
-
missing_concrete_types.each do |concrete_scope|
|
127
|
-
all_scopes[concrete_scope] = all_scopes[scope]
|
94
|
+
# Expand abstract scope types into their concrete implementations;
|
95
|
+
# overlapping abstracts coalesce through their intersecting types.
|
96
|
+
possible_scope_types.keys.each do |possible_scope_type|
|
97
|
+
next unless possible_scope_type.kind.abstract?
|
98
|
+
|
99
|
+
query.possible_types(possible_scope_type).each do |impl_type|
|
100
|
+
possible_scope_types[impl_type] ||= true
|
128
101
|
end
|
129
|
-
|
102
|
+
possible_scope_types.delete(possible_scope_type)
|
130
103
|
end
|
131
104
|
|
132
|
-
#
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
# as a set and calculate the complexity for them as a unit
|
140
|
-
all_scopes.each do |scope, _|
|
141
|
-
# These will be the selections on `scope`
|
142
|
-
children_for_scope = []
|
143
|
-
scoped_children_hashes.each do |sc_h|
|
144
|
-
sc_h.each do |inner_scope, children_hash|
|
145
|
-
if applies_to?(query, scope, inner_scope)
|
146
|
-
children_for_scope << children_hash
|
147
|
-
end
|
105
|
+
# Aggregate the lexical selections that may apply to each possible type,
|
106
|
+
# and then return the maximum cost among possible typed selections.
|
107
|
+
possible_scope_types.each_key.reduce(0) do |max, possible_scope_type|
|
108
|
+
# Collect inner selections from all scopes that intersect with this possible type.
|
109
|
+
all_inner_selections = scopes.each_with_object([]) do |scope, memo|
|
110
|
+
scope.each do |scope_type, inner_selections|
|
111
|
+
memo << inner_selections if types_intersect?(query, scope_type, possible_scope_type)
|
148
112
|
end
|
149
113
|
end
|
150
114
|
|
151
|
-
#
|
152
|
-
|
153
|
-
|
154
|
-
complexity_by_scope[scope] = complexity_value
|
115
|
+
# Find the maximum complexity for the scope type among possible lexical branches.
|
116
|
+
complexity = merged_max_complexity(query, all_inner_selections)
|
117
|
+
complexity > max ? complexity : max
|
155
118
|
end
|
156
|
-
|
157
|
-
# Return the max complexity among all scopes
|
158
|
-
complexity_by_scope.each_value.max
|
159
119
|
end
|
160
120
|
|
161
|
-
def
|
162
|
-
if
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
# Check if these two scopes have _any_ types in common.
|
167
|
-
possible_right_types = query.possible_types(right_scope)
|
168
|
-
possible_left_types = query.possible_types(left_scope)
|
169
|
-
!(possible_right_types & possible_left_types).empty?
|
170
|
-
end
|
121
|
+
def types_intersect?(query, a, b)
|
122
|
+
return true if a == b
|
123
|
+
|
124
|
+
a_types = query.possible_types(a)
|
125
|
+
query.possible_types(b).any? { |t| a_types.include?(t) }
|
171
126
|
end
|
172
127
|
|
173
128
|
# A hook which is called whenever a field's max complexity is calculated.
|
@@ -179,50 +134,47 @@ module GraphQL
|
|
179
134
|
def field_complexity(scoped_type_complexity, max_complexity:, child_complexity: nil)
|
180
135
|
end
|
181
136
|
|
182
|
-
# @param
|
183
|
-
#
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
137
|
+
# @param inner_selections [Array<Hash<String, ScopedTypeComplexity>>] Field selections for a scope
|
138
|
+
# @return [Integer] Total complexity value for all these selections in the parent scope
|
139
|
+
def merged_max_complexity(query, inner_selections)
|
140
|
+
# Aggregate a set of all unique field selection keys across all scopes.
|
141
|
+
# Use a hash, but ignore the values; it's just a fast way to work with the keys.
|
142
|
+
unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo|
|
143
|
+
memo.merge!(inner_selection)
|
189
144
|
end
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
field_complexity(complexity_for_key, max_complexity: complexity, child_complexity: nil)
|
145
|
+
|
146
|
+
# Add up the total cost for each unique field name's coalesced selections
|
147
|
+
unique_field_keys.each_key.reduce(0) do |total, field_key|
|
148
|
+
composite_scopes = nil
|
149
|
+
field_cost = 0
|
150
|
+
|
151
|
+
# Collect composite selection scopes for further aggregation,
|
152
|
+
# leaf selections report their costs directly.
|
153
|
+
inner_selections.each do |inner_selection|
|
154
|
+
child_scope = inner_selection[field_key]
|
155
|
+
next unless child_scope
|
156
|
+
|
157
|
+
# Empty child scopes are leaf nodes with zero child complexity.
|
158
|
+
if child_scope.empty?
|
159
|
+
field_cost = child_scope.own_complexity(0)
|
160
|
+
field_complexity(child_scope, max_complexity: field_cost, child_complexity: nil)
|
207
161
|
else
|
208
|
-
|
209
|
-
|
162
|
+
composite_scopes ||= []
|
163
|
+
composite_scopes << child_scope
|
210
164
|
end
|
211
165
|
end
|
212
166
|
|
213
|
-
|
167
|
+
if composite_scopes
|
168
|
+
child_complexity = merged_max_complexity_for_scopes(query, composite_scopes)
|
214
169
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
170
|
+
# This is the last composite scope visited; assume it's representative (for backwards compatibility).
|
171
|
+
# Note: it would be more correct to score each composite scope and use the maximum possibility.
|
172
|
+
field_cost = composite_scopes.last.own_complexity(child_complexity)
|
173
|
+
field_complexity(composite_scopes.last, max_complexity: field_cost, child_complexity: child_complexity)
|
174
|
+
end
|
220
175
|
|
221
|
-
|
176
|
+
total + field_cost
|
222
177
|
end
|
223
|
-
|
224
|
-
# Calculate the child complexity by summing the complexity of all selections
|
225
|
-
complexity_for_keys.each_value.inject(0, &:+)
|
226
178
|
end
|
227
179
|
end
|
228
180
|
end
|
data/lib/graphql/dataloader.rb
CHANGED
@@ -119,12 +119,7 @@ module GraphQL
|
|
119
119
|
#
|
120
120
|
# @return [void]
|
121
121
|
def yield
|
122
|
-
|
123
|
-
Fiber.yield
|
124
|
-
else
|
125
|
-
parent_fiber = Thread.current[:parent_fiber]
|
126
|
-
parent_fiber.transfer
|
127
|
-
end
|
122
|
+
Fiber.yield
|
128
123
|
nil
|
129
124
|
end
|
130
125
|
|
@@ -169,7 +164,11 @@ module GraphQL
|
|
169
164
|
ensure
|
170
165
|
@pending_jobs = prev_queue
|
171
166
|
prev_pending_keys.each do |source_instance, pending|
|
172
|
-
|
167
|
+
pending.each do |key, value|
|
168
|
+
if !source_instance.results.key?(key)
|
169
|
+
source_instance.pending[key] = value
|
170
|
+
end
|
171
|
+
end
|
173
172
|
end
|
174
173
|
end
|
175
174
|
|
@@ -183,7 +182,7 @@ module GraphQL
|
|
183
182
|
while first_pass || job_fibers.any?
|
184
183
|
first_pass = false
|
185
184
|
|
186
|
-
while (f = job_fibers.shift || spawn_job_fiber)
|
185
|
+
while (f = (job_fibers.shift || spawn_job_fiber))
|
187
186
|
if f.alive?
|
188
187
|
finished = run_fiber(f)
|
189
188
|
if !finished
|
@@ -204,38 +203,37 @@ module GraphQL
|
|
204
203
|
end
|
205
204
|
join_queues(source_fibers, next_source_fibers)
|
206
205
|
end
|
207
|
-
|
208
206
|
end
|
209
207
|
end
|
210
208
|
|
211
209
|
run_fiber(manager)
|
212
210
|
|
211
|
+
if manager.alive?
|
212
|
+
raise "Invariant: Manager fiber didn't terminate properly."
|
213
|
+
end
|
214
|
+
|
215
|
+
if job_fibers.any?
|
216
|
+
raise "Invariant: job fibers should have exited but #{job_fibers.size} remained"
|
217
|
+
end
|
218
|
+
if source_fibers.any?
|
219
|
+
raise "Invariant: source fibers should have exited but #{source_fibers.size} remained"
|
220
|
+
end
|
213
221
|
rescue UncaughtThrowError => e
|
214
222
|
throw e.tag, e.value
|
215
223
|
end
|
216
224
|
|
217
225
|
def run_fiber(f)
|
218
|
-
|
219
|
-
f.resume
|
220
|
-
else
|
221
|
-
f.transfer
|
222
|
-
end
|
226
|
+
f.resume
|
223
227
|
end
|
224
228
|
|
225
229
|
def spawn_fiber
|
226
230
|
fiber_vars = get_fiber_variables
|
227
|
-
parent_fiber = use_fiber_resume? ? nil : Fiber.current
|
228
231
|
Fiber.new(blocking: !@nonblocking) {
|
229
232
|
set_fiber_variables(fiber_vars)
|
230
|
-
Thread.current[:parent_fiber] = parent_fiber
|
231
233
|
yield
|
232
234
|
# With `.transfer`, you have to explicitly pass back to the parent --
|
233
235
|
# if the fiber is allowed to terminate normally, control is passed to the main fiber instead.
|
234
|
-
|
235
|
-
parent_fiber.transfer(true)
|
236
|
-
else
|
237
|
-
true
|
238
|
-
end
|
236
|
+
true
|
239
237
|
}
|
240
238
|
end
|
241
239
|
|
@@ -247,15 +245,6 @@ module GraphQL
|
|
247
245
|
new_queue.clear
|
248
246
|
end
|
249
247
|
|
250
|
-
def use_fiber_resume?
|
251
|
-
Fiber.respond_to?(:scheduler) &&
|
252
|
-
(
|
253
|
-
(defined?(::DummyScheduler) && Fiber.scheduler.is_a?(::DummyScheduler)) ||
|
254
|
-
(defined?(::Evt) && ::Evt::Scheduler.singleton_class::BACKENDS.any? { |be| Fiber.scheduler.is_a?(be) }) ||
|
255
|
-
(defined?(::Libev) && Fiber.scheduler.is_a?(::Libev::Scheduler))
|
256
|
-
)
|
257
|
-
end
|
258
|
-
|
259
248
|
def spawn_job_fiber
|
260
249
|
if @pending_jobs.any?
|
261
250
|
spawn_fiber do
|
@@ -62,11 +62,13 @@ module GraphQL
|
|
62
62
|
|
63
63
|
def document
|
64
64
|
any_tokens = advance_token
|
65
|
-
|
65
|
+
defns = []
|
66
|
+
if any_tokens
|
67
|
+
defns << definition
|
68
|
+
else
|
66
69
|
# Only ignored characters is not a valid document
|
67
70
|
raise GraphQL::ParseError.new("Unexpected end of document", nil, nil, @graphql_str)
|
68
71
|
end
|
69
|
-
defns = []
|
70
72
|
while !@lexer.eos?
|
71
73
|
defns << definition
|
72
74
|
end
|
data/lib/graphql/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.2.
|
4
|
+
version: 2.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-12-
|
11
|
+
date: 2023-12-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: racc
|