Skip to content

Commit

Permalink
Use Kernel API for index seek in compiled runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
pontusmelke committed Feb 1, 2018
1 parent 206d332 commit f17e740
Show file tree
Hide file tree
Showing 13 changed files with 181 additions and 64 deletions.
Expand Up @@ -19,12 +19,13 @@
*/
package org.neo4j.cypher.internal.codegen;

import org.neo4j.collection.primitive.PrimitiveLongCollections;
import org.neo4j.collection.primitive.PrimitiveLongIterator;
import org.neo4j.kernel.api.ReadOperations;
import org.neo4j.kernel.api.exceptions.index.IndexNotApplicableKernelException;
import org.neo4j.kernel.api.exceptions.index.IndexNotFoundKernelException;
import org.neo4j.kernel.api.schema.index.IndexDescriptor;
import org.neo4j.internal.kernel.api.CapableIndexReference;
import org.neo4j.internal.kernel.api.CursorFactory;
import org.neo4j.internal.kernel.api.IndexOrder;
import org.neo4j.internal.kernel.api.IndexQuery;
import org.neo4j.internal.kernel.api.NodeValueIndexCursor;
import org.neo4j.internal.kernel.api.Read;
import org.neo4j.internal.kernel.api.exceptions.KernelException;

import static org.neo4j.cypher.internal.codegen.CompiledConversionUtils.makeValueNeoSafe;
import static org.neo4j.internal.kernel.api.IndexQuery.exact;
Expand All @@ -45,23 +46,26 @@ private CompiledIndexUtils()
/**
* Performs an index seek.
*
* @param readOperations The ReadOperation instance to use for seeking
* @param descriptor The descriptor of the index
* @param propertyId The property to seek for
* @param read The Read instance to use for seeking
* @param cursors Used for cursor allocation
* @param index A reference to an index
* @param value The value to seek for
* @return An iterator containing data found in index.
* @return A cursor positioned at the data found in index.
*/
public static PrimitiveLongIterator indexSeek( ReadOperations readOperations, IndexDescriptor descriptor,
int propertyId, Object value )
throws IndexNotApplicableKernelException, IndexNotFoundKernelException
public static NodeValueIndexCursor indexSeek( Read read, CursorFactory cursors, CapableIndexReference index, Object value )
throws KernelException
{
assert index.properties().length == 1;
if ( value == null )
{
return PrimitiveLongCollections.emptyIterator();
return NodeValueIndexCursor.EMPTY;
}
else
{
return readOperations.indexQuery( descriptor, exact( propertyId, makeValueNeoSafe( value ) ) );
NodeValueIndexCursor cursor = cursors.allocateNodeValueIndexCursor();
IndexQuery.ExactPredicate query = exact( index.properties()[0], makeValueNeoSafe( value ) );
read.nodeIndexSeek( index, cursor, IndexOrder.NONE, query );
return cursor;
}
}
}
Expand Up @@ -21,53 +21,53 @@

import org.junit.Test;

import org.neo4j.collection.primitive.PrimitiveLongIterator;
import org.neo4j.kernel.api.ReadOperations;
import org.neo4j.kernel.api.exceptions.EntityNotFoundException;
import org.neo4j.kernel.api.exceptions.index.IndexNotApplicableKernelException;
import org.neo4j.kernel.api.exceptions.index.IndexNotFoundKernelException;
import org.neo4j.kernel.api.schema.index.IndexDescriptor;
import org.neo4j.kernel.api.schema.index.IndexDescriptorFactory;
import org.neo4j.internal.kernel.api.CapableIndexReference;
import org.neo4j.internal.kernel.api.CursorFactory;
import org.neo4j.internal.kernel.api.NodeValueIndexCursor;
import org.neo4j.internal.kernel.api.Read;
import org.neo4j.internal.kernel.api.exceptions.KernelException;

import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.neo4j.kernel.api.index.IndexQueryHelper.exact;
import static org.mockito.Mockito.when;

public class CompiledIndexUtilsTest
{

@Test
public void shouldCallIndexSeek()
throws EntityNotFoundException, IndexNotApplicableKernelException, IndexNotFoundKernelException
public void shouldCallIndexSeek() throws KernelException
{

// GIVEN
ReadOperations read = mock( ReadOperations.class );
Read read = mock( Read.class );
CapableIndexReference index = mock( CapableIndexReference.class );
when( index.properties() ).thenReturn( new int[]{42} );

// WHEN
IndexDescriptor descriptor = IndexDescriptorFactory.forLabel( 12, 42 );
CompiledIndexUtils.indexSeek( read, descriptor, 42, "hello" );
CompiledIndexUtils.indexSeek( read, mock( CursorFactory.class ), index, "hello" );

// THEN
verify( read, times( 1 ) ).indexQuery( descriptor, exact( 42, "hello" ) );
verify( read, times( 1 ) ).nodeIndexSeek( any(), any(), any(), any() );
}

@Test
public void shouldHandleNullInIndexSeek()
throws EntityNotFoundException, IndexNotApplicableKernelException, IndexNotFoundKernelException
public void shouldHandleNullInIndexSeek() throws KernelException
{
// GIVEN
ReadOperations read = mock( ReadOperations.class );
Read read = mock( Read.class );
CapableIndexReference index = mock( CapableIndexReference.class );
when( index.properties() ).thenReturn( new int[]{42} );

// WHEN
IndexDescriptor descriptor = IndexDescriptorFactory.forLabel( 12, 42 );
PrimitiveLongIterator iterator =
CompiledIndexUtils.indexSeek( read, descriptor, 42, null );
NodeValueIndexCursor cursor = CompiledIndexUtils.indexSeek( mock( Read.class ), mock( CursorFactory.class ),
index, null );

// THEN
verify( read, never() ).indexQuery( any( ), any( ) );
assertFalse( iterator.hasNext() );
verify( read, never() ).nodeIndexSeek( any(), any(), any() );
assertFalse( cursor.next() );
}
}
Expand Up @@ -300,5 +300,7 @@ class DelegatingQueryTransactionalContext(val inner: QueryTransactionalContext)

override def tokenRead: TokenRead = inner.tokenRead

override def schemaRead: SchemaRead = inner.schemaRead

override def dataWrite: Write = inner.dataWrite
}
Expand Up @@ -22,8 +22,8 @@ package org.neo4j.cypher.internal.runtime.interpreted
import org.neo4j.cypher.internal.planner.v3_4.spi.KernelStatisticProvider
import org.neo4j.cypher.internal.runtime.QueryTransactionalContext
import org.neo4j.graphdb.{Lock, PropertyContainer}
import org.neo4j.internal.kernel.api._
import org.neo4j.internal.kernel.api.security.SecurityContext
import org.neo4j.internal.kernel.api.{CursorFactory, Read, TokenRead, Write}
import org.neo4j.kernel.GraphDatabaseQueryService
import org.neo4j.kernel.api.KernelTransaction.Revertable
import org.neo4j.kernel.api.dbms.DbmsOperations
Expand Down Expand Up @@ -59,6 +59,8 @@ case class TransactionalContextWrapper(tc: TransactionalContext) extends QueryTr

override def tokenRead: TokenRead = tc.kernelTransaction().tokenRead()

override def schemaRead: SchemaRead = tc.kernelTransaction().schemaRead()

override def dataWrite: Write = tc.kernelTransaction().dataWrite()

override def readOperations: ReadOperations = tc.readOperations()
Expand Down
Expand Up @@ -246,6 +246,8 @@ trait QueryTransactionalContext extends CloseableResource {

def tokenRead: TokenRead

def schemaRead: SchemaRead

def dataWrite: Write

def readOperations: ReadOperations
Expand Down
Expand Up @@ -21,6 +21,8 @@

import org.neo4j.values.storable.Value;

import static org.neo4j.values.storable.Values.NO_VALUE;

/**
* Cursor for scanning the property values of nodes in a schema index.
* <p>
Expand Down Expand Up @@ -65,4 +67,70 @@ public interface NodeValueIndexCursor extends NodeIndexCursor
boolean hasValue();

Value propertyValue( int offset );

class Empty implements NodeValueIndexCursor
{

@Override
public void node( NodeCursor cursor )
{

}

@Override
public long nodeReference()
{
return -1L;
}

@Override
public boolean next()
{
return false;
}

@Override
public boolean shouldRetry()
{
return false;
}

@Override
public void close()
{

}

@Override
public boolean isClosed()
{
return false;
}

@Override
public int numberOfProperties()
{
return 0;
}

@Override
public int propertyKey( int offset )
{
return -1;
}

@Override
public boolean hasValue()
{
return false;
}

@Override
public Value propertyValue( int offset )
{
return NO_VALUE;
}
}

NodeValueIndexCursor EMPTY = new Empty();
}
Expand Up @@ -33,7 +33,7 @@ case class IndexSeek(opName: String, labelName: String, propNames: Seq[String],
val propKeyVar = context.namer.newVarName()
generator.lookupLabelId(labelVar, labelName)
generator.lookupPropertyKey(propNames.head, propKeyVar)
generator.newIndexDescriptor(descriptorVar, labelVar, propKeyVar)
generator.newIndexReference(descriptorVar, labelVar, propKeyVar)
}

override def produceIterator[E](iterVar: String, generator: MethodStructure[E])(implicit context: CodeGenContext) = {
Expand All @@ -44,8 +44,8 @@ case class IndexSeek(opName: String, labelName: String, propNames: Seq[String],
override def produceNext[E](nextVar: Variable, iterVar: String, generator: MethodStructure[E])
(implicit context: CodeGenContext) = {
generator.incrementDbHits()
generator.nextNode(nextVar.name, iterVar)
generator.nodeFromNodeValueIndexCursor(nextVar.name, iterVar)
}

override def hasNext[E](generator: MethodStructure[E], iterVar: String): E = generator.hasNextNode(iterVar)
override def hasNext[E](generator: MethodStructure[E], iterVar: String): E = generator.advanceNodeValueIndexCursor(iterVar)
}
Expand Up @@ -158,7 +158,7 @@ trait MethodStructure[E] {
def nodeGetRelationshipsWithDirectionAndTypes(iterVar: String, nodeVar: String, nodeVarType: CodeGenType, direction: SemanticDirection, typeVars: Seq[String]): Unit
def connectingRelationships(iterVar: String, fromNode: String, fromNodeType: CodeGenType, dir: SemanticDirection, toNode:String, toNodeType: CodeGenType)
def connectingRelationships(iterVar: String, fromNode: String, fromNodeType: CodeGenType, dir: SemanticDirection, types: Seq[String], toNode: String, toNodeType: CodeGenType)
def nextNode(targetVar: String, iterVar: String): Unit
def nodeFromNodeValueIndexCursor(targetVar: String, iterVar: String): Unit
def nodeFromNodeCursor(targetVar: String, iterVar: String): Unit
def nodeFromNodeLabelIndexCursor(targetVar: String, iterVar: String): Unit
def nextRelationshipAndNode(toNodeVar: String, iterVar: String, direction: SemanticDirection, fromNodeVar: String, relVar: String): Unit
Expand All @@ -167,6 +167,8 @@ trait MethodStructure[E] {
def advanceNodeCursor(iterVar: String): E
def advanceNodeLabelIndexCursor(iterVar: String): E
def advanceRelationshipSelectionCursor(iterVar: String): E
def advanceNodeValueIndexCursor(iterVar: String): E
def hasNextRelationship(iterVar: String): E
def nodeGetPropertyById(nodeVar: String, nodeVarType: CodeGenType, propId: Int, propValueVar: String): Unit
def nodeGetPropertyForVar(nodeVar: String, nodeVarType: CodeGenType, propIdVar: String, propValueVar: String): Unit
def nodeIdSeek(nodeIdVar: String, expression: E, codeGenType: CodeGenType)(block: MethodStructure[E] => Unit): Unit
Expand All @@ -175,7 +177,8 @@ trait MethodStructure[E] {
def lookupPropertyKey(propName: String, propVar: String)
def indexSeek(iterVar: String, descriptorVar: String, value: E, codeGenType: CodeGenType): Unit
def relType(relIdVar: String, typeVar: String): Unit
def newIndexDescriptor(descriptorVar: String, labelVar: String, propKeyVar: String): Unit
def newIndexReference(descriptorVar: String, labelVar: String, propKeyVar: String): Unit
def createRelExtractor(extractorName: String): Unit
def nodeCountFromCountStore(expression: E): E
def relCountFromCountStore(start: E, end: E, types: E*): E
def token(t: Int): E
Expand Down
Expand Up @@ -35,5 +35,6 @@ case class Fields(closer: FieldReference,
nodeCursor: FieldReference,
propertyCursor: FieldReference,
dataRead: FieldReference,
tokenRead: FieldReference
tokenRead: FieldReference,
schemaRead: FieldReference
)

0 comments on commit f17e740

Please sign in to comment.