graphql 1.13.24 → 2.5.11

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 (427) 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 +50 -1
  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/orm_mutations_base.rb +1 -1
  9. data/lib/generators/graphql/relay.rb +21 -18
  10. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  11. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  12. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  13. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  14. data/lib/generators/graphql/templates/base_field.erb +2 -0
  15. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  16. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  17. data/lib/generators/graphql/templates/base_object.erb +2 -0
  18. data/lib/generators/graphql/templates/base_resolver.erb +8 -0
  19. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  20. data/lib/generators/graphql/templates/base_union.erb +2 -0
  21. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  22. data/lib/generators/graphql/templates/loader.erb +2 -0
  23. data/lib/generators/graphql/templates/mutation.erb +2 -0
  24. data/lib/generators/graphql/templates/node_type.erb +2 -0
  25. data/lib/generators/graphql/templates/query_type.erb +2 -0
  26. data/lib/generators/graphql/templates/schema.erb +8 -0
  27. data/lib/generators/graphql/type_generator.rb +1 -1
  28. data/lib/graphql/analysis/analyzer.rb +90 -0
  29. data/lib/graphql/analysis/field_usage.rb +65 -28
  30. data/lib/graphql/analysis/max_query_complexity.rb +11 -17
  31. data/lib/graphql/analysis/max_query_depth.rb +13 -19
  32. data/lib/graphql/analysis/query_complexity.rb +236 -61
  33. data/lib/graphql/analysis/query_depth.rb +38 -23
  34. data/lib/graphql/analysis/visitor.rb +280 -0
  35. data/lib/graphql/analysis.rb +93 -6
  36. data/lib/graphql/autoload.rb +38 -0
  37. data/lib/graphql/backtrace/table.rb +118 -73
  38. data/lib/graphql/backtrace.rb +2 -25
  39. data/lib/graphql/coercion_error.rb +1 -9
  40. data/lib/graphql/current.rb +57 -0
  41. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  42. data/lib/graphql/dashboard/installable.rb +22 -0
  43. data/lib/graphql/dashboard/limiters.rb +93 -0
  44. data/lib/graphql/dashboard/operation_store.rb +199 -0
  45. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  46. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  47. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  48. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  49. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  50. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  51. data/lib/graphql/dashboard/statics/icon.png +0 -0
  52. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  53. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  54. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  55. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  56. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  57. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  58. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  59. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  60. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  61. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  62. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  63. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  64. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  65. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  66. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  67. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  68. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  69. data/lib/graphql/dashboard.rb +158 -0
  70. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  71. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  72. data/lib/graphql/dataloader/async_dataloader.rb +101 -0
  73. data/lib/graphql/dataloader/null_dataloader.rb +11 -2
  74. data/lib/graphql/dataloader/request.rb +5 -0
  75. data/lib/graphql/dataloader/source.rb +103 -47
  76. data/lib/graphql/dataloader.rb +174 -148
  77. data/lib/graphql/dig.rb +3 -2
  78. data/lib/graphql/duration_encoding_error.rb +16 -0
  79. data/lib/graphql/execution/errors.rb +12 -82
  80. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  81. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  82. data/lib/graphql/execution/interpreter/arguments_cache.rb +30 -35
  83. data/lib/graphql/execution/interpreter/resolve.rb +32 -2
  84. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +215 -0
  85. data/lib/graphql/execution/interpreter/runtime.rb +525 -502
  86. data/lib/graphql/execution/interpreter.rb +127 -81
  87. data/lib/graphql/execution/lazy.rb +7 -21
  88. data/lib/graphql/execution/lookahead.rb +133 -55
  89. data/lib/graphql/execution/multiplex.rb +6 -176
  90. data/lib/graphql/execution.rb +11 -4
  91. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  92. data/lib/graphql/introspection/directive_type.rb +1 -1
  93. data/lib/graphql/introspection/dynamic_fields.rb +3 -8
  94. data/lib/graphql/introspection/entry_points.rb +10 -17
  95. data/lib/graphql/introspection/field_type.rb +1 -1
  96. data/lib/graphql/introspection/schema_type.rb +8 -11
  97. data/lib/graphql/introspection/type_type.rb +13 -6
  98. data/lib/graphql/introspection.rb +4 -3
  99. data/lib/graphql/invalid_name_error.rb +1 -1
  100. data/lib/graphql/invalid_null_error.rb +20 -17
  101. data/lib/graphql/language/block_string.rb +34 -18
  102. data/lib/graphql/language/cache.rb +13 -0
  103. data/lib/graphql/language/comment.rb +18 -0
  104. data/lib/graphql/language/definition_slice.rb +1 -1
  105. data/lib/graphql/language/document_from_schema_definition.rb +114 -80
  106. data/lib/graphql/language/lexer.rb +375 -1489
  107. data/lib/graphql/language/nodes.rb +189 -104
  108. data/lib/graphql/language/parser.rb +807 -1941
  109. data/lib/graphql/language/printer.rb +366 -163
  110. data/lib/graphql/language/sanitized_printer.rb +21 -23
  111. data/lib/graphql/language/static_visitor.rb +171 -0
  112. data/lib/graphql/language/visitor.rb +189 -138
  113. data/lib/graphql/language.rb +62 -1
  114. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  115. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  116. data/lib/graphql/pagination/array_connection.rb +8 -6
  117. data/lib/graphql/pagination/connection.rb +61 -7
  118. data/lib/graphql/pagination/connections.rb +3 -28
  119. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  120. data/lib/graphql/pagination/relation_connection.rb +2 -0
  121. data/lib/graphql/query/context/scoped_context.rb +101 -0
  122. data/lib/graphql/query/context.rb +131 -225
  123. data/lib/graphql/query/input_validation_result.rb +1 -1
  124. data/lib/graphql/query/null_context.rb +11 -33
  125. data/lib/graphql/query/partial.rb +179 -0
  126. data/lib/graphql/query/validation_pipeline.rb +14 -37
  127. data/lib/graphql/query/variable_validation_error.rb +1 -1
  128. data/lib/graphql/query/variables.rb +6 -19
  129. data/lib/graphql/query.rb +162 -98
  130. data/lib/graphql/railtie.rb +15 -109
  131. data/lib/graphql/rake_task/validate.rb +1 -1
  132. data/lib/graphql/rake_task.rb +30 -11
  133. data/lib/graphql/relay/range_add.rb +9 -20
  134. data/lib/graphql/relay.rb +0 -15
  135. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  136. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  137. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  138. data/lib/graphql/rubocop.rb +2 -0
  139. data/lib/graphql/schema/addition.rb +70 -33
  140. data/lib/graphql/schema/always_visible.rb +15 -0
  141. data/lib/graphql/schema/argument.rb +104 -59
  142. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  143. data/lib/graphql/schema/build_from_definition.rb +154 -74
  144. data/lib/graphql/schema/directive/flagged.rb +4 -2
  145. data/lib/graphql/schema/directive/one_of.rb +24 -0
  146. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  147. data/lib/graphql/schema/directive/transform.rb +1 -1
  148. data/lib/graphql/schema/directive.rb +47 -24
  149. data/lib/graphql/schema/enum.rb +137 -65
  150. data/lib/graphql/schema/enum_value.rb +11 -26
  151. data/lib/graphql/schema/field/connection_extension.rb +6 -16
  152. data/lib/graphql/schema/field/scope_extension.rb +8 -1
  153. data/lib/graphql/schema/field.rb +399 -404
  154. data/lib/graphql/schema/field_extension.rb +2 -5
  155. data/lib/graphql/schema/find_inherited_value.rb +2 -7
  156. data/lib/graphql/schema/has_single_input_argument.rb +160 -0
  157. data/lib/graphql/schema/input_object.rb +144 -99
  158. data/lib/graphql/schema/interface.rb +34 -51
  159. data/lib/graphql/schema/introspection_system.rb +12 -26
  160. data/lib/graphql/schema/late_bound_type.rb +12 -2
  161. data/lib/graphql/schema/list.rb +3 -9
  162. data/lib/graphql/schema/loader.rb +4 -6
  163. data/lib/graphql/schema/member/base_dsl_methods.rb +32 -18
  164. data/lib/graphql/schema/member/build_type.rb +15 -9
  165. data/lib/graphql/schema/member/has_arguments.rb +192 -96
  166. data/lib/graphql/schema/member/has_ast_node.rb +12 -0
  167. data/lib/graphql/schema/member/has_dataloader.rb +62 -0
  168. data/lib/graphql/schema/member/has_deprecation_reason.rb +18 -4
  169. data/lib/graphql/schema/member/has_directives.rb +81 -61
  170. data/lib/graphql/schema/member/has_fields.rb +119 -39
  171. data/lib/graphql/schema/member/has_interfaces.rb +66 -23
  172. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  173. data/lib/graphql/schema/member/has_validators.rb +32 -6
  174. data/lib/graphql/schema/member/relay_shortcuts.rb +47 -2
  175. data/lib/graphql/schema/member/scoped.rb +19 -0
  176. data/lib/graphql/schema/member/type_system_helpers.rb +32 -2
  177. data/lib/graphql/schema/member/validates_input.rb +4 -4
  178. data/lib/graphql/schema/member.rb +1 -6
  179. data/lib/graphql/schema/mutation.rb +7 -9
  180. data/lib/graphql/schema/non_null.rb +1 -7
  181. data/lib/graphql/schema/object.rb +42 -49
  182. data/lib/graphql/schema/printer.rb +12 -8
  183. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  184. data/lib/graphql/schema/relay_classic_mutation.rb +12 -124
  185. data/lib/graphql/schema/resolver/has_payload_type.rb +20 -10
  186. data/lib/graphql/schema/resolver.rb +96 -81
  187. data/lib/graphql/schema/scalar.rb +10 -30
  188. data/lib/graphql/schema/subscription.rb +60 -14
  189. data/lib/graphql/schema/timeout.rb +44 -31
  190. data/lib/graphql/schema/type_expression.rb +2 -2
  191. data/lib/graphql/schema/type_membership.rb +3 -0
  192. data/lib/graphql/schema/union.rb +12 -19
  193. data/lib/graphql/schema/unique_within_type.rb +1 -1
  194. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  195. data/lib/graphql/schema/validator/required_validator.rb +60 -10
  196. data/lib/graphql/schema/validator.rb +5 -3
  197. data/lib/graphql/schema/visibility/migration.rb +188 -0
  198. data/lib/graphql/schema/visibility/profile.rb +445 -0
  199. data/lib/graphql/schema/visibility/visit.rb +190 -0
  200. data/lib/graphql/schema/visibility.rb +311 -0
  201. data/lib/graphql/schema/warden.rb +318 -94
  202. data/lib/graphql/schema/wrapper.rb +0 -5
  203. data/lib/graphql/schema.rb +1148 -1085
  204. data/lib/graphql/static_validation/all_rules.rb +4 -3
  205. data/lib/graphql/static_validation/base_visitor.rb +11 -27
  206. data/lib/graphql/static_validation/definition_dependencies.rb +7 -1
  207. data/lib/graphql/static_validation/error.rb +2 -2
  208. data/lib/graphql/static_validation/literal_validator.rb +24 -7
  209. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  210. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  211. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  212. data/lib/graphql/static_validation/rules/directives_are_defined.rb +13 -7
  213. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +14 -12
  214. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  215. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +48 -6
  216. data/lib/graphql/static_validation/rules/fields_will_merge.rb +90 -27
  217. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  218. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  219. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  220. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  221. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  222. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  223. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  224. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +66 -0
  225. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +29 -0
  226. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  227. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +5 -5
  228. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  229. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  230. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +19 -9
  231. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  232. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  233. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  234. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  235. data/lib/graphql/static_validation/validation_context.rb +21 -5
  236. data/lib/graphql/static_validation/validator.rb +12 -26
  237. data/lib/graphql/static_validation.rb +0 -3
  238. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +14 -6
  239. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  240. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +40 -1
  241. data/lib/graphql/subscriptions/event.rb +24 -12
  242. data/lib/graphql/subscriptions/serialize.rb +3 -1
  243. data/lib/graphql/subscriptions.rb +48 -32
  244. data/lib/graphql/testing/helpers.rb +158 -0
  245. data/lib/graphql/testing.rb +2 -0
  246. data/lib/graphql/tracing/active_support_notifications_trace.rb +27 -0
  247. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  248. data/lib/graphql/tracing/appoptics_trace.rb +259 -0
  249. data/lib/graphql/tracing/appoptics_tracing.rb +9 -2
  250. data/lib/graphql/tracing/appsignal_trace.rb +54 -0
  251. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  252. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  253. data/lib/graphql/tracing/data_dog_trace.rb +71 -0
  254. data/lib/graphql/tracing/data_dog_tracing.rb +3 -0
  255. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  256. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  257. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  258. data/lib/graphql/{execution/instrumentation.rb → tracing/legacy_hooks_trace.rb} +11 -28
  259. data/lib/graphql/tracing/legacy_trace.rb +12 -0
  260. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  261. data/lib/graphql/tracing/new_relic_trace.rb +68 -0
  262. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  263. data/lib/graphql/tracing/notifications_trace.rb +195 -0
  264. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  265. data/lib/graphql/tracing/null_trace.rb +9 -0
  266. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  267. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  268. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  269. data/lib/graphql/tracing/platform_trace.rb +123 -0
  270. data/lib/graphql/tracing/platform_tracing.rb +28 -41
  271. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +6 -2
  272. data/lib/graphql/tracing/prometheus_trace.rb +93 -0
  273. data/lib/graphql/tracing/prometheus_tracing.rb +5 -3
  274. data/lib/graphql/tracing/scout_trace.rb +49 -0
  275. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  276. data/lib/graphql/tracing/sentry_trace.rb +80 -0
  277. data/lib/graphql/tracing/statsd_trace.rb +48 -0
  278. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  279. data/lib/graphql/tracing/trace.rb +186 -0
  280. data/lib/graphql/tracing.rb +32 -52
  281. data/lib/graphql/type_kinds.rb +8 -4
  282. data/lib/graphql/types/iso_8601_date.rb +4 -1
  283. data/lib/graphql/types/iso_8601_date_time.rb +4 -0
  284. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  285. data/lib/graphql/types/relay/base_connection.rb +16 -6
  286. data/lib/graphql/types/relay/connection_behaviors.rb +65 -23
  287. data/lib/graphql/types/relay/edge_behaviors.rb +33 -5
  288. data/lib/graphql/types/relay/node_behaviors.rb +12 -2
  289. data/lib/graphql/types/relay/page_info_behaviors.rb +11 -2
  290. data/lib/graphql/types/relay.rb +0 -3
  291. data/lib/graphql/types/string.rb +1 -1
  292. data/lib/graphql/types.rb +18 -10
  293. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  294. data/lib/graphql/version.rb +1 -1
  295. data/lib/graphql.rb +76 -123
  296. data/readme.md +13 -3
  297. metadata +225 -142
  298. data/lib/graphql/analysis/analyze_query.rb +0 -98
  299. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  300. data/lib/graphql/analysis/ast/field_usage.rb +0 -55
  301. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -23
  302. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  303. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  304. data/lib/graphql/analysis/ast/query_depth.rb +0 -56
  305. data/lib/graphql/analysis/ast/visitor.rb +0 -269
  306. data/lib/graphql/analysis/ast.rb +0 -91
  307. data/lib/graphql/analysis/reducer_state.rb +0 -48
  308. data/lib/graphql/argument.rb +0 -131
  309. data/lib/graphql/authorization.rb +0 -82
  310. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  311. data/lib/graphql/backtrace/legacy_tracer.rb +0 -56
  312. data/lib/graphql/backtrace/tracer.rb +0 -81
  313. data/lib/graphql/backwards_compatibility.rb +0 -61
  314. data/lib/graphql/base_type.rb +0 -232
  315. data/lib/graphql/boolean_type.rb +0 -2
  316. data/lib/graphql/compatibility/execution_specification/counter_schema.rb +0 -53
  317. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +0 -200
  318. data/lib/graphql/compatibility/execution_specification.rb +0 -436
  319. data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +0 -111
  320. data/lib/graphql/compatibility/lazy_execution_specification.rb +0 -215
  321. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +0 -87
  322. data/lib/graphql/compatibility/query_parser_specification/query_assertions.rb +0 -79
  323. data/lib/graphql/compatibility/query_parser_specification.rb +0 -266
  324. data/lib/graphql/compatibility/schema_parser_specification.rb +0 -682
  325. data/lib/graphql/compatibility.rb +0 -5
  326. data/lib/graphql/define/assign_argument.rb +0 -12
  327. data/lib/graphql/define/assign_connection.rb +0 -13
  328. data/lib/graphql/define/assign_enum_value.rb +0 -18
  329. data/lib/graphql/define/assign_global_id_field.rb +0 -11
  330. data/lib/graphql/define/assign_mutation_function.rb +0 -34
  331. data/lib/graphql/define/assign_object_field.rb +0 -42
  332. data/lib/graphql/define/defined_object_proxy.rb +0 -53
  333. data/lib/graphql/define/instance_definable.rb +0 -255
  334. data/lib/graphql/define/no_definition_error.rb +0 -7
  335. data/lib/graphql/define/non_null_with_bang.rb +0 -16
  336. data/lib/graphql/define/type_definer.rb +0 -31
  337. data/lib/graphql/define.rb +0 -31
  338. data/lib/graphql/deprecated_dsl.rb +0 -55
  339. data/lib/graphql/deprecation.rb +0 -9
  340. data/lib/graphql/directive/deprecated_directive.rb +0 -2
  341. data/lib/graphql/directive/include_directive.rb +0 -2
  342. data/lib/graphql/directive/skip_directive.rb +0 -2
  343. data/lib/graphql/directive.rb +0 -107
  344. data/lib/graphql/enum_type.rb +0 -133
  345. data/lib/graphql/execution/execute.rb +0 -333
  346. data/lib/graphql/execution/flatten.rb +0 -40
  347. data/lib/graphql/execution/lazy/resolve.rb +0 -91
  348. data/lib/graphql/execution/typecast.rb +0 -50
  349. data/lib/graphql/field/resolve.rb +0 -59
  350. data/lib/graphql/field.rb +0 -226
  351. data/lib/graphql/filter.rb +0 -53
  352. data/lib/graphql/float_type.rb +0 -2
  353. data/lib/graphql/function.rb +0 -128
  354. data/lib/graphql/id_type.rb +0 -2
  355. data/lib/graphql/input_object_type.rb +0 -138
  356. data/lib/graphql/int_type.rb +0 -2
  357. data/lib/graphql/interface_type.rb +0 -72
  358. data/lib/graphql/internal_representation/document.rb +0 -27
  359. data/lib/graphql/internal_representation/node.rb +0 -206
  360. data/lib/graphql/internal_representation/print.rb +0 -51
  361. data/lib/graphql/internal_representation/rewrite.rb +0 -184
  362. data/lib/graphql/internal_representation/scope.rb +0 -88
  363. data/lib/graphql/internal_representation/visit.rb +0 -36
  364. data/lib/graphql/internal_representation.rb +0 -7
  365. data/lib/graphql/language/lexer.rl +0 -260
  366. data/lib/graphql/language/parser.y +0 -550
  367. data/lib/graphql/language/token.rb +0 -34
  368. data/lib/graphql/list_type.rb +0 -80
  369. data/lib/graphql/non_null_type.rb +0 -71
  370. data/lib/graphql/object_type.rb +0 -130
  371. data/lib/graphql/query/arguments.rb +0 -189
  372. data/lib/graphql/query/arguments_cache.rb +0 -24
  373. data/lib/graphql/query/executor.rb +0 -52
  374. data/lib/graphql/query/literal_input.rb +0 -136
  375. data/lib/graphql/query/serial_execution/field_resolution.rb +0 -92
  376. data/lib/graphql/query/serial_execution/operation_resolution.rb +0 -19
  377. data/lib/graphql/query/serial_execution/selection_resolution.rb +0 -23
  378. data/lib/graphql/query/serial_execution/value_resolution.rb +0 -87
  379. data/lib/graphql/query/serial_execution.rb +0 -40
  380. data/lib/graphql/relay/array_connection.rb +0 -83
  381. data/lib/graphql/relay/base_connection.rb +0 -189
  382. data/lib/graphql/relay/connection_instrumentation.rb +0 -54
  383. data/lib/graphql/relay/connection_resolve.rb +0 -43
  384. data/lib/graphql/relay/connection_type.rb +0 -54
  385. data/lib/graphql/relay/edge.rb +0 -27
  386. data/lib/graphql/relay/edge_type.rb +0 -19
  387. data/lib/graphql/relay/edges_instrumentation.rb +0 -39
  388. data/lib/graphql/relay/global_id_resolve.rb +0 -17
  389. data/lib/graphql/relay/mongo_relation_connection.rb +0 -50
  390. data/lib/graphql/relay/mutation/instrumentation.rb +0 -23
  391. data/lib/graphql/relay/mutation/resolve.rb +0 -56
  392. data/lib/graphql/relay/mutation/result.rb +0 -38
  393. data/lib/graphql/relay/mutation.rb +0 -106
  394. data/lib/graphql/relay/node.rb +0 -39
  395. data/lib/graphql/relay/page_info.rb +0 -7
  396. data/lib/graphql/relay/relation_connection.rb +0 -188
  397. data/lib/graphql/relay/type_extensions.rb +0 -32
  398. data/lib/graphql/scalar_type.rb +0 -91
  399. data/lib/graphql/schema/base_64_bp.rb +0 -26
  400. data/lib/graphql/schema/catchall_middleware.rb +0 -35
  401. data/lib/graphql/schema/default_parse_error.rb +0 -10
  402. data/lib/graphql/schema/default_type_error.rb +0 -17
  403. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  404. data/lib/graphql/schema/member/accepts_definition.rb +0 -164
  405. data/lib/graphql/schema/member/cached_graphql_definition.rb +0 -58
  406. data/lib/graphql/schema/member/instrumentation.rb +0 -131
  407. data/lib/graphql/schema/middleware_chain.rb +0 -82
  408. data/lib/graphql/schema/null_mask.rb +0 -11
  409. data/lib/graphql/schema/possible_types.rb +0 -44
  410. data/lib/graphql/schema/rescue_middleware.rb +0 -60
  411. data/lib/graphql/schema/timeout_middleware.rb +0 -88
  412. data/lib/graphql/schema/traversal.rb +0 -228
  413. data/lib/graphql/schema/validation.rb +0 -313
  414. data/lib/graphql/static_validation/default_visitor.rb +0 -15
  415. data/lib/graphql/static_validation/no_validate_visitor.rb +0 -10
  416. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
  417. data/lib/graphql/static_validation/type_stack.rb +0 -216
  418. data/lib/graphql/string_type.rb +0 -2
  419. data/lib/graphql/subscriptions/instrumentation.rb +0 -79
  420. data/lib/graphql/subscriptions/subscription_root.rb +0 -76
  421. data/lib/graphql/tracing/skylight_tracing.rb +0 -70
  422. data/lib/graphql/types/relay/default_relay.rb +0 -31
  423. data/lib/graphql/types/relay/node_field.rb +0 -24
  424. data/lib/graphql/types/relay/nodes_field.rb +0 -43
  425. data/lib/graphql/union_type.rb +0 -115
  426. data/lib/graphql/upgrader/member.rb +0 -937
  427. data/lib/graphql/upgrader/schema.rb +0 -38
@@ -7,9 +7,9 @@ module GraphQL
7
7
  # @api private
8
8
  def setup(dataloader)
9
9
  # These keys have been requested but haven't been fetched yet
10
- @pending_keys = []
10
+ @pending = {}
11
11
  # These keys have been passed to `fetch` but haven't been finished yet
12
- @fetching_keys = []
12
+ @fetching = {}
13
13
  # { key => result }
14
14
  @results = {}
15
15
  @dataloader = dataloader
@@ -18,42 +18,78 @@ module GraphQL
18
18
  attr_reader :dataloader
19
19
 
20
20
  # @return [Dataloader::Request] a pending request for a value from `key`. Call `.load` on that object to wait for the result.
21
- def request(key)
22
- if !@results.key?(key)
23
- @pending_keys << key
21
+ def request(value)
22
+ res_key = result_key_for(value)
23
+ if !@results.key?(res_key)
24
+ @pending[res_key] ||= normalize_fetch_key(value)
24
25
  end
25
- Dataloader::Request.new(self, key)
26
+ Dataloader::Request.new(self, value)
27
+ end
28
+
29
+ # Implement this method to return a stable identifier if different
30
+ # key objects should load the same data value.
31
+ #
32
+ # @param value [Object] A value passed to `.request` or `.load`, for which a value will be loaded
33
+ # @return [Object] The key for tracking this pending data
34
+ def result_key_for(value)
35
+ value
36
+ end
37
+
38
+ # Implement this method if varying values given to {load} (etc) should be consolidated
39
+ # or normalized before being handed off to your {fetch} implementation.
40
+ #
41
+ # This is different than {result_key_for} because _that_ method handles unification inside Dataloader's cache,
42
+ # but this method changes the value passed into {fetch}.
43
+ #
44
+ # @param value [Object] The value passed to {load}, {load_all}, {request}, or {request_all}
45
+ # @return [Object] The value given to {fetch}
46
+ def normalize_fetch_key(value)
47
+ value
26
48
  end
27
49
 
28
50
  # @return [Dataloader::Request] a pending request for a values from `keys`. Call `.load` on that object to wait for the results.
29
- def request_all(keys)
30
- pending_keys = keys.select { |k| !@results.key?(k) }
31
- @pending_keys.concat(pending_keys)
32
- Dataloader::RequestAll.new(self, keys)
51
+ def request_all(values)
52
+ values.each do |v|
53
+ res_key = result_key_for(v)
54
+ if !@results.key?(res_key)
55
+ @pending[res_key] ||= normalize_fetch_key(v)
56
+ end
57
+ end
58
+ Dataloader::RequestAll.new(self, values)
33
59
  end
34
60
 
35
- # @param key [Object] A loading key which will be passed to {#fetch} if it isn't already in the internal cache.
61
+ # @param value [Object] A loading value which will be passed to {#fetch} if it isn't already in the internal cache.
36
62
  # @return [Object] The result from {#fetch} for `key`. If `key` hasn't been loaded yet, the Fiber will yield until it's loaded.
37
- def load(key)
38
- if @results.key?(key)
39
- result_for(key)
63
+ def load(value)
64
+ result_key = result_key_for(value)
65
+ if @results.key?(result_key)
66
+ result_for(result_key)
40
67
  else
41
- @pending_keys << key
42
- sync
43
- result_for(key)
68
+ @pending[result_key] ||= normalize_fetch_key(value)
69
+ sync([result_key])
70
+ result_for(result_key)
44
71
  end
45
72
  end
46
73
 
47
- # @param keys [Array<Object>] Loading keys which will be passed to `#fetch` (or read from the internal cache).
74
+ # @param values [Array<Object>] Loading keys which will be passed to `#fetch` (or read from the internal cache).
48
75
  # @return [Object] The result from {#fetch} for `keys`. If `keys` haven't been loaded yet, the Fiber will yield until they're loaded.
49
- def load_all(keys)
50
- if keys.any? { |k| !@results.key?(k) }
51
- pending_keys = keys.select { |k| !@results.key?(k) }
52
- @pending_keys.concat(pending_keys)
53
- sync
76
+ def load_all(values)
77
+ result_keys = []
78
+ pending_keys = []
79
+ values.each { |v|
80
+ k = result_key_for(v)
81
+ result_keys << k
82
+ if !@results.key?(k)
83
+ @pending[k] ||= normalize_fetch_key(v)
84
+ pending_keys << k
85
+ end
86
+ }
87
+
88
+ if !pending_keys.empty?
89
+ sync(pending_keys)
54
90
  end
55
91
 
56
- keys.map { |k| result_for(k) }
92
+ result_keys.map { |k| result_for(k) }
57
93
  end
58
94
 
59
95
  # Subclasses must implement this method to return a value for each of `keys`
@@ -64,50 +100,60 @@ module GraphQL
64
100
  raise "Implement `#{self.class}#fetch(#{keys.inspect}) to return a record for each of the keys"
65
101
  end
66
102
 
103
+ MAX_ITERATIONS = 1000
67
104
  # Wait for a batch, if there's anything to batch.
68
105
  # Then run the batch and update the cache.
69
106
  # @return [void]
70
- def sync
71
- pending_keys = @pending_keys.dup
72
- @dataloader.yield
107
+ def sync(pending_result_keys)
108
+ @dataloader.yield(self)
73
109
  iterations = 0
74
- while pending_keys.any? { |k| !@results.key?(k) }
110
+ while pending_result_keys.any? { |key| !@results.key?(key) }
75
111
  iterations += 1
76
- if iterations > 1000
77
- raise "#{self.class}#sync tried 1000 times to load pending keys (#{pending_keys}), but they still weren't loaded. There is likely a circular dependency."
112
+ if iterations > MAX_ITERATIONS
113
+ raise "#{self.class}#sync tried #{MAX_ITERATIONS} times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency#{@dataloader.fiber_limit ? " or `fiber_limit: #{@dataloader.fiber_limit}` is set too low" : ""}."
78
114
  end
79
- @dataloader.yield
115
+ @dataloader.yield(self)
80
116
  end
81
117
  nil
82
118
  end
83
119
 
84
120
  # @return [Boolean] True if this source has any pending requests for data.
85
121
  def pending?
86
- !@pending_keys.empty?
122
+ !@pending.empty?
123
+ end
124
+
125
+ # Add these key-value pairs to this source's cache
126
+ # (future loads will use these merged values).
127
+ # @param new_results [Hash<Object => Object>] key-value pairs to cache in this source
128
+ # @return [void]
129
+ def merge(new_results)
130
+ new_results.each do |new_k, new_v|
131
+ key = result_key_for(new_k)
132
+ @results[key] = new_v
133
+ end
134
+ nil
87
135
  end
88
136
 
89
137
  # Called by {GraphQL::Dataloader} to resolve and pending requests to this source.
90
138
  # @api private
91
139
  # @return [void]
92
140
  def run_pending_keys
93
- if !@fetching_keys.empty?
94
- @pending_keys -= @fetching_keys
141
+ if !@fetching.empty?
142
+ @fetching.each_key { |k| @pending.delete(k) }
95
143
  end
96
- return if @pending_keys.empty?
97
- fetch_keys = @pending_keys.uniq
98
- @fetching_keys.concat(fetch_keys)
99
- @pending_keys = []
100
- results = fetch(fetch_keys)
101
- fetch_keys.each_with_index do |key, idx|
144
+ return if @pending.empty?
145
+ fetch_h = @pending
146
+ @pending = {}
147
+ @fetching.merge!(fetch_h)
148
+ results = fetch(fetch_h.values)
149
+ fetch_h.each_with_index do |(key, _value), idx|
102
150
  @results[key] = results[idx]
103
151
  end
104
152
  nil
105
153
  rescue StandardError => error
106
- fetch_keys.each { |key| @results[key] = error }
154
+ fetch_h.each_key { |key| @results[key] = error }
107
155
  ensure
108
- if fetch_keys
109
- @fetching_keys -= fetch_keys
110
- end
156
+ fetch_h && fetch_h.each_key { |k| @fetching.delete(k) }
111
157
  end
112
158
 
113
159
  # These arguments are given to `dataloader.with(source_class, ...)`. The object
@@ -128,7 +174,14 @@ module GraphQL
128
174
  [*batch_args, **batch_kwargs]
129
175
  end
130
176
 
131
- attr_reader :pending_keys
177
+ # Clear any already-loaded objects for this source
178
+ # @return [void]
179
+ def clear_cache
180
+ @results.clear
181
+ nil
182
+ end
183
+
184
+ attr_reader :pending, :results
132
185
 
133
186
  private
134
187
 
@@ -145,8 +198,11 @@ This key should have been loaded already. This is a bug in GraphQL::Dataloader,
145
198
  ERR
146
199
  end
147
200
  result = @results[key]
148
-
149
- raise result if result.class <= StandardError
201
+ if result.is_a?(StandardError)
202
+ # Dup it because the rescuer may modify it.
203
+ # (This happens for GraphQL::ExecutionErrors, at least)
204
+ raise result.dup
205
+ end
150
206
 
151
207
  result
152
208
  end
@@ -4,6 +4,8 @@ require "graphql/dataloader/null_dataloader"
4
4
  require "graphql/dataloader/request"
5
5
  require "graphql/dataloader/request_all"
6
6
  require "graphql/dataloader/source"
7
+ require "graphql/dataloader/active_record_association_source"
8
+ require "graphql/dataloader/active_record_source"
7
9
 
8
10
  module GraphQL
9
11
  # This plugin supports Fiber-based concurrency, along with {GraphQL::Dataloader::Source}.
@@ -24,17 +26,23 @@ module GraphQL
24
26
  #
25
27
  class Dataloader
26
28
  class << self
27
- attr_accessor :default_nonblocking
29
+ attr_accessor :default_nonblocking, :default_fiber_limit
28
30
  end
29
31
 
30
- AsyncDataloader = Class.new(self) { self.default_nonblocking = true }
31
-
32
- def self.use(schema, nonblocking: nil)
33
- schema.dataloader_class = if nonblocking
34
- AsyncDataloader
32
+ def self.use(schema, nonblocking: nil, fiber_limit: nil)
33
+ dataloader_class = if nonblocking
34
+ warn("`nonblocking: true` is deprecated from `GraphQL::Dataloader`, please use `GraphQL::Dataloader::AsyncDataloader` instead. Docs: https://graphql-ruby.org/dataloader/async_dataloader.")
35
+ Class.new(self) { self.default_nonblocking = true }
35
36
  else
36
37
  self
37
38
  end
39
+
40
+ if fiber_limit
41
+ dataloader_class = Class.new(dataloader_class)
42
+ dataloader_class.default_fiber_limit = fiber_limit
43
+ end
44
+
45
+ schema.dataloader_class = dataloader_class
38
46
  end
39
47
 
40
48
  # Call the block with a Dataloader instance,
@@ -49,18 +57,50 @@ module GraphQL
49
57
  result
50
58
  end
51
59
 
52
- def initialize(nonblocking: self.class.default_nonblocking)
60
+ def initialize(nonblocking: self.class.default_nonblocking, fiber_limit: self.class.default_fiber_limit)
53
61
  @source_cache = Hash.new { |h, k| h[k] = {} }
54
62
  @pending_jobs = []
55
63
  if !nonblocking.nil?
56
64
  @nonblocking = nonblocking
57
65
  end
66
+ @fiber_limit = fiber_limit
58
67
  end
59
68
 
69
+ # @return [Integer, nil]
70
+ attr_reader :fiber_limit
71
+
60
72
  def nonblocking?
61
73
  @nonblocking
62
74
  end
63
75
 
76
+ # This is called before the fiber is spawned, from the parent context (i.e. from
77
+ # the thread or fiber that it is scheduled from).
78
+ #
79
+ # @return [Hash<Symbol, Object>] Current fiber-local variables
80
+ def get_fiber_variables
81
+ fiber_vars = {}
82
+ Thread.current.keys.each do |fiber_var_key|
83
+ fiber_vars[fiber_var_key] = Thread.current[fiber_var_key]
84
+ end
85
+ fiber_vars
86
+ end
87
+
88
+ # Set up the fiber variables in a new fiber.
89
+ #
90
+ # This is called within the fiber, right after it is spawned.
91
+ #
92
+ # @param vars [Hash<Symbol, Object>] Fiber-local variables from {get_fiber_variables}
93
+ # @return [void]
94
+ def set_fiber_variables(vars)
95
+ vars.each { |k, v| Thread.current[k] = v }
96
+ nil
97
+ end
98
+
99
+ # This method is called when Dataloader is finished using a fiber.
100
+ # Use it to perform any cleanup, such as releasing database connections (if required manually)
101
+ def cleanup_fiber
102
+ end
103
+
64
104
  # Get a Source instance from this dataloader, for calling `.load(...)` or `.request(...)` on.
65
105
  #
66
106
  # @param source_class [Class<GraphQL::Dataloader::Source]
@@ -91,8 +131,11 @@ module GraphQL
91
131
  # Dataloader will resume the fiber after the requested data has been loaded (by another Fiber).
92
132
  #
93
133
  # @return [void]
94
- def yield
134
+ def yield(source = Fiber[:__graphql_current_dataloader_source])
135
+ trace = Fiber[:__graphql_current_multiplex]&.current_trace
136
+ trace&.dataloader_fiber_yield(source)
95
137
  Fiber.yield
138
+ trace&.dataloader_fiber_resume(source)
96
139
  nil
97
140
  end
98
141
 
@@ -104,6 +147,15 @@ module GraphQL
104
147
  nil
105
148
  end
106
149
 
150
+ # Clear any already-loaded objects from {Source} caches
151
+ # @return [void]
152
+ def clear_cache
153
+ @source_cache.each do |_source_class, batched_sources|
154
+ batched_sources.each_value(&:clear_cache)
155
+ end
156
+ nil
157
+ end
158
+
107
159
  # Use a self-contained queue for the work in the block.
108
160
  def run_isolated
109
161
  prev_queue = @pending_jobs
@@ -111,8 +163,8 @@ module GraphQL
111
163
  @source_cache.each do |source_class, batched_sources|
112
164
  batched_sources.each do |batch_args, batched_source_instance|
113
165
  if batched_source_instance.pending?
114
- prev_pending_keys[batched_source_instance] = batched_source_instance.pending_keys.dup
115
- batched_source_instance.pending_keys.clear
166
+ prev_pending_keys[batched_source_instance] = batched_source_instance.pending.dup
167
+ batched_source_instance.pending.clear
116
168
  end
117
169
  end
118
170
  end
@@ -127,125 +179,132 @@ module GraphQL
127
179
  res
128
180
  ensure
129
181
  @pending_jobs = prev_queue
130
- prev_pending_keys.each do |source_instance, pending_keys|
131
- source_instance.pending_keys.concat(pending_keys)
182
+ prev_pending_keys.each do |source_instance, pending|
183
+ pending.each do |key, value|
184
+ if !source_instance.results.key?(key)
185
+ source_instance.pending[key] = value
186
+ end
187
+ end
132
188
  end
133
189
  end
134
190
 
135
- # @api private Move along, move along
136
191
  def run
137
- if @nonblocking && !Fiber.scheduler
138
- raise "`nonblocking: true` requires `Fiber.scheduler`, assign one with `Fiber.set_scheduler(...)` before executing GraphQL."
139
- end
140
- # At a high level, the algorithm is:
141
- #
142
- # A) Inside Fibers, run jobs from the queue one-by-one
143
- # - When one of the jobs yields to the dataloader (`Fiber.yield`), then that fiber will pause
144
- # - In that case, if there are still pending jobs, a new Fiber will be created to run jobs
145
- # - Continue until all jobs have been _started_ by a Fiber. (Any number of those Fibers may be waiting to be resumed, after their data is loaded)
146
- # B) Once all known jobs have been run until they are complete or paused for data, run all pending data sources.
147
- # - Similarly, create a Fiber to consume pending sources and tell them to load their data.
148
- # - If one of those Fibers pauses, then create a new Fiber to continue working through remaining pending sources.
149
- # - When a source causes another source to become pending, run the newly-pending source _first_, since it's a dependency of the previous one.
150
- # C) After all pending sources have been completely loaded (there are no more pending sources), resume any Fibers that were waiting for data.
151
- # - Those Fibers assume that source caches will have been populated with the data they were waiting for.
152
- # - Those Fibers may request data from a source again, in which case they will yeilded and be added to a new pending fiber list.
153
- # D) Once all pending fibers have been resumed once, return to `A` above.
154
- #
155
- # For whatever reason, the best implementation I could find was to order the steps `[D, A, B, C]`, with a special case for skipping `D`
156
- # on the first pass. I just couldn't find a better way to write the loops in a way that was DRY and easy to read.
157
- #
158
- pending_fibers = []
159
- next_fibers = []
160
- pending_source_fibers = []
192
+ trace = Fiber[:__graphql_current_multiplex]&.current_trace
193
+ jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
194
+ job_fibers = []
195
+ next_job_fibers = []
196
+ source_fibers = []
161
197
  next_source_fibers = []
162
198
  first_pass = true
163
-
164
- while first_pass || (f = pending_fibers.shift)
165
- if first_pass
199
+ manager = spawn_fiber do
200
+ trace&.begin_dataloader(self)
201
+ while first_pass || !job_fibers.empty?
166
202
  first_pass = false
167
- else
168
- # These fibers were previously waiting for sources to load data,
169
- # resume them. (They might wait again, in which case, re-enqueue them.)
170
- resume(f)
171
- if f.alive?
172
- next_fibers << f
173
- end
174
- end
175
203
 
176
- while @pending_jobs.any?
177
- # Create a Fiber to consume jobs until one of the jobs yields
178
- # or jobs run out
179
- f = spawn_fiber {
180
- while (job = @pending_jobs.shift)
181
- job.call
204
+ while (f = (job_fibers.shift || (((next_job_fibers.size + job_fibers.size) < jobs_fiber_limit) && spawn_job_fiber(trace))))
205
+ if f.alive?
206
+ finished = run_fiber(f)
207
+ if !finished
208
+ next_job_fibers << f
209
+ end
182
210
  end
183
- }
184
- resume(f)
185
- # In this case, the job yielded. Queue it up to run again after
186
- # we load whatever it's waiting for.
187
- if f.alive?
188
- next_fibers << f
189
- end
190
- end
191
-
192
- if pending_fibers.empty?
193
- # Now, run all Sources which have become pending _before_ resuming GraphQL execution.
194
- # Sources might queue up other Sources, which is fine -- those will also run before resuming execution.
195
- #
196
- # This is where an evented approach would be even better -- can we tell which
197
- # fibers are ready to continue, and continue execution there?
198
- #
199
- if (first_source_fiber = create_source_fiber)
200
- pending_source_fibers << first_source_fiber
201
211
  end
212
+ join_queues(job_fibers, next_job_fibers)
202
213
 
203
- while pending_source_fibers.any?
204
- while (outer_source_fiber = pending_source_fibers.pop)
205
- resume(outer_source_fiber)
206
- if outer_source_fiber.alive?
207
- next_source_fibers << outer_source_fiber
208
- end
209
- if (next_source_fiber = create_source_fiber)
210
- pending_source_fibers << next_source_fiber
214
+ while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
215
+ while (f = source_fibers.shift || (((job_fibers.size + source_fibers.size + next_source_fibers.size + next_job_fibers.size) < total_fiber_limit) && spawn_source_fiber(trace)))
216
+ if f.alive?
217
+ finished = run_fiber(f)
218
+ if !finished
219
+ next_source_fibers << f
220
+ end
211
221
  end
212
222
  end
213
- join_queues(pending_source_fibers, next_source_fibers)
214
- next_source_fibers.clear
223
+ join_queues(source_fibers, next_source_fibers)
215
224
  end
216
- # Move newly-enqueued Fibers on to the list to be resumed.
217
- # Clear out the list of next-round Fibers, so that
218
- # any Fibers that pause can be put on it.
219
- join_queues(pending_fibers, next_fibers)
220
- next_fibers.clear
221
225
  end
226
+
227
+ trace&.end_dataloader(self)
228
+ end
229
+
230
+ run_fiber(manager)
231
+
232
+ if manager.alive?
233
+ raise "Invariant: Manager fiber didn't terminate properly."
222
234
  end
223
235
 
224
- if @pending_jobs.any?
225
- raise "Invariant: #{@pending_jobs.size} pending jobs"
226
- elsif pending_fibers.any?
227
- raise "Invariant: #{pending_fibers.size} pending fibers"
228
- elsif next_fibers.any?
229
- raise "Invariant: #{next_fibers.size} next fibers"
236
+ if !job_fibers.empty?
237
+ raise "Invariant: job fibers should have exited but #{job_fibers.size} remained"
230
238
  end
231
- nil
239
+ if !source_fibers.empty?
240
+ raise "Invariant: source fibers should have exited but #{source_fibers.size} remained"
241
+ end
242
+
243
+ rescue UncaughtThrowError => e
244
+ throw e.tag, e.value
245
+ end
246
+
247
+ def run_fiber(f)
248
+ f.resume
249
+ end
250
+
251
+ def spawn_fiber
252
+ fiber_vars = get_fiber_variables
253
+ Fiber.new(blocking: !@nonblocking) {
254
+ set_fiber_variables(fiber_vars)
255
+ yield
256
+ cleanup_fiber
257
+ }
232
258
  end
233
259
 
234
- def join_queues(previous_queue, next_queue)
235
- if @nonblocking
236
- Fiber.scheduler.run
237
- next_queue.select!(&:alive?)
260
+ # Pre-warm the Dataloader cache with ActiveRecord objects which were loaded elsewhere.
261
+ # These will be used by {Dataloader::ActiveRecordSource}, {Dataloader::ActiveRecordAssociationSource} and their helper
262
+ # methods, `dataload_record` and `dataload_association`.
263
+ # @param records [Array<ActiveRecord::Base>] Already-loaded records to warm the cache with
264
+ # @param index_by [Symbol] The attribute to use as the cache key. (Should match `find_by:` when using {ActiveRecordSource})
265
+ # @return [void]
266
+ def merge_records(records, index_by: :id)
267
+ records_by_class = Hash.new { |h, k| h[k] = {} }
268
+ records.each do |r|
269
+ records_by_class[r.class][r.public_send(index_by)] = r
270
+ end
271
+ records_by_class.each do |r_class, records|
272
+ with(ActiveRecordSource, r_class).merge(records)
238
273
  end
239
- previous_queue.concat(next_queue)
240
274
  end
241
275
 
242
276
  private
243
277
 
244
- # If there are pending sources, return a fiber for running them.
245
- # Otherwise, return `nil`.
246
- #
247
- # @return [Fiber, nil]
248
- def create_source_fiber
278
+ def calculate_fiber_limit
279
+ total_fiber_limit = @fiber_limit || Float::INFINITY
280
+ if total_fiber_limit < 4
281
+ raise ArgumentError, "Dataloader fiber limit is too low (#{total_fiber_limit}), it must be at least 4"
282
+ end
283
+ total_fiber_limit -= 1 # deduct one fiber for `manager`
284
+ # Deduct at least one fiber for sources
285
+ jobs_fiber_limit = total_fiber_limit - 2
286
+ return jobs_fiber_limit, total_fiber_limit
287
+ end
288
+
289
+ def join_queues(prev_queue, new_queue)
290
+ @nonblocking && Fiber.scheduler.run
291
+ prev_queue.concat(new_queue)
292
+ new_queue.clear
293
+ end
294
+
295
+ def spawn_job_fiber(trace)
296
+ if !@pending_jobs.empty?
297
+ spawn_fiber do
298
+ trace&.dataloader_spawn_execution_fiber(@pending_jobs)
299
+ while job = @pending_jobs.shift
300
+ job.call
301
+ end
302
+ trace&.dataloader_fiber_exit
303
+ end
304
+ end
305
+ end
306
+
307
+ def spawn_source_fiber(trace)
249
308
  pending_sources = nil
250
309
  @source_cache.each_value do |source_by_batch_params|
251
310
  source_by_batch_params.each_value do |source|
@@ -257,52 +316,19 @@ module GraphQL
257
316
  end
258
317
 
259
318
  if pending_sources
260
- # By passing the whole array into this Fiber, it's possible that we set ourselves up for a bunch of no-ops.
261
- # For example, if you have sources `[a, b, c]`, and `a` is loaded, then `b` yields to wait for `d`, then
262
- # the next fiber would be dispatched with `[c, d]`. It would fulfill `c`, then `d`, then eventually
263
- # the previous fiber would start up again. `c` would no longer be pending, but it would still receive `.run_pending_keys`.
264
- # That method is short-circuited since it isn't pending any more, but it's still a waste.
265
- #
266
- # This design could probably be improved by maintaining a `@pending_sources` queue which is shared by the fibers,
267
- # similar to `@pending_jobs`. That way, when a fiber is resumed, it would never pick up work that was finished by a different fiber.
268
- source_fiber = spawn_fiber do
269
- pending_sources.each(&:run_pending_keys)
270
- end
271
- end
272
-
273
- source_fiber
274
- end
275
-
276
- def resume(fiber)
277
- fiber.resume
278
- rescue UncaughtThrowError => e
279
- throw e.tag, e.value
280
- end
281
-
282
- # Copies the thread local vars into the fiber thread local vars. Many
283
- # gems (such as RequestStore, MiniRacer, etc.) rely on thread local vars
284
- # to keep track of execution context, and without this they do not
285
- # behave as expected.
286
- #
287
- # @see https://github.com/rmosolgo/graphql-ruby/issues/3449
288
- def spawn_fiber
289
- fiber_locals = {}
290
-
291
- Thread.current.keys.each do |fiber_var_key|
292
- fiber_locals[fiber_var_key] = Thread.current[fiber_var_key]
293
- end
294
-
295
- if @nonblocking
296
- Fiber.new(blocking: false) do
297
- fiber_locals.each { |k, v| Thread.current[k] = v }
298
- yield
299
- end
300
- else
301
- Fiber.new do
302
- fiber_locals.each { |k, v| Thread.current[k] = v }
303
- yield
319
+ spawn_fiber do
320
+ trace&.dataloader_spawn_source_fiber(pending_sources)
321
+ pending_sources.each do |source|
322
+ Fiber[:__graphql_current_dataloader_source] = source
323
+ trace&.begin_dataloader_source(source)
324
+ source.run_pending_keys
325
+ trace&.end_dataloader_source(source)
326
+ end
327
+ trace&.dataloader_fiber_exit
304
328
  end
305
329
  end
306
330
  end
307
331
  end
308
332
  end
333
+
334
+ require "graphql/dataloader/async_dataloader"
data/lib/graphql/dig.rb CHANGED
@@ -2,10 +2,11 @@
2
2
  module GraphQL
3
3
  module Dig
4
4
  # implemented using the old activesupport #dig instead of the ruby built-in
5
- # so we can use some of the magic in Schema::InputObject and Query::Arguments
5
+ # so we can use some of the magic in Schema::InputObject and Interpreter::Arguments
6
6
  # to handle stringified/symbolized keys.
7
7
  #
8
- # @param args [Array<[String, Symbol>] Retrieves the value object corresponding to the each key objects repeatedly
8
+ # @param own_key [String, Symbol] A key to retrieve
9
+ # @param rest_keys [Array<[String, Symbol>] Retrieves the value object corresponding to the each key objects repeatedly
9
10
  # @return [Object]
10
11
  def dig(own_key, *rest_keys)
11
12
  val = self[own_key]
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ # This error is raised when `Types::ISO8601Duration` is asked to return a value
4
+ # that cannot be parsed as an ISO8601-formatted duration by ActiveSupport::Duration.
5
+ #
6
+ # @see GraphQL::Types::ISO8601Duration which raises this error
7
+ class DurationEncodingError < GraphQL::RuntimeTypeError
8
+ # The value which couldn't be encoded
9
+ attr_reader :duration_value
10
+
11
+ def initialize(value)
12
+ @duration_value = value
13
+ super("Duration cannot be parsed: #{value}. \nDuration must be an ISO8601-formatted duration.")
14
+ end
15
+ end
16
+ end