From f35307ecda1d8131b515d993de14e11a71d329fd Mon Sep 17 00:00:00 2001 From: fickludd Date: Sat, 20 Jan 2018 00:16:34 +0100 Subject: [PATCH] Tx-state aware traversal by group --- .../kernel/api/RelationshipTestSupport.java | 127 +++++---- .../RelationshipTransactionStateTestBase.java | 196 ++++++++++---- .../RelationshipTraversalCursorTestBase.java | 26 +- .../org/neo4j/kernel/impl/newapi/Read.java | 28 +- .../impl/newapi/RelationshipGroupCursor.java | 71 ++++- .../newapi/RelationshipTraversalCursor.java | 253 ++++++++++-------- 6 files changed, 467 insertions(+), 234 deletions(-) diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipTestSupport.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipTestSupport.java index aec6a55135ed..0e3787a2288e 100644 --- a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipTestSupport.java +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipTestSupport.java @@ -34,6 +34,7 @@ import org.neo4j.helpers.collection.Iterators; import org.neo4j.internal.kernel.api.exceptions.KernelException; +import static java.lang.String.format; import static org.junit.Assert.assertEquals; import static org.neo4j.graphdb.RelationshipType.withName; @@ -87,21 +88,10 @@ static void someGraph( GraphDatabaseService graphDb ) } } - static Function[] sparseDenseRels = Iterators.array( - outgoing( "FOO" ), - outgoing( "BAR" ), - outgoing( "BAR" ), - incoming( "FOO" ), - outgoing( "FOO" ), - incoming( "BAZ" ), - incoming( "BAR" ), - outgoing( "BAZ" ) - ); - static StartNode sparse( GraphDatabaseService graphDb ) { Node node; - Map> relationshipMap; + Map> relationshipMap; try ( Transaction tx = graphDb.beginTx() ) { node = graphDb.createNode(); @@ -114,56 +104,69 @@ static StartNode sparse( GraphDatabaseService graphDb ) static StartNode dense( GraphDatabaseService graphDb ) { Node node; - Map> relationshipMap; + Map> relationshipMap; try ( Transaction tx = graphDb.beginTx() ) { node = graphDb.createNode(); relationshipMap = buildSparseDenseRels( node ); + List bulk = new ArrayList<>(); + RelationshipType bulkType = withName( "BULK" ); + for ( int i = 0; i < 200; i++ ) { - node.createRelationshipTo( graphDb.createNode(), withName( "BULK" ) ); + Relationship r = node.createRelationshipTo( graphDb.createNode(), bulkType ); + bulk.add( new StartRelationship( r.getId(), Direction.OUTGOING, bulkType ) ); } + String bulkKey = computeKey( "BULK", Direction.OUTGOING ); + relationshipMap.put( bulkKey, bulk ); + tx.success(); } return new StartNode( node.getId(), relationshipMap ); } - static void count( + static Map count( Session session, - RelationshipTraversalCursor relationship, - Map counts, - boolean expectSameType ) throws KernelException + RelationshipTraversalCursor relationship ) throws KernelException { - Integer type = null; + HashMap counts = new HashMap<>(); while ( relationship.next() ) { - if ( expectSameType ) - { - if ( type != null ) - { - assertEquals( "same type", type.intValue(), relationship.label() ); - } - else - { - type = relationship.label(); - } - } - String key = computeKey( session, relationship ); - counts.compute( key, ( k, value ) -> value == null ? 1 : value + 1 ); } + return counts; } - static class R + static void assertCount( + Session session, + RelationshipTraversalCursor relationship, + Map expectedCounts, + int expectedType, + Direction direction ) throws KernelException + { + String key = computeKey( session.token().relationshipTypeName( expectedType ), direction ); + int expectedCount = expectedCounts.getOrDefault( key, 0 ); + int count = 0; + + while ( relationship.next() ) + { + assertEquals( "same type", expectedType, relationship.label() ); + count++; + } + + assertEquals( format( "expected number of relationships for key '%s'", key ), expectedCount, count ); + } + + static class StartRelationship { public final long id; public final Direction direction; public final RelationshipType type; - R( long id, Direction direction, RelationshipType type ) + StartRelationship( long id, Direction direction, RelationshipType type ) { this.id = id; this.type = type; @@ -174,9 +177,9 @@ static class R static class StartNode { public final long id; - public final Map> relationships; + public final Map> relationships; - StartNode( long id, Map> relationships ) + StartNode( long id, Map> relationships ) { this.id = id; this.relationships = relationships; @@ -185,7 +188,7 @@ static class StartNode Map expectedCounts() { Map expectedCounts = new HashMap<>(); - for ( Map.Entry> kv : relationships.entrySet() ) + for ( Map.Entry> kv : relationships.entrySet() ) { expectedCounts.put( kv.getKey(), relationships.get( kv.getKey() ).size() ); } @@ -198,24 +201,24 @@ static void assertCounts( Map expectedCounts, Map expected : expectedCounts.entrySet() ) { assertEquals( - String.format( "counts for relationship key '%s' are equal", expected.getKey() ), + format( "counts for relationship key '%s' are equal", expected.getKey() ), expected.getValue(), counts.get( expected.getKey()) ); } } - private static Map> buildSparseDenseRels( Node node ) + private static Map> buildSparseDenseRels( Node node ) { - Map> relationshipMap = new HashMap<>(); - for ( Function rel : sparseDenseRels ) + Map> relationshipMap = new HashMap<>(); + for ( Function rel : sparseDenseRels ) { - R r = rel.apply( node ); - List relsOfType = relationshipMap.computeIfAbsent( computeKey( r ), key -> new ArrayList<>() ); + StartRelationship r = rel.apply( node ); + List relsOfType = relationshipMap.computeIfAbsent( computeKey( r ), key -> new ArrayList<>() ); relsOfType.add( r ); } return relationshipMap; } - private static String computeKey( R r ) + private static String computeKey( StartRelationship r ) { return computeKey( r.type.name(), r.direction ); } @@ -239,34 +242,58 @@ else if ( r.sourceNodeReference() == r.originNodeReference() ) return computeKey( session.token().relationshipTypeName( r.label() ), d ); } - private static String computeKey( String type, Direction direction ) + static String computeKey( String type, Direction direction ) { - return type.toString() + "-" + direction.toString(); + return type + "-" + direction.toString(); } - private static Function outgoing( String type ) + private static Function[] sparseDenseRels = Iterators.array( + outgoing( "FOO" ), + outgoing( "BAR" ), + outgoing( "BAR" ), + incoming( "FOO" ), + outgoing( "FOO" ), + incoming( "BAZ" ), + incoming( "BAR" ), + outgoing( "BAZ" ), + loop( "FOO" ) + ); + + private static Function outgoing( String type ) { return node -> { GraphDatabaseService db = node.getGraphDatabase(); RelationshipType relType = withName( type ); - return new R( + return new StartRelationship( node.createRelationshipTo( db.createNode(), relType ).getId(), Direction.OUTGOING, relType ); }; } - private static Function incoming( String type ) + private static Function incoming( String type ) { return node -> { GraphDatabaseService db = node.getGraphDatabase(); RelationshipType relType = withName( type ); - return new R( + return new StartRelationship( db.createNode().createRelationshipTo( node, relType ).getId(), Direction.INCOMING, relType ); }; } + + private static Function loop( String type ) + { + return node -> + { + RelationshipType relType = withName( type ); + return new StartRelationship( + node.createRelationshipTo( node, relType ).getId(), + Direction.BOTH, + relType ); + }; + } } diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipTransactionStateTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipTransactionStateTestBase.java index 478e7d87cb32..c156937d8361 100644 --- a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipTransactionStateTestBase.java +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipTransactionStateTestBase.java @@ -25,10 +25,17 @@ import java.util.List; import java.util.Map; +import org.neo4j.internal.kernel.api.exceptions.KernelException; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.neo4j.graphdb.Direction.BOTH; +import static org.neo4j.graphdb.Direction.INCOMING; +import static org.neo4j.graphdb.Direction.OUTGOING; +import static org.neo4j.internal.kernel.api.RelationshipTestSupport.assertCount; import static org.neo4j.internal.kernel.api.RelationshipTestSupport.assertCounts; +import static org.neo4j.internal.kernel.api.RelationshipTestSupport.computeKey; import static org.neo4j.internal.kernel.api.RelationshipTestSupport.count; @SuppressWarnings( "Duplicates" ) @@ -194,30 +201,6 @@ public void shouldSeeRelationshipInTransactionBeforeCursorInitialization() throw } } -// @Test -// public void shouldTraverseSparseNodeViaGroups() throws Exception -// { -// traverseViaGroups( RelationshipTestSupport.sparse( graphDb ), false ); -// } -// -// @Test -// public void shouldTraverseDenseNodeViaGroups() throws Exception -// { -// traverseViaGroups( RelationshipTestSupport.dense( graphDb ), false ); -// } -// -// @Test -// public void shouldTraverseSparseNodeViaGroupsWithDetachedReferences() throws Exception -// { -// traverseViaGroups( RelationshipTestSupport.sparse( graphDb ), true ); -// } -// -// @Test -// public void shouldTraverseDenseNodeViaGroupsWithDetachedReferences() throws Exception -// { -// traverseViaGroups( RelationshipTestSupport.dense( graphDb ), true ); -// } - @Test public void shouldTraverseSparseNodeWithoutGroups() throws Exception { @@ -242,40 +225,39 @@ public void shouldTraverseDenseNodeWithoutGroupsWithDetachedReferences() throws traverseWithoutGroups( RelationshipTestSupport.dense( graphDb ), true ); } + @Test + public void shouldTraverseSparseNodeViaGroups() throws Exception + { + traverseViaGroups( RelationshipTestSupport.sparse( graphDb ), false ); + } + + @Test + public void shouldTraverseDenseNodeViaGroups() throws Exception + { + traverseViaGroups( RelationshipTestSupport.dense( graphDb ), false ); + } + + @Test + public void shouldTraverseSparseNodeViaGroupsWithDetachedReferences() throws Exception + { + traverseViaGroups( RelationshipTestSupport.sparse( graphDb ), true ); + } + + @Test + public void shouldTraverseDenseNodeViaGroupsWithDetachedReferences() throws Exception + { + traverseViaGroups( RelationshipTestSupport.dense( graphDb ), true ); + } + private void traverseWithoutGroups( RelationshipTestSupport.StartNode start, boolean detached ) throws Exception { try ( Transaction tx = session.beginTransaction() ) { - Map expectedCounts = new HashMap<>(); - for ( Map.Entry> kv : start.relationships.entrySet() ) - { - List r = kv.getValue(); - RelationshipTestSupport.R head = r.get( 0 ); - int label = session.token().relationshipType( head.type.name() ); - switch ( head.direction ) - { - case INCOMING: - tx.dataWrite().relationshipCreate( tx.dataWrite().nodeCreate(), label, start.id ); - tx.dataWrite().relationshipCreate( tx.dataWrite().nodeCreate(), label, start.id ); - break; - case OUTGOING: - tx.dataWrite().relationshipCreate( start.id, label, tx.dataWrite().nodeCreate() ); - tx.dataWrite().relationshipCreate( start.id, label, tx.dataWrite().nodeCreate() ); - break; - case BOTH: - tx.dataWrite().relationshipCreate( start.id, label, start.id ); - tx.dataWrite().relationshipCreate( start.id, label, start.id ); - break; - default: - throw new IllegalStateException( "Oh ye be cursed, foul checkstyle!" ); - } - tx.dataWrite().relationshipDelete( head.id ); - expectedCounts.put( kv.getKey(), r.size() + 1 ); - } + Map expectedCounts = modifyStartNodeRelationships( start, tx ); // given try ( NodeCursor node = cursors.allocateNodeCursor(); - RelationshipTraversalCursor relationship = cursors.allocateRelationshipTraversalCursor() ) + RelationshipTraversalCursor relationship = cursors.allocateRelationshipTraversalCursor() ) { // when tx.dataRead().singleNode( start.id, node ); @@ -290,8 +272,7 @@ private void traverseWithoutGroups( RelationshipTestSupport.StartNode start, boo node.allRelationships( relationship ); } - Map counts = new HashMap<>(); - count( session, relationship, counts, false ); + Map counts = count( session, relationship ); // then assertCounts( expectedCounts, counts ); @@ -300,4 +281,113 @@ private void traverseWithoutGroups( RelationshipTestSupport.StartNode start, boo tx.failure(); } } + + private void traverseViaGroups( RelationshipTestSupport.StartNode start, boolean detached ) throws Exception + { + try ( Transaction tx = session.beginTransaction() ) + { + Read read = tx.dataRead(); + Map expectedCounts = modifyStartNodeRelationships( start, tx ); + + // given + try ( NodeCursor node = cursors.allocateNodeCursor(); + RelationshipGroupCursor group = cursors.allocateRelationshipGroupCursor(); + RelationshipTraversalCursor relationship = cursors.allocateRelationshipTraversalCursor() ) + { + // when + read.singleNode( start.id, node ); + assertTrue( "access node", node.next() ); + if ( detached ) + { + read.relationshipGroups( start.id, node.relationshipGroupReference(), group ); + } + else + { + node.relationships( group ); + } + + while ( group.next() ) + { + // outgoing + if ( detached ) + { + read.relationships( start.id, group.outgoingReference(), relationship ); + } + else + { + group.outgoing( relationship ); + } + // then + assertCount( session, relationship, expectedCounts, group.relationshipLabel(), OUTGOING ); + + // incoming + if ( detached ) + { + read.relationships( start.id, group.incomingReference(), relationship ); + } + else + { + group.incoming( relationship ); + } + // then + assertCount( session, relationship, expectedCounts, group.relationshipLabel(), INCOMING ); + + // loops + if ( detached ) + { + read.relationships( start.id, group.loopsReference(), relationship ); + } + else + { + group.loops( relationship ); + } + // then + assertCount( session, relationship, expectedCounts, group.relationshipLabel(), BOTH ); + } + } + } + } + + private Map modifyStartNodeRelationships( RelationshipTestSupport.StartNode start, Transaction tx ) + throws KernelException + { + Map expectedCounts = new HashMap<>(); + for ( Map.Entry> kv : start.relationships.entrySet() ) + { + List rs = kv.getValue(); + RelationshipTestSupport.StartRelationship head = rs.get( 0 ); + int label = session.token().relationshipType( head.type.name() ); + switch ( head.direction ) + { + case INCOMING: + tx.dataWrite().relationshipCreate( tx.dataWrite().nodeCreate(), label, start.id ); + tx.dataWrite().relationshipCreate( tx.dataWrite().nodeCreate(), label, start.id ); + break; + case OUTGOING: + tx.dataWrite().relationshipCreate( start.id, label, tx.dataWrite().nodeCreate() ); + tx.dataWrite().relationshipCreate( start.id, label, tx.dataWrite().nodeCreate() ); + break; + case BOTH: + tx.dataWrite().relationshipCreate( start.id, label, start.id ); + tx.dataWrite().relationshipCreate( start.id, label, start.id ); + break; + default: + throw new IllegalStateException( "Oh ye be cursed, foul checkstyle!" ); + } + tx.dataWrite().relationshipDelete( head.id ); + expectedCounts.put( kv.getKey(), rs.size() + 1 ); + } + + String newLabelName = "NEW"; + int newLabel = session.token().relationshipTypeGetOrCreateForName( newLabelName ); + tx.dataWrite().relationshipCreate( tx.dataWrite().nodeCreate(), newLabel, start.id ); + tx.dataWrite().relationshipCreate( start.id, newLabel, tx.dataWrite().nodeCreate() ); + tx.dataWrite().relationshipCreate( start.id, newLabel, start.id ); + + expectedCounts.put( computeKey( newLabelName, OUTGOING ), 1 ); + expectedCounts.put( computeKey( newLabelName, INCOMING ), 1 ); + expectedCounts.put( computeKey( newLabelName, BOTH ), 1 ); + + return expectedCounts; + } } diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipTraversalCursorTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipTraversalCursorTestBase.java index a51822d4ddea..7eb392cc59d4 100644 --- a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipTraversalCursorTestBase.java +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/RelationshipTraversalCursorTestBase.java @@ -22,7 +22,6 @@ import org.junit.Assume; import org.junit.Test; -import java.util.HashMap; import java.util.Map; import org.neo4j.graphdb.GraphDatabaseService; @@ -33,7 +32,11 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.neo4j.graphdb.Direction.BOTH; +import static org.neo4j.graphdb.Direction.INCOMING; +import static org.neo4j.graphdb.Direction.OUTGOING; import static org.neo4j.graphdb.RelationshipType.withName; +import static org.neo4j.internal.kernel.api.RelationshipTestSupport.assertCount; import static org.neo4j.internal.kernel.api.RelationshipTestSupport.assertCounts; import static org.neo4j.internal.kernel.api.RelationshipTestSupport.count; @@ -299,6 +302,8 @@ public void shouldTraverseDenseNodeWithoutGroupsWithDetachedReferences() throws private void traverseViaGroups( RelationshipTestSupport.StartNode start, boolean detached ) throws KernelException { // given + Map expectedCounts = start.expectedCounts(); + try ( NodeCursor node = cursors.allocateNodeCursor(); RelationshipGroupCursor group = cursors.allocateRelationshipGroupCursor(); RelationshipTraversalCursor relationship = cursors.allocateRelationshipTraversalCursor() ) @@ -314,7 +319,7 @@ private void traverseViaGroups( RelationshipTestSupport.StartNode start, boolean { node.relationships( group ); } - Map counts = new HashMap<>(); + while ( group.next() ) { // outgoing @@ -326,7 +331,8 @@ private void traverseViaGroups( RelationshipTestSupport.StartNode start, boolean { group.outgoing( relationship ); } - count( session, relationship, counts, true ); + // then + assertCount( session, relationship, expectedCounts, group.relationshipLabel(), OUTGOING ); // incoming if ( detached ) @@ -337,7 +343,8 @@ private void traverseViaGroups( RelationshipTestSupport.StartNode start, boolean { group.incoming( relationship ); } - count( session, relationship, counts, true ); + // then + assertCount( session, relationship, expectedCounts, group.relationshipLabel(), INCOMING ); // loops if ( detached ) @@ -348,11 +355,9 @@ private void traverseViaGroups( RelationshipTestSupport.StartNode start, boolean { group.loops( relationship ); } - count( session, relationship, counts, true ); + // then + assertCount( session, relationship, expectedCounts, group.relationshipLabel(), BOTH ); } - - // then - assertCounts( start.expectedCounts(), counts ); } } @@ -366,6 +371,7 @@ private void traverseWithoutGroups( RelationshipTestSupport.StartNode start, boo // when read.singleNode( start.id, node ); assertTrue( "access node", node.next() ); + if ( detached ) { read.relationships( start.id, node.allRelationshipsReference(), relationship ); @@ -374,8 +380,8 @@ private void traverseWithoutGroups( RelationshipTestSupport.StartNode start, boo { node.allRelationships( relationship ); } - Map counts = new HashMap<>(); - count( session, relationship, counts, false ); + + Map counts = count( session, relationship ); // then assertCounts( start.expectedCounts(), counts ); diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/Read.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/Read.java index 450a4d3021ac..548ff2c0db52 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/Read.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/Read.java @@ -52,7 +52,14 @@ import static org.neo4j.kernel.impl.newapi.References.Group.isRelationship; import static org.neo4j.kernel.impl.newapi.References.Relationship.isFilter; import static org.neo4j.kernel.impl.newapi.References.Relationship.isGroup; +import static org.neo4j.kernel.impl.newapi.References.Relationship.isNoIncoming; +import static org.neo4j.kernel.impl.newapi.References.Relationship.isNoLoop; +import static org.neo4j.kernel.impl.newapi.References.Relationship.isNoOutgoing; +import static org.neo4j.kernel.impl.newapi.References.Relationship.isTxStateFilter; import static org.neo4j.kernel.impl.newapi.References.clearEncoding; +import static org.neo4j.kernel.impl.newapi.RelationshipDirection.INCOMING; +import static org.neo4j.kernel.impl.newapi.RelationshipDirection.LOOP; +import static org.neo4j.kernel.impl.newapi.RelationshipDirection.OUTGOING; import static org.neo4j.kernel.impl.store.record.AbstractBaseRecord.NO_ID; abstract class Read implements TxStateHolder, @@ -291,7 +298,26 @@ else if ( isGroup( reference ) ) // this reference is actually to a group record } else if ( isFilter( reference ) ) // this relationship chain needs to be filtered { - ((RelationshipTraversalCursor) cursor).filtered( nodeReference, clearEncoding( reference ), this ); + ((RelationshipTraversalCursor) cursor).filtered( nodeReference, clearEncoding( reference ), this, true ); + } + else if ( isTxStateFilter( reference ) ) // tx-state changes should be filtered by the head of this chain + { + ((RelationshipTraversalCursor) cursor).filtered( nodeReference, clearEncoding( reference ), this, false ); + } + else if ( isNoOutgoing( reference ) ) // nothing in store, but proceed to check tx-state changes + { + int relationshipType = (int) clearEncoding( reference ); + ((RelationshipTraversalCursor) cursor).filteredTxState( nodeReference, this, relationshipType, OUTGOING ); + } + else if ( isNoIncoming( reference ) ) // nothing in store, but proceed to check tx-state changes + { + int relationshipType = (int) clearEncoding( reference ); + ((RelationshipTraversalCursor) cursor).filteredTxState( nodeReference, this, relationshipType, INCOMING ); + } + else if ( isNoLoop( reference ) ) // nothing in store, but proceed to check tx-state changes + { + int relationshipType = (int) clearEncoding( reference ); + ((RelationshipTraversalCursor) cursor).filteredTxState( nodeReference, this, relationshipType, LOOP ); } else // this is a normal relationship reference { diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipGroupCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipGroupCursor.java index e8073ee8b02f..a23b23937072 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipGroupCursor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipGroupCursor.java @@ -26,7 +26,9 @@ import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord; import org.neo4j.kernel.impl.store.record.RelationshipRecord; -import static org.neo4j.kernel.impl.newapi.References.Relationship.encodeForFiltering; +import static org.neo4j.kernel.impl.newapi.References.Relationship.encodeNoIncomingRels; +import static org.neo4j.kernel.impl.newapi.References.Relationship.encodeNoLoopRels; +import static org.neo4j.kernel.impl.newapi.References.Relationship.encodeNoOutgoingRels; class RelationshipGroupCursor extends RelationshipGroupRecord implements org.neo4j.internal.kernel.api.RelationshipGroupCursor { @@ -176,7 +178,7 @@ public int outgoingCount() { return bufferedGroup.outgoingCount; } - return count( outgoingReference() ); + return count( outgoingRawId() ); } @Override @@ -186,7 +188,7 @@ public int incomingCount() { return bufferedGroup.incomingCount; } - return count( incomingReference() ); + return count( incomingRawId() ); } @Override @@ -196,7 +198,7 @@ public int loopCount() { return bufferedGroup.loopsCount; } - return count( loopsReference() ); + return count( loopsRawId() ); } private int count( long reference ) @@ -225,7 +227,8 @@ public void outgoing( org.neo4j.internal.kernel.api.RelationshipTraversalCursor { if ( isBuffered() ) { - ((RelationshipTraversalCursor) cursor).buffered( getOwningNode(), bufferedGroup.outgoing, read ); + ((RelationshipTraversalCursor) cursor).buffered( + getOwningNode(), bufferedGroup.outgoing, RelationshipDirection.OUTGOING, bufferedGroup.label, read ); } else { @@ -238,7 +241,8 @@ public void incoming( org.neo4j.internal.kernel.api.RelationshipTraversalCursor { if ( isBuffered() ) { - ((RelationshipTraversalCursor) cursor).buffered( getOwningNode(), bufferedGroup.incoming, read ); + ((RelationshipTraversalCursor) cursor).buffered( + getOwningNode(), bufferedGroup.incoming, RelationshipDirection.INCOMING, bufferedGroup.label, read ); } else { @@ -251,7 +255,8 @@ public void loops( org.neo4j.internal.kernel.api.RelationshipTraversalCursor cur { if ( isBuffered() ) { - ((RelationshipTraversalCursor) cursor).buffered( getOwningNode(), bufferedGroup.loops, read ); + ((RelationshipTraversalCursor) cursor).buffered( + getOwningNode(), bufferedGroup.loops, RelationshipDirection.LOOP, bufferedGroup.label, read ); } else { @@ -262,19 +267,22 @@ public void loops( org.neo4j.internal.kernel.api.RelationshipTraversalCursor cur @Override public long outgoingReference() { - return getFirstOut(); + long outgoing = getFirstOut(); + return outgoing == NO_ID ? encodeNoOutgoingRels( getType() ) : encodeRelationshipReference( outgoing ); } @Override public long incomingReference() { - return getFirstIn(); + long incoming = getFirstIn(); + return incoming == NO_ID ? encodeNoIncomingRels( getType() ) : encodeRelationshipReference( incoming ); } @Override public long loopsReference() { - return getFirstLoop(); + long loops = getFirstLoop(); + return loops == NO_ID ? encodeNoLoopRels( getType() ) : encodeRelationshipReference( loops ); } @Override @@ -305,11 +313,48 @@ public String toString() } } + /** + * Implementation detail which provides the raw non-encoded outgoing relationship id + */ + long outgoingRawId() + { + return getFirstOut(); + } + + /** + * Implementation detail which provides the raw non-encoded incoming relationship id + */ + long incomingRawId() + { + return getFirstIn(); + } + + /** + * Implementation detail which provides the raw non-encoded loops relationship id + */ + long loopsRawId() + { + return getFirstLoop(); + } + private boolean isBuffered() { return bufferedGroup != null; } + private long encodeRelationshipReference( long relationshipId ) + { + assert relationshipId != NO_ID; + if ( isBuffered() ) + { + return References.Relationship.encodeForFiltering( relationshipId ); + } + else + { + return References.Relationship.encodeForTxStateFiltering( relationshipId ); + } + } + static class BufferedGroup { final int label; @@ -362,17 +407,17 @@ void loop( RelationshipRecord edge ) long outgoing() { - return encodeForFiltering( firstOut ); + return firstOut; } long incoming() { - return encodeForFiltering( firstIn ); + return firstIn; } long loops() { - return encodeForFiltering( firstLoop ); + return firstLoop; } } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipTraversalCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipTraversalCursor.java index 6af68e578558..a23cab431128 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipTraversalCursor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/RelationshipTraversalCursor.java @@ -25,7 +25,7 @@ import org.neo4j.io.pagecache.PageCursor; import org.neo4j.kernel.api.txstate.TransactionState; import org.neo4j.kernel.impl.store.record.RelationshipRecord; -import org.neo4j.storageengine.api.Direction; +import org.neo4j.storageengine.api.txstate.NodeState; class RelationshipTraversalCursor extends RelationshipCursor implements org.neo4j.internal.kernel.api.RelationshipTraversalCursor @@ -40,7 +40,7 @@ private enum GroupState private enum FilterState { - NOT_INITIALIZED + NOT_INITIALIZED( RelationshipDirection.ERROR ) { @Override boolean check( long source, long target, long origin ) @@ -48,7 +48,7 @@ boolean check( long source, long target, long origin ) throw new IllegalStateException( "Cannot call check on uninitialized filter" ); } }, - INCOMING + INCOMING( RelationshipDirection.INCOMING ) { @Override boolean check( long source, long target, long origin ) @@ -56,7 +56,7 @@ boolean check( long source, long target, long origin ) return origin == target; } }, - OUTGOING + OUTGOING( RelationshipDirection.OUTGOING ) { @Override boolean check( long source, long target, long origin ) @@ -64,7 +64,7 @@ boolean check( long source, long target, long origin ) return origin == source; } }, - LOOP + LOOP( RelationshipDirection.LOOP ) { @Override boolean check( long source, long target, long origin ) @@ -72,7 +72,7 @@ boolean check( long source, long target, long origin ) return source == target; } }, - NONE + NONE( RelationshipDirection.ERROR ) { @Override boolean check( long source, long target, long origin ) @@ -82,6 +82,30 @@ boolean check( long source, long target, long origin ) }; abstract boolean check( long source, long target, long origin ); + + private final RelationshipDirection direction; + + FilterState( RelationshipDirection direction ) + { + this.direction = direction; + } + + private static FilterState fromRelationshipDirection( RelationshipDirection direction ) + { + switch ( direction ) + { + case OUTGOING: + return FilterState.OUTGOING; + case INCOMING: + return FilterState.INCOMING; + case LOOP: + return FilterState.LOOP; + case ERROR: + throw new IllegalArgumentException( "There has been a RelationshipDirection.ERROR" ); + default: + throw new IllegalStateException( "Still poking my eye, dear checkstyle..." ); + } + } } private long originNodeReference; @@ -91,6 +115,7 @@ boolean check( long source, long target, long origin ) private final RelationshipGroupCursor group; private GroupState groupState; private FilterState filterState; + private boolean filterStore; private int filterType = NO_ID; private PrimitiveLongIterator addedRelationships; @@ -104,19 +129,19 @@ boolean check( long source, long target, long origin ) * Cursor being called as a group, use the buffered records in Record * instead. These are guaranteed to all have the same type and direction. */ - void buffered( long nodeReference, Record record, Read read ) + void buffered( long nodeReference, Record record, RelationshipDirection direction, int type, Read read ) { this.originNodeReference = nodeReference; this.buffer = Record.initialize( record ); this.groupState = GroupState.NONE; - this.filterState = FilterState.NONE; - this.filterType = NO_ID; + this.filterState = FilterState.fromRelationshipDirection( direction ); + this.filterType = type; init( read ); this.addedRelationships = PrimitiveLongCollections.emptyIterator(); } /* - * Normal traversal. Mixed types and directions. + * Normal traversal. Traversal returns mixed types and directions. */ void chain( long nodeReference, long reference, Read read ) { @@ -125,45 +150,63 @@ void chain( long nodeReference, long reference, Read read ) pageCursor = read.relationshipPage( reference ); } setId( NO_ID ); - groupState = GroupState.NONE; - filterState = FilterState.NONE; - filterType = NO_ID; - originNodeReference = nodeReference; - next = reference; + this.groupState = GroupState.NONE; + this.filterState = FilterState.NONE; + this.filterType = NO_ID; + this.originNodeReference = nodeReference; + this.next = reference; init( read ); this.addedRelationships = PrimitiveLongCollections.emptyIterator(); } /* - * Reference to a group record. Mixed types and directions, but grouped. + * Reference to a group record. Traversal returns mixed types and directions. */ void groups( long nodeReference, long groupReference, Read read ) { setId( NO_ID ); - next = NO_ID; - groupState = GroupState.INCOMING; - filterState = FilterState.NONE; - filterType = NO_ID; - originNodeReference = nodeReference; + this.next = NO_ID; + this.groupState = GroupState.INCOMING; + this.filterState = FilterState.NONE; + this.filterType = NO_ID; + this.originNodeReference = nodeReference; read.relationshipGroups( nodeReference, groupReference, group ); init( read ); this.addedRelationships = PrimitiveLongCollections.emptyIterator(); } /* - * Grouped traversal of non-dense node. Same type and direction as first read relationship. + * Grouped traversal of non-dense node. Same type and direction as first read relationship. Store relationships are + * all assumed to be of wanted relationship type and direction iff filterStore == true. */ - void filtered( long nodeReference, long reference, Read read ) + void filtered( long nodeReference, long reference, Read read, boolean filterStore ) { if ( pageCursor == null ) { pageCursor = read.relationshipPage( reference ); } setId( NO_ID ); - groupState = GroupState.NONE; - filterState = FilterState.NOT_INITIALIZED; - originNodeReference = nodeReference; - next = reference; + this.groupState = GroupState.NONE; + this.filterState = FilterState.NOT_INITIALIZED; + this.filterStore = filterStore; + this.originNodeReference = nodeReference; + this.next = reference; + init( read ); + this.addedRelationships = PrimitiveLongCollections.emptyIterator(); + } + + /* + * Empty chain in store. Return from tx-state with provided relationship type and direction. + */ + void filteredTxState( long nodeReference, Read read, int filterType, RelationshipDirection direction ) + { + setId( NO_ID ); + this.groupState = GroupState.NONE; + this.filterType = filterType; + this.filterState = FilterState.fromRelationshipDirection( direction ); + this.filterStore = false; + this.originNodeReference = nodeReference; + this.next = NO_ID; init( read ); this.addedRelationships = PrimitiveLongCollections.emptyIterator(); } @@ -213,26 +256,46 @@ public long originNodeReference() @Override public boolean next() { - if ( hasBufferedData() ) - { //We have buffered data, iterate the chain of buffered records - return nextBufferedData(); - } + boolean hasChanges; + TransactionState txs; - if ( filteringTraversal() ) + if ( filterState == FilterState.NOT_INITIALIZED ) { - return nextFilteredData(); - } + // Initialize filtering: + // - Read first record + // - Remember type and direction + // - Return first relationship if it's not deleted + // Subsequent relationships need to have same type and direction - // Check tx state - boolean hasChanges = hasChanges(); - TransactionState txs = hasChanges ? read.txState() : null; + read.relationship( this, next, pageCursor ); + setupFilterState(); + hasChanges = hasChanges(); + txs = hasChanges ? read.txState() : null; + + if ( !hasChanges || !txs.relationshipIsDeletedInThisTx( getId() ) ) + { + return true; + } + } + else + { + hasChanges = hasChanges(); + txs = hasChanges ? read.txState() : null; + } + + // tx-state relationships if ( hasChanges && addedRelationships.hasNext() ) { loadFromTxState( addedRelationships.next() ); return true; } + if ( hasBufferedData() ) + { // We have buffered data, iterate the chain of buffered records + return nextBuffered( txs ); + } + do { if ( traversingDenseNode() ) @@ -248,80 +311,52 @@ public boolean next() read.relationship( this, next, pageCursor ); computeNext(); - } while ( hasChanges && txs.relationshipIsDeletedInThisTx( getId() ) ); + + } while ( ( filterStore && !correctTypeAndDirection() ) || + ( hasChanges && txs.relationshipIsDeletedInThisTx( getId() ) ) ); return true; } - private boolean nextFilteredData() + private void setupFilterState() { - if ( next == NO_ID ) + filterType = getType(); + final long source = sourceNodeReference(), target = targetNodeReference(); + if ( source == target ) { - reset(); - return false; + next = getFirstNextRel(); + filterState = FilterState.LOOP; } - if ( filterState == FilterState.NOT_INITIALIZED ) + else if ( source == originNodeReference ) { - //Initialize filtering: - // - Read first record - // - Check type and direction - // - Subsequent records need to have same type and direction - read.relationship( this, next, pageCursor ); - filterType = getType(); - final long source = sourceNodeReference(), target = targetNodeReference(); - if ( source == target ) - { - next = getFirstNextRel(); - filterState = FilterState.LOOP; - } - else if ( source == originNodeReference ) - { - next = getFirstNextRel(); - filterState = FilterState.OUTGOING; - } - else if ( target == originNodeReference ) - { - next = getSecondNextRel(); - filterState = FilterState.INCOMING; - } - return true; + next = getFirstNextRel(); + filterState = FilterState.OUTGOING; } - else + else if ( target == originNodeReference ) { - //Iterate until we stop on a valid record, - //i.e. one with the same type and direction. - while ( true ) - { - read.relationship( this, next, pageCursor ); - computeNext(); - if ( correctTypeAndDirection() ) - { - return true; - } - if ( next == NO_ID ) - { - reset(); - return false; - } - - } + next = getSecondNextRel(); + filterState = FilterState.INCOMING; } } - private boolean nextBufferedData() + private boolean nextBuffered( TransactionState txs ) { - buffer = buffer.next; - if ( !hasBufferedData() ) - { - reset(); - return false; - } - else + do { - // Copy buffer data to self - copyFromBuffer(); - return true; - } + buffer = buffer.next; + if ( !hasBufferedData() ) + { + reset(); + return false; + } + else + { + // Copy buffer data to self + copyFromBuffer(); + } + } while ( txs != null && txs.relationshipIsDeletedInThisTx( getId() ) ); + + return true; } private void traverseDenseNode() @@ -369,7 +404,7 @@ private void traverseDenseNode() assert next == NO_ID; return; // no more groups nor relationships } - next = group.incomingReference(); + next = group.incomingRawId(); if ( pageCursor == null ) { pageCursor = read.relationshipPage( Math.max( next, 0L ) ); @@ -378,12 +413,12 @@ private void traverseDenseNode() break; case OUTGOING: - next = group.outgoingReference(); + next = group.outgoingRawId(); groupState = GroupState.LOOP; break; case LOOP: - next = group.loopsReference(); + next = group.loopsRawId(); groupState = GroupState.INCOMING; break; @@ -430,11 +465,6 @@ private boolean traversingDenseNode() return groupState != GroupState.NONE; } - private boolean filteringTraversal() - { - return filterState != FilterState.NONE; - } - @Override public boolean shouldRetry() { @@ -459,13 +489,22 @@ private void reset() groupState = GroupState.NONE; filterState = FilterState.NONE; filterType = NO_ID; + filterStore = false; buffer = null; } @Override protected void collectAddedTxStateSnapshot() { - addedRelationships = read.txState().getNodeState( originNodeReference ).getAddedRelationships( Direction.BOTH ); + NodeState nodeState = read.txState().getNodeState( originNodeReference ); + addedRelationships = hasTxStateFilter() ? + nodeState.getAddedRawRelationships( filterState.direction, filterType ) : + nodeState.getAddedRawRelationships(); + } + + private boolean hasTxStateFilter() + { + return filterState != FilterState.NONE; } @Override @@ -490,9 +529,9 @@ public String toString() { mode = mode + "bufferedData"; } - else if ( filteringTraversal() ) + else if ( filterStore ) { - mode = mode + "filteringTraversal"; + mode = mode + "filterStore"; } else {