Skip to content

Commit

Permalink
Enable range scans for all temporal index parts
Browse files Browse the repository at this point in the history
  • Loading branch information
fickludd committed Mar 13, 2018
1 parent a6d5269 commit 3759b1e
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 41 deletions.
Expand Up @@ -64,52 +64,40 @@ public static ExactPredicate exact( int propertyKeyId, Object value )
* Searches the index for numeric values between {@code from} and {@code to}.
*
* @param propertyKeyId the property ID to match.
* @param from the lower bound of the property value.
* @param from lower bound of the range or null if unbounded
* @param fromInclusive the lower bound is inclusive if true.
* @param to the upper bound of the property value.
* @param to upper bound of the range or null if unbounded
* @param toInclusive the upper bound is inclusive if true.
* @return an {@link IndexQuery} instance to be used for querying an index.
*/
public static RangePredicate range( int propertyKeyId, Number from, boolean fromInclusive,
public static RangePredicate range( int propertyKeyId,
Number from, boolean fromInclusive,
Number to, boolean toInclusive )
{
return new NumberRangePredicate( propertyKeyId,
from == null ? null : Values.numberValue( from ), fromInclusive,
to == null ? null : Values.numberValue( to ), toInclusive );
from == null ? null : Values.numberValue( from ), fromInclusive,
to == null ? null : Values.numberValue( to ), toInclusive );
}

/**
* Searches the index for geometry values between {@code from} and {@code to}.
*
* @param propertyKeyId the property ID to match.
* @param from the lower bound of the property value. In 2D this means lower-left corner of search envelope.
* @param fromInclusive the lower bound is inclusive if true.
* @param to the upper bound of the property value. In 2D this means upper-right corner of search envelope.
* @param toInclusive the upper bound is inclusive if true.
* @return an {@link IndexQuery} instance to be used for querying an index.
*/
public static RangePredicate range( int propertyKeyId, PointValue from, boolean fromInclusive,
PointValue to, boolean toInclusive )
public static RangePredicate range( int propertyKeyId,
String from, boolean fromInclusive,
String to, boolean toInclusive )
{
return new GeometryRangePredicate( propertyKeyId, from, fromInclusive, to, toInclusive );
return new TextRangePredicate( propertyKeyId,
from == null ? null : Values.stringValue( from ), fromInclusive,
to == null ? null : Values.stringValue( to ), toInclusive );
}

/**
* Searches the index for string values between {@code from} and {@code to}.
*
* @param propertyKeyId the property ID to match.
* @param from the lower bound of the property value.
* @param fromInclusive the lower bound is inclusive if true.
* @param to the upper bound of the property value.
* @param toInclusive the upper bound is inclusive if true.
* @return an {@link IndexQuery} instance to be used for querying an index.
*/
public static RangePredicate range( int propertyKeyId, String from, boolean fromInclusive,
String to, boolean toInclusive )
public static <VALUE extends Value> RangePredicate range( int propertyKeyId,
VALUE from, boolean fromInclusive,
VALUE to, boolean toInclusive )
{
return new TextRangePredicate( propertyKeyId,
from == null ? null : Values.stringValue( from ), fromInclusive,
to == null ? null : Values.stringValue( to ), toInclusive );
ValueGroup valueGroup = from != null ? from.valueGroup() : to.valueGroup();
if ( valueGroup == ValueGroup.GEOMETRY )
{
return new GeometryRangePredicate( propertyKeyId, (PointValue)from, fromInclusive, (PointValue)to, toInclusive );
}
return new RangePredicate<>( propertyKeyId, valueGroup, from, fromInclusive, to, toInclusive );
}

/**
Expand Down
Expand Up @@ -26,6 +26,8 @@
import org.neo4j.internal.kernel.api.IndexQuery;
import org.neo4j.kernel.api.schema.index.SchemaIndexDescriptor;
import org.neo4j.kernel.impl.api.index.sampling.IndexSamplingConfig;
import org.neo4j.values.storable.Value;
import org.neo4j.values.storable.Values;

class TemporalIndexPartReader<KEY extends NativeSchemaKey> extends NativeSchemaIndexReader<KEY,NativeSchemaValue>
{
Expand Down Expand Up @@ -58,18 +60,53 @@ protected boolean initializeRangeForQuery( KEY treeKeyFrom, KEY treeKeyTo, Index
treeKeyFrom.initAsLowest();
treeKeyTo.initAsHighest();
break;

case exact:
IndexQuery.ExactPredicate exactPredicate = (IndexQuery.ExactPredicate) predicate;
treeKeyFrom.from( Long.MIN_VALUE, exactPredicate.value() );
treeKeyTo.from( Long.MAX_VALUE, exactPredicate.value() );
break;
// will add range for temporal

case range:
IndexQuery.RangePredicate rangePredicate = (IndexQuery.RangePredicate) predicate;
initFromForRange( rangePredicate, treeKeyFrom );
initToForRange( rangePredicate, treeKeyTo );
break;

default:
throw new IllegalArgumentException( "IndexQuery of type " + predicate.type() + " is not supported." );
}
return false; // no filtering
}

private void initFromForRange( IndexQuery.RangePredicate rangePredicate, KEY treeKeyFrom )
{
Value fromValue = rangePredicate.fromValue();
if ( fromValue == Values.NO_VALUE )
{
treeKeyFrom.initAsLowest();
}
else
{
treeKeyFrom.from( rangePredicate.fromInclusive() ? Long.MIN_VALUE : Long.MAX_VALUE, fromValue );
treeKeyFrom.setCompareId( true );
}
}

private void initToForRange( IndexQuery.RangePredicate rangePredicate, KEY treeKeyTo )
{
Value toValue = rangePredicate.toValue();
if ( toValue == Values.NO_VALUE )
{
treeKeyTo.initAsHighest();
}
else
{
treeKeyTo.from( rangePredicate.toInclusive() ? Long.MAX_VALUE : Long.MIN_VALUE, toValue );
treeKeyTo.setCompareId( true );
}
}

@Override
public boolean hasFullValuePrecision( IndexQuery... predicates )
{
Expand Down
Expand Up @@ -28,7 +28,6 @@
import org.neo4j.helpers.collection.Iterators;
import org.neo4j.internal.kernel.api.IndexOrder;
import org.neo4j.internal.kernel.api.IndexQuery;
import org.neo4j.internal.kernel.api.IndexQuery.ExactPredicate;
import org.neo4j.internal.kernel.api.IndexQuery.ExistsPredicate;
import org.neo4j.kernel.api.schema.index.SchemaIndexDescriptor;
import org.neo4j.kernel.impl.index.schema.fusion.BridgingIndexProgressor;
Expand Down Expand Up @@ -89,7 +88,12 @@ public PrimitiveLongResourceIterator query( IndexQuery... predicates )
@Override
public void query( IndexProgressor.NodeValueClient cursor, IndexOrder indexOrder, IndexQuery... predicates )
{
if ( predicates[0] instanceof ExistsPredicate )
if ( predicates.length != 1 )
{
throw new IllegalArgumentException( "Only single property temporal indexes are supported." );
}
IndexQuery predicate = predicates[0];
if ( predicate instanceof ExistsPredicate )
{
BridgingIndexProgressor multiProgressor = new BridgingIndexProgressor( cursor, descriptor.schema().getPropertyIds() );
cursor.initialize( descriptor, multiProgressor, predicates );
Expand All @@ -100,13 +104,21 @@ public void query( IndexProgressor.NodeValueClient cursor, IndexOrder indexOrder
}
else
{
if ( validPredicates( predicates ) )
if ( validPredicate( predicate ) )
{
NativeSchemaIndexReader<?,NativeSchemaValue> part = uncheckedSelect( predicates[0].valueGroup() );
NativeSchemaIndexReader<?,NativeSchemaValue> part = uncheckedSelect( predicate.valueGroup() );
if ( part != null )
{
part.query( cursor, indexOrder, predicates );
}
else
{
cursor.initialize( descriptor, IndexProgressor.EMPTY, predicates );
}
}
else
{
cursor.initialize( descriptor, IndexProgressor.EMPTY, predicates );
}
}
}
Expand All @@ -117,9 +129,9 @@ public boolean hasFullValuePrecision( IndexQuery... predicates )
return true;
}

private boolean validPredicates( IndexQuery[] predicates )
private boolean validPredicate( IndexQuery predicate )
{
return predicates[0] instanceof ExactPredicate;
return predicate instanceof IndexQuery.ExactPredicate || predicate instanceof IndexQuery.RangePredicate;
}

/**
Expand Down
Expand Up @@ -105,6 +105,13 @@ public PrimitiveLongResourceIterator query( IndexQuery... predicates ) throws In
return instances[SPATIAL].query( predicates );
case TEXT:
return instances[STRING].query( predicates );
case DATE:
case LOCAL_DATE_TIME:
case ZONED_DATE_TIME:
case LOCAL_TIME:
case ZONED_TIME:
case DURATION:
return instances[TEMPORAL].query( predicates );
default: // fall through
}
// TODO: support temporal range queries
Expand Down
Expand Up @@ -64,7 +64,8 @@ private static Value[] toValues( Object[] objects )
Value[] values = new Value[objects.length];
for ( int i = 0; i < objects.length; i++ )
{
values[i] = Values.of( objects[i] );
Object object = objects[i];
values[i] = object instanceof Value ? (Value)object : Values.of( object );
}
return values;
}
Expand Down
Expand Up @@ -28,7 +28,9 @@
import org.neo4j.internal.kernel.api.IndexQuery;
import org.neo4j.kernel.api.schema.index.SchemaIndexDescriptor;
import org.neo4j.kernel.api.schema.index.SchemaIndexDescriptorFactory;
import org.neo4j.values.storable.Value;

import static java.time.ZoneOffset.UTC;
import static java.util.Arrays.asList;
import static java.util.Collections.EMPTY_LIST;
import static java.util.Collections.singletonList;
Expand All @@ -41,6 +43,12 @@
import static org.neo4j.internal.kernel.api.IndexQuery.stringPrefix;
import static org.neo4j.internal.kernel.api.IndexQuery.stringSuffix;
import static org.neo4j.kernel.api.index.IndexQueryHelper.add;
import static org.neo4j.values.storable.DateTimeValue.datetime;
import static org.neo4j.values.storable.DateValue.epochDate;
import static org.neo4j.values.storable.DurationValue.duration;
import static org.neo4j.values.storable.LocalDateTimeValue.localDateTime;
import static org.neo4j.values.storable.LocalTimeValue.localTime;
import static org.neo4j.values.storable.TimeValue.time;

@Ignore( "Not a test. This is a compatibility suite that provides test cases for verifying" +
" IndexProvider implementations. Each index provider that is to be tested by this suite" +
Expand Down Expand Up @@ -210,6 +218,79 @@ public void testIndexRangeSeekByStringWithDuplicates() throws Exception
assertThat( query( range( 1, "Anna", true, "William", true ) ), equalTo( asList( 1L, 2L, 3L, 4L, 5L ) ) );
}

@Test
public void testIndexRangeSeekByDateWithDuplicates() throws Exception
{
testIndexRangeSeekWithDuplicates( epochDate( 100 ),
epochDate( 101 ),
epochDate( 200 ),
epochDate( 300 ) );
}

@Test
public void testIndexRangeSeekByLocalDateTimeWithDuplicates() throws Exception
{
testIndexRangeSeekWithDuplicates( localDateTime( 1000, 10 ),
localDateTime( 1000, 11 ),
localDateTime( 2000, 10 ),
localDateTime( 3000, 10 ) );
}

@Test
public void testIndexRangeSeekByDateTimeWithDuplicates() throws Exception
{
testIndexRangeSeekWithDuplicates( datetime( 1000, 10, UTC ),
datetime( 1000, 11, UTC ),
datetime( 2000, 10, UTC ),
datetime( 3000, 10, UTC ) );
}

@Test
public void testIndexRangeSeekByLocalTimeWithDuplicates() throws Exception
{
testIndexRangeSeekWithDuplicates( localTime( 1000 ),
localTime( 1001 ),
localTime( 2000 ),
localTime( 3000 ) );
}

@Test
public void testIndexRangeSeekByTimeWithDuplicates() throws Exception
{
testIndexRangeSeekWithDuplicates( time( 1000, UTC ),
time( 1001, UTC ),
time( 2000, UTC ),
time( 3000, UTC ) );
}

@Test
public void testIndexRangeSeekByDurationWithDuplicates() throws Exception
{
testIndexRangeSeekWithDuplicates( duration( 1, 1, 1, 1 ),
duration( 1, 1, 1, 2 ),
duration( 2, 1, 1, 1 ),
duration( 3, 1, 1, 1 ) );
}

/**
* Helper for testing range seeks. Takes 4 ordered sample values.
*/
private <VALUE extends Value> void testIndexRangeSeekWithDuplicates( VALUE v1, VALUE v2, VALUE v3, VALUE v4 ) throws Exception
{
updateAndCommit( asList(
add( 1L, descriptor.schema(), v1 ),
add( 2L, descriptor.schema(), v1 ),
add( 3L, descriptor.schema(), v3 ),
add( 4L, descriptor.schema(), v4 ),
add( 5L, descriptor.schema(), v4 ) ) );

assertThat( query( range( 1, v1, false, v4, false ) ), equalTo( singletonList( 3L ) ) );
assertThat( query( range( 1, v2, false, v3, false ) ), equalTo( EMPTY_LIST ) );
assertThat( query( range( 1, v1, true, v4, false ) ), equalTo( asList( 1L, 2L, 3L ) ) );
assertThat( query( range( 1, v1, false, v4, true ) ), equalTo( asList( 3L, 4L, 5L ) ) );
assertThat( query( range( 1, v1, true, v4, true ) ), equalTo( asList( 1L, 2L, 3L, 4L, 5L ) ) );
}

@Test
public void testIndexRangeSeekByPrefixWithDuplicates() throws Exception
{
Expand Down

0 comments on commit 3759b1e

Please sign in to comment.