* This function is its own inverse function.
*
- * @param reference
- * the reference to invert.
+ * @param reference the reference to invert.
* @return the inverted reference.
*/
static long invertReference( long reference )
@@ -340,7 +346,24 @@ static long invertReference( long reference )
return -2 - reference;
}
- private static ByteBuffer readDynamic( AbstractDynamicStore store, long reference, ByteBuffer buffer, PageCursor page )
+ static long addFilteringFlag( long reference )
+ {
+ // set a high order bit as flag noting that "filtering is required"
+ return reference | FILTER_MASK;
+ }
+
+ static long removeFilteringFlag( long reference )
+ {
+ return reference & ~FILTER_MASK;
+ }
+
+ static boolean needsFiltering( long reference )
+ {
+ return (reference & FILTER_MASK) != 0L;
+ }
+
+ private static ByteBuffer readDynamic( AbstractDynamicStore store, long reference, ByteBuffer buffer,
+ PageCursor page )
{
if ( buffer == null )
{
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 c7f6f21fb3883..826a50b92452d 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,7 @@
import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord;
import org.neo4j.kernel.impl.store.record.RelationshipRecord;
-import static org.neo4j.kernel.impl.newapi.Read.invertReference;
+import static org.neo4j.kernel.impl.newapi.Read.addFilteringFlag;
class RelationshipGroupCursor extends RelationshipGroupRecord
implements org.neo4j.internal.kernel.api.RelationshipGroupCursor
@@ -322,24 +322,18 @@ void loop( RelationshipRecord edge )
long outgoing()
{
- return flaggedAsRequiringFiltering( firstOut );
+ return addFilteringFlag( firstOut );
}
long incoming()
{
- return flaggedAsRequiringFiltering( firstIn );
+ return addFilteringFlag( firstIn );
}
long loops()
{
- return flaggedAsRequiringFiltering( firstLoop );
+ return addFilteringFlag( firstLoop );
}
- private long flaggedAsRequiringFiltering( long reference )
- {
- // set a high order bit as flag noting that "filtering is required"
- // invert the reference to note that "this reference is special"
- return invertReference( reference | 0x2000_0000_0000_0000L );
- }
}
}
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 9df7f706495ae..6dd63fa06b28b 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
@@ -23,15 +23,63 @@
import org.neo4j.io.pagecache.PageCursor;
import org.neo4j.kernel.impl.store.record.RelationshipRecord;
-import static org.neo4j.kernel.impl.newapi.RelationshipTraversalCursor.GroupState.INCOMING;
-import static org.neo4j.kernel.impl.newapi.RelationshipTraversalCursor.GroupState.LOOP;
-import static org.neo4j.kernel.impl.newapi.RelationshipTraversalCursor.GroupState.NONE;
-import static org.neo4j.kernel.impl.newapi.RelationshipTraversalCursor.GroupState.OUTGOING;
class RelationshipTraversalCursor extends RelationshipCursor
implements org.neo4j.internal.kernel.api.RelationshipTraversalCursor
{
- enum GroupState { INCOMING, OUTGOING, LOOP, NONE };
+ private enum GroupState
+ {
+ INCOMING,
+ OUTGOING,
+ LOOP,
+ NONE
+ }
+
+ private enum FilterState
+ {
+ NOT_INITIALIZED
+ {
+ @Override
+ boolean check( long source, long target, long origin )
+ {
+ throw new IllegalStateException( "Cannot call check on uninitialized filter" );
+ }
+ },
+ INCOMING
+ {
+ @Override
+ boolean check( long source, long target, long origin )
+ {
+ return origin == target;
+ }
+ },
+ OUTGOING
+ {
+ @Override
+ boolean check( long source, long target, long origin )
+ {
+ return origin == source;
+ }
+ },
+ LOOP
+ {
+ @Override
+ boolean check( long source, long target, long origin )
+ {
+ return source == target;
+ }
+ },
+ NONE
+ {
+ @Override
+ boolean check( long source, long target, long origin )
+ {
+ return true;
+ }
+ };
+
+ abstract boolean check( long source, long target, long origin );
+ }
private long originNodeReference;
private long next;
@@ -39,7 +87,8 @@ enum GroupState { INCOMING, OUTGOING, LOOP, NONE };
private PageCursor pageCursor;
private final RelationshipGroupCursor group;
private GroupState groupState;
-
+ private FilterState filterState;
+ private int filterType = NO_ID;
RelationshipTraversalCursor( Read read )
{
@@ -55,6 +104,9 @@ void buffered( long nodeReference, Record record )
{
this.originNodeReference = nodeReference;
this.buffer = Record.initialize( record );
+ this.groupState = GroupState.NONE;
+ this.filterState = FilterState.NONE;
+ this.filterType = NO_ID;
}
/*
@@ -67,7 +119,9 @@ void chain( long nodeReference, long reference )
pageCursor = read.relationshipPage( reference );
}
setId( NO_ID );
- groupState = NONE;
+ groupState = GroupState.NONE;
+ filterState = FilterState.NONE;
+ filterType = NO_ID;
originNodeReference = nodeReference;
next = reference;
}
@@ -79,16 +133,24 @@ void groups( long nodeReference, long groupReference )
{
setId( NO_ID );
next = NO_ID;
- groupState = INCOMING;
+ groupState = GroupState.INCOMING;
+ filterState = FilterState.NONE;
+ filterType = NO_ID;
originNodeReference = nodeReference;
read.relationshipGroups( nodeReference, groupReference, group );
}
void filtered( long nodeReference, long reference )
{
- // TODO: read the first record and use the type of it for filtering the chain
- // - only include records with that type
- throw new UnsupportedOperationException( "not implemented" );
+ if ( pageCursor == null )
+ {
+ pageCursor = read.relationshipPage( reference );
+ }
+ setId( NO_ID );
+ groupState = GroupState.NONE;
+ filterState = FilterState.NOT_INITIALIZED;
+ originNodeReference = nodeReference;
+ next = reference;
}
@Override
@@ -136,33 +198,35 @@ public long originNodeReference()
@Override
public boolean next()
{
- /*
- Node(dense=true)
- |
- v
-
- Group(:HOLDS) -incoming-> Rel(id=2) -> Rel(id=3)
- -outgoing-> Rel(id=5) -> Rel(id=10) -> Rel(id=3)
- -loop-> Rel(id=9)
- |
- v
-
- Group(:USES) -incoming-> Rel(id=14)
- -outgoing-> Rel(id=55) -> Rel(id=51) -> ...
- -loop-> Rel(id=21) -> Rel(id=11)
-
- |
- v
- ...
-
- */
if ( traversingDenseNode() )
{
+
while ( next == NO_ID )
{
- /*
- Defines a small state machine, we start in state INCOMING.
+ /*
+ Dense nodes looks something like:
+
+ Node(dense=true)
+
+ |
+ v
+
+ Group(:HOLDS) -incoming-> Rel(id=2) -> Rel(id=3)
+ -outgoing-> Rel(id=5) -> Rel(id=10) -> Rel(id=3)
+ -loop-> Rel(id=9)
+ |
+ v
+
+ Group(:USES) -incoming-> Rel(id=14)
+ -outgoing-> Rel(id=55) -> Rel(id=51) -> ...
+ -loop-> Rel(id=21) -> Rel(id=11)
+
+ |
+ v
+ ...
+
+ We iterate over dense nodes using a small state machine staring in state INCOMING.
1) fetch next group, if no more group stop.
2) set next to group.incomingReference, switch state to OUTGOING
3) Iterate relationship chain until we reach the end
@@ -186,17 +250,17 @@ public boolean next()
{
pageCursor = read.relationshipPage( Math.max( next, 0L ) );
}
- groupState = OUTGOING;
+ groupState = GroupState.OUTGOING;
break;
case OUTGOING:
next = group.outgoingReference();
- groupState = LOOP;
+ groupState = GroupState.LOOP;
break;
case LOOP:
next = group.loopsReference();
- groupState = INCOMING;
+ groupState = GroupState.INCOMING;
break;
default:
@@ -216,11 +280,7 @@ public boolean next()
else
{
// Copy buffer data to self
- this.setId( buffer.id );
- this.setType( buffer.type );
- this.setNextProp( buffer.nextProp );
- this.setFirstNode( buffer.firstNode );
- this.setSecondNode( buffer.secondNode );
+ copyFromBuffer();
return true;
}
}
@@ -231,7 +291,66 @@ public boolean next()
reset();
return false;
}
+
+ if ( filteringTraversal() )
+ {
+ if ( filterState == FilterState.NOT_INITIALIZED )
+ {
+ //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;
+ }
+ else
+ {
+ //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 ( predicate() )
+ {
+ return true;
+ }
+ if ( next == NO_ID )
+ {
+ reset();
+ return false;
+ }
+
+ }
+ }
+ }
+
+ //Not a group, nothing buffered, no filtering.
+ //Just a plain old traversal.
read.relationship( this, next, pageCursor );
+ computeNext();
+ return true;
+ }
+
+ private void computeNext()
+ {
final long source = sourceNodeReference(), target = targetNodeReference();
if ( source == originNodeReference )
{
@@ -245,12 +364,31 @@ else if ( target == originNodeReference )
{
throw new IllegalStateException( "NOT PART OF CHAIN" );
}
- return true;
+ }
+
+ private boolean predicate()
+ {
+ return filterType == getType() &&
+ filterState.check( sourceNodeReference(), targetNodeReference(), originNodeReference );
+ }
+
+ private void copyFromBuffer()
+ {
+ this.setId( buffer.id );
+ this.setType( buffer.type );
+ this.setNextProp( buffer.nextProp );
+ this.setFirstNode( buffer.firstNode );
+ this.setSecondNode( buffer.secondNode );
}
private boolean traversingDenseNode()
{
- return groupState != NONE;
+ return groupState != GroupState.NONE;
+ }
+
+ private boolean filteringTraversal()
+ {
+ return filterState != FilterState.NONE;
}
@Override
@@ -273,7 +411,9 @@ public void close()
private void reset()
{
setId( next = NO_ID );
- groupState = NONE;
+ groupState = GroupState.NONE;
+ filterState = FilterState.NONE;
+ filterType = NO_ID;
buffer = null;
}
diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/NodeRecord.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/NodeRecord.java
index 093c05f1a772d..308ea5685877c 100644
--- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/NodeRecord.java
+++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/NodeRecord.java
@@ -134,7 +134,6 @@ public Iterable