diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9153791a03..3a05bac1eb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -57,18 +57,18 @@ jobs: ruby: 3.3 - gemfile: gemfiles/rails_master.gemfile ruby: 3.3 - graphql_reject_numbers_followed_by_names: 1 + graphql_future: 1 redis: 1 - gemfile: gemfiles/rails_master.gemfile ruby: 3.4 - graphql_reject_numbers_followed_by_names: 1 + graphql_future: 1 isolation_level_fiber: 1 redis: 1 runs-on: ubuntu-latest steps: - run: echo BUNDLE_GEMFILE=${{ matrix.gemfile }} > $GITHUB_ENV - - run: echo GRAPHQL_REJECT_NUMBERS_FOLLOWED_BY_NAMES=1 > $GITHUB_ENV - if: ${{ !!matrix.graphql_reject_numbers_followed_by_names }} + - run: echo GRAPHQL_FUTURE=1 > $GITHUB_ENV + if: ${{ !!matrix.graphql_future }} - run: echo ISOLATION_LEVEL_FIBER=1 > $GITHUB_ENV if: ${{ !!matrix.isolation_level_fiber }} - uses: shogo82148/actions-setup-redis@v1 diff --git a/lib/graphql/analysis.rb b/lib/graphql/analysis.rb index b90ebf8e25..08f3d70011 100644 --- a/lib/graphql/analysis.rb +++ b/lib/graphql/analysis.rb @@ -54,6 +54,10 @@ def analyze_multiplex(multiplex, analyzers) # @param analyzers [Array] # @return [Array] Results from those analyzers def analyze_query(query, analyzers, multiplex_analyzers: []) + # If called outside of execution: + if query.context.runtime.nil? + query.init_runtime(lazies_at_depth: nil) + end query.current_trace.analyze_query(query: query) do query_analyzers = analyzers .map { |analyzer| analyzer.new(query) } diff --git a/lib/graphql/dataloader.rb b/lib/graphql/dataloader.rb index 92ddac4f6c..b15e90db72 100644 --- a/lib/graphql/dataloader.rb +++ b/lib/graphql/dataloader.rb @@ -6,6 +6,7 @@ require "graphql/dataloader/source" require "graphql/dataloader/active_record_association_source" require "graphql/dataloader/active_record_source" +require "graphql/dataloader/lazy_source" module GraphQL # This plugin supports Fiber-based concurrency, along with {GraphQL::Dataloader::Source}. @@ -26,10 +27,10 @@ module GraphQL # class Dataloader class << self - attr_accessor :default_nonblocking, :default_fiber_limit + attr_accessor :default_nonblocking, :default_fiber_limit, :default_lazy_compat_mode end - def self.use(schema, nonblocking: nil, fiber_limit: nil) + def self.use(schema, nonblocking: nil, fiber_limit: nil, lazy_compat_mode: false) dataloader_class = if nonblocking warn("`nonblocking: true` is deprecated from `GraphQL::Dataloader`, please use `GraphQL::Dataloader::AsyncDataloader` instead. Docs: https://graphql-ruby.org/dataloader/async_dataloader.") Class.new(self) { self.default_nonblocking = true } @@ -42,6 +43,13 @@ def self.use(schema, nonblocking: nil, fiber_limit: nil) dataloader_class.default_fiber_limit = fiber_limit end + if lazy_compat_mode + if dataloader_class == self + dataloader_class = Class.new(dataloader_class) + end + dataloader_class.default_lazy_compat_mode = lazy_compat_mode + end + schema.dataloader_class = dataloader_class end @@ -57,18 +65,22 @@ def self.with_dataloading(&block) result end - def initialize(nonblocking: self.class.default_nonblocking, fiber_limit: self.class.default_fiber_limit) + def initialize(nonblocking: self.class.default_nonblocking, fiber_limit: self.class.default_fiber_limit, lazy_compat_mode: self.class.default_lazy_compat_mode) @source_cache = Hash.new { |h, k| h[k] = {} } @pending_jobs = [] if !nonblocking.nil? @nonblocking = nonblocking end @fiber_limit = fiber_limit + @running_jobs = false + @lazy_compat_mode = lazy_compat_mode end # @return [Integer, nil] attr_reader :fiber_limit + attr_reader :source_cache + def nonblocking? @nonblocking end @@ -141,9 +153,17 @@ def yield(source = Fiber[:__graphql_current_dataloader_source]) # @api private Nothing to see here def append_job(&job) - # Given a block, queue it up to be worked through when `#run` is called. - # (If the dataloader is already running, than a Fiber will pick this up later.) - @pending_jobs.push(job) + # This is to match GraphQL-Batch-type execution. + # In that approach, a field's children would be resolved right after the parent. + # But the default dataloader approach is to run siblings, then "cousin" fields -- each depth in a wave. + # This option restores the flow of lazy_resolve. + if @lazy_compat_mode && @running_jobs + job.call + else + # Given a block, queue it up to be worked through when `#run` is called. + # (If the dataloader is already running, than a Fiber will pick this up later.) + @pending_jobs.push(job) + end nil end @@ -160,6 +180,7 @@ def clear_cache def run_isolated prev_queue = @pending_jobs prev_pending_keys = {} + prev_running_jobs = @running_jobs @source_cache.each do |source_class, batched_sources| batched_sources.each do |batch_args, batched_source_instance| if batched_source_instance.pending? @@ -170,6 +191,7 @@ def run_isolated end @pending_jobs = [] + @running_jobs = false res = nil # Make sure the block is inside a Fiber, so it can `Fiber.yield` append_job { @@ -179,6 +201,7 @@ def run_isolated res ensure @pending_jobs = prev_queue + @running_jobs = prev_running_jobs prev_pending_keys.each do |source_instance, pending| pending.each do |key, value| if !source_instance.results.key?(key) @@ -201,6 +224,7 @@ def run while first_pass || !job_fibers.empty? first_pass = false + @running_jobs = true while (f = (job_fibers.shift || (((next_job_fibers.size + job_fibers.size) < jobs_fiber_limit) && spawn_job_fiber(trace)))) if f.alive? finished = run_fiber(f) @@ -209,10 +233,21 @@ def run end end end + @running_jobs = false join_queues(job_fibers, next_job_fibers) - while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }) - 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))) + defer_sources = nil + @source_cache.each_value do |group_sources| + group_sources.each_value do |source_inst| + if source_inst.defer? + defer_sources ||= [] + defer_sources << source_inst + end + end + end + + while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any? { |s| s.pending? && (defer_sources ? !defer_sources.include?(s) : true) } }) + 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, defer_sources))) if f.alive? finished = run_fiber(f) if !finished @@ -242,6 +277,8 @@ def run rescue UncaughtThrowError => e throw e.tag, e.value + ensure + @running_jobs = false end def run_fiber(f) @@ -304,11 +341,11 @@ def spawn_job_fiber(trace) end end - def spawn_source_fiber(trace) + def spawn_source_fiber(trace, defer_sources) pending_sources = nil @source_cache.each_value do |source_by_batch_params| source_by_batch_params.each_value do |source| - if source.pending? + if source.pending? && !defer_sources&.include?(source) pending_sources ||= [] pending_sources << source end diff --git a/lib/graphql/dataloader/lazy_source.rb b/lib/graphql/dataloader/lazy_source.rb new file mode 100644 index 0000000000..44ae1deb91 --- /dev/null +++ b/lib/graphql/dataloader/lazy_source.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +require "graphql/dataloader/source" + +module GraphQL + class Dataloader + class LazySource < GraphQL::Dataloader::Source + def initialize(phase, context) + @context = context + @phase = phase + end + + def fetch(lazies) + lazies.map do |l| + @context.schema.sync_lazy(l) + rescue StandardError => err + err + end + end + + attr_reader :phase + + def defer? + @phase == :field_resolve && dataloader.source_cache[self.class].any? { |k, v| v.phase == :object_wrap && v.pending? } + end + end + end +end diff --git a/lib/graphql/dataloader/source.rb b/lib/graphql/dataloader/source.rb index 86ce88726e..9732bf60c3 100644 --- a/lib/graphql/dataloader/source.rb +++ b/lib/graphql/dataloader/source.rb @@ -80,6 +80,10 @@ def load_all(values) result_keys.map { |k| result_for(k) } end + def defer? + false + end + # Subclasses must implement this method to return a value for each of `keys` # @param keys [Array] keys passed to {#load}, {#load_all}, {#request}, or {#request_all} # @return [Array] A loaded value for each of `keys`. The array must match one-for-one to the list of `keys`. diff --git a/lib/graphql/execution/interpreter.rb b/lib/graphql/execution/interpreter.rb index 4b6a60099e..288f219b62 100644 --- a/lib/graphql/execution/interpreter.rb +++ b/lib/graphql/execution/interpreter.rb @@ -22,6 +22,7 @@ class << self # @param max_complexity [Integer, nil] # @return [Array] One result per query def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity) + lazies_at_depth = Hash.new { |h, k| h[k] = [] } queries = query_options.map do |opts| query = case opts when Hash @@ -40,9 +41,9 @@ def run_all(schema, query_options, context: {}, max_complexity: schema.max_compl trace = multiplex.current_trace Fiber[:__graphql_current_multiplex] = multiplex trace.execute_multiplex(multiplex: multiplex) do - schema = multiplex.schema queries = multiplex.queries - lazies_at_depth = Hash.new { |h, k| h[k] = [] } + queries.each { |query| query.init_runtime(lazies_at_depth: lazies_at_depth) } + schema = multiplex.schema multiplex_analyzers = schema.multiplex_analyzers if multiplex.max_complexity multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity] @@ -70,14 +71,8 @@ def run_all(schema, query_options, context: {}, max_complexity: schema.max_compl NO_OPERATION else begin - # Although queries in a multiplex _share_ an Interpreter instance, - # they also have another item of state, which is private to that query - # in particular, assign it here: - runtime = Runtime.new(query: query, lazies_at_depth: lazies_at_depth) - query.context.namespace(:interpreter_runtime)[:runtime] = runtime - query.current_trace.execute_query(query: query) do - runtime.run_eager + query.context.runtime.run_eager end rescue GraphQL::ExecutionError => err query.context.errors << err diff --git a/lib/graphql/execution/interpreter/runtime.rb b/lib/graphql/execution/interpreter/runtime.rb index bc488b0f9d..401f5b4317 100644 --- a/lib/graphql/execution/interpreter/runtime.rb +++ b/lib/graphql/execution/interpreter/runtime.rb @@ -38,7 +38,7 @@ def current_object def initialize(query:, lazies_at_depth:) @query = query @current_trace = query.current_trace - @dataloader = query.multiplex.dataloader + @dataloader = query.context.dataloader @lazies_at_depth = lazies_at_depth @schema = query.schema @context = query.context @@ -54,6 +54,7 @@ def initialize(query:, lazies_at_depth:) end # { Class => Boolean } @lazy_cache = {}.compare_by_identity + @default_lazy_legacy = @schema.legacy_sync_lazy end def final_result @@ -876,7 +877,6 @@ def resolve_type(type, value) query.resolve_type(type, value) end @current_trace.end_resolve_type(type, value, context, resolved_type) - if lazy?(resolved_type) GraphQL::Execution::Lazy.new do @current_trace.begin_resolve_type(type, value, context) @@ -891,13 +891,17 @@ def resolve_type(type, value) end end - def lazy?(object) - obj_class = object.class - is_lazy = @lazy_cache[obj_class] - if is_lazy.nil? - is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object) + def lazy?(object, legacy: @default_lazy_legacy) + if legacy + obj_class = object.class + is_lazy = @lazy_cache[obj_class] + if is_lazy.nil? + is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object, legacy: true) + end + is_lazy + else + false end - is_lazy end end end diff --git a/lib/graphql/execution/lazy.rb b/lib/graphql/execution/lazy.rb index 41849f5f50..9169b1d4d4 100644 --- a/lib/graphql/execution/lazy.rb +++ b/lib/graphql/execution/lazy.rb @@ -12,6 +12,41 @@ module Execution # - It has no error-catching functionality # @api private class Lazy + module FieldIntegration + def resolve(_obj, _args, ctx) + result = super + if ctx.runtime.lazy?(result, legacy: true) + ctx.dataloader.with(GraphQL::Dataloader::LazySource, :field_resolve, ctx).load(result) + else + result + end + end + end + + module ObjectIntegration + def self.included(child_class) + child_class.extend(ClassMethods) + child_class.singleton_class.prepend(Authorized) + end + + module ClassMethods + def inherited(child_class) + child_class.singleton_class.prepend(Authorized) + super + end + end + + module Authorized + def authorized?(obj, ctx) + result = super + if ctx.runtime.lazy?(result, legacy: true) + ctx.dataloader.with(GraphQL::Dataloader::LazySource, :object_wrap, ctx).load(result) + else + result + end + end + end + end attr_reader :field # Create a {Lazy} which will get its inner value by calling the block diff --git a/lib/graphql/query.rb b/lib/graphql/query.rb index bf4813a406..a48862e485 100644 --- a/lib/graphql/query.rb +++ b/lib/graphql/query.rb @@ -419,6 +419,11 @@ def after_lazy(value, &block) attr_reader :logger + def init_runtime(lazies_at_depth:) + runtime = Execution::Interpreter::Runtime.new(query: self, lazies_at_depth: lazies_at_depth) + context.namespace(:interpreter_runtime)[:runtime] = runtime + end + private def find_operation(operations, operation_name) diff --git a/lib/graphql/query/context.rb b/lib/graphql/query/context.rb index 675bd91596..eb5cf983e1 100644 --- a/lib/graphql/query/context.rb +++ b/lib/graphql/query/context.rb @@ -271,6 +271,11 @@ def scoped Scoped.new(@scoped_context, current_path) end + # @api private + def runtime + @runtime ||= namespace(:interpreter_runtime)[:runtime] + end + class Scoped def initialize(scoped_context, path) @path = path diff --git a/lib/graphql/schema.rb b/lib/graphql/schema.rb index a5fcf40dba..b74423573c 100644 --- a/lib/graphql/schema.rb +++ b/lib/graphql/schema.rb @@ -1332,6 +1332,9 @@ def parse_error(parse_err, ctx) end def lazy_resolve(lazy_class, value_method) + if !legacy_sync_lazy && self.dataloader_class == GraphQL::Dataloader::NullDataloader + warn "`#{self}.lazy_resolve` requires `self.legacy_sync_lazy` or `use GraphQL::Dataloader` to be configured first\n #{caller(1, 1).first}" + end lazy_methods.set(lazy_class, value_method) end @@ -1629,6 +1632,18 @@ def after_lazy(value, &block) end end + def legacy_sync_lazy(new_value = NOT_CONFIGURED) + if !NOT_CONFIGURED.equal?(new_value) + @legacy_sync_lazy = new_value + elsif defined?(@legacy_sync_lazy) + @legacy_sync_lazy + elsif superclass.respond_to?(:legacy_sync_lazy) + superclass.legacy_sync_lazy + else + false + end + end + # Override this method to handle lazy objects in a custom way. # @param value [Object] an instance of a class registered with {.lazy_resolve} # @return [Object] A GraphQL-ready (non-lazy) object @@ -1649,8 +1664,12 @@ def lazy_method_name(obj) end # @return [Boolean] True if this object should be lazily resolved - def lazy?(obj) - !!lazy_method_name(obj) + def lazy?(obj, legacy: false) + if legacy == true || legacy_sync_lazy + !!lazy_method_name(obj) + else + false + end end # Return a lazy if any of `maybe_lazies` are lazy, diff --git a/spec/graphql/authorization_spec.rb b/spec/graphql/authorization_spec.rb index b49af2aa44..f6c803a8fc 100644 --- a/spec/graphql/authorization_spec.rb +++ b/spec/graphql/authorization_spec.rb @@ -351,6 +351,7 @@ class Schema < GraphQL::Schema mutation(Mutation) directive(Nothing) use GraphQL::Schema::Warden if ADD_WARDEN + dataloader_lazy_setup(self) lazy_resolve(Box, :value) def self.unauthorized_object(err) @@ -369,6 +370,7 @@ def self.unauthorized_object(err) class SchemaWithFieldHook < GraphQL::Schema query(Query) use GraphQL::Schema::Warden if ADD_WARDEN + dataloader_lazy_setup(self) lazy_resolve(Box, :value) def self.unauthorized_field(err) @@ -651,6 +653,7 @@ def auth_execute(*args, **kwargs) it "returns nil if not authorized" do query = "{ unauthorized }" response = AuthTest::SchemaWithFieldHook.execute(query, root_value: 34, context: { lazy_field_authorized: false }) + assert_nil response["data"].fetch("unauthorized") assert_equal ["Unauthorized field unauthorized on Query: 34"], response["errors"].map { |e| e["message"] } end diff --git a/spec/graphql/execution/errors_spec.rb b/spec/graphql/execution/errors_spec.rb index 5bc1e354ee..3ec452c1f3 100644 --- a/spec/graphql/execution/errors_spec.rb +++ b/spec/graphql/execution/errors_spec.rb @@ -171,6 +171,7 @@ def error_in_each end query(Query) + dataloader_lazy_setup(self) lazy_resolve(Proc, :call) def self.object_from_id(id, ctx) diff --git a/spec/graphql/execution/interpreter_spec.rb b/spec/graphql/execution/interpreter_spec.rb index 798cb7dd74..4d12f6ac5b 100644 --- a/spec/graphql/execution/interpreter_spec.rb +++ b/spec/graphql/execution/interpreter_spec.rb @@ -241,6 +241,10 @@ class Counter < GraphQL::Schema::Object field :value, Integer, null: false field :lazy_value, Integer, null: false + def value + object.value + end + def lazy_value Box.new { object.value } end @@ -266,6 +270,7 @@ def increment_counter class Schema < GraphQL::Schema query(Query) mutation(Mutation) + dataloader_lazy_setup(self) lazy_resolve(Box, :value) use GraphQL::Schema::AlwaysVisible @@ -647,6 +652,7 @@ class Subscription < GraphQL::Schema::Object query Query subscription Subscription use InMemoryBackend::Subscriptions, extra: nil + dataloader_lazy_setup(self) lazy_resolve Proc, :call end diff --git a/spec/graphql/query/context/scoped_context_spec.rb b/spec/graphql/query/context/scoped_context_spec.rb index a752114ac5..ffb0975227 100644 --- a/spec/graphql/query/context/scoped_context_spec.rb +++ b/spec/graphql/query/context/scoped_context_spec.rb @@ -78,6 +78,7 @@ def things end query(Query) + dataloader_lazy_setup(self) lazy_resolve(Promise, :value) end diff --git a/spec/graphql/query/context_spec.rb b/spec/graphql/query/context_spec.rb index 56150903f1..eb266e4ee5 100644 --- a/spec/graphql/query/context_spec.rb +++ b/spec/graphql/query/context_spec.rb @@ -176,7 +176,11 @@ def fetch(keys) end end + class BaseField < GraphQL::Schema::Field + include GraphQL::Execution::Lazy::FieldIntegration + end class ContextQuery < GraphQL::Schema::Object + field_class(BaseField) field :get_scoped_context, String do argument :key, String argument :lazy, Boolean, required: false, default_value: false @@ -227,6 +231,7 @@ def set_scoped_int class ContextSchema < GraphQL::Schema query(ContextQuery) + dataloader_lazy_setup(self) lazy_resolve(LazyBlock, :value) use GraphQL::Dataloader end diff --git a/spec/graphql/schema/argument_spec.rb b/spec/graphql/schema/argument_spec.rb index b156c1cbe5..c8aaf94368 100644 --- a/spec/graphql/schema/argument_spec.rb +++ b/spec/graphql/schema/argument_spec.rb @@ -87,6 +87,8 @@ def context_arg_test(input:) class Schema < GraphQL::Schema query(Query) + dataloader_lazy_setup(self) + lazy_resolve(Proc, :call) def self.object_from_id(id, ctx) @@ -745,6 +747,7 @@ def test; end end query(query_type) + use GraphQL::Dataloader lazy_resolve(Proc, :call) end end diff --git a/spec/graphql/schema/directive_spec.rb b/spec/graphql/schema/directive_spec.rb index 0660ef5451..7592264ae2 100644 --- a/spec/graphql/schema/directive_spec.rb +++ b/spec/graphql/schema/directive_spec.rb @@ -184,6 +184,7 @@ def fetch(names) class Schema < GraphQL::Schema query(Query) directive(CountFields) + dataloader_lazy_setup(self) lazy_resolve(Proc, :call) use GraphQL::Dataloader end diff --git a/spec/graphql/schema/input_object_spec.rb b/spec/graphql/schema/input_object_spec.rb index 59cc301ea4..294f4fc471 100644 --- a/spec/graphql/schema/input_object_spec.rb +++ b/spec/graphql/schema/input_object_spec.rb @@ -227,6 +227,7 @@ def resolve(list:) class Schema < GraphQL::Schema query(Query) mutation(Mutation) + dataloader_lazy_setup(self) lazy_resolve(Proc, :call) def self.object_from_id(id, ctx) @@ -1287,7 +1288,7 @@ class Query < GraphQL::Schema::Object def self.object_from_id(id, ctx) -> { nil } end - + dataloader_lazy_setup(self) lazy_resolve(Proc, :call) rescue_from(StandardError) { diff --git a/spec/graphql/schema/member/scoped_spec.rb b/spec/graphql/schema/member/scoped_spec.rb index 8b5fc76074..1ade73a6ed 100644 --- a/spec/graphql/schema/member/scoped_spec.rb +++ b/spec/graphql/schema/member/scoped_spec.rb @@ -120,6 +120,7 @@ def lazy_items end query(Query) + dataloader_lazy_setup(self) lazy_resolve(Proc, :call) end diff --git a/spec/graphql/schema/resolver_spec.rb b/spec/graphql/schema/resolver_spec.rb index 622d32cdfa..689acd74bd 100644 --- a/spec/graphql/schema/resolver_spec.rb +++ b/spec/graphql/schema/resolver_spec.rb @@ -500,6 +500,7 @@ def resolve(*) class Schema < GraphQL::Schema query(Query) mutation(Mutation) + dataloader_lazy_setup(self) lazy_resolve LazyBlock, :value orphan_types IntegerWrapper diff --git a/spec/graphql/testing/helpers_spec.rb b/spec/graphql/testing/helpers_spec.rb index a371363ded..f5971af13d 100644 --- a/spec/graphql/testing/helpers_spec.rb +++ b/spec/graphql/testing/helpers_spec.rb @@ -110,6 +110,7 @@ def lookahead_selections(lookahead:) query(Query) use GraphQL::Dataloader + legacy_sync_lazy(true) # TODO this shouldn't be required lazy_resolve Proc, :call def self.unauthorized_object(err) diff --git a/spec/graphql/tracing/appsignal_trace_spec.rb b/spec/graphql/tracing/appsignal_trace_spec.rb index 4e7c9ab96e..b5d878079c 100644 --- a/spec/graphql/tracing/appsignal_trace_spec.rb +++ b/spec/graphql/tracing/appsignal_trace_spec.rb @@ -69,6 +69,7 @@ def thing; :thing; end class TestSchema < GraphQL::Schema query(Query) trace_with(GraphQL::Tracing::AppsignalTrace) + dataloader_lazy_setup(self) lazy_resolve(IntBox, :value) end end @@ -100,6 +101,7 @@ class AppsignalAndDatadogTestSchema < GraphQL::Schema query(AppsignalTraceTest::Query) trace_with(GraphQL::Tracing::DataDogTrace) trace_with(GraphQL::Tracing::AppsignalTrace) + dataloader_lazy_setup(self) lazy_resolve(IntBox, :value) end @@ -108,6 +110,7 @@ class AppsignalAndDatadogReverseOrderTestSchema < GraphQL::Schema # Include these modules in different order than above: trace_with(GraphQL::Tracing::AppsignalTrace) trace_with(GraphQL::Tracing::DataDogTrace) + dataloader_lazy_setup(self) lazy_resolve(IntBox, :value) end diff --git a/spec/graphql/tracing/data_dog_trace_spec.rb b/spec/graphql/tracing/data_dog_trace_spec.rb index 9f1e6eb7fc..a11ee3f218 100644 --- a/spec/graphql/tracing/data_dog_trace_spec.rb +++ b/spec/graphql/tracing/data_dog_trace_spec.rb @@ -45,6 +45,7 @@ class TestSchema < GraphQL::Schema query(Query) use GraphQL::Dataloader trace_with(GraphQL::Tracing::DataDogTrace) + dataloader_lazy_setup(self) lazy_resolve(Box, :value) end @@ -57,6 +58,7 @@ def prepare_span(trace_key, object, span) end query(Query) trace_with(CustomDataDogTracing) + dataloader_lazy_setup(self) lazy_resolve(Box, :value) end end diff --git a/spec/graphql/tracing/trace_modes_spec.rb b/spec/graphql/tracing/trace_modes_spec.rb index f668df6381..40602931bf 100644 --- a/spec/graphql/tracing/trace_modes_spec.rb +++ b/spec/graphql/tracing/trace_modes_spec.rb @@ -286,6 +286,7 @@ def execute_query(query) CustomTraceClass = Class.new(GraphQL::Tracing::Trace) class BaseSchemaWithCustomTraceClass < GraphQL::Schema + dataloader_lazy_setup(self) use(GraphQL::Batch) trace_class(CustomTraceClass) trace_with(SomeTraceMod) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 26f41e0492..ac9959d0a7 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -32,7 +32,15 @@ USING_C_PARSER = false end -if ENV["GRAPHQL_REJECT_NUMBERS_FOLLOWED_BY_NAMES"] +def dataloader_lazy_setup(schema, lazy_compat_mode: true) + if ENV["GRAPHQL_FUTURE"] + schema.use(GraphQL::Dataloader, lazy_compat_mode: lazy_compat_mode) + else + legacy_sync_lazy(true) + end +end + +if ENV["GRAPHQL_FUTURE"] puts "Opting into GraphQL.reject_numbers_followed_by_names" GraphQL.reject_numbers_followed_by_names = true puts "Opting into GraphQL::Schema::Visibility::Profile" diff --git a/spec/support/dummy/schema.rb b/spec/support/dummy/schema.rb index 0fbcb6c7f1..c79c1ccb62 100644 --- a/spec/support/dummy/schema.rb +++ b/spec/support/dummy/schema.rb @@ -568,8 +568,7 @@ def self.type_error(err, ctx) end end - use GraphQL::Dataloader - + dataloader_lazy_setup(self, lazy_compat_mode: false) lazy_resolve(Proc, :call) end diff --git a/spec/support/jazz.rb b/spec/support/jazz.rb index ded347eee3..208a16f16e 100644 --- a/spec/support/jazz.rb +++ b/spec/support/jazz.rb @@ -916,6 +916,7 @@ def self.object_from_id(id, ctx) BlogPost.has_no_fields(true) extra_types BlogPost use GraphQL::Dataloader + legacy_sync_lazy(true) # for literal use of `Execution::Lazy` above use GraphQL::Schema::Warden if ADD_WARDEN end diff --git a/spec/support/lazy_helpers.rb b/spec/support/lazy_helpers.rb index b6f3d60581..411466ae92 100644 --- a/spec/support/lazy_helpers.rb +++ b/spec/support/lazy_helpers.rb @@ -49,7 +49,16 @@ def self.all end end - class LazySum < GraphQL::Schema::Object + class BaseField < GraphQL::Schema::Field + include GraphQL::Execution::Lazy::FieldIntegration + end + + class BaseObject < GraphQL::Schema::Object + include GraphQL::Execution::Lazy::ObjectIntegration + field_class(BaseField) + end + + class LazySum < BaseObject field :value, Integer def value if object == MAGIC_NUMBER_THAT_RAISES_ERROR @@ -85,7 +94,7 @@ def nested_sum(value:) alias :nullable_nested_sum :nested_sum end - class LazyQuery < GraphQL::Schema::Object + class LazyQuery < BaseObject field :int, Integer, null: false do argument :value, Integer argument :plus, Integer, required: false, default_value: 0 @@ -178,11 +187,11 @@ def add_check(object, text) class LazySchema < GraphQL::Schema query(LazyQuery) mutation(LazyQuery) + use GraphQL::Dataloader lazy_resolve(Wrapper, :item) lazy_resolve(SumAll, :value) trace_with(SumAllInstrumentation2) trace_with(SumAllInstrumentation) - def self.sync_lazy(lazy) if lazy.is_a?(SumAll) && lazy.own_value > 1000 lazy.value # clear the previous set diff --git a/spec/support/star_wars/schema.rb b/spec/support/star_wars/schema.rb index 4d73d1e0a5..1c8f82f092 100644 --- a/spec/support/star_wars/schema.rb +++ b/spec/support/star_wars/schema.rb @@ -420,7 +420,7 @@ def self.object_from_id(node_id, ctx) def self.id_from_object(object, type, ctx) GraphQL::Schema::UniqueWithinType.encode(type.graphql_name, object.id) end - + dataloader_lazy_setup(self) lazy_resolve(LazyWrapper, :value) lazy_resolve(LazyLoader, :value) end