graphql 2.3.7 → 2.4.7
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/generators/graphql/install_generator.rb +46 -0
- data/lib/generators/graphql/orm_mutations_base.rb +1 -1
- data/lib/generators/graphql/templates/base_resolver.erb +2 -0
- data/lib/generators/graphql/type_generator.rb +1 -1
- data/lib/graphql/analysis/field_usage.rb +1 -1
- data/lib/graphql/analysis/query_complexity.rb +3 -3
- data/lib/graphql/analysis/visitor.rb +8 -7
- data/lib/graphql/analysis.rb +4 -4
- data/lib/graphql/autoload.rb +38 -0
- data/lib/graphql/current.rb +52 -0
- data/lib/graphql/dataloader/async_dataloader.rb +7 -6
- data/lib/graphql/dataloader/source.rb +7 -4
- data/lib/graphql/dataloader.rb +40 -19
- data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
- data/lib/graphql/execution/interpreter/resolve.rb +13 -9
- data/lib/graphql/execution/interpreter/runtime.rb +35 -31
- data/lib/graphql/execution/interpreter.rb +6 -4
- data/lib/graphql/execution/lookahead.rb +18 -11
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/field_type.rb +1 -1
- data/lib/graphql/introspection/schema_type.rb +6 -11
- data/lib/graphql/introspection/type_type.rb +5 -5
- data/lib/graphql/invalid_null_error.rb +1 -1
- data/lib/graphql/language/cache.rb +13 -0
- data/lib/graphql/language/comment.rb +18 -0
- data/lib/graphql/language/document_from_schema_definition.rb +62 -34
- data/lib/graphql/language/lexer.rb +18 -15
- data/lib/graphql/language/nodes.rb +24 -16
- data/lib/graphql/language/parser.rb +14 -1
- data/lib/graphql/language/printer.rb +31 -15
- data/lib/graphql/language/sanitized_printer.rb +1 -1
- data/lib/graphql/language.rb +6 -6
- data/lib/graphql/pagination/connection.rb +1 -1
- data/lib/graphql/query/context/scoped_context.rb +1 -1
- data/lib/graphql/query/context.rb +13 -6
- data/lib/graphql/query/null_context.rb +3 -5
- data/lib/graphql/query/variable_validation_error.rb +1 -1
- data/lib/graphql/query.rb +72 -18
- data/lib/graphql/railtie.rb +7 -0
- data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
- data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
- data/lib/graphql/rubocop.rb +2 -0
- data/lib/graphql/schema/addition.rb +2 -1
- data/lib/graphql/schema/always_visible.rb +6 -2
- data/lib/graphql/schema/argument.rb +14 -1
- data/lib/graphql/schema/build_from_definition.rb +9 -1
- data/lib/graphql/schema/directive/flagged.rb +2 -2
- data/lib/graphql/schema/directive.rb +1 -1
- data/lib/graphql/schema/enum.rb +71 -23
- data/lib/graphql/schema/enum_value.rb +10 -2
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field/scope_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +102 -47
- data/lib/graphql/schema/field_extension.rb +1 -1
- data/lib/graphql/schema/has_single_input_argument.rb +5 -2
- data/lib/graphql/schema/input_object.rb +90 -39
- data/lib/graphql/schema/interface.rb +22 -5
- data/lib/graphql/schema/introspection_system.rb +5 -16
- data/lib/graphql/schema/loader.rb +1 -1
- data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
- data/lib/graphql/schema/member/has_arguments.rb +36 -23
- data/lib/graphql/schema/member/has_directives.rb +3 -3
- data/lib/graphql/schema/member/has_fields.rb +26 -6
- data/lib/graphql/schema/member/has_interfaces.rb +4 -4
- data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
- data/lib/graphql/schema/member/has_validators.rb +1 -1
- data/lib/graphql/schema/object.rb +8 -0
- data/lib/graphql/schema/printer.rb +1 -0
- data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
- data/lib/graphql/schema/resolver.rb +12 -14
- data/lib/graphql/schema/subscription.rb +2 -2
- data/lib/graphql/schema/type_expression.rb +2 -2
- data/lib/graphql/schema/union.rb +1 -1
- data/lib/graphql/schema/validator/all_validator.rb +62 -0
- data/lib/graphql/schema/validator/required_validator.rb +28 -4
- data/lib/graphql/schema/validator.rb +3 -1
- data/lib/graphql/schema/visibility/migration.rb +188 -0
- data/lib/graphql/schema/visibility/profile.rb +359 -0
- data/lib/graphql/schema/visibility/visit.rb +190 -0
- data/lib/graphql/schema/visibility.rb +294 -0
- data/lib/graphql/schema/warden.rb +179 -16
- data/lib/graphql/schema.rb +348 -94
- data/lib/graphql/static_validation/base_visitor.rb +6 -5
- data/lib/graphql/static_validation/literal_validator.rb +4 -4
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +2 -2
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +8 -7
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
- data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
- data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
- data/lib/graphql/static_validation/validation_context.rb +18 -2
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +3 -2
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
- data/lib/graphql/subscriptions/event.rb +1 -1
- data/lib/graphql/subscriptions/serialize.rb +2 -0
- data/lib/graphql/subscriptions.rb +6 -4
- data/lib/graphql/testing/helpers.rb +10 -6
- data/lib/graphql/tracing/notifications_trace.rb +2 -2
- data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
- data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
- data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
- data/lib/graphql/types.rb +18 -11
- data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +53 -45
- metadata +31 -8
- data/lib/graphql/language/token.rb +0 -34
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 69f8b4084adf0530a973073bcacf76e536975b385e4927525504b648762e496e
|
4
|
+
data.tar.gz: ef8e532575d445e0e96c1e5cccb2f6518d4a8759127ba8f27fd4d2aca4ef877a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9522e680f06a1810e019c64656fba5dd7d7d95e098f136e2f656837c928829eab7a1be5656c7c4c9fd68bf8f262ca9b7ed415d87c12997dc613c9f600e8994b
|
7
|
+
data.tar.gz: 99a5a17272d80d555c98f8f091799e5b84656bfd42310e79d64b65140c7b2786e9f23f4759dfefb5b09c8ffc913349bfd0db3bcff5f6475087b350122aadd066
|
@@ -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
|
21
|
+
class_option :namespaced_types,
|
22
22
|
type: :boolean,
|
23
23
|
required: false,
|
24
24
|
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.
|
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 && @
|
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
|
-
|
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
|
-
|
176
|
+
@types.argument(directive_defn, node.name)
|
176
177
|
elsif (field_defn = @field_definitions.last)
|
177
|
-
|
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
|
-
@
|
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.
|
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
|
-
@
|
272
|
+
@types.type(node.type.name)
|
272
273
|
else
|
273
274
|
@object_types.last
|
274
275
|
end
|
data/lib/graphql/analysis.rb
CHANGED
@@ -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.
|
58
|
+
if !analyzers_to_run.empty?
|
59
59
|
|
60
60
|
analyzers_to_run.select!(&:visit?)
|
61
|
-
if analyzers_to_run.
|
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.
|
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,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
# @see GraphQL::Railtie for automatic Rails integration
|
5
|
+
module Autoload
|
6
|
+
# Register a constant named `const_name` to be loaded from `path`.
|
7
|
+
# This is like `Kernel#autoload` but it tracks the constants so they can be eager-loaded with {#eager_load!}
|
8
|
+
# @param const_name [Symbol]
|
9
|
+
# @param path [String]
|
10
|
+
# @return [void]
|
11
|
+
def autoload(const_name, path)
|
12
|
+
@_eagerloaded_constants ||= []
|
13
|
+
@_eagerloaded_constants << const_name
|
14
|
+
|
15
|
+
super const_name, path
|
16
|
+
end
|
17
|
+
|
18
|
+
# Call this to load this constant's `autoload` dependents and continue calling recursively
|
19
|
+
# @return [void]
|
20
|
+
def eager_load!
|
21
|
+
@_eager_loading = true
|
22
|
+
if @_eagerloaded_constants
|
23
|
+
@_eagerloaded_constants.each { |const_name| const_get(const_name) }
|
24
|
+
@_eagerloaded_constants = nil
|
25
|
+
end
|
26
|
+
nil
|
27
|
+
ensure
|
28
|
+
@_eager_loading = false
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# @return [Boolean] `true` if GraphQL-Ruby is currently eager-loading its constants
|
34
|
+
def eager_loading?
|
35
|
+
@_eager_loading ||= false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
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 =
|
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.
|
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.
|
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
|
-
|
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.
|
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
|
-
|
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
|
data/lib/graphql/dataloader.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
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
|
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
|
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
|
-
|
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
|
-
|
19
|
-
|
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
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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.
|
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.
|
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**
|