diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/config/SpatialIndexValueTestUtil.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/config/SpatialIndexValueTestUtil.java
new file mode 100644
index 0000000000000..799f58a566924
--- /dev/null
+++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/config/SpatialIndexValueTestUtil.java
@@ -0,0 +1,48 @@
+/*
+ * 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 .
+ */
+package org.neo4j.kernel.impl.index.schema.config;
+
+import org.neo4j.gis.spatial.index.curves.SpaceFillingCurve;
+import org.neo4j.helpers.collection.Pair;
+import org.neo4j.kernel.configuration.Config;
+import org.neo4j.values.storable.CoordinateReferenceSystem;
+import org.neo4j.values.storable.PointValue;
+import org.neo4j.values.storable.Values;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
+public class SpatialIndexValueTestUtil
+{
+ public static Pair pointsWithSameValueOnSpaceFillingCurve( Config config )
+ {
+ SpaceFillingCurveSettingsFactory spaceFillingCurveSettingsFactory = new SpaceFillingCurveSettingsFactory( config );
+ SpaceFillingCurveSettings spaceFillingCurveSettings = spaceFillingCurveSettingsFactory.settingsFor( CoordinateReferenceSystem.WGS84 );
+ SpaceFillingCurve curve = spaceFillingCurveSettings.curve();
+ double[] origin = {0.0, 0.0};
+ Long spaceFillingCurveMapForOrigin = curve.derivedValueFor( origin );
+ double[] centerPointForOriginTile = curve.centerPointFor( spaceFillingCurveMapForOrigin );
+ PointValue originValue = Values.pointValue( CoordinateReferenceSystem.WGS84, origin );
+ PointValue centerPointValue = Values.pointValue( CoordinateReferenceSystem.WGS84, centerPointForOriginTile );
+ assertThat( "need non equal points for this test", origin, not( equalTo( centerPointValue ) ) );
+ return Pair.of( originValue, centerPointValue );
+ }
+}
diff --git a/community/lucene-index/src/test/java/org/neo4j/kernel/api/impl/schema/UniqueSpatialIndexIT.java b/community/lucene-index/src/test/java/org/neo4j/kernel/api/impl/schema/UniqueSpatialIndexIT.java
new file mode 100644
index 0000000000000..f9bfa9a64d965
--- /dev/null
+++ b/community/lucene-index/src/test/java/org/neo4j/kernel/api/impl/schema/UniqueSpatialIndexIT.java
@@ -0,0 +1,220 @@
+/*
+ * 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 .
+ */
+package org.neo4j.kernel.api.impl.schema;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.concurrent.TimeUnit;
+
+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.rule.TestDirectory;
+import org.neo4j.values.storable.PointValue;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith( Parameterized.class )
+public class UniqueSpatialIndexIT
+{
+ private static final String KEY = "prop";
+ private static final TestLabels LABEL = TestLabels.LABEL_ONE;
+
+ @Parameterized.Parameters( name = "{0}" )
+ public static GraphDatabaseSettings.SchemaIndex[] schemaIndexes()
+ {
+ return GraphDatabaseSettings.SchemaIndex.values();
+ }
+
+ @Parameterized.Parameter( 0 )
+ public GraphDatabaseSettings.SchemaIndex schemaIndex;
+
+ @Rule
+ public TestDirectory directory = TestDirectory.testDirectory();
+ private GraphDatabaseService db;
+ private PointValue point1;
+ private PointValue point2;
+
+ @Before
+ public void setup()
+ {
+ Pair collidingPoints = SpatialIndexValueTestUtil.pointsWithSameValueOnSpaceFillingCurve( Config.defaults() );
+ point1 = collidingPoints.first();
+ point2 = collidingPoints.other();
+ }
+
+ @After
+ public void tearDown()
+ {
+ if ( db != null )
+ {
+ db.shutdown();
+ }
+ }
+
+ @Test
+ public void shouldPopulateIndexWithUniquePointsThatCollideOnSpaceFillingCurve()
+ {
+ // given
+ setupDb( schemaIndex );
+ Pair nodeIds = createUniqueNodes();
+
+ // when
+ createUniquenessConstraint();
+
+ // then
+ assertBothNodesArePresent( nodeIds );
+ }
+
+ @Test
+ public void shouldAddPointsThatCollideOnSpaceFillingCurveToUniqueIndexInSameTx()
+ {
+ // given
+ setupDb( schemaIndex );
+ createUniquenessConstraint();
+
+ // when
+ Pair nodeIds = createUniqueNodes();
+
+ // then
+ assertBothNodesArePresent( nodeIds );
+ }
+
+ @Test
+ public void shouldThrowWhenPopulatingWithNonUniquePoints()
+ {
+ // given
+ setupDb( schemaIndex );
+ createNonUniqueNodes();
+
+ // then
+ try
+ {
+ createUniquenessConstraint();
+ fail( "Should have failed" );
+ }
+ catch ( ConstraintViolationException e )
+ { // good
+ }
+ }
+
+ @Test
+ public void shouldThrowWhenAddingNonUniquePoints()
+ {
+ // given
+ setupDb( schemaIndex );
+ createUniquenessConstraint();
+
+ // when
+ try
+ {
+ createNonUniqueNodes();
+ fail( "Should have failed" );
+ }
+ catch ( ConstraintViolationException e )
+ { // good
+ }
+ }
+
+ 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 createUniqueNodes()
+ {
+ Pair 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 nodeIds )
+ {
+ try ( Transaction tx = db.beginTx() )
+ {
+ ResourceIterator origin = db.findNodes( LABEL, KEY, point1 );
+ assertTrue( origin.hasNext() );
+ assertEquals( nodeIds.first().longValue(), origin.next().getId() );
+ assertFalse( origin.hasNext() );
+
+ ResourceIterator 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.graphDbDir() )
+ .setConfig( GraphDatabaseSettings.default_schema_provider, schemaIndex.providerName() );
+ db = builder.newGraphDatabase();
+ }
+}
diff --git a/community/lucene-index/src/test/java/org/neo4j/unsafe/batchinsert/internal/BatchInsertIndexProviderTest.java b/community/lucene-index/src/test/java/org/neo4j/unsafe/batchinsert/internal/BatchInsertIndexProviderTest.java
index 34275bb22b981..ef5d5e4ba8845 100644
--- a/community/lucene-index/src/test/java/org/neo4j/unsafe/batchinsert/internal/BatchInsertIndexProviderTest.java
+++ b/community/lucene-index/src/test/java/org/neo4j/unsafe/batchinsert/internal/BatchInsertIndexProviderTest.java
@@ -28,18 +28,26 @@
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Map;
+import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import org.neo4j.graphdb.DependencyResolver;
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.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.CapableIndexReference;
import org.neo4j.internal.kernel.api.SchemaRead;
import org.neo4j.internal.kernel.api.TokenRead;
import org.neo4j.kernel.api.KernelTransaction;
+import org.neo4j.kernel.configuration.Config;
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.test.TestGraphDatabaseFactory;
import org.neo4j.test.TestLabels;
@@ -48,7 +56,12 @@
import org.neo4j.test.rule.fs.DefaultFileSystemRule;
import org.neo4j.unsafe.batchinsert.BatchInserter;
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.neo4j.graphdb.factory.GraphDatabaseSettings.default_schema_provider;
import static org.neo4j.helpers.collection.MapUtil.stringMap;
@@ -82,11 +95,12 @@ public BatchInsertIndexProviderTest( GraphDatabaseSettings.SchemaIndex schemaInd
@Test
public void batchInserterShouldUseConfiguredIndexProvider() throws Exception
{
- Map config = stringMap( default_schema_provider.name(), schemaIndex.providerName() );
+ Config config = Config.defaults( stringMap( default_schema_provider.name(), schemaIndex.providerName() ) );
BatchInserter inserter = newBatchInserter( config );
inserter.createDeferredSchemaIndex( TestLabels.LABEL_ONE ).on( "key" ).create();
inserter.shutdown();
GraphDatabaseService db = graphDatabaseService( inserter.getStoreDir(), config );
+ awaitIndexesOnline( db );
try ( Transaction tx = db.beginTx() )
{
DependencyResolver dependencyResolver = ((GraphDatabaseAPI) db).getDependencyResolver();
@@ -107,27 +121,94 @@ public void batchInserterShouldUseConfiguredIndexProvider() throws Exception
}
}
- private BatchInserter newBatchInserter( Map config ) throws Exception
+ @Test
+ public void shouldPopulateIndexWithUniquePointsThatCollideOnSpaceFillingCurve() throws Exception
+ {
+ Config config = Config.defaults( stringMap( default_schema_provider.name(), schemaIndex.providerName() ) );
+ BatchInserter inserter = newBatchInserter( config );
+ Pair 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( inserter.getStoreDir(), config );
+ try
+ {
+ awaitIndexesOnline( db );
+ try ( Transaction tx = db.beginTx() )
+ {
+ assertSingleCorrectHit( db, collidingPoints.first() );
+ assertSingleCorrectHit( db, collidingPoints.other() );
+ tx.success();
+ }
+ }
+ finally
+ {
+ db.shutdown();
+ }
+ }
+
+ @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( inserter.getStoreDir(), config );
+ try ( Transaction tx = db.beginTx() )
+ {
+ Iterator 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();
+ }
+ finally
+ {
+ db.shutdown();
+ }
+ }
+
+ private void assertSingleCorrectHit( GraphDatabaseService db, PointValue point )
{
- return BatchInserters.inserter( storeDir.absolutePath(), fileSystemRule.get(), config );
+ ResourceIterator 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 GraphDatabaseService graphDatabaseService( String storeDir, Map config )
+ private BatchInserter newBatchInserter( Config config ) throws Exception
+ {
+ return BatchInserters.inserter( storeDir.absolutePath(), fileSystemRule.get(), config.getRaw() );
+ }
+
+ private GraphDatabaseService graphDatabaseService( String storeDir, Config config )
{
TestGraphDatabaseFactory factory = new TestGraphDatabaseFactory();
factory.setFileSystem( fileSystemRule.get() );
- GraphDatabaseService db = factory.newImpermanentDatabaseBuilder( new File( storeDir ) )
+ return factory.newImpermanentDatabaseBuilder( new File( storeDir ) )
// Shouldn't be necessary to set dense node threshold since it's a stick config
- .setConfig( config )
+ .setConfig( config.getRaw() )
.newGraphDatabase();
+ }
+ private void awaitIndexesOnline( GraphDatabaseService db )
+ {
try ( Transaction tx = db.beginTx() )
{
db.schema().awaitIndexesOnline( 10, TimeUnit.SECONDS );
tx.success();
}
-
- return db;
}
private String unexpectedIndexProviderMessage( CapableIndexReference index )