From cd00c9e5f9e872352855b8e8d3f65236e2a94d27 Mon Sep 17 00:00:00 2001 From: Olivia Ytterbrink Date: Thu, 18 Jan 2018 15:33:02 +0100 Subject: [PATCH] Added spatial fusion tests --- .../impl/index/schema/SpatialKnownIndex.java | 2 +- .../fusion/SpatialFusionIndexPopulator.java | 5 +- .../fusion/SpatialFusionIndexReader.java | 21 +- .../fusion/SpatialFusionIndexUpdater.java | 7 +- .../SpatialFusionSchemaIndexProvider.java | 14 +- .../fusion/FusionIndexAccessorTest.java | 26 +- .../SpatialFusionIndexAccessorTest.java | 316 +++++++++++++++++ .../SpatialFusionIndexPopulatorTest.java | 320 ++++++++++++++++++ .../fusion/SpatialFusionIndexReaderTest.java | 190 +++++++++++ .../fusion/SpatialFusionIndexUpdaterTest.java | 275 +++++++++++++++ .../SpatialFusionSchemaIndexProviderTest.java | 135 ++++++++ 11 files changed, 1284 insertions(+), 27 deletions(-) create mode 100644 community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexAccessorTest.java create mode 100644 community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexPopulatorTest.java create mode 100644 community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexReaderTest.java create mode 100644 community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexUpdaterTest.java create mode 100644 community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionSchemaIndexProviderTest.java diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/SpatialKnownIndex.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/SpatialKnownIndex.java index c67bc3f4781ce..dce1e22289e33 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/SpatialKnownIndex.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/SpatialKnownIndex.java @@ -55,7 +55,7 @@ public class SpatialKnownIndex public interface Factory { - SpatialKnownIndex selectAndCreate( Map indexMap, long indexId, Value... values ); + SpatialKnownIndex selectAndCreate( Map indexMap, long indexId, Value value ); SpatialKnownIndex selectAndCreate( Map indexMap, long indexId, CoordinateReferenceSystem crs ); } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexPopulator.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexPopulator.java index b1b3b932429fa..be6e04742fccc 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexPopulator.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexPopulator.java @@ -128,7 +128,10 @@ public void markAsFailed( String failure ) throws IOException @Override public void includeSample( IndexEntryUpdate update ) { - indexFactory.selectAndCreate( indexMap, indexId, update.values() ).getPopulator( descriptor, samplingConfig ).includeSample( update ); + Value[] values = update.values(); + assert values.length == 1; + SpatialKnownIndex index = indexFactory.selectAndCreate( indexMap, indexId, values[0] ); + index.getPopulator( descriptor, samplingConfig ).includeSample( update ); } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexReader.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexReader.java index 2fa6b355d1f03..a7b0b39132e68 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexReader.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexReader.java @@ -21,6 +21,7 @@ import java.util.Map; +import org.neo4j.collection.primitive.PrimitiveLongResourceCollections; import org.neo4j.collection.primitive.PrimitiveLongResourceIterator; import org.neo4j.internal.kernel.api.IndexOrder; import org.neo4j.internal.kernel.api.IndexQuery; @@ -95,9 +96,23 @@ public IndexSampler createSampler() @Override public PrimitiveLongResourceIterator query( IndexQuery... predicates ) throws IndexNotApplicableKernelException { - NodeValueIterator nodeValueIterator = new NodeValueIterator(); - query( nodeValueIterator, IndexOrder.NONE, predicates ); - return nodeValueIterator; + if ( predicates[0] instanceof ExistsPredicate ) + { + PrimitiveLongResourceIterator[] iterators = new PrimitiveLongResourceIterator[readerMap.size()]; + int i = 0; + for ( IndexReader reader : readerMap.values() ) + { + iterators[i] = reader.query( predicates[0] ); + ++i; + } + return PrimitiveLongResourceCollections.concat( iterators ); + } + else + { + NodeValueIterator nodeValueIterator = new NodeValueIterator(); + query( nodeValueIterator, IndexOrder.NONE, predicates ); + return nodeValueIterator; + } } private IndexReader selectIf( IndexQuery... predicates ) throws IndexNotApplicableKernelException diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexUpdater.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexUpdater.java index 1c84901ab1272..a36611cf54baf 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexUpdater.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexUpdater.java @@ -35,6 +35,8 @@ import org.neo4j.values.storable.PointValue; import org.neo4j.values.storable.Value; +import static org.neo4j.kernel.impl.index.schema.fusion.FusionIndexUtils.forAll; + class SpatialFusionIndexUpdater implements IndexUpdater { private final Map indexMap; @@ -136,10 +138,7 @@ public void close() throws IOException, IndexEntryConflictException { while ( !currentUpdaters.isEmpty() ) { - for ( IndexUpdater updater : currentUpdaters.values() ) - { - updater.close(); - } + forAll( updater -> ((IndexUpdater) updater).close(), currentUpdaters.values().toArray() ); currentUpdaters.clear(); } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionSchemaIndexProvider.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionSchemaIndexProvider.java index 75c05ea24dae7..a6f2060648c9e 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionSchemaIndexProvider.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionSchemaIndexProvider.java @@ -91,7 +91,7 @@ public IndexAccessor getOnlineAccessor( long indexId, IndexDescriptor descriptor return new SpatialFusionIndexAccessor( indexesFor( indexId ), indexId, descriptor, samplingConfig, this ); } - private Map indexesFor( long indexId ) + Map indexesFor( long indexId ) { return indexes.computeIfAbsent( indexId, k -> new HashMap<>() ); } @@ -170,10 +170,9 @@ public StoreMigrationParticipant storeMigrationParticipant( FileSystemAbstractio } @Override - public SpatialKnownIndex selectAndCreate( Map indexMap, long indexId, Value... values ) + public SpatialKnownIndex selectAndCreate( Map indexMap, long indexId, Value value ) { - assert values.length == 1; - PointValue pointValue = (PointValue) values[0]; + PointValue pointValue = (PointValue) value; CoordinateReferenceSystem crs = pointValue.getCoordinateReferenceSystem(); return selectAndCreate( indexMap, indexId, crs ); } @@ -188,7 +187,12 @@ public SpatialKnownIndex selectAndCreate( Map result, long[] nativeEntries ) + static void assertResultContainsAll( Set result, long[] nativeEntries ) { for ( long nativeEntry : nativeEntries ) { @@ -425,42 +425,42 @@ private void assertResultContainsAll( Set result, long[] nativeEntries ) } } - private void mockAllEntriesReaders( long[] nativeEntries, long[] luceneEntries ) - { - mockSingleAllEntriesReader( nativeAccessor, nativeEntries ); - mockSingleAllEntriesReader( spatialAccessor, nativeEntries ); - mockSingleAllEntriesReader( luceneAccessor, luceneEntries ); - } - - private BoundedIterable mockSingleAllEntriesReader( IndexAccessor targetAccessor, long[] entries ) + static BoundedIterable mockSingleAllEntriesReader( IndexAccessor targetAccessor, long[] entries ) { BoundedIterable allEntriesReader = mockedAllEntriesReader( entries ); when( targetAccessor.newAllEntriesReader() ).thenReturn( allEntriesReader ); return allEntriesReader; } - private BoundedIterable mockedAllEntriesReader( long... entries ) + static BoundedIterable mockedAllEntriesReader( long... entries ) { return mockedAllEntriesReader( true, entries ); } - private BoundedIterable mockSingleAllEntriesReaderWithUnknownMaxCount( IndexAccessor targetAccessor, long[] entries ) + static BoundedIterable mockSingleAllEntriesReaderWithUnknownMaxCount( IndexAccessor targetAccessor, long[] entries ) { BoundedIterable allEntriesReader = mockedAllEntriesReaderUnknownMaxCount( entries ); when( targetAccessor.newAllEntriesReader() ).thenReturn( allEntriesReader ); return allEntriesReader; } - private BoundedIterable mockedAllEntriesReaderUnknownMaxCount( long... entries ) + static BoundedIterable mockedAllEntriesReaderUnknownMaxCount( long... entries ) { return mockedAllEntriesReader( false, entries ); } - private BoundedIterable mockedAllEntriesReader( boolean knownMaxCount, long... entries ) + static BoundedIterable mockedAllEntriesReader( boolean knownMaxCount, long... entries ) { BoundedIterable mockedAllEntriesReader = mock( BoundedIterable.class ); when( mockedAllEntriesReader.maxCount() ).thenReturn( knownMaxCount ? entries.length : BoundedIterable.UNKNOWN_MAX_COUNT ); when( mockedAllEntriesReader.iterator() ).thenReturn( Iterators.asIterator(entries ) ); return mockedAllEntriesReader; } + + private void mockAllEntriesReaders( long[] nativeEntries, long[] luceneEntries ) + { + mockSingleAllEntriesReader( nativeAccessor, nativeEntries ); + mockSingleAllEntriesReader( spatialAccessor, nativeEntries ); + mockSingleAllEntriesReader( luceneAccessor, luceneEntries ); + } } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexAccessorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexAccessorTest.java new file mode 100644 index 0000000000000..cf43b99182547 --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexAccessorTest.java @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2002-2018 "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.fusion; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.internal.verification.VerificationModeFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.neo4j.helpers.collection.BoundedIterable; +import org.neo4j.helpers.collection.Iterables; +import org.neo4j.kernel.api.index.IndexAccessor; +import org.neo4j.kernel.api.schema.index.IndexDescriptor; +import org.neo4j.kernel.impl.index.schema.SpatialKnownIndex; +import org.neo4j.values.storable.CoordinateReferenceSystem; + +import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.sameInstance; +import static org.hamcrest.core.AnyOf.anyOf; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.kernel.impl.index.schema.fusion.FusionIndexAccessorTest.assertResultContainsAll; +import static org.neo4j.kernel.impl.index.schema.fusion.FusionIndexAccessorTest.mockSingleAllEntriesReader; +import static org.neo4j.kernel.impl.index.schema.fusion.FusionIndexAccessorTest.mockSingleAllEntriesReaderWithUnknownMaxCount; +import static org.neo4j.kernel.impl.index.schema.fusion.FusionIndexTestHelp.verifyFusionCloseThrowIfAllThrow; +import static org.neo4j.kernel.impl.index.schema.fusion.FusionIndexTestHelp.verifyFusionCloseThrowOnSingleCloseThrow; +import static org.neo4j.kernel.impl.index.schema.fusion.FusionIndexTestHelp.verifyOtherIsClosedOnSingleThrow; +import static org.neo4j.values.storable.CoordinateReferenceSystem.Cartesian; +import static org.neo4j.values.storable.CoordinateReferenceSystem.WGS84; + +public class SpatialFusionIndexAccessorTest +{ + + private SpatialFusionIndexAccessor fusionIndexAccessor; + private Map indexMap = new HashMap<>(); + private Map accessorMap = new HashMap<>(); + + @Before + public void setup() throws Exception + { + SpatialKnownIndex.Factory indexFactory = mock( SpatialKnownIndex.Factory.class ); + + for ( CoordinateReferenceSystem crs : asList( WGS84, Cartesian ) ) + { + indexMap.put( crs, mock( SpatialKnownIndex.class ) ); + accessorMap.put( crs, mock( IndexAccessor.class ) ); + + when( indexMap.get( crs ).getOnlineAccessor( any(), any() ) ).thenReturn( accessorMap.get( crs ) ); + } + + fusionIndexAccessor = new SpatialFusionIndexAccessor( indexMap, 0, mock( IndexDescriptor.class ), null, indexFactory ); + } + + @Test + public void dropMustDropAll() throws Exception + { + fusionIndexAccessor.drop(); + + for ( IndexAccessor accessor : accessorMap.values() ) + { + verify( accessor, times( 1 ) ).drop(); + } + } + + @Test + public void dropMustThrowIfDropOneFail() throws Exception + { + verifyFailOnSingleDropFailure( accessorMap.get( WGS84 ) ); + verify( accessorMap.get( Cartesian ), times( 1 ) ).drop(); + reset( accessorMap.values().toArray() ); + verifyFailOnSingleDropFailure( accessorMap.get( Cartesian ) ); + verify( accessorMap.get( WGS84 ), times( 1 ) ).drop(); + } + + @Test + public void dropMustThrowIfBothFail() throws Exception + { + // given + IOException wgsFailure = new IOException( "wgs" ); + IOException cartesianFailure = new IOException( "cartesian" ); + doThrow( wgsFailure ).when( accessorMap.get( WGS84 ) ).drop(); + doThrow( cartesianFailure ).when( accessorMap.get( Cartesian ) ).drop(); + + try + { + // when + fusionIndexAccessor.drop(); + fail( "Should have failed" ); + } + catch ( IOException e ) + { + // then + assertThat( e, anyOf( sameInstance( wgsFailure ), sameInstance( cartesianFailure ) ) ); + } + } + + @Test + public void clostMustCloseAll() throws Exception + { + fusionIndexAccessor.close(); + + for ( IndexAccessor accessor : accessorMap.values() ) + { + verify( accessor, times( 1 ) ).close(); + } + } + + @Test + public void closeMustCloseOthersIfCloseOneFail() throws Exception + { + verifyOtherIsClosedOnSingleThrow( accessorMap.get( WGS84 ), fusionIndexAccessor, accessorMap.get( Cartesian ) ); + reset( accessorMap.values().toArray() ); + verifyOtherIsClosedOnSingleThrow( accessorMap.get( Cartesian ), fusionIndexAccessor, accessorMap.get( WGS84 ) ); + } + + @Test + public void closeMustThrowIfCloseOneFail() throws Exception + { + verifyFusionCloseThrowOnSingleCloseThrow( accessorMap.get( WGS84 ), fusionIndexAccessor ); + reset( accessorMap.values().toArray() ); + verifyFusionCloseThrowOnSingleCloseThrow( accessorMap.get( Cartesian ), fusionIndexAccessor ); + } + + @Test + public void closeMustThrowIfAllFail() throws Exception + { + verifyFusionCloseThrowIfAllThrow( fusionIndexAccessor, accessorMap.values().toArray( new AutoCloseable[2] ) ); + } + + @Test + public void allEntriesReaderMustCombineResultFromAll() throws Exception + { + // given + long[] wgsEntries = {0, 1, 2, 5, 6}; + long[] cartesianEntries = {3, 4, 7, 8}; + mockSingleAllEntriesReader( accessorMap.get( WGS84 ), wgsEntries ); + mockSingleAllEntriesReader( accessorMap.get( Cartesian ), cartesianEntries ); + + // when + Set result = Iterables.asSet( fusionIndexAccessor.newAllEntriesReader() ); + + // then + assertResultContainsAll( result, wgsEntries ); + assertResultContainsAll( result, cartesianEntries ); + } + + @Test + public void allEntriesReaderMustCombineResultFromAllWithWGSEmpty() throws Exception + { + // given + long[] wgsEntries = new long[0]; + long[] cartesianEntries = {3, 4, 7, 8}; + mockSingleAllEntriesReader( accessorMap.get( WGS84 ), wgsEntries ); + mockSingleAllEntriesReader( accessorMap.get( Cartesian ), cartesianEntries ); + + // when + Set result = Iterables.asSet( fusionIndexAccessor.newAllEntriesReader() ); + + // then + assertResultContainsAll( result, wgsEntries ); + assertResultContainsAll( result, cartesianEntries ); + } + + @Test + public void allEntriesReaderMustCombineResultFromAllWithCartesianEmpty() throws Exception + { + // given + long[] wgsEntries = {0, 1, 2, 5, 6}; + long[] cartesianEntries = new long[0]; + mockSingleAllEntriesReader( accessorMap.get( WGS84 ), wgsEntries ); + mockSingleAllEntriesReader( accessorMap.get( Cartesian ), cartesianEntries ); + + // when + Set result = Iterables.asSet( fusionIndexAccessor.newAllEntriesReader() ); + + // then + assertResultContainsAll( result, wgsEntries ); + assertResultContainsAll( result, cartesianEntries ); + } + + @Test + public void allEntriesReaderMustCombineResultFromAllWithAllEmpty() throws Exception + { + // given + long[] wgsEntries = new long[0]; + long[] cartesianEntries = new long[0]; + mockSingleAllEntriesReader( accessorMap.get( WGS84 ), wgsEntries ); + mockSingleAllEntriesReader( accessorMap.get( Cartesian ), cartesianEntries ); + + // when + Set result = Iterables.asSet( fusionIndexAccessor.newAllEntriesReader() ); + + // then + assertResultContainsAll( result, wgsEntries ); + assertResultContainsAll( result, cartesianEntries ); + } + + @Test + public void allEntriesReaderMustCloseAll() throws Exception + { + // given + BoundedIterable wgsAllEntriesReader = mockSingleAllEntriesReader( accessorMap.get( WGS84 ), new long[0] ); + BoundedIterable cartesianAllEntriesReader = mockSingleAllEntriesReader( accessorMap.get( Cartesian ), new long[0] ); + + // when + fusionIndexAccessor.newAllEntriesReader().close(); + + // then + verify( wgsAllEntriesReader, VerificationModeFactory.times( 1 ) ).close(); + verify( cartesianAllEntriesReader, VerificationModeFactory.times( 1 ) ).close(); + } + + @Test + public void allEntriesReaderMustCloseOtherIfOneThrow() throws Exception + { + // given + BoundedIterable wgsAllEntriesReader = mockSingleAllEntriesReader( accessorMap.get( WGS84 ), new long[0] ); + BoundedIterable cartesianAllEntriesReader = mockSingleAllEntriesReader( accessorMap.get( Cartesian ), new long[0] ); + + // then + BoundedIterable fusionAllEntriesReader = fusionIndexAccessor.newAllEntriesReader(); + verifyOtherIsClosedOnSingleThrow( wgsAllEntriesReader, fusionAllEntriesReader, cartesianAllEntriesReader ); + reset( wgsAllEntriesReader, cartesianAllEntriesReader ); + fusionAllEntriesReader = fusionIndexAccessor.newAllEntriesReader(); + verifyOtherIsClosedOnSingleThrow( cartesianAllEntriesReader, fusionAllEntriesReader, wgsAllEntriesReader ); + } + + @Test + public void allEntriesReaderMustThrowIfOneThrow() throws Exception + { + BoundedIterable wgsAllEntriesReader = mockSingleAllEntriesReader( accessorMap.get( WGS84 ), new long[0] ); + BoundedIterable cartesianAllEntriesReader = mockSingleAllEntriesReader( accessorMap.get( Cartesian ), new long[0] ); + + // then + verifyFusionCloseThrowOnSingleCloseThrow( wgsAllEntriesReader, fusionIndexAccessor.newAllEntriesReader() ); + reset( wgsAllEntriesReader, cartesianAllEntriesReader ); + verifyFusionCloseThrowOnSingleCloseThrow( cartesianAllEntriesReader, fusionIndexAccessor.newAllEntriesReader() ); + } + + @Test + public void allEntriesReaderMustReportFusionUnknownMaxCountIfWGSReportUnknownMaxCount() throws Exception + { + mockSingleAllEntriesReaderWithUnknownMaxCount( accessorMap.get( WGS84 ), new long[0] ); + mockSingleAllEntriesReader( accessorMap.get( Cartesian ), new long[0] ); + + // then + BoundedIterable fusionAllEntriesReader = fusionIndexAccessor.newAllEntriesReader(); + assertThat( fusionAllEntriesReader.maxCount(), is( BoundedIterable.UNKNOWN_MAX_COUNT ) ); + } + + @Test + public void allEntriesReaderMustReportFusionUnknownMaxCountIfCartesianReportUnknownMaxCount() throws Exception + { + mockSingleAllEntriesReader( accessorMap.get( WGS84 ), new long[0] ); + mockSingleAllEntriesReaderWithUnknownMaxCount( accessorMap.get( Cartesian ), new long[0] ); + + // then + BoundedIterable fusionAllEntriesReader = fusionIndexAccessor.newAllEntriesReader(); + assertThat( fusionAllEntriesReader.maxCount(), is( BoundedIterable.UNKNOWN_MAX_COUNT ) ); + } + + @Test + public void allEntriesReaderMustReportFusionMacCountOfAll() throws Exception + { + mockSingleAllEntriesReader( accessorMap.get( WGS84 ), new long[]{0, 1, 2, 5, 6} ); + mockSingleAllEntriesReader( accessorMap.get( Cartesian ), new long[]{3, 4, 7, 8} ); + + BoundedIterable fusionAllEntriesReader = fusionIndexAccessor.newAllEntriesReader(); + assertThat( fusionAllEntriesReader.maxCount(), is( 9L ) ); + } + + private void verifyFailOnSingleDropFailure( IndexAccessor failingAccessor ) throws IOException + { + IOException expectedFailure = new IOException( "fail" ); + doThrow( expectedFailure ).when( failingAccessor ).drop(); + try + { + fusionIndexAccessor.drop(); + fail( "Should have failed" ); + } + catch ( IOException e ) + { + assertSame( expectedFailure, e ); + } + } +} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexPopulatorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexPopulatorTest.java new file mode 100644 index 0000000000000..572d6bcf9443c --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexPopulatorTest.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2002-2018 "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.fusion; + +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.neo4j.internal.kernel.api.schema.LabelSchemaDescriptor; +import org.neo4j.kernel.api.exceptions.index.IndexEntryConflictException; +import org.neo4j.kernel.api.index.IndexEntryUpdate; +import org.neo4j.kernel.api.index.IndexPopulator; +import org.neo4j.kernel.api.schema.index.IndexDescriptor; +import org.neo4j.kernel.impl.index.schema.SpatialKnownIndex; +import org.neo4j.values.storable.CoordinateReferenceSystem; +import org.neo4j.values.storable.PointValue; +import org.neo4j.values.storable.Value; + +import static java.util.Arrays.asList; +import static org.hamcrest.Matchers.sameInstance; +import static org.hamcrest.core.AnyOf.anyOf; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.kernel.impl.index.schema.fusion.FusionIndexTestHelp.add; +import static org.neo4j.kernel.impl.index.schema.fusion.FusionIndexTestHelp.verifyCallFail; + +public class SpatialFusionIndexPopulatorTest +{ + + private Map populatorMap = new HashMap<>(); + private SpatialFusionIndexPopulator fusionIndexPopulator; + private Map indexMap = new HashMap<>(); + + @Before + public void setup() throws Exception + { + SpatialKnownIndex.Factory indexFactory = mock( SpatialKnownIndex.Factory.class ); + for ( CoordinateReferenceSystem crs : asList( CoordinateReferenceSystem.WGS84, CoordinateReferenceSystem.Cartesian ) ) + { + populatorMap.put( crs, mock( IndexPopulator.class ) ); + indexMap.put( crs, mock( SpatialKnownIndex.class ) ); + + when( indexMap.get( crs ).getPopulator( any(), any() ) ).thenReturn( populatorMap.get( crs ) ); + when( indexFactory.selectAndCreate( indexMap, 0, crs ) ).thenReturn( indexMap.get( crs ) ); + when( indexMap.get( crs ).getPopulator( any(), any() ) ).thenReturn( populatorMap.get( crs ) ); + } + + when( indexFactory.selectAndCreate( eq( indexMap ), eq( 0L ), any( PointValue.class ) ) ).thenAnswer( ( a ) -> + { + PointValue pointValue = a.getArgument( 2 ); + return indexMap.get( pointValue.getCoordinateReferenceSystem() ); + } ); + + fusionIndexPopulator = new SpatialFusionIndexPopulator( indexMap, 0, mock( IndexDescriptor.class ), null, indexFactory ); + } + + @Test + public void dropMustDropAll() throws Exception + { + // when + fusionIndexPopulator.drop(); + + // then + for ( IndexPopulator populator : populatorMap.values() ) + { + verify( populator, times( 1 ) ).drop(); + } + } + + @Test + public void dropMustThrowIfWGSThrow() throws Exception + { + // given + IOException failure = new IOException( "fail" ); + doThrow( failure ).when( populatorMap.get( CoordinateReferenceSystem.WGS84 ) ).drop(); + + verifyCallFail( failure, () -> + { + fusionIndexPopulator.drop(); + return null; + } ); + verify( populatorMap.get( CoordinateReferenceSystem.Cartesian ), times( 1 ) ).drop(); + } + + @Test + public void dropMustThrowIfCartesianThrow() throws Exception + { + // given + IOException failure = new IOException( "fail" ); + doThrow( failure ).when( populatorMap.get( CoordinateReferenceSystem.Cartesian ) ).drop(); + + verifyCallFail( failure, () -> + { + fusionIndexPopulator.drop(); + return null; + } ); + verify( populatorMap.get( CoordinateReferenceSystem.WGS84 ), times( 1 ) ).drop(); + } + + @Test + public void addMustSelectCorrectPopulator() throws Exception + { + for ( Value value : FusionIndexTestHelp.valuesSupportedBySpatial() ) + { + PointValue point = (PointValue) value; + verifyAddWithCorrectPopulator( populatorMap.get( point.getCoordinateReferenceSystem() ), value ); + } + } + + @Test + public void verifyDeferredConstraintsMustThrowIfWGSThrow() throws Exception + { + // given + IndexEntryConflictException failure = mock( IndexEntryConflictException.class ); + doThrow( failure ).when( populatorMap.get( CoordinateReferenceSystem.WGS84 ) ).verifyDeferredConstraints( any() ); + + verifyCallFail( failure, () -> + { + fusionIndexPopulator.verifyDeferredConstraints( null ); + return null; + } ); + verify( populatorMap.get( CoordinateReferenceSystem.Cartesian ), times( 1 ) ).verifyDeferredConstraints( any() ); + } + + @Test + public void verifyDeferredConstraintsMustThrowIfCartesianThrow() throws Exception + { + // given + IndexEntryConflictException failure = mock( IndexEntryConflictException.class ); + doThrow( failure ).when( populatorMap.get( CoordinateReferenceSystem.Cartesian ) ).verifyDeferredConstraints( any() ); + + verifyCallFail( failure, () -> + { + fusionIndexPopulator.verifyDeferredConstraints( null ); + return null; + } ); + verify( populatorMap.get( CoordinateReferenceSystem.WGS84 ), times( 1 ) ).verifyDeferredConstraints( any() ); + } + + @Test + public void successfulCloseMustCloseAll() throws Exception + { + closeAndVerifyPropagation( true ); + } + + @Test + public void unsuccessfulCloseMustCloseAll() throws Exception + { + closeAndVerifyPropagation( false ); + } + + @Test + public void closeMustCloseOtherAndThrowIfCloseWGSThrow() throws Exception + { + IOException failure = new IOException( "fail" ); + doThrow( failure ).when( indexMap.get( CoordinateReferenceSystem.WGS84 ) ).closePopulator( any(), any(), anyBoolean() ); + + verifyCallFail( failure, () -> + { + fusionIndexPopulator.close( true ); + return null; + } ); + verify( indexMap.get( CoordinateReferenceSystem.Cartesian ), times( 1 ) ).closePopulator( any(), any(), eq( true ) ); + } + + @Test + public void closeMustCloseOtherAndThrowIfCloseCartesianThrow() throws Exception + { + IOException failure = new IOException( "fail" ); + doThrow( failure ).when( indexMap.get( CoordinateReferenceSystem.Cartesian ) ).closePopulator( any(), any(), anyBoolean() ); + + verifyCallFail( failure, () -> + { + fusionIndexPopulator.close( true ); + return null; + } ); + verify( indexMap.get( CoordinateReferenceSystem.WGS84 ), times( 1 ) ).closePopulator( any(), any(), eq( true ) ); + } + + @Test + public void closeMustThrowIfAllThrow() throws Exception + { + IOException wgsFailure = new IOException( "fail" ); + IOException cartesianFailure = new IOException( "fail" ); + doThrow( cartesianFailure ).when( indexMap.get( CoordinateReferenceSystem.Cartesian ) ).closePopulator( any(), any(), anyBoolean() ); + doThrow( wgsFailure ).when( indexMap.get( CoordinateReferenceSystem.WGS84 ) ).closePopulator( any(), any(), anyBoolean() ); + + try + { + // when + fusionIndexPopulator.close( true ); + fail( "Should have failed" ); + } + catch ( IOException e ) + { + //then + assertThat( e, anyOf( sameInstance( wgsFailure ), sameInstance( cartesianFailure ) ) ); + } + } + + @Test + public void markAsFailedMustMarkAll() throws Exception + { + // when + String failureMessage = "failure"; + fusionIndexPopulator.markAsFailed( failureMessage ); + + // then + for ( IndexPopulator populator : populatorMap.values() ) + { + verify( populator, times( 1 ) ).markAsFailed( failureMessage ); + } + } + + @Test + public void markAsFailedMustThrowIfWGSThrow() throws Exception + { + // given + IOException failure = new IOException( "fail" ); + doThrow( failure ).when( populatorMap.get( CoordinateReferenceSystem.WGS84 ) ).markAsFailed( "failed" ); + + // then + verifyCallFail( failure, () -> + { + fusionIndexPopulator.markAsFailed( "failed" ); + return null; + } ); + verify( populatorMap.get( CoordinateReferenceSystem.Cartesian ) ).markAsFailed( "failed" ); + } + + @Test + public void markAsFailedMustThrowIfCartesianThrow() throws Exception + { + // given + IOException failure = new IOException( "fail" ); + doThrow( failure ).when( populatorMap.get( CoordinateReferenceSystem.Cartesian ) ).markAsFailed( "failed" ); + + // then + verifyCallFail( failure, () -> + { + fusionIndexPopulator.markAsFailed( "failed" ); + return null; + } ); + verify( populatorMap.get( CoordinateReferenceSystem.WGS84 ) ).markAsFailed( "failed" ); + } + + @Test + public void shouldIncludeSampleOnCorrectPopulator() throws Exception + { + // given + for ( Value value : FusionIndexTestHelp.valuesSupportedBySpatial() ) + { + PointValue point = (PointValue) value; + // when + IndexEntryUpdate update = add( value ); + fusionIndexPopulator.includeSample( update ); + + // then + IndexPopulator populator = populatorMap.get( point.getCoordinateReferenceSystem() ); + verify( populator ).includeSample( update ); + reset( populator ); + } + } + + private void closeAndVerifyPropagation( boolean populationCompletedSuccessfully ) throws IOException + { + fusionIndexPopulator.close( populationCompletedSuccessfully ); + + // then + for ( SpatialKnownIndex index : indexMap.values() ) + { + verify( index, times( 1 ) ).closePopulator( any(), any(), eq( populationCompletedSuccessfully ) ); + } + } + + private void verifyAddWithCorrectPopulator( IndexPopulator correctPopulator, Value numberValues ) throws IndexEntryConflictException, IOException + { + Collection> update = Collections.singletonList( add( numberValues ) ); + fusionIndexPopulator.add( update ); + verify( correctPopulator, times( 1 ) ).add( update ); + for ( IndexPopulator populator : populatorMap.values() ) + { + if ( populator != correctPopulator ) + { + verify( populator, times( 0 ) ).add( update ); + } + } + } +} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexReaderTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexReaderTest.java new file mode 100644 index 0000000000000..a95f2c7240cff --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexReaderTest.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2002-2018 "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.fusion; + +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import org.neo4j.collection.primitive.PrimitiveLongCollections; +import org.neo4j.collection.primitive.PrimitiveLongIterator; +import org.neo4j.collection.primitive.PrimitiveLongResourceCollections; +import org.neo4j.collection.primitive.PrimitiveLongResourceIterator; +import org.neo4j.collection.primitive.PrimitiveLongSet; +import org.neo4j.internal.kernel.api.IndexOrder; +import org.neo4j.internal.kernel.api.IndexQuery; +import org.neo4j.kernel.api.exceptions.index.IndexNotApplicableKernelException; +import org.neo4j.kernel.api.schema.index.IndexDescriptorFactory; +import org.neo4j.storageengine.api.schema.IndexReader; +import org.neo4j.values.storable.CoordinateReferenceSystem; +import org.neo4j.values.storable.PointValue; +import org.neo4j.values.storable.Value; +import org.neo4j.values.storable.Values; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class SpatialFusionIndexReaderTest +{ + private SpatialFusionIndexReader fusionIndexReader; + private Map readerMap; + private static final int PROP_KEY = 1; + private static final int LABEL_KEY = 11; + + @Before + public void setup() + { + readerMap = new HashMap<>(); + readerMap.put( CoordinateReferenceSystem.WGS84, mock( IndexReader.class ) ); + readerMap.put( CoordinateReferenceSystem.Cartesian, mock( IndexReader.class ) ); + fusionIndexReader = new SpatialFusionIndexReader( readerMap, IndexDescriptorFactory.forLabel( LABEL_KEY, PROP_KEY ) ); + } + + @Test + public void closeMustCloseAll() throws Exception + { + // when + fusionIndexReader.close(); + + // then + for ( IndexReader reader : readerMap.values() ) + { + verify( reader, times( 1 ) ).close(); + } + } + + @Test + public void closeIteratorMustCloseAll() throws Exception + { + // Given + PrimitiveLongResourceIterator wgs84Iter = mock( PrimitiveLongResourceIterator.class ); + PrimitiveLongResourceIterator cartesianIter = mock( PrimitiveLongResourceIterator.class ); + when( readerMap.get( CoordinateReferenceSystem.WGS84 ).query( any( IndexQuery.class ) ) ).thenReturn( wgs84Iter ); + when( readerMap.get( CoordinateReferenceSystem.Cartesian ).query( any( IndexQuery.class ) ) ).thenReturn( cartesianIter ); + + // When + fusionIndexReader.query( IndexQuery.exists( PROP_KEY ) ).close(); + + // Then + verify( wgs84Iter, times( 1 ) ).close(); + verify( cartesianIter, times( 1 ) ).close(); + } + + @Test + public void countIndexedNodesMustSelectCorrectReader() throws Exception + { + for ( Value spatialValue : FusionIndexTestHelp.valuesSupportedBySpatial() ) + { + PointValue point = (PointValue) spatialValue; + CoordinateReferenceSystem crs = point.getCoordinateReferenceSystem(); + verifyCountIndexedNodesWithCorrectReader( readerMap.get( crs ), spatialValue ); + } + } + + @Test + public void mustSelectCorrectForExactPredicate() throws Exception + { + for ( Value spatialValue : FusionIndexTestHelp.valuesSupportedBySpatial() ) + { + PointValue point = (PointValue) spatialValue; + CoordinateReferenceSystem crs = point.getCoordinateReferenceSystem(); + IndexQuery indexQuery = IndexQuery.exact( PROP_KEY, spatialValue ); + + verifyQueryWithCorrectReader( readerMap.get( crs ), indexQuery ); + } + } + + @Test + public void mustSelectCorrectForRangeGeometricPredicate() throws Exception + { + { + PointValue from = Values.pointValue( CoordinateReferenceSystem.Cartesian, 1.0, 1.0 ); + PointValue to = Values.pointValue( CoordinateReferenceSystem.Cartesian, 2.0, 2.0 ); + IndexQuery.GeometryRangePredicate geometryRange = IndexQuery.range( PROP_KEY, from, true, to, false ); + + verifyQueryWithCorrectReader( readerMap.get( CoordinateReferenceSystem.Cartesian ), geometryRange ); + } + + { + PointValue from = Values.pointValue( CoordinateReferenceSystem.WGS84, 1.0, 1.0 ); + PointValue to = Values.pointValue( CoordinateReferenceSystem.WGS84, 2.0, 2.0 ); + IndexQuery.GeometryRangePredicate geometryRange = IndexQuery.range( PROP_KEY, from, true, to, false ); + + verifyQueryWithCorrectReader( readerMap.get( CoordinateReferenceSystem.WGS84 ), geometryRange ); + } + } + + @Test + public void mustCombineResultFromExistsPredicate() throws Exception + { + // given + IndexQuery.ExistsPredicate exists = IndexQuery.exists( PROP_KEY ); + when( readerMap.get( CoordinateReferenceSystem.Cartesian ).query( exists ) ).thenReturn( PrimitiveLongResourceCollections.iterator( null, 0L, 1L, 4L, 5L ) ); + when( readerMap.get( CoordinateReferenceSystem.WGS84 ).query( exists ) ).thenReturn( PrimitiveLongResourceCollections.iterator( null, 2L, 3L, 6L ) ); + + PrimitiveLongIterator result = fusionIndexReader.query( exists ); + + PrimitiveLongSet resultSet = PrimitiveLongCollections.asSet( result ); + for ( long i = 0L; i < 7L; i++ ) + { + assertTrue( "Expected to contain " + i + ", but was " + resultSet, resultSet.contains( i ) ); + } + } + + private void verifyCountIndexedNodesWithCorrectReader( IndexReader correct, Value... nativeValue ) + { + fusionIndexReader.countIndexedNodes( 0, nativeValue ); + verify( correct, times( 1 ) ).countIndexedNodes( 0, nativeValue ); + for ( IndexReader reader : readerMap.values() ) + { + if ( reader != correct ) + { + verify( reader, times( 0 ) ).countIndexedNodes( 0, nativeValue ); + } + } + } + + private void verifyQueryWithCorrectReader( IndexReader expectedReader, IndexQuery indexQuery ) + throws IndexNotApplicableKernelException + { + // when + fusionIndexReader.query( indexQuery ); + + // then + verify( expectedReader, times( 1 ) ). + query( any(), eq( IndexOrder.NONE ), eq( indexQuery ) ); + for ( IndexReader reader : readerMap.values() ) + { + if ( reader != expectedReader ) + { + verifyNoMoreInteractions( reader ); + } + } + } + +} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexUpdaterTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexUpdaterTest.java new file mode 100644 index 0000000000000..962369b66a547 --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionIndexUpdaterTest.java @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2002-2018 "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.fusion; + +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.neo4j.internal.kernel.api.schema.LabelSchemaDescriptor; +import org.neo4j.kernel.api.exceptions.index.IndexEntryConflictException; +import org.neo4j.kernel.api.index.IndexAccessor; +import org.neo4j.kernel.api.index.IndexEntryUpdate; +import org.neo4j.kernel.api.index.IndexPopulator; +import org.neo4j.kernel.api.index.IndexUpdater; +import org.neo4j.kernel.api.index.PropertyAccessor; +import org.neo4j.kernel.api.schema.index.IndexDescriptor; +import org.neo4j.kernel.impl.api.index.IndexUpdateMode; +import org.neo4j.kernel.impl.index.schema.SpatialKnownIndex; +import org.neo4j.values.storable.CoordinateReferenceSystem; +import org.neo4j.values.storable.PointValue; +import org.neo4j.values.storable.Value; +import org.neo4j.values.storable.Values; + +import static java.util.Arrays.asList; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.kernel.impl.index.schema.fusion.FusionIndexTestHelp.add; +import static org.neo4j.kernel.impl.index.schema.fusion.FusionIndexTestHelp.change; +import static org.neo4j.kernel.impl.index.schema.fusion.FusionIndexTestHelp.remove; + +public class SpatialFusionIndexUpdaterTest +{ + private Map updaterMap = new HashMap<>(); + private Map indexMap = new HashMap<>(); + private Map accessorMap = new HashMap<>(); + private Map populatorMap = new HashMap<>(); + private SpatialFusionIndexUpdater fusionIndexAccessorUpdater; + private SpatialFusionIndexUpdater fusionIndexPopulatorUpdater; + + @Before + public void setup() throws Exception + { + SpatialKnownIndex.Factory indexFactory = mock( SpatialKnownIndex.Factory.class ); + + for ( CoordinateReferenceSystem crs : asList( CoordinateReferenceSystem.WGS84, CoordinateReferenceSystem.Cartesian ) ) + { + updaterMap.put( crs, mock( IndexUpdater.class ) ); + indexMap.put( crs, mock( SpatialKnownIndex.class ) ); + accessorMap.put( crs, mock( IndexAccessor.class ) ); + populatorMap.put( crs, mock( IndexPopulator.class ) ); + when( indexFactory.selectAndCreate( indexMap, 0, crs ) ).thenReturn( indexMap.get( crs ) ); + when( indexMap.get( crs ).getOnlineAccessor( any(), any() ) ).thenReturn( accessorMap.get( crs ) ); + when( accessorMap.get( crs ).newUpdater( any() ) ).thenReturn( updaterMap.get( crs ) ); + when( indexMap.get( crs ).getPopulator( any(), any() ) ).thenReturn( populatorMap.get( crs ) ); + when( populatorMap.get( crs ).newPopulatingUpdater( any() ) ).thenReturn( updaterMap.get( crs ) ); + } + + fusionIndexAccessorUpdater = new SpatialFusionIndexUpdater( indexMap, 0, indexFactory, mock( IndexDescriptor.class ), null, IndexUpdateMode.ONLINE ); + fusionIndexPopulatorUpdater = + new SpatialFusionIndexUpdater( indexMap, 0, indexFactory, mock( IndexDescriptor.class ), null, mock( PropertyAccessor.class ) ); + } + + @Test + public void processMustSelectCorrectForAdd() throws Exception + { + for ( Value value : FusionIndexTestHelp.valuesSupportedBySpatial() ) + { + PointValue point = (PointValue) value; + CoordinateReferenceSystem crs = point.getCoordinateReferenceSystem(); + verifyAddWithCorrectUpdater( fusionIndexAccessorUpdater, updaterMap.get( crs ), value ); + reset( updaterMap.get( crs ) ); + verifyAddWithCorrectUpdater( fusionIndexPopulatorUpdater, updaterMap.get( crs ), value ); + } + } + + @Test + public void processMustSelectCorrectForRemove() throws Exception + { + for ( Value value : FusionIndexTestHelp.valuesSupportedBySpatial() ) + { + PointValue point = (PointValue) value; + CoordinateReferenceSystem crs = point.getCoordinateReferenceSystem(); + verifyRemoveWithCorrectUpdater( fusionIndexAccessorUpdater, updaterMap.get( crs ), value ); + reset( updaterMap.get( crs ) ); + verifyRemoveWithCorrectUpdater( fusionIndexPopulatorUpdater, updaterMap.get( crs ), value ); + } + } + + @Test + public void processMustSelectCorrectForChangeSameCRS() throws Exception + { + PointValue from = Values.pointValue( CoordinateReferenceSystem.Cartesian, 1.0, 1.0 ); + PointValue to = Values.pointValue( CoordinateReferenceSystem.Cartesian, 2.0, 2.0 ); + verifyChangeWithCorrectUpdaterNotMixed( fusionIndexAccessorUpdater, updaterMap.get( CoordinateReferenceSystem.Cartesian ), from, to ); + reset( updaterMap.get( CoordinateReferenceSystem.Cartesian ) ); + verifyChangeWithCorrectUpdaterNotMixed( fusionIndexPopulatorUpdater, updaterMap.get( CoordinateReferenceSystem.Cartesian ), from, to ); + } + + @Test + public void processMustSelectCorrectForChangeCRS() throws Exception + { + PointValue from = Values.pointValue( CoordinateReferenceSystem.Cartesian, 1.0, 1.0 ); + PointValue to = Values.pointValue( CoordinateReferenceSystem.WGS84, 2.0, 2.0 ); + verifyChangeWithCorrectUpdaterMixed( fusionIndexAccessorUpdater, updaterMap.get( CoordinateReferenceSystem.Cartesian ), + updaterMap.get( CoordinateReferenceSystem.WGS84 ), from, to ); + reset( updaterMap.values().toArray() ); + verifyChangeWithCorrectUpdaterMixed( fusionIndexPopulatorUpdater, updaterMap.get( CoordinateReferenceSystem.Cartesian ), + updaterMap.get( CoordinateReferenceSystem.WGS84 ), from, to ); + } + + @Test + public void closeMustCloseAll() throws Exception + { + //given + populateUpdaters(); + + // when + fusionIndexPopulatorUpdater.close(); + + // then + for ( IndexUpdater updater : updaterMap.values() ) + { + verify( updater, times( 1 ) ).close(); + reset( updater ); + } + + // when + fusionIndexAccessorUpdater.close(); + + // then + for ( IndexUpdater updater : updaterMap.values() ) + { + verify( updater, times( 1 ) ).close(); + } + } + + @Test + public void closeMustThrowIfWGSThrow() throws Exception + { + populateUpdaters(); + FusionIndexTestHelp.verifyFusionCloseThrowOnSingleCloseThrow( updaterMap.get( CoordinateReferenceSystem.WGS84 ), fusionIndexAccessorUpdater ); + reset( updaterMap.values().toArray() ); + FusionIndexTestHelp.verifyFusionCloseThrowOnSingleCloseThrow( updaterMap.get( CoordinateReferenceSystem.WGS84 ), fusionIndexPopulatorUpdater ); + } + + @Test + public void closeMustThrowIfCartesianThrow() throws Exception + { + populateUpdaters(); + FusionIndexTestHelp.verifyFusionCloseThrowOnSingleCloseThrow( updaterMap.get( CoordinateReferenceSystem.Cartesian ), fusionIndexAccessorUpdater ); + reset( updaterMap.values().toArray() ); + FusionIndexTestHelp.verifyFusionCloseThrowOnSingleCloseThrow( updaterMap.get( CoordinateReferenceSystem.Cartesian ), fusionIndexPopulatorUpdater ); + } + + @Test + public void closeMustThrowIfAllThrow() throws Exception + { + populateUpdaters(); + FusionIndexTestHelp.verifyFusionCloseThrowIfAllThrow( fusionIndexAccessorUpdater, updaterMap.values().toArray(new AutoCloseable[2]) ); + reset( updaterMap.values().toArray() ); + FusionIndexTestHelp.verifyFusionCloseThrowIfAllThrow( fusionIndexPopulatorUpdater, updaterMap.values().toArray(new AutoCloseable[2]) ); + + } + + @Test + public void closeMustCloseIfWGSThrow() throws Exception + { + populateUpdaters(); + FusionIndexTestHelp.verifyOtherIsClosedOnSingleThrow( updaterMap.get( CoordinateReferenceSystem.WGS84 ), fusionIndexAccessorUpdater, + updaterMap.get( CoordinateReferenceSystem.Cartesian ) ); + reset( updaterMap.values().toArray() ); + FusionIndexTestHelp.verifyOtherIsClosedOnSingleThrow( updaterMap.get( CoordinateReferenceSystem.WGS84 ), fusionIndexPopulatorUpdater, + updaterMap.get( CoordinateReferenceSystem.Cartesian ) ); + } + + @Test + public void closeMustCloseIfCartesianThrow() throws Exception + { + populateUpdaters(); + FusionIndexTestHelp.verifyOtherIsClosedOnSingleThrow( updaterMap.get( CoordinateReferenceSystem.Cartesian ), fusionIndexAccessorUpdater, + updaterMap.get( CoordinateReferenceSystem.WGS84 ) ); + reset( updaterMap.values().toArray() ); + FusionIndexTestHelp.verifyOtherIsClosedOnSingleThrow( updaterMap.get( CoordinateReferenceSystem.Cartesian ), fusionIndexPopulatorUpdater, + updaterMap.get( CoordinateReferenceSystem.WGS84 ) ); + } + + private void verifyAddWithCorrectUpdater( SpatialFusionIndexUpdater fusionIndexUpdater, IndexUpdater indexUpdater, Value... numberValues ) + throws IndexEntryConflictException, IOException + { + IndexEntryUpdate update = add( numberValues ); + fusionIndexUpdater.process( update ); + verify( indexUpdater, times( 1 ) ).process( update ); + for ( IndexUpdater populator : updaterMap.values() ) + { + if ( populator != indexUpdater ) + { + verify( populator, times( 0 ) ).process( update ); + } + } + } + + private void verifyRemoveWithCorrectUpdater( SpatialFusionIndexUpdater fusionIndexUpdater, IndexUpdater indexUpdater, Value... numberValues ) + throws IndexEntryConflictException, IOException + { + IndexEntryUpdate update = remove( numberValues ); + fusionIndexUpdater.process( update ); + verify( indexUpdater, times( 1 ) ).process( update ); + for ( IndexUpdater populator : updaterMap.values() ) + { + if ( populator != indexUpdater ) + { + verify( populator, times( 0 ) ).process( update ); + } + } + } + + private void verifyChangeWithCorrectUpdaterNotMixed( SpatialFusionIndexUpdater fusionIndexUpdater, IndexUpdater indexUpdater, Value before, Value after ) + throws IndexEntryConflictException, IOException + { + IndexEntryUpdate update = FusionIndexTestHelp.change( before, after ); + fusionIndexUpdater.process( update ); + verify( indexUpdater, times( 1 ) ).process( update ); + for ( IndexUpdater populator : updaterMap.values() ) + { + if ( populator != indexUpdater ) + { + verify( populator, times( 0 ) ).process( update ); + } + } + } + + private void verifyChangeWithCorrectUpdaterMixed( SpatialFusionIndexUpdater fusionIndexUpdater, IndexUpdater expectRemoveFrom, IndexUpdater expectAddTo, + Value beforeValue, Value afterValue ) throws IOException, IndexEntryConflictException + { + IndexEntryUpdate change = change( beforeValue, afterValue ); + IndexEntryUpdate remove = remove( beforeValue ); + IndexEntryUpdate add = add( afterValue ); + fusionIndexUpdater.process( change ); + verify( expectRemoveFrom, times( 1 ) ).process( remove ); + verify( expectAddTo, times( 1 ) ).process( add ); + } + + private void populateUpdaters() throws IOException, IndexEntryConflictException + { + fusionIndexPopulatorUpdater.process( add( Values.pointValue( CoordinateReferenceSystem.Cartesian, 1.0, 1.0 ) ) ); + fusionIndexPopulatorUpdater.process( add( Values.pointValue( CoordinateReferenceSystem.WGS84, 1.0, 1.0 ) ) ); + fusionIndexAccessorUpdater.process( add( Values.pointValue( CoordinateReferenceSystem.Cartesian, 1.0, 1.0 ) ) ); + fusionIndexAccessorUpdater.process( add( Values.pointValue( CoordinateReferenceSystem.WGS84, 1.0, 1.0 ) ) ); + } +} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionSchemaIndexProviderTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionSchemaIndexProviderTest.java new file mode 100644 index 0000000000000..f2f555327f852 --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/fusion/SpatialFusionSchemaIndexProviderTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2002-2018 "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.fusion; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.Map; + +import org.neo4j.internal.kernel.api.InternalIndexState; +import org.neo4j.io.fs.FileSystemAbstraction; +import org.neo4j.io.pagecache.PageCache; +import org.neo4j.kernel.api.index.SchemaIndexProvider; +import org.neo4j.kernel.api.schema.index.IndexDescriptor; +import org.neo4j.kernel.api.schema.index.IndexDescriptorFactory; +import org.neo4j.kernel.impl.index.schema.SpatialKnownIndex; +import org.neo4j.test.rule.RandomRule; +import org.neo4j.values.storable.CoordinateReferenceSystem; +import org.neo4j.values.storable.PointValue; +import org.neo4j.values.storable.Value; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.neo4j.helpers.ArrayUtil.array; +import static org.neo4j.kernel.api.index.IndexDirectoryStructure.NONE; + +public class SpatialFusionSchemaIndexProviderTest +{ + @Rule + public RandomRule random = new RandomRule(); + + private Map indexMap; + private SpatialFusionSchemaIndexProvider provider; + + @Before + public void setup() + { + provider = new SpatialFusionSchemaIndexProvider( + mock( PageCache.class ), + mock( FileSystemAbstraction.class ), + NONE, + SchemaIndexProvider.Monitor.EMPTY, + null, + false ); + indexMap = provider.indexesFor( 0 ); + indexMap.put( CoordinateReferenceSystem.WGS84, mock( SpatialKnownIndex.class ) ); + indexMap.put( CoordinateReferenceSystem.Cartesian, mock( SpatialKnownIndex.class ) ); + } + + + @Test + public void shouldReportPopulatingIfAnyIsPopulating() throws Exception + { + IndexDescriptor indexDescriptor = IndexDescriptorFactory.forLabel( 1, 1 ); + + for ( InternalIndexState state : array( InternalIndexState.ONLINE, InternalIndexState.POPULATING ) ) + { + // when + for ( SpatialKnownIndex index : indexMap.values() ) + { + setInitialState( index, state ); + } + + for ( SpatialKnownIndex index : indexMap.values() ) + { + setInitialState( index, InternalIndexState.POPULATING ); + // then + assertEquals( InternalIndexState.POPULATING, provider.getInitialState( 0, indexDescriptor ) ); + setInitialState( index, state ); + } + } + } + + @Test + public void getPopulationFailureMustThrowIfNoFailure() throws Exception + { + // when + // ... no failure + for ( SpatialKnownIndex index : indexMap.values() ) + { + when( index.readPopupationFailure() ).thenReturn( null ); + } + // then + try + { + provider.getPopulationFailure( 0 ); + fail( "Should have failed" ); + } + catch ( IllegalStateException e ) + { // good + } + } + + @Test + public void mustSelectCorrectTargetForValues() throws Exception + { + for ( Value spatialValue : FusionIndexTestHelp.valuesSupportedBySpatial() ) + { + PointValue point = (PointValue) spatialValue; + CoordinateReferenceSystem crs = point.getCoordinateReferenceSystem(); + SpatialKnownIndex index = provider.selectAndCreate( indexMap, 0, point ); + assertSame( indexMap.get( crs ), index ); + index = provider.selectAndCreate( indexMap, 0, crs ); + assertSame( indexMap.get( crs ), index ); + } + } + + private void setInitialState( SpatialKnownIndex mockedIndex, InternalIndexState state ) throws IOException + { + when( mockedIndex.indexExists() ).thenReturn( true ); + when( mockedIndex.readState() ).thenReturn( state ); + } +}