Skip to content

Commit

Permalink
Inject node values if asked for and genetic native index provider
Browse files Browse the repository at this point in the history
If a index query targets an index which is backed by the generic native
index provider, we have the ability to serve all property values except
points. This commit introduces a value injector with fills in that
remaining capability by performing property store lookup, meaning that
the index will seem capable of returning all property values to
Kernel API users.
  • Loading branch information
fickludd committed Sep 20, 2018
1 parent 7cccdb9 commit 7d1c4ff
Show file tree
Hide file tree
Showing 5 changed files with 456 additions and 5 deletions.
Expand Up @@ -54,4 +54,10 @@ protected boolean indexProvidesNumericValues()
{
return true;
}

@Override
protected boolean indexProvidesAllValues()
{
return true;
}
}
Expand Up @@ -19,7 +19,9 @@
*/
package org.neo4j.internal.kernel.api;

import org.eclipse.collections.api.list.primitive.MutableLongList;
import org.eclipse.collections.api.set.primitive.MutableLongSet;
import org.eclipse.collections.impl.factory.primitive.LongLists;
import org.eclipse.collections.impl.set.mutable.primitive.LongHashSet;
import org.junit.Test;

Expand Down Expand Up @@ -57,6 +59,8 @@ public abstract class NodeValueIndexCursorTestBase<G extends KernelAPIReadTestSu
private static long strOneNoLabel;
private static long joeDalton, williamDalton, jackDalton, averellDalton;
private static long date891, date892, date86;
private static long[] nodesOfAllPropertyTypes;
private static long whateverPoint;

@Override
void createTestGraph( GraphDatabaseService graphDb )
Expand All @@ -67,6 +71,11 @@ void createTestGraph( GraphDatabaseService graphDb )
tx.success();
}
try ( Transaction tx = graphDb.beginTx() )
{
graphDb.schema().indexFor( label( "What" ) ).on( "ever" ).create();
tx.success();
}
try ( Transaction tx = graphDb.beginTx() )
{
createCompositeIndex( graphDb, "Person", "firstname", "surname" );
tx.success();
Expand Down Expand Up @@ -125,6 +134,17 @@ void createTestGraph( GraphDatabaseService graphDb )
nodeWithProp( graphDb, new String[]{"first", "second", "third"} );
nodeWithProp( graphDb, new String[]{"fourth", "fifth", "sixth", "seventh"} );

MutableLongList listOfIds = LongLists.mutable.empty();
listOfIds.add(nodeWithWhatever( graphDb, "string" ));
listOfIds.add(nodeWithWhatever( graphDb, false ));
listOfIds.add(nodeWithWhatever( graphDb, 3 ));
listOfIds.add(nodeWithWhatever( graphDb, 13.0 ));
whateverPoint = nodeWithWhatever( graphDb, Values.pointValue( Cartesian, 1, 0 ) );
listOfIds.add( whateverPoint );
listOfIds.add(nodeWithWhatever( graphDb, DateValue.date( 1989, 3, 24 ) ));
listOfIds.add(nodeWithWhatever( graphDb, new String[]{"first", "second", "third"} ));

nodesOfAllPropertyTypes = listOfIds.toArray();
tx.success();
}
}
Expand All @@ -145,6 +165,11 @@ protected boolean indexProvidesSpatialValues()
return false;
}

protected boolean indexProvidesAllValues()
{
return false;
}

@Test
public void shouldPerformExactLookup() throws Exception
{
Expand Down Expand Up @@ -724,6 +749,46 @@ public void shouldRespectOrderCapabilitiesForWildcard() throws Exception
}
}

@Test
public void shouldProvideValuesForPoints() throws Exception
{
// given
boolean needsValues = indexProvidesAllValues();
int label = token.nodeLabel( "What" );
int prop = token.propertyKey( "ever" );
IndexReference index = schemaRead.index( label, prop );
try ( NodeValueIndexCursor node = cursors.allocateNodeValueIndexCursor() )
{
MutableLongSet uniqueIds = new LongHashSet();

// when
read.nodeIndexSeek( index, node, IndexOrder.NONE, needsValues, IndexQuery.range( prop, Cartesian ) );

// then
assertFoundNodesAndValue( node, uniqueIds, index.valueCapability( ValueCategory.GEOMETRY ),needsValues, whateverPoint );
}
}

@Test
public void shouldProvideValuesForAllTypes() throws Exception
{
// given
boolean needsValues = indexProvidesAllValues();
int label = token.nodeLabel( "What" );
int prop = token.propertyKey( "ever" );
IndexReference index = schemaRead.index( label, prop );
try ( NodeValueIndexCursor node = cursors.allocateNodeValueIndexCursor() )
{
MutableLongSet uniqueIds = new LongHashSet();

// when
read.nodeIndexSeek( index, node, IndexOrder.NONE, needsValues, IndexQuery.exists( prop ) );

// then
assertFoundNodesAndValue( node, uniqueIds, index.valueCapability( ValueCategory.UNKNOWN ),needsValues, nodesOfAllPropertyTypes );
}
}

private void assertFoundNodesInOrder( NodeValueIndexCursor node, IndexOrder indexOrder )
{
Value currentValue = null;
Expand Down Expand Up @@ -1340,6 +1405,13 @@ private long nodeWithProp( GraphDatabaseService graphDb, Object value )
return node.getId();
}

private long nodeWithWhatever( GraphDatabaseService graphDb, Object value )
{
Node node = graphDb.createNode( label( "What" ) );
node.setProperty( "ever", value );
return node.getId();
}

private long nodeWithNoLabel( GraphDatabaseService graphDb, Object value )
{
Node node = graphDb.createNode();
Expand Down
@@ -0,0 +1,138 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.impl.newapi;

import org.neo4j.internal.kernel.api.IndexOrder;
import org.neo4j.internal.kernel.api.IndexQuery;
import org.neo4j.internal.kernel.api.NodeCursor;
import org.neo4j.internal.kernel.api.PropertyCursor;
import org.neo4j.internal.kernel.api.Read;
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.util.Preconditions;
import org.neo4j.values.storable.Value;

import static org.neo4j.values.storable.Values.NO_VALUE;

/**
* Injects missing property values by lookup in the property store.
*/
class NodeValueInjector implements NodeValueClient, IndexProgressor
{
private final NodeValueClient target;
private final NodeCursor node;
private final PropertyCursor property;
private final int[] propertyKeyIds;
private final Read read;
private IndexProgressor progressor;

NodeValueInjector( NodeValueClient target,
NodeCursor node,
PropertyCursor property,
Read read,
int[] propertyKeyIds )
{
this.target = target;
this.node = node;
this.property = property;
this.propertyKeyIds = propertyKeyIds;
this.read = read;
}

@Override
public void initialize( IndexDescriptor descriptor,
IndexProgressor progressor,
IndexQuery[] query,
IndexOrder indexOrder,
boolean needsValues )
{
Preconditions.checkArgument( needsValues, "No point in injecting values that are not needed!" );

this.progressor = progressor;
target.initialize( descriptor, this, query, indexOrder, needsValues );
}

@Override
public boolean acceptNode( long reference, Value[] values )
{
Preconditions.checkArgument( values != null, "Can only inject missing parts, not whole value array!" );

int nMissing = countMissingValues( values );
if ( nMissing > 0 )
{
// Initialize the property cursor scan
read.singleNode( reference, node );
if ( !node.next() )
{
return false;
}

node.properties( property );
while ( nMissing > 0 && property.next() )
{
for ( int i = 0; i < propertyKeyIds.length; i++ )
{
Value value = values[i];
if ( ( value == null || value == NO_VALUE ) && property.propertyKey() == propertyKeyIds[i] )
{
values[i] = property.propertyValue();
nMissing--;
}
}
}
}

return target.acceptNode( reference, values );
}

private int countMissingValues( Value[] values )
{
int nMissing = 0;
for ( Value v : values )
{
if ( v == null || v == NO_VALUE )
{
nMissing++;
}
}
return nMissing;
}

@Override
public boolean needsValues()
{
return true;
}

@Override
public boolean next()
{
return progressor.next();
}

@Override
public void close()
{
node.close();
property.close();
progressor.close();
}
}
Expand Up @@ -19,6 +19,7 @@
*/
package org.neo4j.kernel.impl.newapi;

import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.internal.kernel.api.IndexOrder;
import org.neo4j.internal.kernel.api.IndexQuery;
import org.neo4j.internal.kernel.api.IndexReference;
Expand Down Expand Up @@ -94,12 +95,31 @@ public final void nodeIndexSeek( IndexReference index, NodeValueIndexCursor curs
DefaultNodeValueIndexCursor cursorImpl = (DefaultNodeValueIndexCursor) cursor;
IndexReader reader = indexReader( index, false );
cursorImpl.setRead( this, null );
IndexProgressor.NodeValueClient target = withFullValuePrecision( cursorImpl, query, reader );
reader.query( target, indexOrder, needsValues, query );
IndexProgressor.NodeValueClient withValues = injectValues( cursorImpl, needsValues, index, reader );
IndexProgressor.NodeValueClient withFullPrecision = injectFullValuePrecision( withValues, query, reader );
reader.query( withFullPrecision, indexOrder, needsValues, query );
}

private IndexProgressor.NodeValueClient withFullValuePrecision( DefaultNodeValueIndexCursor cursor,
IndexQuery[] query, IndexReader reader )
private IndexProgressor.NodeValueClient injectValues( IndexProgressor.NodeValueClient cursor,
boolean needsValues,
IndexReference index, IndexReader reader )
{
if ( needsValues && GraphDatabaseSettings.SchemaIndex.NATIVE_BTREE10.providerName().equals( index.providerKey() ) )
{
return new NodeValueInjector( cursor,
cursors.allocateNodeCursor(),
cursors.allocatePropertyCursor(),
this,
index.properties() );
}
else
{
return cursor;
}
}

private IndexProgressor.NodeValueClient injectFullValuePrecision( IndexProgressor.NodeValueClient cursor,
IndexQuery[] query, IndexReader reader )
{
IndexProgressor.NodeValueClient target = cursor;
if ( !reader.hasFullValuePrecision( query ) )
Expand Down Expand Up @@ -191,7 +211,7 @@ void nodeIndexSeekWithFreshIndexReader(
{
IndexReader reader = indexReader( index, true );
cursor.setRead( this, reader );
IndexProgressor.NodeValueClient target = withFullValuePrecision( cursor, query, reader );
IndexProgressor.NodeValueClient target = injectFullValuePrecision( cursor, query, reader );
// we never need values for exact predicates
reader.query( target, IndexOrder.NONE, false, query );
}
Expand Down

0 comments on commit 7d1c4ff

Please sign in to comment.