graphql 2.6.13 → 2.6.14

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.
@@ -7,6 +7,10 @@ module GraphQL
7
7
  module Pagination
8
8
  # A generic class for working with database query objects.
9
9
  class RelationConnection < Pagination::Connection
10
+ # Database limits and offsets must fit in a signed 64-bit integer.
11
+ MAX_OFFSET = (2**63) - 1
12
+ private_constant :MAX_OFFSET
13
+
10
14
  def nodes
11
15
  load_nodes
12
16
  @nodes
@@ -87,7 +91,11 @@ module GraphQL
87
91
  # :call-seq:
88
92
  # relation_larger_than(Object relation, Integer _initial_offset, Integer size) -> bool
89
93
  def relation_larger_than(relation, _initial_offset, size)
90
- relation_count(set_limit(relation, size + 1)) == size + 1
94
+ if (limit = relation_limit(relation)) && limit <= size
95
+ false
96
+ else
97
+ relation_count(set_limit(relation, size + 1)) == size + 1
98
+ end
91
99
  end
92
100
 
93
101
  # **Parameters**
@@ -154,7 +162,8 @@ module GraphQL
154
162
  # offset_from_cursor(cursor) -> Integer
155
163
  def offset_from_cursor(cursor)
156
164
  offset = Integer(decode(cursor), 10, exception: false) #: Integer?
157
- if offset.nil? || offset <= 0
165
+ if offset.nil? || offset <= 0 || offset > MAX_OFFSET ||
166
+ offset + (relation_offset(items) || 0) > MAX_OFFSET
158
167
  raise GraphQL::ExecutionError, "Invalid cursor: #{cursor.inspect}"
159
168
  end
160
169
  offset
@@ -191,20 +200,21 @@ module GraphQL
191
200
 
192
201
  if after_offset
193
202
  next_offset += after_offset
203
+ relation_limit = [relation_limit - after_offset, 0].max if relation_limit
194
204
  end
195
205
 
196
206
  if before_offset && after_offset
197
207
  if after_offset < before_offset
198
208
  # Get the number of items between the two cursors
199
209
  space_between = before_offset - after_offset - 1
200
- relation_limit = space_between
210
+ relation_limit = [relation_limit, space_between].compact.min
201
211
  else
202
212
  # The cursors overextend one another to an empty set
203
213
  @sliced_nodes_null_relation = true
204
214
  end
205
215
  elsif before_offset
206
216
  # Use limit to cut off the tail of the relation
207
- relation_limit = before_offset - 1
217
+ relation_limit = [relation_limit, before_offset - 1].compact.min
208
218
  end
209
219
 
210
220
  @sliced_nodes_limit = relation_limit
@@ -222,13 +232,13 @@ module GraphQL
222
232
  if @sliced_nodes_null_relation
223
233
  paginated_nodes = null_relation(paginated_nodes)
224
234
  else
225
- if @sliced_nodes_limit
226
- paginated_nodes = set_limit(paginated_nodes, @sliced_nodes_limit)
227
- end
228
-
229
235
  if @sliced_nodes_offset
230
236
  paginated_nodes = set_offset(paginated_nodes, @sliced_nodes_offset)
231
237
  end
238
+
239
+ if @sliced_nodes_limit
240
+ paginated_nodes = set_limit(paginated_nodes, @sliced_nodes_limit)
241
+ end
232
242
  end
233
243
 
234
244
  paginated_nodes
@@ -330,10 +340,11 @@ module GraphQL
330
340
  end
331
341
 
332
342
  # Fetch one row past `first` so `has_next_page` needs no extra query.
333
- # Skips arrays (no query to save) and relations limited below `first` (no next page).
343
+ # Skips arrays (no query to save) and slices limited to `first` or fewer rows (no next page).
334
344
  def load_page
335
345
  page = limited_nodes
336
- return page.to_a if page.is_a?(Array) || !peek_next_page? || relation_limit(page) != first
346
+ return page.to_a if page.is_a?(Array) || !peek_next_page? || relation_limit(page) != first ||
347
+ (@sliced_nodes_limit && @sliced_nodes_limit <= first)
337
348
 
338
349
  rows = set_limit(page, first + 1).to_a
339
350
  @has_next_page = rows.size > first
@@ -179,8 +179,7 @@ module GraphQL
179
179
  next_selections << sel
180
180
  end
181
181
  else
182
- raise "Unexpected selection in partial path: #{sel.class}, #{sel.inspect}"
183
- sel #: absurd
182
+ raise "Unexpected selection in partial path: #{sel.class}, #{sel.inspect}" #: absurd(sel)
184
183
  end
185
184
  end
186
185
  end
@@ -11,9 +11,10 @@ module GraphQL
11
11
  @own_types = own_types
12
12
  @directives = Set.new
13
13
  @possible_types = {}
14
+ @possible_types_sets = {}
14
15
  @types = {}
15
16
  @union_memberships = {}
16
- @references = Hash.new { |h, k| h[k] = Set.new }
17
+ @references = {}.compare_by_identity
17
18
  @arguments_with_default_values = []
18
19
  add_type_and_traverse(new_types)
19
20
  end
@@ -21,7 +22,8 @@ module GraphQL
21
22
  private
22
23
 
23
24
  def references_to(thing, from:)
24
- @references[thing].add(from)
25
+ refs = @references[thing] ||= {}.compare_by_identity
26
+ refs[from] = true
25
27
  end
26
28
 
27
29
  def get_type(name)
@@ -42,19 +44,25 @@ module GraphQL
42
44
 
43
45
  def add_directives_from(owner)
44
46
  if !(dir_instances = owner.directives).empty?
45
- dirs = dir_instances.map(&:class)
46
- @directives.merge(dirs)
47
- add_type_and_traverse(dirs)
47
+ new_dirs = nil #: Array[singleton(GraphQL::Schema::Directive)]?
48
+ dir_instances.each do |dir|
49
+ dir_class = dir.class
50
+ if !@directives.include?(dir_class)
51
+ new_dirs ||= []
52
+ new_dirs << dir_class
53
+ end
54
+ end
55
+ if new_dirs
56
+ @directives.merge(new_dirs)
57
+ add_type_and_traverse(new_dirs)
58
+ end
48
59
  end
49
60
  end
50
61
 
51
62
  def add_type_and_traverse(new_types)
52
63
  late_types = []
53
- path = []
54
64
  new_types.each do |t|
55
- path.push(t.graphql_name)
56
- add_type(t, owner: nil, late_types: late_types, path: path)
57
- path.pop
65
+ add_type(t, owner: nil, late_types: late_types)
58
66
  end
59
67
  missed_late_types = 0
60
68
  while (late_type_vals = late_types.shift)
@@ -64,13 +72,13 @@ module GraphQL
64
72
  # Reset the counter, since we might succeed next go-round
65
73
  missed_late_types = 0
66
74
  update_type_owner(type_owner, type)
67
- add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name])
75
+ add_type(type, owner: type_owner, late_types: late_types)
68
76
  elsif lt.is_a?(LateBoundType)
69
77
  if (type = get_type(lt.name))
70
78
  # Reset the counter, since we might succeed next go-round
71
79
  missed_late_types = 0
72
80
  update_type_owner(type_owner, type)
73
- add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name])
81
+ add_type(type, owner: type_owner, late_types: late_types)
74
82
  else
75
83
  missed_late_types += 1
76
84
  # Add it back to the list, maybe we'll be able to resolve it later.
@@ -89,6 +97,15 @@ module GraphQL
89
97
  nil
90
98
  end
91
99
 
100
+ # The Set makes this fast for interfaces with many possible types. The Array keeps their order.
101
+ def add_possible_type(interface_type, object_type)
102
+ types_set = @possible_types_sets[interface_type] ||= Set.new
103
+ if types_set.add?(object_type)
104
+ pt = @possible_types[interface_type] ||= []
105
+ pt << object_type
106
+ end
107
+ end
108
+
92
109
  def update_type_owner(owner, type)
93
110
  case owner
94
111
  when Module
@@ -114,9 +131,8 @@ module GraphQL
114
131
  end
115
132
  implementing_type.implements(*new_interfaces)
116
133
  new_interfaces.each do |int|
117
- pt = @possible_types[int] ||= []
118
- if !pt.include?(owner) && owner.is_a?(Class)
119
- pt << owner
134
+ if owner.is_a?(Class)
135
+ add_possible_type(int, owner)
120
136
  end
121
137
  int.interfaces.each do |indirect_int|
122
138
  if indirect_int.is_a?(LateBoundType)
@@ -125,10 +141,7 @@ module GraphQL
125
141
  end
126
142
  elsif owner.is_a?(Class)
127
143
  # `implements` added this one, too, because `int` implements it
128
- indirect_pt = @possible_types[indirect_int] ||= []
129
- if !indirect_pt.include?(owner)
130
- indirect_pt << owner
131
- end
144
+ add_possible_type(indirect_int, owner)
132
145
  end
133
146
  end
134
147
  end
@@ -161,7 +174,7 @@ module GraphQL
161
174
  end
162
175
  end
163
176
 
164
- def add_type(type, owner:, late_types:, path:)
177
+ def add_type(type, owner:, late_types:)
165
178
  if type.is_a?(String) || type.is_a?(GraphQL::Schema::LateBoundType)
166
179
  late_types << [owner, type]
167
180
  return
@@ -181,9 +194,7 @@ module GraphQL
181
194
  if !arg_type.is_a?(GraphQL::Schema::LateBoundType)
182
195
  references_to(arg_type, from: arg)
183
196
  end
184
- path.push(arg.graphql_name)
185
- add_type(arg_type, owner: arg, late_types: late_types, path: path)
186
- path.pop
197
+ add_type(arg_type, owner: arg, late_types: late_types)
187
198
  if arg.default_value?
188
199
  @arguments_with_default_values << arg
189
200
  end
@@ -202,40 +213,45 @@ module GraphQL
202
213
  if type.kind.fields?
203
214
  type.all_field_definitions.each do |field|
204
215
  field.ensure_loaded
205
- name = field.graphql_name
206
216
  field_type = field.type.unwrap
207
- if !field_type.is_a?(GraphQL::Schema::LateBoundType)
217
+ if field_type.is_a?(GraphQL::Schema::LateBoundType)
218
+ add_type(field_type, owner: field, late_types: late_types)
219
+ else
208
220
  references_to(field_type, from: field)
221
+ if !@types[field_type.graphql_name].equal?(field_type)
222
+ add_type(field_type, owner: field, late_types: late_types)
223
+ end
209
224
  end
210
- path.push(name)
211
- add_type(field_type, owner: field, late_types: late_types, path: path)
212
225
  add_directives_from(field)
213
226
  field.all_argument_definitions.each do |arg|
214
227
  add_directives_from(arg)
215
228
  arg_type = arg.type.unwrap
216
- if !arg_type.is_a?(GraphQL::Schema::LateBoundType)
229
+ if arg_type.is_a?(GraphQL::Schema::LateBoundType)
230
+ add_type(arg_type, owner: arg, late_types: late_types)
231
+ else
217
232
  references_to(arg_type, from: arg)
233
+ if !@types[arg_type.graphql_name].equal?(arg_type)
234
+ add_type(arg_type, owner: arg, late_types: late_types)
235
+ end
218
236
  end
219
- path.push(arg.graphql_name)
220
- add_type(arg_type, owner: arg, late_types: late_types, path: path)
221
- path.pop
222
237
  if arg.default_value?
223
238
  @arguments_with_default_values << arg
224
239
  end
225
240
  end
226
- path.pop
227
241
  end
228
242
  end
229
243
  if type.kind.input_object?
230
244
  type.all_argument_definitions.each do |arg|
231
245
  add_directives_from(arg)
232
246
  arg_type = arg.type.unwrap
233
- if !arg_type.is_a?(GraphQL::Schema::LateBoundType)
247
+ if arg_type.is_a?(GraphQL::Schema::LateBoundType)
248
+ add_type(arg_type, owner: arg, late_types: late_types)
249
+ else
234
250
  references_to(arg_type, from: arg)
251
+ if !@types[arg_type.graphql_name].equal?(arg_type)
252
+ add_type(arg_type, owner: arg, late_types: late_types)
253
+ end
235
254
  end
236
- path.push(arg.graphql_name)
237
- add_type(arg_type, owner: arg, late_types: late_types, path: path)
238
- path.pop
239
255
  if arg.default_value?
240
256
  @arguments_with_default_values << arg
241
257
  end
@@ -243,18 +259,14 @@ module GraphQL
243
259
  end
244
260
  if type.kind.union?
245
261
  @possible_types[type] = type.all_possible_types
246
- path.push("possible_types")
247
262
  type.all_possible_types.each do |t|
248
- add_type(t, owner: type, late_types: late_types, path: path)
263
+ add_type(t, owner: type, late_types: late_types)
249
264
  end
250
- path.pop
251
265
  end
252
266
  if type.kind.interface?
253
- path.push("orphan_types")
254
267
  type.orphan_types.each do |t|
255
- add_type(t, owner: type, late_types: late_types, path: path)
268
+ add_type(t, owner: type, late_types: late_types)
256
269
  end
257
- path.pop
258
270
  end
259
271
  if type.kind.object?
260
272
  possible_types_for_this_name = @possible_types[type] ||= []
@@ -262,26 +274,21 @@ module GraphQL
262
274
  end
263
275
 
264
276
  if type.kind.object? || type.kind.interface?
265
- path.push("implements")
266
277
  type.interface_type_memberships.each do |interface_type_membership|
267
278
  case interface_type_membership
268
279
  when Schema::TypeMembership
269
280
  interface_type = interface_type_membership.abstract_type
270
281
  # We can get these now; we'll have to get late-bound types later
271
282
  if interface_type.is_a?(Module) && type.is_a?(Class)
272
- implementers = @possible_types[interface_type] ||= []
273
- if !implementers.include?(type)
274
- implementers << type
275
- end
283
+ add_possible_type(interface_type, type)
276
284
  end
277
285
  when String, Schema::LateBoundType
278
286
  interface_type = interface_type_membership
279
287
  else
280
288
  raise ArgumentError, "Invariant: unexpected type membership for #{type.graphql_name}: #{interface_type_membership.class} (#{interface_type_membership.inspect})"
281
289
  end
282
- add_type(interface_type, owner: type, late_types: late_types, path: path)
290
+ add_type(interface_type, owner: type, late_types: late_types)
283
291
  end
284
- path.pop
285
292
  end
286
293
 
287
294
  if type.kind.enum?
@@ -515,7 +515,7 @@ module GraphQL
515
515
  coerced_value = begin
516
516
  type.coerce_input(value, context)
517
517
  rescue StandardError => err
518
- context.schema.handle_or_reraise(context, err)
518
+ handle_or_reraise_arg_error(context, err)
519
519
  end
520
520
 
521
521
  # If this isn't lazy, then the block returns eagerly and assigns the result here
@@ -525,14 +525,14 @@ module GraphQL
525
525
  prepared_value = begin
526
526
  prepare_value(parent_object, resolved_coerced_value, context: context)
527
527
  rescue StandardError => err
528
- context.schema.handle_or_reraise(context, err)
528
+ handle_or_reraise_arg_error(context, err)
529
529
  end
530
530
 
531
531
  if loads && !from_resolver?
532
532
  loaded_value = begin
533
533
  load_and_authorize_value(owner, prepared_value, context)
534
534
  rescue StandardError => err
535
- context.schema.handle_or_reraise(context, err)
535
+ handle_or_reraise_arg_error(context, err)
536
536
  end
537
537
  end
538
538
 
@@ -549,6 +549,17 @@ module GraphQL
549
549
  end
550
550
  end
551
551
 
552
+ # When a `rescue_from` handler *returns* an error object instead of raising,
553
+ # it must not be used as this argument's value — raise it so that
554
+ # `coerce_arguments` short-circuits resolution with it.
555
+ def handle_or_reraise_arg_error(context, err) # :nodoc:
556
+ handled_err = context.schema.handle_or_reraise(context, err)
557
+ if handled_err.is_a?(GraphQL::ExecutionError) || handled_err.is_a?(GraphQL::UnauthorizedError)
558
+ raise handled_err
559
+ end
560
+ handled_err
561
+ end
562
+
552
563
  def load_and_authorize_value(load_method_owner, coerced_value, context)
553
564
  if coerced_value.nil?
554
565
  return nil