Skip to content

Commit

Permalink
Migrate to using elementId (#84)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
robsdedude authored Sep 12, 2023
1 parent fece746 commit f20d96e
Show file tree
Hide file tree
Showing 25 changed files with 272 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -18,4 +19,6 @@ public interface GraphDatabaseApi {
GraphQueryResult execute(String query, Map<String, Object> statementParameters);

GraphMetadata metadata();

GraphDatabaseVersion getVersion();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Copied and adapted from plugin
* <a href="https://github.com/neueda/jetbrains-plugin-graph-database-support">Graph Database Support</a>
* 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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -110,4 +120,19 @@ public GraphQueryResult execute(String query, Map<String, Object> 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]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class Neo4jBoltNode implements GraphNode {
private final List<String> 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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> types, GraphPropertyContainer propertyContainer, String startNodeId, String endNodeId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copied and adapted from plugin
* <a href="https://github.com/neueda/jetbrains-plugin-graph-database-support">Graph Database Support</a>
* 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);
}
}
2 changes: 2 additions & 0 deletions graph-database-plugin/src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@
serviceImplementation="com.albertoventurini.graphdbplugin.jetbrains.ui.datasource.DataSourcesView"/>
<projectService
serviceImplementation="com.albertoventurini.graphdbplugin.jetbrains.ui.console.params.ParametersService"/>
<projectService
serviceImplementation="com.albertoventurini.graphdbplugin.jetbrains.database.VersionService"/>

<scratch.rootType implementation="com.albertoventurini.graphdbplugin.jetbrains.ui.datasource.interactions.GraphDbEditorsConsoleRootType"/>
<scratch.rootType implementation="com.albertoventurini.graphdbplugin.jetbrains.ui.console.params.ParameterRootType"/>
Expand Down
1 change: 1 addition & 0 deletions platform/src/main/java/icons/GraphIcons.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions testing/integration-neo4j/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,13 +30,13 @@ public void setUp() throws Exception {
public abstract DataSourceApi getDataSource();

public void testMetadataExists() throws ExecutionException, InterruptedException {
Optional<DataSourceMetadata> metadata = component().dataSourcesMetadata().getMetadata(getDataSource()).get();
Optional<DataSourceMetadata> metadata = component().dataSourcesMetadata().updateMetadata(getDataSource()).get();
assertThat(metadata).isPresent();
}

protected DataSourceMetadata getMetadata() {
try {
CompletableFuture<Optional<DataSourceMetadata>> futureMeta = component().dataSourcesMetadata().getMetadata(getDataSource());
CompletableFuture<Optional<DataSourceMetadata>> futureMeta = component().dataSourcesMetadata().updateMetadata(getDataSource());
return futureMeta.get(30, TimeUnit.SECONDS)
.orElseThrow(() -> new IllegalStateException("Metadata should not be null"));
} catch (InterruptedException | ExecutionException | TimeoutException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@ public DataSourcesComponentState getState() {
}

public void refreshAllMetadata() {
getDataSourceContainer().getDataSources().forEach(d -> {
componentMetadata.getMetadata(d);
});
getDataSourceContainer().getDataSources().forEach(componentMetadata::updateMetadata);
}
//
// @NotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Optional<DataSourceMetadata>> getMetadata(DataSourceApi dataSource) {
public CompletableFuture<Optional<DataSourceMetadata>> updateMetadata(DataSourceApi dataSource) {
MetadataRetrieveEvent metadataRetrieveEvent = messageBus.syncPublisher(MetadataRetrieveEvent.METADATA_RETRIEVE_EVENT);

metadataRetrieveEvent.startMetadataRefresh(dataSource);
Expand Down Expand Up @@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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<Neo4jFunctionMetadata> functions,
List<Neo4jProcedureMetadata> procedures,
List<Neo4jConstraintMetadata> constraints,
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<Neo4jIndexMetadata> indexes = new ArrayList<>();
final List<Neo4jProcedureMetadata> procedures = new ArrayList<>();
final List<Neo4jConstraintMetadata> constraints = new ArrayList<>();
Expand All @@ -60,15 +63,17 @@ 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);
final var labels = getLabels(db);
final var relationshipTypes = getRelationshipTypes(db);

return new Neo4jMetadata(
version,
functions,
procedures,
constraints,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit f20d96e

Please sign in to comment.