graphql 2.0.13 → 2.3.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (228) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  3. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  4. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  5. data/lib/generators/graphql/install_generator.rb +3 -0
  6. data/lib/generators/graphql/mutation_delete_generator.rb +1 -1
  7. data/lib/generators/graphql/mutation_update_generator.rb +1 -1
  8. data/lib/generators/graphql/relay.rb +18 -1
  9. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  10. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  11. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  12. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  13. data/lib/generators/graphql/templates/base_field.erb +2 -0
  14. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  15. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  16. data/lib/generators/graphql/templates/base_object.erb +2 -0
  17. data/lib/generators/graphql/templates/base_resolver.erb +6 -0
  18. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  19. data/lib/generators/graphql/templates/base_union.erb +2 -0
  20. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  21. data/lib/generators/graphql/templates/loader.erb +2 -0
  22. data/lib/generators/graphql/templates/mutation.erb +2 -0
  23. data/lib/generators/graphql/templates/node_type.erb +2 -0
  24. data/lib/generators/graphql/templates/query_type.erb +2 -0
  25. data/lib/generators/graphql/templates/schema.erb +8 -0
  26. data/lib/graphql/analysis/analyzer.rb +89 -0
  27. data/lib/graphql/analysis/field_usage.rb +82 -0
  28. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  29. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  30. data/lib/graphql/analysis/query_complexity.rb +183 -0
  31. data/lib/graphql/analysis/query_depth.rb +58 -0
  32. data/lib/graphql/analysis/visitor.rb +283 -0
  33. data/lib/graphql/analysis.rb +92 -1
  34. data/lib/graphql/backtrace/inspect_result.rb +0 -12
  35. data/lib/graphql/backtrace/table.rb +2 -2
  36. data/lib/graphql/backtrace/trace.rb +93 -0
  37. data/lib/graphql/backtrace/tracer.rb +1 -1
  38. data/lib/graphql/backtrace.rb +2 -1
  39. data/lib/graphql/coercion_error.rb +1 -9
  40. data/lib/graphql/dataloader/async_dataloader.rb +88 -0
  41. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  42. data/lib/graphql/dataloader/request.rb +5 -0
  43. data/lib/graphql/dataloader/source.rb +89 -45
  44. data/lib/graphql/dataloader.rb +115 -142
  45. data/lib/graphql/duration_encoding_error.rb +16 -0
  46. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  47. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  48. data/lib/graphql/execution/interpreter/arguments_cache.rb +33 -33
  49. data/lib/graphql/execution/interpreter/resolve.rb +19 -0
  50. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +175 -0
  51. data/lib/graphql/execution/interpreter/runtime.rb +331 -455
  52. data/lib/graphql/execution/interpreter.rb +125 -61
  53. data/lib/graphql/execution/lazy.rb +6 -12
  54. data/lib/graphql/execution/lookahead.rb +124 -46
  55. data/lib/graphql/execution/multiplex.rb +3 -117
  56. data/lib/graphql/execution.rb +0 -1
  57. data/lib/graphql/introspection/directive_type.rb +3 -3
  58. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  59. data/lib/graphql/introspection/entry_points.rb +11 -5
  60. data/lib/graphql/introspection/field_type.rb +2 -2
  61. data/lib/graphql/introspection/schema_type.rb +10 -13
  62. data/lib/graphql/introspection/type_type.rb +17 -10
  63. data/lib/graphql/introspection.rb +3 -2
  64. data/lib/graphql/language/block_string.rb +34 -18
  65. data/lib/graphql/language/definition_slice.rb +1 -1
  66. data/lib/graphql/language/document_from_schema_definition.rb +75 -59
  67. data/lib/graphql/language/lexer.rb +358 -1506
  68. data/lib/graphql/language/nodes.rb +166 -93
  69. data/lib/graphql/language/parser.rb +795 -1953
  70. data/lib/graphql/language/printer.rb +340 -160
  71. data/lib/graphql/language/sanitized_printer.rb +21 -23
  72. data/lib/graphql/language/static_visitor.rb +167 -0
  73. data/lib/graphql/language/visitor.rb +188 -141
  74. data/lib/graphql/language.rb +61 -1
  75. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  76. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  77. data/lib/graphql/pagination/array_connection.rb +6 -6
  78. data/lib/graphql/pagination/connection.rb +33 -6
  79. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  80. data/lib/graphql/query/context/scoped_context.rb +101 -0
  81. data/lib/graphql/query/context.rb +117 -112
  82. data/lib/graphql/query/null_context.rb +12 -25
  83. data/lib/graphql/query/validation_pipeline.rb +6 -5
  84. data/lib/graphql/query/variables.rb +3 -3
  85. data/lib/graphql/query.rb +86 -30
  86. data/lib/graphql/railtie.rb +9 -6
  87. data/lib/graphql/rake_task.rb +29 -11
  88. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  89. data/lib/graphql/schema/addition.rb +59 -23
  90. data/lib/graphql/schema/always_visible.rb +11 -0
  91. data/lib/graphql/schema/argument.rb +55 -26
  92. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  93. data/lib/graphql/schema/build_from_definition.rb +56 -32
  94. data/lib/graphql/schema/directive/one_of.rb +24 -0
  95. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  96. data/lib/graphql/schema/directive/transform.rb +1 -1
  97. data/lib/graphql/schema/directive.rb +15 -3
  98. data/lib/graphql/schema/enum.rb +35 -24
  99. data/lib/graphql/schema/enum_value.rb +2 -3
  100. data/lib/graphql/schema/field/connection_extension.rb +2 -16
  101. data/lib/graphql/schema/field/scope_extension.rb +8 -1
  102. data/lib/graphql/schema/field.rb +147 -107
  103. data/lib/graphql/schema/field_extension.rb +1 -4
  104. data/lib/graphql/schema/find_inherited_value.rb +2 -7
  105. data/lib/graphql/schema/has_single_input_argument.rb +158 -0
  106. data/lib/graphql/schema/input_object.rb +47 -11
  107. data/lib/graphql/schema/interface.rb +15 -21
  108. data/lib/graphql/schema/introspection_system.rb +7 -17
  109. data/lib/graphql/schema/late_bound_type.rb +10 -0
  110. data/lib/graphql/schema/list.rb +2 -2
  111. data/lib/graphql/schema/loader.rb +2 -3
  112. data/lib/graphql/schema/member/base_dsl_methods.rb +18 -14
  113. data/lib/graphql/schema/member/build_type.rb +11 -3
  114. data/lib/graphql/schema/member/has_arguments.rb +170 -130
  115. data/lib/graphql/schema/member/has_ast_node.rb +12 -0
  116. data/lib/graphql/schema/member/has_deprecation_reason.rb +3 -4
  117. data/lib/graphql/schema/member/has_directives.rb +81 -61
  118. data/lib/graphql/schema/member/has_fields.rb +100 -38
  119. data/lib/graphql/schema/member/has_interfaces.rb +65 -10
  120. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  121. data/lib/graphql/schema/member/has_validators.rb +32 -6
  122. data/lib/graphql/schema/member/relay_shortcuts.rb +19 -0
  123. data/lib/graphql/schema/member/scoped.rb +19 -0
  124. data/lib/graphql/schema/member/type_system_helpers.rb +16 -0
  125. data/lib/graphql/schema/member/validates_input.rb +3 -3
  126. data/lib/graphql/schema/mutation.rb +7 -0
  127. data/lib/graphql/schema/object.rb +16 -5
  128. data/lib/graphql/schema/printer.rb +11 -8
  129. data/lib/graphql/schema/relay_classic_mutation.rb +7 -129
  130. data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
  131. data/lib/graphql/schema/resolver.rb +47 -32
  132. data/lib/graphql/schema/scalar.rb +3 -3
  133. data/lib/graphql/schema/subscription.rb +11 -4
  134. data/lib/graphql/schema/subset.rb +397 -0
  135. data/lib/graphql/schema/timeout.rb +25 -29
  136. data/lib/graphql/schema/type_expression.rb +2 -2
  137. data/lib/graphql/schema/type_membership.rb +3 -0
  138. data/lib/graphql/schema/union.rb +11 -2
  139. data/lib/graphql/schema/unique_within_type.rb +1 -1
  140. data/lib/graphql/schema/validator/all_validator.rb +60 -0
  141. data/lib/graphql/schema/validator.rb +4 -2
  142. data/lib/graphql/schema/warden.rb +238 -93
  143. data/lib/graphql/schema.rb +498 -103
  144. data/lib/graphql/static_validation/all_rules.rb +2 -1
  145. data/lib/graphql/static_validation/base_visitor.rb +7 -6
  146. data/lib/graphql/static_validation/definition_dependencies.rb +7 -1
  147. data/lib/graphql/static_validation/literal_validator.rb +24 -7
  148. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  149. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  150. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -2
  151. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
  152. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -4
  153. data/lib/graphql/static_validation/rules/fields_will_merge.rb +10 -10
  154. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  155. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  156. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  157. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  158. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +66 -0
  159. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +29 -0
  160. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  161. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
  162. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  163. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  164. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  165. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  166. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  167. data/lib/graphql/static_validation/validation_context.rb +5 -5
  168. data/lib/graphql/static_validation/validator.rb +4 -1
  169. data/lib/graphql/static_validation.rb +0 -1
  170. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +11 -4
  171. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  172. data/lib/graphql/subscriptions/event.rb +11 -10
  173. data/lib/graphql/subscriptions/serialize.rb +2 -0
  174. data/lib/graphql/subscriptions.rb +20 -13
  175. data/lib/graphql/testing/helpers.rb +151 -0
  176. data/lib/graphql/testing.rb +2 -0
  177. data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
  178. data/lib/graphql/tracing/appoptics_trace.rb +251 -0
  179. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  180. data/lib/graphql/tracing/appsignal_trace.rb +77 -0
  181. data/lib/graphql/tracing/data_dog_trace.rb +183 -0
  182. data/lib/graphql/tracing/data_dog_tracing.rb +9 -21
  183. data/lib/graphql/{execution/instrumentation.rb → tracing/legacy_hooks_trace.rb} +10 -28
  184. data/lib/graphql/tracing/legacy_trace.rb +69 -0
  185. data/lib/graphql/tracing/new_relic_trace.rb +75 -0
  186. data/lib/graphql/tracing/notifications_trace.rb +45 -0
  187. data/lib/graphql/tracing/platform_trace.rb +118 -0
  188. data/lib/graphql/tracing/platform_tracing.rb +17 -3
  189. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +4 -2
  190. data/lib/graphql/tracing/prometheus_trace.rb +89 -0
  191. data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
  192. data/lib/graphql/tracing/scout_trace.rb +72 -0
  193. data/lib/graphql/tracing/sentry_trace.rb +112 -0
  194. data/lib/graphql/tracing/statsd_trace.rb +56 -0
  195. data/lib/graphql/tracing/trace.rb +76 -0
  196. data/lib/graphql/tracing.rb +20 -40
  197. data/lib/graphql/type_kinds.rb +7 -4
  198. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  199. data/lib/graphql/types/relay/base_connection.rb +1 -1
  200. data/lib/graphql/types/relay/connection_behaviors.rb +68 -6
  201. data/lib/graphql/types/relay/edge_behaviors.rb +33 -5
  202. data/lib/graphql/types/relay/node_behaviors.rb +8 -2
  203. data/lib/graphql/types/relay/page_info_behaviors.rb +11 -2
  204. data/lib/graphql/types/relay.rb +0 -1
  205. data/lib/graphql/types/string.rb +1 -1
  206. data/lib/graphql/types.rb +1 -0
  207. data/lib/graphql/version.rb +1 -1
  208. data/lib/graphql.rb +27 -20
  209. data/readme.md +13 -3
  210. metadata +96 -47
  211. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  212. data/lib/graphql/analysis/ast/field_usage.rb +0 -57
  213. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  214. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  215. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  216. data/lib/graphql/analysis/ast/query_depth.rb +0 -55
  217. data/lib/graphql/analysis/ast/visitor.rb +0 -269
  218. data/lib/graphql/analysis/ast.rb +0 -81
  219. data/lib/graphql/deprecation.rb +0 -9
  220. data/lib/graphql/filter.rb +0 -53
  221. data/lib/graphql/language/lexer.rl +0 -280
  222. data/lib/graphql/language/parser.y +0 -554
  223. data/lib/graphql/language/token.rb +0 -34
  224. data/lib/graphql/schema/base_64_bp.rb +0 -26
  225. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  226. data/lib/graphql/static_validation/type_stack.rb +0 -216
  227. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
  228. data/lib/graphql/types/relay/default_relay.rb +0 -21
@@ -11,76 +11,140 @@ require "graphql/execution/interpreter/handles_raw_value"
11
11
  module GraphQL
12
12
  module Execution
13
13
  class Interpreter
14
- def self.begin_multiplex(multiplex)
15
- # Since this is basically the batching context,
16
- # share it for a whole multiplex
17
- multiplex.context[:interpreter_instance] ||= self.new
18
- end
14
+ class << self
15
+ # Used internally to signal that the query shouldn't be executed
16
+ # @api private
17
+ NO_OPERATION = GraphQL::EmptyObjects::EMPTY_HASH
19
18
 
20
- def self.begin_query(query, multiplex)
21
- # The batching context is shared by the multiplex,
22
- # so fetch it out and use that instance.
23
- interpreter =
24
- query.context.namespace(:interpreter)[:interpreter_instance] =
25
- multiplex.context[:interpreter_instance]
26
- interpreter.evaluate(query)
27
- query
28
- end
19
+ # @param schema [GraphQL::Schema]
20
+ # @param queries [Array<GraphQL::Query, Hash>]
21
+ # @param context [Hash]
22
+ # @param max_complexity [Integer, nil]
23
+ # @return [Array<GraphQL::Query::Result>] One result per query
24
+ def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
25
+ queries = query_options.map do |opts|
26
+ case opts
27
+ when Hash
28
+ schema.query_class.new(schema, nil, **opts)
29
+ when GraphQL::Query
30
+ opts
31
+ else
32
+ raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
33
+ end
34
+ end
29
35
 
30
- def self.finish_multiplex(_results, multiplex)
31
- interpreter = multiplex.context[:interpreter_instance]
32
- interpreter.sync_lazies(multiplex: multiplex)
33
- end
36
+ multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
37
+ multiplex.current_trace.execute_multiplex(multiplex: multiplex) do
38
+ schema = multiplex.schema
39
+ queries = multiplex.queries
40
+ lazies_at_depth = Hash.new { |h, k| h[k] = [] }
41
+ multiplex_analyzers = schema.multiplex_analyzers
42
+ if multiplex.max_complexity
43
+ multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
44
+ end
34
45
 
35
- def self.finish_query(query, _multiplex)
36
- {
37
- "data" => query.context.namespace(:interpreter)[:runtime].final_result
38
- }
39
- end
46
+ schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
47
+ begin
48
+ # Since this is basically the batching context,
49
+ # share it for a whole multiplex
50
+ multiplex.context[:interpreter_instance] ||= multiplex.schema.query_execution_strategy(deprecation_warning: false).new
51
+ # Do as much eager evaluation of the query as possible
52
+ results = []
53
+ queries.each_with_index do |query, idx|
54
+ if query.subscription? && !query.subscription_update?
55
+ query.context.namespace(:subscriptions)[:events] = []
56
+ end
57
+ multiplex.dataloader.append_job {
58
+ operation = query.selected_operation
59
+ result = if operation.nil? || !query.valid? || query.context.errors.any?
60
+ NO_OPERATION
61
+ else
62
+ begin
63
+ # Although queries in a multiplex _share_ an Interpreter instance,
64
+ # they also have another item of state, which is private to that query
65
+ # in particular, assign it here:
66
+ runtime = Runtime.new(query: query, lazies_at_depth: lazies_at_depth)
67
+ query.context.namespace(:interpreter_runtime)[:runtime] = runtime
40
68
 
41
- # Run the eager part of `query`
42
- # @return {Interpreter::Runtime}
43
- def evaluate(query)
44
- # Although queries in a multiplex _share_ an Interpreter instance,
45
- # they also have another item of state, which is private to that query
46
- # in particular, assign it here:
47
- runtime = Runtime.new(query: query)
48
- query.context.namespace(:interpreter)[:runtime] = runtime
69
+ query.current_trace.execute_query(query: query) do
70
+ runtime.run_eager
71
+ end
72
+ rescue GraphQL::ExecutionError => err
73
+ query.context.errors << err
74
+ NO_OPERATION
75
+ end
76
+ end
77
+ results[idx] = result
78
+ }
79
+ end
49
80
 
50
- query.trace("execute_query", {query: query}) do
51
- runtime.run_eager
52
- end
81
+ multiplex.dataloader.run
53
82
 
54
- runtime
55
- end
83
+ # Then, work through lazy results in a breadth-first way
84
+ multiplex.dataloader.append_job {
85
+ query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil
86
+ queries = multiplex ? multiplex.queries : [query]
87
+ final_values = queries.map do |query|
88
+ runtime = query.context.namespace(:interpreter_runtime)[:runtime]
89
+ # it might not be present if the query has an error
90
+ runtime ? runtime.final_result : nil
91
+ end
92
+ final_values.compact!
93
+ multiplex.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
94
+ Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader)
95
+ end
96
+ }
97
+ multiplex.dataloader.run
56
98
 
57
- # Run the lazy part of `query` or `multiplex`.
58
- # @return [void]
59
- def sync_lazies(query: nil, multiplex: nil)
60
- tracer = query || multiplex
61
- if query.nil? && multiplex.queries.length == 1
62
- query = multiplex.queries[0]
63
- end
64
- queries = multiplex ? multiplex.queries : [query]
65
- final_values = queries.map do |query|
66
- runtime = query.context.namespace(:interpreter)[:runtime]
67
- # it might not be present if the query has an error
68
- runtime ? runtime.final_result : nil
69
- end
70
- final_values.compact!
71
- tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do
72
- Interpreter::Resolve.resolve_all(final_values, multiplex.dataloader)
73
- end
74
- queries.each do |query|
75
- runtime = query.context.namespace(:interpreter)[:runtime]
76
- if runtime
77
- runtime.delete_interpreter_context(:current_path)
78
- runtime.delete_interpreter_context(:current_field)
79
- runtime.delete_interpreter_context(:current_object)
80
- runtime.delete_interpreter_context(:current_arguments)
99
+ # Then, find all errors and assign the result to the query object
100
+ results.each_with_index do |data_result, idx|
101
+ query = queries[idx]
102
+ if (events = query.context.namespace(:subscriptions)[:events]) && events.any?
103
+ schema.subscriptions.write_subscription(query, events)
104
+ end
105
+ # Assign the result so that it can be accessed in instrumentation
106
+ query.result_values = if data_result.equal?(NO_OPERATION)
107
+ if !query.valid? || query.context.errors.any?
108
+ # A bit weird, but `Query#static_errors` _includes_ `query.context.errors`
109
+ { "errors" => query.static_errors.map(&:to_h) }
110
+ else
111
+ data_result
112
+ end
113
+ else
114
+ result = {}
115
+
116
+ if query.context.errors.any?
117
+ error_result = query.context.errors.map(&:to_h)
118
+ result["errors"] = error_result
119
+ end
120
+
121
+ result["data"] = query.context.namespace(:interpreter_runtime)[:runtime].final_result
122
+
123
+ result
124
+ end
125
+ if query.context.namespace?(:__query_result_extensions__)
126
+ query.result_values["extensions"] = query.context.namespace(:__query_result_extensions__)
127
+ end
128
+ # Get the Query::Result, not the Hash
129
+ results[idx] = query.result
130
+ end
131
+
132
+ results
133
+ rescue Exception
134
+ # TODO rescue at a higher level so it will catch errors in analysis, too
135
+ # Assign values here so that the query's `@executed` becomes true
136
+ queries.map { |q| q.result_values ||= {} }
137
+ raise
138
+ ensure
139
+ queries.map { |query|
140
+ runtime = query.context.namespace(:interpreter_runtime)[:runtime]
141
+ if runtime
142
+ runtime.delete_all_interpreter_context
143
+ end
144
+ }
145
+ end
81
146
  end
82
147
  end
83
- nil
84
148
  end
85
149
 
86
150
  class ListResultFailedError < GraphQL::Error
@@ -12,16 +12,14 @@ module GraphQL
12
12
  # - It has no error-catching functionality
13
13
  # @api private
14
14
  class Lazy
15
- attr_reader :path, :field
15
+ attr_reader :field
16
16
 
17
17
  # Create a {Lazy} which will get its inner value by calling the block
18
- # @param path [Array<String, Integer>]
19
18
  # @param field [GraphQL::Schema::Field]
20
19
  # @param get_value_func [Proc] a block to get the inner value (later)
21
- def initialize(path: nil, field: nil, &get_value_func)
20
+ def initialize(field: nil, &get_value_func)
22
21
  @get_value_func = get_value_func
23
22
  @resolved = false
24
- @path = path
25
23
  @field = field
26
24
  end
27
25
 
@@ -29,15 +27,11 @@ module GraphQL
29
27
  def value
30
28
  if !@resolved
31
29
  @resolved = true
32
- @value = begin
33
- v = @get_value_func.call
34
- if v.is_a?(Lazy)
35
- v = v.value
36
- end
37
- v
38
- rescue GraphQL::ExecutionError => err
39
- err
30
+ v = @get_value_func.call
31
+ if v.is_a?(Lazy)
32
+ v = v.value
40
33
  end
34
+ @value = v
41
35
  end
42
36
 
43
37
  # `SKIP` was made into a subclass of `GraphQL::Error` to improve runtime performance
@@ -55,7 +55,7 @@ module GraphQL
55
55
  @arguments
56
56
  else
57
57
  @arguments = if @field
58
- @query.schema.after_lazy(@query.arguments_for(@ast_nodes.first, @field)) do |args|
58
+ @query.after_lazy(@query.arguments_for(@ast_nodes.first, @field)) do |args|
59
59
  args.is_a?(Execution::Interpreter::Arguments) ? args.keyword_arguments : args
60
60
  end
61
61
  else
@@ -76,8 +76,24 @@ module GraphQL
76
76
  # @param field_name [String, Symbol]
77
77
  # @param arguments [Hash] Arguments which must match in the selection
78
78
  # @return [Boolean]
79
- def selects?(field_name, arguments: nil)
80
- selection(field_name, arguments: arguments).selected?
79
+ def selects?(field_name, selected_type: @selected_type, arguments: nil)
80
+ selection(field_name, selected_type: selected_type, arguments: arguments).selected?
81
+ end
82
+
83
+ # True if this node has a selection with alias matching `alias_name`.
84
+ # If `alias_name` is a String, it is treated as a GraphQL-style (camelized)
85
+ # field name and used verbatim. If `alias_name` is a Symbol, it is
86
+ # treated as a Ruby-style (underscored) name and camelized before comparing.
87
+ #
88
+ # If `arguments:` is provided, each provided key/value will be matched
89
+ # against the arguments in the next selection. This method will return false
90
+ # if any of the given `arguments:` are not present and matching in the next selection.
91
+ # (But, the next selection may contain _more_ than the given arguments.)
92
+ # @param alias_name [String, Symbol]
93
+ # @param arguments [Hash] Arguments which must match in the selection
94
+ # @return [Boolean]
95
+ def selects_alias?(alias_name, arguments: nil)
96
+ alias_selection(alias_name, arguments: arguments).selected?
81
97
  end
82
98
 
83
99
  # @return [Boolean] True if this lookahead represents a field that was requested
@@ -87,27 +103,57 @@ module GraphQL
87
103
 
88
104
  # Like {#selects?}, but can be used for chaining.
89
105
  # It returns a null object (check with {#selected?})
106
+ # @param field_name [String, Symbol]
90
107
  # @return [GraphQL::Execution::Lookahead]
91
108
  def selection(field_name, selected_type: @selected_type, arguments: nil)
92
- next_field_name = normalize_name(field_name)
93
-
94
- next_field_defn = @query.get_field(selected_type, next_field_name)
95
- if next_field_defn
96
- next_nodes = []
97
- @ast_nodes.each do |ast_node|
98
- ast_node.selections.each do |selection|
99
- find_selected_nodes(selection, next_field_name, next_field_defn, arguments: arguments, matches: next_nodes)
100
- end
109
+ next_field_defn = case field_name
110
+ when String
111
+ @query.types.field(selected_type, field_name)
112
+ when Symbol
113
+ # Try to avoid the `.to_s` below, if possible
114
+ all_fields = if selected_type.kind.fields?
115
+ @query.types.fields(selected_type)
116
+ else
117
+ # Handle unions by checking possible
118
+ @query.types
119
+ .possible_types(selected_type)
120
+ .map { |t| @query.types.fields(t) }
121
+ .tap(&:flatten!)
101
122
  end
102
123
 
103
- if next_nodes.any?
104
- Lookahead.new(query: @query, ast_nodes: next_nodes, field: next_field_defn, owner_type: selected_type)
124
+
125
+ if (match_by_orig_name = all_fields.find { |f| f.original_name == field_name })
126
+ match_by_orig_name
105
127
  else
106
- NULL_LOOKAHEAD
128
+ # Symbol#name is only present on 3.0+
129
+ sym_s = field_name.respond_to?(:name) ? field_name.name : field_name.to_s
130
+ guessed_name = Schema::Member::BuildType.camelize(sym_s)
131
+ @query.types.field(selected_type, guessed_name)
107
132
  end
108
- else
109
- NULL_LOOKAHEAD
110
133
  end
134
+ lookahead_for_selection(next_field_defn, selected_type, arguments)
135
+ end
136
+
137
+ # Like {#selection}, but for aliases.
138
+ # It returns a null object (check with {#selected?})
139
+ # @return [GraphQL::Execution::Lookahead]
140
+ def alias_selection(alias_name, selected_type: @selected_type, arguments: nil)
141
+ alias_cache_key = [alias_name, arguments]
142
+ return alias_selections[key] if alias_selections.key?(alias_name)
143
+
144
+ alias_node = lookup_alias_node(ast_nodes, alias_name)
145
+ return NULL_LOOKAHEAD unless alias_node
146
+
147
+ next_field_defn = @query.types.field(selected_type, alias_node.name)
148
+
149
+ alias_arguments = @query.arguments_for(alias_node, next_field_defn)
150
+ if alias_arguments.is_a?(::GraphQL::Execution::Interpreter::Arguments)
151
+ alias_arguments = alias_arguments.keyword_arguments
152
+ end
153
+
154
+ return NULL_LOOKAHEAD if arguments && arguments != alias_arguments
155
+
156
+ alias_selections[alias_cache_key] = lookahead_for_selection(next_field_defn, selected_type, alias_arguments, alias_name)
111
157
  end
112
158
 
113
159
  # Like {#selection}, but for all nodes.
@@ -137,7 +183,7 @@ module GraphQL
137
183
 
138
184
  subselections_by_type.each do |type, ast_nodes_by_response_key|
139
185
  ast_nodes_by_response_key.each do |response_key, ast_nodes|
140
- field_defn = @query.get_field(type, ast_nodes.first.name)
186
+ field_defn = @query.types.field(type, ast_nodes.first.name)
141
187
  lookahead = Lookahead.new(query: @query, ast_nodes: ast_nodes, field: field_defn, owner_type: type)
142
188
  subselections.push(lookahead)
143
189
  end
@@ -196,23 +242,6 @@ module GraphQL
196
242
 
197
243
  private
198
244
 
199
- # If it's a symbol, stringify and camelize it
200
- def normalize_name(name)
201
- if name.is_a?(Symbol)
202
- Schema::Member::BuildType.camelize(name.to_s)
203
- else
204
- name
205
- end
206
- end
207
-
208
- def normalize_keyword(keyword)
209
- if keyword.is_a?(String)
210
- Schema::Member::BuildType.underscore(keyword).to_sym
211
- else
212
- keyword
213
- end
214
- end
215
-
216
245
  def skipped_by_directive?(ast_selection)
217
246
  ast_selection.directives.each do |directive|
218
247
  dir_defn = @query.schema.directives.fetch(directive.name)
@@ -237,7 +266,7 @@ module GraphQL
237
266
  elsif arguments.nil? || arguments.empty?
238
267
  selections_on_type[response_key] = [ast_selection]
239
268
  else
240
- field_defn = @query.get_field(selected_type, ast_selection.name)
269
+ field_defn = @query.types.field(selected_type, ast_selection.name)
241
270
  if arguments_match?(arguments, field_defn, ast_selection)
242
271
  selections_on_type[response_key] = [ast_selection]
243
272
  end
@@ -247,14 +276,14 @@ module GraphQL
247
276
  subselections_on_type = selections_on_type
248
277
  if (t = ast_selection.type)
249
278
  # Assuming this is valid, that `t` will be found.
250
- on_type = @query.get_type(t.name)
279
+ on_type = @query.types.type(t.name)
251
280
  subselections_on_type = subselections_by_type[on_type] ||= {}
252
281
  end
253
282
  find_selections(subselections_by_type, subselections_on_type, on_type, ast_selection.selections, arguments)
254
283
  when GraphQL::Language::Nodes::FragmentSpread
255
- frag_defn = @query.fragments[ast_selection.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{ast_selection.name} (found: #{@query.fragments.keys})")
284
+ frag_defn = lookup_fragment(ast_selection)
256
285
  # Again, assuming a valid AST
257
- on_type = @query.get_type(frag_defn.type.name)
286
+ on_type = @query.types.type(frag_defn.type.name)
258
287
  subselections_on_type = subselections_by_type[on_type] ||= {}
259
288
  find_selections(subselections_by_type, subselections_on_type, on_type, frag_defn.selections, arguments)
260
289
  else
@@ -265,11 +294,11 @@ module GraphQL
265
294
 
266
295
  # If a selection on `node` matches `field_name` (which is backed by `field_defn`)
267
296
  # and matches the `arguments:` constraints, then add that node to `matches`
268
- def find_selected_nodes(node, field_name, field_defn, arguments:, matches:)
297
+ def find_selected_nodes(node, field_name, field_defn, arguments:, matches:, alias_name: NOT_CONFIGURED)
269
298
  return if skipped_by_directive?(node)
270
299
  case node
271
300
  when GraphQL::Language::Nodes::Field
272
- if node.name == field_name
301
+ if node.name == field_name && (NOT_CONFIGURED.equal?(alias_name) || node.alias == alias_name)
273
302
  if arguments.nil? || arguments.empty?
274
303
  # No constraint applied
275
304
  matches << node
@@ -278,10 +307,10 @@ module GraphQL
278
307
  end
279
308
  end
280
309
  when GraphQL::Language::Nodes::InlineFragment
281
- node.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches) }
310
+ node.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches, alias_name: alias_name) }
282
311
  when GraphQL::Language::Nodes::FragmentSpread
283
- frag_defn = @query.fragments[node.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{node.name} (found: #{@query.fragments.keys})")
284
- frag_defn.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches) }
312
+ frag_defn = lookup_fragment(node)
313
+ frag_defn.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches, alias_name: alias_name) }
285
314
  else
286
315
  raise "Unexpected selection comparison on #{node.class.name} (#{node})"
287
316
  end
@@ -290,11 +319,60 @@ module GraphQL
290
319
  def arguments_match?(arguments, field_defn, field_node)
291
320
  query_kwargs = @query.arguments_for(field_node, field_defn)
292
321
  arguments.all? do |arg_name, arg_value|
293
- arg_name = normalize_keyword(arg_name)
322
+ arg_name_sym = if arg_name.is_a?(String)
323
+ Schema::Member::BuildType.underscore(arg_name).to_sym
324
+ else
325
+ arg_name
326
+ end
327
+
294
328
  # Make sure the constraint is present with a matching value
295
- query_kwargs.key?(arg_name) && query_kwargs[arg_name] == arg_value
329
+ query_kwargs.key?(arg_name_sym) && query_kwargs[arg_name_sym] == arg_value
296
330
  end
297
331
  end
332
+
333
+ def lookahead_for_selection(field_defn, selected_type, arguments, alias_name = NOT_CONFIGURED)
334
+ return NULL_LOOKAHEAD unless field_defn
335
+
336
+ next_nodes = []
337
+ field_name = field_defn.name
338
+ @ast_nodes.each do |ast_node|
339
+ ast_node.selections.each do |selection|
340
+ find_selected_nodes(selection, field_name, field_defn, arguments: arguments, matches: next_nodes, alias_name: alias_name)
341
+ end
342
+ end
343
+
344
+ return NULL_LOOKAHEAD if next_nodes.empty?
345
+
346
+ Lookahead.new(query: @query, ast_nodes: next_nodes, field: field_defn, owner_type: selected_type)
347
+ end
348
+
349
+ def alias_selections
350
+ return @alias_selections if defined?(@alias_selections)
351
+ @alias_selections ||= {}
352
+ end
353
+
354
+ def lookup_alias_node(nodes, name)
355
+ return if nodes.empty?
356
+
357
+ nodes.flat_map(&:children)
358
+ .flat_map { |child| unwrap_fragments(child) }
359
+ .find { |child| child.is_a?(GraphQL::Language::Nodes::Field) && child.alias == name }
360
+ end
361
+
362
+ def unwrap_fragments(node)
363
+ case node
364
+ when GraphQL::Language::Nodes::InlineFragment
365
+ node.children
366
+ when GraphQL::Language::Nodes::FragmentSpread
367
+ lookup_fragment(node).children
368
+ else
369
+ [node]
370
+ end
371
+ end
372
+
373
+ def lookup_fragment(ast_selection)
374
+ @query.fragments[ast_selection.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{ast_selection.name} (found: #{@query.fragments.keys})")
375
+ end
298
376
  end
299
377
  end
300
378
  end
@@ -23,18 +23,16 @@ module GraphQL
23
23
  # @see {Schema#multiplex} for public API
24
24
  # @api private
25
25
  class Multiplex
26
- # Used internally to signal that the query shouldn't be executed
27
- # @api private
28
- NO_OPERATION = {}.freeze
29
-
30
26
  include Tracing::Traceable
31
27
 
32
- attr_reader :context, :queries, :schema, :max_complexity, :dataloader
28
+ attr_reader :context, :queries, :schema, :max_complexity, :dataloader, :current_trace
29
+
33
30
  def initialize(schema:, queries:, context:, max_complexity:)
34
31
  @schema = schema
35
32
  @queries = queries
36
33
  @queries.each { |q| q.multiplex = self }
37
34
  @context = context
35
+ @current_trace = @context[:trace] || schema.new_trace(multiplex: self)
38
36
  @dataloader = @context[:dataloader] ||= @schema.dataloader_class.new
39
37
  @tracers = schema.tracers + (context[:tracers] || [])
40
38
  # Support `context: {backtrace: true}`
@@ -43,118 +41,6 @@ module GraphQL
43
41
  end
44
42
  @max_complexity = max_complexity
45
43
  end
46
-
47
- class << self
48
- # @param schema [GraphQL::Schema]
49
- # @param queries [Array<GraphQL::Query, Hash>]
50
- # @param context [Hash]
51
- # @param max_complexity [Integer, nil]
52
- # @return [Array<Hash>] One result per query
53
- def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
54
- queries = query_options.map do |opts|
55
- case opts
56
- when Hash
57
- GraphQL::Query.new(schema, nil, **opts)
58
- when GraphQL::Query
59
- opts
60
- else
61
- raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
62
- end
63
- end
64
-
65
- multiplex = self.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
66
- multiplex.trace("execute_multiplex", { multiplex: multiplex }) do
67
- GraphQL::Execution::Instrumentation.apply_instrumenters(multiplex) do
68
- schema = multiplex.schema
69
- multiplex_analyzers = schema.multiplex_analyzers
70
- if multiplex.max_complexity
71
- multiplex_analyzers += [GraphQL::Analysis::AST::MaxQueryComplexity]
72
- end
73
-
74
- schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
75
-
76
- begin
77
- multiplex.schema.query_execution_strategy.begin_multiplex(multiplex)
78
- # Do as much eager evaluation of the query as possible
79
- results = []
80
- queries.each_with_index do |query, idx|
81
- multiplex.dataloader.append_job { begin_query(results, idx, query, multiplex) }
82
- end
83
-
84
- multiplex.dataloader.run
85
-
86
- # Then, work through lazy results in a breadth-first way
87
- multiplex.dataloader.append_job {
88
- multiplex.schema.query_execution_strategy.finish_multiplex(results, multiplex)
89
- }
90
- multiplex.dataloader.run
91
-
92
- # Then, find all errors and assign the result to the query object
93
- results.each_with_index do |data_result, idx|
94
- query = queries[idx]
95
- finish_query(data_result, query, multiplex)
96
- # Get the Query::Result, not the Hash
97
- results[idx] = query.result
98
- end
99
-
100
- results
101
- rescue Exception
102
- # TODO rescue at a higher level so it will catch errors in analysis, too
103
- # Assign values here so that the query's `@executed` becomes true
104
- queries.map { |q| q.result_values ||= {} }
105
- raise
106
- end
107
- end
108
- end
109
- end
110
-
111
- # @param query [GraphQL::Query]
112
- def begin_query(results, idx, query, multiplex)
113
- operation = query.selected_operation
114
- result = if operation.nil? || !query.valid? || query.context.errors.any?
115
- NO_OPERATION
116
- else
117
- begin
118
- query.schema.query_execution_strategy.begin_query(query, multiplex)
119
- rescue GraphQL::ExecutionError => err
120
- query.context.errors << err
121
- NO_OPERATION
122
- end
123
- end
124
- results[idx] = result
125
- nil
126
- end
127
-
128
- private
129
-
130
- # @param data_result [Hash] The result for the "data" key, if any
131
- # @param query [GraphQL::Query] The query which was run
132
- # @return [Hash] final result of this query, including all values and errors
133
- def finish_query(data_result, query, multiplex)
134
- # Assign the result so that it can be accessed in instrumentation
135
- query.result_values = if data_result.equal?(NO_OPERATION)
136
- if !query.valid? || query.context.errors.any?
137
- # A bit weird, but `Query#static_errors` _includes_ `query.context.errors`
138
- { "errors" => query.static_errors.map(&:to_h) }
139
- else
140
- data_result
141
- end
142
- else
143
- # Use `context.value` which was assigned during execution
144
- result = query.schema.query_execution_strategy.finish_query(query, multiplex)
145
-
146
- if query.context.errors.any?
147
- error_result = query.context.errors.map(&:to_h)
148
- result["errors"] = error_result
149
- end
150
-
151
- result
152
- end
153
- if query.context.namespace?(:__query_result_extensions__)
154
- query.result_values["extensions"] = query.context.namespace(:__query_result_extensions__)
155
- end
156
- end
157
- end
158
44
  end
159
45
  end
160
46
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/execution/directive_checks"
3
- require "graphql/execution/instrumentation"
4
3
  require "graphql/execution/interpreter"
5
4
  require "graphql/execution/lazy"
6
5
  require "graphql/execution/lookahead"
@@ -11,8 +11,8 @@ module GraphQL
11
11
  "to the executor."
12
12
  field :name, String, null: false, method: :graphql_name
13
13
  field :description, String
14
- field :locations, [GraphQL::Schema::LateBoundType.new("__DirectiveLocation")], null: false
15
- field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false do
14
+ field :locations, [GraphQL::Schema::LateBoundType.new("__DirectiveLocation")], null: false, scope: false
15
+ field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false do
16
16
  argument :include_deprecated, Boolean, required: false, default_value: false
17
17
  end
18
18
  field :on_operation, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_operation?
@@ -22,7 +22,7 @@ module GraphQL
22
22
  field :is_repeatable, Boolean, method: :repeatable?
23
23
 
24
24
  def args(include_deprecated:)
25
- args = @context.warden.arguments(@object)
25
+ args = @context.types.arguments(@object)
26
26
  args = args.reject(&:deprecation_reason) unless include_deprecated
27
27
  args
28
28
  end