From 756aeafb4b941aef779dce93c7ccf1bc31e35b95 Mon Sep 17 00:00:00 2001 From: Chris Vest Date: Tue, 21 Feb 2017 11:40:57 +0100 Subject: [PATCH] Add support for string prefix, suffix and contains to OperationsFacade.indexQuery --- .../v3_2/TransactionBoundQueryContext.scala | 8 +- .../kernel/api/schema_new/IndexQuery.java | 252 +++++++++--------- .../api/StateHandlingStatementOperations.java | 30 ++- .../NonUniqueIndexAccessorCompatibility.java | 49 ++++ .../api/index/inmemory/HashBasedIndex.java | 15 +- .../StateHandlingStatementOperationsTest.java | 90 +++++++ .../impl/schema/reader/SimpleIndexReader.java | 13 +- 7 files changed, 316 insertions(+), 141 deletions(-) diff --git a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_2/TransactionBoundQueryContext.scala b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_2/TransactionBoundQueryContext.scala index c9b00e7f1f10f..460924b46541f 100644 --- a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_2/TransactionBoundQueryContext.scala +++ b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_2/TransactionBoundQueryContext.scala @@ -204,7 +204,7 @@ final class TransactionBoundQueryContext(val transactionalContext: Transactional } private def indexSeekByPrefixRange(index: IndexDescriptor, prefix: String): scala.Iterator[Node] = { - val indexedNodes = transactionalContext.statement.readOperations().nodesGetFromIndexRangeSeekByPrefix(index, prefix) + val indexedNodes = transactionalContext.statement.readOperations().indexQuery(index, IndexQuery.stringPrefix(index.property, prefix)) JavaConversionSupport.mapToScalaENFXSafe(indexedNodes)(nodeOps.getById) } @@ -264,13 +264,13 @@ final class TransactionBoundQueryContext(val transactionalContext: Transactional } override def indexScan(index: IndexDescriptor) = - mapToScalaENFXSafe(transactionalContext.statement.readOperations().nodesGetFromIndexScan(index))(nodeOps.getById) + mapToScalaENFXSafe(transactionalContext.statement.readOperations().indexQuery(index, IndexQuery.exists(index.property)))(nodeOps.getById) override def indexScanByContains(index: IndexDescriptor, value: String) = - mapToScalaENFXSafe(transactionalContext.statement.readOperations().nodesGetFromIndexContainsScan(index, value))(nodeOps.getById) + mapToScalaENFXSafe(transactionalContext.statement.readOperations().indexQuery(index, IndexQuery.stringContains(index.property, value)))(nodeOps.getById) override def indexScanByEndsWith(index: IndexDescriptor, value: String) = - mapToScalaENFXSafe(transactionalContext.statement.readOperations().nodesGetFromIndexEndsWithScan(index, value))(nodeOps.getById) + mapToScalaENFXSafe(transactionalContext.statement.readOperations().indexQuery(index, IndexQuery.stringSuffix(index.property, value)))(nodeOps.getById) override def lockingUniqueIndexSeek(index: IndexDescriptor, value: Any): Option[Node] = { indexSearchMonitor.lockingUniqueIndexSeek(index, value) diff --git a/community/kernel/src/main/java/org/neo4j/kernel/api/schema_new/IndexQuery.java b/community/kernel/src/main/java/org/neo4j/kernel/api/schema_new/IndexQuery.java index e71b7b5d38df4..0217e75c23e0f 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/api/schema_new/IndexQuery.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/api/schema_new/IndexQuery.java @@ -19,6 +19,11 @@ */ package org.neo4j.kernel.api.schema_new; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + public abstract class IndexQuery { public static ExistsPredicate exists( int propertyKeyId ) @@ -43,51 +48,89 @@ public static StringRangePredicate range( int propertyKeyId, String from, boolea return new StringRangePredicate( propertyKeyId, from, fromInclusive, to, toInclusive ); } + public static StringPrefixPredicate stringPrefix( int propertyKeyId, String prefix ) + { + return new StringPrefixPredicate( propertyKeyId, prefix ); + } + + public static StringContainsPredicate stringContains( int propertyKeyId, String contains ) + { + return new StringContainsPredicate( propertyKeyId, contains ); + } + + public static StringSuffixPredicate stringSuffix( int propertyKeyId, String suffix ) + { + return new StringSuffixPredicate( propertyKeyId, suffix ); + } + + private final int propertyKeyId; + + protected IndexQuery( int propertyKeyId ) + { + this.propertyKeyId = propertyKeyId; + } + public abstract IndexQueryType type(); - public static final class ExistsPredicate extends IndexQuery + @SuppressWarnings( "EqualsWhichDoesntCheckParameterClass" ) + @Override + public final boolean equals( Object other ) { - private final int propertyKeyId; + // equals() and hashcode() are only used for testing so we don't care that they are a bit slow. + return EqualsBuilder.reflectionEquals( this, other ); + } - public ExistsPredicate( int propertyKeyId ) - { - this.propertyKeyId = propertyKeyId; - } + @Override + public final int hashCode() + { + // equals() and hashcode() are only used for testing so we don't care that they are a bit slow. + return HashCodeBuilder.reflectionHashCode( this, false ); + } - @Override - public IndexQueryType type() - { - return IndexQueryType.exists; - } + @Override + public final String toString() + { + // Only used to debugging, it's okay to be a bit slow. + return ToStringBuilder.reflectionToString( this, ToStringStyle.SHORT_PREFIX_STYLE ); + } - @Override - public boolean equals( Object o ) - { - if ( this == o ) - { return true; } - if ( o == null || getClass() != o.getClass() ) - { return false; } + public final int propertyKeyId() + { + return propertyKeyId; + } - ExistsPredicate that = (ExistsPredicate) o; + public enum IndexQueryType + { + exists, + exact, + rangeString, + rangeNumeric, + stringPrefix, + stringSuffix, + stringContains + } - return propertyKeyId == that.propertyKeyId; + public static final class ExistsPredicate extends IndexQuery + { + public ExistsPredicate( int propertyKeyId ) + { + super( propertyKeyId ); } @Override - public int hashCode() + public IndexQueryType type() { - return propertyKeyId; + return IndexQueryType.exists; } } public static final class ExactPredicate extends IndexQuery { - private final int propertyKeyId; private final Object value; public ExactPredicate( int propertyKeyId, Object value ) { - this.propertyKeyId = propertyKeyId; + super( propertyKeyId ); this.value = value; } @@ -101,34 +144,10 @@ public Object value() { return value; } - - @Override - public boolean equals( Object o ) - { - if ( this == o ) - { return true; } - if ( o == null || getClass() != o.getClass() ) - { return false; } - - ExactPredicate that = (ExactPredicate) o; - - if ( propertyKeyId != that.propertyKeyId ) - { return false; } - return value.equals( that.value ); - } - - @Override - public int hashCode() - { - int result = propertyKeyId; - result = 31 * result + value.hashCode(); - return result; - } } public static final class NumberRangePredicate extends IndexQuery { - private final int propertyKeyId; private final Number from; private final boolean fromInclusive; private final Number to; @@ -137,7 +156,7 @@ public static final class NumberRangePredicate extends IndexQuery public NumberRangePredicate( int propertyKeyId, Number from, boolean fromInclusive, Number to, boolean toInclusive ) { - this.propertyKeyId = propertyKeyId; + super( propertyKeyId ); this.from = from; this.fromInclusive = fromInclusive; this.to = to; @@ -150,62 +169,29 @@ public IndexQueryType type() return IndexQueryType.rangeNumeric; } - public Number getFrom() + public Number from() { return from; } - public Number getTo() + public Number to() { return to; } - public boolean isFromInclusive() + public boolean fromInclusive() { return fromInclusive; } - public boolean isToInclusive() + public boolean toInclusive() { return toInclusive; } - - @Override - public boolean equals( Object o ) - { - if ( this == o ) - { return true; } - if ( o == null || getClass() != o.getClass() ) - { return false; } - - NumberRangePredicate that = (NumberRangePredicate) o; - - if ( propertyKeyId != that.propertyKeyId ) - { return false; } - if ( fromInclusive != that.fromInclusive ) - { return false; } - if ( toInclusive != that.toInclusive ) - { return false; } - if ( !from.equals( that.from ) ) - { return false; } - return to.equals( that.to ); - } - - @Override - public int hashCode() - { - int result = propertyKeyId; - result = 31 * result + from.hashCode(); - result = 31 * result + (fromInclusive ? 1 : 0); - result = 31 * result + to.hashCode(); - result = 31 * result + (toInclusive ? 1 : 0); - return result; - } } public static final class StringRangePredicate extends IndexQuery { - private final int propertyKeyId; private final String from; private final boolean fromInclusive; private final String to; @@ -215,7 +201,7 @@ public StringRangePredicate( int propertyKeyId, String from, boolean fromInclusive, String to, boolean toInclusive ) { - this.propertyKeyId = propertyKeyId; + super( propertyKeyId ); this.from = from; this.fromInclusive = fromInclusive; this.to = to; @@ -228,64 +214,90 @@ public IndexQueryType type() return IndexQueryType.rangeString; } - public String getFrom() + public String from() { return from; } - public boolean isFromInclusive() + public boolean fromInclusive() { return fromInclusive; } - public String getTo() + public String to() { return to; } - public boolean isToInclusive() + public boolean toInclusive() { return toInclusive; } + } + + public static final class StringPrefixPredicate extends IndexQuery + { + private final String prefix; + + public StringPrefixPredicate( int propertyKeyId, String prefix ) + { + super( propertyKeyId ); + this.prefix = prefix; + } @Override - public boolean equals( Object o ) + public IndexQueryType type() + { + return IndexQueryType.stringPrefix; + } + + public String prefix() + { + return prefix; + } + } + + public static final class StringContainsPredicate extends IndexQuery + { + private final String contains; + + public StringContainsPredicate( int propertyKeyId, String contains ) { - if ( this == o ) - { return true; } - if ( o == null || getClass() != o.getClass() ) - { return false; } - - StringRangePredicate that = (StringRangePredicate) o; - - if ( propertyKeyId != that.propertyKeyId ) - { return false; } - if ( fromInclusive != that.fromInclusive ) - { return false; } - if ( toInclusive != that.toInclusive ) - { return false; } - if ( !from.equals( that.from ) ) - { return false; } - return to.equals( that.to ); + super( propertyKeyId ); + this.contains = contains; } @Override - public int hashCode() + public IndexQueryType type() { - int result = propertyKeyId; - result = 31 * result + from.hashCode(); - result = 31 * result + (fromInclusive ? 1 : 0); - result = 31 * result + to.hashCode(); - result = 31 * result + (toInclusive ? 1 : 0); - return result; + return IndexQueryType.stringContains; + } + + public String contains() + { + return contains; } } - public enum IndexQueryType + public static final class StringSuffixPredicate extends IndexQuery { - exists, - exact, - rangeString, - rangeNumeric + private final String suffix; + + public StringSuffixPredicate( int propertyKeyId, String suffix ) + { + super( propertyKeyId ); + this.suffix = suffix; + } + + @Override + public IndexQueryType type() + { + return IndexQueryType.stringSuffix; + } + + public String suffix() + { + return suffix; + } } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/StateHandlingStatementOperations.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/StateHandlingStatementOperations.java index 05e87d3996f54..d065dd5ec006c 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/StateHandlingStatementOperations.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/StateHandlingStatementOperations.java @@ -733,32 +733,38 @@ public PrimitiveLongIterator indexQuery( KernelStatement state, NewIndexDescript assert predicates.length == 1: "composite indexes not yet supported"; IndexQuery predicate = predicates[0]; + Object filterValue = null; switch ( predicate.type() ) { case exact: - { - Object value = ((IndexQuery.ExactPredicate) predicate).value(); - PrimitiveLongIterator exactMatches = filterExactIndexMatches( state, index, value, committed ); - return filterIndexStateChangesForScanOrSeek( state, index, value, exactMatches ); - } + filterValue = ((IndexQuery.ExactPredicate) predicate).value(); + committed = filterExactIndexMatches( state, index, filterValue, committed ); + case stringSuffix: + case stringContains: case exists: - return filterIndexStateChangesForScanOrSeek( state, index, null, committed ); + return filterIndexStateChangesForScanOrSeek( state, index, filterValue, committed ); + case rangeNumeric: { IndexQuery.NumberRangePredicate numPred = (IndexQuery.NumberRangePredicate) predicate; PrimitiveLongIterator exactMatches = - filterExactRangeMatches( state, index, committed, numPred.getFrom(), numPred.isFromInclusive(), - numPred.getTo(), numPred.isToInclusive() ); - return filterIndexStateChangesForRangeSeekByNumber( state, index, numPred.getFrom(), - numPred.isFromInclusive(), numPred.getTo(), numPred.isToInclusive(), + filterExactRangeMatches( state, index, committed, numPred.from(), numPred.fromInclusive(), + numPred.to(), numPred.toInclusive() ); + return filterIndexStateChangesForRangeSeekByNumber( state, index, numPred.from(), + numPred.fromInclusive(), numPred.to(), numPred.toInclusive(), exactMatches ); } case rangeString: { IndexQuery.StringRangePredicate strPred = (IndexQuery.StringRangePredicate) predicate; return filterIndexStateChangesForRangeSeekByString( - state, index, strPred.getFrom(), strPred.isFromInclusive(), strPred.getTo(), - strPred.isToInclusive(), committed ); + state, index, strPred.from(), strPred.fromInclusive(), strPred.to(), + strPred.toInclusive(), committed ); + } + case stringPrefix: + { + IndexQuery.StringPrefixPredicate strPred = (IndexQuery.StringPrefixPredicate) predicate; + return filterIndexStateChangesForRangeSeekByPrefix( state, index, strPred.prefix(), committed ); } default: throw new RuntimeException( "Query not supported: " + Arrays.toString( predicates ) ); diff --git a/community/kernel/src/test/java/org/neo4j/kernel/api/index/NonUniqueIndexAccessorCompatibility.java b/community/kernel/src/test/java/org/neo4j/kernel/api/index/NonUniqueIndexAccessorCompatibility.java index db67d3eedc3ac..a1ac61d5e0c77 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/api/index/NonUniqueIndexAccessorCompatibility.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/api/index/NonUniqueIndexAccessorCompatibility.java @@ -35,6 +35,9 @@ import static org.neo4j.kernel.api.schema_new.IndexQuery.exact; import static org.neo4j.kernel.api.schema_new.IndexQuery.exists; import static org.neo4j.kernel.api.schema_new.IndexQuery.range; +import static org.neo4j.kernel.api.schema_new.IndexQuery.stringContains; +import static org.neo4j.kernel.api.schema_new.IndexQuery.stringPrefix; +import static org.neo4j.kernel.api.schema_new.IndexQuery.stringSuffix; @Ignore( "Not a test. This is a compatibility suite that provides test cases for verifying" + " SchemaIndexProvider implementations. Each index provider that is to be tested by this suite" + @@ -172,6 +175,20 @@ public void testIndexRangeSeekByPrefixWithDuplicates() throws Exception assertThat( getAllNodesFromIndexSeekByPrefix( "apa" ), equalTo( asList( 3L, 4L, 5L ) ) ); } + @Test + public void testIndexRangeSeekByPrefixWithDuplicatesWithIndexQuery() throws Exception + { + updateAndCommit( asList( + IndexEntryUpdate.add( 1L, index, "a" ), + IndexEntryUpdate.add( 2L, index, "A" ), + IndexEntryUpdate.add( 3L, index, "apa" ), + IndexEntryUpdate.add( 4L, index, "apa" ), + IndexEntryUpdate.add( 5L, index, "apa" ) ) ); + + assertThat( query( stringPrefix( 1, "a" ) ), equalTo( asList( 1L, 3L, 4L, 5L ) ) ); + assertThat( query( stringPrefix( 1, "apa" ) ), equalTo( asList( 3L, 4L, 5L ) ) ); + } + @Test public void testIndexFullSearchWithDuplicates() throws Exception { @@ -187,6 +204,21 @@ public void testIndexFullSearchWithDuplicates() throws Exception assertThat( getAllNodesFromIndexScanByContains( "apa*" ), equalTo( Collections.emptyList() ) ); } + @Test + public void testIndexFullSearchWithDuplicatesWithIndexQuery() throws Exception + { + updateAndCommit( asList( + IndexEntryUpdate.add( 1L, index, "a" ), + IndexEntryUpdate.add( 2L, index, "A" ), + IndexEntryUpdate.add( 3L, index, "apa" ), + IndexEntryUpdate.add( 4L, index, "apa" ), + IndexEntryUpdate.add( 5L, index, "apalong" ) ) ); + + assertThat( query( stringContains( 1, "a" ) ), equalTo( asList( 1L, 3L, 4L, 5L ) ) ); + assertThat( query( stringContains( 1, "apa" ) ), equalTo( asList( 3L, 4L, 5L ) ) ); + assertThat( query( stringContains( 1, "apa*" ) ), equalTo( Collections.emptyList() ) ); + } + @Test public void testIndexEndsWithWithDuplicated() throws Exception { @@ -203,4 +235,21 @@ public void testIndexEndsWithWithDuplicated() throws Exception assertThat( getAllNodesFromIndexScanEndsWith( "apa*" ), equalTo( Collections.emptyList() ) ); assertThat( getAllNodesFromIndexScanEndsWith( "" ), equalTo( asList( 1L, 2L, 3L, 4L, 5L, 6L ) ) ); } + + @Test + public void testIndexEndsWithWithDuplicatedWithIndexQuery() throws Exception + { + updateAndCommit( asList( + IndexEntryUpdate.add( 1L, index, "a" ), + IndexEntryUpdate.add( 2L, index, "A" ), + IndexEntryUpdate.add( 3L, index, "apa" ), + IndexEntryUpdate.add( 4L, index, "apa" ), + IndexEntryUpdate.add( 5L, index, "longapa" ), + IndexEntryUpdate.add( 6L, index, "apalong" ) ) ); + + assertThat( query( stringSuffix( 1, "a" ) ), equalTo( asList( 1L, 3L, 4L, 5L ) ) ); + assertThat( query( stringSuffix( 1, "apa" ) ), equalTo( asList( 3L, 4L, 5L ) ) ); + assertThat( query( stringSuffix( 1, "apa*" ) ), equalTo( Collections.emptyList() ) ); + assertThat( query( stringSuffix( 1, "" ) ), equalTo( asList( 1L, 2L, 3L, 4L, 5L, 6L ) ) ); + } } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/index/inmemory/HashBasedIndex.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/index/inmemory/HashBasedIndex.java index 0a42b9968e32f..2578dc5f4f657 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/index/inmemory/HashBasedIndex.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/index/inmemory/HashBasedIndex.java @@ -262,10 +262,19 @@ public PrimitiveLongIterator query( IndexQuery... predicates ) case exact: return seek( ((IndexQuery.ExactPredicate)predicate).value() ); case rangeNumeric: IndexQuery.NumberRangePredicate np = (IndexQuery.NumberRangePredicate) predicate; - return rangeSeekByNumberInclusive( np.getFrom(), np.getTo() ); + return rangeSeekByNumberInclusive( np.from(), np.to() ); case rangeString: - IndexQuery.StringRangePredicate sp = (IndexQuery.StringRangePredicate) predicate; - return rangeSeekByString( sp.getFrom(), sp.isFromInclusive(), sp.getTo(), sp.isToInclusive() ); + IndexQuery.StringRangePredicate srp = (IndexQuery.StringRangePredicate) predicate; + return rangeSeekByString( srp.from(), srp.fromInclusive(), srp.to(), srp.toInclusive() ); + case stringPrefix: + IndexQuery.StringPrefixPredicate spp = (IndexQuery.StringPrefixPredicate) predicate; + return rangeSeekByPrefix( spp.prefix() ); + case stringContains: + IndexQuery.StringContainsPredicate scp = (IndexQuery.StringContainsPredicate) predicate; + return containsString( scp.contains() ); + case stringSuffix: + IndexQuery.StringSuffixPredicate ssp = (IndexQuery.StringSuffixPredicate) predicate; + return endsWith( ssp.suffix() ); default: throw new RuntimeException( "Unsupported query: " + Arrays.toString( predicates ) ); } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/StateHandlingStatementOperationsTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/StateHandlingStatementOperationsTest.java index a1aba4d60d438..5a8f486638c78 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/StateHandlingStatementOperationsTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/StateHandlingStatementOperationsTest.java @@ -348,6 +348,96 @@ public void shouldConsiderTransactionStateDuringIndexRangeSeekByPrefix() throws assertEquals( asSet( 42L, 43L ), PrimitiveLongCollections.toSet( results ) ); } + @Test + public void shouldConsiderTransactionStateDuringIndexRangeSeekByPrefixWithIndexQuery() throws Exception + { + // Given + TransactionState txState = mock( TransactionState.class ); + KernelStatement statement = mock( KernelStatement.class ); + when( statement.hasTxStateWithChanges() ).thenReturn( true ); + when( statement.txState() ).thenReturn( txState ); + when( txState.indexUpdatesForRangeSeekByPrefix( index, "prefix" ) ).thenReturn( + new DiffSets<>( Collections.singleton( 42L ), Collections.singleton( 44L ) ) + ); + when( txState.addedAndRemovedNodes() ).thenReturn( + new DiffSets<>( Collections.singleton( 45L ), Collections.singleton( 46L ) ) + ); + + StoreReadLayer storeReadLayer = mock( StoreReadLayer.class ); + IndexReader indexReader = addMockedIndexReader( statement ); + IndexQuery.StringPrefixPredicate indexQuery = IndexQuery.stringPrefix( index.schema().getPropertyId(), "prefix" ); + when( indexReader.query( indexQuery ) ).thenReturn( + PrimitiveLongCollections.resourceIterator( PrimitiveLongCollections.iterator( 43L, 44L, 46L ), null ) ); + + StateHandlingStatementOperations context = newTxStateOps( storeReadLayer ); + + // When + PrimitiveLongIterator results = context.indexQuery( statement, index, indexQuery ); + + // Then + assertEquals( asSet( 42L, 43L ), PrimitiveLongCollections.toSet( results ) ); + } + + @Test + public void shouldConsiderTransactionStateDuringIndexRangeSeekByContainsWithIndexQuery() throws Exception + { + // Given + TransactionState txState = mock( TransactionState.class ); + KernelStatement statement = mock( KernelStatement.class ); + when( statement.hasTxStateWithChanges() ).thenReturn( true ); + when( statement.txState() ).thenReturn( txState ); + when( txState.indexUpdatesForScanOrSeek( index, null ) ).thenReturn( + new DiffSets<>( Collections.singleton( 42L ), Collections.singleton( 44L ) ) + ); + when( txState.addedAndRemovedNodes() ).thenReturn( + new DiffSets<>( Collections.singleton( 45L ), Collections.singleton( 46L ) ) + ); + + StoreReadLayer storeReadLayer = mock( StoreReadLayer.class ); + IndexReader indexReader = addMockedIndexReader( statement ); + IndexQuery.StringContainsPredicate indexQuery = IndexQuery.stringContains( index.schema().getPropertyId(), "contains" ); + when( indexReader.query( indexQuery ) ).thenReturn( + PrimitiveLongCollections.resourceIterator( PrimitiveLongCollections.iterator( 43L, 44L, 46L ), null ) ); + + StateHandlingStatementOperations context = newTxStateOps( storeReadLayer ); + + // When + PrimitiveLongIterator results = context.indexQuery( statement, index, indexQuery ); + + // Then + assertEquals( asSet( 42L, 43L ), PrimitiveLongCollections.toSet( results ) ); + } + + @Test + public void shouldConsiderTransactionStateDuringIndexRangeSeekBySuffixWithIndexQuery() throws Exception + { + // Given + TransactionState txState = mock( TransactionState.class ); + KernelStatement statement = mock( KernelStatement.class ); + when( statement.hasTxStateWithChanges() ).thenReturn( true ); + when( statement.txState() ).thenReturn( txState ); + when( txState.indexUpdatesForScanOrSeek( index, null ) ).thenReturn( + new DiffSets<>( Collections.singleton( 42L ), Collections.singleton( 44L ) ) + ); + when( txState.addedAndRemovedNodes() ).thenReturn( + new DiffSets<>( Collections.singleton( 45L ), Collections.singleton( 46L ) ) + ); + + StoreReadLayer storeReadLayer = mock( StoreReadLayer.class ); + IndexReader indexReader = addMockedIndexReader( statement ); + IndexQuery.StringSuffixPredicate indexQuery = IndexQuery.stringSuffix( index.schema().getPropertyId(), "suffix" ); + when( indexReader.query( indexQuery ) ).thenReturn( + PrimitiveLongCollections.resourceIterator( PrimitiveLongCollections.iterator( 43L, 44L, 46L ), null ) ); + + StateHandlingStatementOperations context = newTxStateOps( storeReadLayer ); + + // When + PrimitiveLongIterator results = context.indexQuery( statement, index, indexQuery ); + + // Then + assertEquals( asSet( 42L, 43L ), PrimitiveLongCollections.toSet( results ) ); + } + @SuppressWarnings( "unchecked" ) @Test public void shouldConsiderTransactionStateDuringIndexBetweenRangeSeekByNumber() throws Exception diff --git a/community/lucene-index/src/main/java/org/neo4j/kernel/api/impl/schema/reader/SimpleIndexReader.java b/community/lucene-index/src/main/java/org/neo4j/kernel/api/impl/schema/reader/SimpleIndexReader.java index 7912ec9fed9cf..d9add489c9ee8 100644 --- a/community/lucene-index/src/main/java/org/neo4j/kernel/api/impl/schema/reader/SimpleIndexReader.java +++ b/community/lucene-index/src/main/java/org/neo4j/kernel/api/impl/schema/reader/SimpleIndexReader.java @@ -95,10 +95,19 @@ public PrimitiveLongIterator query( IndexQuery... predicates ) return scan(); case rangeNumeric: IndexQuery.NumberRangePredicate np = (IndexQuery.NumberRangePredicate) predicate; - return rangeSeekByNumberInclusive( np.getFrom(), np.getTo() ); + return rangeSeekByNumberInclusive( np.from(), np.to() ); case rangeString: IndexQuery.StringRangePredicate sp = (IndexQuery.StringRangePredicate) predicate; - return rangeSeekByString( sp.getFrom(), sp.isFromInclusive(), sp.getTo(), sp.isToInclusive() ); + return rangeSeekByString( sp.from(), sp.fromInclusive(), sp.to(), sp.toInclusive() ); + case stringPrefix: + IndexQuery.StringPrefixPredicate spp = (IndexQuery.StringPrefixPredicate) predicate; + return rangeSeekByPrefix( spp.prefix() ); + case stringContains: + IndexQuery.StringContainsPredicate scp = (IndexQuery.StringContainsPredicate) predicate; + return containsString( scp.contains() ); + case stringSuffix: + IndexQuery.StringSuffixPredicate ssp = (IndexQuery.StringSuffixPredicate) predicate; + return endsWith( ssp.suffix() ); default: // todo figure out a more specific exception throw new RuntimeException( "Index query not supported: " + Arrays.toString( predicates ) );