diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/RecoveryCleanupWorkCollector.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/RecoveryCleanupWorkCollector.java index 9ede1c13306cb..3d3e0822764e5 100644 --- a/community/index/src/main/java/org/neo4j/index/internal/gbptree/RecoveryCleanupWorkCollector.java +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/RecoveryCleanupWorkCollector.java @@ -63,6 +63,9 @@ public void add( CleanupJob job ) } } + /** + * {@link RecoveryCleanupWorkCollector} ignoring all {@link CleanupJob} added to it. + */ class NullRecoveryCleanupWorkCollector extends LifecycleAdapter implements RecoveryCleanupWorkCollector { @Override diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/ValueMerger.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/ValueMerger.java index d741ad289573d..da7d155ec6ac7 100644 --- a/community/index/src/main/java/org/neo4j/index/internal/gbptree/ValueMerger.java +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/ValueMerger.java @@ -23,6 +23,7 @@ * Decides what to do when inserting key which already exists in index. Different implementations of * {@link ValueMerger} can result in unique/non-unique indexes for example. * + * @param type of keys to merge. * @param type of values to merge. */ public interface ValueMerger diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NativeSchemaNumberIndexAccessor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NativeSchemaNumberIndexAccessor.java index ab261e72d8541..53feb505cc99f 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NativeSchemaNumberIndexAccessor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NativeSchemaNumberIndexAccessor.java @@ -21,15 +21,18 @@ import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import org.neo4j.collection.primitive.PrimitiveLongIterator; -import org.neo4j.collection.primitive.PrimitiveLongSet; import org.neo4j.graphdb.ResourceIterator; import org.neo4j.helpers.collection.BoundedIterable; +import org.neo4j.index.internal.gbptree.Layout; +import org.neo4j.index.internal.gbptree.RecoveryCleanupWorkCollector; +import org.neo4j.io.pagecache.IOLimiter; +import org.neo4j.io.pagecache.PageCache; import org.neo4j.kernel.api.exceptions.index.IndexEntryConflictException; import org.neo4j.kernel.api.exceptions.index.IndexNotApplicableKernelException; import org.neo4j.kernel.api.index.IndexAccessor; -import org.neo4j.kernel.api.index.IndexEntryUpdate; import org.neo4j.kernel.api.index.IndexUpdater; import org.neo4j.kernel.api.index.PropertyAccessor; import org.neo4j.kernel.api.schema.IndexQuery; @@ -37,8 +40,19 @@ import org.neo4j.storageengine.api.schema.IndexReader; import org.neo4j.storageengine.api.schema.IndexSampler; -public class NativeSchemaNumberIndexAccessor implements IndexAccessor +public class NativeSchemaNumberIndexAccessor + extends NativeSchemaNumberIndex implements IndexAccessor { + private final NativeSchemaNumberIndexUpdater singleUpdater; + + NativeSchemaNumberIndexAccessor( PageCache pageCache, File storeFile, + Layout layout, RecoveryCleanupWorkCollector recoveryCleanupWorkCollector ) throws IOException + { + super( pageCache, storeFile, layout ); + singleUpdater = new NativeSchemaNumberIndexUpdater<>( layout.newKey(), layout.newValue() ); + instantiateTree( recoveryCleanupWorkCollector ); + } + @Override public void drop() throws IOException { @@ -48,25 +62,34 @@ public void drop() throws IOException @Override public IndexUpdater newUpdater( IndexUpdateMode mode ) { - return new NativeSchemaNumberIndexUpdater(); + try + { + return singleUpdater.initialize( tree.writer(), true ); + } + catch ( IOException e ) + { + throw new UncheckedIOException( e ); + } } @Override public void flush() throws IOException { + // todo remove this from interface completely throw new UnsupportedOperationException( "Implement me" ); } @Override public void force() throws IOException { - throw new UnsupportedOperationException( "Implement me" ); + // TODO add IOLimiter arg + tree.checkpoint( IOLimiter.unlimited() ); } @Override public void close() throws IOException { - throw new UnsupportedOperationException( "Implement me" ); + closeTree(); } @Override @@ -94,31 +117,8 @@ public void verifyDeferredConstraints( PropertyAccessor propertyAccessor ) throw new UnsupportedOperationException( "Implement me" ); } - private class NativeSchemaNumberIndexUpdater implements IndexUpdater - { - - @Override - public void process( IndexEntryUpdate update ) throws IOException, IndexEntryConflictException - { - throw new UnsupportedOperationException( "Implement me" ); - } - - @Override - public void close() throws IOException, IndexEntryConflictException - { - throw new UnsupportedOperationException( "Implement me" ); - } - - @Override - public void remove( PrimitiveLongSet nodeIds ) throws IOException - { - throw new UnsupportedOperationException( "Implement me" ); - } - } - private class NativeSchemaNumberIndexReader implements IndexReader { - @Override public void close() { diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NativeSchemaNumberIndexUpdater.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NativeSchemaNumberIndexUpdater.java index 0654d2a4226ac..1b296a2b0135f 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NativeSchemaNumberIndexUpdater.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NativeSchemaNumberIndexUpdater.java @@ -72,7 +72,8 @@ public void process( IndexEntryUpdate update ) throws IOException, IndexEntryCon processChange( treeKey, treeValue, update, writer, conflictDetectingValueMerger ); break; case REMOVED: - throw new UnsupportedOperationException( "Implement me" ); + processRemove( treeKey, update, writer ); + break; default: throw new IllegalArgumentException(); } @@ -102,29 +103,38 @@ private void assertOpen() } } + private static void processRemove( KEY treeKey, + IndexEntryUpdate update, Writer writer ) throws IOException + { + // todo Do we need to verify that we actually removed something at all? + // todo Difference between online and recovery? + treeKey.from( update.getEntityId(), update.values() ); + writer.remove( treeKey ); + } + private static void processChange( KEY treeKey, VALUE treeValue, - IndexEntryUpdate update, Writer singleWriter, + IndexEntryUpdate update, Writer writer, ConflictDetectingValueMerger conflictDetectingValueMerger ) throws IOException, IndexEntryConflictException { // Remove old entry treeKey.from( update.getEntityId(), update.beforeValues() ); - singleWriter.remove( treeKey ); - // Insert new entrty + writer.remove( treeKey ); + // Insert new entry treeKey.from( update.getEntityId(), update.values() ); treeValue.from( update.values() ); - singleWriter.merge( treeKey, treeValue, conflictDetectingValueMerger ); + writer.merge( treeKey, treeValue, conflictDetectingValueMerger ); assertNoConflict( update, conflictDetectingValueMerger ); } static void processAdd( KEY treeKey, VALUE treeValue, - IndexEntryUpdate update, Writer singleWriter, + IndexEntryUpdate update, Writer writer, ConflictDetectingValueMerger conflictDetectingValueMerger ) throws IOException, IndexEntryConflictException { treeKey.from( update.getEntityId(), update.values() ); treeValue.from( update.values() ); - singleWriter.merge( treeKey, treeValue, conflictDetectingValueMerger ); + writer.merge( treeKey, treeValue, conflictDetectingValueMerger ); assertNoConflict( update, conflictDetectingValueMerger ); } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NumberValue.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NumberValue.java index fb59b7e8b86d1..de09407a47520 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NumberValue.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NumberValue.java @@ -22,6 +22,8 @@ import org.neo4j.index.internal.gbptree.GBPTree; import org.neo4j.values.Value; +import static org.neo4j.kernel.impl.index.schema.NumberValueConversion.toValue; + /** * Value in a {@link GBPTree} handling numbers suitable for schema indexing. * Contains actual number for internal filtering after accidental query hits due to double value coersion. @@ -72,4 +74,10 @@ else if ( value instanceof Float ) rawValueBits = value.longValue(); } } + + @Override + public String toString() + { + return "type=" + type + ",rawValue=" + rawValueBits + ",value=" + toValue( type, rawValueBits ); + } } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeNonUniqueSchemaNumberIndexAccessorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeNonUniqueSchemaNumberIndexAccessorTest.java new file mode 100644 index 0000000000000..a42d3d8acf585 --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeNonUniqueSchemaNumberIndexAccessorTest.java @@ -0,0 +1,30 @@ +/* + * 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 . + */ +package org.neo4j.kernel.impl.index.schema; + +public class NativeNonUniqueSchemaNumberIndexAccessorTest + extends NativeSchemaNumberIndexAccessorTest +{ + @Override + protected LayoutTestUtil createLayoutTestUtil() + { + return new NonUniqueLayoutTestUtil(); + } +} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeNonUniqueSchemaNumberIndexPopulatorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeNonUniqueSchemaNumberIndexPopulatorTest.java index ac953e9e38937..c021b79a36880 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeNonUniqueSchemaNumberIndexPopulatorTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeNonUniqueSchemaNumberIndexPopulatorTest.java @@ -94,7 +94,7 @@ public void shouldApplyLargeAmountOfInterleavedRandomUpdatesWithDuplicates() thr populator.create(); random.reset(); Random updaterRandom = new Random( random.seed() ); - Iterator> updates = randomUniqueUpdateGenerator( random, 0.1f ); + Iterator> updates = randomUniqueUpdateGenerator( 0.1f ); // when int count = interleaveLargeAmountOfUpdates( updaterRandom, updates ); @@ -102,7 +102,7 @@ public void shouldApplyLargeAmountOfInterleavedRandomUpdatesWithDuplicates() thr // then populator.close( true ); random.reset(); - verifyUpdates( randomUniqueUpdateGenerator( random, 0.1f ), count ); + verifyUpdates( randomUniqueUpdateGenerator( 0.1f ), count ); } @Test diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeSchemaIndexPopulatorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeSchemaIndexPopulatorTest.java index c457c5d0da4d5..b638421915af7 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeSchemaIndexPopulatorTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeSchemaIndexPopulatorTest.java @@ -26,17 +26,13 @@ import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.Random; -import java.util.Set; -import org.neo4j.helpers.collection.PrefetchingIterator; import org.neo4j.index.internal.gbptree.GBPTree; import org.neo4j.index.internal.gbptree.Header; import org.neo4j.index.internal.gbptree.Layout; @@ -51,8 +47,6 @@ import org.neo4j.kernel.api.schema.index.IndexDescriptor; import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.impl.api.index.sampling.IndexSamplingConfig; -import org.neo4j.test.rule.RandomRule; -import org.neo4j.values.Values; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -376,7 +370,7 @@ public void shouldApplyLargeAmountOfInterleavedRandomUpdates() throws Exception populator.create(); random.reset(); Random updaterRandom = new Random( random.seed() ); - Iterator> updates = randomUniqueUpdateGenerator( random, 0 ); + Iterator> updates = randomUniqueUpdateGenerator( 0 ); // when int count = interleaveLargeAmountOfUpdates( updaterRandom, updates ); @@ -384,7 +378,7 @@ public void shouldApplyLargeAmountOfInterleavedRandomUpdates() throws Exception // then populator.close( true ); random.reset(); - verifyUpdates( randomUniqueUpdateGenerator( random, 0 ), count ); + verifyUpdates( randomUniqueUpdateGenerator( 0 ), count ); } @Test @@ -524,53 +518,6 @@ int interleaveLargeAmountOfUpdates( Random updaterRandom, return count; } - Iterator> randomUniqueUpdateGenerator( RandomRule randomRule, - float fractionDuplicates ) - { - return new PrefetchingIterator>() - { - private final Set uniqueCompareValues = new HashSet<>(); - private final List uniqueValues = new ArrayList<>(); - private long currentEntityId; - - @Override - protected IndexEntryUpdate fetchNextOrNull() - { - Number value; - if ( fractionDuplicates > 0 && !uniqueValues.isEmpty() && - randomRule.nextFloat() < fractionDuplicates ) - { - value = existingNonUniqueValue( randomRule ); - } - else - { - value = newUniqueValue( randomRule ); - } - - return add( currentEntityId++, value ); - } - - private Number newUniqueValue( RandomRule randomRule ) - { - Number value; - Double compareValue; - do - { - value = randomRule.numberPropertyValue(); - compareValue = value.doubleValue(); - } - while ( !uniqueCompareValues.add( compareValue ) ); - uniqueValues.add( value ); - return value; - } - - private Number existingNonUniqueValue( RandomRule randomRule ) - { - return uniqueValues.get( randomRule.nextInt( uniqueValues.size() ) ); - } - }; - } - private void assertHeader( boolean online, String failureMessage, boolean messageTruncated ) throws IOException { NativeSchemaIndexHeaderReader headerReader = new NativeSchemaIndexHeaderReader(); diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeSchemaNumberIndexAccessorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeSchemaNumberIndexAccessorTest.java new file mode 100644 index 0000000000000..ce090ef8600b3 --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeSchemaNumberIndexAccessorTest.java @@ -0,0 +1,347 @@ +/* + * 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 . + */ +package org.neo4j.kernel.impl.index.schema; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.neo4j.collection.primitive.Primitive; +import org.neo4j.kernel.api.exceptions.index.IndexEntryConflictException; +import org.neo4j.kernel.api.index.IndexEntryUpdate; +import org.neo4j.kernel.api.index.IndexUpdater; +import org.neo4j.kernel.api.schema.index.IndexDescriptor; +import org.neo4j.kernel.api.schema.index.IndexDescriptorFactory; +import static org.junit.Assert.fail; +import static org.neo4j.index.internal.gbptree.RecoveryCleanupWorkCollector.IMMEDIATE; +import static org.neo4j.kernel.api.index.IndexEntryUpdate.change; +import static org.neo4j.kernel.api.index.IndexEntryUpdate.remove; +import static org.neo4j.kernel.impl.api.index.IndexUpdateMode.ONLINE; + +/** + * Tests for + *
    + *
  • {@link NativeSchemaNumberIndexAccessor}
  • + *
  • {@link NativeSchemaNumberIndexUpdater}
  • + *
  • {@link NativeSchemaNumberIndexAccessor.NativeSchemaNumberIndexReader}
  • + *
+ */ +public abstract class NativeSchemaNumberIndexAccessorTest + extends SchemaNumberIndexTestUtil +{ + private static final IndexDescriptor indexDescriptor = IndexDescriptorFactory.forLabel( 42, 666 ); + private NativeSchemaNumberIndexAccessor accessor; + + @Before + public void setupAccessor() throws IOException + { + accessor = new NativeSchemaNumberIndexAccessor<>( pageCache, indexFile, layout, IMMEDIATE ); + } + + @After + public void closeAccessor() throws IOException + { + accessor.close(); + } + + // UPDATER + + @Test + public void shouldHandleCloseWithoutCallsToProcess() throws Exception + { + // given + IndexUpdater updater = accessor.newUpdater( ONLINE ); + + // when + updater.close(); + + // then + // ... should be fine + } + + @Test + public void processMustThrowAfterClose() throws Exception + { + // given + IndexUpdater updater = accessor.newUpdater( ONLINE ); + updater.close(); + + // when + try + { + updater.process( simpleUpdate() ); + fail( "Should have failed" ); + } + catch ( IllegalStateException e ) + { + // then good + } + } + + @Test + public void removeMustThrowAfterClose() throws Exception + { + // given + IndexUpdater updater = accessor.newUpdater( ONLINE ); + updater.close(); + + // when + try + { + updater.remove( Primitive.longSet() ); + fail( "Should have failed" ); + } + catch ( IllegalStateException e ) + { + // then good + } + } + + @Test + public void shouldIndexAdd() throws Exception + { + // given + @SuppressWarnings( "unchecked" ) + IndexEntryUpdate[] updates = someIndexEntryUpdates(); + try ( IndexUpdater updater = accessor.newUpdater( ONLINE ) ) + { + // when + processAll( updater, updates ); + } + + // then + forceAndCloseAccessor(); + verifyUpdates( updates ); + } + + @Test + public void shouldIndexChange() throws Exception + { + // given + @SuppressWarnings( "unchecked" ) + IndexEntryUpdate[] updates = someIndexEntryUpdates(); + processAll( updates ); + + for ( int i = 0; i < updates.length; i++ ) + { + IndexEntryUpdate update = updates[i]; + Object newValue; + switch ( i % 3 ) + { + case 0: + newValue = (long) i; + break; + case 1: + newValue = (float) i; + break; + case 2: + newValue = (double) i; + break; + default: + throw new IllegalArgumentException(); + } + updates[i] = change( update.getEntityId(), indexDescriptor, update.values()[0], newValue ); + } + + // when + processAll( updates ); + + // then + forceAndCloseAccessor(); + verifyUpdates( updates ); + } + + @Test + public void shouldIndexRemove() throws Exception + { + // given + @SuppressWarnings( "unchecked" ) + IndexEntryUpdate[] updates = someIndexEntryUpdates(); + processAll( updates ); + + for ( int i = 0; i < updates.length; i++ ) + { + // when + IndexEntryUpdate update = updates[i]; + IndexEntryUpdate remove = remove( update.getEntityId(), indexDescriptor, update.values() ); + processAll( remove ); + forceAndCloseAccessor(); + + // then + verifyUpdates( Arrays.copyOfRange( updates, i + 1, updates.length ) ); + setupAccessor(); + } + } + + @SuppressWarnings( "unchecked" ) + @Test + public void shouldHandleRandomUpdates() throws Exception + { + // given + Set> expectedData = new HashSet<>(); + Iterator> newDataGenerator = randomUniqueUpdateGenerator( 0 ); + + // when + int rounds = 50; + for ( int round = 0; round < rounds; round++ ) + { + // generate a batch of updates (add, change, remove) + IndexEntryUpdate[] batch = generateRandomUpdates( expectedData, newDataGenerator, + random.nextInt( 5, 20 ), (float) round / rounds * 2 ); + // apply to tree + processAll( batch ); + // apply to expectedData + applyUpdatesToExpectedData( expectedData, batch ); + // verifyUpdates + forceAndCloseAccessor(); + verifyUpdates( expectedData.toArray( new IndexEntryUpdate[expectedData.size()] ) ); + setupAccessor(); + } + } + + private void applyUpdatesToExpectedData( Set> expectedData, + IndexEntryUpdate[] batch ) + { + for ( IndexEntryUpdate update : batch ) + { + IndexEntryUpdate addition = null; + IndexEntryUpdate removal = null; + switch ( update.updateMode() ) + { + case ADDED: + addition = add( update.getEntityId(), update.values()[0] ); + break; + case CHANGED: + addition = add( update.getEntityId(), update.values()[0] ); + removal = add( update.getEntityId(), update.beforeValues()[0] ); + break; + case REMOVED: + removal = add( update.getEntityId(), update.values()[0] ); + break; + default: + throw new IllegalArgumentException( update.updateMode().name() ); + } + + if ( addition != null ) + { + expectedData.add( addition ); + } + if ( removal != null ) + { + expectedData.remove( removal ); + } + } + } + + private IndexEntryUpdate[] generateRandomUpdates( Set> expectedData, + Iterator> newDataGenerator, int count, float removeFactor ) + { + @SuppressWarnings( "unchecked" ) + IndexEntryUpdate[] updates = new IndexEntryUpdate[count]; + float addChangeRatio = 0.5f; + for ( int i = 0; i < count; i++ ) + { + float factor = random.nextFloat(); + if ( !expectedData.isEmpty() && factor < removeFactor ) + { + // remove something + IndexEntryUpdate toRemove = selectRandomItem( expectedData ); + updates[i] = remove( toRemove.getEntityId(), indexDescriptor, toRemove.values() ); + } + else if ( !expectedData.isEmpty() && factor < (1 - removeFactor) * addChangeRatio ) + { + // change + IndexEntryUpdate toChange = selectRandomItem( expectedData ); + // use the data generator to generate values, even if the whole update as such won't be used + IndexEntryUpdate updateContainingValue = newDataGenerator.next(); + updates[i] = change( toChange.getEntityId(), indexDescriptor, toChange.values(), + updateContainingValue.values() ); + } + else + { + // add + updates[i] = newDataGenerator.next(); + } + } + return updates; + } + + @SuppressWarnings( "unchecked" ) + private IndexEntryUpdate selectRandomItem( Set> expectedData ) + { + return expectedData.toArray( new IndexEntryUpdate[expectedData.size()] )[random.nextInt( expectedData.size() )]; + } + + @SafeVarargs + private final void processAll( IndexEntryUpdate... updates ) + throws IOException, IndexEntryConflictException + { + try ( IndexUpdater updater = accessor.newUpdater( ONLINE ) ) + { + processAll( updater, updates ); + } + } + + private void forceAndCloseAccessor() throws IOException + { + accessor.force(); + closeAccessor(); + } + + private void processAll( IndexUpdater updater, IndexEntryUpdate[] updates ) + throws IOException, IndexEntryConflictException + { + for ( IndexEntryUpdate update : updates ) + { + updater.process( update ); + } + } + + private IndexEntryUpdate simpleUpdate() + { + return IndexEntryUpdate.add( 0, indexDescriptor, 0 ); + } + + // READER + +// long countIndexedNodes( long nodeId, Object... propertyValues ) +// IndexSampler createSampler() +// PrimitiveLongIterator query( IndexQuery... predicates ) + + // ACCESSOR + // todo shouldHandleMultipleConsecutiveUpdaters + // todo requestForSecondUpdaterMustThrow + +// void drop() +// IndexUpdater newUpdater( IndexUpdateMode mode ) +// void flush() +// void force() +// void close() +// IndexReader newReader() +// BoundedIterable newAllEntriesReader() +// ResourceIterator snapshotFiles() +// void verifyDeferredConstraints( PropertyAccessor propertyAccessor ) +} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeUniqueSchemaNumberIndexAccessorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeUniqueSchemaNumberIndexAccessorTest.java new file mode 100644 index 0000000000000..ae9e0b75caa71 --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeUniqueSchemaNumberIndexAccessorTest.java @@ -0,0 +1,30 @@ +/* + * 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 . + */ +package org.neo4j.kernel.impl.index.schema; + +public class NativeUniqueSchemaNumberIndexAccessorTest + extends NativeSchemaNumberIndexAccessorTest +{ + @Override + protected LayoutTestUtil createLayoutTestUtil() + { + return new UniqueLayoutTestUtil(); + } +} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeUniqueSchemaNumberIndexPopulatorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeUniqueSchemaNumberIndexPopulatorTest.java index c037713219d1f..cb180e9c86d8b 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeUniqueSchemaNumberIndexPopulatorTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/NativeUniqueSchemaNumberIndexPopulatorTest.java @@ -114,7 +114,7 @@ public void shouldThrowOnLargeAmountOfInterleavedRandomUpdatesWithDuplicates() t populator.create(); random.reset(); Random updaterRandom = new Random( random.seed() ); - Iterator> updates = randomUniqueUpdateGenerator( random, 0.01f ); + Iterator> updates = randomUniqueUpdateGenerator( 0.01f ); Number failSafeDuplicateValue = 12345.6789D; // when diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/SchemaNumberIndexTestUtil.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/SchemaNumberIndexTestUtil.java index 807e40870f23a..3f52c7a9bb33f 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/SchemaNumberIndexTestUtil.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/SchemaNumberIndexTestUtil.java @@ -28,10 +28,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.Set; import org.neo4j.cursor.RawCursor; +import org.neo4j.helpers.collection.PrefetchingIterator; import org.neo4j.index.internal.gbptree.GBPTree; import org.neo4j.index.internal.gbptree.Hit; import org.neo4j.index.internal.gbptree.Layout; @@ -53,6 +57,8 @@ import static org.neo4j.index.internal.gbptree.GBPTree.NO_HEADER_READER; import static org.neo4j.index.internal.gbptree.GBPTree.NO_HEADER_WRITER; +import static java.lang.String.format; + import static org.neo4j.test.rule.PageCacheRule.config; public abstract class SchemaNumberIndexTestUtil @@ -132,6 +138,52 @@ GBPTree getTree() throws IOException NO_HEADER_READER, NO_HEADER_WRITER, RecoveryCleanupWorkCollector.IMMEDIATE ); } + Iterator> randomUniqueUpdateGenerator( float fractionDuplicates ) + { + return new PrefetchingIterator>() + { + private final Set uniqueCompareValues = new HashSet<>(); + private final List uniqueValues = new ArrayList<>(); + private long currentEntityId; + + @Override + protected IndexEntryUpdate fetchNextOrNull() + { + Number value; + if ( fractionDuplicates > 0 && !uniqueValues.isEmpty() && + random.nextFloat() < fractionDuplicates ) + { + value = existingNonUniqueValue( random ); + } + else + { + value = newUniqueValue( random ); + } + + return add( currentEntityId++, value ); + } + + private Number newUniqueValue( RandomRule randomRule ) + { + Number value; + Double compareValue; + do + { + value = randomRule.numberPropertyValue(); + compareValue = value.doubleValue(); + } + while ( !uniqueCompareValues.add( compareValue ) ); + uniqueValues.add( value ); + return value; + } + + private Number existingNonUniqueValue( RandomRule randomRule ) + { + return uniqueValues.get( randomRule.nextInt( uniqueValues.size() ) ); + } + }; + } + private RawCursor, IOException> scan( GBPTree tree ) throws IOException { KEY lowest = layout.newKey(); @@ -146,7 +198,9 @@ private void assertSameHits( Hit[] expectedHits, Hit[] a { Arrays.sort( expectedHits, comparator ); Arrays.sort( actualHits, comparator ); - assertEquals( "Array length differ", expectedHits.length, actualHits.length ); + assertEquals( format( "Array length differ%nExpected:%s%nActual:%s", + Arrays.toString( expectedHits ), Arrays.toString( actualHits ) ), + expectedHits.length, actualHits.length ); for ( int i = 0; i < expectedHits.length; i++ ) { @@ -239,6 +293,7 @@ public String toString() } } + @SuppressWarnings( "rawtypes" ) IndexEntryUpdate[] someIndexEntryUpdates() { return new IndexEntryUpdate[]{