graphql 2.3.7 → 2.4.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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +46 -0
  3. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  4. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  5. data/lib/generators/graphql/type_generator.rb +1 -1
  6. data/lib/graphql/analysis/field_usage.rb +1 -1
  7. data/lib/graphql/analysis/query_complexity.rb +3 -3
  8. data/lib/graphql/analysis/visitor.rb +8 -7
  9. data/lib/graphql/analysis.rb +4 -4
  10. data/lib/graphql/autoload.rb +37 -0
  11. data/lib/graphql/current.rb +52 -0
  12. data/lib/graphql/dataloader/async_dataloader.rb +7 -6
  13. data/lib/graphql/dataloader/source.rb +7 -4
  14. data/lib/graphql/dataloader.rb +40 -19
  15. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  16. data/lib/graphql/execution/interpreter/resolve.rb +13 -9
  17. data/lib/graphql/execution/interpreter/runtime.rb +35 -31
  18. data/lib/graphql/execution/interpreter.rb +6 -4
  19. data/lib/graphql/execution/lookahead.rb +18 -11
  20. data/lib/graphql/introspection/directive_type.rb +1 -1
  21. data/lib/graphql/introspection/entry_points.rb +2 -2
  22. data/lib/graphql/introspection/field_type.rb +1 -1
  23. data/lib/graphql/introspection/schema_type.rb +6 -11
  24. data/lib/graphql/introspection/type_type.rb +5 -5
  25. data/lib/graphql/invalid_null_error.rb +1 -1
  26. data/lib/graphql/language/cache.rb +13 -0
  27. data/lib/graphql/language/comment.rb +18 -0
  28. data/lib/graphql/language/document_from_schema_definition.rb +62 -34
  29. data/lib/graphql/language/lexer.rb +18 -15
  30. data/lib/graphql/language/nodes.rb +24 -16
  31. data/lib/graphql/language/parser.rb +14 -1
  32. data/lib/graphql/language/printer.rb +31 -15
  33. data/lib/graphql/language/sanitized_printer.rb +1 -1
  34. data/lib/graphql/language.rb +6 -6
  35. data/lib/graphql/pagination/connection.rb +1 -1
  36. data/lib/graphql/query/context/scoped_context.rb +1 -1
  37. data/lib/graphql/query/context.rb +13 -6
  38. data/lib/graphql/query/null_context.rb +3 -5
  39. data/lib/graphql/query/variable_validation_error.rb +1 -1
  40. data/lib/graphql/query.rb +72 -18
  41. data/lib/graphql/railtie.rb +7 -0
  42. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  43. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  44. data/lib/graphql/rubocop.rb +2 -0
  45. data/lib/graphql/schema/addition.rb +2 -1
  46. data/lib/graphql/schema/always_visible.rb +6 -2
  47. data/lib/graphql/schema/argument.rb +14 -1
  48. data/lib/graphql/schema/build_from_definition.rb +9 -1
  49. data/lib/graphql/schema/directive/flagged.rb +2 -2
  50. data/lib/graphql/schema/directive.rb +1 -1
  51. data/lib/graphql/schema/enum.rb +71 -23
  52. data/lib/graphql/schema/enum_value.rb +10 -2
  53. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  54. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  55. data/lib/graphql/schema/field.rb +102 -47
  56. data/lib/graphql/schema/field_extension.rb +1 -1
  57. data/lib/graphql/schema/has_single_input_argument.rb +5 -2
  58. data/lib/graphql/schema/input_object.rb +90 -39
  59. data/lib/graphql/schema/interface.rb +22 -5
  60. data/lib/graphql/schema/introspection_system.rb +5 -16
  61. data/lib/graphql/schema/loader.rb +1 -1
  62. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  63. data/lib/graphql/schema/member/has_arguments.rb +25 -20
  64. data/lib/graphql/schema/member/has_directives.rb +3 -3
  65. data/lib/graphql/schema/member/has_fields.rb +26 -6
  66. data/lib/graphql/schema/member/has_interfaces.rb +4 -4
  67. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  68. data/lib/graphql/schema/member/has_validators.rb +1 -1
  69. data/lib/graphql/schema/object.rb +8 -0
  70. data/lib/graphql/schema/printer.rb +1 -0
  71. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  72. data/lib/graphql/schema/resolver.rb +12 -14
  73. data/lib/graphql/schema/subscription.rb +2 -2
  74. data/lib/graphql/schema/type_expression.rb +2 -2
  75. data/lib/graphql/schema/union.rb +1 -1
  76. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  77. data/lib/graphql/schema/validator/required_validator.rb +28 -4
  78. data/lib/graphql/schema/validator.rb +3 -1
  79. data/lib/graphql/schema/visibility/migration.rb +187 -0
  80. data/lib/graphql/schema/visibility/profile.rb +353 -0
  81. data/lib/graphql/schema/visibility/visit.rb +190 -0
  82. data/lib/graphql/schema/visibility.rb +294 -0
  83. data/lib/graphql/schema/warden.rb +166 -16
  84. data/lib/graphql/schema.rb +348 -94
  85. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  86. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  87. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  88. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  89. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  90. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  91. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  92. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  93. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +2 -2
  94. data/lib/graphql/static_validation/rules/fields_will_merge.rb +8 -7
  95. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  96. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  97. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  98. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  99. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  100. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  101. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
  102. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  103. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  104. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  105. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  106. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  107. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  108. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  109. data/lib/graphql/static_validation/validation_context.rb +18 -2
  110. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +3 -2
  111. data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
  112. data/lib/graphql/subscriptions/event.rb +1 -1
  113. data/lib/graphql/subscriptions.rb +6 -4
  114. data/lib/graphql/testing/helpers.rb +10 -6
  115. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  116. data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
  117. data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
  118. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  119. data/lib/graphql/types.rb +18 -11
  120. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  121. data/lib/graphql/version.rb +1 -1
  122. data/lib/graphql.rb +81 -45
  123. metadata +31 -8
  124. data/lib/graphql/language/token.rb +0 -34
  125. data/lib/graphql/schema/invalid_type_error.rb +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13269b31415c3c837e80ec0097b8162c65035119f16860a18012b15e71ace318
4
- data.tar.gz: a3d1a936954352bd4ac46aab7f682377f3c219c477db32a65eb414d04655f099
3
+ metadata.gz: 576505327425ece2720724b18acb06b712d9574f18bbde7f43242a3288046966
4
+ data.tar.gz: 6b67a9aac34abf8e340c8fcdba329ff7e64357f9ade833d909244ec2683c4174
5
5
  SHA512:
6
- metadata.gz: 721413cdc90187b7348514ea90d65f25a22a88551c3665dee7ff7f5d986b515f535bb147fedc72fbee4942ac5fdea2f9c1ffb38f5751706a98cecef98e7f94e6
7
- data.tar.gz: 4a1b47909ffbc71665ee2451002da4e2633dd010775b49e66e7a98068b73e60e5dc3c37e2491ee10f4f52bacc24d0c38f19d31a2cc9f436e8af2ebd6fac73d03
6
+ metadata.gz: 394b8ba06f32a1a983b5cb41cf322c361fb05fe68b9dd5946e5d0eb846923fc03edf9674935767a0a06737ccad300a790439a160e0499f82ed0f6ff5223a675d
7
+ data.tar.gz: 0410a99ab8efe22a1d130b93d08a12d2a3e989974a50af65416ea4979023826bd829da64d1eeb6d4b9dd75d552547a80e26f100f14a7c107acc0740339044430
@@ -45,6 +45,13 @@ module Graphql
45
45
  # post "/graphql", to: "graphql#execute"
46
46
  # ```
47
47
  #
48
+ # Add ActiveRecord::QueryLogs metadata:
49
+ # ```ruby
50
+ # current_graphql_operation: -> { GraphQL::Current.operation_name },
51
+ # current_graphql_field: -> { GraphQL::Current.field&.path },
52
+ # current_dataloader_source: -> { GraphQL::Current.dataloader_source_class },
53
+ # ```
54
+ #
48
55
  # Accept a `--batch` option which adds `GraphQL::Batch` setup.
49
56
  #
50
57
  # Use `--skip-graphiql` to skip `graphiql-rails` installation.
@@ -92,6 +99,11 @@ module Graphql
92
99
  default: false,
93
100
  desc: "Use GraphQL Playground over Graphiql as IDE"
94
101
 
102
+ class_option :skip_query_logs,
103
+ type: :boolean,
104
+ default: false,
105
+ desc: "Skip ActiveRecord::QueryLogs hooks in config/application.rb"
106
+
95
107
  # These two options are taken from Rails' own generators'
96
108
  class_option :api,
97
109
  type: :boolean,
@@ -180,6 +192,40 @@ RUBY
180
192
  install_relay
181
193
  end
182
194
 
195
+ if !options[:skip_query_logs]
196
+ config_file = "config/application.rb"
197
+ current_app_rb = File.read(Rails.root.join(config_file))
198
+ existing_log_tags_pattern = /config.active_record.query_log_tags = \[\n?(\s*:[a-z_]+,?\s*\n?|\s*#[^\]]*\n)*/m
199
+ existing_log_tags = existing_log_tags_pattern.match(current_app_rb)
200
+ if existing_log_tags && behavior == :invoke
201
+ code = <<-RUBY
202
+ # GraphQL-Ruby query log tags:
203
+ current_graphql_operation: -> { GraphQL::Current.operation_name },
204
+ current_graphql_field: -> { GraphQL::Current.field&.path },
205
+ current_dataloader_source: -> { GraphQL::Current.dataloader_source_class },
206
+ RUBY
207
+ if !existing_log_tags.to_s.end_with?(",")
208
+ code = ",\n#{code} "
209
+ end
210
+ # Try to insert this code _after_ any plain symbol entries in the array of query log tags:
211
+ after_code = existing_log_tags_pattern
212
+ else
213
+ code = <<-RUBY
214
+ config.active_record.query_log_tags_enabled = true
215
+ config.active_record.query_log_tags = [
216
+ # Rails query log tags:
217
+ :application, :controller, :action, :job,
218
+ # GraphQL-Ruby query log tags:
219
+ current_graphql_operation: -> { GraphQL::Current.operation_name },
220
+ current_graphql_field: -> { GraphQL::Current.field&.path },
221
+ current_dataloader_source: -> { GraphQL::Current.dataloader_source_class },
222
+ ]
223
+ RUBY
224
+ after_code = "class Application < Rails::Application\n"
225
+ end
226
+ insert_into_file(config_file, code, after: after_code)
227
+ end
228
+
183
229
  if gemfile_modified?
184
230
  say "Gemfile has been modified, make sure you `bundle install`"
185
231
  end
@@ -18,7 +18,7 @@ module Graphql
18
18
  class_option :orm, banner: "NAME", type: :string, required: true,
19
19
  desc: "ORM to generate the controller for"
20
20
 
21
- class_option 'namespaced_types',
21
+ class_option :namespaced_types,
22
22
  type: :boolean,
23
23
  required: false,
24
24
  default: false,
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  <% module_namespacing_when_supported do -%>
2
4
  module Resolvers
3
5
  class BaseResolver < GraphQL::Schema::Resolver
@@ -11,7 +11,7 @@ module Graphql
11
11
  class TypeGeneratorBase < Rails::Generators::NamedBase
12
12
  include Core
13
13
 
14
- class_option 'namespaced_types',
14
+ class_option :namespaced_types,
15
15
  type: :boolean,
16
16
  required: false,
17
17
  default: false,
@@ -72,7 +72,7 @@ module GraphQL
72
72
  end
73
73
 
74
74
  def extract_deprecated_enum_value(enum_type, value)
75
- enum_value = @query.warden.enum_values(enum_type).find { |ev| ev.value == value }
75
+ enum_value = @query.types.enum_values(enum_type).find { |ev| ev.value == value }
76
76
  if enum_value&.deprecation_reason
77
77
  @used_deprecated_enum_values << enum_value.path
78
78
  end
@@ -98,7 +98,7 @@ module GraphQL
98
98
  possible_scope_types.keys.each do |possible_scope_type|
99
99
  next unless possible_scope_type.kind.abstract?
100
100
 
101
- query.possible_types(possible_scope_type).each do |impl_type|
101
+ query.types.possible_types(possible_scope_type).each do |impl_type|
102
102
  possible_scope_types[impl_type] ||= true
103
103
  end
104
104
  possible_scope_types.delete(possible_scope_type)
@@ -123,8 +123,8 @@ module GraphQL
123
123
  def types_intersect?(query, a, b)
124
124
  return true if a == b
125
125
 
126
- a_types = query.possible_types(a)
127
- query.possible_types(b).any? { |t| a_types.include?(t) }
126
+ a_types = query.types.possible_types(a)
127
+ query.types.possible_types(b).any? { |t| a_types.include?(t) }
128
128
  end
129
129
 
130
130
  # A hook which is called whenever a field's max complexity is calculated.
@@ -21,6 +21,7 @@ module GraphQL
21
21
  @rescued_errors = []
22
22
  @query = query
23
23
  @schema = query.schema
24
+ @types = query.types
24
25
  @response_path = []
25
26
  @skip_stack = [false]
26
27
  super(query.selected_operation)
@@ -131,7 +132,7 @@ module GraphQL
131
132
  @response_path.push(node.alias || node.name)
132
133
  parent_type = @object_types.last
133
134
  # This could be nil if the previous field wasn't found:
134
- field_definition = parent_type && @schema.get_field(parent_type, node.name, @query.context)
135
+ field_definition = parent_type && @types.field(parent_type, node.name)
135
136
  @field_definitions.push(field_definition)
136
137
  if !field_definition.nil?
137
138
  next_object_type = field_definition.type.unwrap
@@ -167,14 +168,14 @@ module GraphQL
167
168
  argument_defn = if (arg = @argument_definitions.last)
168
169
  arg_type = arg.type.unwrap
169
170
  if arg_type.kind.input_object?
170
- arg_type.get_argument(node.name, @query.context)
171
+ @types.argument(arg_type, node.name)
171
172
  else
172
173
  nil
173
174
  end
174
175
  elsif (directive_defn = @directive_definitions.last)
175
- directive_defn.get_argument(node.name, @query.context)
176
+ @types.argument(directive_defn, node.name)
176
177
  elsif (field_defn = @field_definitions.last)
177
- field_defn.get_argument(node.name, @query.context)
178
+ @types.argument(field_defn, node.name)
178
179
  else
179
180
  nil
180
181
  end
@@ -245,7 +246,7 @@ module GraphQL
245
246
  fragment_def = query.fragments[fragment_spread.name]
246
247
 
247
248
  object_type = if fragment_def.type
248
- @query.warden.get_type(fragment_def.type.name)
249
+ @types.type(fragment_def.type.name)
249
250
  else
250
251
  object_types.last
251
252
  end
@@ -263,12 +264,12 @@ module GraphQL
263
264
 
264
265
  def skip?(ast_node)
265
266
  dir = ast_node.directives
266
- dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
267
+ !dir.empty? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
267
268
  end
268
269
 
269
270
  def on_fragment_with_type(node)
270
271
  object_type = if node.type
271
- @query.warden.get_type(node.type.name)
272
+ @types.type(node.type.name)
272
273
  else
273
274
  @object_types.last
274
275
  end
@@ -55,10 +55,10 @@ module GraphQL
55
55
  .tap { _1.select!(&:analyze?) }
56
56
 
57
57
  analyzers_to_run = query_analyzers + multiplex_analyzers
58
- if analyzers_to_run.any?
58
+ if !analyzers_to_run.empty?
59
59
 
60
60
  analyzers_to_run.select!(&:visit?)
61
- if analyzers_to_run.any?
61
+ if !analyzers_to_run.empty?
62
62
  visitor = GraphQL::Analysis::Visitor.new(
63
63
  query: query,
64
64
  analyzers: analyzers_to_run
@@ -69,7 +69,7 @@ module GraphQL
69
69
  visitor.visit
70
70
  end
71
71
 
72
- if visitor.rescued_errors.any?
72
+ if !visitor.rescued_errors.empty?
73
73
  return visitor.rescued_errors
74
74
  end
75
75
  end
@@ -81,7 +81,7 @@ module GraphQL
81
81
  end
82
82
  rescue Timeout::Error
83
83
  [GraphQL::AnalysisError.new("Timeout on validation of query")]
84
- rescue GraphQL::UnauthorizedError
84
+ rescue GraphQL::UnauthorizedError, GraphQL::ExecutionError
85
85
  # This error was raised during analysis and will be returned the client before execution
86
86
  []
87
87
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Autoload
5
+ # Register a constant named `const_name` to be loaded from `path`.
6
+ # This is like `Kernel#autoload` but it tracks the constants so they can be eager-loaded with {#eager_load!}
7
+ # @param const_name [Symbol]
8
+ # @param path [String]
9
+ # @return [void]
10
+ def autoload(const_name, path)
11
+ @_eagerloaded_constants ||= []
12
+ @_eagerloaded_constants << const_name
13
+
14
+ super const_name, path
15
+ end
16
+
17
+ # Call this to load this constant's `autoload` dependents and continue calling recursively
18
+ # @return [void]
19
+ def eager_load!
20
+ @_eager_loading = true
21
+ if @_eagerloaded_constants
22
+ @_eagerloaded_constants.each { |const_name| const_get(const_name) }
23
+ @_eagerloaded_constants = nil
24
+ end
25
+ nil
26
+ ensure
27
+ @_eager_loading = false
28
+ end
29
+
30
+ private
31
+
32
+ # @return [Boolean] `true` if GraphQL-Ruby is currently eager-loading its constants
33
+ def eager_loading?
34
+ @_eager_loading ||= false
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ # This module exposes Fiber-level runtime information.
5
+ #
6
+ # It won't work across unrelated fibers, although it will work in child Fibers.
7
+ #
8
+ # @example Setting Up ActiveRecord::QueryLogs
9
+ #
10
+ # config.active_record.query_log_tags = [
11
+ # :namespaced_controller,
12
+ # :action,
13
+ # :job,
14
+ # # ...
15
+ # {
16
+ # # GraphQL runtime info:
17
+ # current_graphql_operation: -> { GraphQL::Current.operation_name },
18
+ # current_graphql_field: -> { GraphQL::Current.field&.path },
19
+ # current_dataloader_source: -> { GraphQL::Current.dataloader_source_class },
20
+ # # ...
21
+ # },
22
+ # ]
23
+ #
24
+ module Current
25
+ # @return [String, nil] Comma-joined operation names for the currently-running {Multiplex}. `nil` if all operations are anonymous.
26
+ def self.operation_name
27
+ if (m = Fiber[:__graphql_current_multiplex])
28
+ m.context[:__graphql_current_operation_name] ||= begin
29
+ names = m.queries.map { |q| q.selected_operation_name }
30
+ if names.all?(&:nil?)
31
+ nil
32
+ else
33
+ names.join(",")
34
+ end
35
+ end
36
+ else
37
+ nil
38
+ end
39
+ end
40
+
41
+ # @see GraphQL::Field#path for a string identifying this field
42
+ # @return [GraphQL::Field, nil] The currently-running field, if there is one.
43
+ def self.field
44
+ Fiber[:__graphql_runtime_info]&.values&.first&.current_field
45
+ end
46
+
47
+ # @return [Class, nil] The currently-running {Dataloader::Source} class, if there is one.
48
+ def self.dataloader_source_class
49
+ Fiber[:__graphql_current_dataloader_source]&.class
50
+ end
51
+ end
52
+ end
@@ -3,7 +3,7 @@ module GraphQL
3
3
  class Dataloader
4
4
  class AsyncDataloader < Dataloader
5
5
  def yield
6
- if (condition = Thread.current[:graphql_dataloader_next_tick])
6
+ if (condition = Fiber[:graphql_dataloader_next_tick])
7
7
  condition.wait
8
8
  else
9
9
  Fiber.yield
@@ -12,6 +12,7 @@ module GraphQL
12
12
  end
13
13
 
14
14
  def run
15
+ jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
15
16
  job_fibers = []
16
17
  next_job_fibers = []
17
18
  source_tasks = []
@@ -19,11 +20,11 @@ module GraphQL
19
20
  first_pass = true
20
21
  sources_condition = Async::Condition.new
21
22
  manager = spawn_fiber do
22
- while first_pass || job_fibers.any?
23
+ while first_pass || !job_fibers.empty?
23
24
  first_pass = false
24
25
  fiber_vars = get_fiber_variables
25
26
 
26
- while (f = (job_fibers.shift || spawn_job_fiber))
27
+ while (f = (job_fibers.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size) < jobs_fiber_limit) && spawn_job_fiber)))
27
28
  if f.alive?
28
29
  finished = run_fiber(f)
29
30
  if !finished
@@ -36,8 +37,8 @@ module GraphQL
36
37
 
37
38
  Sync do |root_task|
38
39
  set_fiber_variables(fiber_vars)
39
- while source_tasks.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
40
- while (task = source_tasks.shift || spawn_source_task(root_task, sources_condition))
40
+ while !source_tasks.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
41
+ while (task = (source_tasks.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size + next_source_tasks.size) < total_fiber_limit) && spawn_source_task(root_task, sources_condition))))
41
42
  if task.alive?
42
43
  root_task.yield # give the source task a chance to run
43
44
  next_source_tasks << task
@@ -77,7 +78,7 @@ module GraphQL
77
78
  fiber_vars = get_fiber_variables
78
79
  parent_task.async do
79
80
  set_fiber_variables(fiber_vars)
80
- Thread.current[:graphql_dataloader_next_tick] = condition
81
+ Fiber[:graphql_dataloader_next_tick] = condition
81
82
  pending_sources.each(&:run_pending_keys)
82
83
  cleanup_fiber
83
84
  end
@@ -73,7 +73,7 @@ module GraphQL
73
73
  end
74
74
  }
75
75
 
76
- if pending_keys.any?
76
+ if !pending_keys.empty?
77
77
  sync(pending_keys)
78
78
  end
79
79
 
@@ -98,7 +98,7 @@ module GraphQL
98
98
  while pending_result_keys.any? { |key| !@results.key?(key) }
99
99
  iterations += 1
100
100
  if iterations > MAX_ITERATIONS
101
- raise "#{self.class}#sync tried #{MAX_ITERATIONS} times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency."
101
+ raise "#{self.class}#sync tried #{MAX_ITERATIONS} times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency#{@dataloader.fiber_limit ? " or `fiber_limit: #{@dataloader.fiber_limit}` is set too low" : ""}."
102
102
  end
103
103
  @dataloader.yield
104
104
  end
@@ -186,8 +186,11 @@ This key should have been loaded already. This is a bug in GraphQL::Dataloader,
186
186
  ERR
187
187
  end
188
188
  result = @results[key]
189
-
190
- raise result if result.class <= StandardError
189
+ if result.is_a?(StandardError)
190
+ # Dup it because the rescuer may modify it.
191
+ # (This happens for GraphQL::ExecutionErrors, at least)
192
+ raise result.dup
193
+ end
191
194
 
192
195
  result
193
196
  end
@@ -24,18 +24,23 @@ module GraphQL
24
24
  #
25
25
  class Dataloader
26
26
  class << self
27
- attr_accessor :default_nonblocking
27
+ attr_accessor :default_nonblocking, :default_fiber_limit
28
28
  end
29
29
 
30
- NonblockingDataloader = Class.new(self) { self.default_nonblocking = true }
31
-
32
- def self.use(schema, nonblocking: nil)
33
- schema.dataloader_class = if nonblocking
30
+ def self.use(schema, nonblocking: nil, fiber_limit: nil)
31
+ dataloader_class = if nonblocking
34
32
  warn("`nonblocking: true` is deprecated from `GraphQL::Dataloader`, please use `GraphQL::Dataloader::AsyncDataloader` instead. Docs: https://graphql-ruby.org/dataloader/async_dataloader.")
35
- NonblockingDataloader
33
+ Class.new(self) { self.default_nonblocking = true }
36
34
  else
37
35
  self
38
36
  end
37
+
38
+ if fiber_limit
39
+ dataloader_class = Class.new(dataloader_class)
40
+ dataloader_class.default_fiber_limit = fiber_limit
41
+ end
42
+
43
+ schema.dataloader_class = dataloader_class
39
44
  end
40
45
 
41
46
  # Call the block with a Dataloader instance,
@@ -50,14 +55,18 @@ module GraphQL
50
55
  result
51
56
  end
52
57
 
53
- def initialize(nonblocking: self.class.default_nonblocking)
58
+ def initialize(nonblocking: self.class.default_nonblocking, fiber_limit: self.class.default_fiber_limit)
54
59
  @source_cache = Hash.new { |h, k| h[k] = {} }
55
60
  @pending_jobs = []
56
61
  if !nonblocking.nil?
57
62
  @nonblocking = nonblocking
58
63
  end
64
+ @fiber_limit = fiber_limit
59
65
  end
60
66
 
67
+ # @return [Integer, nil]
68
+ attr_reader :fiber_limit
69
+
61
70
  def nonblocking?
62
71
  @nonblocking
63
72
  end
@@ -69,10 +78,7 @@ module GraphQL
69
78
  def get_fiber_variables
70
79
  fiber_vars = {}
71
80
  Thread.current.keys.each do |fiber_var_key|
72
- # This variable should be fresh in each new fiber
73
- if fiber_var_key != :__graphql_runtime_info
74
- fiber_vars[fiber_var_key] = Thread.current[fiber_var_key]
75
- end
81
+ fiber_vars[fiber_var_key] = Thread.current[fiber_var_key]
76
82
  end
77
83
  fiber_vars
78
84
  end
@@ -178,16 +184,17 @@ module GraphQL
178
184
  end
179
185
 
180
186
  def run
187
+ jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
181
188
  job_fibers = []
182
189
  next_job_fibers = []
183
190
  source_fibers = []
184
191
  next_source_fibers = []
185
192
  first_pass = true
186
193
  manager = spawn_fiber do
187
- while first_pass || job_fibers.any?
194
+ while first_pass || !job_fibers.empty?
188
195
  first_pass = false
189
196
 
190
- while (f = (job_fibers.shift || spawn_job_fiber))
197
+ while (f = (job_fibers.shift || (((next_job_fibers.size + job_fibers.size) < jobs_fiber_limit) && spawn_job_fiber)))
191
198
  if f.alive?
192
199
  finished = run_fiber(f)
193
200
  if !finished
@@ -197,8 +204,8 @@ module GraphQL
197
204
  end
198
205
  join_queues(job_fibers, next_job_fibers)
199
206
 
200
- while source_fibers.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
201
- while (f = source_fibers.shift || spawn_source_fiber)
207
+ while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
208
+ while (f = source_fibers.shift || (((job_fibers.size + source_fibers.size + next_source_fibers.size + next_job_fibers.size) < total_fiber_limit) && spawn_source_fiber))
202
209
  if f.alive?
203
210
  finished = run_fiber(f)
204
211
  if !finished
@@ -217,10 +224,10 @@ module GraphQL
217
224
  raise "Invariant: Manager fiber didn't terminate properly."
218
225
  end
219
226
 
220
- if job_fibers.any?
227
+ if !job_fibers.empty?
221
228
  raise "Invariant: job fibers should have exited but #{job_fibers.size} remained"
222
229
  end
223
- if source_fibers.any?
230
+ if !source_fibers.empty?
224
231
  raise "Invariant: source fibers should have exited but #{source_fibers.size} remained"
225
232
  end
226
233
  rescue UncaughtThrowError => e
@@ -242,6 +249,17 @@ module GraphQL
242
249
 
243
250
  private
244
251
 
252
+ def calculate_fiber_limit
253
+ total_fiber_limit = @fiber_limit || Float::INFINITY
254
+ if total_fiber_limit < 4
255
+ raise ArgumentError, "Dataloader fiber limit is too low (#{total_fiber_limit}), it must be at least 4"
256
+ end
257
+ total_fiber_limit -= 1 # deduct one fiber for `manager`
258
+ # Deduct at least one fiber for sources
259
+ jobs_fiber_limit = total_fiber_limit - 2
260
+ return jobs_fiber_limit, total_fiber_limit
261
+ end
262
+
245
263
  def join_queues(prev_queue, new_queue)
246
264
  @nonblocking && Fiber.scheduler.run
247
265
  prev_queue.concat(new_queue)
@@ -249,7 +267,7 @@ module GraphQL
249
267
  end
250
268
 
251
269
  def spawn_job_fiber
252
- if @pending_jobs.any?
270
+ if !@pending_jobs.empty?
253
271
  spawn_fiber do
254
272
  while job = @pending_jobs.shift
255
273
  job.call
@@ -271,7 +289,10 @@ module GraphQL
271
289
 
272
290
  if pending_sources
273
291
  spawn_fiber do
274
- pending_sources.each(&:run_pending_keys)
292
+ pending_sources.each do |source|
293
+ Fiber[:__graphql_current_dataloader_source] = source
294
+ source.run_pending_keys
295
+ end
275
296
  end
276
297
  end
277
298
  end
@@ -8,22 +8,17 @@ module GraphQL
8
8
  @query = query
9
9
  @dataloader = query.context.dataloader
10
10
  @storage = Hash.new do |h, argument_owner|
11
- args_by_parent = if argument_owner.arguments_statically_coercible?
11
+ h[argument_owner] = if argument_owner.arguments_statically_coercible?
12
12
  shared_values_cache = {}
13
13
  Hash.new do |h2, ignored_parent_object|
14
14
  h2[ignored_parent_object] = shared_values_cache
15
- end
15
+ end.compare_by_identity
16
16
  else
17
17
  Hash.new do |h2, parent_object|
18
- args_by_node = {}
19
- args_by_node.compare_by_identity
20
- h2[parent_object] = args_by_node
21
- end
18
+ h2[parent_object] = {}.compare_by_identity
19
+ end.compare_by_identity
22
20
  end
23
- args_by_parent.compare_by_identity
24
- h[argument_owner] = args_by_parent
25
- end
26
- @storage.compare_by_identity
21
+ end.compare_by_identity
27
22
  end
28
23
 
29
24
  def fetch(ast_node, argument_owner, parent_object)
@@ -12,13 +12,17 @@ module GraphQL
12
12
  end
13
13
 
14
14
  def self.resolve_each_depth(lazies_at_depth, dataloader)
15
- depths = lazies_at_depth.keys
16
- depths.sort!
17
- next_depth = depths.first
18
- if next_depth
19
- lazies = lazies_at_depth[next_depth]
20
- lazies_at_depth.delete(next_depth)
21
- if lazies.any?
15
+ smallest_depth = nil
16
+ lazies_at_depth.each_key do |depth_key|
17
+ smallest_depth ||= depth_key
18
+ if depth_key < smallest_depth
19
+ smallest_depth = depth_key
20
+ end
21
+ end
22
+
23
+ if smallest_depth
24
+ lazies = lazies_at_depth.delete(smallest_depth)
25
+ if !lazies.empty?
22
26
  dataloader.append_job {
23
27
  lazies.each(&:value) # resolve these Lazy instances
24
28
  }
@@ -51,7 +55,7 @@ module GraphQL
51
55
  # these approaches.
52
56
  dataloader.run
53
57
  next_results = []
54
- while results.any?
58
+ while !results.empty?
55
59
  result_value = results.shift
56
60
  if result_value.is_a?(Runtime::GraphQLResultHash) || result_value.is_a?(Hash)
57
61
  results.concat(result_value.values)
@@ -77,7 +81,7 @@ module GraphQL
77
81
  end
78
82
  end
79
83
 
80
- if next_results.any?
84
+ if !next_results.empty?
81
85
  # Any pending data loader jobs may populate the
82
86
  # resutl arrays or result hashes accumulated in
83
87
  # `next_results``. Run those **to completion**