From 1e4d27f75489c2a0f40ef49ffbc1528b401cf17b Mon Sep 17 00:00:00 2001 From: Vahe Khachikyan Date: Thu, 23 Jul 2026 17:45:36 -0400 Subject: [PATCH] Fix O(n^2) transitive interface check in Schema.from_definition 238f1932 added a transitive-interface validation that scans document.definitions linearly for every interface name of every object/interface type. On large schemas this dominates build time: a 9MB SDL (10.7k types) went from ~750ms to ~1.5s. Build a name=>definition hash once (first occurrence wins, matching the previous .find semantics for extension nodes) and skip the check for types with no interfaces. Schema.from_definition on the 9MB SDL: 1598ms -> 758ms. Assisted-By: devx/57cd31bb-9c77-4723-8862-7b08599a5d4f --- lib/graphql/schema/build_from_definition.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/graphql/schema/build_from_definition.rb b/lib/graphql/schema/build_from_definition.rb index 1295af1bbe..0490eaa534 100644 --- a/lib/graphql/schema/build_from_definition.rb +++ b/lib/graphql/schema/build_from_definition.rb @@ -82,6 +82,7 @@ def build(schema_superclass, document, default_resolve:, using: {}, base_types: replace_late_bound_types_with_built_in(types) schema_extensions = nil + definitions_by_name = nil document.definitions.each do |definition| case definition when GraphQL::Language::Nodes::SchemaDefinition, GraphQL::Language::Nodes::DirectiveDefinition @@ -101,9 +102,16 @@ def build(schema_superclass, document, default_resolve:, using: {}, base_types: if prev_type.nil? || prev_type.is_a?(Schema::LateBoundType) if definition.is_a?(GraphQL::Language::Nodes::ObjectTypeDefinition) || definition.is_a?(Language::Nodes::InterfaceTypeDefinition) interface_names = definition.interfaces.map(&:name) - transitive_names = interface_names.map { |n| document.definitions.find { |d| d.respond_to?(:name) && d.name == n }&.interfaces&.map(&:name) } - transitive_names.flatten! - transitive_names.compact! + if !interface_names.empty? + definitions_by_name ||= document.definitions.each_with_object({}) do |d, by_name| + by_name[d.name] ||= d if d.respond_to?(:name) + end + transitive_names = interface_names.map { |n| definitions_by_name[n]&.interfaces&.map(&:name) } + transitive_names.flatten! + transitive_names.compact! + else + transitive_names = interface_names + end if !(missing_transitive_interfaces = transitive_names - interface_names).empty? raise GraphQL::Schema::InvalidDocumentError, "type #{definition.name} is missing one or more transitive interface names: #{missing_transitive_interfaces.join(", ")}. Add them to the type's `implements` list and try again." end