Skip to content

Commit

Permalink
Test for uniqueness index with spatial values that collide on space f…
Browse files Browse the repository at this point in the history
…illing curve

Include:
- Rename BatchInsertIndexProviderTest -> BatchInsertIndexTest
- Small utility for creating points that collide on space filling curve

- Test for normal constraint index population
When creating a constraint, verification of population will be done through
IndexAccessor.verifyDeferredConstraint in the cases where index can not
directly identify violations.

- Test for constraint population with BatchInserterImpl
BatchInserterImpl use IndexPopulator.verifyDeferredConstraint to verify
that constraint is fulfilled by index.
  • Loading branch information
burqen committed Nov 6, 2018
1 parent 3a44364 commit d9654ce
Show file tree
Hide file tree
Showing 6 changed files with 498 additions and 13 deletions.
4 changes: 4 additions & 0 deletions community/community-it/index-it/pom.xml
Expand Up @@ -111,6 +111,10 @@
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId> <artifactId>junit-jupiter-engine</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.hamcrest</groupId> <groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId> <artifactId>hamcrest-core</artifactId>
Expand Down
@@ -0,0 +1,209 @@
/*
* Copyright (c) 2002-2018 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.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 schema;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

import org.neo4j.graphdb.ConstraintViolationException;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.ResourceIterator;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.factory.GraphDatabaseBuilder;
import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.helpers.collection.Pair;
import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.impl.index.schema.config.SpatialIndexValueTestUtil;
import org.neo4j.test.TestGraphDatabaseFactory;
import org.neo4j.test.TestLabels;
import org.neo4j.test.extension.Inject;
import org.neo4j.test.extension.TestDirectoryExtension;
import org.neo4j.test.rule.TestDirectory;
import org.neo4j.values.storable.PointValue;

import static org.junit.Assert.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

@ExtendWith( TestDirectoryExtension.class )
class UniqueSpatialIndexIT
{
private static final String KEY = "prop";
private static final TestLabels LABEL = TestLabels.LABEL_ONE;

@Inject
private TestDirectory directory;
private GraphDatabaseService db;
private PointValue point1;
private PointValue point2;

@BeforeEach
void setup()
{
Pair<PointValue,PointValue> collidingPoints = SpatialIndexValueTestUtil.pointsWithSameValueOnSpaceFillingCurve( Config.defaults() );
point1 = collidingPoints.first();
point2 = collidingPoints.other();
}

@AfterEach
void tearDown()
{
if ( db != null )
{
db.shutdown();
}
}

@ParameterizedTest
@MethodSource( "providerSettings" )
void shouldPopulateIndexWithUniquePointsThatCollideOnSpaceFillingCurve( GraphDatabaseSettings.SchemaIndex schemaIndex )
{
// given
setupDb( schemaIndex );
Pair<Long,Long> nodeIds = createUniqueNodes();

// when
createUniquenessConstraint();

// then
assertBothNodesArePresent( nodeIds );
}

@ParameterizedTest
@MethodSource( "providerSettings" )
void shouldAddPointsThatCollideOnSpaceFillingCurveToUniqueIndexInSameTx( GraphDatabaseSettings.SchemaIndex schemaIndex )
{
// given
setupDb( schemaIndex );
createUniquenessConstraint();

// when
Pair<Long,Long> nodeIds = createUniqueNodes();

// then
assertBothNodesArePresent( nodeIds );
}

@ParameterizedTest
@MethodSource( "providerSettings" )
void shouldThrowWhenPopulatingWithNonUniquePoints( GraphDatabaseSettings.SchemaIndex schemaIndex )
{
// given
setupDb( schemaIndex );
createNonUniqueNodes();

// then
assertThrows( ConstraintViolationException.class, this::createUniquenessConstraint);
}

@ParameterizedTest
@MethodSource( "providerSettings" )
void shouldThrowWhenAddingNonUniquePoints( GraphDatabaseSettings.SchemaIndex schemaIndex )
{
// given
setupDb( schemaIndex );
createUniquenessConstraint();

// when
assertThrows( ConstraintViolationException.class, this::createNonUniqueNodes );
}

private static Stream<GraphDatabaseSettings.SchemaIndex> providerSettings()
{
return Arrays.stream( GraphDatabaseSettings.SchemaIndex.values() );
}

private void createNonUniqueNodes()
{
try ( Transaction tx = db.beginTx() )
{
Node originNode = db.createNode( LABEL );
originNode.setProperty( KEY, point1 );
Node centerNode = db.createNode( LABEL );
centerNode.setProperty( KEY, point1 );
tx.success();
}
}

private Pair<Long,Long> createUniqueNodes()
{
Pair<Long,Long> nodeIds;
try ( Transaction tx = db.beginTx() )
{
Node originNode = db.createNode( LABEL );
originNode.setProperty( KEY, point1 );
Node centerNode = db.createNode( LABEL );
centerNode.setProperty( KEY, point2 );

nodeIds = Pair.of( originNode.getId(), centerNode.getId() );
tx.success();
}
return nodeIds;
}

private void assertBothNodesArePresent( Pair<Long,Long> nodeIds )
{
try ( Transaction tx = db.beginTx() )
{
ResourceIterator<Node> origin = db.findNodes( LABEL, KEY, point1 );
assertTrue( origin.hasNext() );
assertEquals( nodeIds.first().longValue(), origin.next().getId() );
assertFalse( origin.hasNext() );

ResourceIterator<Node> center = db.findNodes( LABEL, KEY, point2 );
assertTrue( center.hasNext() );
assertEquals( nodeIds.other().longValue(), center.next().getId() );
assertFalse( center.hasNext() );

tx.success();
}
}

private void createUniquenessConstraint()
{
try ( Transaction tx = db.beginTx() )
{
db.schema().constraintFor( TestLabels.LABEL_ONE ).assertPropertyIsUnique( KEY ).create();
tx.success();
}
try ( Transaction tx = db.beginTx() )
{
db.schema().awaitIndexesOnline( 1, TimeUnit.MINUTES );
tx.success();
}
}

private void setupDb( GraphDatabaseSettings.SchemaIndex schemaIndex )
{
TestGraphDatabaseFactory dbFactory = new TestGraphDatabaseFactory();
GraphDatabaseBuilder builder = dbFactory.newEmbeddedDatabaseBuilder( directory.storeDir() )
.setConfig( GraphDatabaseSettings.default_schema_provider, schemaIndex.providerName() );
db = builder.newGraphDatabase();
}
}
Expand Up @@ -25,19 +25,26 @@
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Parameterized; import org.junit.runners.Parameterized;


import java.io.File; import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;


import org.neo4j.graphdb.DependencyResolver; import org.neo4j.graphdb.DependencyResolver;
import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.ResourceIterator;
import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.graphdb.schema.IndexDefinition;
import org.neo4j.graphdb.schema.Schema;
import org.neo4j.helpers.collection.MapUtil;
import org.neo4j.helpers.collection.Pair;
import org.neo4j.internal.kernel.api.IndexReference; import org.neo4j.internal.kernel.api.IndexReference;
import org.neo4j.internal.kernel.api.SchemaRead; import org.neo4j.internal.kernel.api.SchemaRead;
import org.neo4j.internal.kernel.api.TokenRead; import org.neo4j.internal.kernel.api.TokenRead;
import org.neo4j.kernel.api.KernelTransaction; import org.neo4j.kernel.api.KernelTransaction;
import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.impl.core.ThreadToStatementContextBridge; import org.neo4j.kernel.impl.core.ThreadToStatementContextBridge;
import org.neo4j.kernel.impl.index.schema.config.SpatialIndexValueTestUtil;
import org.neo4j.kernel.internal.GraphDatabaseAPI; import org.neo4j.kernel.internal.GraphDatabaseAPI;
import org.neo4j.test.TestGraphDatabaseFactory; import org.neo4j.test.TestGraphDatabaseFactory;
import org.neo4j.test.TestLabels; import org.neo4j.test.TestLabels;
Expand All @@ -46,13 +53,18 @@
import org.neo4j.test.rule.fs.DefaultFileSystemRule; import org.neo4j.test.rule.fs.DefaultFileSystemRule;
import org.neo4j.unsafe.batchinsert.BatchInserter; import org.neo4j.unsafe.batchinsert.BatchInserter;
import org.neo4j.unsafe.batchinsert.BatchInserters; import org.neo4j.unsafe.batchinsert.BatchInserters;
import org.neo4j.values.storable.CoordinateReferenceSystem;
import org.neo4j.values.storable.PointValue;
import org.neo4j.values.storable.Values;


import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.neo4j.graphdb.factory.GraphDatabaseSettings.default_schema_provider; import static org.neo4j.graphdb.factory.GraphDatabaseSettings.default_schema_provider;
import static org.neo4j.helpers.collection.MapUtil.stringMap; import static org.neo4j.helpers.collection.MapUtil.stringMap;


@RunWith( Parameterized.class ) @RunWith( Parameterized.class )
public class BatchInsertIndexProviderTest public class BatchInsertIndexTest
{ {
private final GraphDatabaseSettings.SchemaIndex schemaIndex; private final GraphDatabaseSettings.SchemaIndex schemaIndex;
private DefaultFileSystemRule fileSystemRule = new DefaultFileSystemRule(); private DefaultFileSystemRule fileSystemRule = new DefaultFileSystemRule();
Expand All @@ -68,19 +80,20 @@ public static GraphDatabaseSettings.SchemaIndex[] data()
return GraphDatabaseSettings.SchemaIndex.values(); return GraphDatabaseSettings.SchemaIndex.values();
} }


public BatchInsertIndexProviderTest( GraphDatabaseSettings.SchemaIndex schemaIndex ) public BatchInsertIndexTest( GraphDatabaseSettings.SchemaIndex schemaIndex )
{ {
this.schemaIndex = schemaIndex; this.schemaIndex = schemaIndex;
} }


@Test @Test
public void batchInserterShouldUseConfiguredIndexProvider() throws Exception public void batchInserterShouldUseConfiguredIndexProvider() throws Exception
{ {
Map<String,String> config = stringMap( default_schema_provider.name(), schemaIndex.providerName() ); Config config = Config.defaults( stringMap( default_schema_provider.name(), schemaIndex.providerName() ) );
BatchInserter inserter = newBatchInserter( config ); BatchInserter inserter = newBatchInserter( config );
inserter.createDeferredSchemaIndex( TestLabels.LABEL_ONE ).on( "key" ).create(); inserter.createDeferredSchemaIndex( TestLabels.LABEL_ONE ).on( "key" ).create();
inserter.shutdown(); inserter.shutdown();
GraphDatabaseService db = graphDatabaseService( storeDir.databaseDir(), config ); GraphDatabaseService db = graphDatabaseService( config );
awaitIndexesOnline( db );
try ( Transaction tx = db.beginTx() ) try ( Transaction tx = db.beginTx() )
{ {
DependencyResolver dependencyResolver = ((GraphDatabaseAPI) db).getDependencyResolver(); DependencyResolver dependencyResolver = ((GraphDatabaseAPI) db).getDependencyResolver();
Expand All @@ -101,27 +114,84 @@ public void batchInserterShouldUseConfiguredIndexProvider() throws Exception
} }
} }


private BatchInserter newBatchInserter( Map<String,String> config ) throws Exception @Test
public void shouldPopulateIndexWithUniquePointsThatCollideOnSpaceFillingCurve() throws Exception
{ {
return BatchInserters.inserter( storeDir.databaseDir(), fileSystemRule.get(), config ); Config config = Config.defaults( stringMap( default_schema_provider.name(), schemaIndex.providerName() ) );
BatchInserter inserter = newBatchInserter( config );
Pair<PointValue,PointValue> collidingPoints = SpatialIndexValueTestUtil.pointsWithSameValueOnSpaceFillingCurve( config );
inserter.createNode( MapUtil.map( "prop", collidingPoints.first() ), TestLabels.LABEL_ONE );
inserter.createNode( MapUtil.map( "prop", collidingPoints.other() ), TestLabels.LABEL_ONE );
inserter.createDeferredConstraint( TestLabels.LABEL_ONE ).assertPropertyIsUnique( "prop" ).create();
inserter.shutdown();

GraphDatabaseService db = graphDatabaseService( config );
awaitIndexesOnline( db );
try ( Transaction tx = db.beginTx() )
{
assertSingleCorrectHit( db, collidingPoints.first() );
assertSingleCorrectHit( db, collidingPoints.other() );
tx.success();
}
} }


private GraphDatabaseService graphDatabaseService( File storeDir, Map<String, String> config ) @Test
public void shouldThrowWhenPopulatingWithNonUniquePoints() throws Exception
{
Config config = Config.defaults( stringMap( default_schema_provider.name(), schemaIndex.providerName() ) );
BatchInserter inserter = newBatchInserter( config );
PointValue point = Values.pointValue( CoordinateReferenceSystem.WGS84, 0.0, 0.0 );
inserter.createNode( MapUtil.map( "prop", point ), TestLabels.LABEL_ONE );
inserter.createNode( MapUtil.map( "prop", point ), TestLabels.LABEL_ONE );
inserter.createDeferredConstraint( TestLabels.LABEL_ONE ).assertPropertyIsUnique( "prop" ).create();
inserter.shutdown();

GraphDatabaseService db = graphDatabaseService( config );
try ( Transaction tx = db.beginTx() )
{
Iterator<IndexDefinition> indexes = db.schema().getIndexes().iterator();
assertTrue( indexes.hasNext() );
IndexDefinition index = indexes.next();
Schema.IndexState indexState = db.schema().getIndexState( index );
assertEquals( Schema.IndexState.FAILED, indexState );
assertFalse( indexes.hasNext() );
tx.success();
}

}

private void assertSingleCorrectHit( GraphDatabaseService db, PointValue point )
{
ResourceIterator<Node> nodes = db.findNodes( TestLabels.LABEL_ONE, "prop", point );
assertTrue( nodes.hasNext() );
Node node = nodes.next();
Object prop = node.getProperty( "prop" );
assertEquals( point, prop );
assertFalse( nodes.hasNext() );
}

private BatchInserter newBatchInserter( Config config ) throws Exception
{
return BatchInserters.inserter( storeDir.databaseDir(), fileSystemRule.get(), config.getRaw() );
}

private GraphDatabaseService graphDatabaseService( Config config )
{ {
TestGraphDatabaseFactory factory = new TestGraphDatabaseFactory(); TestGraphDatabaseFactory factory = new TestGraphDatabaseFactory();
factory.setFileSystem( fileSystemRule.get() ); factory.setFileSystem( fileSystemRule.get() );
GraphDatabaseService db = factory.newImpermanentDatabaseBuilder( storeDir ) return factory.newImpermanentDatabaseBuilder( storeDir.databaseDir() )
// Shouldn't be necessary to set dense node threshold since it's a stick config // Shouldn't be necessary to set dense node threshold since it's a stick config
.setConfig( config ) .setConfig( config.getRaw() )
.newGraphDatabase(); .newGraphDatabase();
}


private void awaitIndexesOnline( GraphDatabaseService db )
{
try ( Transaction tx = db.beginTx() ) try ( Transaction tx = db.beginTx() )
{ {
db.schema().awaitIndexesOnline( 10, TimeUnit.SECONDS ); db.schema().awaitIndexesOnline( 10, TimeUnit.SECONDS );
tx.success(); tx.success();
} }

return db;
} }


private static String unexpectedIndexProviderMessage( IndexReference index ) private static String unexpectedIndexProviderMessage( IndexReference index )
Expand Down

0 comments on commit d9654ce

Please sign in to comment.