Skip to content

Commit a086500

Browse files
committed
Initial stab at caching the schema for better perf in V2 (#164)
1 parent 0a3fa45 commit a086500

File tree

9 files changed

+262
-130
lines changed

9 files changed

+262
-130
lines changed

src/main/com/intellij/lang/jsgraphql/endpoint/ide/project/JSGraphQLEndpointNamedTypeRegistry.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public class JSGraphQLEndpointNamedTypeRegistry implements JSGraphQLNamedTypeReg
4444

4545
private final Map<Project, Map<String, JSGraphQLNamedType>> endpointTypesByName = Maps.newConcurrentMap();
4646
private final Map<Project, PsiFile> endpointEntryPsiFile = Maps.newConcurrentMap();
47+
private final Map<Project, TypeDefinitionRegistryWithErrors> projectToRegistry = Maps.newConcurrentMap();
4748

4849
public static JSGraphQLEndpointNamedTypeRegistry getService(@NotNull Project project) {
4950
return ServiceManager.getService(project, JSGraphQLEndpointNamedTypeRegistry.class);
@@ -58,6 +59,7 @@ public void beforePsiChanged(boolean isPhysical) {
5859
// clear the cache on each PSI change
5960
endpointTypesByName.clear();
6061
endpointEntryPsiFile.clear();
62+
projectToRegistry.clear();
6163
}
6264
});
6365
}
@@ -86,6 +88,10 @@ public void enumerateTypes(Consumer<JSGraphQLNamedType> consumer) {
8688
}
8789

8890
public TypeDefinitionRegistryWithErrors getTypesAsRegistry() {
91+
return projectToRegistry.computeIfAbsent(project, p -> doGetTypesAsRegistry());
92+
}
93+
94+
private TypeDefinitionRegistryWithErrors doGetTypesAsRegistry() {
8995

9096
final TypeDefinitionRegistry registry = new TypeDefinitionRegistry();
9197
final List<GraphQLException> errors = Lists.newArrayList();

src/main/com/intellij/lang/jsgraphql/ide/project/GraphQLPsiSearchHelper.java

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public class GraphQLPsiSearchHelper {
7373
private final GraphQLSettings mySettings;
7474
private final PluginDescriptor pluginDescriptor;
7575
private final Map<String, GraphQLFragmentDefinition> fragmentDefinitionsByName = Maps.newConcurrentMap();
76+
private final Map<String, GlobalSearchScope> fileNameToSchemaScope = Maps.newConcurrentMap();
7677
private final GlobalSearchScope searchScope;
7778
private final GlobalSearchScope allBuiltInSchemaScopes;
7879
private final GraphQLConfigManager graphQLConfigManager;
@@ -101,6 +102,7 @@ public GraphQLPsiSearchHelper(@NotNull final Project project) {
101102
public void beforePsiChanged(boolean isPhysical) {
102103
// clear the cache on each PSI change
103104
fragmentDefinitionsByName.clear();
105+
fileNameToSchemaScope.clear();
104106
}
105107
});
106108
}
@@ -110,25 +112,30 @@ public void beforePsiChanged(boolean isPhysical) {
110112
*/
111113
public GlobalSearchScope getSchemaScope(PsiElement element) {
112114

113-
final GraphQLScopeResolution scopeResolution = mySettings.getScopeResolution();
114-
115-
switch (scopeResolution) {
116-
case PROJECT_SCOPES:
117-
case GRAPHQL_CONFIG_GLOBS:
118-
final VirtualFile virtualFile = getVirtualFile(element.getContainingFile());
119-
final NamedScope schemaScope = (scopeResolution == GraphQLScopeResolution.PROJECT_SCOPES
120-
? graphQLProjectScopesManager.getSchemaScope(virtualFile)
121-
: graphQLConfigManager.getSchemaScope(virtualFile)
122-
);
123-
if (schemaScope != null) {
124-
final GlobalSearchScope filterSearchScope = GlobalSearchScopesCore.filterScope(myProject, schemaScope);
125-
return searchScope.intersectWith(filterSearchScope.union(allBuiltInSchemaScopes));
126-
}
127-
break;
128-
}
115+
return fileNameToSchemaScope.computeIfAbsent(getFileName(element.getContainingFile()), fileName -> {
116+
117+
final GraphQLScopeResolution scopeResolution = mySettings.getScopeResolution();
118+
119+
switch (scopeResolution) {
120+
case PROJECT_SCOPES:
121+
case GRAPHQL_CONFIG_GLOBS:
122+
final VirtualFile virtualFile = getVirtualFile(element.getContainingFile());
123+
final NamedScope schemaScope = (scopeResolution == GraphQLScopeResolution.PROJECT_SCOPES
124+
? graphQLProjectScopesManager.getSchemaScope(virtualFile)
125+
: graphQLConfigManager.getSchemaScope(virtualFile)
126+
);
127+
if (schemaScope != null) {
128+
final GlobalSearchScope filterSearchScope = GlobalSearchScopesCore.filterScope(myProject, schemaScope);
129+
return searchScope.intersectWith(filterSearchScope.union(allBuiltInSchemaScopes));
130+
}
131+
break;
132+
}
133+
134+
// default is entire project limited by relevant file types
135+
return searchScope;
136+
137+
});
129138

130-
// default is entire project limited by relevant file types
131-
return searchScope;
132139
}
133140

134141
private static VirtualFile getVirtualFile(PsiFile containingFile) {

src/main/com/intellij/lang/jsgraphql/ide/project/graphqlconfig/GraphQLConfigManager.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
import com.intellij.psi.PsiTreeChangeEvent;
5454
import com.intellij.psi.search.FilenameIndex;
5555
import com.intellij.psi.search.GlobalSearchScope;
56-
import com.intellij.psi.search.scope.packageSet.NamedScope;
5756
import com.intellij.ui.EditorNotifications;
5857
import com.intellij.util.ui.UIUtil;
5958
import org.apache.commons.io.IOUtils;
@@ -89,15 +88,15 @@ public class GraphQLConfigManager {
8988
GRAPHQLCONFIG_YAML
9089
};
9190

92-
private static final NamedScope NONE = new NamedScope("", null);
91+
private static final GraphQLNamedScope NONE = new GraphQLNamedScope("", null);
9392

9493
private final Project myProject;
9594
private final GlobalSearchScope projectScope;
9695
private final GraphQLConfigGlobMatcher graphQLConfigGlobMatcher;
9796
public final IdeaPluginDescriptor pluginDescriptor;
9897

9998
private volatile Map<String, GraphQLConfigData> configPathToConfigurations = Maps.newConcurrentMap();
100-
private final Map<String, NamedScope> virtualFilePathToScopes = Maps.newConcurrentMap();
99+
private final Map<String, GraphQLNamedScope> virtualFilePathToScopes = Maps.newConcurrentMap();
101100
private VirtualFileListener virtualFileListener;
102101

103102
public GraphQLConfigManager(Project myProject) {
@@ -119,7 +118,7 @@ public static GraphQLConfigManager getService(@NotNull Project project) {
119118
* @return the closest config file that includes the specified virtualFile, or null if none found or not included
120119
*/
121120
public VirtualFile getClosestIncludingConfigFile(VirtualFile virtualFile) {
122-
NamedScope schemaScope = getSchemaScope(virtualFile);
121+
GraphQLNamedScope schemaScope = getSchemaScope(virtualFile);
123122
if (schemaScope == null) {
124123
return null;
125124
}
@@ -196,7 +195,7 @@ public List<GraphQLConfigEndpoint> getEndpoints(VirtualFile virtualFile) {
196195
return null;
197196
}
198197

199-
final NamedScope schemaScope = getSchemaScope(virtualFile);
198+
final GraphQLNamedScope schemaScope = getSchemaScope(virtualFile);
200199
if (schemaScope != null) {
201200
GraphQLConfigPackageSet packageSet = (GraphQLConfigPackageSet) schemaScope.getValue();
202201
if (packageSet != null && packageSet.getConfigData() != null) {
@@ -427,13 +426,13 @@ public Set<VirtualFile> getContentRoots(VirtualFile virtualFile) {
427426
}
428427

429428
@Nullable
430-
public NamedScope getSchemaScope(VirtualFile virtualFile) {
429+
public GraphQLNamedScope getSchemaScope(VirtualFile virtualFile) {
431430
final Ref<VirtualFile> virtualFileWithPath = new Ref<>(virtualFile);
432431
if (virtualFile instanceof VirtualFileWindow) {
433432
// injected virtual files
434433
virtualFileWithPath.set(((VirtualFileWindow) virtualFile).getDelegate());
435434
}
436-
NamedScope namedScope = virtualFilePathToScopes.computeIfAbsent(virtualFileWithPath.get().getPath(), path -> {
435+
GraphQLNamedScope namedScope = virtualFilePathToScopes.computeIfAbsent(virtualFileWithPath.get().getPath(), path -> {
437436
VirtualFile configBaseDir;
438437
if (virtualFileWithPath.get().getFileType() != ScratchFileType.INSTANCE) {
439438
configBaseDir = virtualFileWithPath.get().getParent();
@@ -451,14 +450,14 @@ public NamedScope getSchemaScope(VirtualFile virtualFile) {
451450
for (Map.Entry<String, GraphQLResolvedConfigData> entry : configData.projects.entrySet()) {
452451
final GraphQLConfigPackageSet packageSet = new GraphQLConfigPackageSet(configBaseDir, entry.getValue(), graphQLConfigGlobMatcher);
453452
if (packageSet.includesVirtualFile(virtualFileWithPath.get())) {
454-
return new NamedScope("graphql-config:" + entry.getKey(), packageSet);
453+
return new GraphQLNamedScope("graphql-config:" + entry.getKey(), packageSet);
455454
}
456455
}
457456
}
458457
// then top level config
459458
final GraphQLConfigPackageSet packageSet = new GraphQLConfigPackageSet(configBaseDir, configData, graphQLConfigGlobMatcher);
460459
if (packageSet.includesVirtualFile(virtualFileWithPath.get())) {
461-
return new NamedScope("graphql-config:" + configBaseDirPath, packageSet);
460+
return new GraphQLNamedScope("graphql-config:" + configBaseDirPath, packageSet);
462461
}
463462
return NONE;
464463
} else {

src/main/com/intellij/lang/jsgraphql/ide/project/graphqlconfig/GraphQLConfigPackageSet.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@
1717
import org.apache.commons.lang.StringUtils;
1818
import org.jetbrains.annotations.NotNull;
1919

20-
import java.util.Collections;
21-
import java.util.List;
22-
import java.util.Map;
23-
import java.util.Optional;
20+
import java.util.*;
2421
import java.util.stream.Collectors;
2522

2623
/**
@@ -145,4 +142,20 @@ public GraphQLResolvedConfigData getConfigData() {
145142
public VirtualFile getConfigBaseDir() {
146143
return configBaseDir;
147144
}
145+
146+
@Override
147+
public boolean equals(Object o) {
148+
if (this == o) return true;
149+
if (o == null || getClass() != o.getClass()) return false;
150+
GraphQLConfigPackageSet that = (GraphQLConfigPackageSet) o;
151+
return hasIncludes == that.hasIncludes &&
152+
Objects.equals(configData, that.configData) &&
153+
Objects.equals(configBaseDirPath, that.configBaseDirPath) &&
154+
Objects.equals(schemaFilePath, that.schemaFilePath);
155+
}
156+
157+
@Override
158+
public int hashCode() {
159+
return Objects.hash(configBaseDirPath, hasIncludes, schemaFilePath);
160+
}
148161
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2000-2009 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.intellij.lang.jsgraphql.ide.project.graphqlconfig;
17+
18+
import com.intellij.psi.search.scope.packageSet.NamedScope;
19+
import com.intellij.psi.search.scope.packageSet.PackageSet;
20+
import org.jetbrains.annotations.NotNull;
21+
import org.jetbrains.annotations.Nullable;
22+
23+
import java.util.Objects;
24+
25+
/**
26+
* Named scope for use with multiple GraphQL schemas.
27+
* Implements hash and equals to work in computed maps.
28+
*/
29+
public class GraphQLNamedScope extends NamedScope {
30+
31+
public GraphQLNamedScope(@NotNull String name, @Nullable PackageSet value) {
32+
super(name, value);
33+
}
34+
35+
@NotNull
36+
@Override
37+
public NamedScope createCopy() {
38+
final PackageSet value = getValue();
39+
return new GraphQLNamedScope(getName(), value != null ? value.createCopy() : null);
40+
}
41+
42+
@Override
43+
public boolean equals(Object o) {
44+
if (this == o) return true;
45+
if (o == null || getClass() != o.getClass()) return false;
46+
if (!super.equals(o)) return false;
47+
GraphQLNamedScope that = (GraphQLNamedScope) o;
48+
return Objects.equals(getName(), that.getName()) && Objects.equals(getValue(), that.getValue());
49+
}
50+
51+
@Override
52+
public int hashCode() {
53+
return Objects.hash(getName(), getValue());
54+
}
55+
56+
}

src/main/com/intellij/lang/jsgraphql/ide/project/scopes/ConditionalGlobalSearchScope.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.intellij.psi.search.GlobalSearchScope;
1313
import org.jetbrains.annotations.NotNull;
1414

15+
import java.util.Objects;
1516
import java.util.function.Supplier;
1617

1718
/**
@@ -34,4 +35,18 @@ public boolean contains(@NotNull VirtualFile file) {
3435
}
3536
return super.contains(file);
3637
}
38+
39+
@Override
40+
public boolean equals(Object o) {
41+
if (this == o) return true;
42+
if (o == null || getClass() != o.getClass()) return false;
43+
if (!super.equals(o)) return false;
44+
ConditionalGlobalSearchScope that = (ConditionalGlobalSearchScope) o;
45+
return Objects.equals(isEnabled, that.isEnabled);
46+
}
47+
48+
@Override
49+
public int hashCode() {
50+
return Objects.hash(super.hashCode(), isEnabled);
51+
}
3752
}

src/main/com/intellij/lang/jsgraphql/schema/GraphQLTypeDefinitionRegistryService.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ public interface GraphQLTypeDefinitionRegistryService {
2020
*/
2121
TypeDefinitionRegistry getRegistry(PsiElement psiElement);
2222

23+
/**
24+
* Get a registry representing the known types and fields in a schema, and also includes
25+
* Can be based on type system definitions, an introspection result, or the more abstract endpoint language type definition
26+
* @param psiElement the element from which the registry is needed, serving as a scope restriction
27+
*/
28+
TypeDefinitionRegistryWithErrors getRegistryWithErrors(PsiElement psiElement);
29+
2330
GraphQLSchema getSchema(PsiElement psiElement);
2431

2532
GraphQLSchemaWithErrors getSchemaWithErrors(PsiElement psiElement);

src/main/com/intellij/lang/jsgraphql/schema/GraphQLTypeDefinitionRegistryServiceImpl.java

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,58 +8,72 @@
88
package com.intellij.lang.jsgraphql.schema;
99

1010
import com.google.common.collect.Lists;
11+
import com.google.common.collect.Maps;
12+
import com.intellij.lang.jsgraphql.ide.project.GraphQLPsiSearchHelper;
1113
import com.intellij.openapi.components.ServiceManager;
1214
import com.intellij.openapi.project.Project;
1315
import com.intellij.psi.PsiElement;
16+
import com.intellij.psi.impl.AnyPsiChangeListener;
17+
import com.intellij.psi.impl.PsiManagerImpl;
1418
import graphql.GraphQLException;
1519
import graphql.language.*;
16-
import graphql.schema.GraphQLEnumType;
17-
import graphql.schema.GraphQLInputObjectType;
18-
import graphql.schema.GraphQLInterfaceType;
19-
import graphql.schema.GraphQLObjectType;
20-
import graphql.schema.GraphQLScalarType;
21-
import graphql.schema.GraphQLSchema;
22-
import graphql.schema.GraphQLType;
23-
import graphql.schema.GraphQLUnionType;
20+
import graphql.schema.*;
2421
import graphql.schema.idl.TypeDefinitionRegistry;
2522
import graphql.schema.idl.UnExecutableSchemaGenerator;
2623
import org.jetbrains.annotations.NotNull;
2724

2825
import java.util.Collections;
26+
import java.util.Map;
2927

3028
public class GraphQLTypeDefinitionRegistryServiceImpl implements GraphQLTypeDefinitionRegistryService {
3129

3230
public static final GraphQLSchema EMPTY_SCHEMA = GraphQLSchema.newSchema().query(GraphQLObjectType.newObject().name("Query").build()).build();
3331

3432
private Project project;
3533

34+
private final Map<String, TypeDefinitionRegistryWithErrors> fileNameToRegistry = Maps.newConcurrentMap();
35+
private final Map<String, GraphQLSchemaWithErrors> fileNameToSchema = Maps.newConcurrentMap();
36+
3637
public static GraphQLTypeDefinitionRegistryServiceImpl getService(@NotNull Project project) {
3738
return ServiceManager.getService(project, GraphQLTypeDefinitionRegistryServiceImpl.class);
3839
}
3940

4041
public GraphQLTypeDefinitionRegistryServiceImpl(Project project) {
4142
this.project = project;
43+
project.getMessageBus().connect().subscribe(PsiManagerImpl.ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener.Adapter() {
44+
@Override
45+
public void beforePsiChanged(boolean isPhysical) {
46+
// clear the cache on each PSI change
47+
fileNameToRegistry.clear();
48+
fileNameToSchema.clear();
49+
}
50+
});
51+
4252
}
4353

4454
@Override
45-
public TypeDefinitionRegistry getRegistry(PsiElement psiElement) {
46-
47-
// TODO JKM can cache schema as long as not a modification to type system definitions
48-
// or a GraphQL file is deleted/added
49-
50-
return SchemaIDLTypeDefinitionRegistry.getService(project).getRegistry(psiElement);
55+
public TypeDefinitionRegistryWithErrors getRegistryWithErrors(PsiElement psiElement) { ;
56+
return fileNameToRegistry.computeIfAbsent(GraphQLPsiSearchHelper.getFileName(psiElement.getContainingFile()), fileName -> {
57+
return SchemaIDLTypeDefinitionRegistry.getService(project).getRegistryWithErrors(psiElement);
58+
});
59+
}
5160

61+
@Override
62+
public TypeDefinitionRegistry getRegistry(PsiElement psiElement) { ;
63+
return getRegistryWithErrors(psiElement).getRegistry();
5264
}
5365

5466
@Override
5567
public GraphQLSchemaWithErrors getSchemaWithErrors(PsiElement psiElement) {
56-
final TypeDefinitionRegistryWithErrors registryWithErrors = SchemaIDLTypeDefinitionRegistry.getService(project).getRegistryWithErrors(psiElement);
57-
try {
58-
final GraphQLSchema schema = UnExecutableSchemaGenerator.makeUnExecutableSchema(registryWithErrors.getRegistry());
59-
return new GraphQLSchemaWithErrors(schema, Collections.emptyList(), registryWithErrors);
60-
} catch (GraphQLException e) {
61-
return new GraphQLSchemaWithErrors(EMPTY_SCHEMA, Lists.newArrayList(e), registryWithErrors);
62-
}
68+
return fileNameToSchema.computeIfAbsent(GraphQLPsiSearchHelper.getFileName(psiElement.getContainingFile()), fileName -> {
69+
final TypeDefinitionRegistryWithErrors registryWithErrors = getRegistryWithErrors(psiElement);
70+
try {
71+
final GraphQLSchema schema = UnExecutableSchemaGenerator.makeUnExecutableSchema(registryWithErrors.getRegistry());
72+
return new GraphQLSchemaWithErrors(schema, Collections.emptyList(), registryWithErrors);
73+
} catch (GraphQLException e) {
74+
return new GraphQLSchemaWithErrors(EMPTY_SCHEMA, Lists.newArrayList(e), registryWithErrors);
75+
}
76+
});
6377
}
6478

6579
@Override

0 commit comments

Comments
 (0)