Skip to content

Commit 9d84d8b

Browse files
authored
Merge pull request #5550 from rmosolgo/null-dataloader-isolated
Null dataloader isolated
2 parents 59c13e4 + c1e7cbd commit 9d84d8b

7 files changed

Lines changed: 45 additions & 14 deletions

File tree

lib/graphql/dataloader/null_dataloader.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,17 @@ def run(trace_query_lazy: nil)
3737
end
3838

3939
def run_isolated
40-
new_dl = self.class.new
40+
# Reuse this instance because execution code may already have a reference to _this_ `dataloader` inside the given block.
41+
prev_lazies_at_depth = @lazies_at_depth
42+
@lazies_at_depth = @lazies_at_depth.dup.clear
4143
res = nil
42-
new_dl.append_job {
44+
append_job {
4345
res = yield
4446
}
45-
new_dl.run
47+
run
4648
res
49+
ensure
50+
@lazies_at_depth = prev_lazies_at_depth
4751
end
4852

4953
def clear_cache; end

lib/graphql/query/null_context.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ module GraphQL
44
class Query
55
# This object can be `ctx` in places where there is no query
66
class NullContext < Context
7-
include Singleton
7+
def self.instance
8+
@instance ||= self.new
9+
end
10+
11+
def self.instance=(new_inst)
12+
@instance = new_inst
13+
end
814

915
class NullQuery
1016
def after_lazy(value)
@@ -20,10 +26,10 @@ class NullSchema < GraphQL::Schema
2026
attr_reader :schema, :query, :warden, :dataloader
2127
def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key?, :to_h
2228

23-
def initialize
29+
def initialize(schema: NullSchema)
2430
@query = NullQuery.new
2531
@dataloader = GraphQL::Dataloader::NullDataloader.new
26-
@schema = NullSchema
32+
@schema = schema
2733
@warden = Schema::Warden::NullWarden.new(context: self, schema: @schema)
2834
@types = @warden.visibility_profile
2935
freeze

lib/graphql/schema.rb

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -330,10 +330,16 @@ def plugins
330330
find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins
331331
end
332332

333+
attr_writer :null_context
334+
335+
def null_context
336+
@null_context || GraphQL::Query::NullContext.instance
337+
end
338+
333339
# Build a map of `{ name => type }` and return it
334340
# @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
335341
# @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
336-
def types(context = GraphQL::Query::NullContext.instance)
342+
def types(context = null_context)
337343
if use_visibility_profile?
338344
types = Visibility::Profile.from_context(context, self)
339345
return types.all_types_h
@@ -366,7 +372,7 @@ def types(context = GraphQL::Query::NullContext.instance)
366372
# @param context [GraphQL::Query::Context] Used for filtering definitions at query-time
367373
# @param use_visibility_profile Private, for migration to {Schema::Visibility}
368374
# @return [Module, nil] A type, or nil if there's no type called `type_name`
369-
def get_type(type_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
375+
def get_type(type_name, context = null_context, use_visibility_profile = use_visibility_profile?)
370376
if use_visibility_profile
371377
profile = Visibility::Profile.from_context(context, self)
372378
return profile.type(type_name)
@@ -617,7 +623,7 @@ def use_visibility_profile?
617623
# @param use_visibility_profile Private, for migration to {Schema::Visibility}
618624
# @return [Hash<String, Module>] All possible types, if no `type` is given.
619625
# @return [Array<Module>] Possible types for `type`, if it's given.
620-
def possible_types(type = nil, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
626+
def possible_types(type = nil, context = null_context, use_visibility_profile = use_visibility_profile?)
621627
if use_visibility_profile
622628
if type
623629
return Visibility::Profile.from_context(context, self).possible_types(type)
@@ -701,7 +707,7 @@ def type_from_ast(ast_node, context: self.query_class.new(self, "{ __typename }"
701707
GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node)
702708
end
703709

704-
def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
710+
def get_field(type_or_name, field_name, context = null_context, use_visibility_profile = use_visibility_profile?)
705711
if use_visibility_profile
706712
profile = Visibility::Profile.from_context(context, self)
707713
parent_type = case type_or_name
@@ -738,7 +744,7 @@ def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.in
738744
end
739745
end
740746

741-
def get_fields(type, context = GraphQL::Query::NullContext.instance)
747+
def get_fields(type, context = null_context)
742748
type.fields(context)
743749
end
744750

@@ -1228,6 +1234,7 @@ def inherited(child_class)
12281234
vis = self.visibility
12291235
child_class.visibility = vis.dup_for(child_class)
12301236
end
1237+
child_class.null_context = Query::NullContext.new(schema: child_class)
12311238
super
12321239
end
12331240

lib/graphql/schema/visibility.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def top_level_profile(refresh: false)
191191
if refresh
192192
@top_level_profile = nil
193193
end
194-
@top_level_profile ||= @schema.visibility_profile_class.new(context: Query::NullContext.instance, schema: @schema, visibility: self)
194+
@top_level_profile ||= @schema.visibility_profile_class.new(context: @schema.null_context, schema: @schema, visibility: self)
195195
end
196196

197197
private

lib/graphql/subscriptions.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def trigger(event_name, args, object, scope: nil, context: {})
8080

8181
# Normalize symbol-keyed args to strings, try camelizing them
8282
# Should this accept a real context somehow?
83-
normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext.instance)
83+
normalized_args = normalize_arguments(normalized_event_name, field, args, @schema.null_context)
8484

8585
event = Subscriptions::Event.new(
8686
name: normalized_event_name,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# frozen_string_literal: true
2+
require "spec_helper"
3+
4+
describe "GraphQL NullDataloader" do
5+
it "can run_isolated with previously-captured blocks that register lazies" do
6+
dl = GraphQL::Dataloader::NullDataloader.new
7+
result = 0
8+
dl.run_isolated {
9+
lazy = GraphQL::Execution::Lazy.new { result = 100 }
10+
dl.lazy_at_depth(1, lazy)
11+
}
12+
assert_equal 100, result
13+
end
14+
end

spec/spec_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
end
5050

5151
# C methods aren't fair game in non-main Ractors
52-
RUN_RACTOR_TESTS = (defined?(::Ractor) && !USING_C_PARSER && !ENV["TEST"])
52+
RUN_RACTOR_TESTS = (defined?(::Ractor) && !USING_C_PARSER && (ENV["TEST"].nil? || ENV["TEST"].include?("ractor_shareable")))
5353

5454
require "rake"
5555
require "graphql/rake_task"

0 commit comments

Comments
 (0)