graphql 2.2.1 → 2.2.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3efbb5d392546a7fce8398ec727d1a3a3580f8d7af6e544eab427c899f03eead
4
- data.tar.gz: f51809ef796336903cafcfaf70ded342d380ef7c0f91d3a267bc217ce6ab8217
3
+ metadata.gz: 7177c15aedde0b99419bee77e944a4907bb84e39b5e9aafb05b2a1360903f3e6
4
+ data.tar.gz: e7252b975a41c1d8a0871e71a3fc37522741c0b872899f2f33863b76ad09934d
5
5
  SHA512:
6
- metadata.gz: 32822e5ddaf9f83e0d905e812c61464d770379ec2cd4ef05a66f4fe261aa15868eb8955527054fa4aaeb2282f32956d3631e5df120494be79e4bbfd9ccde910a
7
- data.tar.gz: 0d32a3f57eda3e26c032bf3f93f4605df643387e20f6429c84cc66ca76a41fa362c66cb420a8b6daa98616a0189a380d8bedf07b5f55f723d94119dd6a35d061
6
+ metadata.gz: d29b25a1186ce62664cd2225de1eac675925ebbf93ed14c6513a1008c7cd6a1b01b375a1785082ab14024ead927a073f8e3afb72ffa5905a342fcdec6a60d9fa
7
+ data.tar.gz: 6d2a445f577027d5f87a0a0ee0f0c1a562d494d7d9a5bc7209dfbb2fc5c98ce38f5fab86e7101b6537f9b9b590a2d41e1303f5dc606809683a1bde22c6bc283d
@@ -106,7 +106,7 @@ module Graphql
106
106
  end
107
107
 
108
108
  # All resolvers are defined as living in their own module, including this class.
109
- template("base_resolver.erb", "#{options[:directory]}/resolvers/base.rb")
109
+ template("base_resolver.erb", "#{options[:directory]}/resolvers/base_resolver.rb")
110
110
 
111
111
  # Note: You can't have a schema without the query type, otherwise introspection breaks
112
112
  template("query_type.erb", "#{options[:directory]}/types/query_type.rb")
@@ -1,5 +1,5 @@
1
1
  <% module_namespacing_when_supported do -%>
2
- module Types
2
+ module Resolvers
3
3
  class BaseResolver < GraphQL::Schema::Resolver
4
4
  end
5
5
  end
@@ -16,10 +16,11 @@ module GraphQL
16
16
  max_possible_complexity
17
17
  end
18
18
 
19
- class ScopedTypeComplexity
20
- # A single proc for {#scoped_children} hashes. Use this to avoid repeated allocations,
21
- # since the lexical binding isn't important.
22
- HASH_CHILDREN = ->(h, k) { h[k] = {} }
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
- # Find the complexity calculation for this field --
69
- # if we're re-entering a selection, we'll already have one.
70
- # Otherwise, make a new one and store it.
71
- #
72
- # `node` and `visitor.field_definition` may appear from a cache,
73
- # but I think that's ok. If the arguments _didn't_ match,
74
- # then the query would have been rejected as invalid.
75
- complexities_on_type = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query, visitor.response_path)]
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
- complexities_on_type = @complexities_on_type_by_query[visitor.query]
89
- complexities_on_type.pop
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, complexities_on_type)|
97
- root_complexity = complexities_on_type.last
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 scoped_children_hashes [Array<Hash>] Array of scoped children hashes
85
+ # @param scopes [Array<ScopedTypeComplexity>] Array of scoped type complexities
106
86
  # @return [Integer]
107
- def merged_max_complexity_for_scopes(query, scoped_children_hashes)
108
- # Figure out what scopes are possible here.
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
- all_scopes = {}
111
- scoped_children_hashes.each do |h|
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
- # If an abstract scope is present, but _all_ of its concrete types
116
- # are also in the list, remove it from the list of scopes to check,
117
- # because every possible type is covered by a concrete type.
118
- # (That is, there are no remainder types to check.)
119
- prev_keys = all_scopes.keys
120
- prev_keys.each do |scope|
121
- next unless scope.kind.abstract?
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
- all_scopes.delete(scope)
102
+ possible_scope_types.delete(possible_scope_type)
130
103
  end
131
104
 
132
- # This will hold `{ type => int }` pairs, one for each possible branch
133
- complexity_by_scope = {}
134
-
135
- # For each scope,
136
- # find the lexical selections that might apply to it,
137
- # and gather them together into an array.
138
- # Then, treat the set of selection hashes
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
- # Calculate the complexity for `scope`, merging all
152
- # possible lexical branches.
153
- complexity_value = merged_max_complexity(query, children_for_scope)
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 applies_to?(query, left_scope, right_scope)
162
- if left_scope == right_scope
163
- # This can happen when several branches are being analyzed together
164
- true
165
- else
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 children_for_scope [Array<Hash>] An array of `scoped_children[scope]` hashes
183
- # (`{field_key => complexity}`)
184
- # @return [Integer] Complexity value for all these selections in the current scope
185
- def merged_max_complexity(query, children_for_scope)
186
- all_keys = []
187
- children_for_scope.each do |c|
188
- all_keys.concat(c.keys)
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
- all_keys.uniq!
191
- complexity_for_keys = {}
192
-
193
- all_keys.each do |child_key|
194
- scoped_children_for_key = nil
195
- complexity_for_key = nil
196
- children_for_scope.each do |children_hash|
197
- next unless children_hash.key?(child_key)
198
-
199
- complexity_for_key = children_hash[child_key]
200
- if complexity_for_key.terminal?
201
- # Assume that all terminals would return the same complexity
202
- # Since it's a terminal, its child complexity is zero.
203
- complexity = complexity_for_key.own_complexity(0)
204
- complexity_for_keys[child_key] = complexity
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
- scoped_children_for_key ||= []
209
- scoped_children_for_key << complexity_for_key.scoped_children
162
+ composite_scopes ||= []
163
+ composite_scopes << child_scope
210
164
  end
211
165
  end
212
166
 
213
- next unless scoped_children_for_key
167
+ if composite_scopes
168
+ child_complexity = merged_max_complexity_for_scopes(query, composite_scopes)
214
169
 
215
- child_complexity = merged_max_complexity_for_scopes(query, scoped_children_for_key)
216
- # This is the _last_ one we visited; assume it's representative.
217
- max_complexity = complexity_for_key.own_complexity(child_complexity)
218
-
219
- field_complexity(complexity_for_key, max_complexity: max_complexity, child_complexity: child_complexity)
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
- complexity_for_keys[child_key] = max_complexity
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
@@ -19,29 +19,27 @@ module GraphQL
19
19
  while first_pass || job_tasks.any?
20
20
  first_pass = false
21
21
 
22
- root_task.async do |jobs_task|
23
- while (task = job_tasks.shift || spawn_job_task(jobs_task, jobs_condition))
24
- if task.alive?
25
- next_job_tasks << task
26
- elsif task.failed?
27
- # re-raise a raised error -
28
- # this also covers errors from sources since
29
- # these jobs wait for sources as needed.
30
- task.wait
31
- end
22
+ while (task = job_tasks.shift || spawn_job_task(root_task, jobs_condition))
23
+ if task.alive?
24
+ next_job_tasks << task
25
+ elsif task.failed?
26
+ # re-raise a raised error -
27
+ # this also covers errors from sources since
28
+ # these jobs wait for sources as needed.
29
+ task.wait
32
30
  end
33
- end.wait
31
+ end
32
+ root_task.yield # give job tasks a chance to run
34
33
  job_tasks.concat(next_job_tasks)
35
34
  next_job_tasks.clear
36
35
 
37
36
  while source_tasks.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
38
- root_task.async do |sources_loop_task|
39
- while (task = source_tasks.shift || spawn_source_task(sources_loop_task, sources_condition))
40
- if task.alive?
41
- next_source_tasks << task
42
- end
37
+ while (task = source_tasks.shift || spawn_source_task(root_task, sources_condition))
38
+ if task.alive?
39
+ next_source_tasks << task
43
40
  end
44
- end.wait
41
+ end
42
+ root_task.yield # give source tasks a chance to run
45
43
  sources_condition.signal
46
44
  source_tasks.concat(next_source_tasks)
47
45
  next_source_tasks.clear
@@ -58,7 +56,7 @@ module GraphQL
58
56
  def spawn_job_task(parent_task, condition)
59
57
  if @pending_jobs.any?
60
58
  fiber_vars = get_fiber_variables
61
- parent_task.async do |t|
59
+ parent_task.async do
62
60
  set_fiber_variables(fiber_vars)
63
61
  Thread.current[:graphql_dataloader_next_tick] = condition
64
62
  while job = @pending_jobs.shift
@@ -169,7 +169,7 @@ module GraphQL
169
169
  nil
170
170
  end
171
171
 
172
- attr_reader :pending
172
+ attr_reader :pending, :results
173
173
 
174
174
  private
175
175
 
@@ -119,12 +119,7 @@ module GraphQL
119
119
  #
120
120
  # @return [void]
121
121
  def yield
122
- if use_fiber_resume?
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
- source_instance.pending.merge!(pending)
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
- if use_fiber_resume?
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
- if parent_fiber
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
- if !any_tokens
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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.2.1"
3
+ VERSION = "2.2.3"
4
4
  end
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.1
4
+ version: 2.2.3
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-20 00:00:00.000000000 Z
11
+ date: 2023-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: racc
@@ -631,7 +631,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
631
631
  - !ruby/object:Gem::Version
632
632
  version: '0'
633
633
  requirements: []
634
- rubygems_version: 3.4.10
634
+ rubygems_version: 3.5.3
635
635
  signing_key:
636
636
  specification_version: 4
637
637
  summary: A GraphQL language and runtime for Ruby