graphql 2.3.7 → 2.4.0
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.
- 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 +7 -6
- data/lib/graphql/analysis.rb +1 -1
- data/lib/graphql/current.rb +52 -0
- data/lib/graphql/dataloader/async_dataloader.rb +3 -2
- data/lib/graphql/dataloader/source.rb +6 -3
- data/lib/graphql/dataloader.rb +35 -11
- data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
- data/lib/graphql/execution/interpreter/resolve.rb +10 -6
- data/lib/graphql/execution/interpreter/runtime.rb +29 -25
- data/lib/graphql/execution/interpreter.rb +2 -0
- data/lib/graphql/execution/lookahead.rb +10 -10
- 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/comment.rb +18 -0
- data/lib/graphql/language/document_from_schema_definition.rb +55 -28
- data/lib/graphql/language/lexer.rb +15 -15
- data/lib/graphql/language/nodes.rb +24 -16
- data/lib/graphql/language/parser.rb +14 -1
- data/lib/graphql/language/printer.rb +23 -7
- data/lib/graphql/language/sanitized_printer.rb +1 -1
- data/lib/graphql/language.rb +6 -6
- data/lib/graphql/query/context.rb +7 -1
- data/lib/graphql/query/null_context.rb +2 -2
- data/lib/graphql/query.rb +60 -8
- 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 +1 -0
- 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 +1 -1
- data/lib/graphql/schema/enum.rb +54 -20
- data/lib/graphql/schema/enum_value.rb +9 -1
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +92 -37
- data/lib/graphql/schema/has_single_input_argument.rb +2 -1
- data/lib/graphql/schema/input_object.rb +27 -13
- data/lib/graphql/schema/interface.rb +21 -4
- data/lib/graphql/schema/introspection_system.rb +5 -16
- data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
- data/lib/graphql/schema/member/has_arguments.rb +14 -9
- data/lib/graphql/schema/member/has_fields.rb +8 -6
- data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
- data/lib/graphql/schema/printer.rb +1 -0
- data/lib/graphql/schema/resolver.rb +7 -9
- data/lib/graphql/schema/type_expression.rb +2 -2
- 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 +2 -0
- data/lib/graphql/schema/visibility/migration.rb +186 -0
- data/lib/graphql/schema/visibility/profile.rb +523 -0
- data/lib/graphql/schema/visibility.rb +75 -0
- data/lib/graphql/schema/warden.rb +156 -10
- data/lib/graphql/schema.rb +283 -85
- 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/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 +3 -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/query_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -3
- 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/variable_default_values_are_correctly_typed.rb +18 -27
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
- data/lib/graphql/static_validation/validation_context.rb +17 -2
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
- data/lib/graphql/subscriptions/event.rb +1 -1
- data/lib/graphql/subscriptions.rb +5 -3
- data/lib/graphql/testing/helpers.rb +9 -5
- data/lib/graphql/tracing/notifications_trace.rb +2 -2
- data/lib/graphql/types/relay/connection_behaviors.rb +10 -0
- data/lib/graphql/types/relay/edge_behaviors.rb +10 -0
- data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
- data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +3 -0
- metadata +29 -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: 26e43b0bc48317698ed17f8a11498c19a7a4a0df9d33fdc9785edc47ae1147b6
|
|
4
|
+
data.tar.gz: 74402e930ebe03a451bc2bdb82285642aeeba7222ab491dd9329400fc852eb41
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f1d97c4397ca8410f6b62c3ea2de9a0a4b18ca610adcb92de9676c5377b00a071cbff4fbd0396760eea9ab359e4b0b8cdfff1630c9fa83de51cfd31b0b96307f
|
|
7
|
+
data.tar.gz: d9250a9ad1d57f40e7b0151484f77a66e11a361f8b0f50938f42219eb8322a9b4dc59c59102f4b49f62a28e90f03b8f9e0cf5b2799b5fd5080dbcf443cebac15
|
|
@@ -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
|
|
@@ -268,7 +269,7 @@ module GraphQL
|
|
|
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
|
@@ -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,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
|
+
Thread.current[:__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
|
|
@@ -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 = []
|
|
@@ -23,7 +24,7 @@ module GraphQL
|
|
|
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
|
|
@@ -37,7 +38,7 @@ module GraphQL
|
|
|
37
38
|
Sync do |root_task|
|
|
38
39
|
set_fiber_variables(fiber_vars)
|
|
39
40
|
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))
|
|
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
|
|
@@ -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
|
|
@@ -178,6 +187,7 @@ module GraphQL
|
|
|
178
187
|
end
|
|
179
188
|
|
|
180
189
|
def run
|
|
190
|
+
jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
|
|
181
191
|
job_fibers = []
|
|
182
192
|
next_job_fibers = []
|
|
183
193
|
source_fibers = []
|
|
@@ -187,7 +197,7 @@ module GraphQL
|
|
|
187
197
|
while first_pass || job_fibers.any?
|
|
188
198
|
first_pass = false
|
|
189
199
|
|
|
190
|
-
while (f = (job_fibers.shift || spawn_job_fiber))
|
|
200
|
+
while (f = (job_fibers.shift || (((next_job_fibers.size + job_fibers.size) < jobs_fiber_limit) && spawn_job_fiber)))
|
|
191
201
|
if f.alive?
|
|
192
202
|
finished = run_fiber(f)
|
|
193
203
|
if !finished
|
|
@@ -197,8 +207,8 @@ module GraphQL
|
|
|
197
207
|
end
|
|
198
208
|
join_queues(job_fibers, next_job_fibers)
|
|
199
209
|
|
|
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)
|
|
210
|
+
while (source_fibers.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
|
|
211
|
+
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
212
|
if f.alive?
|
|
203
213
|
finished = run_fiber(f)
|
|
204
214
|
if !finished
|
|
@@ -242,6 +252,17 @@ module GraphQL
|
|
|
242
252
|
|
|
243
253
|
private
|
|
244
254
|
|
|
255
|
+
def calculate_fiber_limit
|
|
256
|
+
total_fiber_limit = @fiber_limit || Float::INFINITY
|
|
257
|
+
if total_fiber_limit < 4
|
|
258
|
+
raise ArgumentError, "Dataloader fiber limit is too low (#{total_fiber_limit}), it must be at least 4"
|
|
259
|
+
end
|
|
260
|
+
total_fiber_limit -= 1 # deduct one fiber for `manager`
|
|
261
|
+
# Deduct at least one fiber for sources
|
|
262
|
+
jobs_fiber_limit = total_fiber_limit - 2
|
|
263
|
+
return jobs_fiber_limit, total_fiber_limit
|
|
264
|
+
end
|
|
265
|
+
|
|
245
266
|
def join_queues(prev_queue, new_queue)
|
|
246
267
|
@nonblocking && Fiber.scheduler.run
|
|
247
268
|
prev_queue.concat(new_queue)
|
|
@@ -271,7 +292,10 @@ module GraphQL
|
|
|
271
292
|
|
|
272
293
|
if pending_sources
|
|
273
294
|
spawn_fiber do
|
|
274
|
-
pending_sources.each
|
|
295
|
+
pending_sources.each do |source|
|
|
296
|
+
Fiber[:__graphql_current_dataloader_source] = source
|
|
297
|
+
source.run_pending_keys
|
|
298
|
+
end
|
|
275
299
|
end
|
|
276
300
|
end
|
|
277
301
|
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,12 +12,16 @@ module GraphQL
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def self.resolve_each_depth(lazies_at_depth, dataloader)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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)
|
|
21
25
|
if lazies.any?
|
|
22
26
|
dataloader.append_job {
|
|
23
27
|
lazies.each(&:value) # resolve these Lazy instances
|
|
@@ -53,8 +53,7 @@ module GraphQL
|
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
# { Class => Boolean }
|
|
56
|
-
@lazy_cache = {}
|
|
57
|
-
@lazy_cache.compare_by_identity
|
|
56
|
+
@lazy_cache = {}.compare_by_identity
|
|
58
57
|
end
|
|
59
58
|
|
|
60
59
|
def final_result
|
|
@@ -160,9 +159,9 @@ module GraphQL
|
|
|
160
159
|
case node
|
|
161
160
|
when GraphQL::Language::Nodes::InlineFragment
|
|
162
161
|
if node.type
|
|
163
|
-
type_defn =
|
|
162
|
+
type_defn = query.types.type(node.type.name)
|
|
164
163
|
|
|
165
|
-
if query.
|
|
164
|
+
if query.types.possible_types(type_defn).include?(owner_type)
|
|
166
165
|
result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
|
|
167
166
|
if !result.equal?(next_selections)
|
|
168
167
|
selections_to_run = result
|
|
@@ -177,8 +176,8 @@ module GraphQL
|
|
|
177
176
|
end
|
|
178
177
|
when GraphQL::Language::Nodes::FragmentSpread
|
|
179
178
|
fragment_def = query.fragments[node.name]
|
|
180
|
-
type_defn = query.
|
|
181
|
-
if query.
|
|
179
|
+
type_defn = query.types.type(fragment_def.type.name)
|
|
180
|
+
if query.types.possible_types(type_defn).include?(owner_type)
|
|
182
181
|
result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
|
|
183
182
|
if !result.equal?(next_selections)
|
|
184
183
|
selections_to_run = result
|
|
@@ -208,22 +207,32 @@ module GraphQL
|
|
|
208
207
|
finished_jobs = 0
|
|
209
208
|
enqueued_jobs = gathered_selections.size
|
|
210
209
|
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
|
211
|
-
|
|
212
|
-
evaluate_selection(
|
|
213
|
-
result_name, field_ast_nodes_or_ast_node, selections_result
|
|
214
|
-
)
|
|
215
|
-
finished_jobs += 1
|
|
216
|
-
if target_result && finished_jobs == enqueued_jobs
|
|
217
|
-
selections_result.merge_into(target_result)
|
|
218
|
-
end
|
|
219
|
-
}
|
|
210
|
+
|
|
220
211
|
# Field resolution may pause the fiber,
|
|
221
212
|
# so it wouldn't get to the `Resolve` call that happens below.
|
|
222
213
|
# So instead trigger a run from this outer context.
|
|
223
214
|
if selections_result.graphql_is_eager
|
|
224
215
|
@dataloader.clear_cache
|
|
225
|
-
@dataloader.
|
|
226
|
-
|
|
216
|
+
@dataloader.run_isolated {
|
|
217
|
+
evaluate_selection(
|
|
218
|
+
result_name, field_ast_nodes_or_ast_node, selections_result
|
|
219
|
+
)
|
|
220
|
+
finished_jobs += 1
|
|
221
|
+
if target_result && finished_jobs == enqueued_jobs
|
|
222
|
+
selections_result.merge_into(target_result)
|
|
223
|
+
end
|
|
224
|
+
@dataloader.clear_cache
|
|
225
|
+
}
|
|
226
|
+
else
|
|
227
|
+
@dataloader.append_job {
|
|
228
|
+
evaluate_selection(
|
|
229
|
+
result_name, field_ast_nodes_or_ast_node, selections_result
|
|
230
|
+
)
|
|
231
|
+
finished_jobs += 1
|
|
232
|
+
if target_result && finished_jobs == enqueued_jobs
|
|
233
|
+
selections_result.merge_into(target_result)
|
|
234
|
+
end
|
|
235
|
+
}
|
|
227
236
|
end
|
|
228
237
|
end
|
|
229
238
|
selections_result
|
|
@@ -245,7 +254,7 @@ module GraphQL
|
|
|
245
254
|
end
|
|
246
255
|
field_name = ast_node.name
|
|
247
256
|
owner_type = selections_result.graphql_result_type
|
|
248
|
-
field_defn = query.
|
|
257
|
+
field_defn = query.types.field(owner_type, field_name)
|
|
249
258
|
|
|
250
259
|
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
|
251
260
|
runtime_state = get_current_runtime_state
|
|
@@ -579,7 +588,7 @@ module GraphQL
|
|
|
579
588
|
resolved_value = value
|
|
580
589
|
end
|
|
581
590
|
|
|
582
|
-
possible_types = query.possible_types(current_type)
|
|
591
|
+
possible_types = query.types.possible_types(current_type)
|
|
583
592
|
if !possible_types.include?(resolved_type)
|
|
584
593
|
parent_type = field.owner_type
|
|
585
594
|
err_class = current_type::UnresolvedTypeError
|
|
@@ -727,12 +736,7 @@ module GraphQL
|
|
|
727
736
|
end
|
|
728
737
|
|
|
729
738
|
def get_current_runtime_state
|
|
730
|
-
current_state = Thread.current[:__graphql_runtime_info] ||=
|
|
731
|
-
per_query_state = {}
|
|
732
|
-
per_query_state.compare_by_identity
|
|
733
|
-
per_query_state
|
|
734
|
-
end
|
|
735
|
-
|
|
739
|
+
current_state = Thread.current[:__graphql_runtime_info] ||= {}.compare_by_identity
|
|
736
740
|
current_state[@query] ||= CurrentState.new
|
|
737
741
|
end
|
|
738
742
|
|
|
@@ -34,6 +34,7 @@ module GraphQL
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
|
|
37
|
+
Fiber[:__graphql_current_multiplex] = multiplex
|
|
37
38
|
multiplex.current_trace.execute_multiplex(multiplex: multiplex) do
|
|
38
39
|
schema = multiplex.schema
|
|
39
40
|
queries = multiplex.queries
|
|
@@ -136,6 +137,7 @@ module GraphQL
|
|
|
136
137
|
queries.map { |q| q.result_values ||= {} }
|
|
137
138
|
raise
|
|
138
139
|
ensure
|
|
140
|
+
Fiber[:__graphql_current_multiplex] = nil
|
|
139
141
|
queries.map { |query|
|
|
140
142
|
runtime = query.context.namespace(:interpreter_runtime)[:runtime]
|
|
141
143
|
if runtime
|
|
@@ -108,16 +108,16 @@ module GraphQL
|
|
|
108
108
|
def selection(field_name, selected_type: @selected_type, arguments: nil)
|
|
109
109
|
next_field_defn = case field_name
|
|
110
110
|
when String
|
|
111
|
-
@query.
|
|
111
|
+
@query.types.field(selected_type, field_name)
|
|
112
112
|
when Symbol
|
|
113
113
|
# Try to avoid the `.to_s` below, if possible
|
|
114
114
|
all_fields = if selected_type.kind.fields?
|
|
115
|
-
@query.
|
|
115
|
+
@query.types.fields(selected_type)
|
|
116
116
|
else
|
|
117
117
|
# Handle unions by checking possible
|
|
118
|
-
@query.
|
|
118
|
+
@query.types
|
|
119
119
|
.possible_types(selected_type)
|
|
120
|
-
.map { |t| @query.
|
|
120
|
+
.map { |t| @query.types.fields(t) }
|
|
121
121
|
.tap(&:flatten!)
|
|
122
122
|
end
|
|
123
123
|
|
|
@@ -128,7 +128,7 @@ module GraphQL
|
|
|
128
128
|
# Symbol#name is only present on 3.0+
|
|
129
129
|
sym_s = field_name.respond_to?(:name) ? field_name.name : field_name.to_s
|
|
130
130
|
guessed_name = Schema::Member::BuildType.camelize(sym_s)
|
|
131
|
-
@query.
|
|
131
|
+
@query.types.field(selected_type, guessed_name)
|
|
132
132
|
end
|
|
133
133
|
end
|
|
134
134
|
lookahead_for_selection(next_field_defn, selected_type, arguments)
|
|
@@ -144,7 +144,7 @@ module GraphQL
|
|
|
144
144
|
alias_node = lookup_alias_node(ast_nodes, alias_name)
|
|
145
145
|
return NULL_LOOKAHEAD unless alias_node
|
|
146
146
|
|
|
147
|
-
next_field_defn = @query.
|
|
147
|
+
next_field_defn = @query.types.field(selected_type, alias_node.name)
|
|
148
148
|
|
|
149
149
|
alias_arguments = @query.arguments_for(alias_node, next_field_defn)
|
|
150
150
|
if alias_arguments.is_a?(::GraphQL::Execution::Interpreter::Arguments)
|
|
@@ -183,7 +183,7 @@ module GraphQL
|
|
|
183
183
|
|
|
184
184
|
subselections_by_type.each do |type, ast_nodes_by_response_key|
|
|
185
185
|
ast_nodes_by_response_key.each do |response_key, ast_nodes|
|
|
186
|
-
field_defn = @query.
|
|
186
|
+
field_defn = @query.types.field(type, ast_nodes.first.name)
|
|
187
187
|
lookahead = Lookahead.new(query: @query, ast_nodes: ast_nodes, field: field_defn, owner_type: type)
|
|
188
188
|
subselections.push(lookahead)
|
|
189
189
|
end
|
|
@@ -266,7 +266,7 @@ module GraphQL
|
|
|
266
266
|
elsif arguments.nil? || arguments.empty?
|
|
267
267
|
selections_on_type[response_key] = [ast_selection]
|
|
268
268
|
else
|
|
269
|
-
field_defn = @query.
|
|
269
|
+
field_defn = @query.types.field(selected_type, ast_selection.name)
|
|
270
270
|
if arguments_match?(arguments, field_defn, ast_selection)
|
|
271
271
|
selections_on_type[response_key] = [ast_selection]
|
|
272
272
|
end
|
|
@@ -276,14 +276,14 @@ module GraphQL
|
|
|
276
276
|
subselections_on_type = selections_on_type
|
|
277
277
|
if (t = ast_selection.type)
|
|
278
278
|
# Assuming this is valid, that `t` will be found.
|
|
279
|
-
on_type = @query.
|
|
279
|
+
on_type = @query.types.type(t.name)
|
|
280
280
|
subselections_on_type = subselections_by_type[on_type] ||= {}
|
|
281
281
|
end
|
|
282
282
|
find_selections(subselections_by_type, subselections_on_type, on_type, ast_selection.selections, arguments)
|
|
283
283
|
when GraphQL::Language::Nodes::FragmentSpread
|
|
284
284
|
frag_defn = lookup_fragment(ast_selection)
|
|
285
285
|
# Again, assuming a valid AST
|
|
286
|
-
on_type = @query.
|
|
286
|
+
on_type = @query.types.type(frag_defn.type.name)
|
|
287
287
|
subselections_on_type = subselections_by_type[on_type] ||= {}
|
|
288
288
|
find_selections(subselections_by_type, subselections_on_type, on_type, frag_defn.selections, arguments)
|
|
289
289
|
else
|