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 4348d5c66de88..fa17cd82e0b4f 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 @@ -137,8 +137,7 @@ public interface GraphDatabaseService /** * 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. + * nodes. *

* If no indexes exist for the label with all provided properties, the database will * scan all labeled nodes looking for matching nodes. @@ -162,14 +161,13 @@ public interface GraphDatabaseService */ default ResourceIterator findNodes( Label label, String key1, Object value1, String key2, Object value2 ) { - throw new UnsupportedOperationException( "Composite findNodes is not supported by this GraphDatabaseService" ); + throw new UnsupportedOperationException( "findNodes by multiple property names and values is not supported." ); } /** * 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. + * nodes. *

* If no indexes exist for the label with all provided properties, the database will * scan all labeled nodes looking for matching nodes. @@ -198,14 +196,13 @@ default ResourceIterator findNodes( Label label, String key2, Object value2, String key3, Object value3 ) { - throw new UnsupportedOperationException( "Composite findNodes is not supported by this GraphDatabaseService" ); + throw new UnsupportedOperationException( "findNodes by multiple property names and values is not supported." ); } /** * 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. + * nodes. *

* If no indexes exist for the label with all provided properties, the database will * scan all labeled nodes looking for matching nodes. @@ -226,7 +223,7 @@ default ResourceIterator findNodes( Label label, */ default ResourceIterator findNodes( Label label, Map propertyValues ) { - throw new UnsupportedOperationException( "Composite findNodes is not supported by this GraphDatabaseService" ); + throw new UnsupportedOperationException( "findNodes by multiple property names and values is not supported." ); } /** @@ -258,7 +255,7 @@ default ResourceIterator findNodes( Label label, Map prope */ default ResourceIterator findNodes( Label label, String key, String template, StringSearchMode searchMode ) { - throw new UnsupportedOperationException( "Specialized string queries are not supported by this GraphDatabaseService" ); + throw new UnsupportedOperationException( "Specialized string queries are not supported" ); } /** diff --git a/community/graphdb-api/src/main/java/org/neo4j/graphdb/StringSearchMode.java b/community/graphdb-api/src/main/java/org/neo4j/graphdb/StringSearchMode.java index 452f234e836a0..fafa53f2098ed 100644 --- a/community/graphdb-api/src/main/java/org/neo4j/graphdb/StringSearchMode.java +++ b/community/graphdb-api/src/main/java/org/neo4j/graphdb/StringSearchMode.java @@ -44,7 +44,7 @@ public enum StringSearchMode */ SUFFIX, /** - * The value must contain the template. Only exact matches are supported. + * The value must contain the template exactly. Regular expressions are not supported. */ CONTAINS; } 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 b538815935c1f..71b9c9bc44f52 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 @@ -1018,7 +1018,8 @@ public PrimitiveLongReadableDiffSets indexUpdatesForScan( SchemaIndexDescriptor @Override public PrimitiveLongReadableDiffSets indexUpdatesForSuffixOrContains( SchemaIndexDescriptor descriptor, IndexQuery query ) { - descriptor.schema().getPropertyId(); // assert single property index + assert descriptor.schema().getPropertyIds().length == 0 : + "Suffix and contains queries are only supported for single property queries"; if ( indexUpdates == null ) { 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 83fbdf710807d..125e46bdf68f5 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 @@ -76,6 +76,7 @@ import org.neo4j.io.IOUtils; import org.neo4j.kernel.GraphDatabaseQueryService; import org.neo4j.kernel.api.KernelTransaction; +import org.neo4j.kernel.api.SilentTokenNameLookup; import org.neo4j.kernel.api.Statement; import org.neo4j.kernel.api.exceptions.Status; import org.neo4j.kernel.api.explicitindex.AutoIndexing; @@ -617,7 +618,7 @@ public ResourceIterator findNodes( Label label, Map propert KernelTransaction transaction = statementContext.getKernelTransactionBoundToThisThread( true ); TokenRead tokenRead = transaction.tokenRead(); int labelId = tokenRead.nodeLabel( label.name() ); - IndexQuery[] queries = new IndexQuery[propertyValues.size()]; + IndexQuery.ExactPredicate[] queries = new IndexQuery.ExactPredicate[propertyValues.size()]; int i = 0; for ( Map.Entry entry : propertyValues.entrySet() ) { @@ -722,7 +723,8 @@ private ResourceIterator nodesByLabelAndProperty( KernelTransaction transa return getNodesByLabelAndPropertyWithoutIndex( statement, labelId, query ); } - private ResourceIterator nodesByLabelAndProperties( KernelTransaction transaction, int labelId, IndexQuery... queries ) + private ResourceIterator nodesByLabelAndProperties( + KernelTransaction transaction, int labelId, IndexQuery.ExactPredicate... queries ) { Statement statement = transaction.acquireStatement(); Read read = transaction.dataRead(); @@ -733,33 +735,54 @@ private ResourceIterator nodesByLabelAndProperties( KernelTransaction tran return emptyResourceIterator(); } - int[] propertyIds = getSortedPropertyIds( queries ); - int[] workingCopy = new int[propertyIds.length]; + int[] propertyIds = getPropertyIds( queries ); + CapableIndexReference index = findMatchingIndex( transaction, labelId, propertyIds ); - Iterator indexes = transaction.schemaRead().indexesGetForLabel( labelId ); - while ( indexes.hasNext() ) + if ( index != CapableIndexReference.NO_INDEX ) { - CapableIndexReference index = indexes.next(); - int[] original = index.properties(); - if ( hasSamePropertyIds( original, workingCopy, propertyIds ) ) + try { - // 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 ) ); + NodeValueIndexCursor cursor = transaction.cursors().allocateNodeValueIndexCursor(); + read.nodeIndexSeek( index, cursor, IndexOrder.NONE, getReorderedIndexQueries( index.properties(), queries ) ); + return new NodeCursorResourceIterator<>( cursor, statement, this::newNodeProxy ); + } + catch ( KernelException e ) + { + // weird at this point but ignore and fallback to a label scan + } + } + return getNodesByLabelAndPropertyWithoutIndex( statement, labelId, queries ); + } - return new NodeCursorResourceIterator<>( cursor, statement, this::newNodeProxy ); - } - catch ( KernelException e ) + private CapableIndexReference findMatchingIndex( KernelTransaction transaction, int labelId, int[] propertyIds ) + { + CapableIndexReference index = transaction.schemaRead().index( labelId, propertyIds ); + if ( index != CapableIndexReference.NO_INDEX ) + { + // index found with property order matching the query + return index; + } + else + { + // attempt to find matching index with different property order + Arrays.sort( propertyIds ); + assertNoDuplicates( propertyIds, transaction.tokenRead() ); + + int[] workingCopy = new int[propertyIds.length]; + + Iterator indexes = transaction.schemaRead().indexesGetForLabel( labelId ); + while ( indexes.hasNext() ) + { + index = indexes.next(); + int[] original = index.properties(); + if ( hasSamePropertyIds( original, workingCopy, propertyIds ) ) { - // weird at this point but ignore and fallback to a label scan - break; + // Ha! We found an index with the same properties in another order + return index; } } + return CapableIndexReference.NO_INDEX; } - - return getNodesByLabelAndPropertyWithoutIndex( statement, labelId, queries ); } private IndexQuery[] getReorderedIndexQueries( int[] indexPropertyIds, IndexQuery[] queries ) @@ -791,14 +814,13 @@ private boolean hasSamePropertyIds( int[] original, int[] workingCopy, int[] pro return false; } - private int[] getSortedPropertyIds( IndexQuery[] queries ) + private int[] getPropertyIds( 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; } @@ -809,11 +831,27 @@ private boolean isInvalidQuery( int labelId, IndexQuery[] queries ) { int propertyKeyId = query.propertyKeyId(); invalidQuery = invalidQuery || propertyKeyId == NO_SUCH_PROPERTY_KEY; - } return invalidQuery; } + private void assertNoDuplicates( int[] propertyIds, TokenRead tokenRead ) + { + int prev = propertyIds[0]; + for ( int i = 1; i < propertyIds.length; i++ ) + { + int curr = propertyIds[i]; + if ( curr == prev ) + { + SilentTokenNameLookup tokenLookup = new SilentTokenNameLookup( tokenRead ); + throw new IllegalArgumentException( + format( "Provided two queries for property %s. Only one query per property key can be performed", + tokenLookup.propertyKeyGetName( curr ) ) ); + } + prev = curr; + } + } + private ResourceIterator getNodesByLabelAndPropertyWithoutIndex( Statement statement, int labelId, IndexQuery... queries ) { @@ -1030,13 +1068,19 @@ private boolean hasPropertiesWithValues() { for ( IndexQuery query : queries ) { - if ( propertyCursor.propertyKey() == query.propertyKeyId() && - query.acceptsValueAt( propertyCursor ) ) + if ( propertyCursor.propertyKey() == query.propertyKeyId() ) { - targetCount--; - if ( targetCount == 0 ) + if ( query.acceptsValueAt( propertyCursor ) ) + { + targetCount--; + if ( targetCount == 0 ) + { + return true; + } + } + else { - return true; + return false; } } } 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 8723ab1285617..0bd8503329eea 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 @@ -85,6 +85,7 @@ public void initialize( SchemaIndexDescriptor descriptor, IndexProgressor progre break; case stringPrefix: + assert query.length == 1; prefixQuery( descriptor, (IndexQuery.StringPrefixPredicate) firstPredicate ); break; diff --git a/community/kernel/src/test/java/org/neo4j/graphdb/IndexingCompositeQueryAcceptanceTest.java b/community/kernel/src/test/java/org/neo4j/graphdb/IndexingCompositeQueryAcceptanceTest.java index 8d11350a60cd3..f46c49582d411 100644 --- a/community/kernel/src/test/java/org/neo4j/graphdb/IndexingCompositeQueryAcceptanceTest.java +++ b/community/kernel/src/test/java/org/neo4j/graphdb/IndexingCompositeQueryAcceptanceTest.java @@ -32,11 +32,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; 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.graphdb.schema.IndexCreator; import org.neo4j.test.rule.ImpermanentDatabaseRule; import static org.hamcrest.core.IsEqual.equalTo; @@ -84,8 +85,24 @@ public void setup() db = dbRule.getGraphDatabaseAPI(); if ( withIndex ) { - Neo4jMatchers.createIndex( db, LABEL, keys ); - Neo4jMatchers.createIndex( db, LABEL, keys[0] ); + try ( org.neo4j.graphdb.Transaction tx = db.beginTx() ) + { + db.schema().indexFor( LABEL ).on( keys[0] ).create(); + + IndexCreator indexCreator = db.schema().indexFor( LABEL ); + for ( String key : keys ) + { + indexCreator = indexCreator.on( key ); + } + indexCreator.create(); + tx.success(); + } + + try ( org.neo4j.graphdb.Transaction tx = db.beginTx() ) + { + db.schema().awaitIndexesOnline( 5, TimeUnit.MINUTES ); + tx.success(); + } } } diff --git a/community/kernel/src/test/java/org/neo4j/graphdb/IndexingStringQueryAcceptanceTestBase.java b/community/kernel/src/test/java/org/neo4j/graphdb/IndexingStringQueryAcceptanceTestBase.java index f053bf6e38591..7f9dd18f81b2e 100644 --- a/community/kernel/src/test/java/org/neo4j/graphdb/IndexingStringQueryAcceptanceTestBase.java +++ b/community/kernel/src/test/java/org/neo4j/graphdb/IndexingStringQueryAcceptanceTestBase.java @@ -26,11 +26,11 @@ import org.junit.rules.TestName; import java.util.Map; +import java.util.concurrent.TimeUnit; 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; @@ -71,7 +71,17 @@ public void setup() db = dbRule.getGraphDatabaseAPI(); if ( withIndex ) { - Neo4jMatchers.createIndex( db, LABEL, KEY ); + try ( org.neo4j.graphdb.Transaction tx = db.beginTx() ) + { + db.schema().indexFor( LABEL ).on( KEY ).create(); + tx.success(); + } + + try ( org.neo4j.graphdb.Transaction tx = db.beginTx() ) + { + db.schema().awaitIndexesOnline( 5, TimeUnit.MINUTES ); + tx.success(); + } } }