diff --git a/community/graphdb-api/src/main/java/org/neo4j/graphdb/GraphDatabaseService.java b/community/graphdb-api/src/main/java/org/neo4j/graphdb/GraphDatabaseService.java index e0828f9e59e66..8d72c0b166d55 100644 --- a/community/graphdb-api/src/main/java/org/neo4j/graphdb/GraphDatabaseService.java +++ b/community/graphdb-api/src/main/java/org/neo4j/graphdb/GraphDatabaseService.java @@ -134,6 +134,92 @@ public interface GraphDatabaseService */ ResourceIterator findNodes( Label label, String key, Object value ); + /** + * Returns all nodes having the label, and the wanted property values. + * If an online index is found, it will be used to look up the requested + * nodes. The specified properties do not have to be provided in the same order + * as they were defined in the index. + *

+ * If no indexes exist for the label with all provided properties, the database will + * scan all labeled nodes looking for matching nodes. + *

+ * Note that equality for values do not follow the rules of Java. This means that the number 42 is equals to all + * other 42 numbers, indifferently of if they are encoded as Integer, Long, Float, Short, Byte or Double. + *

+ * Same rules follow Character and String - the Character 'A' is equal to the String 'A'. + *

+ * Finally - arrays also follow these rules. An int[] {1,2,3} is equal to a double[] {1.0, 2.0, 3.0} + *

+ * Please ensure that the returned {@link ResourceIterator} is closed correctly and as soon as possible + * inside your transaction to avoid potential blocking of write operations. + * + * @param label consider nodes with this label + * @param key1 required property key1 + * @param value1 required property value of key1 + * @param key2 required property key2 + * @param value2 required property value of key2 + * @return an iterator containing all matching nodes. See {@link ResourceIterator} for responsibilities. + */ + ResourceIterator findNodes( Label label, String key1, Object value1, String key2, Object value2 ); + + /** + * Returns all nodes having the label, and the wanted property values. + * If an online index is found, it will be used to look up the requested + * nodes. The specified properties do not have to be provided in the same order + * as they were defined in the index. + *

+ * If no indexes exist for the label with all provided properties, the database will + * scan all labeled nodes looking for matching nodes. + *

+ * Note that equality for values do not follow the rules of Java. This means that the number 42 is equals to all + * other 42 numbers, indifferently of if they are encoded as Integer, Long, Float, Short, Byte or Double. + *

+ * Same rules follow Character and String - the Character 'A' is equal to the String 'A'. + *

+ * Finally - arrays also follow these rules. An int[] {1,2,3} is equal to a double[] {1.0, 2.0, 3.0} + *

+ * Please ensure that the returned {@link ResourceIterator} is closed correctly and as soon as possible + * inside your transaction to avoid potential blocking of write operations. + * + * @param label consider nodes with this label + * @param key1 required property key1 + * @param value1 required property value of key1 + * @param key2 required property key2 + * @param value2 required property value of key2 + * @param key3 required property key3 + * @param value3 required property value of key3 + * @return an iterator containing all matching nodes. See {@link ResourceIterator} for responsibilities. + */ + ResourceIterator findNodes( Label label, + String key1, Object value1, + String key2, Object value2, + String key3, Object value3 ); + + /** + * Returns all nodes having the label, and the wanted property values. + * If an online index is found, it will be used to look up the requested + * nodes. The specified properties do not have to be provided in the same order + * as they were defined in the index. + *

+ * If no indexes exist for the label with all provided properties, the database will + * scan all labeled nodes looking for matching nodes. + *

+ * Note that equality for values do not follow the rules of Java. This means that the number 42 is equals to all + * other 42 numbers, indifferently of if they are encoded as Integer, Long, Float, Short, Byte or Double. + *

+ * Same rules follow Character and String - the Character 'A' is equal to the String 'A'. + *

+ * Finally - arrays also follow these rules. An int[] {1,2,3} is equal to a double[] {1.0, 2.0, 3.0} + *

+ * Please ensure that the returned {@link ResourceIterator} is closed correctly and as soon as possible + * inside your transaction to avoid potential blocking of write operations. + * + * @param label consider nodes with this label + * @param propertyValues required property key-value combinations + * @return an iterator containing all matching nodes. See {@link ResourceIterator} for responsibilities. + */ + ResourceIterator findNodes( Label label, Map propertyValues ); + /** * Returns all nodes having a given label, and a property value of type String or Character matching the * given value template and search mode. diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/factory/GraphDatabaseFacade.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/factory/GraphDatabaseFacade.java index 5793aa57fb4bf..83fbdf710807d 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/factory/GraphDatabaseFacade.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/factory/GraphDatabaseFacade.java @@ -21,7 +21,9 @@ import java.io.File; import java.net.URL; +import java.util.Arrays; import java.util.Collections; +import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.TimeUnit; @@ -585,6 +587,45 @@ public ResourceIterator findNodes( final Label myLabel, final String key, return nodesByLabelAndProperty( transaction, labelId, IndexQuery.exact( propertyId, Values.of( value ) ) ); } + @Override + public ResourceIterator findNodes( Label label, String key1, Object value1, String key2, Object value2 ) + { + KernelTransaction transaction = statementContext.getKernelTransactionBoundToThisThread( true ); + TokenRead tokenRead = transaction.tokenRead(); + int labelId = tokenRead.nodeLabel( label.name() ); + return nodesByLabelAndProperties( transaction, labelId, + IndexQuery.exact( tokenRead.propertyKey( key1 ), Values.of( value1 ) ), + IndexQuery.exact( tokenRead.propertyKey( key2 ), Values.of( value2 ) ) ); + } + + @Override + public ResourceIterator findNodes( Label label, String key1, Object value1, String key2, Object value2, + String key3, Object value3 ) + { + KernelTransaction transaction = statementContext.getKernelTransactionBoundToThisThread( true ); + TokenRead tokenRead = transaction.tokenRead(); + int labelId = tokenRead.nodeLabel( label.name() ); + return nodesByLabelAndProperties( transaction, labelId, + IndexQuery.exact( tokenRead.propertyKey( key1 ), Values.of( value1 ) ), + IndexQuery.exact( tokenRead.propertyKey( key2 ), Values.of( value2 ) ), + IndexQuery.exact( tokenRead.propertyKey( key3 ), Values.of( value3 ) ) ); + } + + @Override + public ResourceIterator findNodes( Label label, Map propertyValues ) + { + KernelTransaction transaction = statementContext.getKernelTransactionBoundToThisThread( true ); + TokenRead tokenRead = transaction.tokenRead(); + int labelId = tokenRead.nodeLabel( label.name() ); + IndexQuery[] queries = new IndexQuery[propertyValues.size()]; + int i = 0; + for ( Map.Entry entry : propertyValues.entrySet() ) + { + queries[i++] = IndexQuery.exact( tokenRead.propertyKey( entry.getKey() ), Values.of( entry.getValue() ) ); + } + return nodesByLabelAndProperties( transaction, labelId, queries ); + } + @Override public ResourceIterator findNodes( final Label myLabel, final String key, final String value, final StringSearchMode searchMode ) @@ -678,11 +719,103 @@ private ResourceIterator nodesByLabelAndProperty( KernelTransaction transa } } - return getNodesByLabelAndPropertyWithoutIndex( query, statement, labelId ); + return getNodesByLabelAndPropertyWithoutIndex( statement, labelId, query ); + } + + private ResourceIterator nodesByLabelAndProperties( KernelTransaction transaction, int labelId, IndexQuery... queries ) + { + Statement statement = transaction.acquireStatement(); + Read read = transaction.dataRead(); + + if ( isInvalidQuery( labelId, queries ) ) + { + statement.close(); + return emptyResourceIterator(); + } + + int[] propertyIds = getSortedPropertyIds( queries ); + int[] workingCopy = new int[propertyIds.length]; + + Iterator indexes = transaction.schemaRead().indexesGetForLabel( labelId ); + while ( indexes.hasNext() ) + { + CapableIndexReference index = indexes.next(); + int[] original = index.properties(); + if ( hasSamePropertyIds( original, workingCopy, propertyIds ) ) + { + // Ha! We found an index - let's use it to find matching nodes + try + { + NodeValueIndexCursor cursor = transaction.cursors().allocateNodeValueIndexCursor(); + read.nodeIndexSeek( index, cursor, IndexOrder.NONE, getReorderedIndexQueries( original, queries ) ); + + return new NodeCursorResourceIterator<>( cursor, statement, this::newNodeProxy ); + } + catch ( KernelException e ) + { + // weird at this point but ignore and fallback to a label scan + break; + } + } + } + + return getNodesByLabelAndPropertyWithoutIndex( statement, labelId, queries ); + } + + private IndexQuery[] getReorderedIndexQueries( int[] indexPropertyIds, IndexQuery[] queries ) + { + IndexQuery[] orderedQueries = new IndexQuery[queries.length]; + for ( int i = 0; i < indexPropertyIds.length; i++ ) + { + int propertyKeyId = indexPropertyIds[i]; + for ( IndexQuery query : queries ) + { + if ( query.propertyKeyId() == propertyKeyId ) + { + orderedQueries[i] = query; + break; + } + } + } + return orderedQueries; + } + + private boolean hasSamePropertyIds( int[] original, int[] workingCopy, int[] propertyIds ) + { + if ( original.length == propertyIds.length ) + { + System.arraycopy( original, 0, workingCopy, 0, original.length ); + Arrays.sort( workingCopy ); + return Arrays.equals( propertyIds, workingCopy ); + } + return false; + } + + private int[] getSortedPropertyIds( IndexQuery[] queries ) + { + int[] propertyIds = new int[queries.length]; + for ( int i = 0; i < queries.length; i++ ) + { + propertyIds[i] = queries[i].propertyKeyId(); + } + Arrays.sort( propertyIds ); + return propertyIds; + } + + private boolean isInvalidQuery( int labelId, IndexQuery[] queries ) + { + boolean invalidQuery = labelId == NO_SUCH_LABEL; + for ( IndexQuery query : queries ) + { + int propertyKeyId = query.propertyKeyId(); + invalidQuery = invalidQuery || propertyKeyId == NO_SUCH_PROPERTY_KEY; + + } + return invalidQuery; } private ResourceIterator getNodesByLabelAndPropertyWithoutIndex( - IndexQuery query, Statement statement, int labelId ) + Statement statement, int labelId, IndexQuery... queries ) { KernelTransaction transaction = statementContext.getKernelTransactionBoundToThisThread( true ); @@ -698,7 +831,7 @@ private ResourceIterator getNodesByLabelAndPropertyWithoutIndex( propertyCursor, statement, this::newNodeProxy, - query ); + queries ); } private ResourceIterator allNodesWithLabel( final Label myLabel ) @@ -840,7 +973,7 @@ private static class NodeLabelPropertyIterator extends PrefetchingNodeResourceIt private final NodeLabelIndexCursor nodeLabelCursor; private final NodeCursor nodeCursor; private final PropertyCursor propertyCursor; - private final IndexQuery query; + private final IndexQuery[] queries; NodeLabelPropertyIterator( Read read, @@ -849,14 +982,14 @@ private static class NodeLabelPropertyIterator extends PrefetchingNodeResourceIt PropertyCursor propertyCursor, Statement statement, NodeFactory nodeFactory, - IndexQuery query ) + IndexQuery... queries ) { super( statement, nodeFactory ); this.read = read; this.nodeLabelCursor = nodeLabelCursor; this.nodeCursor = nodeCursor; this.propertyCursor = propertyCursor; - this.query = query; + this.queries = queries; } @Override @@ -867,7 +1000,7 @@ protected long fetchNext() { hasNext = nodeLabelCursor.next(); - } while ( hasNext && !hasPropertyWithValue() ); + } while ( hasNext && !hasPropertiesWithValues() ); if ( hasNext ) { @@ -886,18 +1019,26 @@ void closeResources( Statement statement ) IOUtils.closeAllSilently( statement, nodeLabelCursor, nodeCursor, propertyCursor ); } - private boolean hasPropertyWithValue() + private boolean hasPropertiesWithValues() { + int targetCount = queries.length; read.singleNode( nodeLabelCursor.nodeReference(), nodeCursor ); if ( nodeCursor.next() ) { nodeCursor.properties( propertyCursor ); while ( propertyCursor.next() ) { - if ( propertyCursor.propertyKey() == query.propertyKeyId() && - query.acceptsValueAt( propertyCursor ) ) + for ( IndexQuery query : queries ) { - return true; + if ( propertyCursor.propertyKey() == query.propertyKeyId() && + query.acceptsValueAt( propertyCursor ) ) + { + targetCount--; + if ( targetCount == 0 ) + { + return true; + } + } } } } diff --git a/community/kernel/src/test/java/org/neo4j/graphdb/IndexingCompositeQueryAcceptanceTest.java b/community/kernel/src/test/java/org/neo4j/graphdb/IndexingCompositeQueryAcceptanceTest.java new file mode 100644 index 0000000000000..916df4d231890 --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/graphdb/IndexingCompositeQueryAcceptanceTest.java @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.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.graphdb; + +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.neo4j.collection.primitive.Primitive; +import org.neo4j.collection.primitive.PrimitiveLongIterator; +import org.neo4j.collection.primitive.PrimitiveLongSet; +import org.neo4j.test.mockito.matcher.Neo4jMatchers; +import org.neo4j.test.rule.ImpermanentDatabaseRule; + +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; +import static org.neo4j.helpers.collection.Iterators.array; + +@RunWith( Parameterized.class ) +public class IndexingCompositeQueryAcceptanceTest +{ + @ClassRule + public static ImpermanentDatabaseRule dbRule = new ImpermanentDatabaseRule(); + @Rule + public final TestName testName = new TestName(); + + @Parameterized.Parameters + public static List data() + { + return Arrays.asList( + testCase( array( 2, 3 ), biIndexSeek, true ), + testCase( array( 2, 3 ), biIndexSeek, false ), + testCase( array( 2, 3, 4 ), triIndexSeek, true ), + testCase( array( 2, 3, 4 ), triIndexSeek, false ), + testCase( array( 2, 3, 4, 5, 6 ), mapIndexSeek, true ), + testCase( array( 2, 3, 4, 5, 6 ), mapIndexSeek, false ) + ); + } + + @Parameterized.Parameter( 0 ) + public String[] keys; + @Parameterized.Parameter( 1 ) + public Object[] values; + @Parameterized.Parameter( 2 ) + public Object[][] nonMatching; + @Parameterized.Parameter( 3 ) + public IndexSeek indexSeek; + @Parameterized.Parameter( 4 ) + public boolean withIndex; + + private static Label LABEL = Label.label( "LABEL1" );; + private GraphDatabaseService db; + + @Before + public void setup() + { + db = dbRule.getGraphDatabaseAPI(); + if ( withIndex ) + { + Neo4jMatchers.createIndex( db, LABEL, keys ); + Neo4jMatchers.createIndex( db, LABEL, keys[0] ); + } + } + + @After + public void tearDown() + { + dbRule.shutdown(); + } + + @Test + public void shouldSupportIndexSeek() + { + // GIVEN + createNodes( db, LABEL, nonMatching ); + PrimitiveLongSet expected = createNodes( db, LABEL, values ); + + // WHEN + PrimitiveLongSet found = Primitive.longSet(); + try ( Transaction tx = db.beginTx() ) + { + collectNodes( found, indexSeek.findNodes( keys, values, db ) ); + } + + // THEN + assertThat( found, equalTo( expected ) ); + } + + @Test + public void shouldSupportIndexSeekBackwardsOrder() + { + // GIVEN + createNodes( db, LABEL, nonMatching ); + PrimitiveLongSet expected = createNodes( db, LABEL, values ); + + // WHEN + PrimitiveLongSet found = Primitive.longSet(); + String[] reversedKeys = new String[keys.length]; + Object[] reversedValues = new Object[keys.length]; + for ( int i = 0; i < keys.length; i++ ) + { + reversedValues[keys.length - 1 - i] = values[i]; + reversedKeys[keys.length - 1 - i] = keys[i]; + } + try ( Transaction tx = db.beginTx() ) + { + collectNodes( found, indexSeek.findNodes( reversedKeys, reversedValues, db ) ); + } + + // THEN + assertThat( found, equalTo( expected ) ); + } + + @Test + public void shouldIncludeNodesCreatedInSameTxInIndexSeek() + { + // GIVEN + createNodes( db, LABEL, nonMatching[0], nonMatching[1] ); + PrimitiveLongSet expected = createNodes( db, LABEL, values ); + // WHEN + PrimitiveLongSet found = Primitive.longSet(); + try ( Transaction tx = db.beginTx() ) + { + expected.add( createNode( db, propertyMap( keys, values ), LABEL ).getId() ); + createNode( db, propertyMap( keys, nonMatching[2] ), LABEL ); + + collectNodes( found, indexSeek.findNodes( keys, values, db ) ); + } + // THEN + assertThat( found, equalTo( expected ) ); + } + + @Test + public void shouldNotIncludeNodesDeletedInSameTxInIndexSeek() + { + // GIVEN + createNodes( db, LABEL, nonMatching[0] ); + PrimitiveLongSet toDelete = createNodes( db, LABEL, values, nonMatching[1], nonMatching[2] ); + PrimitiveLongSet expected = createNodes( db, LABEL, values ); + // WHEN + PrimitiveLongSet found = Primitive.longSet(); + try ( Transaction tx = db.beginTx() ) + { + PrimitiveLongIterator deleting = toDelete.iterator(); + while ( deleting.hasNext() ) + { + long id = deleting.next(); + db.getNodeById( id ).delete(); + expected.remove( id ); + } + + collectNodes( found, indexSeek.findNodes( keys, values, db ) ); + } + // THEN + assertThat( found, equalTo( expected ) ); + } + + @Test + public void shouldConsiderNodesChangedInSameTxInIndexSeek() + { + // GIVEN + createNodes( db, LABEL, nonMatching[0] ); + PrimitiveLongSet toChangeToMatch = createNodes( db, LABEL, nonMatching[1] ); + PrimitiveLongSet toChangeToNotMatch = createNodes( db, LABEL, values ); + PrimitiveLongSet expected = createNodes( db, LABEL, values ); + // WHEN + PrimitiveLongSet found = Primitive.longSet(); + try ( Transaction tx = db.beginTx() ) + { + PrimitiveLongIterator toMatching = toChangeToMatch.iterator(); + while ( toMatching.hasNext() ) + { + long id = toMatching.next(); + setProperties( id, values ); + expected.add( id ); + } + PrimitiveLongIterator toNotMatching = toChangeToNotMatch.iterator(); + while ( toNotMatching.hasNext() ) + { + long id = toNotMatching.next(); + setProperties( id, nonMatching[2] ); + expected.remove( id ); + } + + collectNodes( found, indexSeek.findNodes( keys, values, db ) ); + } + // THEN + assertThat( found, equalTo( expected ) ); + } + + public PrimitiveLongSet createNodes( GraphDatabaseService db, Label label, Object[]... propertyValueTuples ) + { + PrimitiveLongSet expected = Primitive.longSet(); + try ( Transaction tx = db.beginTx() ) + { + for ( Object[] valueTuple : propertyValueTuples ) + { + expected.add( createNode( db, propertyMap( keys, valueTuple ), label ).getId() ); + } + tx.success(); + } + return expected; + } + + public static Map propertyMap( String[] keys, Object[] valueTuple ) + { + Map propertyValues = new HashMap<>(); + for ( int i = 0; i < keys.length; i++ ) + { + propertyValues.put( keys[i], valueTuple[i] ); + } + return propertyValues; + } + + public void collectNodes( PrimitiveLongSet bucket, ResourceIterator toCollect ) + { + while ( toCollect.hasNext() ) + { + bucket.add( toCollect.next().getId() ); + } + } + + public Node createNode( GraphDatabaseService beansAPI, Map properties, Label... labels ) + { + try ( Transaction tx = beansAPI.beginTx() ) + { + Node node = beansAPI.createNode( labels ); + for ( Map.Entry property : properties.entrySet() ) + { + node.setProperty( property.getKey(), property.getValue() ); + } + tx.success(); + return node; + } + } + + public static Object[] testCase( Integer[] values, IndexSeek indexSeek, boolean withIndex ) + { + Object[][] nonMatching = {plus( values, 1 ), plus( values, 2 ), plus( values, 3 )}; + String[] keys = Arrays.stream( values ).map( v -> "key" + v ).toArray( String[]::new ); + return new Object[]{keys, values, nonMatching, indexSeek, withIndex}; + } + + public static Object[] plus( Integer[] values, int offset ) + { + Object[] result = new Object[values.length]; + for ( int i = 0; i < values.length; i++ ) + { + result[i] = values[i] + offset; + } + return result; + } + + private void setProperties( long id, Object[] values ) + { + Node node = db.getNodeById( id ); + for ( int i = 0; i < keys.length; i++ ) + { + node.setProperty( keys[i], values[i] ); + } + } + + public interface IndexSeek { + ResourceIterator findNodes( String[] keys, Object[] values, GraphDatabaseService db ); + } + + public static IndexSeek biIndexSeek = + ( keys, values, db ) -> + { + assert keys.length == 2; + assert values.length == 2; + return db.findNodes( LABEL, keys[0], values[0], keys[1], values[1] ); + }; + + public static IndexSeek triIndexSeek = + ( keys, values, db ) -> + { + assert keys.length == 3; + assert values.length == 3; + return db.findNodes( LABEL, keys[0], values[0], keys[1], values[1], keys[2], values[2] ); + }; + + public static IndexSeek mapIndexSeek = + ( keys, values, db ) -> + { + return db.findNodes( LABEL, propertyMap( keys, values ) ); + }; +} diff --git a/community/kernel/src/test/java/org/neo4j/test/rule/DatabaseRule.java b/community/kernel/src/test/java/org/neo4j/test/rule/DatabaseRule.java index a119839ae8c24..a7572ca0515e4 100644 --- a/community/kernel/src/test/java/org/neo4j/test/rule/DatabaseRule.java +++ b/community/kernel/src/test/java/org/neo4j/test/rule/DatabaseRule.java @@ -535,6 +535,25 @@ public ResourceIterator findNodes( Label label, String key, Object value ) return database.findNodes( label, key, value ); } + @Override + public ResourceIterator findNodes( Label label, String key1, Object value1, String key2, Object value2 ) + { + return database.findNodes( label, key1, value1, key2, value2 ); + } + + @Override + public ResourceIterator findNodes( Label label, String key1, Object value1, String key2, Object value2, + String key3, Object value3 ) + { + return database.findNodes( label, key1, value1, key2, value2, key3, value3 ); + } + + @Override + public ResourceIterator findNodes( Label label, Map propertyValues ) + { + return database.findNodes( label, propertyValues ); + } + @Override public ResourceIterator findNodes( Label label, String key, String template, StringSearchMode searchMode ) { diff --git a/community/shell/src/main/java/org/neo4j/shell/kernel/ReadOnlyGraphDatabaseProxy.java b/community/shell/src/main/java/org/neo4j/shell/kernel/ReadOnlyGraphDatabaseProxy.java index 5b66ccebdb569..95fd45873507e 100644 --- a/community/shell/src/main/java/org/neo4j/shell/kernel/ReadOnlyGraphDatabaseProxy.java +++ b/community/shell/src/main/java/org/neo4j/shell/kernel/ReadOnlyGraphDatabaseProxy.java @@ -1085,6 +1085,25 @@ public ResourceIterator findNodes( Label label, String key, Object value ) return actual.findNodes( label, key, value ); } + @Override + public ResourceIterator findNodes( Label label, String key1, Object value1, String key2, Object value2 ) + { + return actual.findNodes( label, key1, value1, key2, value2 ); + } + + @Override + public ResourceIterator findNodes( Label label, String key1, Object value1, String key2, Object value2, + String key3, Object value3 ) + { + return actual.findNodes( label, key1, value1, key2, value2, key3, value3 ); + } + + @Override + public ResourceIterator findNodes( Label label, Map propertyValues ) + { + return actual.findNodes( label, propertyValues ); + } + @Override public ResourceIterator findNodes( Label label, String key, String template, StringSearchMode searchMode ) { diff --git a/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/StubGraphDatabaseService.java b/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/StubGraphDatabaseService.java index 44e5e4bea79f1..b24fd6c786ae4 100644 --- a/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/StubGraphDatabaseService.java +++ b/enterprise/fulltext-addon/src/test/java/org/neo4j/kernel/api/impl/fulltext/StubGraphDatabaseService.java @@ -90,6 +90,25 @@ public ResourceIterator findNodes( Label label, String key, Object value ) return null; } + @Override + public ResourceIterator findNodes( Label label, String key1, Object value1, String key2, Object value2 ) + { + return null; + } + + @Override + public ResourceIterator findNodes( Label label, String key1, Object value1, String key2, Object value2, + String key3, Object value3 ) + { + return null; + } + + @Override + public ResourceIterator findNodes( Label label, Map propertyValues ) + { + return null; + } + @Override public ResourceIterator findNodes( Label label, String key, String template, StringSearchMode searchMode ) {