Skip to content

Commit

Permalink
Implemented composite findNodes in Core API
Browse files Browse the repository at this point in the history
  • Loading branch information
fickludd committed Mar 13, 2018
1 parent b8de4ac commit 98d2353
Show file tree
Hide file tree
Showing 6 changed files with 610 additions and 11 deletions.
Expand Up @@ -134,6 +134,92 @@ public interface GraphDatabaseService
*/
ResourceIterator<Node> 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.
* <p>
* If no indexes exist for the label with all provided properties, the database will
* scan all labeled nodes looking for matching nodes.
* <p>
* 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.
* <p>
* Same rules follow Character and String - the Character 'A' is equal to the String 'A'.
* <p>
* Finally - arrays also follow these rules. An int[] {1,2,3} is equal to a double[] {1.0, 2.0, 3.0}
* <p>
* 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<Node> 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.
* <p>
* If no indexes exist for the label with all provided properties, the database will
* scan all labeled nodes looking for matching nodes.
* <p>
* 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.
* <p>
* Same rules follow Character and String - the Character 'A' is equal to the String 'A'.
* <p>
* Finally - arrays also follow these rules. An int[] {1,2,3} is equal to a double[] {1.0, 2.0, 3.0}
* <p>
* 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<Node> 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.
* <p>
* If no indexes exist for the label with all provided properties, the database will
* scan all labeled nodes looking for matching nodes.
* <p>
* 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.
* <p>
* Same rules follow Character and String - the Character 'A' is equal to the String 'A'.
* <p>
* Finally - arrays also follow these rules. An int[] {1,2,3} is equal to a double[] {1.0, 2.0, 3.0}
* <p>
* 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<Node> findNodes( Label label, Map<String, Object> 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.
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -585,6 +587,45 @@ public ResourceIterator<Node> findNodes( final Label myLabel, final String key,
return nodesByLabelAndProperty( transaction, labelId, IndexQuery.exact( propertyId, Values.of( value ) ) );
}

@Override
public ResourceIterator<Node> 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<Node> 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<Node> findNodes( Label label, Map<String,Object> 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<String,Object> entry : propertyValues.entrySet() )
{
queries[i++] = IndexQuery.exact( tokenRead.propertyKey( entry.getKey() ), Values.of( entry.getValue() ) );
}
return nodesByLabelAndProperties( transaction, labelId, queries );
}

@Override
public ResourceIterator<Node> findNodes(
final Label myLabel, final String key, final String value, final StringSearchMode searchMode )
Expand Down Expand Up @@ -678,11 +719,103 @@ private ResourceIterator<Node> nodesByLabelAndProperty( KernelTransaction transa
}
}

return getNodesByLabelAndPropertyWithoutIndex( query, statement, labelId );
return getNodesByLabelAndPropertyWithoutIndex( statement, labelId, query );
}

private ResourceIterator<Node> 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<CapableIndexReference> 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<Node> getNodesByLabelAndPropertyWithoutIndex(
IndexQuery query, Statement statement, int labelId )
Statement statement, int labelId, IndexQuery... queries )
{
KernelTransaction transaction = statementContext.getKernelTransactionBoundToThisThread( true );

Expand All @@ -698,7 +831,7 @@ private ResourceIterator<Node> getNodesByLabelAndPropertyWithoutIndex(
propertyCursor,
statement,
this::newNodeProxy,
query );
queries );
}

private ResourceIterator<Node> allNodesWithLabel( final Label myLabel )
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -867,7 +1000,7 @@ protected long fetchNext()
{
hasNext = nodeLabelCursor.next();

} while ( hasNext && !hasPropertyWithValue() );
} while ( hasNext && !hasPropertiesWithValues() );

if ( hasNext )
{
Expand All @@ -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;
}
}
}
}
}
Expand Down

0 comments on commit 98d2353

Please sign in to comment.