-
Notifications
You must be signed in to change notification settings - Fork 56
Added inspection to show warnings from explain queries #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.neueda.jetbrains.plugin.graphdb.database.api.query; | ||
|
||
public interface GraphQueryNotification { | ||
|
||
String getTitle(); | ||
|
||
String getDescription(); | ||
|
||
Integer getPositionOffset(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package com.neueda.jetbrains.plugin.graphdb.database.neo4j.bolt.data; | ||
|
||
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryNotification; | ||
|
||
public class Neo4jBoltQueryNotification implements GraphQueryNotification { | ||
|
||
private String title; | ||
private String description; | ||
private Integer positionOffset; | ||
|
||
public Neo4jBoltQueryNotification(String title, String description, Integer positionOffset) { | ||
this.title = title; | ||
this.description = description; | ||
this.positionOffset = positionOffset; | ||
} | ||
|
||
@Override | ||
public String getTitle() { | ||
return title; | ||
} | ||
|
||
@Override | ||
public String getDescription() { | ||
return description; | ||
} | ||
|
||
@Override | ||
public Integer getPositionOffset() { | ||
return positionOffset; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<html> | ||
<body> | ||
<!-- tooltip end --> | ||
Executes EXPLAIN query against active data source. | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package com.neueda.jetbrains.plugin.graphdb.test.integration.neo4j.tests.cypher.inspection; | ||
|
||
import com.intellij.psi.PsiFile; | ||
import com.neueda.jetbrains.plugin.graphdb.jetbrains.component.datasource.state.DataSourceApi; | ||
import com.neueda.jetbrains.plugin.graphdb.jetbrains.inspection.CypherExplainWarningInspection; | ||
import com.neueda.jetbrains.plugin.graphdb.jetbrains.util.NameUtil; | ||
import com.neueda.jetbrains.plugin.graphdb.test.integration.neo4j.util.base.BaseIntegrationTest; | ||
|
||
public class CypherExplainWarningInspectionTest extends BaseIntegrationTest { | ||
|
||
private DataSourceApi dsApi; | ||
|
||
@Override | ||
public void setUp() throws Exception { | ||
super.setUp(); | ||
this.dsApi = dataSource().neo4j31(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to include neo4j30() data source in tests? Some of my tests are doing multi-neo4j-version tests. This is not a requirement, more like general question. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, dataSource here is used just to get an instance of data source api, so we could easily generate file name for data source specific cypher file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are wrong. Data source is used not only for generation, but also for Query execution. You need to get actual notification somewhere :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep, true. Completely forgot about that, while was writing comment :) |
||
myFixture.enableInspections(CypherExplainWarningInspection.class); | ||
} | ||
|
||
public void testNonDataSourceFile_NoHighlight() { | ||
PsiFile psiFile = myFixture.addFileToProject("a.cyp", "MATCH (a)-->(b) RETURN *"); | ||
myFixture.configureFromExistingVirtualFile(psiFile.getVirtualFile()); | ||
myFixture.checkHighlighting(); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There should be test that check behaviour if we are in data source bound file, but there are no actual data source, |
||
public void testDataSourceFile_NoHighlight() { | ||
String fileName = NameUtil.createDataSourceFileName(dsApi); | ||
PsiFile psiFile = myFixture.addFileToProject(fileName, | ||
"MATCH (a)-->(b) RETURN *"); | ||
myFixture.configureFromExistingVirtualFile(psiFile.getVirtualFile()); | ||
myFixture.checkHighlighting(); | ||
} | ||
|
||
public void testDataSourceFile_HighlightExplainWarning() { | ||
String fileName = NameUtil.createDataSourceFileName(dsApi); | ||
PsiFile psiFile = myFixture.addFileToProject(fileName, | ||
"MATCH (a)-[r:" + | ||
"<warning descr=\"The provided relationship type is not in the database.\">" + | ||
"ART</warning>]-(b) RETURN *;"); | ||
myFixture.configureFromExistingVirtualFile(psiFile.getVirtualFile()); | ||
myFixture.checkHighlighting(); | ||
} | ||
|
||
public void testDataSourceFile_NoHighlightQueryError() { | ||
String fileName = NameUtil.createDataSourceFileName(dsApi); | ||
PsiFile psiFile = myFixture.addFileToProject(fileName, | ||
"MATCH (a)-->() RETURN b;"); | ||
myFixture.configureFromExistingVirtualFile(psiFile.getVirtualFile()); | ||
myFixture.checkHighlighting(); | ||
} | ||
|
||
public void testDataSourceFile_NoHighlightParserError() { | ||
String fileName = NameUtil.createDataSourceFileName(dsApi); | ||
PsiFile psiFile = myFixture.addFileToProject(fileName, | ||
"MATCH a<error>-</error>->() RETURN *;"); | ||
myFixture.configureFromExistingVirtualFile(psiFile.getVirtualFile()); | ||
myFixture.checkHighlighting(); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package com.neueda.jetbrains.plugin.graphdb.jetbrains.inspection; | ||
|
||
import com.intellij.codeInspection.LocalInspectionTool; | ||
import com.intellij.codeInspection.LocalInspectionToolSession; | ||
import com.intellij.codeInspection.ProblemsHolder; | ||
import com.intellij.openapi.components.ServiceManager; | ||
import com.intellij.psi.PsiElement; | ||
import com.intellij.psi.PsiElementVisitor; | ||
import com.neueda.jetbrains.plugin.graphdb.database.api.GraphDatabaseApi; | ||
import com.neueda.jetbrains.plugin.graphdb.database.api.query.GraphQueryResult; | ||
import com.neueda.jetbrains.plugin.graphdb.jetbrains.component.datasource.DataSourcesComponent; | ||
import com.neueda.jetbrains.plugin.graphdb.jetbrains.database.DatabaseManagerService; | ||
import com.neueda.jetbrains.plugin.graphdb.jetbrains.util.NameUtil; | ||
import com.neueda.jetbrains.plugin.graphdb.language.cypher.psi.CypherTypes; | ||
import com.neueda.jetbrains.plugin.graphdb.platform.GraphConstants; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
import org.neo4j.driver.v1.exceptions.ClientException; | ||
|
||
import java.util.Objects; | ||
import java.util.Optional; | ||
|
||
public class CypherExplainWarningInspection extends LocalInspectionTool { | ||
|
||
private DatabaseManagerService service; | ||
|
||
public CypherExplainWarningInspection() { | ||
this.service = ServiceManager.getService(DatabaseManagerService.class); | ||
} | ||
|
||
@NotNull | ||
@Override | ||
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, | ||
@NotNull LocalInspectionToolSession session) { | ||
return new PsiElementVisitor() { | ||
@Override | ||
public void visitElement(PsiElement element) { | ||
checkStatement(element, holder); | ||
} | ||
}; | ||
} | ||
|
||
@NotNull | ||
@Override | ||
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { | ||
return new PsiElementVisitor() { | ||
@Override | ||
public void visitElement(PsiElement element) { | ||
checkStatement(element, holder); | ||
} | ||
}; | ||
} | ||
|
||
private void checkStatement(@NotNull PsiElement statement, @NotNull ProblemsHolder problemsHolder) { | ||
if (statement.getNode().getElementType() == CypherTypes.SINGLE_QUERY) { | ||
|
||
String fileName = statement.getContainingFile().getName(); | ||
if (fileName.startsWith(GraphConstants.BOUND_DATA_SOURCE_PREFIX)) { | ||
DataSourcesComponent component = statement.getProject().getComponent(DataSourcesComponent.class); | ||
|
||
component.getDataSourceContainer() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. streams ftw |
||
.findDataSource(NameUtil.extractDataSourceUUID(fileName)) | ||
.map(service::getDatabaseFor) | ||
.map(api -> this.executeExplainQuery(api, statement.getText())) | ||
.filter(Objects::nonNull) | ||
.map(GraphQueryResult::getNotifications) | ||
.filter(list -> !list.isEmpty()) | ||
.ifPresent(notifications -> notifications.forEach(notification -> { | ||
PsiElement elementAt = Optional.ofNullable(notification.getPositionOffset()) | ||
.filter(position -> position > 0) | ||
.map(statement::findElementAt) | ||
.orElse(statement); | ||
|
||
problemsHolder.registerProblem(elementAt, notification.getTitle()); | ||
})); | ||
} | ||
} | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public String loadDescription() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove this accidentally added code |
||
return super.loadDescription(); | ||
} | ||
|
||
private GraphQueryResult executeExplainQuery(GraphDatabaseApi api, String query) { | ||
try { | ||
return api.execute("EXPLAIN " + query); | ||
} catch (ClientException ex) { | ||
return null; | ||
} | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove this empty line |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are go going to add more inspections in future. Let's extract some of the logic in "BaseInspectionTest"
Logic to extract: