From cb2edb9af75ff8a6b5a028b526b9ba4bb3e9edbd Mon Sep 17 00:00:00 2001 From: Satia Herfert Date: Tue, 31 Jul 2018 17:33:07 +0200 Subject: [PATCH] TxState: new methods to return DiffSets with PropertyValues When we retrieve the actual property values from an index during an index query, we need to also get the actual property values from the TX state instead of just the IDs of added nodes. This adds new methods for 5 index operations to get DiffSets including the property values. --- .../interpreted/pipes/NodeIndexSeeker.scala | 1 + .../api/txstate/NodeWithPropertyValues.java | 83 +++ .../api/txstate/ReadableTransactionState.java | 33 + .../NodeIndexTransactionStateTestBase.java | 289 ++++++++- .../neo4j/kernel/impl/api/state/TxState.java | 163 ++++- .../newapi/DefaultNodeValueIndexCursor.java | 114 +++- .../api/state/TxStateCompositeIndexTest.java | 22 +- .../kernel/impl/api/state/TxStateTest.java | 587 +++++++++++++----- .../org/neo4j/values/storable/ValueTuple.java | 8 + .../operators/NodeIndexSeekOperator.scala | 9 +- 10 files changed, 1084 insertions(+), 225 deletions(-) create mode 100644 community/kernel-api/src/main/java/org/neo4j/storageengine/api/txstate/NodeWithPropertyValues.java diff --git a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeIndexSeeker.scala b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeIndexSeeker.scala index 8a042b7e0409..67a0bc5ca8f8 100644 --- a/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeIndexSeeker.scala +++ b/community/cypher/interpreted-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeIndexSeeker.scala @@ -47,6 +47,7 @@ trait NodeIndexSeeker { // index seek + // TODO make case class of (NodeValue, Seq[Value]) and use Array instead of Seq protected def indexSeek(state: QueryState, indexReference: IndexReference, propertyIndicesWithValues: Seq[Int], diff --git a/community/kernel-api/src/main/java/org/neo4j/storageengine/api/txstate/NodeWithPropertyValues.java b/community/kernel-api/src/main/java/org/neo4j/storageengine/api/txstate/NodeWithPropertyValues.java new file mode 100644 index 000000000000..022d96c665e1 --- /dev/null +++ b/community/kernel-api/src/main/java/org/neo4j/storageengine/api/txstate/NodeWithPropertyValues.java @@ -0,0 +1,83 @@ +/* + * 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.storageengine.api.txstate; + +import java.util.Arrays; +import java.util.Objects; + +import org.neo4j.values.storable.Value; + +/** + * A node together with properties. This class is needed to present changes in the transaction state to index operations + * that require knowing the affected property values as well. + * + * TODO create interface and impl + */ +public class NodeWithPropertyValues +{ + + private final long nodeId; + private final Value[] values; + + public NodeWithPropertyValues( long nodeId, Value[] values ) + { + this.nodeId = nodeId; + this.values = values; + } + + public long getNodeId() + { + return nodeId; + } + + public Value[] getValues() + { + return values; + } + + @Override + public String toString() + { + return "NodeWithPropertyValues{" + "nodeId=" + nodeId + ", values=" + Arrays.toString( values ) + '}'; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + NodeWithPropertyValues that = (NodeWithPropertyValues) o; + return nodeId == that.nodeId && Arrays.equals( values, that.values ); + } + + @Override + public int hashCode() + { + int result = Objects.hash( nodeId ); + result = 31 * result + Arrays.hashCode( values ); + return result; + } +} diff --git a/community/kernel-api/src/main/java/org/neo4j/storageengine/api/txstate/ReadableTransactionState.java b/community/kernel-api/src/main/java/org/neo4j/storageengine/api/txstate/ReadableTransactionState.java index c59fa2aec086..7acdb410e867 100644 --- a/community/kernel-api/src/main/java/org/neo4j/storageengine/api/txstate/ReadableTransactionState.java +++ b/community/kernel-api/src/main/java/org/neo4j/storageengine/api/txstate/ReadableTransactionState.java @@ -109,16 +109,49 @@ public interface ReadableTransactionState LongDiffSets indexUpdatesForScan( IndexDescriptor index ); + /** + * Returns the updates for a particular index as a DiffSets of both Node values and propertyValues. + * + * If property values are not needed, {@link ReadableTransactionState#indexUpdatesForScan(IndexDescriptor)} )} + * should be used instead. + */ + ReadableDiffSets indexUpdatesWithValuesForScan( IndexDescriptor descriptor ); + LongDiffSets indexUpdatesForSuffixOrContains( IndexDescriptor index, IndexQuery query ); + /** + * Returns the updates for a particular range as a DiffSets of both Node values and propertyValues. + * + * If property values are not needed, {@link ReadableTransactionState#indexUpdatesForSuffixOrContains(IndexDescriptor, IndexQuery)} )} + * should be used instead. + */ + ReadableDiffSets indexUpdatesWithValuesForSuffixOrContains( IndexDescriptor descriptor, IndexQuery query ); + LongDiffSets indexUpdatesForSeek( IndexDescriptor index, ValueTuple values ); LongDiffSets indexUpdatesForRangeSeek( IndexDescriptor index, ValueGroup valueGroup, Value lower, boolean includeLower, Value upper, boolean includeUpper ); + /** + * Returns the updates for a particular range as a DiffSets of both Node values and propertyValues. + * + * If property values are not needed, {@link ReadableTransactionState#indexUpdatesForRangeSeek(IndexDescriptor, ValueGroup, Value, boolean, Value, boolean)} + * should be used instead. + */ + ReadableDiffSets indexUpdatesWithValuesForRangeSeek( IndexDescriptor descriptor, ValueGroup valueGroup, Value lower, + boolean includeLower, Value upper, boolean includeUpper ); + LongDiffSets indexUpdatesForRangeSeekByPrefix( IndexDescriptor index, String prefix ); + /** + * Returns the updates for a particular range as a DiffSets of both Node values and propertyValues. + * + * If property values are not needed, {@link ReadableTransactionState#indexUpdatesForRangeSeekByPrefix(IndexDescriptor, String)} )} + * should be used instead. + */ + ReadableDiffSets indexUpdatesWithValuesForRangeSeekByPrefix( IndexDescriptor descriptor, String prefix ); + NodeState getNodeState( long id ); RelationshipState getRelationshipState( long id ); diff --git a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/NodeIndexTransactionStateTestBase.java b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/NodeIndexTransactionStateTestBase.java index b926bf15a1b8..1b042d960de9 100644 --- a/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/NodeIndexTransactionStateTestBase.java +++ b/community/kernel-api/src/test/java/org/neo4j/internal/kernel/api/NodeIndexTransactionStateTestBase.java @@ -25,15 +25,23 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.TimeUnit; import org.neo4j.graphdb.Label; import org.neo4j.graphdb.TransactionTerminatedException; +import org.neo4j.helpers.collection.Pair; +import org.neo4j.internal.kernel.api.exceptions.KernelException; +import org.neo4j.values.storable.TextValue; +import org.neo4j.values.storable.Value; import org.neo4j.values.storable.Values; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; +import static org.neo4j.values.storable.Values.stringValue; +// TODO test without values (when we don't want to get values and thus choose a different code path). Create a rule or similar to to this public abstract class NodeIndexTransactionStateTestBase extends KernelAPIWriteTestBase { @@ -44,7 +52,7 @@ public abstract class NodeIndexTransactionStateTestBase> expected = new HashSet<>(); try ( Transaction tx = beginTransaction() ) { expected.add( nodeWithProp( tx, "1suff" ) ); @@ -60,19 +68,212 @@ public void shouldPerformStringSuffixSearch() throws Exception int label = tx.tokenRead().nodeLabel( "Node" ); int prop = tx.tokenRead().propertyKey( "prop" ); expected.add( nodeWithProp( tx, "2suff" ) ); - nodeWithProp( tx, "skruff" ); + nodeWithPropId( tx, "skruff" ); IndexReference index = tx.schemaRead().index( label, prop ); - try ( NodeValueIndexCursor nodes = tx.cursors().allocateNodeValueIndexCursor() ) - { - tx.dataRead().nodeIndexSeek( index, nodes, IndexOrder.NONE, IndexQuery.stringSuffix( prop, "suff" ) ); - MutableLongSet found = new LongHashSet(); - while ( nodes.next() ) - { - found.add( nodes.nodeReference() ); - } + assertNodeAndValueForSeek( expected, tx, index, IndexQuery.stringSuffix( prop, "suff" )); + } + } - assertThat( found, equalTo( expected ) ); - } + @Test + public void shouldPerformScan() throws Exception + { + // given + Set> expected = new HashSet<>(); + long nodeToDelete; + long nodeToChange; + try ( Transaction tx = beginTransaction() ) + { + expected.add( nodeWithProp( tx, "suff1" ) ); + expected.add( nodeWithProp( tx, "supp" ) ); + nodeToDelete = nodeWithPropId( tx, "supp" ); + nodeToChange = nodeWithPropId( tx, "supper" ); + tx.success(); + } + + createIndex(); + + // when + try ( Transaction tx = beginTransaction() ) + { + int label = tx.tokenRead().nodeLabel( "Node" ); + int prop = tx.tokenRead().propertyKey( "prop" ); + expected.add( nodeWithProp( tx, "suff2" ) ); + tx.dataWrite().nodeDelete( nodeToDelete ); + tx.dataWrite().nodeRemoveProperty( nodeToChange, prop ); + + IndexReference index = tx.schemaRead().index( label, prop ); + + assertNodeAndValueForScan( expected, tx, index ); + } + } + + @Test + public void shouldPerformEqualitySeek() throws Exception + { + // given + Set> expected = new HashSet<>(); + try ( Transaction tx = beginTransaction() ) + { + expected.add( nodeWithProp( tx, "banana" ) ); + nodeWithProp( tx, "apple" ); + tx.success(); + } + + createIndex(); + + // when + try ( Transaction tx = beginTransaction() ) + { + int label = tx.tokenRead().nodeLabel( "Node" ); + int prop = tx.tokenRead().propertyKey( "prop" ); + expected.add( nodeWithProp( tx, "banana" ) ); + nodeWithProp( tx, "dragonfruit" ); + IndexReference index = tx.schemaRead().index( label, prop ); + assertNodeAndValueForSeek( expected, tx, index, IndexQuery.exact( prop, "banana") ); + } + } + + @Test + public void shouldPerformStringPrefixSearch() throws Exception + { + // given + Set> expected = new HashSet<>(); + try ( Transaction tx = beginTransaction() ) + { + expected.add( nodeWithProp( tx, "suff1" ) ); + nodeWithPropId( tx, "supp" ); + tx.success(); + } + + createIndex(); + + // when + try ( Transaction tx = beginTransaction() ) + { + int label = tx.tokenRead().nodeLabel( "Node" ); + int prop = tx.tokenRead().propertyKey( "prop" ); + expected.add( nodeWithProp( tx, "suff2" ) ); + nodeWithPropId( tx, "skruff" ); + IndexReference index = tx.schemaRead().index( label, prop ); + + assertNodeAndValueForSeek( expected, tx, index, IndexQuery.stringPrefix( prop, "suff" ) ); + } + } + + @Test + public void shouldPerformStringRangeSearch() throws Exception + { + // given + Set> expected = new HashSet<>(); + try ( Transaction tx = beginTransaction() ) + { + expected.add( nodeWithProp( tx, "banana" ) ); + nodeWithProp( tx, "apple" ); + tx.success(); + } + + createIndex(); + + // when + try ( Transaction tx = beginTransaction() ) + { + int label = tx.tokenRead().nodeLabel( "Node" ); + int prop = tx.tokenRead().propertyKey( "prop" ); + expected.add( nodeWithProp( tx, "cherry" ) ); + nodeWithProp( tx, "dragonfruit" ); + IndexReference index = tx.schemaRead().index( label, prop ); + assertNodeAndValueForSeek( expected, tx, index, IndexQuery.range( prop, "b", true, "d", false ) ); + } + } + + @Test + public void shouldPerformStringRangeSearchWithAddedNodeInTxState() throws Exception + { + // given + Set> expected = new HashSet<>(); + long nodeToChange; + try ( Transaction tx = beginTransaction() ) + { + expected.add( nodeWithProp( tx, "banana" ) ); + nodeToChange = nodeWithPropId( tx, "apple" ); + tx.success(); + } + + createIndex(); + + // when + try ( Transaction tx = beginTransaction() ) + { + int label = tx.tokenRead().nodeLabel( "Node" ); + int prop = tx.tokenRead().propertyKey( "prop" ); + expected.add( nodeWithProp( tx, "cherry" ) ); + nodeWithProp( tx, "dragonfruit" ); + IndexReference index = tx.schemaRead().index( label, prop ); + TextValue newProperty = stringValue( "blueberry" ); + tx.dataWrite().nodeSetProperty( nodeToChange, prop, newProperty ); + expected.add(Pair.of(nodeToChange, newProperty )); + + assertNodeAndValueForSeek( expected, tx, index, IndexQuery.range( prop, "b", true, "d", false ) ); + } + } + + @Test + public void shouldPerformStringRangeSearchWithRemovedNodeInTxState() throws Exception + { + // given + Set> expected = new HashSet<>(); + long nodeToChange; + try ( Transaction tx = beginTransaction() ) + { + nodeToChange = nodeWithPropId( tx, "banana" ); + nodeWithPropId( tx, "apple" ); + tx.success(); + } + + createIndex(); + + // when + try ( Transaction tx = beginTransaction() ) + { + int label = tx.tokenRead().nodeLabel( "Node" ); + int prop = tx.tokenRead().propertyKey( "prop" ); + expected.add( nodeWithProp( tx, "cherry" ) ); + nodeWithProp( tx, "dragonfruit" ); + IndexReference index = tx.schemaRead().index( label, prop ); + TextValue newProperty = stringValue( "kiwi" ); + tx.dataWrite().nodeSetProperty( nodeToChange, prop, newProperty ); + + assertNodeAndValueForSeek( expected, tx, index, IndexQuery.range( prop, "b", true, "d", false ) ); + } + } + + + @Test + public void shouldPerformStringRangeSearchWithDeletedNodeInTxState() throws Exception + { + // given + Set> expected = new HashSet<>(); + long nodeToChange; + try ( Transaction tx = beginTransaction() ) + { + nodeToChange = nodeWithPropId( tx, "banana" ); + nodeWithPropId( tx, "apple" ); + tx.success(); + } + + createIndex(); + + // when + try ( Transaction tx = beginTransaction() ) + { + int label = tx.tokenRead().nodeLabel( "Node" ); + int prop = tx.tokenRead().propertyKey( "prop" ); + expected.add( nodeWithProp( tx, "cherry" ) ); + nodeWithProp( tx, "dragonfruit" ); + IndexReference index = tx.schemaRead().index( label, prop ); + tx.dataWrite().nodeDelete( nodeToChange ); + + assertNodeAndValueForSeek( expected, tx, index, IndexQuery.range( prop, "b", true, "d", false ) ); } } @@ -83,8 +284,8 @@ public void shouldPerformStringContainsSearch() throws Exception MutableLongSet expected = new LongHashSet(); try ( Transaction tx = beginTransaction() ) { - expected.add( nodeWithProp( tx, "gnomebat" ) ); - nodeWithProp( tx, "fishwombat" ); + expected.add( nodeWithPropId( tx, "gnomebat" ) ); + nodeWithPropId( tx, "fishwombat" ); tx.success(); } @@ -95,8 +296,8 @@ public void shouldPerformStringContainsSearch() throws Exception { int label = tx.tokenRead().nodeLabel( "Node" ); int prop = tx.tokenRead().propertyKey( "prop" ); - expected.add( nodeWithProp( tx, "homeopatic" ) ); - nodeWithProp( tx, "telephonecompany" ); + expected.add( nodeWithPropId( tx, "homeopatic" ) ); + nodeWithPropId( tx, "telephonecompany" ); IndexReference index = tx.schemaRead().index( label, prop ); try ( NodeValueIndexCursor nodes = tx.cursors().allocateNodeValueIndexCursor() ) { @@ -130,13 +331,19 @@ public void shouldThrowIfTransactionTerminated() throws Exception protected abstract void terminate( Transaction transaction ); - private long nodeWithProp( Transaction tx, Object value ) throws Exception + private long nodeWithPropId( Transaction tx, Object value ) throws Exception + { + return nodeWithProp( tx, value ).first(); + } + + private Pair nodeWithProp( Transaction tx, Object value ) throws Exception { Write write = tx.dataWrite(); long node = write.nodeCreate(); write.nodeAddLabel( node, tx.tokenWrite().labelGetOrCreateForName( "Node" ) ); - write.nodeSetProperty( node, tx.tokenWrite().propertyKeyGetOrCreateForName( "prop" ), Values.of( value ) ); - return node; + Value val = Values.of( value ); + write.nodeSetProperty( node, tx.tokenWrite().propertyKeyGetOrCreateForName( "prop" ), val ); + return Pair.of( node, val ); } private void createIndex() @@ -152,4 +359,48 @@ private void createIndex() graphDb.schema().awaitIndexesOnline( 1, TimeUnit.MINUTES ); } } + + private void assertNodeAndValueForSeek( Set> expected, Transaction tx, IndexReference index, IndexQuery... queries ) throws KernelException + { + try ( NodeValueIndexCursor nodes = tx.cursors().allocateNodeValueIndexCursor() ) + { + tx.dataRead().nodeIndexSeek( index, nodes, IndexOrder.NONE, queries ); + + // Modify tx state with changes that should not be reflected in the cursor, since it was already initialized in the above statement + for ( Pair pair : expected ) + { + tx.dataWrite().nodeDelete( pair.first() ); + } + + Set> found = new HashSet<>(); + while ( nodes.next() ) + { + found.add( Pair.of( nodes.nodeReference(), nodes.propertyValue( 0 ) ) ); + } + + assertThat( found, equalTo( expected ) ); + } + } + + private void assertNodeAndValueForScan( Set> expected, Transaction tx, IndexReference index ) throws KernelException + { + try ( NodeValueIndexCursor nodes = tx.cursors().allocateNodeValueIndexCursor() ) + { + tx.dataRead().nodeIndexScan( index, nodes, IndexOrder.NONE ); + + // Modify tx state with changes that should not be reflected in the cursor, since it was already initialized in the above statement + for ( Pair pair : expected ) + { + tx.dataWrite().nodeDelete( pair.first() ); + } + + Set> found = new HashSet<>(); + while ( nodes.next() ) + { + found.add( Pair.of( nodes.nodeReference(), nodes.propertyValue( 0 ) ) ); + } + + assertThat( found, equalTo( expected ) ); + } + } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/state/TxState.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/state/TxState.java index a8eaa3e40b73..62c932f60490 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/state/TxState.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/state/TxState.java @@ -52,6 +52,7 @@ import org.neo4j.storageengine.api.txstate.GraphState; import org.neo4j.storageengine.api.txstate.LongDiffSets; import org.neo4j.storageengine.api.txstate.NodeState; +import org.neo4j.storageengine.api.txstate.NodeWithPropertyValues; import org.neo4j.storageengine.api.txstate.ReadableDiffSets; import org.neo4j.storageengine.api.txstate.RelationshipState; import org.neo4j.storageengine.api.txstate.TxStateVisitor; @@ -726,6 +727,30 @@ public LongDiffSets indexUpdatesForScan( IndexDescriptor descriptor ) return diffs; } + @Override + public ReadableDiffSets indexUpdatesWithValuesForScan( IndexDescriptor descriptor ) + { + if ( indexUpdates == null ) + { + return ReadableDiffSets.Empty.instance(); + } + Map updates = indexUpdates.get( descriptor.schema() ); + if ( updates == null ) + { + return ReadableDiffSets.Empty.instance(); + } + DiffSets diffs = new DiffSets<>(); + for ( Map.Entry entry : updates.entrySet() ) + { + Value[] values = entry.getKey().getValues(); + MutableLongDiffSets diffSets = entry.getValue(); + + diffSets.getAdded().each( ( nodeId ) -> diffs.add( new NodeWithPropertyValues( nodeId, values ) ) ); + diffSets.getRemoved().each( ( nodeId ) -> diffs.remove( new NodeWithPropertyValues( nodeId, values ) ) ); + } + return diffs; + } + @Override public LongDiffSets indexUpdatesForSuffixOrContains( IndexDescriptor descriptor, IndexQuery query ) { @@ -754,11 +779,45 @@ public LongDiffSets indexUpdatesForSuffixOrContains( IndexDescriptor descriptor, return diffs; } + @Override + public ReadableDiffSets indexUpdatesWithValuesForSuffixOrContains( IndexDescriptor descriptor, IndexQuery query ) + { + assert descriptor.schema().getPropertyIds().length == 1 : "Suffix and contains queries are only supported for single property queries"; + + if ( indexUpdates == null ) + { + return ReadableDiffSets.Empty.instance(); + } + Map updates = indexUpdates.get( descriptor.schema() ); + if ( updates == null ) + { + return ReadableDiffSets.Empty.instance(); + } + DiffSets diffs = new DiffSets<>(); + for ( Map.Entry entry : updates.entrySet() ) + { + ValueTuple key = entry.getKey(); + if ( query.acceptsValue( key.getOnlyValue() ) ) + { + Value[] values = key.getValues(); + MutableLongDiffSets diffSets = entry.getValue(); + diffSets.getAdded().each( ( nodeId ) -> diffs.add( new NodeWithPropertyValues( nodeId, values ) ) ); + diffSets.getRemoved().each( ( nodeId ) -> diffs.remove( new NodeWithPropertyValues( nodeId, values ) ) ); + } + } + return diffs; + } + @Override public LongDiffSets indexUpdatesForSeek( IndexDescriptor descriptor, ValueTuple values ) { - MutableLongDiffSets indexUpdatesForSeek = getIndexUpdatesForSeek( descriptor.schema(), values, /*create=*/false ); - return indexUpdatesForSeek == null ? LongDiffSets.EMPTY : indexUpdatesForSeek; + Map updates = getIndexUpdatesByDescriptor( descriptor.schema(), /*create*/ false ); + if ( updates != null ) + { + MutableLongDiffSets indexUpdatesForSeek = getIndexUpdatesForSeek( updates, values, /*create*/ false ); + return indexUpdatesForSeek == null ? LongDiffSets.EMPTY : indexUpdatesForSeek; + } + return LongDiffSets.EMPTY; } @Override @@ -819,6 +878,67 @@ private LongDiffSets indexUpdatesForRangeSeek( TreeMap indexUpdatesWithValuesForRangeSeek( IndexDescriptor descriptor, ValueGroup valueGroup, Value lower, + boolean includeLower, Value upper, boolean includeUpper ) + { + assert lower != null && upper != null : "Use Values.NO_VALUE to encode the lack of a bound"; + + TreeMap sortedUpdates = getSortedIndexUpdates( descriptor.schema() ); + if ( sortedUpdates == null ) + { + return ReadableDiffSets.Empty.instance(); + } + + ValueTuple selectedLower; + boolean selectedIncludeLower; + + ValueTuple selectedUpper; + boolean selectedIncludeUpper; + + if ( lower == NO_VALUE ) + { + selectedLower = ValueTuple.of( Values.minValue( valueGroup, upper ) ); + selectedIncludeLower = true; + } + else + { + selectedLower = ValueTuple.of( lower ); + selectedIncludeLower = includeLower; + } + + if ( upper == NO_VALUE ) + { + selectedUpper = ValueTuple.of( Values.maxValue( valueGroup, lower ) ); + selectedIncludeUpper = false; + } + else + { + selectedUpper = ValueTuple.of( upper ); + selectedIncludeUpper = includeUpper; + } + + return indexUpdatesWithValuesForRangeSeek( sortedUpdates, selectedLower, selectedIncludeLower, selectedUpper, selectedIncludeUpper ); + } + + private ReadableDiffSets indexUpdatesWithValuesForRangeSeek( TreeMap sortedUpdates, + ValueTuple selectedLower, boolean selectedIncludeLower, ValueTuple selectedUpper, boolean selectedIncludeUpper ) + { + DiffSets diffs = new DiffSets<>(); + + Map inRange = sortedUpdates.subMap( selectedLower, selectedIncludeLower, selectedUpper, selectedIncludeUpper ); + for ( Map.Entry entry : inRange.entrySet() ) + { + ValueTuple values = entry.getKey(); + Value[] valuesArray = values.getValues(); + MutableLongDiffSets diffForSpecificValue = entry.getValue(); + + diffForSpecificValue.getAdded().each( ( nodeId ) -> diffs.add( new NodeWithPropertyValues( nodeId, valuesArray ) ) ); + diffForSpecificValue.getRemoved().each( ( nodeId ) -> diffs.remove( new NodeWithPropertyValues( nodeId, valuesArray ) ) ); + } + return diffs; + } + @Override public LongDiffSets indexUpdatesForRangeSeekByPrefix( IndexDescriptor descriptor, String prefix ) { @@ -846,6 +966,34 @@ public LongDiffSets indexUpdatesForRangeSeekByPrefix( IndexDescriptor descriptor return diffs; } + @Override + public ReadableDiffSets indexUpdatesWithValuesForRangeSeekByPrefix( IndexDescriptor descriptor, String prefix ) + { + TreeMap sortedUpdates = getSortedIndexUpdates( descriptor.schema() ); + if ( sortedUpdates == null ) + { + return ReadableDiffSets.Empty.instance(); + } + ValueTuple floor = ValueTuple.of( Values.stringValue( prefix ) ); + DiffSets diffs = new DiffSets<>(); + for ( Map.Entry entry : sortedUpdates.tailMap( floor ).entrySet() ) + { + ValueTuple key = entry.getKey(); + if ( ((TextValue) key.getOnlyValue()).stringValue().startsWith( prefix ) ) + { + MutableLongDiffSets diffSets = entry.getValue(); + Value[] values = key.getValues(); + diffSets.getAdded().each( ( nodeId ) -> diffs.add( new NodeWithPropertyValues( nodeId, values ) ) ); + diffSets.getRemoved().each( ( nodeId ) -> diffs.remove( new NodeWithPropertyValues( nodeId, values ) ) ); + } + else + { + break; + } + } + return diffs; + } + // Ensure sorted index updates for a given index. This is needed for range query support and // may involve converting the existing hash map first // @@ -910,17 +1058,6 @@ public void indexDoUpdateEntry( SchemaDescriptor descriptor, long nodeId, } } - private MutableLongDiffSets getIndexUpdatesForSeek( - SchemaDescriptor schema, ValueTuple values, boolean create ) - { - Map updates = getIndexUpdatesByDescriptor( schema, create ); - if ( updates != null ) - { - return getIndexUpdatesForSeek( updates, values, create ); - } - return null; - } - private MutableLongDiffSets getIndexUpdatesForSeek( Map updates, ValueTuple values, boolean create ) { diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/DefaultNodeValueIndexCursor.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/DefaultNodeValueIndexCursor.java index dceb1aee16f8..415cfe19cd13 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/DefaultNodeValueIndexCursor.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/newapi/DefaultNodeValueIndexCursor.java @@ -21,20 +21,27 @@ import org.eclipse.collections.api.iterator.LongIterator; import org.eclipse.collections.api.set.primitive.LongSet; +import org.eclipse.collections.api.set.primitive.MutableLongSet; import org.eclipse.collections.impl.factory.primitive.LongSets; import org.eclipse.collections.impl.iterator.ImmutableEmptyLongIterator; +import org.eclipse.collections.impl.set.mutable.primitive.LongHashSet; import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.Set; import org.neo4j.graphdb.Resource; import org.neo4j.internal.kernel.api.IndexQuery; import org.neo4j.internal.kernel.api.NodeCursor; import org.neo4j.internal.kernel.api.NodeValueIndexCursor; import org.neo4j.kernel.api.txstate.TransactionState; +import org.neo4j.storageengine.api.txstate.NodeWithPropertyValues; import org.neo4j.storageengine.api.schema.IndexDescriptor; import org.neo4j.storageengine.api.schema.IndexProgressor; import org.neo4j.storageengine.api.schema.IndexProgressor.NodeValueClient; import org.neo4j.storageengine.api.txstate.LongDiffSets; +import org.neo4j.storageengine.api.txstate.ReadableDiffSets; import org.neo4j.values.storable.Value; import org.neo4j.values.storable.ValueCategory; import org.neo4j.values.storable.ValueGroup; @@ -52,6 +59,7 @@ final class DefaultNodeValueIndexCursor extends IndexCursor private IndexQuery[] query; private Value[] values; private LongIterator added = ImmutableEmptyLongIterator.INSTANCE; + private Iterator addedWithValues = Collections.emptyIterator(); private LongSet removed = LongSets.immutable.empty(); // TODO This should not be set simply to true. The NodeIndexSeeker should dictate that, either on creation or on initialize private boolean needsValues; @@ -132,13 +140,19 @@ public boolean needsValues() @Override public boolean next() { - if ( added.hasNext() ) + if ( !needsValues && added.hasNext() ) { this.node = added.next(); - // TODO we need to get the values also for added nodes (from the TXState)! this.values = null; return true; } + else if ( needsValues && addedWithValues.hasNext() ) + { + NodeWithPropertyValues nodeWithPropertyValues = addedWithValues.next(); + this.node = nodeWithPropertyValues.getNodeId(); + this.values = nodeWithPropertyValues.getValues(); + return true; + } else { return innerNext(); @@ -198,6 +212,7 @@ public void close() this.values = null; this.read = null; this.added = ImmutableEmptyLongIterator.INSTANCE; + this.addedWithValues = Collections.emptyIterator(); this.removed = LongSets.immutable.empty(); try @@ -243,10 +258,21 @@ private void prefixQuery( IndexDescriptor descriptor, IndexQuery.StringPrefixPre if ( read.hasTxStateWithChanges() ) { TransactionState txState = read.txState(); - LongDiffSets changes = txState - .indexUpdatesForRangeSeekByPrefix( descriptor, predicate.prefix() ); - added = changes.augment( ImmutableEmptyLongIterator.INSTANCE ); - removed = removed( txState, changes ); + + if ( needsValues ) + { + ReadableDiffSets changes = + txState.indexUpdatesWithValuesForRangeSeekByPrefix( descriptor, predicate.prefix() ); + addedWithValues = changes.getAdded().iterator(); + removed = removed( txState, changes.getRemoved() ); + } + else + { + LongDiffSets changes = txState + .indexUpdatesForRangeSeekByPrefix( descriptor, predicate.prefix() ); + added = changes.augment( ImmutableEmptyLongIterator.INSTANCE ); + removed = removed( txState, changes ); + } } } @@ -258,12 +284,24 @@ private void rangeQuery( IndexDescriptor descriptor, IndexQuery.RangePredicate changes = + txState.indexUpdatesWithValuesForRangeSeek( descriptor, valueGroup, predicate.fromValue(), predicate.fromInclusive(), + predicate.toValue(), predicate.toInclusive() ); + addedWithValues = changes.getAdded().iterator(); + removed = removed( txState, changes.getRemoved() ); + } + else + { + LongDiffSets changes = txState.indexUpdatesForRangeSeek( + descriptor, valueGroup, + predicate.fromValue(), predicate.fromInclusive(), + predicate.toValue(), predicate.toInclusive() ); + added = changes.augment( ImmutableEmptyLongIterator.INSTANCE ); + removed = removed( txState, changes ); + } } } @@ -273,9 +311,20 @@ private void scanQuery( IndexDescriptor descriptor ) if ( read.hasTxStateWithChanges() ) { TransactionState txState = read.txState(); - LongDiffSets changes = txState.indexUpdatesForScan( descriptor ); - added = changes.augment( ImmutableEmptyLongIterator.INSTANCE ); - removed = removed( txState, changes ); + + if ( needsValues ) + { + ReadableDiffSets changes = + txState.indexUpdatesWithValuesForScan( descriptor ); + addedWithValues = changes.getAdded().iterator(); + removed = removed( txState, changes.getRemoved() ); + } + else + { + LongDiffSets changes = txState.indexUpdatesForScan( descriptor ); + added = changes.augment( ImmutableEmptyLongIterator.INSTANCE ); + removed = removed( txState, changes ); + } } } @@ -285,19 +334,34 @@ private void suffixOrContainsQuery( IndexDescriptor descriptor, IndexQuery query if ( read.hasTxStateWithChanges() ) { TransactionState txState = read.txState(); - LongDiffSets changes = txState.indexUpdatesForSuffixOrContains( descriptor, query ); - added = changes.augment( ImmutableEmptyLongIterator.INSTANCE ); - removed = removed( txState, changes ); + + if ( needsValues ) + { + ReadableDiffSets changes = + txState.indexUpdatesWithValuesForSuffixOrContains( descriptor, query );; + addedWithValues = changes.getAdded().iterator(); + removed = removed( txState, changes.getRemoved() ); + } + else + { + LongDiffSets changes = txState.indexUpdatesForSuffixOrContains( descriptor, query ); + added = changes.augment( ImmutableEmptyLongIterator.INSTANCE ); + removed = removed( txState, changes ); + } } } private void seekQuery( IndexDescriptor descriptor, IndexQuery[] query ) { - needsValues = true; + // We should never be asked to provide values for a seek query. But a NodeValueClientFilter + // might ask us, and then we just have to ignore that and set it back to false + this.needsValues = false; + IndexQuery.ExactPredicate[] exactPreds = assertOnlyExactPredicates( query ); if ( read.hasTxStateWithChanges() ) { TransactionState txState = read.txState(); + LongDiffSets changes = read.txState() .indexUpdatesForSeek( descriptor, IndexQuery.asValueTuple( exactPreds ) ); added = changes.augment( ImmutableEmptyLongIterator.INSTANCE ); @@ -310,6 +374,18 @@ private LongSet removed( TransactionState txState, LongDiffSets changes ) return mergeToSet( txState.addedAndRemovedNodes().getRemoved(), changes.getRemoved() ); } + private LongSet removed( TransactionState txState, Set removed ) + { + LongSet removedFromTXState = txState.addedAndRemovedNodes().getRemoved(); + final MutableLongSet set = new LongHashSet( removedFromTXState.size() + removed.size() ); + set.addAll( removedFromTXState ); + for ( NodeWithPropertyValues nodeWithPropertyValues : removed ) + { + set.add( nodeWithPropertyValues.getNodeId() ); + } + return set; + } + private static IndexQuery.ExactPredicate[] assertOnlyExactPredicates( IndexQuery[] predicates ) { IndexQuery.ExactPredicate[] exactPredicates; diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/TxStateCompositeIndexTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/TxStateCompositeIndexTest.java index 37d0d4db08a0..2907724e6d28 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/TxStateCompositeIndexTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/TxStateCompositeIndexTest.java @@ -31,13 +31,17 @@ import org.neo4j.kernel.api.txstate.TransactionState; import org.neo4j.storageengine.api.schema.IndexDescriptor; import org.neo4j.storageengine.api.txstate.LongDiffSets; +import org.neo4j.storageengine.api.txstate.NodeWithPropertyValues; +import org.neo4j.storageengine.api.txstate.ReadableDiffSets; import org.neo4j.values.storable.ValueTuple; +import static org.eclipse.collections.impl.factory.Sets.unionAll; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.neo4j.collection.PrimitiveLongCollections.toSet; import static org.neo4j.helpers.collection.Iterators.asSet; import static org.neo4j.helpers.collection.Pair.of; +import static org.neo4j.kernel.impl.api.state.TxStateTest.newSetWithValues; public class TxStateCompositeIndexTest { @@ -59,9 +63,11 @@ public void shouldScanOnAnEmptyTxState() { // WHEN LongDiffSets diffSets = state.indexUpdatesForScan( indexOn_1_1_2 ); + ReadableDiffSets diffSets2 = state.indexUpdatesWithValuesForScan( indexOn_1_1_2 ); // THEN assertTrue( diffSets.isEmpty() ); + assertTrue( diffSets2.isEmpty() ); } @Test @@ -84,9 +90,11 @@ public void shouldScanWhenThereAreNewNodes() // WHEN LongDiffSets diffSets = state.indexUpdatesForScan( indexOn_1_1_2 ); + ReadableDiffSets diffSets2 = state.indexUpdatesWithValuesForScan( indexOn_1_1_2 ); // THEN assertEquals( asSet( 42L, 43L ), toSet( diffSets.getAdded() ) ); + assertEquals( unionAll( newSetWithValues( 42L, "42value1", "42value2" ), newSetWithValues( 43L, "43value1", "43value2" ) ), diffSets2.getAdded() ); } @Test @@ -99,7 +107,6 @@ public void shouldSeekWhenThereAreNewStringNodes() // WHEN LongDiffSets diffSets = state.indexUpdatesForSeek( indexOn_1_1_2, ValueTuple.of( "43value1", "43value2" ) ); - // THEN assertEquals( asSet( 43L ), toSet( diffSets.getAdded() ) ); } @@ -108,8 +115,8 @@ public void shouldSeekWhenThereAreNewStringNodes() public void shouldSeekWhenThereAreNewNumberNodes() { // GIVEN - modifyIndex( indexOn_1_1_2 ).addDefaultStringProperties( 42L, 43L ); - modifyIndex( indexOn_1_2_3 ).addDefaultStringProperties( 44L ); + modifyIndex( indexOn_1_1_2 ).addDefaultNumberEntries( 42L, 43L ); + modifyIndex( indexOn_1_2_3 ).addDefaultNumberEntries( 44L ); // WHEN LongDiffSets diffSets = @@ -129,16 +136,19 @@ public void shouldHandleMixedAddsAndRemovesEntry() // WHEN LongDiffSets diffSets = state.indexUpdatesForScan( indexOn_1_1_2 ); + ReadableDiffSets diffSets2 = state.indexUpdatesWithValuesForScan( indexOn_1_1_2 ); // THEN assertEquals( asSet( 42L ), toSet( diffSets.getAdded() ) ); + assertEquals( newSetWithValues( 42L, "42value1", "42value2" ), diffSets2.getAdded() ); assertEquals( asSet( 44L ), toSet( diffSets.getRemoved() ) ); + assertEquals( newSetWithValues( 44L, "44value1", "44value2" ), diffSets2.getRemoved() ); } @Test public void shouldSeekWhenThereAreManyEntriesWithTheSameValues() { - // GIVEN + // GIVEN (note that 44 has the same properties as 43) modifyIndex( indexOn_1_1_2 ).addDefaultStringEntries( 42L, 43L ); state.indexDoUpdateEntry( indexOn_1_1_2.schema(), 44L, null, getDefaultStringPropertyValues( 43L, indexOn_1_1_2.schema().getPropertyIds() ) ); @@ -205,7 +215,7 @@ private interface IndexUpdater void removeDefaultStringEntries( long... nodeIds ); - void addDefaultStringProperties( long... nodeIds ); + void addDefaultNumberEntries( long... nodeIds ); } private IndexUpdater modifyIndex( final IndexDescriptor descriptor ) @@ -225,7 +235,7 @@ public void removeDefaultStringEntries( long... nodeIds ) } @Override - public void addDefaultStringProperties( long... nodeIds ) + public void addDefaultNumberEntries( long... nodeIds ) { Collection> entries = new ArrayList<>( nodeIds.length ); for ( long nodeId : nodeIds ) diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/TxStateTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/TxStateTest.java index fe0a77367447..34a8936c290c 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/TxStateTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/state/TxStateTest.java @@ -34,6 +34,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; @@ -43,6 +44,7 @@ import org.neo4j.function.Predicates; import org.neo4j.helpers.collection.Iterators; import org.neo4j.helpers.collection.Pair; +import org.neo4j.internal.kernel.api.IndexQuery; import org.neo4j.internal.kernel.api.exceptions.schema.ConstraintValidationException; import org.neo4j.internal.kernel.api.exceptions.schema.CreateConstraintFailureException; import org.neo4j.internal.kernel.api.schema.constraints.ConstraintDescriptor; @@ -50,12 +52,14 @@ import org.neo4j.kernel.api.schema.constraints.UniquenessConstraintDescriptor; import org.neo4j.kernel.api.schema.index.TestIndexDescriptorFactory; import org.neo4j.kernel.impl.util.collection.CachingOffHeapBlockAllocator; +import org.neo4j.kernel.impl.util.ValueUtils; import org.neo4j.kernel.impl.util.collection.CollectionsFactory; import org.neo4j.kernel.impl.util.collection.CollectionsFactorySupplier; import org.neo4j.kernel.impl.util.collection.OffHeapCollectionsFactory; import org.neo4j.storageengine.api.StorageProperty; import org.neo4j.storageengine.api.schema.IndexDescriptor; import org.neo4j.storageengine.api.txstate.LongDiffSets; +import org.neo4j.storageengine.api.txstate.NodeWithPropertyValues; import org.neo4j.storageengine.api.txstate.ReadableDiffSets; import org.neo4j.storageengine.api.txstate.TxStateVisitor; import org.neo4j.test.rule.RandomRule; @@ -68,6 +72,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; +import static org.eclipse.collections.impl.factory.Sets.unionAll; import static org.eclipse.collections.impl.set.mutable.primitive.LongHashSet.newSetWith; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertEquals; @@ -82,6 +87,7 @@ import static org.neo4j.values.storable.ValueGroup.TEXT; import static org.neo4j.values.storable.Values.NO_VALUE; +@SuppressWarnings( "unchecked" ) @RunWith( Parameterized.class ) public class TxStateTest { @@ -108,36 +114,33 @@ public final TestRule repeatWithDifferentRandomization() @Parameters( name = "{0}" ) public static List data() { - return asList( - new CollectionsFactorySupplier() - { - @Override - public CollectionsFactory create() - { - return CollectionsFactorySupplier.ON_HEAP.create(); - } + return asList( new CollectionsFactorySupplier() + { + @Override + public CollectionsFactory create() + { + return CollectionsFactorySupplier.ON_HEAP.create(); + } - @Override - public String toString() - { - return "On heap"; - } - }, - new CollectionsFactorySupplier() - { - @Override - public CollectionsFactory create() - { - return new OffHeapCollectionsFactory( BLOCK_ALLOCATOR ); - } + @Override + public String toString() + { + return "On heap"; + } + }, new CollectionsFactorySupplier() + { + @Override + public CollectionsFactory create() + { + return new OffHeapCollectionsFactory( BLOCK_ALLOCATOR ); + } - @Override - public String toString() - { - return "Off heap"; - } - } - ); + @Override + public String toString() + { + return "Off heap"; + } + } ); } @AfterClass @@ -251,8 +254,7 @@ public void shouldAddAndGetByLabel() state.indexDoAdd( indexOn_2_1 ); // THEN - assertEquals( asSet( indexOn_1_1 ), - state.indexDiffSetsByLabel( indexOn_1_1.schema().keyId() ).getAdded() ); + assertEquals( asSet( indexOn_1_1 ), state.indexDiffSetsByLabel( indexOn_1_1.schema().keyId() ).getAdded() ); } @Test @@ -274,9 +276,11 @@ public void shouldComputeIndexUpdatesForScanOrSeekOnAnEmptyTxState() { // WHEN LongDiffSets diffSets = state.indexUpdatesForScan( indexOn_1_1 ); + ReadableDiffSets diffSets2 = state.indexUpdatesWithValuesForScan( indexOn_1_1 ); // THEN assertTrue( diffSets.isEmpty() ); + assertTrue( diffSets2.isEmpty() ); } @Test @@ -288,9 +292,11 @@ public void shouldComputeIndexUpdatesForScanWhenThereAreNewNodes() // WHEN LongDiffSets diffSets = state.indexUpdatesForScan( indexOn_1_1 ); + ReadableDiffSets diffSets2 = state.indexUpdatesWithValuesForScan( indexOn_1_1 ); // THEN assertEquals( newSetWith( 42L, 43L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, "value42" ), newSetWithValues( 43L, "value43" ) ), diffSets2.getAdded() ); } @Test @@ -307,6 +313,38 @@ public void shouldComputeIndexUpdatesForSeekWhenThereAreNewNodes() assertEquals( newSetWith( 43L ), diffSets.getAdded() ); } + @Test + public void shouldComputeIndexUpdatesForScanWhenThereAreNewNodesCreatedInTwoBatches() + { + // GIVEN + addNodesToIndex( indexOn_1_1 ).withDefaultStringProperties( 42L ); + addNodesToIndex( indexOn_1_2 ).withDefaultStringProperties( 44L ); + addNodesToIndex( indexOn_1_1 ).withDefaultStringProperties( 43L ); + + // WHEN + LongDiffSets diffSets = state.indexUpdatesForScan( indexOn_1_1 ); + ReadableDiffSets diffSets2 = state.indexUpdatesWithValuesForScan( indexOn_1_1 ); + + // THEN + assertEquals( newSetWith( 42L, 43L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, "value42" ), newSetWithValues( 43L, "value43" ) ), diffSets2.getAdded() ); + } + + @Test + public void shouldComputeIndexUpdatesForSeekWhenThereAreNewNodesCreatedInTwoBatches() + { + // GIVEN + addNodesToIndex( indexOn_1_1 ).withDefaultStringProperties( 42L ); + addNodesToIndex( indexOn_1_2 ).withDefaultStringProperties( 44L ); + addNodesToIndex( indexOn_1_1 ).withDefaultStringProperties( 43L ); + + // WHEN + LongDiffSets diffSets = state.indexUpdatesForSeek( indexOn_1_1, ValueTuple.of( "value42" ) ); + + // THEN + assertEquals( newSetWith( 42L ), diffSets.getAdded() ); + } + //endregion //region range seek by number index update tests @@ -319,11 +357,13 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWhenThereAreNoMa addNodesToIndex( indexOn_1_2 ).withNumberProperties( singletonList( of( 44L, 520 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 660 ), false, Values.of( 800 ), true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 660 ), false, Values.of( 800 ), true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 660 ), false, Values.of( 800 ), true ); // THEN assertEquals( 0, diffSets.getAdded().size() ); + assertEquals( 0, diffSets2.getAdded().size() ); } @Test @@ -334,11 +374,13 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWhenThereAreNewN addNodesToIndex( indexOn_1_2 ).withNumberProperties( singletonList( of( 44L, 520 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), true, Values.of( 600 ), true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), true, Values.of( 600 ), true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), true, Values.of( 600 ), true ); // THEN assertEquals( newSetWith( 43L ), diffSets.getAdded() ); + assertEquals( newSetWithValues( 43L, 550 ), diffSets2.getAdded() ); } @Test @@ -350,11 +392,12 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWhenThereAreNewN addNodesToIndex( indexOn_1_1 ).withNumberProperties( singletonList( of( 43L, 550 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), true, Values.of( 600 ), true ); - + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), true, Values.of( 600 ), true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), true, Values.of( 600 ), true ); // THEN assertEquals( newSetWith( 43L ), diffSets.getAdded() ); + assertEquals( newSetWithValues( 43L, 550 ), diffSets2.getAdded() ); } @Test @@ -362,16 +405,18 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWithIncludeLower { // GIVEN addNodesToIndex( indexOn_1_1 ).withNumberProperties( - asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), - of( 49L, 560 ) ) ); + asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), of( 49L, 560 ) ) ); addNodesToIndex( indexOn_1_2 ).withNumberProperties( singletonList( of( 46L, 520 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), true, Values.of( 550 ), true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), true, Values.of( 550 ), true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), true, Values.of( 550 ), true ); // THEN assertEquals( newSetWith( 43L, 44L, 45L, 47L, 48L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 43L, 510 ), newSetWithValues( 44L, 520 ), newSetWithValues( 45L, 530 ), newSetWithValues( 47L, 540 ), + newSetWithValues( 48L, 550 ) ), diffSets2.getAdded() ); } @Test @@ -379,16 +424,18 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWithIncludeLower { // GIVEN addNodesToIndex( indexOn_1_1 ).withNumberProperties( - asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), - of( 49L, 560 ) ) ); + asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), of( 49L, 560 ) ) ); addNodesToIndex( indexOn_1_2 ).withNumberProperties( singletonList( of( 46L, 520 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), true, Values.of( 550 ), false ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), true, Values.of( 550 ), false ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), true, Values.of( 550 ), false ); // THEN assertEquals( newSetWith( 43L, 44L, 45L, 47L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 43L, 510 ), newSetWithValues( 44L, 520 ), newSetWithValues( 45L, 530 ), newSetWithValues( 47L, 540 ) ), + diffSets2.getAdded() ); } @Test @@ -396,16 +443,18 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWithExcludeLower { // GIVEN addNodesToIndex( indexOn_1_1 ).withNumberProperties( - asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), - of( 49L, 560 ) ) ); + asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), of( 49L, 560 ) ) ); addNodesToIndex( indexOn_1_2 ).withNumberProperties( singletonList( of( 46L, 520 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), false, Values.of( 550 ), true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), false, Values.of( 550 ), true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), false, Values.of( 550 ), true ); // THEN assertEquals( newSetWith( 44L, 45L, 47L, 48L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 44L, 520 ), newSetWithValues( 45L, 530 ), newSetWithValues( 47L, 540 ), newSetWithValues( 48L, 550 ) ), + diffSets2.getAdded() ); } @Test @@ -413,16 +462,17 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWithExcludeLower { // GIVEN addNodesToIndex( indexOn_1_1 ).withNumberProperties( - asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), - of( 49L, 560 ) ) ); + asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), of( 49L, 560 ) ) ); addNodesToIndex( indexOn_1_2 ).withNumberProperties( singletonList( of( 46L, 520 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), false, Values.of( 550 ), false ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), false, Values.of( 550 ), false ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 510 ), false, Values.of( 550 ), false ); // THEN assertEquals( newSetWith( 44L, 45L, 47L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 44L, 520 ), newSetWithValues( 45L, 530 ), newSetWithValues( 47L, 540 ) ), diffSets2.getAdded() ); } @Test @@ -431,16 +481,18 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWithUnboundedLow // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withNumberProperties( - asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), - of( 49L, 560 ) ) ); + asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), of( 49L, 560 ) ) ); addNodesToIndex( indexOn_1_2 ).withNumberProperties( singletonList( of( 46L, 520 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, NO_VALUE, false, Values.of( 550 ), true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, NO_VALUE, false, Values.of( 550 ), true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, NO_VALUE, false, Values.of( 550 ), true ); // THEN assertEquals( newSetWith( 42L, 43L, 44L, 45L, 47L, 48L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, 500 ), newSetWithValues( 43L, 510 ), newSetWithValues( 44L, 520 ), newSetWithValues( 45L, 530 ), + newSetWithValues( 47L, 540 ), newSetWithValues( 48L, 550 ) ), diffSets2.getAdded() ); } @Test @@ -449,16 +501,18 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWithUnboundedLow // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withNumberProperties( - asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), - of( 49L, 560 ) ) ); + asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), of( 49L, 560 ) ) ); addNodesToIndex( indexOn_1_2 ).withNumberProperties( singletonList( of( 46L, 520 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, NO_VALUE, true, Values.of( 550 ), true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, NO_VALUE, true, Values.of( 550 ), true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, NO_VALUE, true, Values.of( 550 ), true ); // THEN assertEquals( newSetWith( 42L, 43L, 44L, 45L, 47L, 48L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, 500 ), newSetWithValues( 43L, 510 ), newSetWithValues( 44L, 520 ), newSetWithValues( 45L, 530 ), + newSetWithValues( 47L, 540 ), newSetWithValues( 48L, 550 ) ), diffSets2.getAdded() ); } @Test @@ -467,16 +521,18 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWithUnboundedLow // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withNumberProperties( - asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), - of( 49L, 560 ) ) ); + asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), of( 49L, 560 ) ) ); addNodesToIndex( indexOn_1_2 ).withNumberProperties( singletonList( of( 46L, 520 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, NO_VALUE, false, Values.of( 550 ), false ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, NO_VALUE, false, Values.of( 550 ), false ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, NO_VALUE, false, Values.of( 550 ), false ); // THEN assertEquals( newSetWith( 42L, 43L, 44L, 45L, 47L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, 500 ), newSetWithValues( 43L, 510 ), newSetWithValues( 44L, 520 ), newSetWithValues( 45L, 530 ), + newSetWithValues( 47L, 540 ) ), diffSets2.getAdded() ); } @Test @@ -485,16 +541,18 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWithUnboundedLow // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withNumberProperties( - asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), - of( 49L, 560 ) ) ); + asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), of( 49L, 560 ) ) ); addNodesToIndex( indexOn_1_2 ).withNumberProperties( singletonList( of( 46L, 520 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, NO_VALUE, true, Values.of( 550 ), false ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, NO_VALUE, true, Values.of( 550 ), false ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, NO_VALUE, true, Values.of( 550 ), false ); // THEN assertEquals( newSetWith( 42L, 43L, 44L, 45L, 47L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, 500 ), newSetWithValues( 43L, 510 ), newSetWithValues( 44L, 520 ), newSetWithValues( 45L, 530 ), + newSetWithValues( 47L, 540 ) ), diffSets2.getAdded() ); } @Test @@ -503,16 +561,17 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWithUnboundedUpp // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withNumberProperties( - asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), - of( 49L, 560 ) ) ); + asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), of( 49L, 560 ) ) ); addNodesToIndex( indexOn_1_2 ).withNumberProperties( singletonList( of( 46L, 520 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 540 ), true, NO_VALUE, true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 540 ), true, NO_VALUE, true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 540 ), true, NO_VALUE, true ); // THEN assertEquals( newSetWith( 47L, 48L, 49L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 47L, 540 ), newSetWithValues( 48L, 550 ), newSetWithValues( 49L, 560 ) ), diffSets2.getAdded() ); } @Test @@ -521,16 +580,17 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWithUnboundedUpp // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withNumberProperties( - asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), - of( 49L, 560 ) ) ); + asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), of( 49L, 560 ) ) ); addNodesToIndex( indexOn_1_2 ).withNumberProperties( singletonList( of( 46L, 520 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 540 ), true, NO_VALUE, false ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 540 ), true, NO_VALUE, false ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 540 ), true, NO_VALUE, false ); // THEN assertEquals( newSetWith( 47L, 48L, 49L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 47L, 540 ), newSetWithValues( 48L, 550 ), newSetWithValues( 49L, 560 ) ), diffSets2.getAdded() ); } @Test @@ -539,16 +599,17 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWithUnboundedUpp // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withNumberProperties( - asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), - of( 49L, 560 ) ) ); + asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), of( 49L, 560 ) ) ); addNodesToIndex( indexOn_1_2 ).withNumberProperties( singletonList( of( 46L, 520 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 540 ), false, NO_VALUE, true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 540 ), false, NO_VALUE, true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 540 ), false, NO_VALUE, true ); // THEN assertEquals( newSetWith( 48L, 49L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 48L, 550 ), newSetWithValues( 49L, 560 ) ), diffSets2.getAdded() ); } @Test @@ -557,16 +618,17 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWithUnboundedUpp // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withNumberProperties( - asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), - of( 49L, 560 ) ) ); + asList( of( 42L, 500 ), of( 43L, 510 ), of( 44L, 520 ), of( 45L, 530 ), of( 47L, 540 ), of( 48L, 550 ), of( 49L, 560 ) ) ); addNodesToIndex( indexOn_1_2 ).withNumberProperties( singletonList( of( 46L, 520 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 540 ), false, NO_VALUE, false ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 540 ), false, NO_VALUE, false ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, Values.of( 540 ), false, NO_VALUE, false ); // THEN assertEquals( newSetWith( 48L, 49L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 48L, 550 ), newSetWithValues( 49L, 560 ) ), diffSets2.getAdded() ); } @Test @@ -578,11 +640,13 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByNumberWithNoBounds() addNodesToIndex( indexOn_1_2 ).withNumberProperties( singletonList( of( 46L, 520 ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, NO_VALUE, true, NO_VALUE, true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, NO_VALUE, true, NO_VALUE, true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, ValueGroup.NUMBER, NO_VALUE, true, NO_VALUE, true ); // THEN assertEquals( newSetWith( 42L, 43L, 44L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, 500 ), newSetWithValues( 43L, 510 ), newSetWithValues( 44L, 520 ) ), diffSets2.getAdded() ); } //endregion @@ -597,11 +661,13 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWhenThereAreNoMa addNodesToIndex( indexOn_1_2 ).withStringProperties( singletonList( of( 44L, "Andreas" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Cindy" ), false, Values.of( "William" ), true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Cindy" ), false, Values.of( "William" ), true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Cindy" ), false, Values.of( "William" ), true ); // THEN assertEquals( 0, diffSets.getAdded().size() ); + assertEquals( 0, diffSets2.getAdded().size() ); } @Test @@ -612,11 +678,13 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWhenThereAreNewN addNodesToIndex( indexOn_1_2 ).withStringProperties( singletonList( of( 44L, "Andreas" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), true, Values.of( "Cathy" ), true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), true, Values.of( "Cathy" ), true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), true, Values.of( "Cathy" ), true ); // THEN assertEquals( newSetWith( 43L ), diffSets.getAdded() ); + assertEquals( newSetWithValues( 43L, "Barbara" ), diffSets2.getAdded() ); } @Test @@ -628,11 +696,13 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWhenThereAreNewN addNodesToIndex( indexOn_1_1 ).withStringProperties( singletonList( of( 43L, "Barbara" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), true, Values.of( "Cathy" ), true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), true, Values.of( "Cathy" ), true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), true, Values.of( "Cathy" ), true ); // THEN assertEquals( newSetWith( 43L ), diffSets.getAdded() ); + assertEquals( newSetWithValues( 43L, "Barbara" ), diffSets2.getAdded() ); } @Test @@ -640,16 +710,19 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWithIncludeLower { // GIVEN addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), - of( 47L, "Arthur" ), of( 48L, "Arwen" ), of( 49L, "Ashley" ) ) ); + asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), of( 47L, "Arthur" ), of( 48L, "Arwen" ), + of( 49L, "Ashley" ) ) ); addNodesToIndex( indexOn_1_2 ).withStringProperties( singletonList( of( 46L, "Andreas" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), true, Values.of( "Arwen" ), true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), true, Values.of( "Arwen" ), true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), true, Values.of( "Arwen" ), true ); // THEN assertEquals( newSetWith( 43L, 44L, 45L, 47L, 48L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 43L, "Amy" ), newSetWithValues( 44L, "Andreas" ), newSetWithValues( 45L, "Aristotle" ), + newSetWithValues( 47L, "Arthur" ), newSetWithValues( 48L, "Arwen" ) ), diffSets2.getAdded() ); } @Test @@ -657,16 +730,19 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWithIncludeLower { // GIVEN addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), - of( 47L, "Arthur" ), of( 48L, "Arwen" ), of( 49L, "Ashley" ) ) ); + asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), of( 47L, "Arthur" ), of( 48L, "Arwen" ), + of( 49L, "Ashley" ) ) ); addNodesToIndex( indexOn_1_2 ).withStringProperties( singletonList( of( 46L, "Andreas" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), true, Values.of( "Arwen" ), false ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), true, Values.of( "Arwen" ), false ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), true, Values.of( "Arwen" ), false ); // THEN assertEquals( newSetWith( 43L, 44L, 45L, 47L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 43L, "Amy" ), newSetWithValues( 44L, "Andreas" ), newSetWithValues( 45L, "Aristotle" ), + newSetWithValues( 47L, "Arthur" ) ), diffSets2.getAdded() ); } @Test @@ -674,16 +750,19 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWithExcludeLower { // GIVEN addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), - of( 47L, "Arthur" ), of( 48L, "Arwen" ), of( 49L, "Ashley" ) ) ); + asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), of( 47L, "Arthur" ), of( 48L, "Arwen" ), + of( 49L, "Ashley" ) ) ); addNodesToIndex( indexOn_1_2 ).withStringProperties( singletonList( of( 46L, "Andreas" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), false, Values.of( "Arwen" ), true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), false, Values.of( "Arwen" ), true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), false, Values.of( "Arwen" ), true ); // THEN assertEquals( newSetWith( 44L, 45L, 47L, 48L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 44L, "Andreas" ), newSetWithValues( 45L, "Aristotle" ), newSetWithValues( 47L, "Arthur" ), + newSetWithValues( 48L, "Arwen" ) ), diffSets2.getAdded() ); } @Test @@ -691,16 +770,19 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWithExcludeLower { // GIVEN addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), - of( 47L, "Arthur" ), of( 48L, "Arwen" ), of( 49L, "Ashley" ) ) ); + asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), of( 47L, "Arthur" ), of( 48L, "Arwen" ), + of( 49L, "Ashley" ) ) ); addNodesToIndex( indexOn_1_2 ).withStringProperties( singletonList( of( 46L, "Andreas" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), false, Values.of( "Arwen" ), false ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), false, Values.of( "Arwen" ), false ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Amy" ), false, Values.of( "Arwen" ), false ); // THEN assertEquals( newSetWith( 44L, 45L, 47L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 44L, "Andreas" ), newSetWithValues( 45L, "Aristotle" ), newSetWithValues( 47L, "Arthur" ) ), + diffSets2.getAdded() ); } @Test @@ -709,16 +791,19 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWithUnboundedLow // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), - of( 47L, "Arthur" ), of( 48L, "Arwen" ), of( 49L, "Ashley" ) ) ); + asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), of( 47L, "Arthur" ), of( 48L, "Arwen" ), + of( 49L, "Ashley" ) ) ); addNodesToIndex( indexOn_1_2 ).withStringProperties( singletonList( of( 46L, "Andreas" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, NO_VALUE, false, Values.of( "Arwen" ) , true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, NO_VALUE, false, Values.of( "Arwen" ), true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, NO_VALUE, false, Values.of( "Arwen" ), true ); // THEN assertEquals( newSetWith( 42L, 43L, 44L, 45L, 47L, 48L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, "Agatha" ), newSetWithValues( 43L, "Amy" ), newSetWithValues( 44L, "Andreas" ), + newSetWithValues( 45L, "Aristotle" ), newSetWithValues( 47L, "Arthur" ), newSetWithValues( 48L, "Arwen" ) ), diffSets2.getAdded() ); } @Test @@ -727,16 +812,19 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWithUnboundedLow // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), - of( 47L, "Arthur" ), of( 48L, "Arwen" ), of( 49L, "Ashley" ) ) ); + asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), of( 47L, "Arthur" ), of( 48L, "Arwen" ), + of( 49L, "Ashley" ) ) ); addNodesToIndex( indexOn_1_2 ).withStringProperties( singletonList( of( 46L, "Andreas" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, NO_VALUE, true, Values.of( "Arwen" ) , true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, NO_VALUE, true, Values.of( "Arwen" ), true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, NO_VALUE, true, Values.of( "Arwen" ), true ); // THEN assertEquals( newSetWith( 42L, 43L, 44L, 45L, 47L, 48L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, "Agatha" ), newSetWithValues( 43L, "Amy" ), newSetWithValues( 44L, "Andreas" ), + newSetWithValues( 45L, "Aristotle" ), newSetWithValues( 47L, "Arthur" ), newSetWithValues( 48L, "Arwen" ) ), diffSets2.getAdded() ); } @Test @@ -745,16 +833,19 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWithUnboundedLow // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), - of( 47L, "Arthur" ), of( 48L, "Arwen" ), of( 49L, "Ashley" ) ) ); + asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), of( 47L, "Arthur" ), of( 48L, "Arwen" ), + of( 49L, "Ashley" ) ) ); addNodesToIndex( indexOn_1_2 ).withStringProperties( singletonList( of( 46L, "Andreas" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, NO_VALUE, false, Values.of( "Arwen" ) , false ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, NO_VALUE, false, Values.of( "Arwen" ), false ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, NO_VALUE, false, Values.of( "Arwen" ), false ); // THEN assertEquals( newSetWith( 42L, 43L, 44L, 45L, 47L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, "Agatha" ), newSetWithValues( 43L, "Amy" ), newSetWithValues( 44L, "Andreas" ), + newSetWithValues( 45L, "Aristotle" ), newSetWithValues( 47L, "Arthur" ) ), diffSets2.getAdded() ); } @Test @@ -763,16 +854,19 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWithUnboundedLow // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), - of( 47L, "Arthur" ), of( 48L, "Arwen" ), of( 49L, "Ashley" ) ) ); + asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), of( 47L, "Arthur" ), of( 48L, "Arwen" ), + of( 49L, "Ashley" ) ) ); addNodesToIndex( indexOn_1_2 ).withStringProperties( singletonList( of( 46L, "Andreas" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, NO_VALUE, true, Values.of( "Arwen" ) , false ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, NO_VALUE, true, Values.of( "Arwen" ), false ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, NO_VALUE, true, Values.of( "Arwen" ), false ); // THEN assertEquals( newSetWith( 42L, 43L, 44L, 45L, 47L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, "Agatha" ), newSetWithValues( 43L, "Amy" ), newSetWithValues( 44L, "Andreas" ), + newSetWithValues( 45L, "Aristotle" ), newSetWithValues( 47L, "Arthur" ) ), diffSets2.getAdded() ); } @Test @@ -781,16 +875,19 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWithUnboundedUpp // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), - of( 47L, "Arthur" ), of( 48L, "Arwen" ), of( 49L, "Ashley" ) ) ); + asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), of( 47L, "Arthur" ), of( 48L, "Arwen" ), + of( 49L, "Ashley" ) ) ); addNodesToIndex( indexOn_1_2 ).withStringProperties( singletonList( of( 46L, "Andreas" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Arthur" ), true, NO_VALUE, true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Arthur" ), true, NO_VALUE, true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Arthur" ), true, NO_VALUE, true ); // THEN assertEquals( newSetWith( 47L, 48L, 49L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 47L, "Arthur" ), newSetWithValues( 48L, "Arwen" ), newSetWithValues( 49L, "Ashley" ) ), + diffSets2.getAdded() ); } @Test @@ -799,16 +896,19 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWithUnboundedUpp // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), - of( 47L, "Arthur" ), of( 48L, "Arwen" ), of( 49L, "Ashley" ) ) ); + asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), of( 47L, "Arthur" ), of( 48L, "Arwen" ), + of( 49L, "Ashley" ) ) ); addNodesToIndex( indexOn_1_2 ).withStringProperties( singletonList( of( 46L, "Andreas" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Arthur" ), true, NO_VALUE, false ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Arthur" ), true, NO_VALUE, false ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Arthur" ), true, NO_VALUE, false ); // THEN assertEquals( newSetWith( 47L, 48L, 49L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 47L, "Arthur" ), newSetWithValues( 48L, "Arwen" ), newSetWithValues( 49L, "Ashley" ) ), + diffSets2.getAdded() ); } @Test @@ -817,16 +917,18 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWithUnboundedUpp // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), - of( 47L, "Arthur" ), of( 48L, "Arwen" ), of( 49L, "Ashley" ) ) ); + asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), of( 47L, "Arthur" ), of( 48L, "Arwen" ), + of( 49L, "Ashley" ) ) ); addNodesToIndex( indexOn_1_2 ).withStringProperties( singletonList( of( 46L, "Andreas" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Arthur" ), false, NO_VALUE, true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Arthur" ), false, NO_VALUE, true ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Arthur" ), false, NO_VALUE, true ); // THEN assertEquals( newSetWith( 48L, 49L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 48L, "Arwen" ), newSetWithValues( 49L, "Ashley" ) ), diffSets2.getAdded() ); } @Test @@ -835,16 +937,18 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWithUnboundedUpp // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), - of( 47L, "Arthur" ), of( 48L, "Arwen" ), of( 49L, "Ashley" ) ) ); + asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ), of( 45L, "Aristotle" ), of( 47L, "Arthur" ), of( 48L, "Arwen" ), + of( 49L, "Ashley" ) ) ); addNodesToIndex( indexOn_1_2 ).withStringProperties( singletonList( of( 46L, "Andreas" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Arthur" ), false, NO_VALUE, false ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Arthur" ), false, NO_VALUE, false ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, Values.of( "Arthur" ), false, NO_VALUE, false ); // THEN assertEquals( newSetWith( 48L, 49L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 48L, "Arwen" ), newSetWithValues( 49L, "Ashley" ) ), diffSets2.getAdded() ); } @Test @@ -852,16 +956,153 @@ public void shouldComputeIndexUpdatesForBetweenRangeSeekByStringWithNoBounds() { // GIVEN addNodesToIndex( indexOn_1_1 ).withBooleanProperties( asList( of( 39L, true ), of( 38L, false ) ) ); - addNodesToIndex( indexOn_1_1 ) - .withStringProperties( asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ) ) ); + addNodesToIndex( indexOn_1_1 ).withStringProperties( asList( of( 42L, "Agatha" ), of( 43L, "Amy" ), of( 44L, "Andreas" ) ) ); addNodesToIndex( indexOn_1_2 ).withStringProperties( singletonList( of( 46L, "Andreas" ) ) ); // WHEN - LongDiffSets diffSets = - state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, NO_VALUE, true, NO_VALUE, true ); + LongDiffSets diffSets = state.indexUpdatesForRangeSeek( indexOn_1_1, TEXT, NO_VALUE, true, NO_VALUE, true ); + ReadableDiffSets diffSets2 = state.indexUpdatesWithValuesForRangeSeek( indexOn_1_1, TEXT, NO_VALUE, true, NO_VALUE, true ); // THEN assertEquals( newSetWith( 42L, 43L, 44L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, "Agatha" ), newSetWithValues( 43L, "Amy" ), newSetWithValues( 44L, "Andreas" ) ), diffSets2.getAdded() ); + } + + //endregion + + //region range seek by suffix or contains index update tests + + @Test + public void shouldComputeIndexUpdatesForRangeSeekByContainsWhenThereAreNoMatchingNodes() + { + // GIVEN + addNodesToIndex( indexOn_1_1 ).withDefaultStringProperties( 42L, 43L ); + addNodesToIndex( indexOn_1_2 ).withDefaultStringProperties( 44L ); + + // WHEN + LongDiffSets diffSets = + state.indexUpdatesForSuffixOrContains( indexOn_1_1, IndexQuery.stringContains( indexOn_1_1.schema().getPropertyId(), "eulav" ) ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForSuffixOrContains( indexOn_1_1, IndexQuery.stringContains( indexOn_1_1.schema().getPropertyId(), "eulav" ) ); + + // THEN + assertEquals( 0, diffSets.getAdded().size() ); + assertEquals( 0, diffSets2.getAdded().size() ); + } + + @Test + public void shouldComputeIndexUpdatesForRangeSeekByContainsWhenThereAreNewNodesCreatedInOneBatch() + { + // GIVEN + addNodesToIndex( indexOn_1_1 ).withDefaultStringProperties( 42L, 43L ); + addNodesToIndex( indexOn_1_2 ).withDefaultStringProperties( 44L ); + + // WHEN + LongDiffSets diffSets = + state.indexUpdatesForSuffixOrContains( indexOn_1_1, IndexQuery.stringContains( indexOn_1_1.schema().getPropertyId(), "value" ) ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForSuffixOrContains( indexOn_1_1, IndexQuery.stringContains( indexOn_1_1.schema().getPropertyId(), "value" ) ); + + // THEN + assertEquals( newSetWith( 42L, 43L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, "value42" ), newSetWithValues( 43L, "value43" ) ), diffSets2.getAdded() ); + } + + @Test + public void shouldComputeIndexUpdatesForRangeSeekBySuffixWhenThereArePartiallyMatchingNewNodes() + { + // GIVEN + addNodesToIndex( indexOn_1_1 ).withStringProperties( + asList( of( 40L, "Aaron" ), of( 41L, "Agatha" ), of( 42L, "Andreas" ), of( 43L, "Andrea" ), of( 44L, "Aristotle" ), of( 45L, "Barbara" ), + of( 46L, "Barbarella" ), of( 47L, "Cinderella" ) ) ); + addNodesToIndex( indexOn_1_2 ).withDefaultStringProperties( 44L ); + + // WHEN + LongDiffSets diffSets = state.indexUpdatesForSuffixOrContains( indexOn_1_1, IndexQuery.stringSuffix( indexOn_1_1.schema().getPropertyId(), "ella" ) ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForSuffixOrContains( indexOn_1_1, IndexQuery.stringSuffix( indexOn_1_1.schema().getPropertyId(), "ella" ) ); + + // THEN + assertEquals( newSetWith( 46L, 47L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 46L, "Barbarella" ), newSetWithValues( 47L, "Cinderella" ) ), diffSets2.getAdded() ); + } + + @Test + public void shouldComputeIndexUpdatesForRangeSeekByContainsWhenThereArePartiallyMatchingNewNodes() + { + // GIVEN + addNodesToIndex( indexOn_1_1 ).withStringProperties( + asList( of( 40L, "Aaron" ), of( 41L, "Agatha" ), of( 42L, "Andreas" ), of( 43L, "Andrea" ), of( 44L, "Aristotle" ), of( 45L, "Barbara" ), + of( 46L, "Barbarella" ), of( 47L, "Cinderella" ) ) ); + addNodesToIndex( indexOn_1_2 ).withDefaultStringProperties( 44L ); + + // WHEN + LongDiffSets diffSets = + state.indexUpdatesForSuffixOrContains( indexOn_1_1, IndexQuery.stringContains( indexOn_1_1.schema().getPropertyId(), "arbar" ) ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForSuffixOrContains( indexOn_1_1, IndexQuery.stringContains( indexOn_1_1.schema().getPropertyId(), "arbar" ) ); + + // THEN + assertEquals( newSetWith( 45L, 46L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 45L, "Barbara" ), newSetWithValues( 46L, "Barbarella" ) ), diffSets2.getAdded() ); + } + + @Test + public void shouldComputeIndexUpdatesForRangeSeekBySuffixWhenThereArePartiallyMatchingLeadingNewNodes() + { + // GIVEN + addNodesToIndex( indexOn_1_1 ).withStringProperties( + asList( of( 40L, "Aaron" ), of( 41L, "Agatha" ), of( 42L, "Andreas" ), of( 43L, "Andrea" ), of( 44L, "Aristotle" ), of( 45L, "Barbara" ), + of( 46L, "Barbarella" ), of( 47L, "Cinderella" ) ) ); + addNodesToIndex( indexOn_1_2 ).withDefaultStringProperties( 44L ); + + // WHEN + LongDiffSets diffSets = state.indexUpdatesForSuffixOrContains( indexOn_1_1, IndexQuery.stringSuffix( indexOn_1_1.schema().getPropertyId(), "ron" ) ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForSuffixOrContains( indexOn_1_1, IndexQuery.stringSuffix( indexOn_1_1.schema().getPropertyId(), "ron" ) ); + + // THEN + assertEquals( newSetWith( 40L ), diffSets.getAdded() ); + assertEquals( newSetWithValues( 40L, "Aaron" ), diffSets2.getAdded() ); + } + + @Test + public void shouldComputeIndexUpdatesForRangeSeekByContainsWhenThereArePartiallyMatchingTrailingNewNodes() + { + // GIVEN + addNodesToIndex( indexOn_1_1 ).withStringProperties( + asList( of( 40L, "Aaron" ), of( 41L, "Agatha" ), of( 42L, "Andreas" ), of( 43L, "Andrea" ), of( 44L, "Aristotle" ), of( 45L, "Barbara" ), + of( 46L, "Barbarella" ), of( 47L, "Cinderella" ) ) ); + addNodesToIndex( indexOn_1_2 ).withDefaultStringProperties( 44L ); + + // WHEN + LongDiffSets diffSets = + state.indexUpdatesForSuffixOrContains( indexOn_1_1, IndexQuery.stringContains( indexOn_1_1.schema().getPropertyId(), "inder" ) ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForSuffixOrContains( indexOn_1_1, IndexQuery.stringContains( indexOn_1_1.schema().getPropertyId(), "inder" ) ); + + // THEN + assertEquals( newSetWith( 47L ), diffSets.getAdded() ); + assertEquals( newSetWithValues( 47L, "Cinderella" ), diffSets2.getAdded() ); + } + + @Test + public void shouldComputeIndexUpdatesForRangeSeekByContainsWhenThereAreNewNodesCreatedInTwoBatches() + { + // GIVEN + addNodesToIndex( indexOn_1_1 ).withDefaultStringProperties( 42L ); + addNodesToIndex( indexOn_1_2 ).withDefaultStringProperties( 44L ); + addNodesToIndex( indexOn_1_1 ).withDefaultStringProperties( 43L ); + + // WHEN + LongDiffSets diffSets = + state.indexUpdatesForSuffixOrContains( indexOn_1_1, IndexQuery.stringContains( indexOn_1_1.schema().getPropertyId(), "value4" ) ); + ReadableDiffSets diffSets2 = + state.indexUpdatesWithValuesForSuffixOrContains( indexOn_1_1, IndexQuery.stringContains( indexOn_1_1.schema().getPropertyId(), "value4" ) ); + + // THEN + assertEquals( newSetWith( 42L, 43L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, "value42" ), newSetWithValues( 43L, "value43" ) ), diffSets2.getAdded() ); } //endregion @@ -877,9 +1118,11 @@ public void shouldComputeIndexUpdatesForRangeSeekByPrefixWhenThereAreNoMatchingN // WHEN LongDiffSets diffSets = state.indexUpdatesForRangeSeekByPrefix( indexOn_1_1, "eulav" ); + ReadableDiffSets diffSets2 = state.indexUpdatesWithValuesForRangeSeekByPrefix( indexOn_1_1, "eulav" ); // THEN assertEquals( 0, diffSets.getAdded().size() ); + assertEquals( 0, diffSets2.getAdded().size() ); } @Test @@ -891,9 +1134,11 @@ public void shouldComputeIndexUpdatesForRangeSeekByPrefixWhenThereAreNewNodesCre // WHEN LongDiffSets diffSets = state.indexUpdatesForRangeSeekByPrefix( indexOn_1_1, "value" ); + ReadableDiffSets diffSets2 = state.indexUpdatesWithValuesForRangeSeekByPrefix( indexOn_1_1, "value" ); // THEN assertEquals( newSetWith( 42L, 43L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, "value42" ), newSetWithValues( 43L, "value43" ) ), diffSets2.getAdded() ); } @Test @@ -901,16 +1146,17 @@ public void shouldComputeIndexUpdatesForRangeSeekByPrefixWhenThereArePartiallyMa { // GIVEN addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 40L, "Aaron" ), of( 41L, "Agatha" ), of( 42L, "Andreas" ), of( 43L, "Andrea" ), - of( 44L, "Aristotle" ), of( 45L, "Barbara" ), of( 46L, "Barbarella" ), - of( 47L, "Cinderella" ) ) ); + asList( of( 40L, "Aaron" ), of( 41L, "Agatha" ), of( 42L, "Andreas" ), of( 43L, "Andrea" ), of( 44L, "Aristotle" ), of( 45L, "Barbara" ), + of( 46L, "Barbarella" ), of( 47L, "Cinderella" ) ) ); addNodesToIndex( indexOn_1_2 ).withDefaultStringProperties( 44L ); // WHEN LongDiffSets diffSets = state.indexUpdatesForRangeSeekByPrefix( indexOn_1_1, "And" ); + ReadableDiffSets diffSets2 = state.indexUpdatesWithValuesForRangeSeekByPrefix( indexOn_1_1, "And" ); // THEN assertEquals( newSetWith( 42L, 43L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, "Andreas" ), newSetWithValues( 43L, "Andrea" ) ), diffSets2.getAdded() ); } @Test @@ -918,16 +1164,17 @@ public void shouldComputeIndexUpdatesForRangeSeekByPrefixWhenThereArePartiallyMa { // GIVEN addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 40L, "Aaron" ), of( 41L, "Agatha" ), of( 42L, "Andreas" ), of( 43L, "Andrea" ), - of( 44L, "Aristotle" ), of( 45L, "Barbara" ), of( 46L, "Barbarella" ), - of( 47L, "Cinderella" ) ) ); + asList( of( 40L, "Aaron" ), of( 41L, "Agatha" ), of( 42L, "Andreas" ), of( 43L, "Andrea" ), of( 44L, "Aristotle" ), of( 45L, "Barbara" ), + of( 46L, "Barbarella" ), of( 47L, "Cinderella" ) ) ); addNodesToIndex( indexOn_1_2 ).withDefaultStringProperties( 44L ); // WHEN LongDiffSets diffSets = state.indexUpdatesForRangeSeekByPrefix( indexOn_1_1, "Bar" ); + ReadableDiffSets diffSets2 = state.indexUpdatesWithValuesForRangeSeekByPrefix( indexOn_1_1, "Bar" ); // THEN assertEquals( newSetWith( 45L, 46L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 45L, "Barbara" ), newSetWithValues( 46L, "Barbarella" ) ), diffSets2.getAdded() ); } @Test @@ -935,16 +1182,17 @@ public void shouldComputeIndexUpdatesForRangeSeekByPrefixWhenThereArePartiallyMa { // GIVEN addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 40L, "Aaron" ), of( 41L, "Agatha" ), of( 42L, "Andreas" ), of( 43L, "Andrea" ), - of( 44L, "Aristotle" ), of( 45L, "Barbara" ), of( 46L, "Barbarella" ), - of( 47L, "Cinderella" ) ) ); + asList( of( 40L, "Aaron" ), of( 41L, "Agatha" ), of( 42L, "Andreas" ), of( 43L, "Andrea" ), of( 44L, "Aristotle" ), of( 45L, "Barbara" ), + of( 46L, "Barbarella" ), of( 47L, "Cinderella" ) ) ); addNodesToIndex( indexOn_1_2 ).withDefaultStringProperties( 44L ); // WHEN LongDiffSets diffSets = state.indexUpdatesForRangeSeekByPrefix( indexOn_1_1, "Aa" ); + ReadableDiffSets diffSets2 = state.indexUpdatesWithValuesForRangeSeekByPrefix( indexOn_1_1, "Aa" ); // THEN assertEquals( newSetWith( 40L ), diffSets.getAdded() ); + assertEquals( newSetWithValues( 40L, "Aaron" ), diffSets2.getAdded() ); } @Test @@ -952,16 +1200,17 @@ public void shouldComputeIndexUpdatesForRangeSeekByPrefixWhenThereArePartiallyMa { // GIVEN addNodesToIndex( indexOn_1_1 ).withStringProperties( - asList( of( 40L, "Aaron" ), of( 41L, "Agatha" ), of( 42L, "Andreas" ), of( 43L, "Andrea" ), - of( 44L, "Aristotle" ), of( 45L, "Barbara" ), of( 46L, "Barbarella" ), - of( 47L, "Cinderella" ) ) ); + asList( of( 40L, "Aaron" ), of( 41L, "Agatha" ), of( 42L, "Andreas" ), of( 43L, "Andrea" ), of( 44L, "Aristotle" ), of( 45L, "Barbara" ), + of( 46L, "Barbarella" ), of( 47L, "Cinderella" ) ) ); addNodesToIndex( indexOn_1_2 ).withDefaultStringProperties( 44L ); // WHEN LongDiffSets diffSets = state.indexUpdatesForRangeSeekByPrefix( indexOn_1_1, "Ci" ); + ReadableDiffSets diffSets2 = state.indexUpdatesWithValuesForRangeSeekByPrefix( indexOn_1_1, "Ci" ); // THEN assertEquals( newSetWith( 47L ), diffSets.getAdded() ); + assertEquals( newSetWithValues( 47L, "Cinderella" ), diffSets2.getAdded() ); } @Test @@ -974,9 +1223,11 @@ public void shouldComputeIndexUpdatesForRangeSeekByPrefixWhenThereAreNewNodesCre // WHEN LongDiffSets diffSets = state.indexUpdatesForRangeSeekByPrefix( indexOn_1_1, "value" ); + ReadableDiffSets diffSets2 = state.indexUpdatesWithValuesForRangeSeekByPrefix( indexOn_1_1, "value" ); // THEN assertEquals( newSetWith( 42L, 43L ), diffSets.getAdded() ); + assertEquals( unionAll( newSetWithValues( 42L, "value42" ), newSetWithValues( 43L, "value43" ) ), diffSets2.getAdded() ); } //endregion @@ -1098,13 +1349,10 @@ public void shouldDifferentiateRelationshipPropertyExistenceConstraints() // Then assertEquals( asSet( constraint1, constraint2 ), state.constraintsChangesForRelationshipType( 1 ).getAdded() ); - assertEquals( singleton( constraint1 ), - state.constraintsChangesForSchema( constraint1.schema() ).getAdded() ); - assertEquals( singleton( constraint2 ), - state.constraintsChangesForSchema( constraint2.schema() ).getAdded() ); + assertEquals( singleton( constraint1 ), state.constraintsChangesForSchema( constraint1.schema() ).getAdded() ); + assertEquals( singleton( constraint2 ), state.constraintsChangesForSchema( constraint2.schema() ).getAdded() ); assertEquals( singleton( constraint3 ), state.constraintsChangesForRelationshipType( 3 ).getAdded() ); - assertEquals( singleton( constraint3 ), - state.constraintsChangesForSchema( constraint3.schema() ).getAdded() ); + assertEquals( singleton( constraint3 ), state.constraintsChangesForSchema( constraint3.schema() ).getAdded() ); } @Test @@ -1478,6 +1726,16 @@ public void visitDeletedNode( long id ) //endregion + static Set newSetWithValues( long nodeId, Object... values ) + { + return singleton( new NodeWithPropertyValues( nodeId, Arrays.stream( values ).map( ValueUtils::of ).toArray( Value[]::new ) ) ); + } + + static Set newSetWithValues( long nodeId, Value... values ) + { + return singleton( new NodeWithPropertyValues( nodeId, values ) ); + } + abstract class VisitationOrder extends TxStateVisitor.Adapter { private final Set visitMethods = new HashSet<>(); @@ -1598,8 +1856,7 @@ private void withProperties( Collection> nodesWithValues ) state.nodeDoAddLabel( labelId, nodeId ); Value valueAfter = Values.of( entry.other() ); state.nodeDoAddProperty( nodeId, propertyKeyId, valueAfter ); - state.indexDoUpdateEntry( descriptor.schema(), nodeId, null, - ValueTuple.of( valueAfter ) ); + state.indexDoUpdateEntry( descriptor.schema(), nodeId, null, ValueTuple.of( valueAfter ) ); } } }; diff --git a/community/values/src/main/java/org/neo4j/values/storable/ValueTuple.java b/community/values/src/main/java/org/neo4j/values/storable/ValueTuple.java index 4c423f15588d..b4466b4282c7 100644 --- a/community/values/src/main/java/org/neo4j/values/storable/ValueTuple.java +++ b/community/values/src/main/java/org/neo4j/values/storable/ValueTuple.java @@ -62,6 +62,14 @@ public Value valueAt( int offset ) return values[offset]; } + /** + * WARNING: this method does not create a defensive copy. Do not modify the returned array. + */ + public Value[] getValues() + { + return values; + } + @Override public boolean equals( Object o ) { diff --git a/enterprise/cypher/morsel-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/vectorized/operators/NodeIndexSeekOperator.scala b/enterprise/cypher/morsel-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/vectorized/operators/NodeIndexSeekOperator.scala index d1ec60e5322c..b1eca90bbe1d 100644 --- a/enterprise/cypher/morsel-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/vectorized/operators/NodeIndexSeekOperator.scala +++ b/enterprise/cypher/morsel-runtime/src/main/scala/org/neo4j/cypher/internal/runtime/vectorized/operators/NodeIndexSeekOperator.scala @@ -29,6 +29,7 @@ import org.neo4j.cypher.internal.runtime.interpreted.pipes.{IndexSeek, IndexSeek import org.neo4j.cypher.internal.runtime.vectorized._ import org.neo4j.cypher.internal.v3_5.logical.plans.QueryExpression import org.neo4j.internal.kernel.api._ +import org.neo4j.values.storable.Value import org.neo4j.values.virtual.NodeValue import org.opencypher.v9_0.expressions.{LabelToken, PropertyKeyToken} @@ -45,7 +46,8 @@ class NodeIndexSeekOperator(offset: Int, val read = context.transactionalContext.dataRead val queryState = new OldQueryState(context, resources = null, params = state.params) val indexReference = reference(context) - val nodeIterator = indexSeek(queryState, indexReference, currentRow) + // TODO + val nodeIterator = indexSeek(queryState, indexReference, Seq.empty, currentRow) new OTask(nodeIterator) } @@ -60,7 +62,7 @@ class NodeIndexSeekOperator(offset: Int, reference } - class OTask(nodeIterator: Iterator[NodeValue]) extends ContinuableOperatorTask { + class OTask(nodeIterator: Iterator[(NodeValue, Seq[Value])]) extends ContinuableOperatorTask { override def operate(currentRow: MorselExecutionContext, context: QueryContext, state: QueryState): Unit = { @@ -68,7 +70,8 @@ class NodeIndexSeekOperator(offset: Int, var processedRows = 0 while (currentRow.hasMoreRows && nodeIterator.hasNext) { // iterationState.copyArgumentStateTo(currentRow, argumentSize.nLongs, argumentSize.nReferences) - currentRow.setLongAt(offset, nodeIterator.next().id()) + currentRow.setLongAt(offset, nodeIterator.next()._1.id()) + // TODO currentRow.moveToNextRow() }