Skip to content

Commit

Permalink
Introduce IndexOrder capability for schema index queries
Browse files Browse the repository at this point in the history
Schema indexes report what orderings (ascending, descending)
it is capable of providing for given combination of ValueGroups.

A specific ordering of results can be requested through Read.nodeIndexSeek
or Read.nodeIndexScan. Requested ordering must be supported by given
IndexReference.

At this moment, only single property number indexes are capable
of any result ordering and that ordering is ascending.
Provided by NativeSchemaIndexProvider through FusionSchemaIndexProvider.
  • Loading branch information
burqen committed Nov 16, 2017
1 parent 0cfc331 commit be8eeca
Show file tree
Hide file tree
Showing 12 changed files with 218 additions and 84 deletions.
Expand Up @@ -11,9 +11,9 @@ public interface CapableIndexReference extends IndexReference, IndexCapability
CapableIndexReference NO_INDEX = new CapableIndexReference() CapableIndexReference NO_INDEX = new CapableIndexReference()
{ {
@Override @Override
public IndexOrderCapability[] order( ValueGroup... valueGroups ) public IndexOrder[] order( ValueGroup... valueGroups )
{ {
return new IndexOrderCapability[0]; return new IndexOrder[0];
} }


@Override @Override
Expand Down
Expand Up @@ -38,10 +38,10 @@ public interface IndexCapability
* must correspond to related {@link IndexReference#properties()}. A {@code null} value in the array * must correspond to related {@link IndexReference#properties()}. A {@code null} value in the array
* ({@code new ValueGroup[]{null}}) is interpreted as a wildcard for any {@link ValueGroup}. Note that this is not the same as * ({@code new ValueGroup[]{null}}) is interpreted as a wildcard for any {@link ValueGroup}. Note that this is not the same as
* {@code order(null)} which is undefined. * {@code order(null)} which is undefined.
* @return {@link IndexOrderCapability} array containing all possible orderings for provided value groups or empty array if no explicit * @return {@link IndexOrder} array containing all possible orderings for provided value groups or empty array if no explicit
* ordering is possible or if length of {@code valueGroups} and {@link IndexReference#properties()} differ. * ordering is possible or if length of {@code valueGroups} and {@link IndexReference#properties()} differ.
*/ */
IndexOrderCapability[] order( ValueGroup... valueGroups ); IndexOrder[] order( ValueGroup... valueGroups );


/** /**
* Is the index capable of providing values for a query on given combination of {@link ValueGroup}. * Is the index capable of providing values for a query on given combination of {@link ValueGroup}.
Expand All @@ -60,9 +60,9 @@ public interface IndexCapability
IndexCapability NO_CAPABILITY = new IndexCapability() IndexCapability NO_CAPABILITY = new IndexCapability()
{ {
@Override @Override
public IndexOrderCapability[] order( ValueGroup... valueGroups ) public IndexOrder[] order( ValueGroup... valueGroups )
{ {
return new IndexOrderCapability[0]; return new IndexOrder[0];
} }


@Override @Override
Expand Down
Expand Up @@ -19,7 +19,7 @@
*/ */
package org.neo4j.internal.kernel.api; package org.neo4j.internal.kernel.api;


public enum IndexOrderCapability public enum IndexOrder
{ {
ASCENDING, DESCENDING ASCENDING, DESCENDING
} }
Expand Up @@ -27,14 +27,15 @@
public interface Read public interface Read
{ {
/** /**
* @param index * @param index {@link IndexReference} referencing index to query.
* @param cursor * @param cursor the cursor to use for consuming the results.
* the cursor to use for consuming the results. * @param indexOrder requested {@link IndexOrder} of result. Must be among the capabilities of {@link IndexReference referenced index}.
* @param query * @param query Combination of {@link IndexQuery index queries} to run against referenced index.
*/ */
void nodeIndexSeek( IndexReference index, NodeValueIndexCursor cursor, IndexQuery... query ) throws KernelException; void nodeIndexSeek( IndexReference index, NodeValueIndexCursor cursor, IndexOrder indexOrder, IndexQuery... query )
throws KernelException;


void nodeIndexScan( IndexReference index, NodeValueIndexCursor cursor ) throws KernelException; void nodeIndexScan( IndexReference index, NodeValueIndexCursor cursor, IndexOrder indexOrder ) throws KernelException;


void nodeLabelScan( int label, NodeLabelIndexCursor cursor ); void nodeLabelScan( int label, NodeLabelIndexCursor cursor );


Expand Down
Expand Up @@ -29,6 +29,7 @@
import org.neo4j.graphdb.schema.IndexDefinition; import org.neo4j.graphdb.schema.IndexDefinition;
import org.neo4j.values.storable.Value; import org.neo4j.values.storable.Value;
import org.neo4j.values.storable.ValueGroup; import org.neo4j.values.storable.ValueGroup;
import org.neo4j.values.storable.Values;


import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
Expand Down Expand Up @@ -68,12 +69,12 @@ void createTestGraph( GraphDatabaseService graphDb )
strThree3 = nodeWithProp( graphDb, "three" ); strThree3 = nodeWithProp( graphDb, "three" );
nodeWithProp( graphDb, false ); nodeWithProp( graphDb, false );
boolTrue = nodeWithProp( graphDb, true ); boolTrue = nodeWithProp( graphDb, true );
nodeWithProp( graphDb, 1 ); nodeWithProp( graphDb, 3 ); // Purposely mix ordering
nodeWithProp( graphDb, 2 );
nodeWithProp( graphDb, 2 );
nodeWithProp( graphDb, 3 );
nodeWithProp( graphDb, 3 ); nodeWithProp( graphDb, 3 );
nodeWithProp( graphDb, 3 ); nodeWithProp( graphDb, 3 );
nodeWithProp( graphDb, 2 );
nodeWithProp( graphDb, 2 );
nodeWithProp( graphDb, 1 );
nodeWithProp( graphDb, 4 ); nodeWithProp( graphDb, 4 );
num5 = nodeWithProp( graphDb, 5 ); num5 = nodeWithProp( graphDb, 5 );
num6 = nodeWithProp( graphDb, 6 ); num6 = nodeWithProp( graphDb, 6 );
Expand Down Expand Up @@ -101,64 +102,64 @@ public void shouldPerformExactLookup() throws Exception
{ {
// when // when
IndexValueCapability expectValue = index.value( ValueGroup.TEXT ); IndexValueCapability expectValue = index.value( ValueGroup.TEXT );
read.nodeIndexSeek( index, node, IndexQuery.exact( prop, "zero" ) ); read.nodeIndexSeek( index, node, null, IndexQuery.exact( prop, "zero" ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, expectValue ); assertFoundNodesAndValue( node, uniqueIds, expectValue );


// when // when
read.nodeIndexSeek( index, node, IndexQuery.exact( prop, "one" ) ); read.nodeIndexSeek( index, node, null, IndexQuery.exact( prop, "one" ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, expectValue, strOne ); assertFoundNodesAndValue( node, uniqueIds, expectValue, strOne );


// when // when
read.nodeIndexSeek( index, node, IndexQuery.exact( prop, "two" ) ); read.nodeIndexSeek( index, node, null, IndexQuery.exact( prop, "two" ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, expectValue, strTwo1, strTwo2 ); assertFoundNodesAndValue( node, uniqueIds, expectValue, strTwo1, strTwo2 );


// when // when
read.nodeIndexSeek( index, node, IndexQuery.exact( prop, "three" ) ); read.nodeIndexSeek( index, node, null, IndexQuery.exact( prop, "three" ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, expectValue, strThree1, strThree2, strThree3 ); assertFoundNodesAndValue( node, uniqueIds, expectValue, strThree1, strThree2, strThree3 );


// when // when
expectValue = index.value( ValueGroup.NUMBER ); expectValue = index.value( ValueGroup.NUMBER );
read.nodeIndexSeek( index, node, IndexQuery.exact( prop, 1 ) ); read.nodeIndexSeek( index, node, null, IndexQuery.exact( prop, 1 ) );


// then // then
// todo continue here // todo continue here
assertFoundNodesAndValue( node, 1, uniqueIds, expectValue ); assertFoundNodesAndValue( node, 1, uniqueIds, expectValue );


// when //when
read.nodeIndexSeek( index, node, IndexQuery.exact( prop, 2 ) ); read.nodeIndexSeek( index, node, null, IndexQuery.exact( prop, 2 ) );


// then // then
assertFoundNodesAndValue( node, 2, uniqueIds, expectValue ); assertFoundNodesAndValue( node, 2, uniqueIds, expectValue );


// when //when
read.nodeIndexSeek( index, node, IndexQuery.exact( prop, 3 ) ); read.nodeIndexSeek( index, node, null, IndexQuery.exact( prop, 3 ) );


// then // then
assertFoundNodesAndValue( node, 3, uniqueIds, expectValue ); assertFoundNodesAndValue( node, 3, uniqueIds, expectValue );


// when //when
read.nodeIndexSeek( index, node, IndexQuery.exact( prop, 6 ) ); read.nodeIndexSeek( index, node, null, IndexQuery.exact( prop, 6 ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, expectValue, num6 ); assertFoundNodesAndValue( node, uniqueIds, expectValue, num6 );


// when // when
read.nodeIndexSeek( index, node, IndexQuery.exact( prop, 12.0 ) ); read.nodeIndexSeek( index, node, null, IndexQuery.exact( prop, 12.0 ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, expectValue, num12a, num12b ); assertFoundNodesAndValue( node, uniqueIds, expectValue, num12a, num12b );


// when // when
expectValue = index.value( ValueGroup.BOOLEAN ); expectValue = index.value( ValueGroup.BOOLEAN );
read.nodeIndexSeek( index, node, IndexQuery.exact( prop, true ) ); read.nodeIndexSeek( index, node, null, IndexQuery.exact( prop, true ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, expectValue, boolTrue ); assertFoundNodesAndValue( node, uniqueIds, expectValue, boolTrue );
Expand All @@ -177,7 +178,7 @@ public void shouldPerformStringPrefixSearch() throws Exception
PrimitiveLongSet uniqueIds = Primitive.longSet() ) PrimitiveLongSet uniqueIds = Primitive.longSet() )
{ {
// when // when
read.nodeIndexSeek( index, node, IndexQuery.stringPrefix( prop, "t" ) ); read.nodeIndexSeek( index, node, null, IndexQuery.stringPrefix( prop, "t" ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, stringCapability, strTwo1, strTwo2, strThree1, strThree2, strThree3 ); assertFoundNodesAndValue( node, uniqueIds, stringCapability, strTwo1, strTwo2, strThree1, strThree2, strThree3 );
Expand All @@ -196,7 +197,7 @@ public void shouldPerformStringSuffixSearch() throws Exception
PrimitiveLongSet uniqueIds = Primitive.longSet() ) PrimitiveLongSet uniqueIds = Primitive.longSet() )
{ {
// when // when
read.nodeIndexSeek( index, node, IndexQuery.stringSuffix( prop, "e" ) ); read.nodeIndexSeek( index, node, null, IndexQuery.stringSuffix( prop, "e" ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, stringCapability, strOne, strThree1, strThree2, strThree3 ); assertFoundNodesAndValue( node, uniqueIds, stringCapability, strOne, strThree1, strThree2, strThree3 );
Expand All @@ -215,7 +216,7 @@ public void shouldPerformStringContainmentSearch() throws Exception
PrimitiveLongSet uniqueIds = Primitive.longSet() ) PrimitiveLongSet uniqueIds = Primitive.longSet() )
{ {
// when // when
read.nodeIndexSeek( index, node, IndexQuery.stringContains( prop, "o" ) ); read.nodeIndexSeek( index, node, null, IndexQuery.stringContains( prop, "o" ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, stringCapability, strOne, strTwo1, strTwo2 ); assertFoundNodesAndValue( node, uniqueIds, stringCapability, strOne, strTwo1, strTwo2 );
Expand All @@ -234,32 +235,32 @@ public void shouldPerformStringRangeSearch() throws Exception
PrimitiveLongSet uniqueIds = Primitive.longSet() ) PrimitiveLongSet uniqueIds = Primitive.longSet() )
{ {
// when // when
read.nodeIndexSeek( index, node, IndexQuery.range( prop, "one", true, "three", true ) ); read.nodeIndexSeek( index, node, null, IndexQuery.range( prop, "one", true, "three", true ) );


// then // then


assertFoundNodesAndValue( node, uniqueIds, stringCapability, strOne, strThree1, strThree2, strThree3 ); assertFoundNodesAndValue( node, uniqueIds, stringCapability, strOne, strThree1, strThree2, strThree3 );


// when // when
read.nodeIndexSeek( index, node, IndexQuery.range( prop, "one", true, "three", false ) ); read.nodeIndexSeek( index, node, null, IndexQuery.range( prop, "one", true, "three", false ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, stringCapability, strOne ); assertFoundNodesAndValue( node, uniqueIds, stringCapability, strOne );


// when // when
read.nodeIndexSeek( index, node, IndexQuery.range( prop, "one", false, "three", true ) ); read.nodeIndexSeek( index, node, null, IndexQuery.range( prop, "one", false, "three", true ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, stringCapability, strThree1, strThree2, strThree3 ); assertFoundNodesAndValue( node, uniqueIds, stringCapability, strThree1, strThree2, strThree3 );


// when // when
read.nodeIndexSeek( index, node, IndexQuery.range( prop, "one", false, "two", false ) ); read.nodeIndexSeek( index, node, null, IndexQuery.range( prop, "one", false, "two", false ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, stringCapability, strThree1, strThree2, strThree3 ); assertFoundNodesAndValue( node, uniqueIds, stringCapability, strThree1, strThree2, strThree3 );


// when // when
read.nodeIndexSeek( index, node, IndexQuery.range( prop, "one", true, "two", true ) ); read.nodeIndexSeek( index, node, null, IndexQuery.range( prop, "one", true, "two", true ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, stringCapability, strOne, strThree1, strThree2, strThree3, strTwo1, strTwo2 ); assertFoundNodesAndValue( node, uniqueIds, stringCapability, strOne, strThree1, strThree2, strThree3, strTwo1, strTwo2 );
Expand All @@ -278,26 +279,26 @@ public void shouldPerformNumericRangeSearch() throws Exception
PrimitiveLongSet uniqueIds = Primitive.longSet() ) PrimitiveLongSet uniqueIds = Primitive.longSet() )
{ {
// when // when
read.nodeIndexSeek( index, node, IndexQuery.range( prop, 5, true, 12, true ) ); read.nodeIndexSeek( index, node, null, IndexQuery.range( prop, 5, true, 12, true ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, numberCapability, num5, num6, num12a, num12b ); assertFoundNodesAndValue( node, uniqueIds, numberCapability, num5, num6, num12a, num12b );


// when // when
read.nodeIndexSeek( index, node, IndexQuery.range( prop, 5, true, 12, false ) ); read.nodeIndexSeek( index, node, null, IndexQuery.range( prop, 5, true, 12, false ) );


// then // then


assertFoundNodesAndValue( node, uniqueIds, numberCapability, num5, num6 ); assertFoundNodesAndValue( node, uniqueIds, numberCapability, num5, num6 );


// when // when
read.nodeIndexSeek( index, node, IndexQuery.range( prop, 5, false, 12, true ) ); read.nodeIndexSeek( index, node, null, IndexQuery.range( prop, 5, false, 12, true ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, numberCapability, num6, num12a, num12b ); assertFoundNodesAndValue( node, uniqueIds, numberCapability, num6, num12a, num12b );


// when // when
read.nodeIndexSeek( index, node, IndexQuery.range( prop, 5, false, 12, false ) ); read.nodeIndexSeek( index, node, null, IndexQuery.range( prop, 5, false, 12, false ) );


// then // then
assertFoundNodesAndValue( node, uniqueIds, numberCapability, num6 ); assertFoundNodesAndValue( node, uniqueIds, numberCapability, num6 );
Expand All @@ -316,14 +317,105 @@ public void shouldPerformIndexScan() throws Exception
PrimitiveLongSet uniqueIds = Primitive.longSet() ) PrimitiveLongSet uniqueIds = Primitive.longSet() )
{ {
// when // when
read.nodeIndexScan( index, node ); read.nodeIndexScan( index, node, null );


// then // then
assertFoundNodesAndValue( node, 24, uniqueIds, wildcardCapability ); assertFoundNodesAndValue( node, 24, uniqueIds, wildcardCapability );
} }
} }


private void assertFoundNodesAndValue( NodeValueIndexCursor node, int nodes, PrimitiveLongSet uniqueIds, IndexValueCapability expectValue ) @Test
public void shouldRespectOrderCapabilitiesForNumbers() throws Exception
{
// given
int label = token.nodeLabel( "Node" );
int prop = token.propertyKey( "prop" );
CapableIndexReference index = schemaRead.index( label, prop );
IndexOrder[] orderCapabilities = index.order( ValueGroup.NUMBER );
try ( NodeValueIndexCursor node = cursors.allocateNodeValueIndexCursor() )
{
for ( IndexOrder orderCapability : orderCapabilities )
{
// when
read.nodeIndexSeek( index, node, orderCapability, IndexQuery.range( prop, 1, true, 42, true ) );

// then
assertFoundNodesInOrder( node, orderCapability );
}
}
}

@Test
public void shouldRespectOrderCapabilitiesForStrings() throws Exception
{
// given
int label = token.nodeLabel( "Node" );
int prop = token.propertyKey( "prop" );
CapableIndexReference index = schemaRead.index( label, prop );
IndexOrder[] orderCapabilities = index.order( ValueGroup.TEXT );
try ( NodeValueIndexCursor node = cursors.allocateNodeValueIndexCursor() )
{
for ( IndexOrder orderCapability : orderCapabilities )
{
// when
read.nodeIndexSeek( index, node, orderCapability, IndexQuery.range( prop, "one", true, "two", true ) );

// then
assertFoundNodesInOrder( node, orderCapability );
}
}
}

@Test
public void shouldRespectOrderCapabilitiesForWildcard() throws Exception
{
// given
int label = token.nodeLabel( "Node" );
int prop = token.propertyKey( "prop" );
CapableIndexReference index = schemaRead.index( label, prop );
IndexOrder[] orderCapabilities = index.order( new ValueGroup[]{null} );
try ( NodeValueIndexCursor node = cursors.allocateNodeValueIndexCursor() )
{
for ( IndexOrder orderCapability : orderCapabilities )
{
// when
read.nodeIndexSeek( index, node, orderCapability, IndexQuery.exists( prop ) );

// then
assertFoundNodesInOrder( node, orderCapability );
}
}
}

private void assertFoundNodesInOrder( NodeValueIndexCursor node, IndexOrder indexOrder )
{
Value currentValue = null;
while ( node.next() )
{
long nodeReference = node.nodeReference();
Value storedValue = getPropertyValueFromStore( nodeReference );
if ( currentValue != null )
{
switch ( indexOrder )
{
case ASCENDING:
assertTrue( "Requested ordering " + indexOrder + " was not respected.",
Values.COMPARATOR.compare( currentValue, storedValue ) <= 0 );
break;
case DESCENDING:
assertTrue( "Requested ordering " + indexOrder + " was not respected.",
Values.COMPARATOR.compare( currentValue, storedValue ) >= 0 );
break;
default:
throw new UnsupportedOperationException( "Can not verify ordering for " + indexOrder );
}
}
currentValue = storedValue;
}
}

private void assertFoundNodesAndValue( NodeValueIndexCursor node, int nodes, PrimitiveLongSet uniqueIds,
IndexValueCapability expectValue )
{ {
uniqueIds.clear(); uniqueIds.clear();
for ( int i = 0; i < nodes; i++ ) for ( int i = 0; i < nodes; i++ )
Expand All @@ -332,10 +424,13 @@ private void assertFoundNodesAndValue( NodeValueIndexCursor node, int nodes, Pri
long nodeReference = node.nodeReference(); long nodeReference = node.nodeReference();
assertTrue( "all nodes are unique", uniqueIds.add( nodeReference ) ); assertTrue( "all nodes are unique", uniqueIds.add( nodeReference ) );


// Assert has value capability
if ( IndexValueCapability.YES.equals( expectValue ) ) if ( IndexValueCapability.YES.equals( expectValue ) )
{ {
assertTrue( "has value", node.hasValue() ); assertTrue( "has value", node.hasValue() );
} }

// Assert has correct value
if ( node.hasValue() ) if ( node.hasValue() )
{ {
Value storedValue = getPropertyValueFromStore( nodeReference ); Value storedValue = getPropertyValueFromStore( nodeReference );
Expand Down

0 comments on commit be8eeca

Please sign in to comment.