From 70db347e1d1c10f59e8736c6f7d0cc4c9dd004d3 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Thu, 15 Jul 2021 18:10:19 +1000 Subject: [PATCH 1/2] Initial work on Traversal Improvements in GraphqlSchema builds --- .../graphql/schema/CodeRegistryVisitor.java | 20 +- .../java/graphql/schema/GraphQLSchema.java | 107 ++++++++- .../graphql/schema/SchemaTransformer.java | 2 +- .../GraphQLTypeCollectingVisitor.java | 20 +- .../impl/MultiReadOnlyGraphQLTypeVisitor.java | 205 ++++++++++++++++++ .../graphql/schema/{ => impl}/SchemaUtil.java | 76 ++++++- ...onnectedComponentsTopologicallySorted.java | 4 +- src/main/java/graphql/util/Anonymizer.java | 2 +- .../schema/{ => impl}/SchemaUtilTest.groovy | 10 +- src/test/java/benchmark/SchemaBenchMark.java | 72 ++++++ 10 files changed, 494 insertions(+), 24 deletions(-) rename src/main/java/graphql/schema/{ => impl}/GraphQLTypeCollectingVisitor.java (85%) create mode 100644 src/main/java/graphql/schema/impl/MultiReadOnlyGraphQLTypeVisitor.java rename src/main/java/graphql/schema/{ => impl}/SchemaUtil.java (63%) rename src/main/java/graphql/schema/{ => impl}/StronglyConnectedComponentsTopologicallySorted.java (98%) rename src/test/groovy/graphql/schema/{ => impl}/SchemaUtilTest.groovy (96%) create mode 100644 src/test/java/benchmark/SchemaBenchMark.java diff --git a/src/main/java/graphql/schema/CodeRegistryVisitor.java b/src/main/java/graphql/schema/CodeRegistryVisitor.java index 458b77c1dd..66af22f209 100644 --- a/src/main/java/graphql/schema/CodeRegistryVisitor.java +++ b/src/main/java/graphql/schema/CodeRegistryVisitor.java @@ -1,6 +1,17 @@ package graphql.schema; import graphql.Internal; +import graphql.introspection.Introspection; +import graphql.schema.DataFetcher; +import graphql.schema.FieldCoordinates; +import graphql.schema.GraphQLCodeRegistry; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLFieldsContainer; +import graphql.schema.GraphQLInterfaceType; +import graphql.schema.GraphQLSchemaElement; +import graphql.schema.GraphQLTypeVisitorStub; +import graphql.schema.GraphQLUnionType; +import graphql.schema.TypeResolver; import graphql.util.TraversalControl; import graphql.util.TraverserContext; @@ -12,11 +23,12 @@ * This ensure that all fields have data fetchers and that unions and interfaces have type resolvers */ @Internal -class CodeRegistryVisitor extends GraphQLTypeVisitorStub { +public class CodeRegistryVisitor extends GraphQLTypeVisitorStub { private final GraphQLCodeRegistry.Builder codeRegistry; - CodeRegistryVisitor(GraphQLCodeRegistry.Builder codeRegistry) { + public CodeRegistryVisitor(GraphQLCodeRegistry.Builder codeRegistry) { this.codeRegistry = codeRegistry; + Introspection.addCodeForIntrospectionTypes(codeRegistry); } @Override @@ -27,7 +39,7 @@ public TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node, FieldCoordinates coordinates = coordinates(parentContainerType, node); codeRegistry.dataFetcherIfAbsent(coordinates, dataFetcher); } - + return CONTINUE; } @@ -38,7 +50,7 @@ public TraversalControl visitGraphQLInterfaceType(GraphQLInterfaceType node, Tra codeRegistry.typeResolverIfAbsent(node, typeResolver); } assertTrue(codeRegistry.getTypeResolver(node) != null, - () -> String.format("You MUST provide a type resolver for the interface type '%s'",node.getName())); + () -> String.format("You MUST provide a type resolver for the interface type '%s'", node.getName())); return CONTINUE; } diff --git a/src/main/java/graphql/schema/GraphQLSchema.java b/src/main/java/graphql/schema/GraphQLSchema.java index e935fec98d..dfa250df58 100644 --- a/src/main/java/graphql/schema/GraphQLSchema.java +++ b/src/main/java/graphql/schema/GraphQLSchema.java @@ -8,9 +8,12 @@ import graphql.DirectivesUtil; import graphql.Internal; import graphql.PublicApi; +import graphql.collect.ImmutableKit; import graphql.introspection.Introspection; import graphql.language.SchemaDefinition; import graphql.language.SchemaExtensionDefinition; +import graphql.schema.impl.GraphQLTypeCollectingVisitor; +import graphql.schema.impl.SchemaUtil; import graphql.schema.validation.InvalidSchemaException; import graphql.schema.validation.SchemaValidationError; import graphql.schema.validation.SchemaValidator; @@ -45,7 +48,6 @@ @PublicApi public class GraphQLSchema { - private final GraphQLObjectType queryType; private final GraphQLObjectType mutationType; private final GraphQLObjectType subscriptionType; @@ -68,6 +70,67 @@ public class GraphQLSchema { private final String description; + /* + * This constructs partial GraphQL schema object which has has the schema (query / mutation / subscription) trees + * in it but it does not have the collected types, code registry nor the type references replaced + * + * But it can be traversed to discover all that and filled out later via another constructor. + * + */ + @Internal + private GraphQLSchema(Builder builder) { + assertNotNull(builder.additionalTypes, () -> "additionalTypes can't be null"); + assertNotNull(builder.queryType, () -> "queryType can't be null"); + assertNotNull(builder.additionalDirectives, () -> "directives can't be null"); + assertNotNull(builder.codeRegistry, () -> "codeRegistry can't be null"); + + + this.queryType = builder.queryType; + this.mutationType = builder.mutationType; + this.subscriptionType = builder.subscriptionType; + this.additionalTypes = ImmutableSet.copyOf(builder.additionalTypes); + this.introspectionSchemaType = builder.introspectionSchemaType; + this.intospectionSchemaField = Introspection.buildSchemaField(builder.introspectionSchemaType); + this.introspectionTypeField = Introspection.buildTypeField(builder.introspectionSchemaType); + this.directives = new DirectivesUtil.DirectivesHolder(builder.additionalDirectives); + this.schemaDirectives = new DirectivesUtil.DirectivesHolder(builder.schemaDirectives); + this.definition = builder.definition; + this.extensionDefinitions = nonNullCopyOf(builder.extensionDefinitions); + this.description = builder.description; + + this.codeRegistry = null; + this.typeMap = ImmutableKit.emptyMap(); + this.interfaceNameToObjectTypes = ImmutableKit.emptyMap(); + this.interfaceNameToObjectTypeNames = ImmutableKit.emptyMap(); + } + + /* + * This constructs a full fledged graphql schema object that has not yet had its type references replaced + * but its otherwise complete + */ + @Internal + public GraphQLSchema(GraphQLSchema partiallyBuiltSchema, + GraphQLCodeRegistry codeRegistry, ImmutableMap typeMap, + ImmutableMap> interfaceNameToObjectTypes) { + this.queryType = partiallyBuiltSchema.queryType; + this.mutationType = partiallyBuiltSchema.mutationType; + this.subscriptionType = partiallyBuiltSchema.subscriptionType; + this.additionalTypes = ImmutableSet.copyOf(partiallyBuiltSchema.additionalTypes); + this.introspectionSchemaType = partiallyBuiltSchema.introspectionSchemaType; + this.intospectionSchemaField = Introspection.buildSchemaField(partiallyBuiltSchema.introspectionSchemaType); + this.introspectionTypeField = Introspection.buildTypeField(partiallyBuiltSchema.introspectionSchemaType); + this.directives = partiallyBuiltSchema.directives; + this.schemaDirectives = partiallyBuiltSchema.schemaDirectives; + this.definition = partiallyBuiltSchema.definition; + this.extensionDefinitions = partiallyBuiltSchema.extensionDefinitions; + this.description = partiallyBuiltSchema.description; + this.codeRegistry = codeRegistry; + this.typeMap = typeMap; + this.interfaceNameToObjectTypes = interfaceNameToObjectTypes; + interfaceNameToObjectTypeNames = buildInterfacesToObjectName(interfaceNameToObjectTypes); + } + + // THIS WILL BE REMOVED @Internal private GraphQLSchema(Builder builder, boolean afterTransform) { assertNotNull(builder.additionalTypes, () -> "additionalTypes can't be null"); @@ -99,6 +162,7 @@ private GraphQLSchema(Builder builder, boolean afterTransform) { // This can be removed once we no longer extract legacy code from types such as data fetchers but for now // we need it to make an efficient copy that does not walk the types twice + // THIS WILL BE REMOVED @Internal private GraphQLSchema(GraphQLSchema otherSchema, GraphQLCodeRegistry codeRegistry) { this.queryType = otherSchema.queryType; @@ -120,6 +184,7 @@ private GraphQLSchema(GraphQLSchema otherSchema, GraphQLCodeRegistry codeRegistr this.description = otherSchema.description; } + /** * @return a new schema builder */ @@ -155,7 +220,11 @@ private static GraphQLDirective[] schemaDirectivesArray(GraphQLSchema existingSc return existingSchema.schemaDirectives.getDirectives().toArray(new GraphQLDirective[0]); } - private ImmutableMap> buildInterfacesToObjectTypes(Map> groupImplementations) { + private static List getAllTypesAsList(ImmutableMap typeMap) { + return sortTypes(byNameAsc(), typeMap.values()); + } + + private static ImmutableMap> buildInterfacesToObjectTypes(Map> groupImplementations) { ImmutableMap.Builder> map = ImmutableMap.builder(); for (Map.Entry> e : groupImplementations.entrySet()) { ImmutableList sortedObjectTypes = ImmutableList.copyOf(sortTypes(byNameAsc(), e.getValue())); @@ -164,7 +233,7 @@ private ImmutableMap> buildInterfacesTo return map.build(); } - private ImmutableMap> buildInterfacesToObjectName(ImmutableMap> byInterface) { + private static ImmutableMap> buildInterfacesToObjectName(ImmutableMap> byInterface) { ImmutableMap.Builder> map = ImmutableMap.builder(); for (Map.Entry> e : byInterface.entrySet()) { ImmutableList objectTypeNames = map(e.getValue(), GraphQLObjectType::getName); @@ -284,7 +353,7 @@ public Map getTypeMap() { } public List getAllTypesAsList() { - return sortTypes(byNameAsc(), typeMap.values()); + return getAllTypesAsList(typeMap); } /** @@ -690,12 +759,42 @@ GraphQLSchema buildImpl(boolean afterTransform) { additionalDirectives.add(Directives.SpecifiedByDirective); } + // quick build - no traversing + final GraphQLSchema partiallyBuiltSchema = new GraphQLSchema(this); + + GraphQLCodeRegistry.Builder extractedDataFetchers = GraphQLCodeRegistry.newCodeRegistry(codeRegistry); + CodeRegistryVisitor codeRegistryVisitor = new CodeRegistryVisitor(extractedDataFetchers); + GraphQLTypeCollectingVisitor typeCollectingVisitor = new GraphQLTypeCollectingVisitor(); + SchemaUtil.visitPartiallySchema(partiallyBuiltSchema, codeRegistryVisitor, typeCollectingVisitor); + + ImmutableMap allTypes = typeCollectingVisitor.getResult(); + List allTypesAsList = getAllTypesAsList(allTypes); + codeRegistry = extractedDataFetchers.build(); + + ImmutableMap> groupedImplementations = schemaUtil.groupInterfaceImplementationsByName(allTypesAsList); + ImmutableMap> interfaceNameToObjectTypes = buildInterfacesToObjectTypes(groupedImplementations); + + final GraphQLSchema finalSchema = new GraphQLSchema(partiallyBuiltSchema, codeRegistry, allTypes, interfaceNameToObjectTypes); + schemaUtil.replaceTypeReferences(finalSchema); + if (true) { + return validateSchema(finalSchema); + } + // + // This is is the old code here. Its not reachable but here + // to show you it. I will clean it up of course along with constructors + // we dont need + // + // grab the legacy code things from types final GraphQLSchema tempSchema = new GraphQLSchema(this, afterTransform); codeRegistry = codeRegistry.transform(codeRegistryBuilder -> schemaUtil.extractCodeFromTypes(codeRegistryBuilder, tempSchema)); GraphQLSchema graphQLSchema = new GraphQLSchema(tempSchema, codeRegistry); schemaUtil.replaceTypeReferences(graphQLSchema); + return validateSchema(graphQLSchema); + } + + private GraphQLSchema validateSchema(GraphQLSchema graphQLSchema) { Collection errors = new SchemaValidator().validateSchema(graphQLSchema); if (errors.size() > 0) { throw new InvalidSchemaException(errors); diff --git a/src/main/java/graphql/schema/SchemaTransformer.java b/src/main/java/graphql/schema/SchemaTransformer.java index 9be5be2295..8bd2fad3f7 100644 --- a/src/main/java/graphql/schema/SchemaTransformer.java +++ b/src/main/java/graphql/schema/SchemaTransformer.java @@ -26,7 +26,7 @@ import static graphql.Assert.assertShouldNeverHappen; import static graphql.schema.GraphQLSchemaElementAdapter.SCHEMA_ELEMENT_ADAPTER; import static graphql.schema.SchemaElementChildrenContainer.newSchemaElementChildrenContainer; -import static graphql.schema.StronglyConnectedComponentsTopologicallySorted.getStronglyConnectedComponentsTopologicallySorted; +import static graphql.schema.impl.StronglyConnectedComponentsTopologicallySorted.getStronglyConnectedComponentsTopologicallySorted; import static graphql.util.NodeZipper.ModificationType.REPLACE; import static graphql.util.TraversalControl.CONTINUE; import static java.lang.String.format; diff --git a/src/main/java/graphql/schema/GraphQLTypeCollectingVisitor.java b/src/main/java/graphql/schema/impl/GraphQLTypeCollectingVisitor.java similarity index 85% rename from src/main/java/graphql/schema/GraphQLTypeCollectingVisitor.java rename to src/main/java/graphql/schema/impl/GraphQLTypeCollectingVisitor.java index 22b579f081..4ac7333fe3 100644 --- a/src/main/java/graphql/schema/GraphQLTypeCollectingVisitor.java +++ b/src/main/java/graphql/schema/impl/GraphQLTypeCollectingVisitor.java @@ -1,12 +1,26 @@ -package graphql.schema; +package graphql.schema.impl; +import com.google.common.collect.ImmutableMap; import graphql.AssertException; import graphql.Internal; +import graphql.schema.GraphQLEnumType; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLInterfaceType; +import graphql.schema.GraphQLNamedType; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLScalarType; +import graphql.schema.GraphQLSchemaElement; +import graphql.schema.GraphQLType; +import graphql.schema.GraphQLTypeReference; +import graphql.schema.GraphQLTypeVisitorStub; +import graphql.schema.GraphQLUnionType; import graphql.util.TraversalControl; import graphql.util.TraverserContext; import java.util.LinkedHashMap; import java.util.Map; +import java.util.TreeMap; import static java.lang.String.format; @@ -109,7 +123,7 @@ private void assertTypeUniqueness(GraphQLNamedType type, Map getResult() { - return result; + public ImmutableMap getResult() { + return ImmutableMap.copyOf(new TreeMap<>(result)); } } diff --git a/src/main/java/graphql/schema/impl/MultiReadOnlyGraphQLTypeVisitor.java b/src/main/java/graphql/schema/impl/MultiReadOnlyGraphQLTypeVisitor.java new file mode 100644 index 0000000000..458d204b9c --- /dev/null +++ b/src/main/java/graphql/schema/impl/MultiReadOnlyGraphQLTypeVisitor.java @@ -0,0 +1,205 @@ +package graphql.schema.impl; + +import graphql.Assert; +import graphql.schema.GraphQLArgument; +import graphql.schema.GraphQLCompositeType; +import graphql.schema.GraphQLDirective; +import graphql.schema.GraphQLDirectiveContainer; +import graphql.schema.GraphQLEnumType; +import graphql.schema.GraphQLEnumValueDefinition; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLFieldsContainer; +import graphql.schema.GraphQLInputFieldsContainer; +import graphql.schema.GraphQLInputObjectField; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLInputType; +import graphql.schema.GraphQLInterfaceType; +import graphql.schema.GraphQLList; +import graphql.schema.GraphQLModifiedType; +import graphql.schema.GraphQLNonNull; +import graphql.schema.GraphQLNullableType; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLOutputType; +import graphql.schema.GraphQLScalarType; +import graphql.schema.GraphQLSchemaElement; +import graphql.schema.GraphQLTypeReference; +import graphql.schema.GraphQLTypeVisitor; +import graphql.schema.GraphQLUnionType; +import graphql.schema.GraphQLUnmodifiedType; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; + +import java.util.List; + +public class MultiReadOnlyGraphQLTypeVisitor implements GraphQLTypeVisitor { + + private final List visitors; + + public MultiReadOnlyGraphQLTypeVisitor(List visitors) { + this.visitors = visitors; + } + + @Override + public TraversalControl visitGraphQLArgument(GraphQLArgument node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLArgument(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLInterfaceType(GraphQLInterfaceType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLInterfaceType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLEnumType(GraphQLEnumType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLEnumType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLEnumValueDefinition(GraphQLEnumValueDefinition node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLEnumValueDefinition(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLFieldDefinition(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLDirective(GraphQLDirective node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLDirective(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLInputObjectField(GraphQLInputObjectField node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLInputObjectField(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLInputObjectType(GraphQLInputObjectType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLInputObjectType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLList(GraphQLList node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLList(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLNonNull(GraphQLNonNull node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLNonNull(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLObjectType(GraphQLObjectType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLObjectType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLScalarType(GraphQLScalarType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLScalarType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLTypeReference(GraphQLTypeReference node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLTypeReference(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLUnionType(GraphQLUnionType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLUnionType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitBackRef(TraverserContext context) { + visitors.forEach(v -> v.visitBackRef(context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLModifiedType(GraphQLModifiedType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLModifiedType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLCompositeType(GraphQLCompositeType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLCompositeType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLDirectiveContainer(GraphQLDirectiveContainer node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLDirectiveContainer(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLFieldsContainer(GraphQLFieldsContainer node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLFieldsContainer(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLInputFieldsContainer(GraphQLInputFieldsContainer node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLInputFieldsContainer(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLInputType(GraphQLInputType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLInputType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLNullableType(GraphQLNullableType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLNullableType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLOutputType(GraphQLOutputType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLOutputType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl visitGraphQLUnmodifiedType(GraphQLUnmodifiedType node, TraverserContext context) { + visitors.forEach(v -> v.visitGraphQLUnmodifiedType(node, context)); + return TraversalControl.CONTINUE; + } + + @Override + public TraversalControl changeNode(TraverserContext context, GraphQLSchemaElement newChangedNode) { + return Assert.assertShouldNeverHappen("This must be a read only operation"); + } + + @Override + public TraversalControl deleteNode(TraverserContext context) { + return Assert.assertShouldNeverHappen("This must be a read only operation"); + } + + @Override + public TraversalControl insertAfter(TraverserContext context, GraphQLSchemaElement toInsertAfter) { + return Assert.assertShouldNeverHappen("This must be a read only operation"); + } + + @Override + public TraversalControl insertBefore(TraverserContext context, GraphQLSchemaElement toInsertBefore) { + return Assert.assertShouldNeverHappen("This must be a read only operation"); + } +} diff --git a/src/main/java/graphql/schema/SchemaUtil.java b/src/main/java/graphql/schema/impl/SchemaUtil.java similarity index 63% rename from src/main/java/graphql/schema/SchemaUtil.java rename to src/main/java/graphql/schema/impl/SchemaUtil.java index 5fbcc4afc7..a733b8d322 100644 --- a/src/main/java/graphql/schema/SchemaUtil.java +++ b/src/main/java/graphql/schema/impl/SchemaUtil.java @@ -1,11 +1,24 @@ -package graphql.schema; +package graphql.schema.impl; import com.google.common.collect.ImmutableMap; import graphql.Internal; -import graphql.introspection.Introspection; +import graphql.schema.CodeRegistryVisitor; +import graphql.schema.GraphQLCodeRegistry; +import graphql.schema.GraphQLImplementingType; +import graphql.schema.GraphQLInterfaceType; +import graphql.schema.GraphQLNamedOutputType; +import graphql.schema.GraphQLNamedType; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLSchema; +import graphql.schema.GraphQLSchemaElement; +import graphql.schema.GraphQLType; +import graphql.schema.GraphQLTypeResolvingVisitor; +import graphql.schema.GraphQLTypeVisitor; +import graphql.schema.SchemaTraverser; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -17,8 +30,44 @@ public class SchemaUtil { private static final SchemaTraverser TRAVERSER = new SchemaTraverser(); + /** + * Called to visit a partially build schema (during {@link GraphQLSchema} build phases) with a set of visitors + * + * Each visitor is expected to hold its own side effects that might be last used to construct a full schema + * + * @param partiallyBuiltSchema the partially built schema + * @param visitors the visitors to call + */ - ImmutableMap allTypes(final GraphQLSchema schema, final Set additionalTypes, boolean afterTransform) { + public static void visitPartiallySchema(final GraphQLSchema partiallyBuiltSchema, GraphQLTypeVisitor... visitors) { + List roots = new ArrayList<>(); + roots.add(partiallyBuiltSchema.getQueryType()); + + if (partiallyBuiltSchema.isSupportingMutations()) { + roots.add(partiallyBuiltSchema.getMutationType()); + } + + if (partiallyBuiltSchema.isSupportingSubscriptions()) { + roots.add(partiallyBuiltSchema.getSubscriptionType()); + } + + if (partiallyBuiltSchema.getAdditionalTypes() != null) { + roots.addAll(partiallyBuiltSchema.getAdditionalTypes()); + } + + if (partiallyBuiltSchema.getDirectives() != null) { + roots.addAll(partiallyBuiltSchema.getDirectives()); + } + + roots.add(partiallyBuiltSchema.getIntrospectionSchemaType()); + + GraphQLTypeVisitor visitor = new MultiReadOnlyGraphQLTypeVisitor(Arrays.asList(visitors)); + SchemaTraverser traverser; + traverser = new SchemaTraverser(schemaElement -> schemaElement.getChildrenWithTypeReferences().getChildrenAsList()); + traverser.depthFirst(visitor, roots); + } + + public ImmutableMap allTypes(final GraphQLSchema schema, final Set additionalTypes, boolean afterTransform) { List roots = new ArrayList<>(); roots.add(schema.getQueryType()); @@ -66,9 +115,14 @@ ImmutableMap allTypes(final GraphQLSchema schema, fina * Provided to replace {@link #findImplementations(graphql.schema.GraphQLSchema, graphql.schema.GraphQLInterfaceType)} * */ - Map> groupImplementations(GraphQLSchema schema) { + public Map> groupImplementations(GraphQLSchema schema) { + List allTypesAsList = schema.getAllTypesAsList(); + return groupInterfaceImplementationsByName(allTypesAsList); + } + + public ImmutableMap> groupInterfaceImplementationsByName(List allTypesAsList) { Map> result = new LinkedHashMap<>(); - for (GraphQLType type : schema.getAllTypesAsList()) { + for (GraphQLType type : allTypesAsList) { if (type instanceof GraphQLObjectType) { List interfaces = ((GraphQLObjectType) type).getInterfaces(); for (GraphQLNamedOutputType interfaceType : interfaces) { @@ -127,17 +181,21 @@ public List findImplementations(GraphQLSchema schema, GraphQL return result; } - void replaceTypeReferences(GraphQLSchema schema) { + // THIS WILL BE REMOVED + public void replaceTypeReferences(GraphQLSchema schema) { final Map typeMap = schema.getTypeMap(); + replaceTypeReferences(schema, typeMap); + } + + public void replaceTypeReferences(GraphQLSchema schema, Map typeMap) { List roots = new ArrayList<>(typeMap.values()); roots.addAll(schema.getDirectives()); SchemaTraverser schemaTraverser = new SchemaTraverser(schemaElement -> schemaElement.getChildrenWithTypeReferences().getChildrenAsList()); schemaTraverser.depthFirst(new GraphQLTypeResolvingVisitor(typeMap), roots); } - void extractCodeFromTypes(GraphQLCodeRegistry.Builder codeRegistry, GraphQLSchema schema) { - Introspection.addCodeForIntrospectionTypes(codeRegistry); - + // THIS WILL BE REMOVED + public void extractCodeFromTypes(GraphQLCodeRegistry.Builder codeRegistry, GraphQLSchema schema) { TRAVERSER.depthFirst(new CodeRegistryVisitor(codeRegistry), schema.getAllTypesAsList()); } } diff --git a/src/main/java/graphql/schema/StronglyConnectedComponentsTopologicallySorted.java b/src/main/java/graphql/schema/impl/StronglyConnectedComponentsTopologicallySorted.java similarity index 98% rename from src/main/java/graphql/schema/StronglyConnectedComponentsTopologicallySorted.java rename to src/main/java/graphql/schema/impl/StronglyConnectedComponentsTopologicallySorted.java index 0b00094c4a..3ff66789ff 100644 --- a/src/main/java/graphql/schema/StronglyConnectedComponentsTopologicallySorted.java +++ b/src/main/java/graphql/schema/impl/StronglyConnectedComponentsTopologicallySorted.java @@ -1,7 +1,9 @@ -package graphql.schema; +package graphql.schema.impl; import graphql.Assert; import graphql.Internal; +import graphql.schema.GraphQLNamedType; +import graphql.schema.GraphQLSchemaElement; import java.util.ArrayDeque; import java.util.ArrayList; diff --git a/src/main/java/graphql/util/Anonymizer.java b/src/main/java/graphql/util/Anonymizer.java index afa0384e4c..9b7812dfc4 100644 --- a/src/main/java/graphql/util/Anonymizer.java +++ b/src/main/java/graphql/util/Anonymizer.java @@ -66,7 +66,7 @@ import graphql.schema.GraphQLTypeVisitorStub; import graphql.schema.GraphQLUnionType; import graphql.schema.SchemaTransformer; -import graphql.schema.SchemaUtil; +import graphql.schema.impl.SchemaUtil; import graphql.schema.TypeResolver; import graphql.schema.idl.DirectiveInfo; import graphql.schema.idl.ScalarInfo; diff --git a/src/test/groovy/graphql/schema/SchemaUtilTest.groovy b/src/test/groovy/graphql/schema/impl/SchemaUtilTest.groovy similarity index 96% rename from src/test/groovy/graphql/schema/SchemaUtilTest.groovy rename to src/test/groovy/graphql/schema/impl/SchemaUtilTest.groovy index cdd97689a4..07b63eca56 100644 --- a/src/test/groovy/graphql/schema/SchemaUtilTest.groovy +++ b/src/test/groovy/graphql/schema/impl/SchemaUtilTest.groovy @@ -1,9 +1,17 @@ -package graphql.schema +package graphql.schema.impl import graphql.AssertException import graphql.DirectivesUtil import graphql.NestedInputSchema import graphql.introspection.Introspection +import graphql.schema.GraphQLArgument +import graphql.schema.GraphQLFieldDefinition +import graphql.schema.GraphQLInputObjectType +import graphql.schema.GraphQLObjectType +import graphql.schema.GraphQLType +import graphql.schema.GraphQLTypeReference +import graphql.schema.GraphQLUnionType +import graphql.schema.impl.SchemaUtil import spock.lang.Specification import static graphql.Scalars.GraphQLBoolean diff --git a/src/test/java/benchmark/SchemaBenchMark.java b/src/test/java/benchmark/SchemaBenchMark.java new file mode 100644 index 0000000000..32c16c52d0 --- /dev/null +++ b/src/test/java/benchmark/SchemaBenchMark.java @@ -0,0 +1,72 @@ +package benchmark; + +import com.google.common.io.Files; +import graphql.schema.GraphQLSchema; +import graphql.schema.idl.RuntimeWiring; +import graphql.schema.idl.SchemaGenerator; +import graphql.schema.idl.SchemaParser; +import graphql.schema.idl.TypeDefinitionRegistry; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.io.File; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.concurrent.TimeUnit; + +/** + * This benchmarks schema creation + *

+ * See https://github.com/openjdk/jmh/tree/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/ for more samples + * on what you can do with JMH + *

+ * You MUST have the JMH plugin for IDEA in place for this to work : https://github.com/artyushov/idea-jmh-plugin + *

+ * Install it and then just hit "Run" on a certain benchmark method + */ +@Warmup(iterations = 2, time = 5, batchSize = 3) +@Measurement(iterations = 3, time = 10, batchSize = 4) +public class SchemaBenchMark { + + static String largeSDL = createResourceSDL("large-schema-1.graphqls"); + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.SECONDS) + public void benchMarkLargeSchemaCreate(Blackhole blackhole) { + blackhole.consume(createSchema(largeSDL)); + } + + private static GraphQLSchema createSchema(String sdl) { + TypeDefinitionRegistry registry = new SchemaParser().parse(sdl); + return new SchemaGenerator().makeExecutableSchema(registry, RuntimeWiring.MOCKED_WIRING); + } + + private static String createResourceSDL(String name) { + try { + URL resource = SchemaBenchMark.class.getClassLoader().getResource(name); + File file = new File(resource.toURI()); + return String.join("\n", Files.readLines(file, Charset.defaultCharset())); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + @SuppressWarnings("InfiniteLoopStatement") + public static void main(String[] args) { + int i = 0; + while (true) { + createSchema(largeSDL); + i++; + if (i % 100 == 0) { + System.out.printf("%d\n", i); + } + } + } +} \ No newline at end of file From 1ebd35feed1dc1e07b033c74bbc6d5d8cbf57f27 Mon Sep 17 00:00:00 2001 From: Brad Baker Date: Sun, 18 Jul 2021 17:56:16 +1000 Subject: [PATCH 2/2] More work on refactoring o schema to be faster --- .../java/graphql/schema/GraphQLSchema.java | 106 +++--------------- .../graphql/schema/SchemaTransformer.java | 3 +- .../impl/MultiReadOnlyGraphQLTypeVisitor.java | 6 + .../java/graphql/schema/impl/SchemaUtil.java | 106 +----------------- .../graphql/schema/impl/SchemaUtilTest.groovy | 25 +++-- 5 files changed, 42 insertions(+), 204 deletions(-) diff --git a/src/main/java/graphql/schema/GraphQLSchema.java b/src/main/java/graphql/schema/GraphQLSchema.java index dfa250df58..5b5316555f 100644 --- a/src/main/java/graphql/schema/GraphQLSchema.java +++ b/src/main/java/graphql/schema/GraphQLSchema.java @@ -53,7 +53,7 @@ public class GraphQLSchema { private final GraphQLObjectType subscriptionType; private final GraphQLObjectType introspectionSchemaType; private final ImmutableSet additionalTypes; - private final GraphQLFieldDefinition intospectionSchemaField; + private final GraphQLFieldDefinition introspectionSchemaField; private final GraphQLFieldDefinition introspectionTypeField; // we don't allow modification of "__typename" - its a scalar private final GraphQLFieldDefinition __typename = Introspection.TypeNameMetaFieldDef; @@ -61,6 +61,7 @@ public class GraphQLSchema { private final DirectivesUtil.DirectivesHolder schemaDirectives; private final SchemaDefinition definition; private final ImmutableList extensionDefinitions; + private final String description; private final GraphQLCodeRegistry codeRegistry; @@ -68,8 +69,6 @@ public class GraphQLSchema { private final ImmutableMap> interfaceNameToObjectTypes; private final ImmutableMap> interfaceNameToObjectTypeNames; - private final String description; - /* * This constructs partial GraphQL schema object which has has the schema (query / mutation / subscription) trees * in it but it does not have the collected types, code registry nor the type references replaced @@ -84,13 +83,12 @@ private GraphQLSchema(Builder builder) { assertNotNull(builder.additionalDirectives, () -> "directives can't be null"); assertNotNull(builder.codeRegistry, () -> "codeRegistry can't be null"); - this.queryType = builder.queryType; this.mutationType = builder.mutationType; this.subscriptionType = builder.subscriptionType; this.additionalTypes = ImmutableSet.copyOf(builder.additionalTypes); this.introspectionSchemaType = builder.introspectionSchemaType; - this.intospectionSchemaField = Introspection.buildSchemaField(builder.introspectionSchemaType); + this.introspectionSchemaField = Introspection.buildSchemaField(builder.introspectionSchemaType); this.introspectionTypeField = Introspection.buildTypeField(builder.introspectionSchemaType); this.directives = new DirectivesUtil.DirectivesHolder(builder.additionalDirectives); this.schemaDirectives = new DirectivesUtil.DirectivesHolder(builder.schemaDirectives); @@ -110,14 +108,15 @@ private GraphQLSchema(Builder builder) { */ @Internal public GraphQLSchema(GraphQLSchema partiallyBuiltSchema, - GraphQLCodeRegistry codeRegistry, ImmutableMap typeMap, + GraphQLCodeRegistry codeRegistry, + ImmutableMap typeMap, ImmutableMap> interfaceNameToObjectTypes) { this.queryType = partiallyBuiltSchema.queryType; this.mutationType = partiallyBuiltSchema.mutationType; this.subscriptionType = partiallyBuiltSchema.subscriptionType; this.additionalTypes = ImmutableSet.copyOf(partiallyBuiltSchema.additionalTypes); this.introspectionSchemaType = partiallyBuiltSchema.introspectionSchemaType; - this.intospectionSchemaField = Introspection.buildSchemaField(partiallyBuiltSchema.introspectionSchemaType); + this.introspectionSchemaField = Introspection.buildSchemaField(partiallyBuiltSchema.introspectionSchemaType); this.introspectionTypeField = Introspection.buildTypeField(partiallyBuiltSchema.introspectionSchemaType); this.directives = partiallyBuiltSchema.directives; this.schemaDirectives = partiallyBuiltSchema.schemaDirectives; @@ -130,61 +129,6 @@ public GraphQLSchema(GraphQLSchema partiallyBuiltSchema, interfaceNameToObjectTypeNames = buildInterfacesToObjectName(interfaceNameToObjectTypes); } - // THIS WILL BE REMOVED - @Internal - private GraphQLSchema(Builder builder, boolean afterTransform) { - assertNotNull(builder.additionalTypes, () -> "additionalTypes can't be null"); - assertNotNull(builder.queryType, () -> "queryType can't be null"); - assertNotNull(builder.additionalDirectives, () -> "directives can't be null"); - assertNotNull(builder.codeRegistry, () -> "codeRegistry can't be null"); - - - this.queryType = builder.queryType; - this.mutationType = builder.mutationType; - this.subscriptionType = builder.subscriptionType; - this.additionalTypes = ImmutableSet.copyOf(builder.additionalTypes); - this.introspectionSchemaType = builder.introspectionSchemaType; - this.intospectionSchemaField = Introspection.buildSchemaField(builder.introspectionSchemaType); - this.introspectionTypeField = Introspection.buildTypeField(builder.introspectionSchemaType); - this.directives = new DirectivesUtil.DirectivesHolder(builder.additionalDirectives); - this.schemaDirectives = new DirectivesUtil.DirectivesHolder(builder.schemaDirectives); - this.definition = builder.definition; - this.extensionDefinitions = nonNullCopyOf(builder.extensionDefinitions); - this.codeRegistry = builder.codeRegistry; - // sorted by type name - SchemaUtil schemaUtil = new SchemaUtil(); - this.typeMap = ImmutableMap.copyOf(schemaUtil.allTypes(this, additionalTypes, afterTransform)); - this.interfaceNameToObjectTypes = buildInterfacesToObjectTypes(schemaUtil.groupImplementations(this)); - this.interfaceNameToObjectTypeNames = buildInterfacesToObjectName(interfaceNameToObjectTypes); - this.description = builder.description; - } - - // This can be removed once we no longer extract legacy code from types such as data fetchers but for now - // we need it to make an efficient copy that does not walk the types twice - - // THIS WILL BE REMOVED - @Internal - private GraphQLSchema(GraphQLSchema otherSchema, GraphQLCodeRegistry codeRegistry) { - this.queryType = otherSchema.queryType; - this.mutationType = otherSchema.mutationType; - this.subscriptionType = otherSchema.subscriptionType; - this.introspectionSchemaType = otherSchema.introspectionSchemaType; - this.additionalTypes = otherSchema.additionalTypes; - this.intospectionSchemaField = otherSchema.intospectionSchemaField; - this.introspectionTypeField = otherSchema.introspectionTypeField; - this.directives = otherSchema.directives; - this.schemaDirectives = otherSchema.schemaDirectives; - this.definition = otherSchema.definition; - this.extensionDefinitions = nonNullCopyOf(otherSchema.extensionDefinitions); - this.codeRegistry = codeRegistry; - - this.typeMap = otherSchema.typeMap; - this.interfaceNameToObjectTypes = otherSchema.interfaceNameToObjectTypes; - this.interfaceNameToObjectTypeNames = otherSchema.interfaceNameToObjectTypeNames; - this.description = otherSchema.description; - } - - /** * @return a new schema builder */ @@ -250,7 +194,7 @@ public GraphQLCodeRegistry getCodeRegistry() { * @return the special system field called "__schema" */ public GraphQLFieldDefinition getIntrospectionSchemaFieldDefinition() { - return intospectionSchemaField; + return introspectionSchemaField; } /** @@ -564,18 +508,16 @@ public static class Builder { private GraphQLObjectType introspectionSchemaType = Introspection.__Schema; private GraphQLObjectType subscriptionType; private GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry().build(); - private Set additionalTypes = new LinkedHashSet<>(); private SchemaDefinition definition; private List extensionDefinitions; private String description; // we default these in - private Set additionalDirectives = new LinkedHashSet<>( + private final Set additionalDirectives = new LinkedHashSet<>( asList(Directives.IncludeDirective, Directives.SkipDirective) ); - private List schemaDirectives = new ArrayList<>(); - - private SchemaUtil schemaUtil = new SchemaUtil(); + private final Set additionalTypes = new LinkedHashSet<>(); + private final List schemaDirectives = new ArrayList<>(); public Builder query(GraphQLObjectType.Builder builder) { return query(builder.build()); @@ -742,10 +684,10 @@ public GraphQLSchema build(Set additionalTypes, Set "additionalTypes can't be null"); assertNotNull(additionalDirectives, () -> "additionalDirectives can't be null"); @@ -767,31 +709,17 @@ GraphQLSchema buildImpl(boolean afterTransform) { GraphQLTypeCollectingVisitor typeCollectingVisitor = new GraphQLTypeCollectingVisitor(); SchemaUtil.visitPartiallySchema(partiallyBuiltSchema, codeRegistryVisitor, typeCollectingVisitor); + codeRegistry = extractedDataFetchers.build(); ImmutableMap allTypes = typeCollectingVisitor.getResult(); List allTypesAsList = getAllTypesAsList(allTypes); - codeRegistry = extractedDataFetchers.build(); - ImmutableMap> groupedImplementations = schemaUtil.groupInterfaceImplementationsByName(allTypesAsList); + ImmutableMap> groupedImplementations = SchemaUtil.groupInterfaceImplementationsByName(allTypesAsList); ImmutableMap> interfaceNameToObjectTypes = buildInterfacesToObjectTypes(groupedImplementations); + // this is now build however its contained types are still to be mutated by type reference replacement final GraphQLSchema finalSchema = new GraphQLSchema(partiallyBuiltSchema, codeRegistry, allTypes, interfaceNameToObjectTypes); - schemaUtil.replaceTypeReferences(finalSchema); - if (true) { - return validateSchema(finalSchema); - } - // - // This is is the old code here. Its not reachable but here - // to show you it. I will clean it up of course along with constructors - // we dont need - // - - // grab the legacy code things from types - final GraphQLSchema tempSchema = new GraphQLSchema(this, afterTransform); - codeRegistry = codeRegistry.transform(codeRegistryBuilder -> schemaUtil.extractCodeFromTypes(codeRegistryBuilder, tempSchema)); - - GraphQLSchema graphQLSchema = new GraphQLSchema(tempSchema, codeRegistry); - schemaUtil.replaceTypeReferences(graphQLSchema); - return validateSchema(graphQLSchema); + SchemaUtil.replaceTypeReferences(finalSchema); + return validateSchema(finalSchema); } private GraphQLSchema validateSchema(GraphQLSchema graphQLSchema) { diff --git a/src/main/java/graphql/schema/SchemaTransformer.java b/src/main/java/graphql/schema/SchemaTransformer.java index 6659d078b4..cf0ba71974 100644 --- a/src/main/java/graphql/schema/SchemaTransformer.java +++ b/src/main/java/graphql/schema/SchemaTransformer.java @@ -26,7 +26,6 @@ import static graphql.Assert.assertShouldNeverHappen; import static graphql.schema.GraphQLSchemaElementAdapter.SCHEMA_ELEMENT_ADAPTER; import static graphql.schema.SchemaElementChildrenContainer.newSchemaElementChildrenContainer; -import static graphql.schema.StronglyConnectedComponentsTopologicallySorted.getStronglyConnectedComponentsTopologicallySorted; import static graphql.util.NodeZipper.ModificationType.DELETE; import static graphql.schema.impl.StronglyConnectedComponentsTopologicallySorted.getStronglyConnectedComponentsTopologicallySorted; import static graphql.util.NodeZipper.ModificationType.REPLACE; @@ -545,7 +544,7 @@ public GraphQLSchema rebuildSchema(GraphQLCodeRegistry.Builder codeRegistry) { .withSchemaDirectives(this.schemaDirectives) .codeRegistry(codeRegistry.build()) .description(schema.getDescription()) - .buildImpl(true); + .build(); } } } diff --git a/src/main/java/graphql/schema/impl/MultiReadOnlyGraphQLTypeVisitor.java b/src/main/java/graphql/schema/impl/MultiReadOnlyGraphQLTypeVisitor.java index 458d204b9c..8950abd8cc 100644 --- a/src/main/java/graphql/schema/impl/MultiReadOnlyGraphQLTypeVisitor.java +++ b/src/main/java/graphql/schema/impl/MultiReadOnlyGraphQLTypeVisitor.java @@ -1,6 +1,7 @@ package graphql.schema.impl; import graphql.Assert; +import graphql.Internal; import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLCompositeType; import graphql.schema.GraphQLDirective; @@ -31,6 +32,11 @@ import java.util.List; +/** + * A delegating type visitor that allows you to call N visitors in a list + * and always continues via {@link TraversalControl#CONTINUE} + */ +@Internal public class MultiReadOnlyGraphQLTypeVisitor implements GraphQLTypeVisitor { private final List visitors; diff --git a/src/main/java/graphql/schema/impl/SchemaUtil.java b/src/main/java/graphql/schema/impl/SchemaUtil.java index a733b8d322..0eaf2fb553 100644 --- a/src/main/java/graphql/schema/impl/SchemaUtil.java +++ b/src/main/java/graphql/schema/impl/SchemaUtil.java @@ -3,10 +3,7 @@ import com.google.common.collect.ImmutableMap; import graphql.Internal; -import graphql.schema.CodeRegistryVisitor; -import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLImplementingType; -import graphql.schema.GraphQLInterfaceType; import graphql.schema.GraphQLNamedOutputType; import graphql.schema.GraphQLNamedType; import graphql.schema.GraphQLObjectType; @@ -22,14 +19,11 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.TreeMap; @Internal public class SchemaUtil { - private static final SchemaTraverser TRAVERSER = new SchemaTraverser(); - /** * Called to visit a partially build schema (during {@link GraphQLSchema} build phases) with a set of visitors * @@ -67,60 +61,7 @@ public static void visitPartiallySchema(final GraphQLSchema partiallyBuiltSchema traverser.depthFirst(visitor, roots); } - public ImmutableMap allTypes(final GraphQLSchema schema, final Set additionalTypes, boolean afterTransform) { - List roots = new ArrayList<>(); - roots.add(schema.getQueryType()); - - if (schema.isSupportingMutations()) { - roots.add(schema.getMutationType()); - } - - if (schema.isSupportingSubscriptions()) { - roots.add(schema.getSubscriptionType()); - } - - if (additionalTypes != null) { - roots.addAll(additionalTypes); - } - - if (schema.getDirectives() != null) { - roots.addAll(schema.getDirectives()); - } - - roots.add(schema.getIntrospectionSchemaType()); - - GraphQLTypeCollectingVisitor visitor = new GraphQLTypeCollectingVisitor(); - SchemaTraverser traverser; - // when collecting all types we never want to follow type references - // When a schema is build first the type references are not replaced, so - // this is not a problem. But when a schema is transformed, - // the type references are actually replaced so we need to make sure we - // use the original type references - if (afterTransform) { - traverser = new SchemaTraverser(schemaElement -> schemaElement.getChildrenWithTypeReferences().getChildrenAsList()); - } else { - traverser = new SchemaTraverser(); - } - traverser.depthFirst(visitor, roots); - Map result = visitor.getResult(); - return ImmutableMap.copyOf(new TreeMap<>(result)); - } - - - /* - * Indexes GraphQLObject types registered with the provided schema by implemented GraphQLInterface name - * - * This helps in accelerates/simplifies collecting types that implement a certain interface - * - * Provided to replace {@link #findImplementations(graphql.schema.GraphQLSchema, graphql.schema.GraphQLInterfaceType)} - * - */ - public Map> groupImplementations(GraphQLSchema schema) { - List allTypesAsList = schema.getAllTypesAsList(); - return groupInterfaceImplementationsByName(allTypesAsList); - } - - public ImmutableMap> groupInterfaceImplementationsByName(List allTypesAsList) { + public static ImmutableMap> groupInterfaceImplementationsByName(List allTypesAsList) { Map> result = new LinkedHashMap<>(); for (GraphQLType type : allTypesAsList) { if (type instanceof GraphQLObjectType) { @@ -148,54 +89,11 @@ public Map> groupImplementationsForInterfa return ImmutableMap.copyOf(new TreeMap<>(result)); } - /** - * This method is deprecated due to a performance concern. - * - * The Algorithm complexity: O(n^2), where n is number of registered GraphQLTypes - * - * That indexing operation is performed twice per input document: - * 1. during validation - * 2. during execution - * - * We now indexed all types at the schema creation, which has brought complexity down to O(1) - * - * @param schema GraphQL schema - * @param interfaceType an interface type to find implementations for - * - * @return List of object types implementing provided interface - * - * @deprecated use {@link graphql.schema.GraphQLSchema#getImplementations(GraphQLInterfaceType)} instead - */ - @Deprecated - public List findImplementations(GraphQLSchema schema, GraphQLInterfaceType interfaceType) { - List result = new ArrayList<>(); - for (GraphQLType type : schema.getAllTypesAsList()) { - if (!(type instanceof GraphQLObjectType)) { - continue; - } - GraphQLObjectType objectType = (GraphQLObjectType) type; - if ((objectType).getInterfaces().contains(interfaceType)) { - result.add(objectType); - } - } - return result; - } - - // THIS WILL BE REMOVED - public void replaceTypeReferences(GraphQLSchema schema) { + public static void replaceTypeReferences(GraphQLSchema schema) { final Map typeMap = schema.getTypeMap(); - replaceTypeReferences(schema, typeMap); - } - - public void replaceTypeReferences(GraphQLSchema schema, Map typeMap) { List roots = new ArrayList<>(typeMap.values()); roots.addAll(schema.getDirectives()); SchemaTraverser schemaTraverser = new SchemaTraverser(schemaElement -> schemaElement.getChildrenWithTypeReferences().getChildrenAsList()); schemaTraverser.depthFirst(new GraphQLTypeResolvingVisitor(typeMap), roots); } - - // THIS WILL BE REMOVED - public void extractCodeFromTypes(GraphQLCodeRegistry.Builder codeRegistry, GraphQLSchema schema) { - TRAVERSER.depthFirst(new CodeRegistryVisitor(codeRegistry), schema.getAllTypesAsList()); - } } diff --git a/src/test/groovy/graphql/schema/impl/SchemaUtilTest.groovy b/src/test/groovy/graphql/schema/impl/SchemaUtilTest.groovy index 07b63eca56..5b39a84aeb 100644 --- a/src/test/groovy/graphql/schema/impl/SchemaUtilTest.groovy +++ b/src/test/groovy/graphql/schema/impl/SchemaUtilTest.groovy @@ -50,7 +50,9 @@ class SchemaUtilTest extends Specification { def "collectAllTypes"() { when: - Map types = new SchemaUtil().allTypes(starWarsSchema, Collections.emptySet(), false) + def collectingVisitor = new GraphQLTypeCollectingVisitor() + SchemaUtil.visitPartiallySchema(starWarsSchema, collectingVisitor) + Map types = collectingVisitor.getResult() then: types.size() == 17 types == [(droidType.name) : droidType, @@ -74,7 +76,9 @@ class SchemaUtilTest extends Specification { def "collectAllTypesNestedInput"() { when: - Map types = new SchemaUtil().allTypes(NestedInputSchema.createSchema(), Collections.emptySet(), false) + def collectingVisitor = new GraphQLTypeCollectingVisitor() + SchemaUtil.visitPartiallySchema(NestedInputSchema.createSchema(), collectingVisitor) + Map types = collectingVisitor.getResult() Map expected = [(NestedInputSchema.rootType().name) : NestedInputSchema.rootType(), @@ -97,7 +101,10 @@ class SchemaUtilTest extends Specification { def "collect all types defined in directives"() { when: - Map types = new SchemaUtil().allTypes(SchemaWithReferences, Collections.emptySet(), false) + def collectingVisitor = new GraphQLTypeCollectingVisitor() + SchemaUtil.visitPartiallySchema(SchemaWithReferences, collectingVisitor) + Map types = collectingVisitor.getResult() + then: types.size() == 30 types.containsValue(UnionDirectiveInput) @@ -114,13 +121,13 @@ class SchemaUtilTest extends Specification { def "group all types by implemented interface"() { when: - Map> byInterface = new SchemaUtil().groupImplementations(starWarsSchema) + Map> byInterface = SchemaUtil.groupInterfaceImplementationsByName(starWarsSchema.getAllTypesAsList()) then: byInterface.size() == 1 byInterface[characterInterface.getName()].size() == 2 byInterface == [ - (characterInterface.getName()): [ droidType, humanType] + (characterInterface.getName()): [droidType, humanType] ] } @@ -129,16 +136,16 @@ class SchemaUtilTest extends Specification { GraphQLInputObjectType PersonInputType = newInputObject() .name("Person") .field(newInputObjectField() - .name("name") - .type(GraphQLString)) + .name("name") + .type(GraphQLString)) .build() GraphQLFieldDefinition field = newFieldDefinition() .name("find") .type(typeRef("Person")) .argument(newArgument() - .name("ssn") - .type(GraphQLString)) + .name("ssn") + .type(GraphQLString)) .build() GraphQLObjectType PersonService = newObject()