From c48165119385f50830997b512f95c69ab91db3fb Mon Sep 17 00:00:00 2001 From: Anton Persson Date: Fri, 2 Nov 2018 16:35:53 +0100 Subject: [PATCH] Generic index also compare point coordinates Before, only space filling curve value was compared. This means index itself can not verify if two points are unique or not but instead need help from outside. Problem arise during index population where we trust index to be able to tell us if a value is violating uniqueness constraint or not. This is now fixed for Generic index. We compare the serialized coordinate values (long) instead of the deserialized once (double) because there is no need for us to convert them to actual coordinates. We only need to provide some deterministic order in which points are stored that keep points that are not exactly equal separate from each other. This also means that coordinate need to propagate to internal nodes and can not be removed in minimalSplitter. This is a format change for native-btree-1.0 index provider and therefore we bump GenericLayout.minor version. --- .../impl/index/schema/GenericLayout.java | 2 +- .../impl/index/schema/GeometryArrayType.java | 17 +- .../impl/index/schema/GeometryType.java | 56 ++-- .../schema/GenericAccessorPointsTest.java | 239 ++++++++++++++++++ .../index/schema/GenericKeyStateTest.java | 66 ++++- .../current-generic-key-state-format.zip | Bin 2145 -> 2100 bytes 6 files changed, 349 insertions(+), 31 deletions(-) create mode 100644 community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/GenericAccessorPointsTest.java diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GenericLayout.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GenericLayout.java index 0c647b6a9f193..e68aade4ad7e4 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GenericLayout.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GenericLayout.java @@ -29,7 +29,7 @@ class GenericLayout extends IndexLayout GenericLayout( int numberOfSlots, IndexSpecificSpaceFillingCurveSettingsCache spatialSettings ) { - super( "NSIL", 0, 4 ); + super( "NSIL", 0, 5 ); this.numberOfSlots = numberOfSlots; this.spatialSettings = spatialSettings; } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GeometryArrayType.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GeometryArrayType.java index 75000e68d7d2e..0564a283cfa11 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GeometryArrayType.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GeometryArrayType.java @@ -36,6 +36,7 @@ import static org.neo4j.collection.PrimitiveLongCollections.EMPTY_LONG_ARRAY; import static org.neo4j.kernel.impl.index.schema.GeometryType.assertHasCoordinates; import static org.neo4j.kernel.impl.index.schema.GeometryType.dimensions; +import static org.neo4j.kernel.impl.index.schema.GeometryType.hasCoordinates; import static org.neo4j.kernel.impl.index.schema.GeometryType.putCrs; import static org.neo4j.kernel.impl.index.schema.GeometryType.putPoint; import static org.neo4j.kernel.impl.index.schema.GeometryType.readCrs; @@ -61,8 +62,8 @@ class GeometryArrayType extends AbstractArrayType { super( ValueGroup.GEOMETRY_ARRAY, typeId, ( o1, o2, i ) -> GeometryType.compare( // intentional long1 and long2 - not the array versions - o1.long0Array[i], o1.long1, o1.long2, - o2.long0Array[i], o2.long1, o2.long2 ), + o1.long0Array[i], o1.long1, o1.long2, o1.long3, o1.long1Array, (int) o1.long3 * i, + o2.long0Array[i], o2.long1, o2.long2, o2.long3, o2.long1Array, (int) o2.long3 * i ), null, null, null, null, null ); } @@ -137,17 +138,9 @@ boolean readValue( PageCursor cursor, int size, GenericKey into ) @Override String toString( GenericKey state ) { + String asValueString = hasCoordinates( state ) ? asValue( state ).toString() : "NO_COORDINATES"; return format( "GeometryArray[tableId:%d, code:%d, rawValues:%s, value:%s]", - state.long1, state.long2, Arrays.toString( Arrays.copyOf( state.long0Array, state.arrayLength ) ), asValue( state ) ); - } - - @Override - void minimalSplitter( GenericKey left, GenericKey right, GenericKey into ) - { - super.minimalSplitter( left, right, into ); - // Set dimensions to 0 so that minimal splitters (i.e. point keys in internal nodes) doesn't have coordinate data, - // they don't need it since values aren't generated from internal keys anyway. - into.long3 = 0; + state.long1, state.long2, Arrays.toString( Arrays.copyOf( state.long0Array, state.arrayLength ) ), asValueString ); } private static boolean readGeometryArrayItem( PageCursor cursor, GenericKey into ) diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GeometryType.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GeometryType.java index 2501adcffaba6..9bfba19ca0f99 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GeometryType.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GeometryType.java @@ -103,8 +103,8 @@ static PointValue asValue( GenericKey state, CoordinateReferenceSystem crs, int int compareValue( GenericKey left, GenericKey right ) { return compare( - left.long0, left.long1, left.long2, - right.long0, right.long1, right.long2 ); + left.long0, left.long1, left.long2, left.long3, left.long1Array, 0, + right.long0, right.long1, right.long2, right.long3, right.long1Array, 0 ); } @Override @@ -123,26 +123,21 @@ boolean readValue( PageCursor cursor, int size, GenericKey into ) @Override String toString( GenericKey state ) { - return format( "Geometry[tableId:%d, code:%d, rawValue:%d, value:%s", state.long1, state.long2, state.long0, asValue( state ) ); - } - - @Override - void minimalSplitter( GenericKey left, GenericKey right, GenericKey into ) - { - super.minimalSplitter( left, right, into ); - // Set dimensions to 0 so that minimal splitters (i.e. point keys in internal nodes) doesn't have coordinate data, - // they don't need it since values aren't generated from internal keys anyway. - into.long3 = 0; + String asValueString = hasCoordinates( state ) ? asValue( state ).toString() : "NO_COORDINATES"; + return format( "Geometry[tableId:%d, code:%d, rawValue:%d, value:%s", state.long1, state.long2, state.long0, asValueString ); } /** * This method will compare along the curve, which is not a spatial comparison, but is correct * for comparison within the space filling index as long as the original spatial range has already * been decomposed into a collection of 1D curve ranges before calling down into the GPTree. + * If value on space filling curve is equal then raw comparison of serialized coordinates is done. + * This way points are only considered equal in index if they have the same coordinates and not if + * they only happen to occupy same value on space filling curve. */ static int compare( - long this_long0, long this_long1, long this_long2, - long that_long0, long that_long1, long that_long2 ) + long this_long0, long this_long1, long this_long2, long this_long3, long[] this_long1Array, int this_coordinates_offset, + long that_long0, long that_long1, long that_long2, long that_long3, long[] that_long1Array, int that_coordinates_offset ) { int tableIdComparison = Integer.compare( (int) this_long1, (int) that_long1 ); if ( tableIdComparison != 0 ) @@ -156,7 +151,26 @@ static int compare( return codeComparison; } - return Long.compare( this_long0, that_long0 ); + int derivedValueComparison = Long.compare( this_long0, that_long0 ); + if ( derivedValueComparison != 0 ) + { + return derivedValueComparison; + } + + long dimensions = Math.min( this_long3, that_long3 ); + for ( int i = 0; i < dimensions; i++ ) + { + // It's ok to compare the coordinate value here without deserializing them + // because we are only defining SOME deterministic order so that we can + // correctly separate unique points from each other, even if they collide + // on the space filling curve. + int coordinateComparison = Long.compare( this_long1Array[this_coordinates_offset + i], that_long1Array[that_coordinates_offset + i] ); + if ( coordinateComparison != 0 ) + { + return coordinateComparison; + } + } + return 0; } static void putCrs( PageCursor cursor, long long1, long long2, long long3 ) @@ -196,12 +210,22 @@ static void putPoint( PageCursor cursor, long long0, long long3, long[] long1Arr */ static void assertHasCoordinates( GenericKey state ) { - if ( state.long3 == 0 || state.long1Array == null ) + if ( !hasCoordinates( state ) ) { throw new IllegalStateException( "This geometry key doesn't have coordinates and can therefore neither be persisted nor generate point value." ); } } + static boolean hasCoordinates( GenericKey state ) + { + return state.long3 != 0 && state.long1Array != null; + } + + static void setNoCoordinates( GenericKey state ) + { + state.long3 = 0; + } + private static void put3BInt( PageCursor cursor, int value ) { cursor.putShort( (short) value ); diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/GenericAccessorPointsTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/GenericAccessorPointsTest.java new file mode 100644 index 0000000000000..033eb12a5b479 --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/GenericAccessorPointsTest.java @@ -0,0 +1,239 @@ +/* + * 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; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.neo4j.gis.spatial.index.curves.SpaceFillingCurve; +import org.neo4j.gis.spatial.index.curves.StandardConfiguration; +import org.neo4j.index.internal.gbptree.RecoveryCleanupWorkCollector; +import org.neo4j.internal.kernel.api.IndexOrder; +import org.neo4j.internal.kernel.api.IndexQuery; +import org.neo4j.internal.kernel.api.exceptions.schema.IndexNotApplicableKernelException; +import org.neo4j.io.fs.DefaultFileSystemAbstraction; +import org.neo4j.io.pagecache.PageCache; +import org.neo4j.kernel.api.exceptions.index.IndexEntryConflictException; +import org.neo4j.kernel.api.index.IndexDirectoryStructure; +import org.neo4j.kernel.api.index.IndexEntryUpdate; +import org.neo4j.kernel.api.schema.index.TestIndexDescriptorFactory; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.impl.api.index.IndexUpdateMode; +import org.neo4j.kernel.impl.index.schema.config.ConfiguredSpaceFillingCurveSettingsCache; +import org.neo4j.kernel.impl.index.schema.config.IndexSpecificSpaceFillingCurveSettingsCache; +import org.neo4j.storageengine.api.schema.IndexReader; +import org.neo4j.storageengine.api.schema.SimpleNodeValueClient; +import org.neo4j.storageengine.api.schema.StoreIndexDescriptor; +import org.neo4j.test.rule.PageCacheRule; +import org.neo4j.test.rule.RandomRule; +import org.neo4j.test.rule.TestDirectory; +import org.neo4j.test.rule.fs.DefaultFileSystemRule; +import org.neo4j.values.storable.CoordinateReferenceSystem; +import org.neo4j.values.storable.PointArray; +import org.neo4j.values.storable.PointValue; +import org.neo4j.values.storable.Value; +import org.neo4j.values.storable.Values; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.rules.RuleChain.outerRule; +import static org.neo4j.kernel.api.index.IndexProvider.Monitor.EMPTY; +import static org.neo4j.test.rule.PageCacheRule.config; +import static org.neo4j.values.storable.CoordinateReferenceSystem.WGS84; + +public class GenericAccessorPointsTest +{ + private static final CoordinateReferenceSystem crs = CoordinateReferenceSystem.WGS84; + private static final ConfiguredSpaceFillingCurveSettingsCache configuredSettings = new ConfiguredSpaceFillingCurveSettingsCache( Config.defaults() ); + private static final IndexSpecificSpaceFillingCurveSettingsCache indexSettings = + new IndexSpecificSpaceFillingCurveSettingsCache( configuredSettings, new HashMap<>() ); + private static final SpaceFillingCurve curve = indexSettings.forCrs( crs, true ); + + private final DefaultFileSystemRule fs = new DefaultFileSystemRule(); + private final TestDirectory directory = TestDirectory.testDirectory( getClass(), fs.get() ); + private final PageCacheRule pageCacheRule = new PageCacheRule( config().withAccessChecks( true ) ); + private final RandomRule random = new RandomRule(); + @Rule + public final RuleChain rules = outerRule( random ).around( fs ).around( directory ).around( pageCacheRule ); + + private NativeIndexAccessor accessor; + private StoreIndexDescriptor descriptor; + + @Before + public void setup() + { + DefaultFileSystemAbstraction fs = this.fs.get(); + PageCache pc = pageCacheRule.getPageCache( fs ); + File file = directory.file( "index" ); + GenericLayout layout = new GenericLayout( 1, indexSettings ); + RecoveryCleanupWorkCollector collector = RecoveryCleanupWorkCollector.ignore(); + descriptor = TestIndexDescriptorFactory.forLabel( 1, 1 ).withId( 1 ); + IndexDirectoryStructure.Factory factory = IndexDirectoryStructure.directoriesByProvider( directory.storeDir() ); + IndexDirectoryStructure structure = factory.forProvider( GenericNativeIndexProvider.DESCRIPTOR ); + accessor = new GenericNativeIndexAccessor( pc, fs, file, layout, collector, EMPTY, descriptor, indexSettings, structure, new StandardConfiguration() ); + } + + @After + public void tearDown() + { + accessor.close(); + } + + /** + * This test verify that we correctly handle unique points that all belong to the same tile on the space filling curve. + * All points share at least one dimension coordinate with another point to exercise minimal splitter. + * We verify this by asserting that we always get exactly one hit on an exact match and that the value is what we expect. + */ + @Test + public void mustHandlePointsWithinSameTile() throws IndexEntryConflictException, IndexNotApplicableKernelException + { + // given + // Many random points that all are close enough to each other to belong to the same tile on the space filling curve. + int nbrOfValues = 10000; + PointValue origin = Values.pointValue( WGS84, 0.0, 0.0 ); + Long derivedValueForCenterPoint = curve.derivedValueFor( origin.coordinate() ); + double[] centerPoint = curve.centerPointFor( derivedValueForCenterPoint ); + double xWidthMultiplier = curve.getTileWidth( 0, curve.getMaxLevel() ) / 2; + double yWidthMultiplier = curve.getTileWidth( 1, curve.getMaxLevel() ) / 2; + + List pointValues = new ArrayList<>(); + List> updates = new ArrayList<>(); + long nodeId = 1; + for ( int i = 0; i < nbrOfValues / 4; i++ ) + { + double x1 = (random.nextDouble() * 2 - 1) * xWidthMultiplier; + double x2 = (random.nextDouble() * 2 - 1) * xWidthMultiplier; + double y1 = (random.nextDouble() * 2 - 1) * yWidthMultiplier; + double y2 = (random.nextDouble() * 2 - 1) * yWidthMultiplier; + PointValue value11 = Values.pointValue( WGS84, centerPoint[0] + x1, centerPoint[1] + y1 ); + PointValue value12 = Values.pointValue( WGS84, centerPoint[0] + x1, centerPoint[1] + y2 ); + PointValue value21 = Values.pointValue( WGS84, centerPoint[0] + x2, centerPoint[1] + y1 ); + PointValue value22 = Values.pointValue( WGS84, centerPoint[0] + x2, centerPoint[1] + y2 ); + assertDerivedValue( derivedValueForCenterPoint, value11, value12, value21, value22 ); + + nodeId = addPointsToLists( pointValues, updates, nodeId, value11, value12, value21, value22 ); + } + + processAll( updates ); + + // then + exactMatchOnAllValues( pointValues ); + } + + /** + * This test verify that we correctly handle unique point arrays where every point in every array belong to the same tile on the space filling curve. + * We verify this by asserting that we always get exactly one hit on an exact match and that the value is what we expect. + */ + @Test + public void mustHandlePointArraysWithinSameTile() throws IndexEntryConflictException, IndexNotApplicableKernelException + { + // given + // Many random points that all are close enough to each other to belong to the same tile on the space filling curve. + int nbrOfValues = 10000; + PointValue origin = Values.pointValue( WGS84, 0.0, 0.0 ); + Long derivedValueForCenterPoint = curve.derivedValueFor( origin.coordinate() ); + double[] centerPoint = curve.centerPointFor( derivedValueForCenterPoint ); + double xWidthMultiplier = curve.getTileWidth( 0, curve.getMaxLevel() ) / 2; + double yWidthMultiplier = curve.getTileWidth( 1, curve.getMaxLevel() ) / 2; + + List pointArrays = new ArrayList<>(); + List> updates = new ArrayList<>(); + for ( int i = 0; i < nbrOfValues; i++ ) + { + int arrayLength = random.nextInt( 5 ) + 1; + PointValue[] pointValues = new PointValue[arrayLength]; + for ( int j = 0; j < arrayLength; j++ ) + { + double x = (random.nextDouble() * 2 - 1) * xWidthMultiplier; + double y = (random.nextDouble() * 2 - 1) * yWidthMultiplier; + PointValue value = Values.pointValue( WGS84, centerPoint[0] + x, centerPoint[1] + y ); + + assertDerivedValue( derivedValueForCenterPoint, value ); + pointValues[j] = value; + } + PointArray array = Values.pointArray( pointValues ); + pointArrays.add( array ); + updates.add( IndexEntryUpdate.add( i, descriptor, array ) ); + } + + processAll( updates ); + + // then + exactMatchOnAllValues( pointArrays ); + } + + private long addPointsToLists( List pointValues, List> updates, long nodeId, PointValue... values ) + { + for ( PointValue value : values ) + { + pointValues.add( value ); + updates.add( IndexEntryUpdate.add( nodeId++, descriptor, value ) ); + } + return nodeId; + } + + private void assertDerivedValue( Long targetDerivedValue, PointValue... values ) + { + for ( PointValue value : values ) + { + Long derivedValueForValue = curve.derivedValueFor( value.coordinate() ); + assertEquals( targetDerivedValue, derivedValueForValue, "expected random value to belong to same tile as center point" ); + } + } + + private void processAll( List> updates ) throws IndexEntryConflictException + { + try ( NativeIndexUpdater updater = accessor.newUpdater( IndexUpdateMode.ONLINE ) ) + { + for ( IndexEntryUpdate update : updates ) + { + //noinspection unchecked + updater.process( update ); + } + } + } + + private void exactMatchOnAllValues( List values ) throws IndexNotApplicableKernelException + { + try ( IndexReader indexReader = accessor.newReader() ) + { + SimpleNodeValueClient client = new SimpleNodeValueClient(); + for ( Value value : values ) + { + IndexQuery.ExactPredicate exact = IndexQuery.exact( descriptor.schema().getPropertyId(), value ); + indexReader.query( client, IndexOrder.NONE, true, exact ); + + // then + assertTrue( client.next() ); + assertEquals( value, client.values[0] ); + assertFalse( client.next() ); + } + } + } +} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/GenericKeyStateTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/GenericKeyStateTest.java index a49f106e26720..5cded9e371544 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/GenericKeyStateTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/index/schema/GenericKeyStateTest.java @@ -39,6 +39,7 @@ import java.util.List; import java.util.stream.Stream; +import org.neo4j.gis.spatial.index.curves.SpaceFillingCurve; import org.neo4j.io.pagecache.ByteArrayPageCursor; import org.neo4j.io.pagecache.PageCache; import org.neo4j.io.pagecache.PageCursor; @@ -49,11 +50,14 @@ import org.neo4j.test.extension.Inject; import org.neo4j.test.extension.RandomExtension; import org.neo4j.test.rule.RandomRule; +import org.neo4j.values.AnyValues; +import org.neo4j.values.storable.CoordinateReferenceSystem; import org.neo4j.values.storable.DateTimeValue; import org.neo4j.values.storable.DateValue; import org.neo4j.values.storable.DurationValue; import org.neo4j.values.storable.LocalDateTimeValue; import org.neo4j.values.storable.LocalTimeValue; +import org.neo4j.values.storable.PointArray; import org.neo4j.values.storable.PointValue; import org.neo4j.values.storable.RandomValues; import org.neo4j.values.storable.TimeValue; @@ -198,7 +202,6 @@ void copyShouldCopyExtremeValues() void compareToMustAlignWithValuesCompareTo( ValueGenerator valueGenerator ) { // Given - random.reset(); List values = new ArrayList<>(); List states = new ArrayList<>(); for ( int i = 0; i < 10; i++ ) @@ -221,6 +224,65 @@ void compareToMustAlignWithValuesCompareTo( ValueGenerator valueGenerator ) } } + @Test + void comparePointsMustOnlyReturnZeroForEqualPoints() + { + PointValue firstPoint = random.randomValues().nextPointValue(); + PointValue equalPoint = Values.point( firstPoint ); + CoordinateReferenceSystem crs = firstPoint.getCoordinateReferenceSystem(); + SpaceFillingCurve curve = noSpecificIndexSettings.forCrs( crs, false ); + Long spaceFillingCurveValue = curve.derivedValueFor( firstPoint.coordinate() ); + PointValue centerPoint = Values.pointValue( crs, curve.centerPointFor( spaceFillingCurveValue ) ); + + GenericKey firstKey = newKeyState(); + firstKey.writeValue( firstPoint, NEUTRAL ); + GenericKey equalKey = newKeyState(); + equalKey.writeValue( equalPoint, NEUTRAL ); + GenericKey centerKey = newKeyState(); + centerKey.writeValue( centerPoint, NEUTRAL ); + GenericKey noCoordsKey = newKeyState(); + noCoordsKey.writeValue( equalPoint, NEUTRAL ); + GeometryType.setNoCoordinates( noCoordsKey ); + + assertEquals( 0, firstKey.compareValueTo( equalKey ), "expected keys to be equal" ); + assertEquals( firstPoint.compareTo( centerPoint ) != 0, firstKey.compareValueTo( centerKey ) != 0, + "expected keys to be equal if and only if source points are equal" ); + assertEquals( 0, firstKey.compareValueTo( noCoordsKey ), "expected keys to be equal" ); + } + + @Test + void comparePointArraysMustOnlyReturnZeroForEqualArrays() + { + PointArray firstArray = random.randomValues().nextPointArray(); + PointValue[] sourcePointValues = firstArray.asObjectCopy(); + PointArray equalArray = Values.pointArray( sourcePointValues ); + PointValue[] centerPointValues = new PointValue[sourcePointValues.length]; + for ( int i = 0; i < sourcePointValues.length; i++ ) + { + PointValue sourcePointValue = sourcePointValues[i]; + CoordinateReferenceSystem crs = sourcePointValue.getCoordinateReferenceSystem(); + SpaceFillingCurve curve = noSpecificIndexSettings.forCrs( crs, false ); + Long spaceFillingCurveValue = curve.derivedValueFor( sourcePointValue.coordinate() ); + centerPointValues[i] = Values.pointValue( crs, curve.centerPointFor( spaceFillingCurveValue ) ); + } + PointArray centerArray = Values.pointArray( centerPointValues ); + + GenericKey firstKey = newKeyState(); + firstKey.writeValue( firstArray, NEUTRAL ); + GenericKey equalKey = newKeyState(); + equalKey.writeValue( equalArray, NEUTRAL ); + GenericKey centerKey = newKeyState(); + centerKey.writeValue( centerArray, NEUTRAL ); + GenericKey noCoordsKey = newKeyState(); + noCoordsKey.writeValue( equalArray, NEUTRAL ); + GeometryType.setNoCoordinates( noCoordsKey ); + + assertEquals( 0, firstKey.compareValueTo( equalKey ), "expected keys to be equal" ); + assertEquals( firstArray.compareToSequence( centerArray, AnyValues.COMPARATOR ) != 0, firstKey.compareValueTo( centerKey ) != 0, + "expected keys to be equal if and only if source points are equal" ); + assertEquals( 0, firstKey.compareValueTo( noCoordsKey ), "expected keys to be equal" ); + } + // The reason this test doesn't test incomparable values is that it relies on ordering being same as that of the Values module. @ParameterizedTest @MethodSource( "validComparableValueGenerators" ) @@ -538,7 +600,7 @@ private void assertValidMinimalSplitterForEqualValues( Value value ) assertEquals( 0, leftState.compareValueTo( minimalSplitter ), "left state not equal to minimal splitter, leftState=" + leftState + ", rightState=" + rightState + ", minimalSplitter=" + minimalSplitter ); assertEquals( 0, rightState.compareValueTo( minimalSplitter ), - "right state equal to minimal splitter, leftState=" + leftState + ", rightState=" + rightState + ", minimalSplitter=" + minimalSplitter ); + "right state not equal to minimal splitter, leftState=" + leftState + ", rightState=" + rightState + ", minimalSplitter=" + minimalSplitter ); } private static Value nextValidValue( boolean includeIncomparable ) diff --git a/community/kernel/src/test/resources/org/neo4j/kernel/impl/index/schema/current-generic-key-state-format.zip b/community/kernel/src/test/resources/org/neo4j/kernel/impl/index/schema/current-generic-key-state-format.zip index 104a87f6a9dfcf7bf6f4ae0d7c3974ea6a65b7b6..cefb4b83a2645d8fb7b006e8a73641f73b4f764e 100644 GIT binary patch delta 1710 zcmV;f22uIp5VQ~-P)h>@6aWAS2moV;Vog|&I2?)x0000W000*N5t9!H7=Kk%4FCs9 zblh7?blh7?blh9*RtZ#7R~o)aAP~wbbYQenAS@BAt%3|nA&H{MfeKiph@b^UnXy(1 zVu64`Na0e7W0x^=Lz)Pl8HY2|o;jYA z?k_t#m@oNB{lHyZff^q7<2NU+FSvhVBNo>_b32hAt1 z`z_fRLw)FBDcRU9IB-di>iN)w9(&T%ujr<{{JVX0Kw)wrj40i8#V1_Y!czm_${-2mAwLn6I9==^ketpgMJy*9SH zC!#a0HMH-ftWGi;=YJ@x^KtNN`4uFQ|Cyp=U75T&MWbcDS3V`@X&Vp07jkaFawzC| zAm_L!Zj3%p&ew8!fBe)#&eyc&^d2Y3x$&1@kACU@^BU$?Sga3_^Y?;nZaNvu`Od?~ z|45jUvH#gUoV=&s3*Hpn9RM%jKZF6mzoAev07PH|E3!B< zjEmw1{&ZuFsDKH1i8-Rnl|5OK+)7y5dEqfH5tio_0uxCF7gm?@W=i?vW!d6C)x)ah z1X$Hr1e{=PMt|CLT0JaIX&y@{hs?TkSlPxHt*(VNE$O1RY*>>b7|TwYsLX`CDyFDd z0IQpl#~K)5J>=#IAeUbcdHj6HVYSaSelEJ$1uF^|lA20j|4s2{a}5s2yxaU=qP)q_X4dwb+BU`XIw489y848&y z;)VcdynX!?3T<0Pmn#_J03o{Nkj1^Z=M05>lyN-J^tGHYWaM?bupSz%b&0ms=giOD z(zv43;lh5UyRq|>X(L2@NZO#&djo9UTtcOtR7z1@U(pg(foo#AcQFSoaBx>j`| zG9-SSeC7`obz3%nk#9hi_%lV4gr~@@>A0t#sJpYv1%YQK+(fzwVb$rXzMtq}``uIkMd4LXW zH91JmQTfEjs{z6|iwy{q?F>Me%1s9Zw4(lv8x`3qDsfVASO728l4P&9VQ9(uqC>U~ zE3)y@@~{-avpFXV=YP1bFl3vuA`87RrT4Ynjl@c_Ttf}Lwe=RZeVZ$DtT2{qpg%wA z27l{v*!PqK1$HQ+M0vKXc z+Jt^yllX$}Y(m17*=zG5TZN&U*lVH=JrO#;-elOE6;2FDb2)PElVIV#o8d69t&+C? zGrRoP*uB~m@?eOL**aP%i8lQ^<6FYb6*t!V?sdb@W*3)#?i(n4fBX@-$#_9XsV5i( zbKLJon0Rj{N6aWAS2moV; zVog|&I2?)x0000W000*N36lW`CLdK)1po(1blh7|O9ci1000010096_0002c2LJ#7 E0KhmLb^rhX delta 1732 zcma*o`8(8!0|)TWm|-+l31LTC;~K*nMH=H8p{y%I(l~Z%eA#FiyE&4P4;n-XGro?N z#5R)1IAh%=L?IFDm|Tbf}B zdYx?^X2q#SlxxPSC8|J=aeg>@RwA*SS|ZzZKcB;hH~H|n62-7?6?`csIWy}-<=eE3 zNOH0gux~67@y?zCQU%&ux@9;dix(OSW_RK`8^(ouE4ji)bQ(a4K>{@mdNCC`UX5av z%^j1Dj~wdS1IoMzDYfj~jm1U?f51WXTt_-0X4}8EP1eWQM)V1L#{h7&S;Lq=JW=VL zoVlVI{?_JaHIlc^@b(t1x7c)@HNzVElJ0*$&uo*E=D#F&yyl7ZR)nubvMK1da5?xQ z862g*%V;AeAdQ`2UBF?E- z-aMS5MJ=VHQa>sMM;g@+MuQ3aWW`aQOkdU@a^$A7D>^GFDDA_p;ZD`Q81uEzzOU?s z{m1P5KD}}6T;@h)Qv8HA{^eD??50Udkf$u}d@g#Hif5Yxwy=07RmD(^xcKZ6@Gzg;0Q5!>j|7j4$uN#FZz!l91` z`7Cl^sQ~ONBGjSgurnN$d-~iOP1=MbZtyN5GbJNa^>@!ish%1QS@01`^2WItTS9Tn*68MeQmRFslChg8l`{FSZ*bTfFxmo7;7!-8V$1*w043 z%7^<-pWUVf9ce@HW3mp&1A3{SVZ9*f!h7>*VD6>ho9emv1ZrTh9x;Qrwq7= z2v!pbzfub`Z8TZ3>vFbC!~V{tvgbOS(^X#i&y>>+SqaRq!fo~4E*KFHu!h44kjSPxzHWlpGnhXE8tLsBRKu%qccbuFF!< ze#F1On3DJMq~Vjojtk~;jktq4iXZhbHgYM93fs2M^WZ~k%n4eX^Hm_0Dt>&hVGnv~ z_H&%pNRL6_k-1A%hrZNntcmi584T@;p&83+ge9(Q$Vw?(B{;I7lY`gJR!Et-!5>4n zwBE*XDOWwz1BBN?cT3CYRTzGYlu|`VB~dzH?@0Sg#;@A6;%(kyTy8fOD}0Y5?i}o> z-e9f8q&PdCL@eYt$h-5f<4#eOw2Z?Z=)vf~d~5of^6@zd7FNsyQp!pc3x*I_LNQfn z@35Cri1I;Q?+ G3iu0uPBz*A