diff --git a/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/impl/newapi/AbstractNodeValueIndexCursorTest.java b/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/impl/newapi/AbstractNodeValueIndexCursorTest.java index f33e1cf8edb9b..f0cb8c7e2a1cd 100644 --- a/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/impl/newapi/AbstractNodeValueIndexCursorTest.java +++ b/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/impl/newapi/AbstractNodeValueIndexCursorTest.java @@ -19,14 +19,22 @@ */ package org.neo4j.kernel.impl.newapi; +import org.neo4j.gis.spatial.index.curves.SpaceFillingCurve; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.internal.kernel.api.NodeValueIndexCursorTestBase; import org.neo4j.internal.kernel.api.SchemaWrite; import org.neo4j.internal.kernel.api.TokenWrite; import org.neo4j.kernel.api.KernelTransaction; import org.neo4j.kernel.api.schema.SchemaDescriptorFactory; +import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.impl.core.ThreadToStatementContextBridge; +import org.neo4j.kernel.impl.index.schema.config.ConfiguredSpaceFillingCurveSettingsCache; +import org.neo4j.kernel.impl.index.schema.config.SpaceFillingCurveSettings; import org.neo4j.kernel.internal.GraphDatabaseAPI; +import org.neo4j.values.storable.CoordinateReferenceSystem; +import org.neo4j.values.storable.PointValue; + +import static org.junit.Assert.assertEquals; abstract class AbstractNodeValueIndexCursorTest extends NodeValueIndexCursorTestBase { @@ -45,4 +53,13 @@ protected void createCompositeIndex( GraphDatabaseService graphDb, String label, token.propertyKeyGetOrCreateForName( "firstname" ), token.propertyKeyGetOrCreateForName( "surname" ) ) ); } + + @Override + protected void assertSameDerivedValue( PointValue p1, PointValue p2 ) + { + ConfiguredSpaceFillingCurveSettingsCache settingsFactory = new ConfiguredSpaceFillingCurveSettingsCache( Config.defaults() ); + SpaceFillingCurveSettings spaceFillingCurveSettings = settingsFactory.forCRS( CoordinateReferenceSystem.WGS84 ); + SpaceFillingCurve curve = spaceFillingCurveSettings.curve(); + assertEquals( curve.derivedValueFor( p1.coordinate() ), curve.derivedValueFor( p2.coordinate() ) ); + } } diff --git a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Read.java b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Read.java index 396340dd4fde0..4f9cb5a8d8adb 100644 --- a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Read.java +++ b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/Read.java @@ -45,8 +45,16 @@ void nodeIndexSeek( IndexReference index, NodeValueIndexCursor cursor, IndexOrde throws KernelException; /** - * Access all distinct counts in an index. Entries fed to the {@code cursor} will be (count,Value[]). - * For merely counting distinct values in an index, loop over and sum all counts. + * Access all distinct counts in an index. Entries fed to the {@code cursor} will be (count,Value[]), + * where the count (number of nodes having the particular value) will be accessed using {@link NodeValueIndexCursor#nodeReference()} + * and the value (if the index can provide it) using {@link NodeValueIndexCursor#propertyValue(int)}. + * Before accessing a property value the caller should check {@link NodeValueIndexCursor#hasValue()} to see + * whether or not the index could yield values. + * + * For merely counting distinct values in an index, loop over and sum iterations. + * For counting number of indexed nodes in an index, loop over and sum all counts. + * + * NOTE spatial values may not return 100% correctly in that some actually distinct points may be returned multiple times. * * @param index {@link IndexReference} referencing index. * @param cursor {@link NodeValueIndexCursor} receiving distinct count data. diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/NodeValueIndexCursorTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/NodeValueIndexCursorTestBase.java index e5753e9c653cc..603a071043ce4 100644 --- a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/NodeValueIndexCursorTestBase.java +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/NodeValueIndexCursorTestBase.java @@ -25,10 +25,8 @@ import org.eclipse.collections.impl.set.mutable.primitive.LongHashSet; import org.junit.Test; -import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; @@ -74,6 +72,14 @@ public abstract class NodeValueIndexCursorTestBase> stringAndNumberDistinctValues = new HashMap<>(); + + private static final PointValue POINT_1 = + PointValue.parse( "{latitude: 40.7128, longitude: -74.0060, crs: 'wgs-84'}" ); + private static final PointValue POINT_2 = + PointValue.parse( "{latitude: 40.7128, longitude: -74.006000001, crs: 'wgs-84'}" ); + @Override public void createTestGraph( GraphDatabaseService graphDb ) { @@ -81,6 +87,7 @@ public void createTestGraph( GraphDatabaseService graphDb ) { graphDb.schema().indexFor( label( "Node" ) ).on( "prop" ).create(); graphDb.schema().indexFor( label( "Node" ) ).on( "prop2" ).create(); + graphDb.schema().indexFor( label( "Node" ) ).on( "prop3" ).create(); tx.success(); } try ( Transaction tx = graphDb.beginTx() ) @@ -158,6 +165,20 @@ public void createTestGraph( GraphDatabaseService graphDb ) listOfIds.add(nodeWithWhatever( graphDb, new String[]{"first", "second", "third"} )); nodesOfAllPropertyTypes = listOfIds.toArray(); + + ThreadLocalRandom random = ThreadLocalRandom.current(); + for ( int i = 0; i < STRING_AND_NUMBER_DISTINCT_VALUES; i++ ) + { + Object value = random.nextBoolean() ? String.valueOf( i % 10 ) : (i % 10); + long nodeId = nodeWithProp( graphDb, "prop2", value ); + stringAndNumberDistinctValues.computeIfAbsent( Values.of( value ), v -> new HashSet<>() ).add( nodeId ); + } + + assertSameDerivedValue( POINT_1, POINT_2 ); + nodeWithProp( graphDb, "prop3", POINT_1.asObjectCopy() ); + nodeWithProp( graphDb, "prop3", POINT_2.asObjectCopy() ); + nodeWithProp( graphDb, "prop3", POINT_2.asObjectCopy() ); + tx.success(); } } @@ -190,6 +211,7 @@ protected boolean indexProvidesTemporalValues() { return true; } + protected abstract void assertSameDerivedValue( PointValue p1, PointValue p2 ); protected boolean indexProvidesSpatialValues() { @@ -1446,24 +1468,7 @@ public void shouldCountDistinctValues() throws Exception int label = token.nodeLabel( "Node" ); int key = token.propertyKey( "prop2" ); IndexReference index = schemaRead.index( label, key ); - Map> expected = new HashMap<>(); - int expectedTotalCount = 100; - List createdNodes = new ArrayList<>(); - try ( org.neo4j.internal.kernel.api.Transaction tx = beginTransaction() ) - { - ThreadLocalRandom random = ThreadLocalRandom.current(); - Write write = tx.dataWrite(); - for ( int i = 0; i < expectedTotalCount; i++ ) - { - long nodeId = write.nodeCreate(); - write.nodeAddLabel( nodeId, label ); - Value value = Values.of( random.nextBoolean() ? String.valueOf( i % 10 ) : (i % 10) ); - write.nodeSetProperty( nodeId, key, value ); - expected.computeIfAbsent( value, v -> new HashSet<>() ).add( nodeId ); - createdNodes.add( nodeId ); - } - tx.success(); - } + Map> expected = new HashMap<>( stringAndNumberDistinctValues ); // then try ( org.neo4j.internal.kernel.api.Transaction tx = beginTransaction(); @@ -1494,10 +1499,8 @@ public void shouldCountDistinctValues() throws Exception { assertTrue( expected.toString(), expected.isEmpty() ); } - assertEquals( expectedTotalCount, totalCount ); + assertEquals( STRING_AND_NUMBER_DISTINCT_VALUES, totalCount ); } - // the tests in this class share and modify the very same graph, so restore it - deleteNodes( createdNodes ); } @Test @@ -1505,42 +1508,13 @@ public void shouldCountDistinctButSimilarPointValues() throws Exception { // given int label = token.nodeLabel( "Node" ); - int key = token.propertyKey( "prop2" ); + int key = token.propertyKey( "prop3" ); IndexReference index = schemaRead.index( label, key ); - Value p1 = PointValue.parse( "{latitude: 40.7128, longitude: -74.0060, crs: 'wgs-84'}" ); - Value p2 = PointValue.parse( "{latitude: 40.7128, longitude: -74.006000001, crs: 'wgs-84'}" ); - List createdNodes = new ArrayList<>(); - try ( org.neo4j.internal.kernel.api.Transaction tx = beginTransaction() ) - { - Write write = tx.dataWrite(); - // point 1 - { - long node1 = write.nodeCreate(); - write.nodeAddLabel( node1, label ); - write.nodeSetProperty( node1, key, p1 ); - createdNodes.add( node1 ); - } - // point 2 - { - long node2 = write.nodeCreate(); - write.nodeAddLabel( node2, label ); - write.nodeSetProperty( node2, key, p2 ); - createdNodes.add( node2 ); - } - // point 2 - { - long node3 = write.nodeCreate(); - write.nodeAddLabel( node3, label ); - write.nodeSetProperty( node3, key, p2 ); - createdNodes.add( node3 ); - } - tx.success(); - } // when Map expected = new HashMap<>(); - expected.put( p1, 1 ); - expected.put( p2, 2 ); + expected.put( POINT_1, 1 ); + expected.put( POINT_2, 2 ); try ( org.neo4j.internal.kernel.api.Transaction tx = beginTransaction(); NodeValueIndexCursor node = cursors.allocateNodeValueIndexCursor() ) { @@ -1555,25 +1529,17 @@ public void shouldCountDistinctButSimilarPointValues() throws Exception } assertTrue( expected.isEmpty() ); } - deleteNodes( createdNodes ); } - private void deleteNodes( List createdNodes ) throws Exception + private long nodeWithProp( GraphDatabaseService graphDb, Object value ) { - try ( org.neo4j.internal.kernel.api.Transaction tx = beginTransaction() ) - { - for ( Long createdNode : createdNodes ) - { - tx.dataWrite().nodeDelete( createdNode ); - } - tx.success(); - } + return nodeWithProp( graphDb, "prop", value ); } - private long nodeWithProp( GraphDatabaseService graphDb, Object value ) + private long nodeWithProp( GraphDatabaseService graphDb, String key, Object value ) { Node node = graphDb.createNode( label( "Node" ) ); - node.setProperty( "prop", value ); + node.setProperty( key, value ); return node.getId(); }