Skip to content

Commit 128e3d5

Browse files
authored
Initial work on Traversal Improvements in GraphqlSchema builds (graphql-java#2463)
* Initial work on Traversal Improvements in GraphqlSchema builds * More work on refactoring o schema to be faster
1 parent 5f69ce6 commit 128e3d5

10 files changed

Lines changed: 450 additions & 213 deletions

src/main/java/graphql/schema/CodeRegistryVisitor.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
package graphql.schema;
22

33
import graphql.Internal;
4+
import graphql.introspection.Introspection;
5+
import graphql.schema.DataFetcher;
6+
import graphql.schema.FieldCoordinates;
7+
import graphql.schema.GraphQLCodeRegistry;
8+
import graphql.schema.GraphQLFieldDefinition;
9+
import graphql.schema.GraphQLFieldsContainer;
10+
import graphql.schema.GraphQLInterfaceType;
11+
import graphql.schema.GraphQLSchemaElement;
12+
import graphql.schema.GraphQLTypeVisitorStub;
13+
import graphql.schema.GraphQLUnionType;
14+
import graphql.schema.TypeResolver;
415
import graphql.util.TraversalControl;
516
import graphql.util.TraverserContext;
617

@@ -12,11 +23,12 @@
1223
* This ensure that all fields have data fetchers and that unions and interfaces have type resolvers
1324
*/
1425
@Internal
15-
class CodeRegistryVisitor extends GraphQLTypeVisitorStub {
26+
public class CodeRegistryVisitor extends GraphQLTypeVisitorStub {
1627
private final GraphQLCodeRegistry.Builder codeRegistry;
1728

18-
CodeRegistryVisitor(GraphQLCodeRegistry.Builder codeRegistry) {
29+
public CodeRegistryVisitor(GraphQLCodeRegistry.Builder codeRegistry) {
1930
this.codeRegistry = codeRegistry;
31+
Introspection.addCodeForIntrospectionTypes(codeRegistry);
2032
}
2133

2234
@Override
@@ -27,7 +39,7 @@ public TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node,
2739
FieldCoordinates coordinates = coordinates(parentContainerType, node);
2840
codeRegistry.dataFetcherIfAbsent(coordinates, dataFetcher);
2941
}
30-
42+
3143
return CONTINUE;
3244
}
3345

@@ -38,7 +50,7 @@ public TraversalControl visitGraphQLInterfaceType(GraphQLInterfaceType node, Tra
3850
codeRegistry.typeResolverIfAbsent(node, typeResolver);
3951
}
4052
assertTrue(codeRegistry.getTypeResolver(node) != null,
41-
() -> String.format("You MUST provide a type resolver for the interface type '%s'",node.getName()));
53+
() -> String.format("You MUST provide a type resolver for the interface type '%s'", node.getName()));
4254
return CONTINUE;
4355
}
4456

src/main/java/graphql/schema/GraphQLSchema.java

Lines changed: 76 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
import graphql.DirectivesUtil;
99
import graphql.Internal;
1010
import graphql.PublicApi;
11+
import graphql.collect.ImmutableKit;
1112
import graphql.introspection.Introspection;
1213
import graphql.language.SchemaDefinition;
1314
import graphql.language.SchemaExtensionDefinition;
15+
import graphql.schema.impl.GraphQLTypeCollectingVisitor;
16+
import graphql.schema.impl.SchemaUtil;
1417
import graphql.schema.validation.InvalidSchemaException;
1518
import graphql.schema.validation.SchemaValidationError;
1619
import graphql.schema.validation.SchemaValidator;
@@ -45,79 +48,85 @@
4548
@PublicApi
4649
public class GraphQLSchema {
4750

48-
4951
private final GraphQLObjectType queryType;
5052
private final GraphQLObjectType mutationType;
5153
private final GraphQLObjectType subscriptionType;
5254
private final GraphQLObjectType introspectionSchemaType;
5355
private final ImmutableSet<GraphQLType> additionalTypes;
54-
private final GraphQLFieldDefinition intospectionSchemaField;
56+
private final GraphQLFieldDefinition introspectionSchemaField;
5557
private final GraphQLFieldDefinition introspectionTypeField;
5658
// we don't allow modification of "__typename" - its a scalar
5759
private final GraphQLFieldDefinition __typename = Introspection.TypeNameMetaFieldDef;
5860
private final DirectivesUtil.DirectivesHolder directives;
5961
private final DirectivesUtil.DirectivesHolder schemaDirectives;
6062
private final SchemaDefinition definition;
6163
private final ImmutableList<SchemaExtensionDefinition> extensionDefinitions;
64+
private final String description;
6265

6366
private final GraphQLCodeRegistry codeRegistry;
6467

6568
private final ImmutableMap<String, GraphQLNamedType> typeMap;
6669
private final ImmutableMap<String, ImmutableList<GraphQLObjectType>> interfaceNameToObjectTypes;
6770
private final ImmutableMap<String, ImmutableList<String>> interfaceNameToObjectTypeNames;
6871

69-
private final String description;
70-
72+
/*
73+
* This constructs partial GraphQL schema object which has has the schema (query / mutation / subscription) trees
74+
* in it but it does not have the collected types, code registry nor the type references replaced
75+
*
76+
* But it can be traversed to discover all that and filled out later via another constructor.
77+
*
78+
*/
7179
@Internal
72-
private GraphQLSchema(Builder builder, boolean afterTransform) {
80+
private GraphQLSchema(Builder builder) {
7381
assertNotNull(builder.additionalTypes, () -> "additionalTypes can't be null");
7482
assertNotNull(builder.queryType, () -> "queryType can't be null");
7583
assertNotNull(builder.additionalDirectives, () -> "directives can't be null");
7684
assertNotNull(builder.codeRegistry, () -> "codeRegistry can't be null");
7785

78-
7986
this.queryType = builder.queryType;
8087
this.mutationType = builder.mutationType;
8188
this.subscriptionType = builder.subscriptionType;
8289
this.additionalTypes = ImmutableSet.copyOf(builder.additionalTypes);
8390
this.introspectionSchemaType = builder.introspectionSchemaType;
84-
this.intospectionSchemaField = Introspection.buildSchemaField(builder.introspectionSchemaType);
91+
this.introspectionSchemaField = Introspection.buildSchemaField(builder.introspectionSchemaType);
8592
this.introspectionTypeField = Introspection.buildTypeField(builder.introspectionSchemaType);
8693
this.directives = new DirectivesUtil.DirectivesHolder(builder.additionalDirectives);
8794
this.schemaDirectives = new DirectivesUtil.DirectivesHolder(builder.schemaDirectives);
8895
this.definition = builder.definition;
8996
this.extensionDefinitions = nonNullCopyOf(builder.extensionDefinitions);
90-
this.codeRegistry = builder.codeRegistry;
91-
// sorted by type name
92-
SchemaUtil schemaUtil = new SchemaUtil();
93-
this.typeMap = ImmutableMap.copyOf(schemaUtil.allTypes(this, additionalTypes, afterTransform));
94-
this.interfaceNameToObjectTypes = buildInterfacesToObjectTypes(schemaUtil.groupImplementations(this));
95-
this.interfaceNameToObjectTypeNames = buildInterfacesToObjectName(interfaceNameToObjectTypes);
9697
this.description = builder.description;
97-
}
9898

99-
// This can be removed once we no longer extract legacy code from types such as data fetchers but for now
100-
// we need it to make an efficient copy that does not walk the types twice
99+
this.codeRegistry = null;
100+
this.typeMap = ImmutableKit.emptyMap();
101+
this.interfaceNameToObjectTypes = ImmutableKit.emptyMap();
102+
this.interfaceNameToObjectTypeNames = ImmutableKit.emptyMap();
103+
}
101104

105+
/*
106+
* This constructs a full fledged graphql schema object that has not yet had its type references replaced
107+
* but its otherwise complete
108+
*/
102109
@Internal
103-
private GraphQLSchema(GraphQLSchema otherSchema, GraphQLCodeRegistry codeRegistry) {
104-
this.queryType = otherSchema.queryType;
105-
this.mutationType = otherSchema.mutationType;
106-
this.subscriptionType = otherSchema.subscriptionType;
107-
this.introspectionSchemaType = otherSchema.introspectionSchemaType;
108-
this.additionalTypes = otherSchema.additionalTypes;
109-
this.intospectionSchemaField = otherSchema.intospectionSchemaField;
110-
this.introspectionTypeField = otherSchema.introspectionTypeField;
111-
this.directives = otherSchema.directives;
112-
this.schemaDirectives = otherSchema.schemaDirectives;
113-
this.definition = otherSchema.definition;
114-
this.extensionDefinitions = nonNullCopyOf(otherSchema.extensionDefinitions);
110+
public GraphQLSchema(GraphQLSchema partiallyBuiltSchema,
111+
GraphQLCodeRegistry codeRegistry,
112+
ImmutableMap<String, GraphQLNamedType> typeMap,
113+
ImmutableMap<String, ImmutableList<GraphQLObjectType>> interfaceNameToObjectTypes) {
114+
this.queryType = partiallyBuiltSchema.queryType;
115+
this.mutationType = partiallyBuiltSchema.mutationType;
116+
this.subscriptionType = partiallyBuiltSchema.subscriptionType;
117+
this.additionalTypes = ImmutableSet.copyOf(partiallyBuiltSchema.additionalTypes);
118+
this.introspectionSchemaType = partiallyBuiltSchema.introspectionSchemaType;
119+
this.introspectionSchemaField = Introspection.buildSchemaField(partiallyBuiltSchema.introspectionSchemaType);
120+
this.introspectionTypeField = Introspection.buildTypeField(partiallyBuiltSchema.introspectionSchemaType);
121+
this.directives = partiallyBuiltSchema.directives;
122+
this.schemaDirectives = partiallyBuiltSchema.schemaDirectives;
123+
this.definition = partiallyBuiltSchema.definition;
124+
this.extensionDefinitions = partiallyBuiltSchema.extensionDefinitions;
125+
this.description = partiallyBuiltSchema.description;
115126
this.codeRegistry = codeRegistry;
116-
117-
this.typeMap = otherSchema.typeMap;
118-
this.interfaceNameToObjectTypes = otherSchema.interfaceNameToObjectTypes;
119-
this.interfaceNameToObjectTypeNames = otherSchema.interfaceNameToObjectTypeNames;
120-
this.description = otherSchema.description;
127+
this.typeMap = typeMap;
128+
this.interfaceNameToObjectTypes = interfaceNameToObjectTypes;
129+
interfaceNameToObjectTypeNames = buildInterfacesToObjectName(interfaceNameToObjectTypes);
121130
}
122131

123132
/**
@@ -155,7 +164,11 @@ private static GraphQLDirective[] schemaDirectivesArray(GraphQLSchema existingSc
155164
return existingSchema.schemaDirectives.getDirectives().toArray(new GraphQLDirective[0]);
156165
}
157166

158-
private ImmutableMap<String, ImmutableList<GraphQLObjectType>> buildInterfacesToObjectTypes(Map<String, List<GraphQLObjectType>> groupImplementations) {
167+
private static List<GraphQLNamedType> getAllTypesAsList(ImmutableMap<String, GraphQLNamedType> typeMap) {
168+
return sortTypes(byNameAsc(), typeMap.values());
169+
}
170+
171+
private static ImmutableMap<String, ImmutableList<GraphQLObjectType>> buildInterfacesToObjectTypes(Map<String, List<GraphQLObjectType>> groupImplementations) {
159172
ImmutableMap.Builder<String, ImmutableList<GraphQLObjectType>> map = ImmutableMap.builder();
160173
for (Map.Entry<String, List<GraphQLObjectType>> e : groupImplementations.entrySet()) {
161174
ImmutableList<GraphQLObjectType> sortedObjectTypes = ImmutableList.copyOf(sortTypes(byNameAsc(), e.getValue()));
@@ -164,7 +177,7 @@ private ImmutableMap<String, ImmutableList<GraphQLObjectType>> buildInterfacesTo
164177
return map.build();
165178
}
166179

167-
private ImmutableMap<String, ImmutableList<String>> buildInterfacesToObjectName(ImmutableMap<String, ImmutableList<GraphQLObjectType>> byInterface) {
180+
private static ImmutableMap<String, ImmutableList<String>> buildInterfacesToObjectName(ImmutableMap<String, ImmutableList<GraphQLObjectType>> byInterface) {
168181
ImmutableMap.Builder<String, ImmutableList<String>> map = ImmutableMap.builder();
169182
for (Map.Entry<String, ImmutableList<GraphQLObjectType>> e : byInterface.entrySet()) {
170183
ImmutableList<String> objectTypeNames = map(e.getValue(), GraphQLObjectType::getName);
@@ -181,7 +194,7 @@ public GraphQLCodeRegistry getCodeRegistry() {
181194
* @return the special system field called "__schema"
182195
*/
183196
public GraphQLFieldDefinition getIntrospectionSchemaFieldDefinition() {
184-
return intospectionSchemaField;
197+
return introspectionSchemaField;
185198
}
186199

187200
/**
@@ -284,7 +297,7 @@ public Map<String, GraphQLNamedType> getTypeMap() {
284297
}
285298

286299
public List<GraphQLNamedType> getAllTypesAsList() {
287-
return sortTypes(byNameAsc(), typeMap.values());
300+
return getAllTypesAsList(typeMap);
288301
}
289302

290303
/**
@@ -495,18 +508,16 @@ public static class Builder {
495508
private GraphQLObjectType introspectionSchemaType = Introspection.__Schema;
496509
private GraphQLObjectType subscriptionType;
497510
private GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry().build();
498-
private Set<GraphQLType> additionalTypes = new LinkedHashSet<>();
499511
private SchemaDefinition definition;
500512
private List<SchemaExtensionDefinition> extensionDefinitions;
501513
private String description;
502514

503515
// we default these in
504-
private Set<GraphQLDirective> additionalDirectives = new LinkedHashSet<>(
516+
private final Set<GraphQLDirective> additionalDirectives = new LinkedHashSet<>(
505517
asList(Directives.IncludeDirective, Directives.SkipDirective)
506518
);
507-
private List<GraphQLDirective> schemaDirectives = new ArrayList<>();
508-
509-
private SchemaUtil schemaUtil = new SchemaUtil();
519+
private final Set<GraphQLType> additionalTypes = new LinkedHashSet<>();
520+
private final List<GraphQLDirective> schemaDirectives = new ArrayList<>();
510521

511522
public Builder query(GraphQLObjectType.Builder builder) {
512523
return query(builder.build());
@@ -673,10 +684,10 @@ public GraphQLSchema build(Set<GraphQLType> additionalTypes, Set<GraphQLDirectiv
673684
* @return the built schema
674685
*/
675686
public GraphQLSchema build() {
676-
return buildImpl(false);
687+
return buildImpl();
677688
}
678689

679-
GraphQLSchema buildImpl(boolean afterTransform) {
690+
private GraphQLSchema buildImpl() {
680691
assertNotNull(additionalTypes, () -> "additionalTypes can't be null");
681692
assertNotNull(additionalDirectives, () -> "additionalDirectives can't be null");
682693

@@ -690,12 +701,28 @@ GraphQLSchema buildImpl(boolean afterTransform) {
690701
additionalDirectives.add(Directives.SpecifiedByDirective);
691702
}
692703

693-
// grab the legacy code things from types
694-
final GraphQLSchema tempSchema = new GraphQLSchema(this, afterTransform);
695-
codeRegistry = codeRegistry.transform(codeRegistryBuilder -> schemaUtil.extractCodeFromTypes(codeRegistryBuilder, tempSchema));
704+
// quick build - no traversing
705+
final GraphQLSchema partiallyBuiltSchema = new GraphQLSchema(this);
706+
707+
GraphQLCodeRegistry.Builder extractedDataFetchers = GraphQLCodeRegistry.newCodeRegistry(codeRegistry);
708+
CodeRegistryVisitor codeRegistryVisitor = new CodeRegistryVisitor(extractedDataFetchers);
709+
GraphQLTypeCollectingVisitor typeCollectingVisitor = new GraphQLTypeCollectingVisitor();
710+
SchemaUtil.visitPartiallySchema(partiallyBuiltSchema, codeRegistryVisitor, typeCollectingVisitor);
711+
712+
codeRegistry = extractedDataFetchers.build();
713+
ImmutableMap<String, GraphQLNamedType> allTypes = typeCollectingVisitor.getResult();
714+
List<GraphQLNamedType> allTypesAsList = getAllTypesAsList(allTypes);
715+
716+
ImmutableMap<String, List<GraphQLObjectType>> groupedImplementations = SchemaUtil.groupInterfaceImplementationsByName(allTypesAsList);
717+
ImmutableMap<String, ImmutableList<GraphQLObjectType>> interfaceNameToObjectTypes = buildInterfacesToObjectTypes(groupedImplementations);
718+
719+
// this is now build however its contained types are still to be mutated by type reference replacement
720+
final GraphQLSchema finalSchema = new GraphQLSchema(partiallyBuiltSchema, codeRegistry, allTypes, interfaceNameToObjectTypes);
721+
SchemaUtil.replaceTypeReferences(finalSchema);
722+
return validateSchema(finalSchema);
723+
}
696724

697-
GraphQLSchema graphQLSchema = new GraphQLSchema(tempSchema, codeRegistry);
698-
schemaUtil.replaceTypeReferences(graphQLSchema);
725+
private GraphQLSchema validateSchema(GraphQLSchema graphQLSchema) {
699726
Collection<SchemaValidationError> errors = new SchemaValidator().validateSchema(graphQLSchema);
700727
if (errors.size() > 0) {
701728
throw new InvalidSchemaException(errors);

src/main/java/graphql/schema/SchemaTransformer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
import static graphql.Assert.assertShouldNeverHappen;
2727
import static graphql.schema.GraphQLSchemaElementAdapter.SCHEMA_ELEMENT_ADAPTER;
2828
import static graphql.schema.SchemaElementChildrenContainer.newSchemaElementChildrenContainer;
29-
import static graphql.schema.StronglyConnectedComponentsTopologicallySorted.getStronglyConnectedComponentsTopologicallySorted;
3029
import static graphql.util.NodeZipper.ModificationType.DELETE;
30+
import static graphql.schema.impl.StronglyConnectedComponentsTopologicallySorted.getStronglyConnectedComponentsTopologicallySorted;
3131
import static graphql.util.NodeZipper.ModificationType.REPLACE;
3232
import static graphql.util.TraversalControl.CONTINUE;
3333
import static java.lang.String.format;
@@ -548,7 +548,7 @@ public GraphQLSchema rebuildSchema(GraphQLCodeRegistry.Builder codeRegistry) {
548548
.withSchemaDirectives(this.schemaDirectives)
549549
.codeRegistry(codeRegistry.build())
550550
.description(schema.getDescription())
551-
.buildImpl(true);
551+
.build();
552552
}
553553
}
554554
}

0 commit comments

Comments
 (0)