From f20d96ed845905b187c4b82b9cb02011651c30ee Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Tue, 12 Sep 2023 06:24:29 +0200 Subject: [PATCH] Migrate to using elementId (#84) * Migrate to using elementId The cypher function `id` has been deprecated since Neo4j 5.0. This PR adds the capability of sniffing the DBMS version so that the DiffService can write an update query that matches the server version. Likewise has `entity.id()` in the driver been deprecated. The driver will set `elementId` to `id.toString()` if the server does not provide it (4.4 and before). Therefore, it's safe to always use `entity.elementId()` regardless of the server version. * Add integration test for fetching Neo4j version * Fix test assertion: expected & actual position --- .../database/api/GraphDatabaseApi.java | 3 + .../api/data/GraphDatabaseVersion.java | 15 ++++ .../neo4j/bolt/Neo4jBoltDatabase.java | 25 +++++++ .../neo4j/bolt/data/Neo4jBoltNode.java | 2 +- .../bolt/data/Neo4jBoltRelationship.java | 6 +- .../bolt/data/Neo4jGraphDatabaseVersion.java | 33 +++++++++ .../src/main/resources/META-INF/plugin.xml | 2 + platform/src/main/java/icons/GraphIcons.java | 1 + testing/integration-neo4j/build.gradle | 1 + .../AbstractDataSourceMetadataTest.java | 6 +- .../neo4j/DataSourceMetadataTest.java | 8 +++ .../datasource/DataSourcesComponent.java | 4 +- .../DataSourcesComponentMetadata.java | 7 +- .../metadata/neo4j/Neo4jMetadata.java | 2 + .../metadata/neo4j/Neo4jMetadataBuilder.java | 7 +- .../jetbrains/database/DiffService.java | 46 +++++++------ .../jetbrains/database/VersionService.java | 69 +++++++++++++++++++ .../event/VersionFetchingProcessEvent.java | 24 +++++++ .../jetbrains/ui/console/log/LogPanel.java | 24 ++++++- .../DataSourceMetadataUpdateService.java | 6 +- .../metadata/Neo4jBoltTreeUpdater.java | 13 ++++ .../ui/datasource/tree/Neo4jTreeNodeType.java | 1 + .../datasource/metadata/ContextMenuTest.java | 6 +- .../DataSourceMetadataUpdateServiceTest.java | 2 +- .../metadata/Neo4JBoltTreeUpdaterTest.java | 2 + 25 files changed, 272 insertions(+), 43 deletions(-) create mode 100644 database/api/src/main/java/com/albertoventurini/graphdbplugin/database/api/data/GraphDatabaseVersion.java create mode 100644 database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/data/Neo4jGraphDatabaseVersion.java create mode 100644 ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/database/VersionService.java create mode 100644 ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/console/event/VersionFetchingProcessEvent.java diff --git a/database/api/src/main/java/com/albertoventurini/graphdbplugin/database/api/GraphDatabaseApi.java b/database/api/src/main/java/com/albertoventurini/graphdbplugin/database/api/GraphDatabaseApi.java index 91426ad2..df793370 100644 --- a/database/api/src/main/java/com/albertoventurini/graphdbplugin/database/api/GraphDatabaseApi.java +++ b/database/api/src/main/java/com/albertoventurini/graphdbplugin/database/api/GraphDatabaseApi.java @@ -6,6 +6,7 @@ */ package com.albertoventurini.graphdbplugin.database.api; +import com.albertoventurini.graphdbplugin.database.api.data.GraphDatabaseVersion; import com.albertoventurini.graphdbplugin.database.api.data.GraphMetadata; import com.albertoventurini.graphdbplugin.database.api.query.GraphQueryResult; @@ -18,4 +19,6 @@ public interface GraphDatabaseApi { GraphQueryResult execute(String query, Map statementParameters); GraphMetadata metadata(); + + GraphDatabaseVersion getVersion(); } diff --git a/database/api/src/main/java/com/albertoventurini/graphdbplugin/database/api/data/GraphDatabaseVersion.java b/database/api/src/main/java/com/albertoventurini/graphdbplugin/database/api/data/GraphDatabaseVersion.java new file mode 100644 index 00000000..7db07ee8 --- /dev/null +++ b/database/api/src/main/java/com/albertoventurini/graphdbplugin/database/api/data/GraphDatabaseVersion.java @@ -0,0 +1,15 @@ +/** + * Copied and adapted from plugin + * Graph Database Support + * by Neueda Technologies, Ltd. + * Modified by Alberto Venturini, 2022 + */ +package com.albertoventurini.graphdbplugin.database.api.data; + +public interface GraphDatabaseVersion { + String toString(); + + String idFunction(); + + Object idToParameter(String id); +} diff --git a/database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/Neo4jBoltDatabase.java b/database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/Neo4jBoltDatabase.java index d83479bf..e8d0c99b 100644 --- a/database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/Neo4jBoltDatabase.java +++ b/database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/Neo4jBoltDatabase.java @@ -7,9 +7,12 @@ package com.albertoventurini.graphdbplugin.database.neo4j.bolt; import com.albertoventurini.graphdbplugin.database.api.GraphDatabaseApi; +import com.albertoventurini.graphdbplugin.database.api.data.GraphDatabaseVersion; import com.albertoventurini.graphdbplugin.database.api.data.GraphMetadata; import com.albertoventurini.graphdbplugin.database.api.query.GraphQueryResult; +import com.albertoventurini.graphdbplugin.database.neo4j.bolt.data.Neo4jGraphDatabaseVersion; import com.albertoventurini.graphdbplugin.database.neo4j.bolt.query.Neo4jBoltQueryResult; +import com.albertoventurini.graphdbplugin.database.neo4j.bolt.query.Neo4jBoltQueryResultRow; import org.neo4j.driver.AuthToken; import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Driver; @@ -21,14 +24,21 @@ import org.neo4j.driver.exceptions.ClientException; import java.nio.channels.UnresolvedAddressException; +import java.util.Arrays; import java.util.Collections; import java.util.Map; +import java.util.stream.IntStream; /** * Communicates with Neo4j 3.0+ database using Bolt driver. */ public class Neo4jBoltDatabase implements GraphDatabaseApi { + private static final String VERSION_QUERY = """ + CALL dbms.components() YIELD versions + RETURN versions[0] AS version + """; + private final String url; private final AuthToken auth; private final SessionConfig dbConfig; @@ -110,4 +120,19 @@ public GraphQueryResult execute(String query, Map statementParam public GraphMetadata metadata() { throw new IllegalStateException("Not implemented"); } + + @Override + public GraphDatabaseVersion getVersion() { + var result = execute(VERSION_QUERY); + var row = result.getRows().get(0); + var neo4jRow = (Neo4jBoltQueryResultRow) row; + var rawVersion = neo4jRow.getValue("version").asString(); + var parsedVersion = IntStream.concat( + Arrays.stream(rawVersion.split("\\.", 4)) + .limit(3) + .mapToInt(Integer::parseInt), + IntStream.generate(() -> 0) + ).limit(3).toArray(); + return new Neo4jGraphDatabaseVersion(parsedVersion[0], parsedVersion[1], parsedVersion[2]); + } } diff --git a/database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/data/Neo4jBoltNode.java b/database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/data/Neo4jBoltNode.java index 3151f3c9..ef4f3d77 100644 --- a/database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/data/Neo4jBoltNode.java +++ b/database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/data/Neo4jBoltNode.java @@ -21,7 +21,7 @@ public class Neo4jBoltNode implements GraphNode { private final List types; public Neo4jBoltNode(Node value) { - this.id = String.valueOf(value.id()); + this.id = String.valueOf(value.elementId()); this.types = Iterables.asList(value.labels()); this.propertyContainer = new Neo4jBoltPropertyContainer(value.asMap()); } diff --git a/database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/data/Neo4jBoltRelationship.java b/database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/data/Neo4jBoltRelationship.java index 057148ce..1ceb6bb5 100644 --- a/database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/data/Neo4jBoltRelationship.java +++ b/database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/data/Neo4jBoltRelationship.java @@ -26,12 +26,12 @@ public class Neo4jBoltRelationship implements GraphRelationship { private GraphNode endNode; public Neo4jBoltRelationship(Relationship rel) { - this.id = String.valueOf(rel.id()); + this.id = String.valueOf(rel.elementId()); this.types = Collections.singletonList(rel.type()); this.propertyContainer = new Neo4jBoltPropertyContainer(rel.asMap()); - this.startNodeId = String.valueOf(rel.startNodeId()); - this.endNodeId = String.valueOf(rel.endNodeId()); + this.startNodeId = String.valueOf(rel.startNodeElementId()); + this.endNodeId = String.valueOf(rel.endNodeElementId()); } public Neo4jBoltRelationship(String id, List types, GraphPropertyContainer propertyContainer, String startNodeId, String endNodeId) { diff --git a/database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/data/Neo4jGraphDatabaseVersion.java b/database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/data/Neo4jGraphDatabaseVersion.java new file mode 100644 index 00000000..131934e1 --- /dev/null +++ b/database/neo4j/src/main/java/com/albertoventurini/graphdbplugin/database/neo4j/bolt/data/Neo4jGraphDatabaseVersion.java @@ -0,0 +1,33 @@ +/** + * Copied and adapted from plugin + * Graph Database Support + * by Neueda Technologies, Ltd. + * Modified by Alberto Venturini, 2022 + */ +package com.albertoventurini.graphdbplugin.database.neo4j.bolt.data; + +import com.albertoventurini.graphdbplugin.database.api.data.GraphDatabaseVersion; + +public record Neo4jGraphDatabaseVersion(int major, int minor, int patch) implements GraphDatabaseVersion { + + @Override + public String toString() { + return "Neo4j/" + major + "." + minor + "." + patch; + } + + @Override + public String idFunction() { + if (major >= 5) { + return "elementId"; + } + return "id"; + } + + @Override + public Object idToParameter(String id) { + if (major >= 5) { + return id; + } + return Long.parseLong(id); + } +} diff --git a/graph-database-plugin/src/main/resources/META-INF/plugin.xml b/graph-database-plugin/src/main/resources/META-INF/plugin.xml index 1e77c982..d9d8cabe 100644 --- a/graph-database-plugin/src/main/resources/META-INF/plugin.xml +++ b/graph-database-plugin/src/main/resources/META-INF/plugin.xml @@ -130,6 +130,8 @@ serviceImplementation="com.albertoventurini.graphdbplugin.jetbrains.ui.datasource.DataSourcesView"/> + diff --git a/platform/src/main/java/icons/GraphIcons.java b/platform/src/main/java/icons/GraphIcons.java index 329f3b7b..f929ad7c 100644 --- a/platform/src/main/java/icons/GraphIcons.java +++ b/platform/src/main/java/icons/GraphIcons.java @@ -27,6 +27,7 @@ public static final class Window { } public static final class Nodes { + public static final Icon VERSION = AllIcons.Actions.InlayGear; public static final Icon INDEX = AllIcons.Nodes.ResourceBundle; public static final Icon CONSTRAINT = AllIcons.Nodes.C_protected; public static final Icon LABEL = AllIcons.Nodes.Class; diff --git a/testing/integration-neo4j/build.gradle b/testing/integration-neo4j/build.gradle index 82f0a77a..0cf642dc 100644 --- a/testing/integration-neo4j/build.gradle +++ b/testing/integration-neo4j/build.gradle @@ -12,6 +12,7 @@ dependencies { testImplementation project(':graph-database-plugin') testImplementation project(':ui:jetbrains') testImplementation project(':language:cypher') + testImplementation project(':database:api') testImplementation project(':database:neo4j') testImplementation project(':testing:common') testImplementation project(':platform') diff --git a/testing/integration-neo4j/src/test/java/com/albertoventurini/graphdbplugin/test/integration/neo4j/tests/database/common/AbstractDataSourceMetadataTest.java b/testing/integration-neo4j/src/test/java/com/albertoventurini/graphdbplugin/test/integration/neo4j/tests/database/common/AbstractDataSourceMetadataTest.java index ec95b66a..2f47a94c 100644 --- a/testing/integration-neo4j/src/test/java/com/albertoventurini/graphdbplugin/test/integration/neo4j/tests/database/common/AbstractDataSourceMetadataTest.java +++ b/testing/integration-neo4j/src/test/java/com/albertoventurini/graphdbplugin/test/integration/neo4j/tests/database/common/AbstractDataSourceMetadataTest.java @@ -7,11 +7,9 @@ package com.albertoventurini.graphdbplugin.test.integration.neo4j.tests.database.common; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.DataSourceMetadata; -import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.neo4j.Neo4jProcedureMetadata; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.state.DataSourceApi; import com.albertoventurini.graphdbplugin.test.integration.neo4j.util.base.BaseIntegrationTest; -import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -32,13 +30,13 @@ public void setUp() throws Exception { public abstract DataSourceApi getDataSource(); public void testMetadataExists() throws ExecutionException, InterruptedException { - Optional metadata = component().dataSourcesMetadata().getMetadata(getDataSource()).get(); + Optional metadata = component().dataSourcesMetadata().updateMetadata(getDataSource()).get(); assertThat(metadata).isPresent(); } protected DataSourceMetadata getMetadata() { try { - CompletableFuture> futureMeta = component().dataSourcesMetadata().getMetadata(getDataSource()); + CompletableFuture> futureMeta = component().dataSourcesMetadata().updateMetadata(getDataSource()); return futureMeta.get(30, TimeUnit.SECONDS) .orElseThrow(() -> new IllegalStateException("Metadata should not be null")); } catch (InterruptedException | ExecutionException | TimeoutException e) { diff --git a/testing/integration-neo4j/src/test/java/com/albertoventurini/graphdbplugin/test/integration/neo4j/tests/database/neo4j/DataSourceMetadataTest.java b/testing/integration-neo4j/src/test/java/com/albertoventurini/graphdbplugin/test/integration/neo4j/tests/database/neo4j/DataSourceMetadataTest.java index 560e95cd..588a2323 100644 --- a/testing/integration-neo4j/src/test/java/com/albertoventurini/graphdbplugin/test/integration/neo4j/tests/database/neo4j/DataSourceMetadataTest.java +++ b/testing/integration-neo4j/src/test/java/com/albertoventurini/graphdbplugin/test/integration/neo4j/tests/database/neo4j/DataSourceMetadataTest.java @@ -6,6 +6,7 @@ */ package com.albertoventurini.graphdbplugin.test.integration.neo4j.tests.database.neo4j; +import com.albertoventurini.graphdbplugin.database.neo4j.bolt.data.Neo4jGraphDatabaseVersion; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.neo4j.Neo4jMetadata; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.neo4j.Neo4jFunctionMetadata; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.neo4j.Neo4jProcedureMetadata; @@ -39,4 +40,11 @@ public void testMetadataHaveRequiredProcedures() { && p.signature().equals("db.labels() :: (label :: STRING?)") && p.description().equals("List all available labels in the database."))); } + + public void testGetVersion() { + var metadata = (Neo4jMetadata) getMetadata(); + var version = (Neo4jGraphDatabaseVersion) metadata.version(); + assertEquals(5, version.major()); + assertEquals(2, version.minor()); + } } diff --git a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/DataSourcesComponent.java b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/DataSourcesComponent.java index 7fc3f825..c29f1c90 100644 --- a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/DataSourcesComponent.java +++ b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/DataSourcesComponent.java @@ -57,9 +57,7 @@ public DataSourcesComponentState getState() { } public void refreshAllMetadata() { - getDataSourceContainer().getDataSources().forEach(d -> { - componentMetadata.getMetadata(d); - }); + getDataSourceContainer().getDataSources().forEach(componentMetadata::updateMetadata); } // // @NotNull diff --git a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/metadata/DataSourcesComponentMetadata.java b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/metadata/DataSourcesComponentMetadata.java index ddba0490..3a704ef9 100644 --- a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/metadata/DataSourcesComponentMetadata.java +++ b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/metadata/DataSourcesComponentMetadata.java @@ -12,6 +12,7 @@ import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.neo4j.Neo4jMetadataBuilder; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.neo4j.Neo4jRelationshipTypeMetadata; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.state.DataSourceApi; +import com.albertoventurini.graphdbplugin.jetbrains.database.VersionService; import com.albertoventurini.graphdbplugin.jetbrains.services.ExecutorService; import com.albertoventurini.graphdbplugin.jetbrains.ui.datasource.metadata.MetadataRetrieveEvent; import com.intellij.openapi.application.ApplicationManager; @@ -31,16 +32,18 @@ public class DataSourcesComponentMetadata { private CypherMetadataProviderService cypherMetadataProviderService; private ExecutorService executorService; private MessageBus messageBus; + private VersionService versionService; public DataSourcesComponentMetadata(final Project project) { messageBus = project.getMessageBus(); cypherMetadataProviderService = project.getService(CypherMetadataProviderService.class); executorService = ApplicationManager.getApplication().getService(ExecutorService.class); + versionService = project.getService(VersionService.class); handlers.put(DataSourceType.NEO4J_BOLT, new Neo4jMetadataBuilder()); } - public CompletableFuture> getMetadata(DataSourceApi dataSource) { + public CompletableFuture> updateMetadata(DataSourceApi dataSource) { MetadataRetrieveEvent metadataRetrieveEvent = messageBus.syncPublisher(MetadataRetrieveEvent.METADATA_RETRIEVE_EVENT); metadataRetrieveEvent.startMetadataRefresh(dataSource); @@ -84,5 +87,7 @@ private void updateNeo4jBoltMetadata(DataSourceApi dataSource, Neo4jMetadata met metadata.propertyKeys().forEach(container::addPropertyKey); metadata.procedures().forEach(p -> container.addProcedure(p.name(), p.signature(), p.description())); metadata.functions().forEach(f -> container.addFunction(f.name(), f.signature(), f.description())); + + versionService.updatedVersion(dataSource, metadata.version()); } } diff --git a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/metadata/neo4j/Neo4jMetadata.java b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/metadata/neo4j/Neo4jMetadata.java index 65a0f632..f389655a 100644 --- a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/metadata/neo4j/Neo4jMetadata.java +++ b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/metadata/neo4j/Neo4jMetadata.java @@ -1,10 +1,12 @@ package com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.neo4j; +import com.albertoventurini.graphdbplugin.database.api.data.GraphDatabaseVersion; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.DataSourceMetadata; import java.util.*; public record Neo4jMetadata( + GraphDatabaseVersion version, List functions, List procedures, List constraints, diff --git a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/metadata/neo4j/Neo4jMetadataBuilder.java b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/metadata/neo4j/Neo4jMetadataBuilder.java index 816a83cf..2959f18b 100644 --- a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/metadata/neo4j/Neo4jMetadataBuilder.java +++ b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/component/datasource/metadata/neo4j/Neo4jMetadataBuilder.java @@ -1,6 +1,8 @@ package com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.neo4j; import com.albertoventurini.graphdbplugin.database.api.GraphDatabaseApi; +import com.albertoventurini.graphdbplugin.database.api.data.GraphDatabaseVersion; +import com.albertoventurini.graphdbplugin.database.neo4j.bolt.data.Neo4jGraphDatabaseVersion; import com.albertoventurini.graphdbplugin.database.neo4j.bolt.query.Neo4jBoltQueryResultRow; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.DataSourceMetadata; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.MetadataBuilder; @@ -50,6 +52,7 @@ public DataSourceMetadata buildMetadata(DataSourceApi dataSource) { final var databaseManager = ApplicationManager.getApplication().getService(DatabaseManagerService.class); final GraphDatabaseApi db = databaseManager.getDatabaseFor(dataSource); + GraphDatabaseVersion version = new Neo4jGraphDatabaseVersion(0, 0, 0); final List indexes = new ArrayList<>(); final List procedures = new ArrayList<>(); final List constraints = new ArrayList<>(); @@ -60,8 +63,9 @@ public DataSourceMetadata buildMetadata(DataSourceApi dataSource) { procedures.addAll(getProcedures(db)); constraints.addAll(getConstraints(db)); functions.addAll(getFunctions(db)); + version = db.getVersion(); } catch (Exception e) { - LOG.warn("Unable to load indexes, constraints, procedures, or functions from the current database. Please upgrade to Neo4j 4.2+ to fix this."); + LOG.warn("Unable to load indexes, constraints, procedures, functions, or version from the current database. Please upgrade to Neo4j 4.2+ to fix this."); } final var propertyKeys = getPropertyKeys(db); @@ -69,6 +73,7 @@ public DataSourceMetadata buildMetadata(DataSourceApi dataSource) { final var relationshipTypes = getRelationshipTypes(db); return new Neo4jMetadata( + version, functions, procedures, constraints, diff --git a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/database/DiffService.java b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/database/DiffService.java index 2cf1b1e2..584c36d5 100644 --- a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/database/DiffService.java +++ b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/database/DiffService.java @@ -20,36 +20,42 @@ public class DiffService { private final QueryExecutionService service; + private final VersionService versionService; public DiffService(Project project) { - this.service = new QueryExecutionService(project, project.getMessageBus()); + service = new QueryExecutionService(project, project.getMessageBus()); + versionService = project.getService(VersionService.class); } public void updateNode(DataSourceApi api, GraphEntity oldNode, GraphEntity newNode) { - - String query = "MATCH (n) WHERE ID(n) = $id " + - diffLabels(oldNode.getTypes(), newNode.getTypes()) + - " SET n = $props" + - " RETURN n"; - service.executeQuery(api, new ExecuteQueryPayload(query, - ImmutableMap.of( - "id", Long.parseLong(oldNode.getId()), - "props", newNode.getPropertyContainer().getProperties()), - null)); + versionService.getVersion(api) + .thenAccept(version -> { + String query = "MATCH (n) WHERE " + version.idFunction() + "(n) = $id " + + diffLabels(oldNode.getTypes(), newNode.getTypes()) + + " SET n = $props" + + " RETURN n"; + service.executeQuery(api, new ExecuteQueryPayload(query, + ImmutableMap.of( + "id", version.idToParameter(oldNode.getId()), + "props", newNode.getPropertyContainer().getProperties()), + null)); + }); } public void updateRelationShip(DataSourceApi api, GraphEntity relationship, GraphEntity updatedRel) { - - String query = "MATCH ()-[n]->() WHERE ID(n) = $id " + - " SET n = $props" + - " RETURN n"; - service.executeQuery(api, new ExecuteQueryPayload(query, - ImmutableMap.of( - "id", Long.parseLong(relationship.getId()), - "props", updatedRel.getPropertyContainer().getProperties()), - null)); + versionService.getVersion(api) + .thenAccept(version -> { + String query = "MATCH ()-[n]->() WHERE " + version.idFunction() + "(n) = $id " + + " SET n = $props" + + " RETURN n"; + service.executeQuery(api, new ExecuteQueryPayload(query, + ImmutableMap.of( + "id", version.idToParameter(relationship.getId()), + "props", updatedRel.getPropertyContainer().getProperties()), + null)); + }); } public void saveNewNode(DataSourceApi api, GraphEntity newNode) { diff --git a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/database/VersionService.java b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/database/VersionService.java new file mode 100644 index 00000000..bf8c74aa --- /dev/null +++ b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/database/VersionService.java @@ -0,0 +1,69 @@ +/** + * Copied and adapted from plugin + * Graph Database Support + * by Neueda Technologies, Ltd. + * Modified by Alberto Venturini, 2022 + */ +package com.albertoventurini.graphdbplugin.jetbrains.database; + +import com.albertoventurini.graphdbplugin.database.api.data.GraphDatabaseVersion; +import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.state.DataSourceApi; +import com.albertoventurini.graphdbplugin.jetbrains.services.ExecutorService; +import com.albertoventurini.graphdbplugin.jetbrains.ui.console.event.VersionFetchingProcessEvent; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.util.messages.MessageBus; + +import java.util.HashMap; +import java.util.concurrent.CompletableFuture; + +public final class VersionService { + private final HashMap> versionRegistry; + private final MessageBus messageBus; + private final DatabaseManagerService databaseManager; + private final ExecutorService executorService; + + VersionService(Project project) { + versionRegistry = new HashMap<>(); + messageBus = project.getMessageBus(); + databaseManager = ApplicationManager.getApplication().getService(DatabaseManagerService.class); + executorService = ApplicationManager.getApplication().getService(ExecutorService.class); + } + + public synchronized CompletableFuture getVersion(DataSourceApi api) { + final var key = api.getName(); + final var version = versionRegistry.get(key); + if (version != null && !version.isCompletedExceptionally()) { + return version; + } + final var future = fetchVersion(api); + versionRegistry.put(key, future); + return future; + } + + public synchronized void updatedVersion(DataSourceApi api, GraphDatabaseVersion version) { + final var key = api.getName(); + final var future = new CompletableFuture(); + future.complete(version); + versionRegistry.put(key, future); + } + + private CompletableFuture fetchVersion(DataSourceApi api) { + VersionFetchingProcessEvent event = messageBus.syncPublisher(VersionFetchingProcessEvent.VERSION_FETCHING_PROCESS_TOPIC); + event.processStarted(api); + final var future = new CompletableFuture(); + final var db = databaseManager.getDatabaseFor(api); + executorService.runInBackground( + db::getVersion, + version -> { + event.versionReceived(version); + future.complete(version); + }, + ex -> { + event.handleError(ex); + future.completeExceptionally(ex); + } + ); + return future; + } +} diff --git a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/console/event/VersionFetchingProcessEvent.java b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/console/event/VersionFetchingProcessEvent.java new file mode 100644 index 00000000..181d9962 --- /dev/null +++ b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/console/event/VersionFetchingProcessEvent.java @@ -0,0 +1,24 @@ +/** + * Copied and adapted from plugin + * Graph Database Support + * by Neueda Technologies, Ltd. + * Modified by Alberto Venturini, 2022 + */ +package com.albertoventurini.graphdbplugin.jetbrains.ui.console.event; + +import com.albertoventurini.graphdbplugin.database.api.data.GraphDatabaseVersion; +import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.state.DataSourceApi; +import com.intellij.util.messages.Topic; + +public interface VersionFetchingProcessEvent { + + Topic VERSION_FETCHING_PROCESS_TOPIC = + Topic.create("GraphDatabaseConsole.VersionFetchingProcessTopic", VersionFetchingProcessEvent.class); + + void processStarted(DataSourceApi dataSource); + + void versionReceived(GraphDatabaseVersion version); + + + void handleError(Exception exception); +} diff --git a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/console/log/LogPanel.java b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/console/log/LogPanel.java index 662d32b7..e5bfe698 100644 --- a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/console/log/LogPanel.java +++ b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/console/log/LogPanel.java @@ -6,10 +6,12 @@ */ package com.albertoventurini.graphdbplugin.jetbrains.ui.console.log; +import com.albertoventurini.graphdbplugin.database.api.data.GraphDatabaseVersion; import com.albertoventurini.graphdbplugin.jetbrains.actions.execute.ExecuteQueryPayload; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.state.DataSourceApi; import com.albertoventurini.graphdbplugin.jetbrains.ui.console.event.QueryExecutionProcessEvent; import com.albertoventurini.graphdbplugin.jetbrains.ui.console.event.QueryParametersRetrievalErrorEvent; +import com.albertoventurini.graphdbplugin.jetbrains.ui.console.event.VersionFetchingProcessEvent; import com.albertoventurini.graphdbplugin.jetbrains.ui.datasource.interactions.DataSourceDialog; import com.albertoventurini.graphdbplugin.jetbrains.ui.datasource.metadata.MetadataRetrieveEvent; import com.intellij.execution.filters.TextConsoleBuilderFactory; @@ -17,7 +19,6 @@ import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.icons.AllIcons; import com.intellij.openapi.Disposable; -import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.IconButton; import com.intellij.openapi.ui.popup.JBPopupFactory; @@ -26,7 +27,6 @@ import com.intellij.util.messages.MessageBus; import com.intellij.util.ui.JBUI; import com.albertoventurini.graphdbplugin.database.api.query.GraphQueryResult; -import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.DataSourceMetadata; import com.albertoventurini.graphdbplugin.jetbrains.ui.console.GraphConsoleView; import org.jetbrains.annotations.Nullable; @@ -107,6 +107,26 @@ public void executionCompleted(ExecuteQueryPayload payload) { } }); + messageBus.connect().subscribe(VersionFetchingProcessEvent.VERSION_FETCHING_PROCESS_TOPIC, new VersionFetchingProcessEvent() { + @Override + public void processStarted(DataSourceApi dataSource) { + info(String.format("Fetching database version for %s:", dataSource.getName())); + newLine(); + } + + @Override + public void versionReceived(GraphDatabaseVersion version) { + info(String.format("Version discovered: %s", version)); + newLine(); + } + + @Override + public void handleError(Exception exception) { + error("Error occurred: "); + printException(exception); + } + }); + messageBus.connect().subscribe(MetadataRetrieveEvent.METADATA_RETRIEVE_EVENT, new MetadataRetrieveEvent() { @Override public void startMetadataRefresh(DataSourceApi nodeDataSource) { diff --git a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/DataSourceMetadataUpdateService.java b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/DataSourceMetadataUpdateService.java index 42c54c5d..e0a45427 100644 --- a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/DataSourceMetadataUpdateService.java +++ b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/DataSourceMetadataUpdateService.java @@ -59,15 +59,15 @@ public CompletableFuture updateDataSourceMetadataUi( final DataSourceType sourceType = nodeDataSource.getDataSourceType(); return treeUpdaters.get(sourceType) - .map(updater -> getMetadataAndApplyHandler(updater, node, nodeDataSource)) + .map(updater -> updateMetadataAndApplyHandler(updater, node, nodeDataSource)) .orElse(completedFuture(false)); } - private CompletableFuture getMetadataAndApplyHandler( + private CompletableFuture updateMetadataAndApplyHandler( final DataSourceTreeUpdater updater, final PatchedDefaultMutableTreeNode node, final DataSourceApi nodeDataSource) { - return dataSourcesComponent.getMetadata(nodeDataSource) + return dataSourcesComponent.updateMetadata(nodeDataSource) .thenApply(data -> data.map(d -> { updater.updateTree(node, d); return true; diff --git a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/Neo4jBoltTreeUpdater.java b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/Neo4jBoltTreeUpdater.java index 81c79641..bd573296 100644 --- a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/Neo4jBoltTreeUpdater.java +++ b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/Neo4jBoltTreeUpdater.java @@ -1,5 +1,6 @@ package com.albertoventurini.graphdbplugin.jetbrains.ui.datasource.metadata; +import com.albertoventurini.graphdbplugin.database.api.data.GraphDatabaseVersion; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.neo4j.*; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.state.DataSourceApi; import com.albertoventurini.graphdbplugin.jetbrains.ui.datasource.tree.LabelTreeNodeModel; @@ -20,6 +21,7 @@ */ final class Neo4jBoltTreeUpdater implements DataSourceTreeUpdater { + private static final String VERSION_TITLE = "version: %s"; private static final String RELATIONSHIP_TYPES_TITLE = "relationship types (%s)"; private static final String PROPERTY_KEYS_TITLE = "property keys"; private static final String LABELS_TITLE = "labels (%s)"; @@ -38,6 +40,7 @@ public void updateTree( TreeNodeModelApi model = (TreeNodeModelApi) dataSourceRootTreeNode.getUserObject(); DataSourceApi dataSourceApi = model.getDataSourceApi(); + dataSourceRootTreeNode.add(createVersionNode(neo4jMetadata.version(), dataSourceApi)); dataSourceRootTreeNode.add(createConstraintsNode(neo4jMetadata.constraints(), dataSourceApi)); dataSourceRootTreeNode.add(createIndexesNode(neo4jMetadata.indexes(), dataSourceApi)); dataSourceRootTreeNode.add(createLabelsNode(neo4jMetadata, dataSourceApi)); @@ -53,6 +56,16 @@ public void updateTree( } } + @NotNull + private PatchedDefaultMutableTreeNode createVersionNode( + final GraphDatabaseVersion version, + final DataSourceApi dataSourceApi) { + return of(new MetadataTreeNodeModel( + VERSION, + dataSourceApi, + VERSION_TITLE.formatted(version.toString()), + GraphIcons.Nodes.VERSION)); + } @NotNull private PatchedDefaultMutableTreeNode createFunctionNode( final List functionMetadata, diff --git a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/tree/Neo4jTreeNodeType.java b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/tree/Neo4jTreeNodeType.java index fb08ddd4..5beff0ee 100644 --- a/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/tree/Neo4jTreeNodeType.java +++ b/ui/jetbrains/src/main/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/tree/Neo4jTreeNodeType.java @@ -9,6 +9,7 @@ public enum Neo4jTreeNodeType implements NodeType { ROOT, DATASOURCE, + VERSION, INDEXES, INDEX, CONSTRAINTS, diff --git a/ui/jetbrains/src/test/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/ContextMenuTest.java b/ui/jetbrains/src/test/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/ContextMenuTest.java index 4b655a4d..5c7dcab0 100644 --- a/ui/jetbrains/src/test/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/ContextMenuTest.java +++ b/ui/jetbrains/src/test/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/ContextMenuTest.java @@ -6,6 +6,7 @@ */ package com.albertoventurini.graphdbplugin.jetbrains.ui.datasource.metadata; +import com.albertoventurini.graphdbplugin.database.neo4j.bolt.data.Neo4jGraphDatabaseVersion; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.DataSourceType; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.neo4j.*; import com.albertoventurini.graphdbplugin.jetbrains.ui.datasource.metadata.dto.DataSourceContextMenu; @@ -28,11 +29,7 @@ import java.util.Enumeration; import java.util.HashMap; -import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; /** * Test that the correct context menu is associated with @@ -71,6 +68,7 @@ public void setUp() throws Exception { "List all labels in the database."); final Neo4jMetadata metadata = new Neo4jMetadata( + new Neo4jGraphDatabaseVersion(0, 0, 0), Collections.emptyList(), Collections.singletonList(procedure), Collections.singletonList(constraintMetadata), diff --git a/ui/jetbrains/src/test/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/DataSourceMetadataUpdateServiceTest.java b/ui/jetbrains/src/test/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/DataSourceMetadataUpdateServiceTest.java index d711dfe7..8d3b0064 100644 --- a/ui/jetbrains/src/test/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/DataSourceMetadataUpdateServiceTest.java +++ b/ui/jetbrains/src/test/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/DataSourceMetadataUpdateServiceTest.java @@ -33,7 +33,7 @@ public void setUp() throws Exception { final var treeUpdaters = mock(DataSourceTreeUpdaters.class); final var dataSourceMetadata = mock(DataSourceMetadata.class); - when(metadataComponent.getMetadata(any())) + when(metadataComponent.updateMetadata(any())) .thenReturn(CompletableFuture.completedFuture(Optional.of(dataSourceMetadata))); final var treeUpdater = mock(DataSourceTreeUpdater.class); diff --git a/ui/jetbrains/src/test/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/Neo4JBoltTreeUpdaterTest.java b/ui/jetbrains/src/test/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/Neo4JBoltTreeUpdaterTest.java index a4cb9593..79413e98 100644 --- a/ui/jetbrains/src/test/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/Neo4JBoltTreeUpdaterTest.java +++ b/ui/jetbrains/src/test/java/com/albertoventurini/graphdbplugin/jetbrains/ui/datasource/metadata/Neo4JBoltTreeUpdaterTest.java @@ -6,6 +6,7 @@ */ package com.albertoventurini.graphdbplugin.jetbrains.ui.datasource.metadata; +import com.albertoventurini.graphdbplugin.database.neo4j.bolt.data.Neo4jGraphDatabaseVersion; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.DataSourceType; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.metadata.neo4j.*; import com.albertoventurini.graphdbplugin.jetbrains.component.datasource.state.impl.DataSourceV1; @@ -58,6 +59,7 @@ public void setUp() { "List all labels in the database."); final Neo4jMetadata metadata = new Neo4jMetadata( + new Neo4jGraphDatabaseVersion(0, 0, 0), Collections.emptyList(), Collections.singletonList(procedure), Collections.singletonList(constraintMetadata),