Skip to content

Commit

Permalink
Use kernel API for degree computation in compiled runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
pontusmelke committed Feb 25, 2018
1 parent 65b2bce commit c8f6130
Show file tree
Hide file tree
Showing 9 changed files with 356 additions and 111 deletions.
Expand Up @@ -23,25 +23,68 @@
import org.neo4j.internal.kernel.api.CursorFactory;
import org.neo4j.internal.kernel.api.NodeCursor;
import org.neo4j.internal.kernel.api.Read;
import org.neo4j.internal.kernel.api.RelationshipGroupCursor;
import org.neo4j.internal.kernel.api.exceptions.EntityNotFoundException;
import org.neo4j.internal.kernel.api.helpers.RelationshipSelectionCursor;
import org.neo4j.kernel.api.ReadOperations;

import static org.neo4j.internal.kernel.api.helpers.Nodes.countAll;
import static org.neo4j.internal.kernel.api.helpers.Nodes.countIncoming;
import static org.neo4j.internal.kernel.api.helpers.Nodes.countOutgoing;

public abstract class CompiledExpandUtils
{
public static RelationshipSelectionCursor connectingRelationships( ReadOperations readOperations,
Read read, CursorFactory cursors, NodeCursor nodeCursor,
public static RelationshipSelectionCursor connectingRelationships( Read read, CursorFactory cursors,
NodeCursor nodeCursor,
long fromNode, Direction direction, long toNode )
{

int fromDegree = nodeGetDegree( read, fromNode, nodeCursor, direction, cursors );
if ( fromDegree == 0 )
{
return RelationshipSelectionCursor.EMPTY;
}

int toDegree = nodeGetDegree( read, toNode, nodeCursor, direction.reverse(), cursors );
if ( toDegree == 0 )
{
return RelationshipSelectionCursor.EMPTY;
}

long startNode;
long endNode;
Direction relDirection;
if ( fromDegree < toDegree )
{
startNode = fromNode;
endNode = toNode;
relDirection = direction;
}
else
{
startNode = toNode;
endNode = fromNode;
relDirection = direction.reverse();
}

RelationshipSelectionCursor selectionCursor = CompiledCursorUtils
.nodeGetRelationships( read, cursors, nodeCursor, startNode, relDirection );

return connectingRelationshipsIterator( selectionCursor, endNode );

}

public static RelationshipSelectionCursor connectingRelationships( Read read, CursorFactory cursors,
NodeCursor nodeCursor, long fromNode, Direction direction, long toNode, int[] relTypes )
{
try
{
int fromDegree = readOperations.nodeGetDegree( fromNode, direction );
int fromDegree = calculateTotalDegree( read, fromNode, nodeCursor, direction, relTypes, cursors );
if ( fromDegree == 0 )
{
return RelationshipSelectionCursor.EMPTY;
}

int toDegree = readOperations.nodeGetDegree( toNode, direction.reverse() );
int toDegree = calculateTotalDegree( read, toNode, nodeCursor, direction.reverse(), relTypes, cursors );
if ( toDegree == 0 )
{
return RelationshipSelectionCursor.EMPTY;
Expand All @@ -64,7 +107,7 @@ public static RelationshipSelectionCursor connectingRelationships( ReadOperatio
}

RelationshipSelectionCursor selectionCursor = CompiledCursorUtils
.nodeGetRelationships( read, cursors, nodeCursor, startNode, relDirection );
.nodeGetRelationships( read, cursors, nodeCursor, startNode, relDirection, relTypes );

return connectingRelationshipsIterator( selectionCursor, endNode );
}
Expand All @@ -74,63 +117,67 @@ public static RelationshipSelectionCursor connectingRelationships( ReadOperatio
}
}

public static RelationshipSelectionCursor connectingRelationships( ReadOperations readOperations, Read read, CursorFactory cursors, NodeCursor nodeCursor,
long fromNode, Direction direction, long toNode, int[] relTypes )
static int nodeGetDegree( Read read, long node, NodeCursor nodeCursor, Direction direction, CursorFactory cursors )
{
try
try ( RelationshipGroupCursor group = cursors.allocateRelationshipGroupCursor() )
{
int fromDegree = calculateTotalDegree( readOperations, fromNode, direction, relTypes );
if ( fromDegree == 0 )
read.singleNode( node, nodeCursor );
if ( !nodeCursor.next() )
{
return RelationshipSelectionCursor.EMPTY;
return 0;
}

int toDegree = calculateTotalDegree( readOperations, toNode, direction.reverse(), relTypes );
if ( toDegree == 0 )
switch ( direction )
{
return RelationshipSelectionCursor.EMPTY;
case OUTGOING:
return countOutgoing( nodeCursor, group );
case INCOMING:
return countIncoming( nodeCursor, group );
case BOTH:
return countAll( nodeCursor, group );
default:
throw new IllegalStateException( "Unknown direction " + direction );
}
}
}

long startNode;
long endNode;
Direction relDirection;
if ( fromDegree < toDegree )
static int nodeGetDegree( Read read, long node, NodeCursor nodeCursor, Direction direction, int type,
CursorFactory cursors )
{
try ( RelationshipGroupCursor group = cursors.allocateRelationshipGroupCursor() )
{
read.singleNode( node, nodeCursor );
if ( !nodeCursor.next() )
{
startNode = fromNode;
endNode = toNode;
relDirection = direction;
return 0;
}
else
switch ( direction )
{
startNode = toNode;
endNode = fromNode;
relDirection = direction.reverse();
case OUTGOING:
return countOutgoing( nodeCursor, group, type );
case INCOMING:
return countIncoming( nodeCursor, group, type );
case BOTH:
return countAll( nodeCursor, group, type );
default:
throw new IllegalStateException( "Unknown direction " + direction );
}

RelationshipSelectionCursor selectionCursor = CompiledCursorUtils
.nodeGetRelationships( read, cursors, nodeCursor, startNode, relDirection, relTypes );

return connectingRelationshipsIterator( selectionCursor, endNode );
}
catch ( EntityNotFoundException ignore )
{
return RelationshipSelectionCursor.EMPTY;
}
}

private static int calculateTotalDegree( ReadOperations readOperations, long fromNode, Direction direction,
int[] relTypes ) throws EntityNotFoundException
private static int calculateTotalDegree( Read read, long fromNode, NodeCursor nodeCursor, Direction direction,
int[] relTypes, CursorFactory cursors ) throws EntityNotFoundException
{
int degree = 0;
for ( int relType : relTypes )
{
degree += readOperations.nodeGetDegree( fromNode, direction, relType );
degree += nodeGetDegree( read, fromNode, nodeCursor, direction, relType, cursors );
}

return degree;
}

private static RelationshipSelectionCursor connectingRelationshipsIterator( final RelationshipSelectionCursor allRelationships, final long toNode )
private static RelationshipSelectionCursor connectingRelationshipsIterator(
final RelationshipSelectionCursor allRelationships, final long toNode )
{
return new RelationshipSelectionCursor()
{
Expand Down
Expand Up @@ -19,90 +19,116 @@
*/
package org.neo4j.cypher.internal.codegen;

import org.junit.Rule;
import org.junit.Test;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.DependencyResolver;
import org.neo4j.internal.kernel.api.CursorFactory;
import org.neo4j.internal.kernel.api.Kernel;
import org.neo4j.internal.kernel.api.NodeCursor;
import org.neo4j.internal.kernel.api.Read;
import org.neo4j.internal.kernel.api.exceptions.EntityNotFoundException;
import org.neo4j.kernel.api.ReadOperations;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.neo4j.cypher.internal.codegen.CompiledExpandUtils.connectingRelationships;
import org.neo4j.internal.kernel.api.Session;
import org.neo4j.internal.kernel.api.TokenWrite;
import org.neo4j.internal.kernel.api.Transaction;
import org.neo4j.internal.kernel.api.Write;
import org.neo4j.internal.kernel.api.security.LoginContext;
import org.neo4j.test.rule.EmbeddedDatabaseRule;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.neo4j.cypher.internal.codegen.CompiledExpandUtils.nodeGetDegree;
import static org.neo4j.graphdb.Direction.BOTH;
import static org.neo4j.graphdb.Direction.INCOMING;
import static org.neo4j.graphdb.Direction.OUTGOING;

public class CompiledExpandUtilsTest
{
@Test
public void shouldUseGivenOrderIfItHasLowerDegree() throws EntityNotFoundException
{
// GIVEN
ReadOperations readOperations = mock( ReadOperations.class );
Read read = mock( Read.class );
NodeCursor nodeCursor = mock( NodeCursor.class );
when( readOperations.nodeGetDegree( 1L, Direction.OUTGOING ) ).thenReturn( 1 );
when( readOperations.nodeGetDegree( 2L, Direction.INCOMING ) ).thenReturn( 3 );

// WHEN
connectingRelationships( readOperations, read, mock( CursorFactory.class ), nodeCursor, 1L, Direction.OUTGOING, 2L );
@Rule
public EmbeddedDatabaseRule db = new EmbeddedDatabaseRule();

// THEN
verify( read, times( 1 ) ).singleNode( 1L, nodeCursor);
}

@Test
public void shouldSwitchOrderIfItHasLowerDegree() throws EntityNotFoundException
private Session session()
{
// GIVEN
ReadOperations readOperations = mock( ReadOperations.class );
Read read = mock( Read.class );
NodeCursor nodeCursor = mock( NodeCursor.class );

when( readOperations.nodeGetDegree( 1L, Direction.OUTGOING ) ).thenReturn( 3 );
when( readOperations.nodeGetDegree( 2L, Direction.INCOMING ) ).thenReturn( 1 );

// WHEN
connectingRelationships( readOperations, read, mock( CursorFactory.class ), nodeCursor, 1L, Direction.OUTGOING, 2L );

// THEN
verify( read, times( 1 ) ).singleNode( 2L, nodeCursor );
DependencyResolver resolver = this.db.getDependencyResolver();
return resolver.resolveDependency( Kernel.class ).beginSession( LoginContext.AUTH_DISABLED );
}

@Test
public void shouldUseGivenOrderIfItHasLowerDegreeWithTypes() throws EntityNotFoundException
public void shouldComputeDegreeWithoutType() throws Exception
{
// GIVEN
ReadOperations readOperations = mock( ReadOperations.class );
Read read = mock( Read.class );
NodeCursor nodeCursor = mock( NodeCursor.class );
when( readOperations.nodeGetDegree( 1L, Direction.OUTGOING, 1 ) ).thenReturn( 1 );
when( readOperations.nodeGetDegree( 2L, Direction.INCOMING, 1 ) ).thenReturn( 3 );

// WHEN
connectingRelationships( readOperations, read, mock( CursorFactory.class ), nodeCursor, 1L, Direction.OUTGOING, 2L, new int[]{1} );

// THEN
verify( read, times( 1 ) ).singleNode( 1L, nodeCursor);
Session session = session();
long node;
try ( Transaction tx = session.beginTransaction() )
{
Write write = tx.dataWrite();
node = write.nodeCreate();
write.relationshipCreate( node,
tx.tokenWrite().relationshipTypeGetOrCreateForName( "R1" ),
write.nodeCreate() );
write.relationshipCreate( node,
tx.tokenWrite().relationshipTypeGetOrCreateForName( "R2" ),
write.nodeCreate() );
write.relationshipCreate( write.nodeCreate(),
tx.tokenWrite().relationshipTypeGetOrCreateForName( "R3" ),
node );
write.relationshipCreate( node,
tx.tokenWrite().relationshipTypeGetOrCreateForName( "R4" ), node );

tx.success();
}

try ( Transaction tx = session.beginTransaction() )
{
Read read = tx.dataRead();
CursorFactory cursors = tx.cursors();
NodeCursor nodes = cursors.allocateNodeCursor();
assertThat( nodeGetDegree( read, node, nodes, OUTGOING, cursors ), equalTo( 3 ) );
assertThat( nodeGetDegree( read, node, nodes, INCOMING, cursors ), equalTo( 2 ) );
assertThat( nodeGetDegree( read, node, nodes, BOTH, cursors ), equalTo( 4 ) );
}
}

@Test
public void shouldSwitchOrderIfItHasLowerDegreeWithTypes() throws EntityNotFoundException
public void shouldComputeDegreeWithType() throws Exception
{
// GIVEN
ReadOperations readOperations = mock( ReadOperations.class );
Read read = mock( Read.class );
NodeCursor nodeCursor = mock( NodeCursor.class );
when( readOperations.nodeGetDegree( 1L, Direction.OUTGOING, 1 ) ).thenReturn( 3 );
when( readOperations.nodeGetDegree( 2L, Direction.INCOMING, 1 ) ).thenReturn( 1 );

// WHEN
connectingRelationships( readOperations, read, mock( CursorFactory.class ), nodeCursor , 1L, Direction.OUTGOING, 2L, new int[]{1} );

// THEN
verify( read, times( 1 ) ).singleNode( 2L, nodeCursor );
Session session = session();
long node;
int in, out, loop;
try ( Transaction tx = session.beginTransaction() )
{
Write write = tx.dataWrite();
node = write.nodeCreate();
TokenWrite tokenWrite = tx.tokenWrite();
out = tokenWrite.relationshipTypeGetOrCreateForName( "OUT" );
in = tokenWrite.relationshipTypeGetOrCreateForName( "IN" );
loop = tokenWrite.relationshipTypeGetOrCreateForName( "LOOP" );
write.relationshipCreate( node,
out,
write.nodeCreate() );
write.relationshipCreate( node, out, write.nodeCreate() );
write.relationshipCreate( write.nodeCreate(), in, node );
write.relationshipCreate( node, loop, node );

tx.success();
}

try ( Transaction tx = session.beginTransaction() )
{
Read read = tx.dataRead();
CursorFactory cursors = tx.cursors();
NodeCursor nodes = cursors.allocateNodeCursor();
assertThat( nodeGetDegree( read, node, nodes, OUTGOING, out, cursors ), equalTo( 2 ) );
assertThat( nodeGetDegree( read, node, nodes, OUTGOING, in, cursors ), equalTo( 0 ) );
assertThat( nodeGetDegree( read, node, nodes, OUTGOING, loop, cursors ), equalTo( 1 ) );

assertThat( nodeGetDegree( read, node, nodes, INCOMING, out, cursors ), equalTo( 0 ) );
assertThat( nodeGetDegree( read, node, nodes, INCOMING, in, cursors ), equalTo( 1 ) );
assertThat( nodeGetDegree( read, node, nodes, INCOMING, loop, cursors ), equalTo( 1 ) );

assertThat( nodeGetDegree( read, node, nodes, BOTH, out, cursors ), equalTo( 2 ) );
assertThat( nodeGetDegree( read, node, nodes, BOTH, in, cursors ), equalTo( 1 ) );
assertThat( nodeGetDegree( read, node, nodes, BOTH, loop, cursors ), equalTo( 1 ) );
}
}

}
Expand Up @@ -78,4 +78,6 @@ default int totalCount()
long incomingReference();

long loopsReference();

int type();
}

0 comments on commit c8f6130

Please sign in to comment.