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();
+ }
}
}