Skip to content

Commit

Permalink
Start of implementing read queries
Browse files Browse the repository at this point in the history
Range queries now work with inclusive/exclusive from AND to due to slight
change in NumberKey where it now has a boolean saying whether or not entityId
should take part in comparisons.
  • Loading branch information
burqen authored and tinwelint committed Jun 26, 2017
1 parent b98b97e commit 5acded4
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 78 deletions.
25 changes: 18 additions & 7 deletions community/common/src/test/java/org/neo4j/test/rule/RandomRule.java
Expand Up @@ -44,6 +44,7 @@
public class RandomRule implements TestRule public class RandomRule implements TestRule
{ {
private long seed; private long seed;
private boolean hasGlobalSeed;
private Random random; private Random random;
private Randoms randoms; private Randoms randoms;
private Configuration config = Randoms.DEFAULT; private Configuration config = Randoms.DEFAULT;
Expand All @@ -54,6 +55,13 @@ public RandomRule withConfiguration( Randoms.Configuration config )
return this; return this;
} }


public RandomRule withSeedForAllTests( long seed )
{
hasGlobalSeed = true;
this.seed = seed;
return this;
}

@Override @Override
public Statement apply( final Statement base, Description description ) public Statement apply( final Statement base, Description description )
{ {
Expand All @@ -62,14 +70,17 @@ public Statement apply( final Statement base, Description description )
@Override @Override
public void evaluate() throws Throwable public void evaluate() throws Throwable
{ {
Seed methodSeed = description.getAnnotation( Seed.class ); if ( !hasGlobalSeed )
if ( methodSeed != null )
{
seed = methodSeed.value();
}
else
{ {
seed = currentTimeMillis(); Seed methodSeed = description.getAnnotation( Seed.class );
if ( methodSeed != null )
{
seed = methodSeed.value();
}
else
{
seed = currentTimeMillis();
}
} }
reset(); reset();
try try
Expand Down
Expand Up @@ -129,8 +129,9 @@ public int hashCode()
@Override @Override
public String toString() public String toString()
{ {
return format( "IndexEntryUpdate[id=%d, mode=%s, %s, values=%s]", entityId, updateMode, indexKey().schema() return format( "IndexEntryUpdate[id=%d, mode=%s, %s, beforeValues=%s, values=%s]", entityId, updateMode,
.userDescription( SchemaUtil.idTokenNameLookup ), Arrays.toString(values) ); indexKey().schema().userDescription( SchemaUtil.idTokenNameLookup ),
Arrays.toString( before ), Arrays.toString( values ) );
} }


public static <INDEX_KEY extends LabelSchemaSupplier> IndexEntryUpdate<INDEX_KEY> add( public static <INDEX_KEY extends LabelSchemaSupplier> IndexEntryUpdate<INDEX_KEY> add(
Expand Down
Expand Up @@ -22,7 +22,6 @@
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;

import org.neo4j.collection.primitive.PrimitiveLongIterator; import org.neo4j.collection.primitive.PrimitiveLongIterator;
import org.neo4j.cursor.RawCursor; import org.neo4j.cursor.RawCursor;
import org.neo4j.graphdb.ResourceIterator; import org.neo4j.graphdb.ResourceIterator;
Expand All @@ -38,6 +37,8 @@
import org.neo4j.kernel.api.index.IndexUpdater; import org.neo4j.kernel.api.index.IndexUpdater;
import org.neo4j.kernel.api.index.PropertyAccessor; import org.neo4j.kernel.api.index.PropertyAccessor;
import org.neo4j.kernel.api.schema.IndexQuery; import org.neo4j.kernel.api.schema.IndexQuery;
import org.neo4j.kernel.api.schema.IndexQuery.ExactPredicate;
import org.neo4j.kernel.api.schema.IndexQuery.NumberRangePredicate;
import org.neo4j.kernel.impl.api.index.IndexUpdateMode; import org.neo4j.kernel.impl.api.index.IndexUpdateMode;
import org.neo4j.storageengine.api.schema.IndexReader; import org.neo4j.storageengine.api.schema.IndexReader;
import org.neo4j.storageengine.api.schema.IndexSampler; import org.neo4j.storageengine.api.schema.IndexSampler;
Expand Down Expand Up @@ -123,11 +124,12 @@ private class NativeSchemaNumberIndexReader implements IndexReader
{ {
private final KEY treeKeyFrom = layout.newKey(); private final KEY treeKeyFrom = layout.newKey();
private final KEY treeKeyTo = layout.newKey(); private final KEY treeKeyTo = layout.newKey();
private RawCursor<Hit<KEY,VALUE>,IOException> openSeeker;


@Override @Override
public void close() public void close()
{ {
throw new UnsupportedOperationException( "Implement me" ); ensureOpenSeekerClosed();
} }


@Override @Override
Expand Down Expand Up @@ -162,7 +164,66 @@ public IndexSampler createSampler()
@Override @Override
public PrimitiveLongIterator query( IndexQuery... predicates ) throws IndexNotApplicableKernelException public PrimitiveLongIterator query( IndexQuery... predicates ) throws IndexNotApplicableKernelException
{ {
throw new UnsupportedOperationException( "Implement me" ); if ( predicates.length != 1 )
{
throw new UnsupportedOperationException();
}

ensureOpenSeekerClosed();
IndexQuery predicate = predicates[0];
switch ( predicate.type() )
{
case exists:
treeKeyFrom.initAsLowest();
treeKeyTo.initAsHighest();
return startSeekForInitializedRange();
case exact:
ExactPredicate exactPredicate = (ExactPredicate) predicate;
Object[] values = new Object[] {exactPredicate.value()};
treeKeyFrom.from( Long.MIN_VALUE, values );
treeKeyTo.from( Long.MAX_VALUE, values );
return startSeekForInitializedRange();
case rangeNumeric:
NumberRangePredicate rangePredicate = (NumberRangePredicate) predicate;
treeKeyFrom.from( rangePredicate.fromInclusive() ? Long.MIN_VALUE : Long.MAX_VALUE,
new Object[] {rangePredicate.from()} );
treeKeyFrom.entityIdIsSpecialTieBreaker = true;
treeKeyTo.from( rangePredicate.toInclusive() ? Long.MAX_VALUE : Long.MIN_VALUE,
new Object[] {rangePredicate.to()} );
treeKeyTo.entityIdIsSpecialTieBreaker = true;
return startSeekForInitializedRange();
default:
throw new IllegalArgumentException( "IndexQuery of type " + predicate.type() + " is not supported." );
}
}

private PrimitiveLongIterator startSeekForInitializedRange()
{
try
{
openSeeker = tree.seek( treeKeyFrom, treeKeyTo );
return new NumberHitIterator<>( openSeeker );
}
catch ( IOException e )
{
throw new UncheckedIOException( e );
}
}

private void ensureOpenSeekerClosed()
{
if ( openSeeker != null )
{
try
{
openSeeker.close();
}
catch ( IOException e )
{
throw new UncheckedIOException( e );
}
openSeeker = null;
}
} }
} }
} }
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.impl.index.schema;

import java.io.IOException;
import java.io.UncheckedIOException;

import org.neo4j.collection.primitive.PrimitiveLongCollections;
import org.neo4j.collection.primitive.PrimitiveLongIterator;
import org.neo4j.cursor.RawCursor;
import org.neo4j.index.internal.gbptree.Hit;

/**
* Wraps number key/value results in a {@link PrimitiveLongIterator}.
* The {@link RawCursor seeker} which gets passed in will have to be closed somewhere else because
* the {@link PrimitiveLongIterator} is just a plain iterator, no resource.
*
* @param <KEY> type of {@link NumberKey}.
* @param <VALUE> type of {@link NumberValue}.
*/
public class NumberHitIterator<KEY extends NumberKey, VALUE extends NumberValue>
extends PrimitiveLongCollections.PrimitiveLongBaseIterator
{
private final RawCursor<Hit<KEY,VALUE>,IOException> seeker;

public NumberHitIterator( RawCursor<Hit<KEY,VALUE>,IOException> seeker )
{
this.seeker = seeker;
}

@Override
protected boolean fetchNext()
{
try
{
return seeker.next() ? next( seeker.get().key().entityId ) : false;
}
catch ( IOException e )
{
throw new UncheckedIOException( e );
}
}
}
Expand Up @@ -40,20 +40,19 @@ class NumberKey
long entityId; long entityId;


/** /**
* Explicitly defines this key as higher than any other if {@code true}. * Marks that comparisons with this key requires also comparing entityId, this allows functionality
* The {@link GBPTree} only supports exclusive end of range for range scans which means highest possible * of inclusive/exclusive bounds of range queries.
* double value can not be found in the tree because it will always fall outside of range. * This is because {@link GBPTree} only support from inclusive and to exclusive.
* But if {@code isHighest} is used as end of range, it will be effectively unbound and thus include all values.
* <p> * <p>
* Note that {@code isHighest} is only an in memory state. * Note that {@code entityIdIsSpecialTieBreaker} is only an in memory state.
*/ */
boolean isHighest; boolean entityIdIsSpecialTieBreaker;


public void from( long entityId, Value[] values ) public void from( long entityId, Value[] values )
{ {
this.value = assertValidSingleNumber( values ).doubleValue(); this.value = assertValidSingleNumber( values ).doubleValue();
this.entityId = entityId; this.entityId = entityId;
this.isHighest = false; entityIdIsSpecialTieBreaker = false;
} }


String propertiesAsString() String propertiesAsString()
Expand All @@ -65,14 +64,14 @@ void initAsLowest()
{ {
value = Double.NEGATIVE_INFINITY; value = Double.NEGATIVE_INFINITY;
entityId = Long.MIN_VALUE; entityId = Long.MIN_VALUE;
isHighest = false; entityIdIsSpecialTieBreaker = true;
} }


void initAsHighest() void initAsHighest()
{ {
value = Double.POSITIVE_INFINITY; value = Double.POSITIVE_INFINITY;
entityId = Long.MAX_VALUE; entityId = Long.MAX_VALUE;
isHighest = true; entityIdIsSpecialTieBreaker = true;
} }


@Override @Override
Expand Down
Expand Up @@ -39,7 +39,7 @@ public NumberKey copyKey( NumberKey key,
{ {
into.value = key.value; into.value = key.value;
into.entityId = key.entityId; into.entityId = key.entityId;
into.isHighest = key.isHighest; into.entityIdIsSpecialTieBreaker = key.entityIdIsSpecialTieBreaker;
return into; return into;
} }


Expand Down
Expand Up @@ -41,15 +41,10 @@ public int compare( NumberKey o1, NumberKey o2 )
int comparison = Double.compare( o1.value, o2.value ); int comparison = Double.compare( o1.value, o2.value );
if ( comparison == 0 ) if ( comparison == 0 )
{ {
// This is a special case where we need to check if o2 is the unbounded max key. // This is a special case where we need also compare entityId support inclusive/exclusive
// This needs to be checked to be able to support storing Double.MAX_VALUE and retrieve it later. if ( o1.entityIdIsSpecialTieBreaker || o2.entityIdIsSpecialTieBreaker )
if ( o2.isHighest )
{ {
return -1; return Long.compare( o1.entityId, o2.entityId );
}
if ( o1.isHighest )
{
return 1;
} }
} }
return comparison; return comparison;
Expand Down
Expand Up @@ -49,7 +49,7 @@ abstract class LayoutTestUtil<KEY extends NumberKey, VALUE extends NumberValue>


abstract Layout<KEY,VALUE> createLayout(); abstract Layout<KEY,VALUE> createLayout();


abstract IndexEntryUpdate[] someUpdates(); abstract IndexEntryUpdate<IndexDescriptor>[] someUpdates();


protected abstract double fractionDuplicates(); protected abstract double fractionDuplicates();


Expand Down Expand Up @@ -90,7 +90,7 @@ int compareIndexedPropertyValue( NumberValue value1, NumberValue value2 )
return typeCompare; return typeCompare;
} }


Iterator<IndexEntryUpdate<IndexDescriptor>> randomUniqueUpdateGenerator( RandomRule random ) Iterator<IndexEntryUpdate<IndexDescriptor>> randomUpdateGenerator( RandomRule random )
{ {
double fractionDuplicates = fractionDuplicates(); double fractionDuplicates = fractionDuplicates();
return new PrefetchingIterator<IndexEntryUpdate<IndexDescriptor>>() return new PrefetchingIterator<IndexEntryUpdate<IndexDescriptor>>()
Expand Down Expand Up @@ -137,21 +137,20 @@ private Number existingNonUniqueValue( RandomRule randomRule )
}; };
} }


@SuppressWarnings( "rawtypes" ) IndexEntryUpdate<IndexDescriptor>[] someUpdatesNoDuplicateValues()
IndexEntryUpdate[] someUpdatesNoDuplicateValues()
{ {
return generateAddUpdatesFor( ALL_EXTREME_VALUES ); return generateAddUpdatesFor( ALL_EXTREME_VALUES );
} }


@SuppressWarnings( "rawtypes" ) IndexEntryUpdate<IndexDescriptor>[] someUpdatesWithDuplicateValues()
IndexEntryUpdate[] someUpdatesWithDuplicateValues()
{ {
return generateAddUpdatesFor( ArrayUtils.addAll( ALL_EXTREME_VALUES, ALL_EXTREME_VALUES ) ); return generateAddUpdatesFor( ArrayUtils.addAll( ALL_EXTREME_VALUES, ALL_EXTREME_VALUES ) );
} }


private IndexEntryUpdate[] generateAddUpdatesFor( Number[] values ) private IndexEntryUpdate<IndexDescriptor>[] generateAddUpdatesFor( Number[] values )
{ {
IndexEntryUpdate[] indexEntryUpdates = new IndexEntryUpdate[values.length]; @SuppressWarnings( "unchecked" )
IndexEntryUpdate<IndexDescriptor>[] indexEntryUpdates = new IndexEntryUpdate[values.length];
for ( int i = 0; i < indexEntryUpdates.length; i++ ) for ( int i = 0; i < indexEntryUpdates.length; i++ )
{ {
indexEntryUpdates[i] = add( i, values[i] ); indexEntryUpdates[i] = add( i, values[i] );
Expand Down
Expand Up @@ -370,15 +370,15 @@ public void shouldApplyLargeAmountOfInterleavedRandomUpdates() throws Exception
populator.create(); populator.create();
random.reset(); random.reset();
Random updaterRandom = new Random( random.seed() ); Random updaterRandom = new Random( random.seed() );
Iterator<IndexEntryUpdate<IndexDescriptor>> updates = layoutUtil.randomUniqueUpdateGenerator( random ); Iterator<IndexEntryUpdate<IndexDescriptor>> updates = layoutUtil.randomUpdateGenerator( random );


// when // when
int count = interleaveLargeAmountOfUpdates( updaterRandom, updates ); int count = interleaveLargeAmountOfUpdates( updaterRandom, updates );


// then // then
populator.close( true ); populator.close( true );
random.reset(); random.reset();
verifyUpdates( layoutUtil.randomUniqueUpdateGenerator( random ), count ); verifyUpdates( layoutUtil.randomUpdateGenerator( random ), count );
} }


@Test @Test
Expand Down

0 comments on commit 5acded4

Please sign in to comment.