Skip to content

Commit 633f244

Browse files
MetaurMetaur
authored andcommitted
Added inspection to show warnings from explain queries
1 parent 0ade5ac commit 633f244

File tree

9 files changed

+244
-4
lines changed

9 files changed

+244
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.neueda.jetbrains.plugin.graphdb.database.api.query;
2+
3+
public interface GraphQueryNotification {
4+
5+
String getTitle();
6+
7+
String getDescription();
8+
9+
Integer getPositionOffset();
10+
}

database/api/src/main/java/com/neueda/jetbrains/plugin/graphdb/database/api/query/GraphQueryResult.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ public interface GraphQueryResult {
1818
List<GraphNode> getNodes();
1919

2020
List<GraphRelationship> getRelationships();
21+
22+
List<GraphQueryNotification> getNotifications();
2123
}

database/neo4j/src/main/java/com/neueda/jetbrains/plugin/graphdb/database/neo4j/bolt/Neo4jBoltBuffer.java

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,22 @@
22

33
import com.neueda.jetbrains.plugin.graphdb.database.api.data.GraphNode;
44
import com.neueda.jetbrains.plugin.graphdb.database.api.data.GraphRelationship;
5+
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryNotification;
56
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResultColumn;
67
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResultRow;
8+
import com.neueda.jetbrains.plugin.graphdb.database.neo4j.bolt.data.Neo4jBoltQueryNotification;
79
import com.neueda.jetbrains.plugin.graphdb.database.neo4j.bolt.data.Neo4jBoltQueryResultColumn;
810
import com.neueda.jetbrains.plugin.graphdb.database.neo4j.bolt.data.Neo4jBoltQueryResultRow;
11+
import org.neo4j.driver.v1.summary.InputPosition;
912
import org.neo4j.driver.v1.summary.ResultSummary;
1013

1114
import java.util.ArrayList;
15+
import java.util.Collections;
1216
import java.util.List;
1317
import java.util.Map;
14-
import java.util.stream.Collectors;
18+
import java.util.Optional;
19+
20+
import static java.util.stream.Collectors.toList;
1521

1622
public class Neo4jBoltBuffer {
1723

@@ -20,6 +26,7 @@ public class Neo4jBoltBuffer {
2026
private ResultSummary resultSummary;
2127
private List<GraphNode> nodes;
2228
private List<GraphRelationship> relationships;
29+
private List<GraphQueryNotification> notifications;
2330

2431
public Neo4jBoltBuffer() {
2532
this.rows = new ArrayList<>();
@@ -28,7 +35,7 @@ public Neo4jBoltBuffer() {
2835
public void addColumns(List<String> columns) {
2936
this.columns = columns.stream()
3037
.map(Neo4jBoltQueryResultColumn::new)
31-
.collect(Collectors.toList());
38+
.collect(toList());
3239
}
3340

3441
public void addResultSummary(ResultSummary resultSummary) {
@@ -55,7 +62,7 @@ public List<GraphNode> getNodes() {
5562
nodes = rows.stream()
5663
.flatMap(row -> row.getNodes().stream())
5764
.distinct()
58-
.collect(Collectors.toList());
65+
.collect(toList());
5966

6067
return nodes;
6168
}
@@ -68,12 +75,32 @@ public List<GraphRelationship> getRelationships() {
6875
relationships = rows.stream()
6976
.flatMap(row -> row.getRelationships().stream())
7077
.distinct()
71-
.collect(Collectors.toList());
78+
.collect(toList());
7279

7380
return relationships;
7481
}
7582

7683
public ResultSummary getResultSummary() {
7784
return resultSummary;
7885
}
86+
87+
public List<GraphQueryNotification> getNotifications() {
88+
if (resultSummary == null) {
89+
return Collections.emptyList();
90+
}
91+
92+
if (notifications != null) {
93+
return notifications;
94+
}
95+
96+
notifications = resultSummary.notifications().stream()
97+
.map(notification -> new Neo4jBoltQueryNotification(notification.title(),
98+
notification.description(),
99+
Optional.ofNullable(notification.position())
100+
.map(InputPosition::offset)
101+
.orElse(null)))
102+
.collect(toList());
103+
104+
return notifications;
105+
}
79106
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.neueda.jetbrains.plugin.graphdb.database.neo4j.bolt.data;
2+
3+
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryNotification;
4+
5+
public class Neo4jBoltQueryNotification implements GraphQueryNotification {
6+
7+
private String title;
8+
private String description;
9+
private Integer positionOffset;
10+
11+
public Neo4jBoltQueryNotification(String title, String description, Integer positionOffset) {
12+
this.title = title;
13+
this.description = description;
14+
this.positionOffset = positionOffset;
15+
}
16+
17+
@Override
18+
public String getTitle() {
19+
return title;
20+
}
21+
22+
@Override
23+
public String getDescription() {
24+
return description;
25+
}
26+
27+
@Override
28+
public Integer getPositionOffset() {
29+
return positionOffset;
30+
}
31+
}

database/neo4j/src/main/java/com/neueda/jetbrains/plugin/graphdb/database/neo4j/bolt/query/Neo4jBoltQueryResult.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.neueda.jetbrains.plugin.graphdb.database.api.data.GraphNode;
44
import com.neueda.jetbrains.plugin.graphdb.database.api.data.GraphRelationship;
5+
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryNotification;
56
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResult;
67
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResultColumn;
78
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResultRow;
@@ -143,6 +144,11 @@ public List<GraphRelationship> getRelationships() {
143144
return buffer.getRelationships();
144145
}
145146

147+
@Override
148+
public List<GraphQueryNotification> getNotifications() {
149+
return buffer.getNotifications();
150+
}
151+
146152
private Optional<GraphNode> findNodeById(List<GraphNode> nodes, String id) {
147153
return nodes.stream().filter((node) -> node.getId().equals(id)).findFirst();
148154
}

graph-database-support-plugin/src/main/resources/META-INF/plugin.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@
150150
<projectService serviceInterface="com.neueda.jetbrains.plugin.graphdb.language.cypher.completion.metadata.CypherMetadataProviderService"
151151
serviceImplementation="com.neueda.jetbrains.plugin.graphdb.language.cypher.completion.metadata.CypherMetadataProviderServiceImpl"/>
152152
<lang.documentationProvider language="Cypher" implementationClass="com.neueda.jetbrains.plugin.graphdb.language.cypher.documentation.CypherDocumentationProvider"/>
153+
154+
<localInspection language="Cypher" displayName="Cypher EXPLAIN warning inspection" groupPath="Cypher"
155+
groupName="General" enabledByDefault="true" level="WARNING"
156+
implementationClass="com.neueda.jetbrains.plugin.graphdb.jetbrains.inspection.CypherExplainWarningInspection"/>
153157
</extensions>
154158

155159
<actions>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<html>
2+
<body>
3+
<!-- tooltip end -->
4+
Executes EXPLAIN query against active data source.
5+
</body>
6+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.neueda.jetbrains.plugin.graphdb.test.integration.neo4j.tests.cypher.inspection;
2+
3+
import com.intellij.psi.PsiFile;
4+
import com.neueda.jetbrains.plugin.graphdb.jetbrains.component.datasource.state.DataSourceApi;
5+
import com.neueda.jetbrains.plugin.graphdb.jetbrains.inspection.CypherExplainWarningInspection;
6+
import com.neueda.jetbrains.plugin.graphdb.jetbrains.util.NameUtil;
7+
import com.neueda.jetbrains.plugin.graphdb.test.integration.neo4j.util.base.BaseIntegrationTest;
8+
9+
public class CypherExplainWarningInspectionTest extends BaseIntegrationTest {
10+
11+
private DataSourceApi dsApi;
12+
13+
@Override
14+
public void setUp() throws Exception {
15+
super.setUp();
16+
this.dsApi = dataSource().neo4j31();
17+
myFixture.enableInspections(CypherExplainWarningInspection.class);
18+
}
19+
20+
public void testNonDataSourceFile_NoHighlight() {
21+
PsiFile psiFile = myFixture.addFileToProject("a.cyp", "MATCH (a)-->(b) RETURN *");
22+
myFixture.configureFromExistingVirtualFile(psiFile.getVirtualFile());
23+
myFixture.checkHighlighting();
24+
}
25+
26+
public void testDataSourceFile_NoHighlight() {
27+
String fileName = NameUtil.createDataSourceFileName(dsApi);
28+
PsiFile psiFile = myFixture.addFileToProject(fileName,
29+
"MATCH (a)-->(b) RETURN *");
30+
myFixture.configureFromExistingVirtualFile(psiFile.getVirtualFile());
31+
myFixture.checkHighlighting();
32+
}
33+
34+
public void testDataSourceFile_HighlightExplainWarning() {
35+
String fileName = NameUtil.createDataSourceFileName(dsApi);
36+
PsiFile psiFile = myFixture.addFileToProject(fileName,
37+
"MATCH (a)-[r:" +
38+
"<warning descr=\"The provided relationship type is not in the database.\">" +
39+
"ART</warning>]-(b) RETURN *;");
40+
myFixture.configureFromExistingVirtualFile(psiFile.getVirtualFile());
41+
myFixture.checkHighlighting();
42+
}
43+
44+
public void testDataSourceFile_NoHighlightQueryError() {
45+
String fileName = NameUtil.createDataSourceFileName(dsApi);
46+
PsiFile psiFile = myFixture.addFileToProject(fileName,
47+
"MATCH (a)-->() RETURN b;");
48+
myFixture.configureFromExistingVirtualFile(psiFile.getVirtualFile());
49+
myFixture.checkHighlighting();
50+
}
51+
52+
public void testDataSourceFile_NoHighlightParserError() {
53+
String fileName = NameUtil.createDataSourceFileName(dsApi);
54+
PsiFile psiFile = myFixture.addFileToProject(fileName,
55+
"MATCH a<error>-</error>->() RETURN *;");
56+
myFixture.configureFromExistingVirtualFile(psiFile.getVirtualFile());
57+
myFixture.checkHighlighting();
58+
}
59+
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.neueda.jetbrains.plugin.graphdb.jetbrains.inspection;
2+
3+
import com.intellij.codeInspection.LocalInspectionTool;
4+
import com.intellij.codeInspection.LocalInspectionToolSession;
5+
import com.intellij.codeInspection.ProblemsHolder;
6+
import com.intellij.openapi.components.ServiceManager;
7+
import com.intellij.psi.PsiElement;
8+
import com.intellij.psi.PsiElementVisitor;
9+
import com.neueda.jetbrains.plugin.graphdb.database.api.GraphDatabaseApi;
10+
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResult;
11+
import com.neueda.jetbrains.plugin.graphdb.jetbrains.component.datasource.DataSourcesComponent;
12+
import com.neueda.jetbrains.plugin.graphdb.jetbrains.database.DatabaseManagerService;
13+
import com.neueda.jetbrains.plugin.graphdb.jetbrains.util.NameUtil;
14+
import com.neueda.jetbrains.plugin.graphdb.language.cypher.psi.CypherTypes;
15+
import com.neueda.jetbrains.plugin.graphdb.platform.GraphConstants;
16+
import org.jetbrains.annotations.NotNull;
17+
import org.jetbrains.annotations.Nullable;
18+
import org.neo4j.driver.v1.exceptions.ClientException;
19+
20+
import java.util.Objects;
21+
import java.util.Optional;
22+
23+
public class CypherExplainWarningInspection extends LocalInspectionTool {
24+
25+
private DatabaseManagerService service;
26+
27+
public CypherExplainWarningInspection() {
28+
this.service = ServiceManager.getService(DatabaseManagerService.class);
29+
}
30+
31+
@NotNull
32+
@Override
33+
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly,
34+
@NotNull LocalInspectionToolSession session) {
35+
return new PsiElementVisitor() {
36+
@Override
37+
public void visitElement(PsiElement element) {
38+
checkStatement(element, holder);
39+
}
40+
};
41+
}
42+
43+
@NotNull
44+
@Override
45+
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
46+
return new PsiElementVisitor() {
47+
@Override
48+
public void visitElement(PsiElement element) {
49+
checkStatement(element, holder);
50+
}
51+
};
52+
}
53+
54+
private void checkStatement(@NotNull PsiElement statement, @NotNull ProblemsHolder problemsHolder) {
55+
if (statement.getNode().getElementType() == CypherTypes.SINGLE_QUERY) {
56+
57+
String fileName = statement.getContainingFile().getName();
58+
if (fileName.startsWith(GraphConstants.BOUND_DATA_SOURCE_PREFIX)) {
59+
DataSourcesComponent component = statement.getProject().getComponent(DataSourcesComponent.class);
60+
61+
component.getDataSourceContainer()
62+
.findDataSource(NameUtil.extractDataSourceUUID(fileName))
63+
.map(service::getDatabaseFor)
64+
.map(api -> this.executeExplainQuery(api, statement.getText()))
65+
.filter(Objects::nonNull)
66+
.map(GraphQueryResult::getNotifications)
67+
.filter(list -> !list.isEmpty())
68+
.ifPresent(notifications -> notifications.forEach(notification -> {
69+
PsiElement elementAt = Optional.ofNullable(notification.getPositionOffset())
70+
.filter(position -> position > 0)
71+
.map(statement::findElementAt)
72+
.orElse(statement);
73+
74+
problemsHolder.registerProblem(elementAt, notification.getTitle());
75+
}));
76+
}
77+
}
78+
}
79+
80+
@Nullable
81+
@Override
82+
public String loadDescription() {
83+
return super.loadDescription();
84+
}
85+
86+
private GraphQueryResult executeExplainQuery(GraphDatabaseApi api, String query) {
87+
try {
88+
return api.execute("EXPLAIN " + query);
89+
} catch (ClientException ex) {
90+
return null;
91+
}
92+
}
93+
94+
}

0 commit comments

Comments
 (0)