Skip to content

Commit

Permalink
neo4j native ids performance improvement
Browse files Browse the repository at this point in the history
  • Loading branch information
rjbaucells committed Oct 20, 2016
1 parent 86aa5e1 commit 2fede93
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 29 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Pros:

Cons:

* CREATE statements will run slower since the entity must be retrieved from the database in order to recover the generated id: `CREATE (n:label{field1: value, ..., fieldN: valueN}) RETURN n`
* CREATE statements will run slower since the entity id must be retrieved from the database after insertion: `CREATE (n:label{field1: value, ..., fieldN: valueN}) RETURN ID(n)`
* Entity IDs in Neo4J are not guaranteed to be the same after a database restart/upgrade. Storing links to Neo4J entities outside the database based on IDs could become invalid after a database restart/upgrade.

### Database sequence support, see [DatabaseSequenceElementIdProvider](https://github.com/SteelBridgeLabs/neo4j-gremlin-bolt/blob/master/src/main/java/com/steelbridgelabs/oss/neo4j/structure/providers/DatabaseSequenceElementIdProvider.java) for more information.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,17 @@
*/
class Neo4JDatabaseCommand {

private static final Consumer<StatementResult> noop = result -> {
};

private final Statement statement;
private final Consumer<StatementResult> callback;

public Neo4JDatabaseCommand(Statement statement) {
this.statement = statement;
this.callback = noop;
}

public Neo4JDatabaseCommand(Statement statement, Consumer<StatementResult> callback) {
this.statement = statement;
this.callback = callback;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,23 +318,27 @@ private Map<String, Object> statementParameters() {

@Override
public Neo4JDatabaseCommand insertCommand() {
// create statement
String statement = out.matchStatement("o", "oid") + " " + in.matchStatement("i", "iid") + " CREATE (o)-[" + (id == null ? "r" : "") + ":`" + label + "`{ep}]->(i)" + (id == null ? " RETURN r" : "");
// parameters
Value parameters = Values.parameters("oid", out.id(), "iid", in.id(), "ep", statementParameters());
// command statement
return new Neo4JDatabaseCommand(new Statement(statement, parameters), result -> {
// check we need to process id
if (id == null) {
// check database side id generation is required
if (id == null) {
// create statement
String statement = out.matchStatement("o", "oid") + " " + in.matchStatement("i", "iid") + " CREATE (o)-[r:`" + label + "`{ep}]->(i) RETURN " + edgeIdProvider.matchPredicateOperand("r");
// command statement
return new Neo4JDatabaseCommand(new Statement(statement, parameters), result -> {
// check we received data
if (result.hasNext()) {
// record
Record record = result.next();
// process node identifier
generatedId = edgeIdProvider.get(record.get(0).asEntity());
generatedId = edgeIdProvider.processIdentifier(record.get(0).asObject());
}
}
});
});
}
// create statement
String statement = out.matchStatement("o", "oid") + " " + in.matchStatement("i", "iid") + " CREATE (o)-[:`" + label + "`{ep}]->(i)";
// command statement
return new Neo4JDatabaseCommand(new Statement(statement, parameters));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -931,23 +931,25 @@ public Neo4JDatabaseCommand insertCommand() {
// concat labels with additional labels on insertion
SortedSet<String> labels = Stream.concat(this.labels.stream(), additionalLabels.stream()).collect(Collectors.toCollection(TreeSet::new));
try {
// create statement
String statement = "CREATE (" + (id == null ? "n" : "") + processLabels(labels, false) + "{vp})" + (id == null ? " RETURN n" : "");
// parameters
Value parameters = Values.parameters("vp", statementParameters());
// command statement
return new Neo4JDatabaseCommand(new Statement(statement, parameters), result -> {
// check we need to process id
if (id == null) {
// check database side id generation is required
if (id == null) {
// create statement
String statement = "CREATE (n" + processLabels(labels, false) + "{vp}) RETURN " + vertexIdProvider.matchPredicateOperand("n");
// command statement
return new Neo4JDatabaseCommand(new Statement(statement, parameters), result -> {
// check we received data
if (result.hasNext()) {
// record
Record record = result.next();
// process node identifier
generatedId = vertexIdProvider.get(record.get(0).asEntity());
generatedId = vertexIdProvider.processIdentifier(record.get(0).asObject());
}
}
});
});
}
// command statement
return new Neo4JDatabaseCommand(new Statement("CREATE (" + processLabels(labels, false) + "{vp})", parameters));
}
finally {
// to find vertex in database (labels + additional labels)
Expand Down Expand Up @@ -985,8 +987,7 @@ public Neo4JDatabaseCommand updateCommand() {
builder.append(" REMOVE v").append(processLabels(labelsRemoved, false));
}
// command statement
return new Neo4JDatabaseCommand(new Statement(builder.toString(), parameters), result -> {
});
return new Neo4JDatabaseCommand(new Statement(builder.toString(), parameters));
}
return null;
}
Expand All @@ -998,8 +999,7 @@ public Neo4JDatabaseCommand deleteCommand() {
// parameters
Value parameters = Values.parameters("id", id());
// command statement
return new Neo4JDatabaseCommand(new Statement(statement, parameters), result -> {
});
return new Neo4JDatabaseCommand(new Statement(statement, parameters));
}

void commit() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ public void givenNoIdGenerationProviderShouldCreateInsertCommand() {
Mockito.when(inVertex.matchPredicate(Mockito.any(), Mockito.any())).thenAnswer(invocation -> "ID(i) = {iid}");
Mockito.when(inVertex.id()).thenAnswer(invocation -> 2L);
Mockito.when(inVertex.matchStatement(Mockito.anyString(), Mockito.anyString())).thenAnswer(invocation -> "MATCH (i) WHERE ID(i) = {iid}");
Mockito.when(edgeIdProvider.get(Mockito.any())).thenAnswer(invocation -> 3L);
Mockito.when(edgeIdProvider.processIdentifier(Mockito.any())).thenAnswer(invocation -> 3L);
Mockito.when(edgeIdProvider.fieldName()).thenAnswer(invocation -> "id");
Mockito.when(edgeIdProvider.matchPredicateOperand(Mockito.anyString())).thenAnswer(invocation -> "r.id");
Mockito.when(edgeIdProvider.matchPredicateOperand(Mockito.anyString())).thenAnswer(invocation -> "ID(r)");
Mockito.when(statementResult.hasNext()).thenAnswer(invocation -> true);
Mockito.when(statementResult.next()).thenAnswer(invocation -> record);
Mockito.when(record.get(Mockito.eq(0))).thenAnswer(invocation -> value);
Expand All @@ -100,7 +100,7 @@ public void givenNoIdGenerationProviderShouldCreateInsertCommand() {
Assert.assertNull("Failed get node identifier", edge.id());
Assert.assertNotNull("Failed to create insert command", command);
Assert.assertNotNull("Failed to create insert command statement", command.getStatement());
Assert.assertEquals("Invalid insert command statement", command.getStatement().text(), "MATCH (o) WHERE ID(o) = {oid} MATCH (i) WHERE ID(i) = {iid} CREATE (o)-[r:`L1`{ep}]->(i) RETURN r");
Assert.assertEquals("Invalid insert command statement", command.getStatement().text(), "MATCH (o) WHERE ID(o) = {oid} MATCH (i) WHERE ID(i) = {iid} CREATE (o)-[r:`L1`{ep}]->(i) RETURN ID(r)");
Assert.assertEquals("Invalid insert command statement", command.getStatement().parameters(), Values.parameters("oid", 1L, "iid", 2L, "ep", Collections.emptyMap()));
Assert.assertNotNull("Failed to create insert command callback", command.getCallback());
// invoke callback
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ public void givenNoIdGenerationProviderShouldCreateInsertCommand() {
Mockito.when(node.labels()).thenAnswer(invocation -> Collections.singletonList("l1"));
Mockito.when(node.keys()).thenAnswer(invocation -> Collections.singleton("key1"));
Mockito.when(node.get(Mockito.eq("key1"))).thenAnswer(invocation -> Values.value("value1"));
Mockito.when(vertexIdProvider.get(Mockito.any())).thenAnswer(invocation -> 1L);
Mockito.when(vertexIdProvider.processIdentifier(Mockito.any())).thenAnswer(invocation -> 1L);
Mockito.when(vertexIdProvider.fieldName()).thenAnswer(invocation -> "id");
Mockito.when(vertexIdProvider.matchPredicateOperand(Mockito.anyString())).thenAnswer(invocation -> "n.id");
Mockito.when(vertexIdProvider.matchPredicateOperand(Mockito.anyString())).thenAnswer(invocation -> "ID(n)");
Mockito.when(statementResult.hasNext()).thenAnswer(invocation -> true);
Mockito.when(statementResult.next()).thenAnswer(invocation -> record);
Mockito.when(record.get(Mockito.eq(0))).thenAnswer(invocation -> value);
Expand All @@ -124,7 +124,7 @@ public void givenNoIdGenerationProviderShouldCreateInsertCommand() {
Assert.assertNull("Failed get node identifier", vertex.id());
Assert.assertNotNull("Failed to create insert command", command);
Assert.assertNotNull("Failed to create insert command statement", command.getStatement());
Assert.assertEquals("Invalid insert command statement", command.getStatement().text(), "CREATE (n:`L1`{vp}) RETURN n");
Assert.assertEquals("Invalid insert command statement", command.getStatement().text(), "CREATE (n:`L1`{vp}) RETURN ID(n)");
Assert.assertEquals("Invalid insert command statement", command.getStatement().parameters(), Values.parameters("vp", Collections.emptyMap()));
Assert.assertNotNull("Failed to create insert command callback", command.getCallback());
// invoke callback
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2016 SteelBridge Laboratories, LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* For more information: http://steelbridgelabs.com
*/

package com.steelbridgelabs.oss.neo4j.structure;

import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.Transaction;
import org.apache.tinkerpop.gremlin.structure.VertexProperty;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.neo4j.driver.v1.Values;
import org.neo4j.driver.v1.types.Node;

import java.util.Collections;

/**
* @author Rogelio J. Baucells
*/
@RunWith(MockitoJUnitRunner.class)
public class Neo4JVertexWhileCreatingMatchStatementTest {

@Mock
private Neo4JGraph graph;

@Mock
private Transaction transaction;

@Mock
private Neo4JSession session;

@Mock
private Neo4JReadPartition partition;

@Mock
private Node node;

@Mock
private Neo4JElementIdProvider provider;

@Mock
private Graph.Features.VertexFeatures vertexFeatures;

@Mock
private Graph.Features features;

@Test
public void givenAliasShouldCreateMatchStatement() {
// arrange
Mockito.when(vertexFeatures.getCardinality(Mockito.anyString())).thenAnswer(invocation -> VertexProperty.Cardinality.single);
Mockito.when(features.vertex()).thenAnswer(invocation -> vertexFeatures);
Mockito.when(partition.validateLabel(Mockito.anyString())).thenAnswer(invocation -> true);
Mockito.when(partition.vertexMatchPredicate(Mockito.eq("a"))).thenAnswer(invocation -> "(a:l1 OR a:l2)");
Mockito.when(graph.tx()).thenAnswer(invocation -> transaction);
Mockito.when(graph.getPartition()).thenAnswer(invocation -> partition);
Mockito.when(graph.features()).thenAnswer(invocation -> features);
Mockito.when(node.get(Mockito.eq("id"))).thenAnswer(invocation -> Values.value(1L));
Mockito.when(node.labels()).thenAnswer(invocation -> Collections.singletonList("l1"));
Mockito.when(node.keys()).thenAnswer(invocation -> Collections.singleton("key1"));
Mockito.when(node.get(Mockito.eq("key1"))).thenAnswer(invocation -> Values.value("value1"));
Mockito.when(provider.generate()).thenAnswer(invocation -> 2L);
Mockito.when(provider.fieldName()).thenAnswer(invocation -> "id");
Mockito.when(provider.matchPredicateOperand(Mockito.anyString())).thenAnswer(invocation -> "a.id");
Neo4JVertex vertex = new Neo4JVertex(graph, session, provider, provider, node);
// act
String result = vertex.matchStatement("a", "id");
// assert
Assert.assertNotNull("Failed to create match predicate", result);
Assert.assertEquals("Invalid match predicate", result, "MATCH (a:`l1`) WHERE a.id = {id}");
}
}

0 comments on commit 2fede93

Please sign in to comment.