graphql 2.4.5 → 2.5.21

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.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  4. data/lib/graphql/analysis/analyzer.rb +2 -1
  5. data/lib/graphql/analysis/query_complexity.rb +87 -7
  6. data/lib/graphql/analysis/visitor.rb +37 -40
  7. data/lib/graphql/analysis.rb +12 -9
  8. data/lib/graphql/autoload.rb +1 -0
  9. data/lib/graphql/backtrace/table.rb +118 -55
  10. data/lib/graphql/backtrace.rb +1 -19
  11. data/lib/graphql/current.rb +6 -1
  12. data/lib/graphql/dashboard/application_controller.rb +41 -0
  13. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  14. data/lib/graphql/dashboard/installable.rb +22 -0
  15. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  16. data/lib/graphql/dashboard/limiters.rb +93 -0
  17. data/lib/graphql/dashboard/operation_store.rb +199 -0
  18. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  19. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  20. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  21. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  22. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  23. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  24. data/lib/graphql/dashboard/statics/icon.png +0 -0
  25. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  26. data/lib/graphql/dashboard/subscriptions.rb +97 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  37. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  38. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  39. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  40. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  41. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  42. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  43. data/lib/graphql/dashboard.rb +96 -0
  44. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  45. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  46. data/lib/graphql/dataloader/async_dataloader.rb +38 -15
  47. data/lib/graphql/dataloader/null_dataloader.rb +55 -10
  48. data/lib/graphql/dataloader/source.rb +18 -6
  49. data/lib/graphql/dataloader.rb +110 -26
  50. data/lib/graphql/date_encoding_error.rb +1 -1
  51. data/lib/graphql/dig.rb +2 -1
  52. data/lib/graphql/execution/interpreter/resolve.rb +10 -16
  53. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +58 -5
  54. data/lib/graphql/execution/interpreter/runtime.rb +229 -93
  55. data/lib/graphql/execution/interpreter.rb +15 -24
  56. data/lib/graphql/execution/multiplex.rb +7 -6
  57. data/lib/graphql/execution/next/field_resolve_step.rb +690 -0
  58. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  59. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  60. data/lib/graphql/execution/next/runner.rb +389 -0
  61. data/lib/graphql/execution/next/selections_step.rb +37 -0
  62. data/lib/graphql/execution/next.rb +69 -0
  63. data/lib/graphql/execution.rb +1 -0
  64. data/lib/graphql/execution_error.rb +13 -10
  65. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  66. data/lib/graphql/introspection/directive_type.rb +7 -3
  67. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  68. data/lib/graphql/introspection/entry_points.rb +11 -3
  69. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  70. data/lib/graphql/introspection/field_type.rb +13 -5
  71. data/lib/graphql/introspection/input_value_type.rb +21 -13
  72. data/lib/graphql/introspection/type_type.rb +64 -28
  73. data/lib/graphql/invalid_name_error.rb +1 -1
  74. data/lib/graphql/invalid_null_error.rb +25 -16
  75. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  76. data/lib/graphql/language/lexer.rb +16 -5
  77. data/lib/graphql/language/nodes.rb +8 -1
  78. data/lib/graphql/language/parser.rb +16 -8
  79. data/lib/graphql/language/static_visitor.rb +37 -33
  80. data/lib/graphql/language/visitor.rb +59 -55
  81. data/lib/graphql/language.rb +21 -12
  82. data/lib/graphql/pagination/connection.rb +2 -0
  83. data/lib/graphql/pagination/connections.rb +32 -0
  84. data/lib/graphql/query/context.rb +6 -10
  85. data/lib/graphql/query/null_context.rb +9 -3
  86. data/lib/graphql/query/partial.rb +179 -0
  87. data/lib/graphql/query.rb +64 -64
  88. data/lib/graphql/railtie.rb +1 -1
  89. data/lib/graphql/schema/addition.rb +3 -1
  90. data/lib/graphql/schema/always_visible.rb +1 -0
  91. data/lib/graphql/schema/argument.rb +24 -8
  92. data/lib/graphql/schema/build_from_definition.rb +113 -54
  93. data/lib/graphql/schema/directive/flagged.rb +2 -0
  94. data/lib/graphql/schema/directive.rb +52 -2
  95. data/lib/graphql/schema/enum.rb +36 -1
  96. data/lib/graphql/schema/enum_value.rb +1 -1
  97. data/lib/graphql/schema/field/connection_extension.rb +15 -35
  98. data/lib/graphql/schema/field/scope_extension.rb +22 -13
  99. data/lib/graphql/schema/field.rb +101 -51
  100. data/lib/graphql/schema/field_extension.rb +33 -0
  101. data/lib/graphql/schema/input_object.rb +45 -38
  102. data/lib/graphql/schema/interface.rb +2 -1
  103. data/lib/graphql/schema/list.rb +1 -1
  104. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  105. data/lib/graphql/schema/member/has_arguments.rb +56 -19
  106. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  107. data/lib/graphql/schema/member/has_dataloader.rb +79 -0
  108. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  109. data/lib/graphql/schema/member/has_directives.rb +1 -1
  110. data/lib/graphql/schema/member/has_fields.rb +81 -5
  111. data/lib/graphql/schema/member/has_interfaces.rb +3 -3
  112. data/lib/graphql/schema/member/scoped.rb +1 -1
  113. data/lib/graphql/schema/member/type_system_helpers.rb +17 -3
  114. data/lib/graphql/schema/member.rb +6 -0
  115. data/lib/graphql/schema/object.rb +18 -8
  116. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  117. data/lib/graphql/schema/resolver.rb +52 -6
  118. data/lib/graphql/schema/scalar.rb +1 -6
  119. data/lib/graphql/schema/subscription.rb +50 -4
  120. data/lib/graphql/schema/timeout.rb +19 -2
  121. data/lib/graphql/schema/validator/required_validator.rb +71 -14
  122. data/lib/graphql/schema/visibility/migration.rb +3 -2
  123. data/lib/graphql/schema/visibility/profile.rb +115 -23
  124. data/lib/graphql/schema/visibility.rb +49 -32
  125. data/lib/graphql/schema/warden.rb +23 -2
  126. data/lib/graphql/schema.rb +333 -68
  127. data/lib/graphql/static_validation/all_rules.rb +2 -2
  128. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  129. data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -17
  130. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  131. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  132. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  133. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  134. data/lib/graphql/static_validation/validator.rb +6 -1
  135. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  136. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  137. data/lib/graphql/subscriptions/event.rb +12 -1
  138. data/lib/graphql/subscriptions/serialize.rb +1 -1
  139. data/lib/graphql/subscriptions.rb +1 -1
  140. data/lib/graphql/testing/helpers.rb +17 -11
  141. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  142. data/lib/graphql/testing.rb +1 -0
  143. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  144. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  145. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  146. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  147. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  148. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  149. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  150. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  151. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  152. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  153. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  154. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  155. data/lib/graphql/tracing/detailed_trace.rb +156 -0
  156. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  157. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  158. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  159. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  160. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  161. data/lib/graphql/tracing/notifications_trace.rb +184 -34
  162. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  163. data/lib/graphql/tracing/null_trace.rb +9 -0
  164. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  165. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  166. data/lib/graphql/tracing/perfetto_trace.rb +864 -0
  167. data/lib/graphql/tracing/platform_trace.rb +5 -0
  168. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  169. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  170. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  171. data/lib/graphql/tracing/scout_trace.rb +32 -55
  172. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  173. data/lib/graphql/tracing/sentry_trace.rb +64 -94
  174. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  175. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  176. data/lib/graphql/tracing/trace.rb +111 -1
  177. data/lib/graphql/tracing.rb +31 -30
  178. data/lib/graphql/type_kinds.rb +1 -0
  179. data/lib/graphql/types/relay/connection_behaviors.rb +9 -7
  180. data/lib/graphql/types/relay/edge_behaviors.rb +5 -4
  181. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  182. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  183. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  184. data/lib/graphql/unauthorized_error.rb +5 -1
  185. data/lib/graphql/version.rb +1 -1
  186. data/lib/graphql.rb +12 -31
  187. metadata +174 -11
  188. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  189. data/lib/graphql/backtrace/trace.rb +0 -93
  190. data/lib/graphql/backtrace/tracer.rb +0 -80
  191. data/lib/graphql/schema/null_mask.rb +0 -11
  192. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -35,11 +35,10 @@ module GraphQL
35
35
  # @return [GraphQL::Query::Context]
36
36
  attr_reader :context
37
37
 
38
- def initialize(query:, lazies_at_depth:)
38
+ def initialize(query:)
39
39
  @query = query
40
40
  @current_trace = query.current_trace
41
41
  @dataloader = query.multiplex.dataloader
42
- @lazies_at_depth = lazies_at_depth
43
42
  @schema = query.schema
44
43
  @context = query.context
45
44
  @response = nil
@@ -57,67 +56,160 @@ module GraphQL
57
56
  end
58
57
 
59
58
  def final_result
60
- @response && @response.graphql_result_data
59
+ @response.respond_to?(:graphql_result_data) ? @response.graphql_result_data : @response
61
60
  end
62
61
 
63
62
  def inspect
64
63
  "#<#{self.class.name} response=#{@response.inspect}>"
65
64
  end
66
65
 
67
- # This _begins_ the execution. Some deferred work
68
- # might be stored up in lazies.
69
66
  # @return [void]
70
67
  def run_eager
71
- root_operation = query.selected_operation
72
- root_op_type = root_operation.operation_type || "query"
73
- root_type = schema.root_type_for_operation(root_op_type)
74
- runtime_object = root_type.wrap(query.root_value, context)
75
- runtime_object = schema.sync_lazy(runtime_object)
76
- is_eager = root_op_type == "mutation"
77
- @response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, root_operation.selections, is_eager)
78
- st = get_current_runtime_state
79
- st.current_result = @response
80
-
81
- if runtime_object.nil?
82
- # Root .authorized? returned false.
83
- @response = nil
68
+ root_type = query.root_type
69
+ case query
70
+ when GraphQL::Query
71
+ ast_node = query.selected_operation
72
+ selections = ast_node.selections
73
+ object = query.root_value
74
+ is_eager = ast_node.operation_type == "mutation"
75
+ base_path = nil
76
+ when GraphQL::Query::Partial
77
+ ast_node = query.ast_nodes.first
78
+ selections = query.ast_nodes.map(&:selections).inject(&:+)
79
+ object = query.object
80
+ is_eager = false
81
+ base_path = query.path
84
82
  else
85
- call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
86
- each_gathered_selections(@response) do |selections, is_selection_array|
87
- if is_selection_array
88
- selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, selections, is_eager)
89
- final_response = @response
90
- else
91
- selection_response = @response
92
- final_response = nil
93
- end
83
+ raise ArgumentError, "Unexpected Runnable, can't execute: #{query.class} (#{query.inspect})"
84
+ end
85
+ object = schema.sync_lazy(object) # TODO test query partial with lazy root object
86
+ runtime_state = get_current_runtime_state
87
+ case root_type.kind.name
88
+ when "OBJECT"
89
+ object_proxy = root_type.wrap(object, context)
90
+ object_proxy = schema.sync_lazy(object_proxy)
91
+ if object_proxy.nil?
92
+ @response = nil
93
+ else
94
+ @response = GraphQLResultHash.new(nil, root_type, object_proxy, nil, false, selections, is_eager, ast_node, nil, nil)
95
+ @response.base_path = base_path
96
+ runtime_state.current_result = @response
97
+ call_method_on_directives(:resolve, object, ast_node.directives) do
98
+ each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys|
99
+ @response.ordered_result_keys ||= ordered_result_keys
100
+ if is_selection_array
101
+ selection_response = GraphQLResultHash.new(nil, root_type, object_proxy, nil, false, selections, is_eager, ast_node, nil, nil)
102
+ selection_response.ordered_result_keys = ordered_result_keys
103
+ final_response = @response
104
+ else
105
+ selection_response = @response
106
+ final_response = nil
107
+ end
94
108
 
95
- @dataloader.append_job {
96
- evaluate_selections(
97
- selections,
98
- selection_response,
99
- final_response,
100
- nil,
109
+ @dataloader.append_job {
110
+ evaluate_selections(
111
+ selections,
112
+ selection_response,
113
+ final_response,
114
+ nil,
115
+ )
116
+ }
117
+ end
118
+ end
119
+ end
120
+ when "LIST"
121
+ inner_type = root_type.unwrap
122
+ case inner_type.kind.name
123
+ when "SCALAR", "ENUM"
124
+ result_name = ast_node.alias || ast_node.name
125
+ field_defn = query.field_definition
126
+ owner_type = field_defn.owner
127
+ selection_result = GraphQLResultHash.new(nil, owner_type, nil, nil, false, EmptyObjects::EMPTY_ARRAY, false, ast_node, nil, nil)
128
+ selection_result.base_path = base_path
129
+ selection_result.ordered_result_keys = [result_name]
130
+ runtime_state = get_current_runtime_state
131
+ runtime_state.current_result = selection_result
132
+ runtime_state.current_result_name = result_name
133
+ continue_value = continue_value(object, field_defn, false, ast_node, result_name, selection_result)
134
+ if HALT != continue_value
135
+ continue_field(continue_value, owner_type, field_defn, root_type, ast_node, nil, false, nil, nil, result_name, selection_result, false, runtime_state) # rubocop:disable Metrics/ParameterLists
136
+ end
137
+ @response = selection_result[result_name]
138
+ else
139
+ @response = GraphQLResultArray.new(nil, root_type, nil, nil, false, selections, false, ast_node, nil, nil)
140
+ @response.base_path = base_path
141
+ idx = nil
142
+ object.each do |inner_value|
143
+ idx ||= 0
144
+ this_idx = idx
145
+ idx += 1
146
+ @dataloader.append_job do
147
+ runtime_state.current_result_name = this_idx
148
+ runtime_state.current_result = @response
149
+ continue_field(
150
+ inner_value, root_type, nil, inner_type, nil, @response.graphql_selections, false, object_proxy,
151
+ nil, this_idx, @response, false, runtime_state
101
152
  )
102
- }
153
+ end
103
154
  end
104
155
  end
156
+ when "SCALAR", "ENUM"
157
+ result_name = ast_node.alias || ast_node.name
158
+ field_defn = query.field_definition
159
+ owner_type = field_defn.owner
160
+ selection_result = GraphQLResultHash.new(nil, owner_type, nil, nil, false, EmptyObjects::EMPTY_ARRAY, false, ast_node, nil, nil)
161
+ selection_result.ordered_result_keys = [result_name]
162
+ selection_result.base_path = base_path
163
+ runtime_state = get_current_runtime_state
164
+ runtime_state.current_result = selection_result
165
+ runtime_state.current_result_name = result_name
166
+ continue_value = continue_value(object, field_defn, false, ast_node, result_name, selection_result)
167
+ if HALT != continue_value
168
+ continue_field(continue_value, owner_type, field_defn, query.root_type, ast_node, nil, false, nil, nil, result_name, selection_result, false, runtime_state) # rubocop:disable Metrics/ParameterLists
169
+ end
170
+ @response = selection_result[result_name]
171
+ when "UNION", "INTERFACE"
172
+ resolved_type, _resolved_obj = resolve_type(root_type, object)
173
+ resolved_type = schema.sync_lazy(resolved_type)
174
+ object_proxy = resolved_type.wrap(object, context)
175
+ object_proxy = schema.sync_lazy(object_proxy)
176
+ @response = GraphQLResultHash.new(nil, resolved_type, object_proxy, nil, false, selections, false, query.ast_nodes.first, nil, nil)
177
+ @response.base_path = base_path
178
+ each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys|
179
+ @response.ordered_result_keys ||= ordered_result_keys
180
+ if is_selection_array == true
181
+ raise "This isn't supported yet"
182
+ end
183
+
184
+ @dataloader.append_job {
185
+ evaluate_selections(
186
+ selections,
187
+ @response,
188
+ nil,
189
+ runtime_state,
190
+ )
191
+ }
192
+ end
193
+ else
194
+ raise "Invariant: unsupported type kind for partial execution: #{root_type.kind.inspect} (#{root_type})"
105
195
  end
106
196
  nil
107
197
  end
108
198
 
109
199
  def each_gathered_selections(response_hash)
110
- gathered_selections = gather_selections(response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections)
200
+ ordered_result_keys = []
201
+ gathered_selections = gather_selections(response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections, nil, {}, ordered_result_keys)
202
+ ordered_result_keys.uniq!
111
203
  if gathered_selections.is_a?(Array)
112
204
  gathered_selections.each do |item|
113
- yield(item, true)
205
+ yield(item, true, ordered_result_keys)
114
206
  end
115
207
  else
116
- yield(gathered_selections, false)
208
+ yield(gathered_selections, false, ordered_result_keys)
117
209
  end
118
210
  end
119
211
 
120
- def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
212
+ def gather_selections(owner_object, owner_type, selections, selections_to_run, selections_by_name, ordered_result_keys)
121
213
  selections.each do |node|
122
214
  # Skip gathering this if the directive says so
123
215
  if !directives_include?(node, owner_object, owner_type)
@@ -126,6 +218,7 @@ module GraphQL
126
218
 
127
219
  if node.is_a?(GraphQL::Language::Nodes::Field)
128
220
  response_key = node.alias || node.name
221
+ ordered_result_keys << response_key
129
222
  selections = selections_by_name[response_key]
130
223
  # if there was already a selection of this field,
131
224
  # use an array to hold all selections,
@@ -162,14 +255,14 @@ module GraphQL
162
255
  type_defn = query.types.type(node.type.name)
163
256
 
164
257
  if query.types.possible_types(type_defn).include?(owner_type)
165
- result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
258
+ result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
166
259
  if !result.equal?(next_selections)
167
260
  selections_to_run = result
168
261
  end
169
262
  end
170
263
  else
171
264
  # it's an untyped fragment, definitely continue
172
- result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
265
+ result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
173
266
  if !result.equal?(next_selections)
174
267
  selections_to_run = result
175
268
  end
@@ -178,7 +271,7 @@ module GraphQL
178
271
  fragment_def = query.fragments[node.name]
179
272
  type_defn = query.types.type(fragment_def.type.name)
180
273
  if query.types.possible_types(type_defn).include?(owner_type)
181
- result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
274
+ result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections, ordered_result_keys)
182
275
  if !result.equal?(next_selections)
183
276
  selections_to_run = result
184
277
  end
@@ -204,10 +297,7 @@ module GraphQL
204
297
  end
205
298
 
206
299
  call_method_on_directives(:resolve, selections_result.graphql_application_value, directives) do
207
- finished_jobs = 0
208
- enqueued_jobs = gathered_selections.size
209
300
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
210
-
211
301
  # Field resolution may pause the fiber,
212
302
  # so it wouldn't get to the `Resolve` call that happens below.
213
303
  # So instead trigger a run from this outer context.
@@ -217,10 +307,6 @@ module GraphQL
217
307
  evaluate_selection(
218
308
  result_name, field_ast_nodes_or_ast_node, selections_result
219
309
  )
220
- finished_jobs += 1
221
- if target_result && finished_jobs == enqueued_jobs
222
- selections_result.merge_into(target_result)
223
- end
224
310
  @dataloader.clear_cache
225
311
  }
226
312
  else
@@ -228,13 +314,12 @@ module GraphQL
228
314
  evaluate_selection(
229
315
  result_name, field_ast_nodes_or_ast_node, selections_result
230
316
  )
231
- finished_jobs += 1
232
- if target_result && finished_jobs == enqueued_jobs
233
- selections_result.merge_into(target_result)
234
- end
235
317
  }
236
318
  end
237
319
  end
320
+ if target_result
321
+ selections_result.merge_into(target_result)
322
+ end
238
323
  selections_result
239
324
  end
240
325
  end
@@ -279,6 +364,10 @@ module GraphQL
279
364
  else
280
365
  @query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
281
366
  runtime_state = get_current_runtime_state # This might be in a different fiber
367
+ runtime_state.current_field = field_defn
368
+ runtime_state.current_arguments = resolved_arguments
369
+ runtime_state.current_result_name = result_name
370
+ runtime_state.current_result = selections_result
282
371
  evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state)
283
372
  end
284
373
  end
@@ -287,6 +376,8 @@ module GraphQL
287
376
  def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) # rubocop:disable Metrics/ParameterLists
288
377
  after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_arguments, runtime_state|
289
378
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
379
+ next if selection_result.collect_result(result_name, resolved_arguments)
380
+
290
381
  return_type_non_null = field_defn.type.non_null?
291
382
  continue_value(resolved_arguments, field_defn, return_type_non_null, ast_node, result_name, selection_result)
292
383
  next
@@ -360,7 +451,7 @@ module GraphQL
360
451
  }
361
452
  end
362
453
 
363
- field_result = call_method_on_directives(:resolve, object, directives) do
454
+ call_method_on_directives(:resolve, object, directives) do
364
455
  if !directives.empty?
365
456
  # This might be executed in a different context; reset this info
366
457
  runtime_state = get_current_runtime_state
@@ -371,6 +462,7 @@ module GraphQL
371
462
  end
372
463
  # Actually call the field resolver and capture the result
373
464
  app_result = begin
465
+ @current_trace.begin_execute_field(field_defn, object, kwarg_arguments, query)
374
466
  @current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
375
467
  field_defn.resolve(object, kwarg_arguments, context)
376
468
  end
@@ -383,7 +475,10 @@ module GraphQL
383
475
  ex_err
384
476
  end
385
477
  end
478
+ @current_trace.end_execute_field(field_defn, object, kwarg_arguments, query, app_result)
386
479
  after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_result, runtime_state|
480
+ next if selection_result.collect_result(result_name, inner_result)
481
+
387
482
  owner_type = selection_result.graphql_result_type
388
483
  return_type = field_defn.type
389
484
  continue_value = continue_value(inner_result, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
@@ -391,6 +486,8 @@ module GraphQL
391
486
  was_scoped = runtime_state.was_authorized_by_scope_items
392
487
  runtime_state.was_authorized_by_scope_items = nil
393
488
  continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result, was_scoped, runtime_state)
489
+ else
490
+ nil
394
491
  end
395
492
  end
396
493
  end
@@ -398,7 +495,7 @@ module GraphQL
398
495
  # all of its child fields before moving on to the next root mutation field.
399
496
  # (Subselections of this mutation will still be resolved level-by-level.)
400
497
  if selection_result.graphql_is_eager
401
- Interpreter::Resolve.resolve_all([field_result], @dataloader)
498
+ @dataloader.run
402
499
  end
403
500
  end
404
501
 
@@ -456,16 +553,17 @@ module GraphQL
456
553
  path
457
554
  end
458
555
 
459
- HALT = Object.new
556
+ HALT = Object.new.freeze
460
557
  def continue_value(value, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
461
558
  case value
462
559
  when nil
463
560
  if is_non_null
464
561
  set_result(selection_result, result_name, nil, false, is_non_null) do
465
562
  # When this comes from a list item, use the parent object:
466
- parent_type = selection_result.is_a?(GraphQLResultArray) ? selection_result.graphql_parent.graphql_result_type : selection_result.graphql_result_type
563
+ is_from_array = selection_result.is_a?(GraphQLResultArray)
564
+ parent_type = is_from_array ? selection_result.graphql_parent.graphql_result_type : selection_result.graphql_result_type
467
565
  # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
468
- err = parent_type::InvalidNullError.new(parent_type, field, value)
566
+ err = parent_type::InvalidNullError.new(parent_type, field, ast_node, is_from_array: is_from_array)
469
567
  schema.type_error(err, context)
470
568
  end
471
569
  else
@@ -573,13 +671,29 @@ module GraphQL
573
671
  when "SCALAR", "ENUM"
574
672
  r = begin
575
673
  current_type.coerce_result(value, context)
674
+ rescue GraphQL::ExecutionError => ex_err
675
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
576
676
  rescue StandardError => err
577
- schema.handle_or_reraise(context, err)
677
+ begin
678
+ query.handle_or_reraise(err)
679
+ rescue GraphQL::ExecutionError => ex_err
680
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
681
+ end
578
682
  end
579
683
  set_result(selection_result, result_name, r, false, is_non_null)
580
684
  r
581
685
  when "UNION", "INTERFACE"
582
- resolved_type_or_lazy = resolve_type(current_type, value)
686
+ resolved_type_or_lazy = begin
687
+ resolve_type(current_type, value)
688
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
689
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
690
+ rescue StandardError => err
691
+ begin
692
+ query.handle_or_reraise(err)
693
+ rescue GraphQL::ExecutionError => ex_err
694
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
695
+ end
696
+ end
583
697
  after_lazy(resolved_type_or_lazy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_type_result, runtime_state|
584
698
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
585
699
  resolved_type, resolved_value = resolved_type_result
@@ -609,11 +723,13 @@ module GraphQL
609
723
  after_lazy(object_proxy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_object, runtime_state|
610
724
  continue_value = continue_value(inner_object, field, is_non_null, ast_node, result_name, selection_result)
611
725
  if HALT != continue_value
612
- response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false)
726
+ response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
613
727
  set_result(selection_result, result_name, response_hash, true, is_non_null)
614
- each_gathered_selections(response_hash) do |selections, is_selection_array|
728
+ each_gathered_selections(response_hash) do |selections, is_selection_array, ordered_result_keys|
729
+ response_hash.ordered_result_keys ||= ordered_result_keys
615
730
  if is_selection_array
616
- this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false)
731
+ this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false, ast_node, arguments, field)
732
+ this_result.ordered_result_keys = ordered_result_keys
617
733
  final_result = response_hash
618
734
  else
619
735
  this_result = response_hash
@@ -634,35 +750,43 @@ module GraphQL
634
750
  # This is true for objects, unions, and interfaces
635
751
  use_dataloader_job = !inner_type.unwrap.kind.input?
636
752
  inner_type_non_null = inner_type.non_null?
637
- response_list = GraphQLResultArray.new(result_name, current_type, owner_object, selection_result, is_non_null, next_selections, false)
753
+ response_list = GraphQLResultArray.new(result_name, current_type, owner_object, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
638
754
  set_result(selection_result, result_name, response_list, true, is_non_null)
639
755
  idx = nil
640
756
  list_value = begin
641
- value.each do |inner_value|
642
- idx ||= 0
643
- this_idx = idx
644
- idx += 1
645
- if use_dataloader_job
646
- @dataloader.append_job do
757
+ begin
758
+ value.each do |inner_value|
759
+ idx ||= 0
760
+ this_idx = idx
761
+ idx += 1
762
+ if use_dataloader_job
763
+ @dataloader.append_job do
764
+ resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state)
765
+ end
766
+ else
647
767
  resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state)
648
768
  end
649
- else
650
- resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state)
651
769
  end
652
- end
653
770
 
654
- response_list
655
- rescue NoMethodError => err
656
- # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
657
- if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
658
- # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
659
- raise ListResultFailedError.new(value: value, field: field, path: current_path)
660
- else
661
- # This was some other NoMethodError -- let it bubble to reveal the real error.
662
- raise
771
+ response_list
772
+ rescue NoMethodError => err
773
+ # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
774
+ if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
775
+ # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
776
+ raise ListResultFailedError.new(value: value, field: field, path: current_path)
777
+ else
778
+ # This was some other NoMethodError -- let it bubble to reveal the real error.
779
+ raise
780
+ end
781
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
782
+ ex_err
783
+ rescue StandardError => err
784
+ begin
785
+ query.handle_or_reraise(err)
786
+ rescue GraphQL::ExecutionError => ex_err
787
+ ex_err
788
+ end
663
789
  end
664
- rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
665
- ex_err
666
790
  rescue StandardError => err
667
791
  begin
668
792
  query.handle_or_reraise(err)
@@ -704,6 +828,13 @@ module GraphQL
704
828
  else
705
829
  dir_defn = @schema_directives.fetch(dir_node.name)
706
830
  raw_dir_args = arguments(nil, dir_defn, dir_node)
831
+ if !raw_dir_args.is_a?(GraphQL::ExecutionError)
832
+ begin
833
+ dir_defn.validate!(raw_dir_args, context)
834
+ rescue GraphQL::ExecutionError => err
835
+ raw_dir_args = err
836
+ end
837
+ end
707
838
  dir_args = continue_value(
708
839
  raw_dir_args, # value
709
840
  nil, # field
@@ -759,7 +890,6 @@ module GraphQL
759
890
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
760
891
  def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, runtime_state:, trace: true, &block)
761
892
  if lazy?(lazy_obj)
762
- orig_result = result
763
893
  was_authorized_by_scope_items = runtime_state.was_authorized_by_scope_items
764
894
  lazy = GraphQL::Execution::Lazy.new(field: field) do
765
895
  # This block might be called in a new fiber;
@@ -769,12 +899,14 @@ module GraphQL
769
899
  runtime_state.current_field = field
770
900
  runtime_state.current_arguments = arguments
771
901
  runtime_state.current_result_name = result_name
772
- runtime_state.current_result = orig_result
902
+ runtime_state.current_result = result
773
903
  runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items
774
904
  # Wrap the execution of _this_ method with tracing,
775
905
  # but don't wrap the continuation below
906
+ sync_result = nil
776
907
  inner_obj = begin
777
- if trace
908
+ sync_result = if trace
909
+ @current_trace.begin_execute_field(field, owner_object, arguments, query)
778
910
  @current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
779
911
  schema.sync_lazy(lazy_obj)
780
912
  end
@@ -789,6 +921,10 @@ module GraphQL
789
921
  rescue GraphQL::ExecutionError => ex_err
790
922
  ex_err
791
923
  end
924
+ ensure
925
+ if trace
926
+ @current_trace.end_execute_field(field, owner_object, arguments, query, sync_result)
927
+ end
792
928
  end
793
929
  yield(inner_obj, runtime_state)
794
930
  end
@@ -797,12 +933,7 @@ module GraphQL
797
933
  lazy.value
798
934
  else
799
935
  set_result(result, result_name, lazy, false, false) # is_non_null is irrelevant here
800
- current_depth = 0
801
- while result
802
- current_depth += 1
803
- result = result.graphql_parent
804
- end
805
- @lazies_at_depth[current_depth] << lazy
936
+ @dataloader.lazy_at_depth(result.depth, lazy)
806
937
  lazy
807
938
  end
808
939
  else
@@ -832,14 +963,19 @@ module GraphQL
832
963
  end
833
964
 
834
965
  def resolve_type(type, value)
966
+ @current_trace.begin_resolve_type(type, value, context)
835
967
  resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do
836
968
  query.resolve_type(type, value)
837
969
  end
970
+ @current_trace.end_resolve_type(type, value, context, resolved_type)
838
971
 
839
972
  if lazy?(resolved_type)
840
973
  GraphQL::Execution::Lazy.new do
974
+ @current_trace.begin_resolve_type(type, value, context)
841
975
  @current_trace.resolve_type_lazy(query: query, type: type, object: value) do
842
- schema.sync_lazy(resolved_type)
976
+ rt = schema.sync_lazy(resolved_type)
977
+ @current_trace.end_resolve_type(type, value, context, rt)
978
+ rt
843
979
  end
844
980
  end
845
981
  else
@@ -23,28 +23,34 @@ module GraphQL
23
23
  # @return [Array<GraphQL::Query::Result>] One result per query
24
24
  def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
25
25
  queries = query_options.map do |opts|
26
- case opts
26
+ query = case opts
27
27
  when Hash
28
28
  schema.query_class.new(schema, nil, **opts)
29
- when GraphQL::Query
29
+ when GraphQL::Query, GraphQL::Query::Partial
30
30
  opts
31
31
  else
32
32
  raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
33
33
  end
34
+ query
34
35
  end
35
36
 
37
+ return GraphQL::EmptyObjects::EMPTY_ARRAY if queries.empty?
38
+
36
39
  multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
40
+ trace = multiplex.current_trace
37
41
  Fiber[:__graphql_current_multiplex] = multiplex
38
- multiplex.current_trace.execute_multiplex(multiplex: multiplex) do
42
+ trace.execute_multiplex(multiplex: multiplex) do
39
43
  schema = multiplex.schema
40
44
  queries = multiplex.queries
41
- lazies_at_depth = Hash.new { |h, k| h[k] = [] }
42
45
  multiplex_analyzers = schema.multiplex_analyzers
43
46
  if multiplex.max_complexity
44
47
  multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
45
48
  end
46
49
 
50
+ trace.begin_analyze_multiplex(multiplex, multiplex_analyzers)
47
51
  schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
52
+ trace.end_analyze_multiplex(multiplex, multiplex_analyzers)
53
+
48
54
  begin
49
55
  # Since this is basically the batching context,
50
56
  # share it for a whole multiplex
@@ -53,7 +59,9 @@ module GraphQL
53
59
  results = []
54
60
  queries.each_with_index do |query, idx|
55
61
  if query.subscription? && !query.subscription_update?
56
- query.context.namespace(:subscriptions)[:events] = []
62
+ subs_namespace = query.context.namespace(:subscriptions)
63
+ subs_namespace[:events] = []
64
+ subs_namespace[:subscriptions] = {}
57
65
  end
58
66
  multiplex.dataloader.append_job {
59
67
  operation = query.selected_operation
@@ -64,7 +72,7 @@ module GraphQL
64
72
  # Although queries in a multiplex _share_ an Interpreter instance,
65
73
  # they also have another item of state, which is private to that query
66
74
  # in particular, assign it here:
67
- runtime = Runtime.new(query: query, lazies_at_depth: lazies_at_depth)
75
+ runtime = Runtime.new(query: query)
68
76
  query.context.namespace(:interpreter_runtime)[:runtime] = runtime
69
77
 
70
78
  query.current_trace.execute_query(query: query) do
@@ -72,30 +80,13 @@ module GraphQL
72
80
  end
73
81
  rescue GraphQL::ExecutionError => err
74
82
  query.context.errors << err
75
- NO_OPERATION
76
83
  end
77
84
  end
78
85
  results[idx] = result
79
86
  }
80
87
  end
81
88
 
82
- multiplex.dataloader.run
83
-
84
- # Then, work through lazy results in a breadth-first way
85
- multiplex.dataloader.append_job {
86
- query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil
87
- queries = multiplex ? multiplex.queries : [query]
88
- final_values = queries.map do |query|
89
- runtime = query.context.namespace(:interpreter_runtime)[:runtime]
90
- # it might not be present if the query has an error
91
- runtime ? runtime.final_result : nil
92
- end
93
- final_values.compact!
94
- multiplex.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
95
- Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader)
96
- end
97
- }
98
- multiplex.dataloader.run
89
+ multiplex.dataloader.run(trace_query_lazy: multiplex)
99
90
 
100
91
  # Then, find all errors and assign the result to the query object
101
92
  results.each_with_index do |data_result, idx|