diff --git a/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/api/index/IndexAccessorCompatibility.java b/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/api/index/IndexAccessorCompatibility.java index 009d4a011ec6..a021b48bd034 100644 --- a/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/api/index/IndexAccessorCompatibility.java +++ b/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/api/index/IndexAccessorCompatibility.java @@ -20,9 +20,11 @@ package org.neo4j.kernel.api.index; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import java.util.Collection; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; @@ -39,7 +41,13 @@ import org.neo4j.storageengine.api.schema.IndexReader; import org.neo4j.storageengine.api.schema.SimpleNodeValueClient; import org.neo4j.values.storable.Value; +import org.neo4j.values.storable.ValueCategory; import org.neo4j.values.storable.ValueGroup; +import org.neo4j.values.storable.Values; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.junit.Assert.assertThat; public abstract class IndexAccessorCompatibility extends IndexProviderCompatibilityTestSuite.Compatibility { @@ -96,6 +104,68 @@ protected List query( IndexQuery... predicates ) throws Exception } } + protected AutoCloseable query( SimpleNodeValueClient client, IndexOrder order, IndexQuery... predicates ) throws Exception + { + IndexReader reader = accessor.newReader(); + reader.query( client, order, false, predicates ); + return reader; + } + + void assertLessThanOrEqualTo( Value[] o1, Value[] o2 ) + { + if ( o1 == null || o2 == null ) + { + return; + } + int length = Math.min( o1.length, o2.length ); + for ( int i = 0; i < length; i++ ) + { + int compare = Values.COMPARATOR.compare( o1[i], o2[i] ); + assertThat( "expected less than or equal to but was " + Arrays.toString( o1 ) + " and " + Arrays.toString( o2 ), + compare, lessThanOrEqualTo( 0 ) ); + if ( compare != 0 ) + { + return; + } + } + } + + void assertOrder( SimpleNodeValueClient client, IndexOrder order, int expectedCount ) + { + Value[] prevValues = null; + Value[] values; + int count = 0; + while ( client.next() ) + { + count++; + values = client.values; + if ( order == IndexOrder.ASCENDING ) + { + assertLessThanOrEqualTo( prevValues, values ); + } + else if ( order == IndexOrder.DESCENDING ) + { + assertLessThanOrEqualTo( values, prevValues ); + } + else + { + Assert.fail( "Unexpected order " + order ); + } + prevValues = values; + } + assertThat( "correct number of hits", count, equalTo( expectedCount ) ); + } + + IndexOrder[] orderCapability( IndexQuery... predicates ) + { + ValueCategory[] categories = new ValueCategory[predicates.length]; + for ( int i = 0; i < predicates.length; i++ ) + { + categories[i] = predicates[i].valueGroup().category(); + } + return indexProvider.getCapability().orderCapability( categories ); + } + /** * Run the Value[] from a particular entityId through the list of IndexQuery[] predicates to see if they all accept the value. */ diff --git a/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/api/index/SimpleIndexAccessorCompatibility.java b/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/api/index/SimpleIndexAccessorCompatibility.java index 4d0c2a0dd722..805697cafe07 100644 --- a/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/api/index/SimpleIndexAccessorCompatibility.java +++ b/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/api/index/SimpleIndexAccessorCompatibility.java @@ -19,13 +19,21 @@ */ package org.neo4j.kernel.api.index; +import org.apache.commons.lang3.ArrayUtils; import org.hamcrest.Matchers; import org.junit.Assume; import org.junit.Ignore; import org.junit.Test; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; import java.time.ZoneId; import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -33,13 +41,20 @@ import java.util.Set; import java.util.function.Supplier; +import org.neo4j.internal.kernel.api.IndexOrder; import org.neo4j.internal.kernel.api.IndexQuery; import org.neo4j.kernel.api.schema.index.TestIndexDescriptorFactory; import org.neo4j.storageengine.api.schema.IndexDescriptor; import org.neo4j.values.storable.ArrayValue; +import org.neo4j.storageengine.api.schema.SimpleNodeValueClient; +import org.neo4j.values.storable.BooleanValue; import org.neo4j.values.storable.CoordinateReferenceSystem; import org.neo4j.values.storable.DateTimeValue; +import org.neo4j.values.storable.DateValue; +import org.neo4j.values.storable.LocalDateTimeValue; +import org.neo4j.values.storable.LocalTimeValue; import org.neo4j.values.storable.PointValue; +import org.neo4j.values.storable.TimeValue; import org.neo4j.values.storable.Value; import org.neo4j.values.storable.Values; @@ -374,6 +389,436 @@ private List ids( int fromIndex, boolean fromInclusive, int toIndex, boole return ids; } + @Test + public void shouldRangeSeekInOrderAscendingNumber() throws Exception + { + Object o0 = 0; + Object o1 = 1; + Object o2 = 2; + Object o3 = 3; + Object o4 = 4; + Object o5 = 5; + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingNumber() throws Exception + { + Object o0 = 0; + Object o1 = 1; + Object o2 = 2; + Object o3 = 3; + Object o4 = 4; + Object o5 = 5; + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingString() throws Exception + { + Object o0 = "0"; + Object o1 = "1"; + Object o2 = "2"; + Object o3 = "3"; + Object o4 = "4"; + Object o5 = "5"; + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingString() throws Exception + { + Object o0 = "0"; + Object o1 = "1"; + Object o2 = "2"; + Object o3 = "3"; + Object o4 = "4"; + Object o5 = "5"; + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingDate() throws Exception + { + Object o0 = DateValue.epochDateRaw( 0 ); + Object o1 = DateValue.epochDateRaw( 1 ); + Object o2 = DateValue.epochDateRaw( 2 ); + Object o3 = DateValue.epochDateRaw( 3 ); + Object o4 = DateValue.epochDateRaw( 4 ); + Object o5 = DateValue.epochDateRaw( 5 ); + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingDate() throws Exception + { + Object o0 = DateValue.epochDateRaw( 0 ); + Object o1 = DateValue.epochDateRaw( 1 ); + Object o2 = DateValue.epochDateRaw( 2 ); + Object o3 = DateValue.epochDateRaw( 3 ); + Object o4 = DateValue.epochDateRaw( 4 ); + Object o5 = DateValue.epochDateRaw( 5 ); + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingLocalTime() throws Exception + { + Object o0 = LocalTimeValue.localTimeRaw( 0 ); + Object o1 = LocalTimeValue.localTimeRaw( 1 ); + Object o2 = LocalTimeValue.localTimeRaw( 2 ); + Object o3 = LocalTimeValue.localTimeRaw( 3 ); + Object o4 = LocalTimeValue.localTimeRaw( 4 ); + Object o5 = LocalTimeValue.localTimeRaw( 5 ); + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingLocalTime() throws Exception + { + Object o0 = LocalTimeValue.localTimeRaw( 0 ); + Object o1 = LocalTimeValue.localTimeRaw( 1 ); + Object o2 = LocalTimeValue.localTimeRaw( 2 ); + Object o3 = LocalTimeValue.localTimeRaw( 3 ); + Object o4 = LocalTimeValue.localTimeRaw( 4 ); + Object o5 = LocalTimeValue.localTimeRaw( 5 ); + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingTime() throws Exception + { + Object o0 = TimeValue.timeRaw( 0, ZoneOffset.ofHours( 0 ) ); + Object o1 = TimeValue.timeRaw( 1, ZoneOffset.ofHours( 0 ) ); + Object o2 = TimeValue.timeRaw( 2, ZoneOffset.ofHours( 0 ) ); + Object o3 = TimeValue.timeRaw( 3, ZoneOffset.ofHours( 0 ) ); + Object o4 = TimeValue.timeRaw( 4, ZoneOffset.ofHours( 0 ) ); + Object o5 = TimeValue.timeRaw( 5, ZoneOffset.ofHours( 0 ) ); + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingTime() throws Exception + { + Object o0 = TimeValue.timeRaw( 0, ZoneOffset.ofHours( 0 ) ); + Object o1 = TimeValue.timeRaw( 1, ZoneOffset.ofHours( 0 ) ); + Object o2 = TimeValue.timeRaw( 2, ZoneOffset.ofHours( 0 ) ); + Object o3 = TimeValue.timeRaw( 3, ZoneOffset.ofHours( 0 ) ); + Object o4 = TimeValue.timeRaw( 4, ZoneOffset.ofHours( 0 ) ); + Object o5 = TimeValue.timeRaw( 5, ZoneOffset.ofHours( 0 ) ); + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingLocalDateTime() throws Exception + { + Object o0 = LocalDateTimeValue.localDateTimeRaw( 10, 0 ); + Object o1 = LocalDateTimeValue.localDateTimeRaw( 10, 1 ); + Object o2 = LocalDateTimeValue.localDateTimeRaw( 10, 2 ); + Object o3 = LocalDateTimeValue.localDateTimeRaw( 10, 3 ); + Object o4 = LocalDateTimeValue.localDateTimeRaw( 10, 4 ); + Object o5 = LocalDateTimeValue.localDateTimeRaw( 10, 5 ); + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingLocalDateTime() throws Exception + { + Object o0 = LocalDateTimeValue.localDateTimeRaw( 10, 0 ); + Object o1 = LocalDateTimeValue.localDateTimeRaw( 10, 1 ); + Object o2 = LocalDateTimeValue.localDateTimeRaw( 10, 2 ); + Object o3 = LocalDateTimeValue.localDateTimeRaw( 10, 3 ); + Object o4 = LocalDateTimeValue.localDateTimeRaw( 10, 4 ); + Object o5 = LocalDateTimeValue.localDateTimeRaw( 10, 5 ); + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingDateTime() throws Exception + { + Object o0 = DateTimeValue.datetimeRaw( 1, 0, ZoneId.of( "UTC" ) ); + Object o1 = DateTimeValue.datetimeRaw( 1, 1, ZoneId.of( "UTC" ) ); + Object o2 = DateTimeValue.datetimeRaw( 1, 2, ZoneId.of( "UTC" ) ); + Object o3 = DateTimeValue.datetimeRaw( 1, 3, ZoneId.of( "UTC" ) ); + Object o4 = DateTimeValue.datetimeRaw( 1, 4, ZoneId.of( "UTC" ) ); + Object o5 = DateTimeValue.datetimeRaw( 1, 5, ZoneId.of( "UTC" ) ); + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingDateTime() throws Exception + { + Object o0 = DateTimeValue.datetimeRaw( 1, 0, ZoneId.of( "UTC" ) ); + Object o1 = DateTimeValue.datetimeRaw( 1, 1, ZoneId.of( "UTC" ) ); + Object o2 = DateTimeValue.datetimeRaw( 1, 2, ZoneId.of( "UTC" ) ); + Object o3 = DateTimeValue.datetimeRaw( 1, 3, ZoneId.of( "UTC" ) ); + Object o4 = DateTimeValue.datetimeRaw( 1, 4, ZoneId.of( "UTC" ) ); + Object o5 = DateTimeValue.datetimeRaw( 1, 5, ZoneId.of( "UTC" ) ); + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingDuration() throws Exception + { + Object o0 = Duration.ofMillis( 0 ); + Object o1 = Duration.ofMillis( 1 ); + Object o2 = Duration.ofMillis( 2 ); + Object o3 = Duration.ofMillis( 3 ); + Object o4 = Duration.ofMillis( 4 ); + Object o5 = Duration.ofMillis( 5 ); + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingDuration() throws Exception + { + Object o0 = Duration.ofMillis( 0 ); + Object o1 = Duration.ofMillis( 1 ); + Object o2 = Duration.ofMillis( 2 ); + Object o3 = Duration.ofMillis( 3 ); + Object o4 = Duration.ofMillis( 4 ); + Object o5 = Duration.ofMillis( 5 ); + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingNumberArray() throws Exception + { + Object o0 = new int[]{0}; + Object o1 = new int[]{1}; + Object o2 = new int[]{2}; + Object o3 = new int[]{3}; + Object o4 = new int[]{4}; + Object o5 = new int[]{5}; + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingNumberArray() throws Exception + { + Object o0 = new int[]{0}; + Object o1 = new int[]{1}; + Object o2 = new int[]{2}; + Object o3 = new int[]{3}; + Object o4 = new int[]{4}; + Object o5 = new int[]{5}; + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingStringArray() throws Exception + { + Object o0 = new String[]{"0"}; + Object o1 = new String[]{"1"}; + Object o2 = new String[]{"2"}; + Object o3 = new String[]{"3"}; + Object o4 = new String[]{"4"}; + Object o5 = new String[]{"5"}; + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingStringArray() throws Exception + { + Object o0 = new String[]{"0"}; + Object o1 = new String[]{"1"}; + Object o2 = new String[]{"2"}; + Object o3 = new String[]{"3"}; + Object o4 = new String[]{"4"}; + Object o5 = new String[]{"5"}; + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingBooleanArray() throws Exception + { + Object o0 = new boolean[]{false}; + Object o1 = new boolean[]{false, false}; + Object o2 = new boolean[]{false, true}; + Object o3 = new boolean[]{true}; + Object o4 = new boolean[]{true, false}; + Object o5 = new boolean[]{true, true}; + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingBooleanArray() throws Exception + { + Object o0 = new boolean[]{false}; + Object o1 = new boolean[]{false, false}; + Object o2 = new boolean[]{false, true}; + Object o3 = new boolean[]{true}; + Object o4 = new boolean[]{true, false}; + Object o5 = new boolean[]{true, true}; + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingDateTimeArray() throws Exception + { + Object o0 = new ZonedDateTime[]{ZonedDateTime.of( 10, 10, 10, 10, 10, 10, 0, ZoneId.of( "UTC" ) )}; + Object o1 = new ZonedDateTime[]{ZonedDateTime.of( 10, 10, 10, 10, 10, 10, 1, ZoneId.of( "UTC" ) )}; + Object o2 = new ZonedDateTime[]{ZonedDateTime.of( 10, 10, 10, 10, 10, 10, 2, ZoneId.of( "UTC" ) )}; + Object o3 = new ZonedDateTime[]{ZonedDateTime.of( 10, 10, 10, 10, 10, 10, 3, ZoneId.of( "UTC" ) )}; + Object o4 = new ZonedDateTime[]{ZonedDateTime.of( 10, 10, 10, 10, 10, 10, 4, ZoneId.of( "UTC" ) )}; + Object o5 = new ZonedDateTime[]{ZonedDateTime.of( 10, 10, 10, 10, 10, 10, 5, ZoneId.of( "UTC" ) )}; + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingDateTimeArray() throws Exception + { + Object o0 = new ZonedDateTime[]{ZonedDateTime.of( 10, 10, 10, 10, 10, 10, 0, ZoneId.of( "UTC" ) )}; + Object o1 = new ZonedDateTime[]{ZonedDateTime.of( 10, 10, 10, 10, 10, 10, 1, ZoneId.of( "UTC" ) )}; + Object o2 = new ZonedDateTime[]{ZonedDateTime.of( 10, 10, 10, 10, 10, 10, 2, ZoneId.of( "UTC" ) )}; + Object o3 = new ZonedDateTime[]{ZonedDateTime.of( 10, 10, 10, 10, 10, 10, 3, ZoneId.of( "UTC" ) )}; + Object o4 = new ZonedDateTime[]{ZonedDateTime.of( 10, 10, 10, 10, 10, 10, 4, ZoneId.of( "UTC" ) )}; + Object o5 = new ZonedDateTime[]{ZonedDateTime.of( 10, 10, 10, 10, 10, 10, 5, ZoneId.of( "UTC" ) )}; + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingLocalDateTimeArray() throws Exception + { + Object o0 = new LocalDateTime[]{LocalDateTime.of( 10, 10, 10, 10, 10, 10, 0 )}; + Object o1 = new LocalDateTime[]{LocalDateTime.of( 10, 10, 10, 10, 10, 10, 1 )}; + Object o2 = new LocalDateTime[]{LocalDateTime.of( 10, 10, 10, 10, 10, 10, 2 )}; + Object o3 = new LocalDateTime[]{LocalDateTime.of( 10, 10, 10, 10, 10, 10, 3 )}; + Object o4 = new LocalDateTime[]{LocalDateTime.of( 10, 10, 10, 10, 10, 10, 4 )}; + Object o5 = new LocalDateTime[]{LocalDateTime.of( 10, 10, 10, 10, 10, 10, 5 )}; + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingLocalDateTimeArray() throws Exception + { + Object o0 = new LocalDateTime[]{LocalDateTime.of( 10, 10, 10, 10, 10, 10, 0 )}; + Object o1 = new LocalDateTime[]{LocalDateTime.of( 10, 10, 10, 10, 10, 10, 1 )}; + Object o2 = new LocalDateTime[]{LocalDateTime.of( 10, 10, 10, 10, 10, 10, 2 )}; + Object o3 = new LocalDateTime[]{LocalDateTime.of( 10, 10, 10, 10, 10, 10, 3 )}; + Object o4 = new LocalDateTime[]{LocalDateTime.of( 10, 10, 10, 10, 10, 10, 4 )}; + Object o5 = new LocalDateTime[]{LocalDateTime.of( 10, 10, 10, 10, 10, 10, 5 )}; + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingTimeArray() throws Exception + { + Object o0 = new OffsetTime[]{OffsetTime.of( 10, 10, 10, 0, ZoneOffset.ofHours( 0 ) )}; + Object o1 = new OffsetTime[]{OffsetTime.of( 10, 10, 10, 1, ZoneOffset.ofHours( 0 ) )}; + Object o2 = new OffsetTime[]{OffsetTime.of( 10, 10, 10, 2, ZoneOffset.ofHours( 0 ) )}; + Object o3 = new OffsetTime[]{OffsetTime.of( 10, 10, 10, 3, ZoneOffset.ofHours( 0 ) )}; + Object o4 = new OffsetTime[]{OffsetTime.of( 10, 10, 10, 4, ZoneOffset.ofHours( 0 ) )}; + Object o5 = new OffsetTime[]{OffsetTime.of( 10, 10, 10, 5, ZoneOffset.ofHours( 0 ) )}; + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingTimeArray() throws Exception + { + Object o0 = new OffsetTime[]{OffsetTime.of( 10, 10, 10, 0, ZoneOffset.ofHours( 0 ) )}; + Object o1 = new OffsetTime[]{OffsetTime.of( 10, 10, 10, 1, ZoneOffset.ofHours( 0 ) )}; + Object o2 = new OffsetTime[]{OffsetTime.of( 10, 10, 10, 2, ZoneOffset.ofHours( 0 ) )}; + Object o3 = new OffsetTime[]{OffsetTime.of( 10, 10, 10, 3, ZoneOffset.ofHours( 0 ) )}; + Object o4 = new OffsetTime[]{OffsetTime.of( 10, 10, 10, 4, ZoneOffset.ofHours( 0 ) )}; + Object o5 = new OffsetTime[]{OffsetTime.of( 10, 10, 10, 5, ZoneOffset.ofHours( 0 ) )}; + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingDateArray() throws Exception + { + Object o0 = new LocalDate[]{LocalDate.of( 10, 10, 1 )}; + Object o1 = new LocalDate[]{LocalDate.of( 10, 10, 2 )}; + Object o2 = new LocalDate[]{LocalDate.of( 10, 10, 3 )}; + Object o3 = new LocalDate[]{LocalDate.of( 10, 10, 4 )}; + Object o4 = new LocalDate[]{LocalDate.of( 10, 10, 5 )}; + Object o5 = new LocalDate[]{LocalDate.of( 10, 10, 6 )}; + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingDateArray() throws Exception + { + Object o0 = new LocalDate[]{LocalDate.of( 10, 10, 1 )}; + Object o1 = new LocalDate[]{LocalDate.of( 10, 10, 2 )}; + Object o2 = new LocalDate[]{LocalDate.of( 10, 10, 3 )}; + Object o3 = new LocalDate[]{LocalDate.of( 10, 10, 4 )}; + Object o4 = new LocalDate[]{LocalDate.of( 10, 10, 5 )}; + Object o5 = new LocalDate[]{LocalDate.of( 10, 10, 6 )}; + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingLocalTimeArray() throws Exception + { + Object o0 = new LocalTime[]{LocalTime.of( 10, 0 )}; + Object o1 = new LocalTime[]{LocalTime.of( 10, 1 )}; + Object o2 = new LocalTime[]{LocalTime.of( 10, 2 )}; + Object o3 = new LocalTime[]{LocalTime.of( 10, 3 )}; + Object o4 = new LocalTime[]{LocalTime.of( 10, 4 )}; + Object o5 = new LocalTime[]{LocalTime.of( 10, 5 )}; + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingLocalTimeArray() throws Exception + { + Object o0 = new LocalTime[]{LocalTime.of( 10, 0 )}; + Object o1 = new LocalTime[]{LocalTime.of( 10, 1 )}; + Object o2 = new LocalTime[]{LocalTime.of( 10, 2 )}; + Object o3 = new LocalTime[]{LocalTime.of( 10, 3 )}; + Object o4 = new LocalTime[]{LocalTime.of( 10, 4 )}; + Object o5 = new LocalTime[]{LocalTime.of( 10, 5 )}; + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderAscendingDurationArray() throws Exception + { + Object o0 = new Duration[]{Duration.of( 0, ChronoUnit.SECONDS )}; + Object o1 = new Duration[]{Duration.of( 1, ChronoUnit.SECONDS )}; + Object o2 = new Duration[]{Duration.of( 2, ChronoUnit.SECONDS )}; + Object o3 = new Duration[]{Duration.of( 3, ChronoUnit.SECONDS )}; + Object o4 = new Duration[]{Duration.of( 4, ChronoUnit.SECONDS )}; + Object o5 = new Duration[]{Duration.of( 5, ChronoUnit.SECONDS )}; + shouldRangeSeekInOrder( IndexOrder.ASCENDING, o0, o1, o2, o3, o4, o5 ); + } + + @Test + public void shouldRangeSeekInOrderDescendingDurationArray() throws Exception + { + Object o0 = new Duration[]{Duration.of( 0, ChronoUnit.SECONDS )}; + Object o1 = new Duration[]{Duration.of( 1, ChronoUnit.SECONDS )}; + Object o2 = new Duration[]{Duration.of( 2, ChronoUnit.SECONDS )}; + Object o3 = new Duration[]{Duration.of( 3, ChronoUnit.SECONDS )}; + Object o4 = new Duration[]{Duration.of( 4, ChronoUnit.SECONDS )}; + Object o5 = new Duration[]{Duration.of( 5, ChronoUnit.SECONDS )}; + shouldRangeSeekInOrder( IndexOrder.DESCENDING, o0, o1, o2, o3, o4, o5 ); + } + + private void shouldRangeSeekInOrder( IndexOrder order, Object o0, Object o1, Object o2, Object o3, Object o4, Object o5 ) throws Exception + { + IndexQuery range = range( 100, Values.of( o0 ), true, Values.of( o5 ), true ); + IndexOrder[] indexOrders = orderCapability( range ); + Assume.assumeTrue( "Assume support for order " + order, ArrayUtils.contains( indexOrders, order ) ); + + updateAndCommit( asList( + add( 1, descriptor.schema(), o0 ), + add( 1, descriptor.schema(), o5 ), + add( 1, descriptor.schema(), o1 ), + add( 1, descriptor.schema(), o4 ), + add( 1, descriptor.schema(), o2 ), + add( 1, descriptor.schema(), o3 ) + ) ); + + SimpleNodeValueClient client = new SimpleNodeValueClient(); + try ( AutoCloseable ignored = query( client, order, range ) ) + { + assertOrder( client, order, 6 ); + } + } + // This behaviour is expected by General indexes @Ignore( "Not a test. This is a compatibility suite" ) diff --git a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/IndexCapability.java b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/IndexCapability.java index cdea24cdc51d..6e95224d45d4 100644 --- a/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/IndexCapability.java +++ b/community/kernel-api/src/main/java/org/neo4j/internal/kernel/api/IndexCapability.java @@ -30,7 +30,7 @@ */ public interface IndexCapability { - IndexOrder[] ORDER_ASC = {IndexOrder.ASCENDING}; + IndexOrder[] ORDER_BOTH = {IndexOrder.ASCENDING, IndexOrder.DESCENDING}; IndexOrder[] ORDER_NONE = new IndexOrder[0]; IndexLimitation[] LIMITIATION_NONE = new IndexLimitation[0]; 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 e51512bfdf37..28e9f766ae61 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 @@ -31,6 +31,7 @@ import org.neo4j.values.storable.DateValue; import org.neo4j.values.storable.Value; import org.neo4j.values.storable.ValueCategory; +import org.neo4j.values.storable.ValueGroup; import org.neo4j.values.storable.Values; import static java.util.concurrent.TimeUnit.MINUTES; @@ -525,7 +526,7 @@ public void shouldPerformBooleanSearch() throws KernelException int label = token.nodeLabel( "Node" ); int prop = token.propertyKey( "prop" ); IndexReference index = schemaRead.index( label, prop ); - IndexValueCapability capability = index.valueCapability( ValueCategory.REST ); + IndexValueCapability capability = index.valueCapability( ValueGroup.BOOLEAN.category() ); try ( NodeValueIndexCursor node = cursors.allocateNodeValueIndexCursor() ) { MutableLongSet uniqueIds = new LongHashSet(); @@ -545,14 +546,14 @@ public void shouldPerformBooleanSearch() throws KernelException } @Test - public void shouldPerformArraySearch() throws KernelException + public void shouldPerformTextArraySearch() throws KernelException { // given boolean needsValues = false; int label = token.nodeLabel( "Node" ); int prop = token.propertyKey( "prop" ); IndexReference index = schemaRead.index( label, prop ); - IndexValueCapability capability = index.valueCapability( ValueCategory.REST ); + IndexValueCapability capability = index.valueCapability( ValueGroup.TEXT_ARRAY.category() ); try ( NodeValueIndexCursor node = cursors.allocateNodeValueIndexCursor() ) { MutableLongSet uniqueIds = new LongHashSet(); diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GenericNativeIndexProvider.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GenericNativeIndexProvider.java index ba009658b44c..2d5f3a78b649 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GenericNativeIndexProvider.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/GenericNativeIndexProvider.java @@ -107,22 +107,7 @@ public class GenericNativeIndexProvider extends NativeIndexProvider,IOException> seeker = makeIndexSeeker( treeKeyFrom, treeKeyTo ); + RawCursor,IOException> seeker = makeIndexSeeker( treeKeyFrom, treeKeyTo, indexOrder ); IndexProgressor hitProgressor = getIndexProgressor( seeker, client, needFilter, query ); client.initialize( descriptor, hitProgressor, query, indexOrder, needsValues ); } @@ -178,8 +171,14 @@ void startSeekForInitializedRange( IndexProgressor.NodeValueClient client, } } - RawCursor,IOException> makeIndexSeeker( KEY treeKeyFrom, KEY treeKeyTo ) throws IOException + RawCursor,IOException> makeIndexSeeker( KEY treeKeyFrom, KEY treeKeyTo, IndexOrder indexOrder ) throws IOException { + if ( indexOrder == IndexOrder.DESCENDING ) + { + KEY tmpKey = treeKeyFrom; + treeKeyFrom = treeKeyTo; + treeKeyTo = tmpKey; + } RawCursor,IOException> seeker = tree.seek( treeKeyFrom, treeKeyTo ); openSeekers.add( seeker ); return seeker; @@ -192,7 +191,7 @@ private IndexProgressor getIndexProgressor( RawCursor,IOException : new NativeHitIndexProgressor<>( seeker, client, openSeekers ); } - private boolean isBackwardsSeek( KEY treeKeyFrom, KEY treeKeyTo ) + private boolean isEmptyRange( KEY treeKeyFrom, KEY treeKeyTo ) { return layout.compare( treeKeyFrom, treeKeyTo ) > 0; } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NumberIndexProvider.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NumberIndexProvider.java index fa43f7458baf..e841e005c262 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NumberIndexProvider.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/NumberIndexProvider.java @@ -103,7 +103,7 @@ public IndexOrder[] orderCapability( ValueCategory... valueCategories ) { if ( support( valueCategories ) ) { - return ORDER_ASC; + return ORDER_BOTH; } return ORDER_NONE; } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/SpatialIndexPartReader.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/SpatialIndexPartReader.java index 161d98cf5374..585371c1d4ff 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/SpatialIndexPartReader.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/SpatialIndexPartReader.java @@ -112,7 +112,7 @@ private void startSeekForExists( SpatialIndexKey treeKeyFrom, SpatialIndexKey tr { treeKeyFrom.initValueAsLowest( ValueGroup.GEOMETRY ); treeKeyTo.initValueAsHighest( ValueGroup.GEOMETRY ); - startSeekForInitializedRange( client, treeKeyFrom, treeKeyTo, predicates, false, IndexOrder.NONE, false ); + startSeekForInitializedRange( client, treeKeyFrom, treeKeyTo, predicates, IndexOrder.NONE, false, false ); } private void startSeekForExact( SpatialIndexKey treeKeyFrom, SpatialIndexKey treeKeyTo, IndexProgressor.NodeValueClient client, Value value, @@ -120,7 +120,7 @@ private void startSeekForExact( SpatialIndexKey treeKeyFrom, SpatialIndexKey tre { treeKeyFrom.from( value ); treeKeyTo.from( value ); - startSeekForInitializedRange( client, treeKeyFrom, treeKeyTo, predicates, false, IndexOrder.NONE, false ); + startSeekForInitializedRange( client, treeKeyFrom, treeKeyTo, predicates, IndexOrder.NONE, false, false ); } private void startSeekForRange( IndexProgressor.NodeValueClient client, GeometryRangePredicate rangePredicate, IndexQuery[] query ) @@ -140,7 +140,7 @@ private void startSeekForRange( IndexProgressor.NodeValueClient client, Geometry initializeKeys( treeKeyFrom, treeKeyTo ); treeKeyFrom.fromDerivedValue( Long.MIN_VALUE, range.min ); treeKeyTo.fromDerivedValue( Long.MAX_VALUE, range.max + 1 ); - RawCursor,IOException> seeker = makeIndexSeeker( treeKeyFrom, treeKeyTo ); + RawCursor,IOException> seeker = makeIndexSeeker( treeKeyFrom, treeKeyTo, IndexOrder.NONE ); IndexProgressor hitProgressor = new NativeHitIndexProgressor<>( seeker, client, openSeekers ); multiProgressor.initialize( descriptor, hitProgressor, query, IndexOrder.NONE, false ); } @@ -157,13 +157,8 @@ private void startSeekForRange( IndexProgressor.NodeValueClient client, Geometry } @Override - void startSeekForInitializedRange( IndexProgressor.NodeValueClient client, - SpatialIndexKey treeKeyFrom, - SpatialIndexKey treeKeyTo, - IndexQuery[] query, - boolean needFilter, - IndexOrder indexOrder, - boolean needsValues ) + void startSeekForInitializedRange( IndexProgressor.NodeValueClient client, SpatialIndexKey treeKeyFrom, SpatialIndexKey treeKeyTo, IndexQuery[] query, + IndexOrder indexOrder, boolean needFilter, boolean needsValues ) { // Spatial does not support providing values assert !needsValues; @@ -175,7 +170,7 @@ void startSeekForInitializedRange( IndexProgressor.NodeValueClient client, } try { - RawCursor,IOException> seeker = makeIndexSeeker( treeKeyFrom, treeKeyTo ); + RawCursor,IOException> seeker = makeIndexSeeker( treeKeyFrom, treeKeyTo, indexOrder ); IndexProgressor hitProgressor = new NativeHitIndexProgressor<>( seeker, client, openSeekers ); client.initialize( descriptor, hitProgressor, query, IndexOrder.NONE, false ); } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/StringIndexProvider.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/StringIndexProvider.java index 06dcd54bc338..a20f8b944772 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/StringIndexProvider.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/StringIndexProvider.java @@ -96,7 +96,7 @@ public IndexOrder[] orderCapability( ValueCategory... valueCategories ) { if ( support( valueCategories ) ) { - return ORDER_ASC; + return ORDER_BOTH; } return ORDER_NONE; } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexProvider.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexProvider.java index d9e4fe29670d..48a3a8c0d6bf 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexProvider.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/index/schema/TemporalIndexProvider.java @@ -161,7 +161,7 @@ public IndexOrder[] orderCapability( ValueCategory... valueCategories ) { if ( support( valueCategories ) ) { - return ORDER_ASC; + return ORDER_BOTH; } return ORDER_NONE; } diff --git a/community/values/src/main/java/org/neo4j/values/storable/ValueCategory.java b/community/values/src/main/java/org/neo4j/values/storable/ValueCategory.java index 89c821b24e67..0450aa4c4ea4 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/ValueCategory.java +++ b/community/values/src/main/java/org/neo4j/values/storable/ValueCategory.java @@ -25,6 +25,7 @@ public enum ValueCategory TEXT, GEOMETRY, TEMPORAL, + GEOMETRY_ARRAY, REST, UNKNOWN, NO_CATEGORY diff --git a/community/values/src/main/java/org/neo4j/values/storable/ValueGroup.java b/community/values/src/main/java/org/neo4j/values/storable/ValueGroup.java index e8087d3c770c..546cba695559 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/ValueGroup.java +++ b/community/values/src/main/java/org/neo4j/values/storable/ValueGroup.java @@ -36,7 +36,7 @@ public enum ValueGroup { UNKNOWN( ValueCategory.UNKNOWN ), - GEOMETRY_ARRAY( REST ), + GEOMETRY_ARRAY( ValueCategory.GEOMETRY_ARRAY ), ZONED_DATE_TIME_ARRAY( REST ), LOCAL_DATE_TIME_ARRAY( REST ), DATE_ARRAY( REST ),