graphql 1.11.3 → 1.12.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.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/generators/graphql/core.rb +8 -0
- data/lib/generators/graphql/install_generator.rb +5 -5
- data/lib/generators/graphql/object_generator.rb +2 -0
- data/lib/generators/graphql/relay_generator.rb +63 -0
- data/lib/generators/graphql/templates/base_argument.erb +2 -0
- data/lib/generators/graphql/templates/base_connection.erb +8 -0
- data/lib/generators/graphql/templates/base_edge.erb +8 -0
- data/lib/generators/graphql/templates/base_enum.erb +2 -0
- data/lib/generators/graphql/templates/base_field.erb +2 -0
- data/lib/generators/graphql/templates/base_input_object.erb +2 -0
- data/lib/generators/graphql/templates/base_interface.erb +2 -0
- data/lib/generators/graphql/templates/base_mutation.erb +2 -0
- data/lib/generators/graphql/templates/base_object.erb +2 -0
- data/lib/generators/graphql/templates/base_scalar.erb +2 -0
- data/lib/generators/graphql/templates/base_union.erb +2 -0
- data/lib/generators/graphql/templates/enum.erb +2 -0
- data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
- data/lib/generators/graphql/templates/interface.erb +2 -0
- data/lib/generators/graphql/templates/loader.erb +2 -0
- data/lib/generators/graphql/templates/mutation.erb +2 -0
- data/lib/generators/graphql/templates/mutation_type.erb +2 -0
- data/lib/generators/graphql/templates/node_type.erb +9 -0
- data/lib/generators/graphql/templates/object.erb +3 -1
- data/lib/generators/graphql/templates/query_type.erb +3 -3
- data/lib/generators/graphql/templates/scalar.erb +2 -0
- data/lib/generators/graphql/templates/schema.erb +10 -35
- data/lib/generators/graphql/templates/union.erb +3 -1
- data/lib/graphql.rb +55 -4
- data/lib/graphql/analysis/analyze_query.rb +7 -0
- data/lib/graphql/analysis/ast.rb +11 -2
- data/lib/graphql/analysis/ast/visitor.rb +9 -1
- data/lib/graphql/argument.rb +3 -3
- data/lib/graphql/backtrace.rb +28 -19
- data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
- data/lib/graphql/backtrace/table.rb +22 -2
- data/lib/graphql/backtrace/tracer.rb +40 -8
- data/lib/graphql/backwards_compatibility.rb +1 -0
- data/lib/graphql/compatibility/execution_specification.rb +1 -0
- data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
- data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
- data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
- data/lib/graphql/dataloader.rb +197 -0
- data/lib/graphql/dataloader/null_dataloader.rb +21 -0
- data/lib/graphql/dataloader/request.rb +24 -0
- data/lib/graphql/dataloader/request_all.rb +22 -0
- data/lib/graphql/dataloader/source.rb +93 -0
- data/lib/graphql/define/assign_global_id_field.rb +2 -2
- data/lib/graphql/define/instance_definable.rb +32 -2
- data/lib/graphql/define/type_definer.rb +5 -5
- data/lib/graphql/deprecated_dsl.rb +5 -0
- data/lib/graphql/enum_type.rb +2 -0
- data/lib/graphql/execution/errors.rb +4 -0
- data/lib/graphql/execution/execute.rb +7 -0
- data/lib/graphql/execution/interpreter.rb +20 -6
- data/lib/graphql/execution/interpreter/arguments.rb +57 -5
- data/lib/graphql/execution/interpreter/arguments_cache.rb +8 -0
- data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
- data/lib/graphql/execution/interpreter/runtime.rb +251 -138
- data/lib/graphql/execution/multiplex.rb +20 -6
- data/lib/graphql/function.rb +4 -0
- data/lib/graphql/input_object_type.rb +2 -0
- data/lib/graphql/integer_decoding_error.rb +17 -0
- data/lib/graphql/interface_type.rb +3 -1
- data/lib/graphql/introspection.rb +96 -0
- data/lib/graphql/introspection/field_type.rb +7 -3
- data/lib/graphql/introspection/input_value_type.rb +6 -0
- data/lib/graphql/introspection/introspection_query.rb +6 -92
- data/lib/graphql/introspection/type_type.rb +7 -3
- data/lib/graphql/invalid_null_error.rb +1 -1
- data/lib/graphql/language/block_string.rb +24 -5
- data/lib/graphql/language/document_from_schema_definition.rb +50 -23
- data/lib/graphql/language/lexer.rb +7 -3
- data/lib/graphql/language/lexer.rl +7 -3
- data/lib/graphql/language/nodes.rb +1 -1
- data/lib/graphql/language/parser.rb +107 -103
- data/lib/graphql/language/parser.y +4 -0
- data/lib/graphql/language/sanitized_printer.rb +59 -26
- data/lib/graphql/name_validator.rb +6 -7
- data/lib/graphql/object_type.rb +2 -0
- data/lib/graphql/pagination/connection.rb +5 -1
- data/lib/graphql/pagination/connections.rb +15 -17
- data/lib/graphql/query.rb +8 -3
- data/lib/graphql/query/context.rb +38 -4
- data/lib/graphql/query/fingerprint.rb +2 -0
- data/lib/graphql/query/serial_execution.rb +1 -0
- data/lib/graphql/query/validation_pipeline.rb +4 -1
- data/lib/graphql/relay/array_connection.rb +2 -2
- data/lib/graphql/relay/base_connection.rb +7 -0
- data/lib/graphql/relay/connection_instrumentation.rb +4 -4
- data/lib/graphql/relay/connection_type.rb +1 -1
- data/lib/graphql/relay/mutation.rb +1 -0
- data/lib/graphql/relay/node.rb +3 -0
- data/lib/graphql/relay/range_add.rb +14 -5
- data/lib/graphql/relay/type_extensions.rb +2 -0
- data/lib/graphql/scalar_type.rb +2 -0
- data/lib/graphql/schema.rb +107 -38
- data/lib/graphql/schema/argument.rb +74 -5
- data/lib/graphql/schema/build_from_definition.rb +203 -86
- data/lib/graphql/schema/default_type_error.rb +2 -0
- data/lib/graphql/schema/directive.rb +76 -0
- data/lib/graphql/schema/directive/deprecated.rb +1 -1
- data/lib/graphql/schema/directive/flagged.rb +57 -0
- data/lib/graphql/schema/enum.rb +3 -0
- data/lib/graphql/schema/enum_value.rb +12 -6
- data/lib/graphql/schema/field.rb +59 -24
- data/lib/graphql/schema/field/connection_extension.rb +11 -9
- data/lib/graphql/schema/field/scope_extension.rb +1 -1
- data/lib/graphql/schema/input_object.rb +38 -25
- data/lib/graphql/schema/interface.rb +2 -1
- data/lib/graphql/schema/late_bound_type.rb +2 -2
- data/lib/graphql/schema/loader.rb +1 -0
- data/lib/graphql/schema/member.rb +4 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
- data/lib/graphql/schema/member/build_type.rb +17 -7
- data/lib/graphql/schema/member/has_arguments.rb +70 -51
- data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
- data/lib/graphql/schema/member/has_directives.rb +98 -0
- data/lib/graphql/schema/member/has_fields.rb +2 -2
- data/lib/graphql/schema/member/has_validators.rb +31 -0
- data/lib/graphql/schema/member/type_system_helpers.rb +3 -3
- data/lib/graphql/schema/object.rb +11 -0
- data/lib/graphql/schema/printer.rb +5 -4
- data/lib/graphql/schema/relay_classic_mutation.rb +4 -2
- data/lib/graphql/schema/resolver.rb +7 -0
- data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
- data/lib/graphql/schema/subscription.rb +20 -12
- data/lib/graphql/schema/timeout.rb +29 -15
- data/lib/graphql/schema/timeout_middleware.rb +2 -0
- data/lib/graphql/schema/unique_within_type.rb +1 -2
- data/lib/graphql/schema/validation.rb +10 -0
- data/lib/graphql/schema/validator.rb +163 -0
- data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
- data/lib/graphql/schema/validator/format_validator.rb +49 -0
- data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
- data/lib/graphql/schema/validator/length_validator.rb +57 -0
- data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
- data/lib/graphql/schema/validator/required_validator.rb +68 -0
- data/lib/graphql/schema/warden.rb +2 -3
- data/lib/graphql/static_validation.rb +1 -0
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +25 -17
- data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
- data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
- data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
- data/lib/graphql/static_validation/validator.rb +31 -7
- data/lib/graphql/subscriptions.rb +23 -16
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +21 -7
- data/lib/graphql/tracing.rb +2 -2
- data/lib/graphql/tracing/appoptics_tracing.rb +12 -2
- data/lib/graphql/tracing/platform_tracing.rb +4 -2
- data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
- data/lib/graphql/tracing/skylight_tracing.rb +1 -1
- data/lib/graphql/types/int.rb +9 -2
- data/lib/graphql/types/iso_8601_date_time.rb +2 -1
- data/lib/graphql/types/relay.rb +11 -3
- data/lib/graphql/types/relay/base_connection.rb +2 -90
- data/lib/graphql/types/relay/base_edge.rb +2 -34
- data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
- data/lib/graphql/types/relay/default_relay.rb +27 -0
- data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
- data/lib/graphql/types/relay/has_node_field.rb +41 -0
- data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
- data/lib/graphql/types/relay/node.rb +2 -4
- data/lib/graphql/types/relay/node_behaviors.rb +15 -0
- data/lib/graphql/types/relay/node_field.rb +1 -19
- data/lib/graphql/types/relay/nodes_field.rb +1 -19
- data/lib/graphql/types/relay/page_info.rb +2 -14
- data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
- data/lib/graphql/types/string.rb +7 -1
- data/lib/graphql/unauthorized_error.rb +1 -1
- data/lib/graphql/union_type.rb +2 -0
- data/lib/graphql/upgrader/member.rb +1 -0
- data/lib/graphql/upgrader/schema.rb +1 -0
- data/lib/graphql/version.rb +1 -1
- data/readme.md +1 -1
- metadata +38 -9
- data/lib/graphql/types/relay/base_field.rb +0 -22
- data/lib/graphql/types/relay/base_interface.rb +0 -29
- data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -3,6 +3,11 @@ module GraphQL
|
|
3
3
|
module Analysis
|
4
4
|
module_function
|
5
5
|
|
6
|
+
def use(schema_class)
|
7
|
+
schema = schema_class.is_a?(Class) ? schema_class : schema_class.target
|
8
|
+
schema.analysis_engine = self
|
9
|
+
end
|
10
|
+
|
6
11
|
# @return [void]
|
7
12
|
def analyze_multiplex(multiplex, analyzers)
|
8
13
|
multiplex.trace("analyze_multiplex", { multiplex: multiplex }) do
|
@@ -38,6 +43,8 @@ module GraphQL
|
|
38
43
|
# @param analyzers [Array<#call>] Objects that respond to `#call(memo, visit_type, irep_node)`
|
39
44
|
# @return [Array<Any>] Results from those analyzers
|
40
45
|
def analyze_query(query, analyzers, multiplex_states: [])
|
46
|
+
warn "Legacy analysis will be removed in GraphQL-Ruby 2.0, please upgrade to AST Analysis: https://graphql-ruby.org/queries/ast_analysis.html (schema: #{query.schema})"
|
47
|
+
|
41
48
|
query.trace("analyze_query", { query: query }) do
|
42
49
|
analyzers_to_run = analyzers.select do |analyzer|
|
43
50
|
if analyzer.respond_to?(:analyze?)
|
data/lib/graphql/analysis/ast.rb
CHANGED
@@ -13,7 +13,12 @@ module GraphQL
|
|
13
13
|
module_function
|
14
14
|
|
15
15
|
def use(schema_class)
|
16
|
-
schema_class.analysis_engine
|
16
|
+
if schema_class.analysis_engine == self
|
17
|
+
definition_line = caller(2, 1).first
|
18
|
+
warn("GraphQL::Analysis::AST is now the default; remove `use GraphQL::Analysis::AST` from the schema definition (#{definition_line})")
|
19
|
+
else
|
20
|
+
schema_class.analysis_engine = self
|
21
|
+
end
|
17
22
|
end
|
18
23
|
|
19
24
|
# Analyze a multiplex, and all queries within.
|
@@ -67,7 +72,11 @@ module GraphQL
|
|
67
72
|
|
68
73
|
visitor.visit
|
69
74
|
|
70
|
-
|
75
|
+
if visitor.rescued_errors.any?
|
76
|
+
visitor.rescued_errors
|
77
|
+
else
|
78
|
+
query_analyzers.map(&:result)
|
79
|
+
end
|
71
80
|
else
|
72
81
|
[]
|
73
82
|
end
|
@@ -19,6 +19,7 @@ module GraphQL
|
|
19
19
|
@field_definitions = []
|
20
20
|
@argument_definitions = []
|
21
21
|
@directive_definitions = []
|
22
|
+
@rescued_errors = []
|
22
23
|
@query = query
|
23
24
|
@schema = query.schema
|
24
25
|
@response_path = []
|
@@ -32,6 +33,9 @@ module GraphQL
|
|
32
33
|
# @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
|
33
34
|
attr_reader :object_types
|
34
35
|
|
36
|
+
# @return [Array<GraphQL::AnalysisError]
|
37
|
+
attr_reader :rescued_errors
|
38
|
+
|
35
39
|
def visit
|
36
40
|
return unless @document
|
37
41
|
super
|
@@ -239,7 +243,11 @@ module GraphQL
|
|
239
243
|
|
240
244
|
def call_analyzers(method, node, parent)
|
241
245
|
@analyzers.each do |analyzer|
|
242
|
-
|
246
|
+
begin
|
247
|
+
analyzer.public_send(method, node, parent, self)
|
248
|
+
rescue AnalysisError => err
|
249
|
+
@rescued_errors << err
|
250
|
+
end
|
243
251
|
end
|
244
252
|
end
|
245
253
|
|
data/lib/graphql/argument.rb
CHANGED
@@ -3,14 +3,14 @@ module GraphQL
|
|
3
3
|
# @api deprecated
|
4
4
|
class Argument
|
5
5
|
include GraphQL::Define::InstanceDefinable
|
6
|
-
accepts_definitions :name, :type, :description, :default_value, :as, :prepare, :method_access
|
6
|
+
accepts_definitions :name, :type, :description, :default_value, :as, :prepare, :method_access, :deprecation_reason
|
7
7
|
attr_reader :default_value
|
8
|
-
attr_accessor :description, :name, :as
|
8
|
+
attr_accessor :description, :name, :as, :deprecation_reason
|
9
9
|
attr_accessor :ast_node
|
10
10
|
attr_accessor :method_access
|
11
11
|
alias :graphql_name :name
|
12
12
|
|
13
|
-
ensure_defined(:name, :description, :default_value, :type=, :type, :as, :expose_as, :prepare, :method_access)
|
13
|
+
ensure_defined(:name, :description, :default_value, :type=, :type, :as, :expose_as, :prepare, :method_access, :deprecation_reason)
|
14
14
|
|
15
15
|
# @api private
|
16
16
|
module DefaultPrepare
|
data/lib/graphql/backtrace.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "graphql/backtrace/inspect_result"
|
3
|
+
require "graphql/backtrace/legacy_tracer"
|
3
4
|
require "graphql/backtrace/table"
|
4
5
|
require "graphql/backtrace/traced_error"
|
5
6
|
require "graphql/backtrace/tracer"
|
@@ -9,13 +10,12 @@ module GraphQL
|
|
9
10
|
# {TracedError} provides a GraphQL backtrace with arguments and return values.
|
10
11
|
# The underlying error is available as {TracedError#cause}.
|
11
12
|
#
|
12
|
-
# WARNING: {.enable} is not threadsafe because {GraphQL::Tracing.install} is not threadsafe.
|
13
|
-
#
|
14
13
|
# @example toggling backtrace annotation
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
14
|
+
# class MySchema < GraphQL::Schema
|
15
|
+
# if Rails.env.development? || Rails.env.test?
|
16
|
+
# use GraphQL::Backtrace
|
17
|
+
# end
|
18
|
+
# end
|
19
19
|
#
|
20
20
|
class Backtrace
|
21
21
|
include Enumerable
|
@@ -23,19 +23,13 @@ module GraphQL
|
|
23
23
|
|
24
24
|
def_delegators :to_a, :each, :[]
|
25
25
|
|
26
|
-
def self.
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
GraphQL::Tracing.uninstall(Backtrace::Tracer)
|
34
|
-
nil
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.use(schema_defn)
|
38
|
-
schema_defn.tracer(self::Tracer)
|
26
|
+
def self.use(schema_defn, legacy: false)
|
27
|
+
tracer = if legacy
|
28
|
+
self::LegacyTracer
|
29
|
+
else
|
30
|
+
self::Tracer
|
31
|
+
end
|
32
|
+
schema_defn.tracer(tracer)
|
39
33
|
end
|
40
34
|
|
41
35
|
def initialize(context, value: nil)
|
@@ -51,5 +45,20 @@ module GraphQL
|
|
51
45
|
def to_a
|
52
46
|
@table.to_backtrace
|
53
47
|
end
|
48
|
+
|
49
|
+
# Used for internal bookkeeping
|
50
|
+
# @api private
|
51
|
+
class Frame
|
52
|
+
attr_reader :path, :query, :ast_node, :object, :field, :arguments, :parent_frame
|
53
|
+
def initialize(path:, query:, ast_node:, object:, field:, arguments:, parent_frame:)
|
54
|
+
@path = path
|
55
|
+
@query = query
|
56
|
+
@ast_node = ast_node
|
57
|
+
@field = field
|
58
|
+
@object = object
|
59
|
+
@arguments = arguments
|
60
|
+
@parent_frame = parent_frame
|
61
|
+
end
|
62
|
+
end
|
54
63
|
end
|
55
64
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class Backtrace
|
4
|
+
module LegacyTracer
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# Implement the {GraphQL::Tracing} API.
|
8
|
+
def trace(key, metadata)
|
9
|
+
case key
|
10
|
+
when "lex", "parse"
|
11
|
+
# No context here, don't have a query yet
|
12
|
+
nil
|
13
|
+
when "execute_multiplex", "analyze_multiplex"
|
14
|
+
# No query context yet
|
15
|
+
nil
|
16
|
+
when "validate", "analyze_query", "execute_query", "execute_query_lazy"
|
17
|
+
query = metadata[:query] || metadata[:queries].first
|
18
|
+
push_data = query
|
19
|
+
multiplex = query.multiplex
|
20
|
+
when "execute_field", "execute_field_lazy"
|
21
|
+
# The interpreter passes `query:`, legacy passes `context:`
|
22
|
+
context = metadata[:context] || ((q = metadata[:query]) && q.context)
|
23
|
+
push_data = context
|
24
|
+
multiplex = context.query.multiplex
|
25
|
+
else
|
26
|
+
# Custom key, no backtrace data for this
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
if push_data
|
31
|
+
multiplex.context[:last_graphql_backtrace_context] = push_data
|
32
|
+
end
|
33
|
+
|
34
|
+
if key == "execute_multiplex"
|
35
|
+
begin
|
36
|
+
yield
|
37
|
+
rescue StandardError => err
|
38
|
+
# This is an unhandled error from execution,
|
39
|
+
# Re-raise it with a GraphQL trace.
|
40
|
+
potential_context = metadata[:multiplex].context[:last_graphql_backtrace_context]
|
41
|
+
|
42
|
+
if potential_context.is_a?(GraphQL::Query::Context) || potential_context.is_a?(GraphQL::Query::Context::FieldResolutionContext)
|
43
|
+
raise TracedError.new(err, potential_context)
|
44
|
+
else
|
45
|
+
raise
|
46
|
+
end
|
47
|
+
ensure
|
48
|
+
metadata[:multiplex].context.delete(:last_graphql_backtrace_context)
|
49
|
+
end
|
50
|
+
else
|
51
|
+
yield
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -79,6 +79,25 @@ module GraphQL
|
|
79
79
|
# @return [Array] 5 items for a backtrace table (not `key`)
|
80
80
|
def build_rows(context_entry, rows:, top: false)
|
81
81
|
case context_entry
|
82
|
+
when Backtrace::Frame
|
83
|
+
field_alias = context_entry.ast_node.respond_to?(:alias) && context_entry.ast_node.alias
|
84
|
+
value = if top && @override_value
|
85
|
+
@override_value
|
86
|
+
else
|
87
|
+
@context.query.context.namespace(:interpreter)[:runtime].value_at(context_entry.path)
|
88
|
+
end
|
89
|
+
rows << [
|
90
|
+
"#{context_entry.ast_node ? context_entry.ast_node.position.join(":") : ""}",
|
91
|
+
"#{context_entry.field.path}#{field_alias ? " as #{field_alias}" : ""}",
|
92
|
+
"#{context_entry.object.object.inspect}",
|
93
|
+
context_entry.arguments.to_h.inspect,
|
94
|
+
Backtrace::InspectResult.inspect_result(value),
|
95
|
+
]
|
96
|
+
if (parent = context_entry.parent_frame)
|
97
|
+
build_rows(parent, rows: rows)
|
98
|
+
else
|
99
|
+
rows
|
100
|
+
end
|
82
101
|
when GraphQL::Query::Context::FieldResolutionContext
|
83
102
|
ctx = context_entry
|
84
103
|
field_name = "#{ctx.irep_node.owner_type.name}.#{ctx.field.name}"
|
@@ -112,15 +131,16 @@ module GraphQL
|
|
112
131
|
if object.is_a?(GraphQL::Schema::Object)
|
113
132
|
object = object.object
|
114
133
|
end
|
134
|
+
value = context_entry.namespace(:interpreter)[:runtime].value_at([])
|
115
135
|
rows << [
|
116
136
|
"#{position}",
|
117
137
|
"#{op_type}#{op_name ? " #{op_name}" : ""}",
|
118
138
|
"#{object.inspect}",
|
119
139
|
query.variables.to_h.inspect,
|
120
|
-
Backtrace::InspectResult.inspect_result(
|
140
|
+
Backtrace::InspectResult.inspect_result(value),
|
121
141
|
]
|
122
142
|
else
|
123
|
-
raise "Unexpected get_rows subject #{context_entry.inspect}"
|
143
|
+
raise "Unexpected get_rows subject #{context_entry.class} (#{context_entry.inspect})"
|
124
144
|
end
|
125
145
|
end
|
126
146
|
end
|
@@ -1,45 +1,77 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module GraphQL
|
3
3
|
class Backtrace
|
4
|
+
# TODO this is not fiber-friendly
|
4
5
|
module Tracer
|
5
6
|
module_function
|
6
7
|
|
7
8
|
# Implement the {GraphQL::Tracing} API.
|
8
9
|
def trace(key, metadata)
|
9
|
-
|
10
|
+
case key
|
10
11
|
when "lex", "parse"
|
11
12
|
# No context here, don't have a query yet
|
12
13
|
nil
|
13
14
|
when "execute_multiplex", "analyze_multiplex"
|
14
|
-
|
15
|
+
# No query context yet
|
16
|
+
nil
|
15
17
|
when "validate", "analyze_query", "execute_query", "execute_query_lazy"
|
16
|
-
metadata[:query] || metadata[:queries]
|
18
|
+
query = metadata[:query] || metadata[:queries].first
|
19
|
+
push_key = []
|
20
|
+
push_data = query
|
21
|
+
multiplex = query.multiplex
|
17
22
|
when "execute_field", "execute_field_lazy"
|
18
|
-
metadata[:
|
23
|
+
query = metadata[:query] || raise(ArgumentError, "Add `legacy: true` to use GraphQL::Backtrace without the interpreter runtime.")
|
24
|
+
context = query.context
|
25
|
+
multiplex = query.multiplex
|
26
|
+
push_key = metadata[:path].reject { |i| i.is_a?(Integer) }
|
27
|
+
parent_frame = multiplex.context[:graphql_backtrace_contexts][push_key[0..-2]]
|
28
|
+
if parent_frame.nil?
|
29
|
+
p push_key
|
30
|
+
binding.pry
|
31
|
+
end
|
32
|
+
if parent_frame.is_a?(GraphQL::Query)
|
33
|
+
parent_frame = parent_frame.context
|
34
|
+
end
|
35
|
+
|
36
|
+
push_data = Frame.new(
|
37
|
+
query: query,
|
38
|
+
path: push_key,
|
39
|
+
ast_node: metadata[:ast_node],
|
40
|
+
field: metadata[:field],
|
41
|
+
object: metadata[:object],
|
42
|
+
arguments: metadata[:arguments],
|
43
|
+
parent_frame: parent_frame,
|
44
|
+
)
|
19
45
|
else
|
20
46
|
# Custom key, no backtrace data for this
|
21
47
|
nil
|
22
48
|
end
|
23
49
|
|
24
50
|
if push_data
|
25
|
-
|
51
|
+
multiplex.context[:graphql_backtrace_contexts][push_key] = push_data
|
52
|
+
multiplex.context[:last_graphql_backtrace_context] = push_data
|
26
53
|
end
|
27
54
|
|
28
55
|
if key == "execute_multiplex"
|
56
|
+
multiplex_context = metadata[:multiplex].context
|
57
|
+
multiplex_context[:graphql_backtrace_contexts] = {}
|
29
58
|
begin
|
30
59
|
yield
|
31
60
|
rescue StandardError => err
|
32
61
|
# This is an unhandled error from execution,
|
33
62
|
# Re-raise it with a GraphQL trace.
|
34
|
-
potential_context =
|
63
|
+
potential_context = multiplex_context[:last_graphql_backtrace_context]
|
35
64
|
|
36
|
-
if potential_context.is_a?(GraphQL::Query::Context) ||
|
65
|
+
if potential_context.is_a?(GraphQL::Query::Context) ||
|
66
|
+
potential_context.is_a?(GraphQL::Query::Context::FieldResolutionContext) ||
|
67
|
+
potential_context.is_a?(Backtrace::Frame)
|
37
68
|
raise TracedError.new(err, potential_context)
|
38
69
|
else
|
39
70
|
raise
|
40
71
|
end
|
41
72
|
ensure
|
42
|
-
|
73
|
+
multiplex_context.delete(:graphql_backtrace_contexts)
|
74
|
+
multiplex_context.delete(:last_graphql_backtrace_context)
|
43
75
|
end
|
44
76
|
else
|
45
77
|
yield
|
@@ -32,6 +32,7 @@ module GraphQL
|
|
32
32
|
# @param execution_strategy [<#new, #execute>] An execution strategy class
|
33
33
|
# @return [Class<Minitest::Test>] A test suite for this execution strategy
|
34
34
|
def self.build_suite(execution_strategy)
|
35
|
+
warn "#{self} will be removed from GraphQL-Ruby 2.0. There is no replacement, please open an issue on GitHub if you need support."
|
35
36
|
Class.new(Minitest::Test) do
|
36
37
|
class << self
|
37
38
|
attr_accessor :counter_schema, :specification_schema
|
@@ -7,6 +7,8 @@ module GraphQL
|
|
7
7
|
# @param execution_strategy [<#new, #execute>] An execution strategy class
|
8
8
|
# @return [Class<Minitest::Test>] A test suite for this execution strategy
|
9
9
|
def self.build_suite(execution_strategy)
|
10
|
+
warn "#{self} will be removed from GraphQL-Ruby 2.0. There is no replacement, please open an issue on GitHub if you need support."
|
11
|
+
|
10
12
|
Class.new(Minitest::Test) do
|
11
13
|
class << self
|
12
14
|
attr_accessor :lazy_schema
|
@@ -11,6 +11,8 @@ module GraphQL
|
|
11
11
|
# @yieldreturn [GraphQL::Language::Nodes::Document]
|
12
12
|
# @return [Class<Minitest::Test>] A test suite for this parse function
|
13
13
|
def self.build_suite(&block)
|
14
|
+
warn "#{self} will be removed from GraphQL-Ruby 2.0. There is no replacement, please open an issue on GitHub if you need support."
|
15
|
+
|
14
16
|
Class.new(Minitest::Test) do
|
15
17
|
include QueryAssertions
|
16
18
|
include ParseErrorSpecification
|
@@ -8,6 +8,8 @@ module GraphQL
|
|
8
8
|
# @yieldreturn [GraphQL::Language::Nodes::Document]
|
9
9
|
# @return [Class<Minitest::Test>] A test suite for this parse function
|
10
10
|
def self.build_suite(&block)
|
11
|
+
warn "#{self} will be removed from GraphQL-Ruby 2.0. There is no replacement, please open an issue on GitHub if you need support."
|
12
|
+
|
11
13
|
Class.new(Minitest::Test) do
|
12
14
|
@@parse_fn = block
|
13
15
|
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "graphql/dataloader/null_dataloader"
|
4
|
+
require "graphql/dataloader/request"
|
5
|
+
require "graphql/dataloader/request_all"
|
6
|
+
require "graphql/dataloader/source"
|
7
|
+
|
8
|
+
module GraphQL
|
9
|
+
# This plugin supports Fiber-based concurrency, along with {GraphQL::Dataloader::Source}.
|
10
|
+
#
|
11
|
+
# @example Installing Dataloader
|
12
|
+
#
|
13
|
+
# class MySchema < GraphQL::Schema
|
14
|
+
# use GraphQL::Dataloader
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @example Waiting for batch-loaded data in a GraphQL field
|
18
|
+
#
|
19
|
+
# field :team, Types::Team, null: true
|
20
|
+
#
|
21
|
+
# def team
|
22
|
+
# dataloader.with(Sources::Record, Team).load(object.team_id)
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
class Dataloader
|
26
|
+
def self.use(schema)
|
27
|
+
schema.dataloader_class = self
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(multiplex_context)
|
31
|
+
@context = multiplex_context
|
32
|
+
@source_cache = Hash.new { |h, source_class| h[source_class] = Hash.new { |h2, batch_parameters|
|
33
|
+
source = source_class.new(*batch_parameters)
|
34
|
+
source.setup(self)
|
35
|
+
h2[batch_parameters] = source
|
36
|
+
}
|
37
|
+
}
|
38
|
+
@waiting_fibers = []
|
39
|
+
@yielded_fibers = Set.new
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Hash] the {Multiplex} context
|
43
|
+
attr_reader :context
|
44
|
+
|
45
|
+
# @api private
|
46
|
+
attr_reader :yielded_fibers
|
47
|
+
|
48
|
+
# Add some work to this dataloader to be scheduled later.
|
49
|
+
# @param block Some work to enqueue
|
50
|
+
# @return [void]
|
51
|
+
def enqueue(&block)
|
52
|
+
@waiting_fibers << Fiber.new {
|
53
|
+
begin
|
54
|
+
yield
|
55
|
+
rescue StandardError => exception
|
56
|
+
exception
|
57
|
+
end
|
58
|
+
}
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# Tell the dataloader that this fiber is waiting for data.
|
63
|
+
#
|
64
|
+
# Dataloader will resume the fiber after the requested data has been loaded (by another Fiber).
|
65
|
+
#
|
66
|
+
# @return [void]
|
67
|
+
def yield
|
68
|
+
Fiber.yield
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [Boolean] Returns true if the current Fiber has yielded once via Dataloader
|
73
|
+
def yielded?
|
74
|
+
@yielded_fibers.include?(Fiber.current)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Run all Fibers until they're all done
|
78
|
+
#
|
79
|
+
# Each cycle works like this:
|
80
|
+
#
|
81
|
+
# - Run each pending execution fiber (`@waiting_fibers`),
|
82
|
+
# - Then run each pending Source, preparing more data for those fibers.
|
83
|
+
# - Run each pending Source _again_ (if one Source requested more data from another Source)
|
84
|
+
# - Continue until there are no pending sources
|
85
|
+
# - Repeat: run execution fibers again ...
|
86
|
+
#
|
87
|
+
# @return [void]
|
88
|
+
def run
|
89
|
+
# Start executing Fibers. This will run until all the Fibers are done.
|
90
|
+
already_run_fibers = []
|
91
|
+
while (current_fiber = @waiting_fibers.pop)
|
92
|
+
# Run each execution fiber, enqueuing it in `already_run_fibers`
|
93
|
+
# if it's still `.alive?`.
|
94
|
+
# Any spin-off continuations will be enqueued in `@waiting_fibers` (via {#enqueue})
|
95
|
+
resume_fiber_and_enqueue_continuation(current_fiber, already_run_fibers)
|
96
|
+
|
97
|
+
if @waiting_fibers.empty?
|
98
|
+
# Now, run all Sources which have become pending _before_ resuming GraphQL execution.
|
99
|
+
# Sources might queue up other Sources, which is fine -- those will also run before resuming execution.
|
100
|
+
#
|
101
|
+
# This is where an evented approach would be even better -- can we tell which
|
102
|
+
# fibers are ready to continue, and continue execution there?
|
103
|
+
#
|
104
|
+
source_fiber_stack = if (first_source_fiber = create_source_fiber)
|
105
|
+
[first_source_fiber]
|
106
|
+
else
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
|
110
|
+
if source_fiber_stack
|
111
|
+
while (outer_source_fiber = source_fiber_stack.pop)
|
112
|
+
resume_fiber_and_enqueue_continuation(outer_source_fiber, source_fiber_stack)
|
113
|
+
|
114
|
+
# If this source caused more sources to become pending, run those before running this one again:
|
115
|
+
next_source_fiber = create_source_fiber
|
116
|
+
if next_source_fiber
|
117
|
+
source_fiber_stack << next_source_fiber
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# We ran all the first round of execution fibers,
|
123
|
+
# and we ran all the pending sources.
|
124
|
+
# So pick up any paused execution fibers and repeat.
|
125
|
+
@waiting_fibers.concat(already_run_fibers)
|
126
|
+
already_run_fibers.clear
|
127
|
+
end
|
128
|
+
end
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
132
|
+
# Get a Source instance from this dataloader, for calling `.load(...)` or `.request(...)` on.
|
133
|
+
#
|
134
|
+
# @param source_class [Class<GraphQL::Dataloader::Source]
|
135
|
+
# @param batch_parameters [Array<Object>]
|
136
|
+
# @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`,
|
137
|
+
# and cached for the lifetime of this {Multiplex}.
|
138
|
+
def with(source_class, *batch_parameters)
|
139
|
+
@source_cache[source_class][batch_parameters]
|
140
|
+
end
|
141
|
+
|
142
|
+
# @api private
|
143
|
+
attr_accessor :current_runtime
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
# Check if this fiber is still alive.
|
148
|
+
# If it is, and it should continue, then enqueue a continuation.
|
149
|
+
# If it is, re-enqueue it in `fiber_queue`.
|
150
|
+
# Otherwise, clean it up from @yielded_fibers.
|
151
|
+
# @return [void]
|
152
|
+
def resume_fiber_and_enqueue_continuation(fiber, fiber_stack)
|
153
|
+
result = fiber.resume
|
154
|
+
if result.is_a?(StandardError)
|
155
|
+
raise result
|
156
|
+
end
|
157
|
+
|
158
|
+
# This fiber yielded; there's more to do here.
|
159
|
+
# (If `#alive?` is false, then the fiber concluded without yielding.)
|
160
|
+
if fiber.alive?
|
161
|
+
if !@yielded_fibers.include?(fiber)
|
162
|
+
# This fiber hasn't yielded yet, we should enqueue a continuation fiber
|
163
|
+
@yielded_fibers.add(fiber)
|
164
|
+
current_runtime.enqueue_selections_fiber
|
165
|
+
end
|
166
|
+
fiber_stack << fiber
|
167
|
+
else
|
168
|
+
# Keep this set clean so that fibers can be GC'ed during execution
|
169
|
+
@yielded_fibers.delete(fiber)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# If there are pending sources, return a fiber for running them.
|
174
|
+
# Otherwise, return `nil`.
|
175
|
+
#
|
176
|
+
# @return [Fiber, nil]
|
177
|
+
def create_source_fiber
|
178
|
+
pending_sources = nil
|
179
|
+
@source_cache.each_value do |source_by_batch_params|
|
180
|
+
source_by_batch_params.each_value do |source|
|
181
|
+
if source.pending?
|
182
|
+
pending_sources ||= []
|
183
|
+
pending_sources << source
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
if pending_sources
|
189
|
+
source_fiber = Fiber.new do
|
190
|
+
pending_sources.each(&:run_pending_keys)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
source_fiber
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|