Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce graphql resolver runtime metadata. #167

Draft
wants to merge 1 commit into
base: myron/improve-perf/move-coerce-result
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@
module ElasticGraph
module SchemaArtifacts
module RuntimeMetadata
class GraphQLField < ::Data.define(:name_in_index, :relation, :computation_detail)
EMPTY = new(nil, nil, nil)
class GraphQLField < ::Data.define(:name_in_index, :relation, :computation_detail, :resolver)
EMPTY = new(nil, nil, nil, nil)
NAME_IN_INDEX = "name_in_index"
RELATION = "relation"
AGGREGATION_DETAIL = "computation_detail"
RESOLVER = "resolver"

def self.from_hash(hash)
new(
name_in_index: hash[NAME_IN_INDEX],
relation: hash[RELATION]&.then { |rel_hash| Relation.from_hash(rel_hash) },
computation_detail: hash[AGGREGATION_DETAIL]&.then { |agg_hash| ComputationDetail.from_hash(agg_hash) }
computation_detail: hash[AGGREGATION_DETAIL]&.then { |agg_hash| ComputationDetail.from_hash(agg_hash) },
resolver: hash[RESOLVER]&.to_sym
)
end

Expand All @@ -31,15 +33,20 @@ def to_dumpable_hash
# Keys here are ordered alphabetically; please keep them that way.
AGGREGATION_DETAIL => computation_detail&.to_dumpable_hash,
NAME_IN_INDEX => name_in_index,
RELATION => relation&.to_dumpable_hash
RELATION => relation&.to_dumpable_hash,
RESOLVER => resolver&.to_s
}
end

# Indicates if we need this field in our dumped runtime metadata, when it has the given
# `name_in_graphql`. Fields that have not been customized in some way do not need to be
# included in the dumped runtime metadata.
def needed?(name_in_graphql)
!!relation || !!computation_detail || name_in_index&.!=(name_in_graphql) || false
def needed?(name_in_graphql, resolver)
!!relation ||
!!computation_detail ||
name_in_index&.!=(name_in_graphql) ||
resolver != self.resolver ||
false
end

def with_computation_detail(empty_bucket_value:, function:)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,28 @@ class ObjectType < ::Data.define(
# imply that this type was user-defined; we have recently introduced this metadata and are not yet setting
# it for all generated GraphQL types. For now, we are only setting it for specific cases where we need it.
:source_type,
:graphql_only_return_type
:graphql_only_return_type,
:default_graphql_resolver
)
UPDATE_TARGETS = "update_targets"
INDEX_DEFINITION_NAMES = "index_definition_names"
GRAPHQL_FIELDS_BY_NAME = "graphql_fields_by_name"
ELASTICGRAPH_CATEGORY = "elasticgraph_category"
SOURCE_TYPE = "source_type"
GRAPHQL_ONLY_RETURN_TYPE = "graphql_only_return_type"
DEFAULT_GRAPHQL_RESOLVER = "default_graphql_resolver"

def initialize(update_targets:, index_definition_names:, graphql_fields_by_name:, elasticgraph_category:, source_type:, graphql_only_return_type:)
graphql_fields_by_name = graphql_fields_by_name.select { |name, field| field.needed?(name) }
def initialize(update_targets:, index_definition_names:, graphql_fields_by_name:, elasticgraph_category:, source_type:, graphql_only_return_type:, default_graphql_resolver:)
graphql_fields_by_name = graphql_fields_by_name.select { |name, field| field.needed?(name, default_graphql_resolver) }

super(
update_targets: update_targets,
index_definition_names: index_definition_names,
graphql_fields_by_name: graphql_fields_by_name,
elasticgraph_category: elasticgraph_category,
source_type: source_type,
graphql_only_return_type: graphql_only_return_type
graphql_only_return_type: graphql_only_return_type,
default_graphql_resolver: default_graphql_resolver
)
end

Expand All @@ -60,13 +63,15 @@ def self.from_hash(hash)
graphql_fields_by_name: graphql_fields_by_name,
elasticgraph_category: hash[ELASTICGRAPH_CATEGORY]&.to_sym || nil,
source_type: hash[SOURCE_TYPE] || nil,
graphql_only_return_type: !!hash[GRAPHQL_ONLY_RETURN_TYPE]
graphql_only_return_type: !!hash[GRAPHQL_ONLY_RETURN_TYPE],
default_graphql_resolver: hash[DEFAULT_GRAPHQL_RESOLVER]&.to_sym
)
end

def to_dumpable_hash
{
# Keys here are ordered alphabetically; please keep them that way.
DEFAULT_GRAPHQL_RESOLVER => default_graphql_resolver&.to_s,
ELASTICGRAPH_CATEGORY => elasticgraph_category&.to_s,
GRAPHQL_FIELDS_BY_NAME => HashDumper.dump_hash(graphql_fields_by_name, &:to_dumpable_hash),
GRAPHQL_ONLY_RETURN_TYPE => graphql_only_return_type ? true : nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,37 @@ module ElasticGraph
attr_reader name_in_index: ::String?
attr_reader relation: Relation?
attr_reader computation_detail: ComputationDetail?
attr_reader resolver: ::Symbol?

def initialize: (
name_in_index: ::String?,
relation: Relation?,
computation_detail: ComputationDetail?
computation_detail: ComputationDetail?,
resolver: ::Symbol?
) -> void

def with: (
?name_in_index: ::String?,
?relation: Relation?,
?computation_detail: ComputationDetail?
?computation_detail: ComputationDetail?,
?resolver: ::Symbol?
) -> instance

def self.new:
(name_in_index: ::String?, relation: Relation?, computation_detail: ComputationDetail?) -> instance
| (::String?, Relation?, ::Symbol?) -> instance
(name_in_index: ::String?, relation: Relation?, computation_detail: ComputationDetail?, resolver: ::Symbol?) -> instance
| (::String?, Relation?, ComputationDetail?, ::Symbol?) -> instance
end

class GraphQLField < GraphQLFieldSupertype
EMPTY: GraphQLField
NAME_IN_INDEX: "name_in_index"
RELATION: "relation"
AGGREGATION_DETAIL: "computation_detail"
RESOLVER: "resolver"

def self.from_hash: (::Hash[::String, untyped]) -> GraphQLField
def to_dumpable_hash: () -> ::Hash[::String, untyped]
def needed?: (::String) -> bool
def needed?: (::String, ::Symbol?) -> bool

def with_computation_detail: (
empty_bucket_value: ::Numeric?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ module ElasticGraph
attr_reader elasticgraph_category: elasticGraphCategory?
attr_reader source_type: ::String?
attr_reader graphql_only_return_type: bool
attr_reader default_graphql_resolver: ::Symbol?

def initialize: (
update_targets: ::Array[UpdateTarget],
index_definition_names: ::Array[::String],
graphql_fields_by_name: ::Hash[::String, GraphQLField],
elasticgraph_category: elasticGraphCategory?,
source_type: ::String?,
graphql_only_return_type: bool
graphql_only_return_type: bool,
default_graphql_resolver: ::Symbol?
) -> void

def with: (
Expand All @@ -26,7 +28,8 @@ module ElasticGraph
?graphql_fields_by_name: ::Hash[::String, GraphQLField],
?elasticgraph_category: elasticGraphCategory?,
?source_type: ::String?,
?graphql_only_return_type: bool
?graphql_only_return_type: bool,
?default_graphql_resolver: ::Symbol?
) -> ObjectType
end

Expand All @@ -37,6 +40,7 @@ module ElasticGraph
ELASTICGRAPH_CATEGORY: "elasticgraph_category"
SOURCE_TYPE: "source_type"
GRAPHQL_ONLY_RETURN_TYPE: "graphql_only_return_type"
DEFAULT_GRAPHQL_RESOLVER: "default_graphql_resolver"
def self.from_hash: (::Hash[::String, untyped]) -> ObjectType
def to_dumpable_hash: () -> ::Hash[::String, untyped]
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,19 @@ module RuntimeMetadata
field = GraphQLField.from_hash({})

expect(field).to eq GraphQLField.new(
computation_detail: nil,
name_in_index: nil,
relation: nil,
computation_detail: nil
resolver: nil
)
end

it "offers `with_computation_detail` updating aggregation detail" do
field = GraphQLField.new(
computation_detail: nil,
name_in_index: nil,
relation: nil,
computation_detail: nil
resolver: :self
)

updated = field.with_computation_detail(
Expand All @@ -42,6 +44,20 @@ module RuntimeMetadata
function: :sum
))
end

it "exposes `resolver` as a symbol while keeping it as a string in dumped form" do
field = GraphQLField.from_hash({"resolver" => "self"})

expect(field.resolver).to eq :self
expect(field.to_dumpable_hash).to include("resolver" => "self")
end

it "exposes `resolver` as nil when it is unset" do
field = GraphQLField.from_hash({})

expect(field.resolver).to eq nil
expect(field.to_dumpable_hash).to include("resolver" => nil)
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,6 @@ module RuntimeMetadata
RSpec.describe ObjectType do
include RuntimeMetadataSupport

it "ignores fields that have no meaningful runtime metadata" do
object_type = object_type_with(graphql_fields_by_name: {
"has_relation" => graphql_field_with(name_in_index: nil, relation: relation_with),
"has_computation_detail" => graphql_field_with(computation_detail: :sum),
"has_alternate_index_name" => graphql_field_with(name_in_index: "alternate"),
"has_nil_index_name" => graphql_field_with(name_in_index: nil),
"has_same_index_name" => graphql_field_with(name_in_index: "has_same_index_name")
})

expect(object_type.graphql_fields_by_name.keys).to contain_exactly(
"has_relation",
"has_computation_detail",
"has_alternate_index_name"
)
end

it "builds from a minimal hash" do
type = ObjectType.from_hash({})

Expand All @@ -40,7 +24,8 @@ module RuntimeMetadata
graphql_fields_by_name: {},
elasticgraph_category: nil,
source_type: nil,
graphql_only_return_type: false
graphql_only_return_type: false,
default_graphql_resolver: nil
)
end

Expand All @@ -51,6 +36,13 @@ module RuntimeMetadata
expect(type.to_dumpable_hash).to include("elasticgraph_category" => "scalar_aggregated_values")
end

it "exposes `default_graphql_resolver` as a symbol while keeping it as a string in dumped form" do
type = ObjectType.from_hash({"default_graphql_resolver" => "self"})

expect(type.default_graphql_resolver).to eq :self
expect(type.to_dumpable_hash).to include("default_graphql_resolver" => "self")
end

it "models `graphql_only_return_type` as `true` or `nil` so that our runtime metadata pruning can omit nils" do
type = ObjectType.from_hash({})

Expand All @@ -62,6 +54,22 @@ module RuntimeMetadata
expect(type.graphql_only_return_type).to eq true
expect(type.to_dumpable_hash).to include("graphql_only_return_type" => true)
end

it "omits GraphQL fields that have no meaningful metadata" do
type = object_type_with(
default_graphql_resolver: :self,
graphql_fields_by_name: {
"foo1" => graphql_field_with(resolver: :self, name_in_index: "foo1"),
"foo2" => graphql_field_with(resolver: :self, name_in_index: "foo2_in_index"),
"foo3" => graphql_field_with(resolver: :self, name_in_index: "foo3", relation: relation_with),
"foo4" => graphql_field_with(resolver: :self, name_in_index: "foo4", computation_detail: computation_detail_with),
"foo5" => graphql_field_with(resolver: :other, name_in_index: "foo5"),
"foo6" => graphql_field_with(resolver: :self, name_in_index: nil)
}
)

expect(type.graphql_fields_by_name.keys).to contain_exactly("foo2", "foo3", "foo4", "foo5")
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module RuntimeMetadata
object_types_by_name: {
"Widget" => ObjectType.new(
index_definition_names: ["widgets"],
default_graphql_resolver: nil,
update_targets: [
UpdateTarget.new(
type: "WidgetCurrency",
Expand All @@ -54,27 +55,30 @@ module RuntimeMetadata
],
graphql_fields_by_name: {
"name_graphql" => GraphQLField.new(
name_in_index: "name_index",
computation_detail: nil,
relation: nil
name_in_index: "name_index",
relation: nil,
resolver: :self
),
"parent" => GraphQLField.new(
name_in_index: nil,
computation_detail: nil,
name_in_index: nil,
relation: Relation.new(
foreign_key: "grandparents.parents.some_id",
direction: :out,
additional_filter: {"flag_field" => {"equalToAnyOf" => [true]}},
foreign_key_nested_paths: ["grandparents", "grandparents.parents"]
)
),
resolver: :self
),
"sum" => GraphQLField.new(
name_in_index: nil,
computation_detail: ComputationDetail.new(
empty_bucket_value: 0,
function: :sum
),
relation: nil
relation: nil,
resolver: :self
)
},
elasticgraph_category: :some_category,
Expand Down Expand Up @@ -167,21 +171,24 @@ module RuntimeMetadata
],
"graphql_fields_by_name" => {
"name_graphql" => {
"name_in_index" => "name_index"
"name_in_index" => "name_index",
"resolver" => "self"
},
"parent" => {
"relation" => {
"foreign_key" => "grandparents.parents.some_id",
"direction" => "out",
"additional_filter" => {"flag_field" => {"equalToAnyOf" => [true]}},
"foreign_key_nested_paths" => ["grandparents", "grandparents.parents"]
}
},
"resolver" => "self"
},
"sum" => {
"computation_detail" => {
"empty_bucket_value" => 0,
"function" => "sum"
}
},
"resolver" => "self"
}
},
"elasticgraph_category" => "some_category",
Expand Down Expand Up @@ -293,7 +300,8 @@ module RuntimeMetadata
"name_graphql" => GraphQLField.new(
name_in_index: "name_index",
computation_detail: nil,
relation: nil
relation: nil,
resolver: :self
)
}),
"NoMetadata" => object_type_with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ def runtime_metadata(extra_update_targets)
graphql_fields_by_name: runtime_metadata_graphql_fields_by_name,
elasticgraph_category: nil,
source_type: nil,
graphql_only_return_type: graphql_only?
graphql_only_return_type: graphql_only?,
default_graphql_resolver: nil
).with(**runtime_metadata_overrides)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,8 @@ def runtime_metadata_graphql_field
SchemaArtifacts::RuntimeMetadata::GraphQLField.new(
name_in_index: name_in_index,
computation_detail: computation_detail,
relation: relationship&.runtime_metadata
relation: relationship&.runtime_metadata,
resolver: nil
)
end

Expand Down
Loading