Skip to content

Commit

Permalink
Update IndexTxStateUpdater on property changes
Browse files Browse the repository at this point in the history
  • Loading branch information
pontusmelke committed Dec 4, 2017
1 parent e9241cf commit 6166f41
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 14 deletions.
Expand Up @@ -25,16 +25,14 @@

import org.neo4j.collection.primitive.Primitive;
import org.neo4j.collection.primitive.PrimitiveIntSet;
import org.neo4j.kernel.api.exceptions.EntityNotFoundException;
import org.neo4j.kernel.api.schema.index.IndexDescriptor;
import org.neo4j.kernel.api.txstate.TxStateHolder;
import org.neo4j.kernel.impl.util.Validators;
import org.neo4j.storageengine.api.StoreReadLayer;
import org.neo4j.values.storable.Value;
import org.neo4j.values.storable.ValueTuple;
import org.neo4j.values.storable.Values;

import static org.neo4j.kernel.api.StatementConstants.NO_SUCH_PROPERTY_KEY;
import static org.neo4j.values.storable.Values.NO_VALUE;

/**
* Utility class that performs necessary updates for the transaction state.
Expand All @@ -43,13 +41,15 @@ class IndexTxStateUpdater
{
private final StoreReadLayer storeReadLayer;
private final Read read;
private final NodeSchemaMatcher nodeIndexMatcher;

// We can use the StoreReadLayer directly instead of the SchemaReadOps, because we know that in transactions
// where this class is needed we will never have index changes.
IndexTxStateUpdater( StoreReadLayer storeReadLayer, Read read )
{
this.storeReadLayer = storeReadLayer;
this.read = read;
this.nodeIndexMatcher = new NodeSchemaMatcher( read );
}

// LABEL CHANGES
Expand All @@ -67,13 +67,11 @@ public enum LabelChangeType
* @param node cursor to the node where the change was applied
* @param propertyCursor cursor to the properties of node
* @param changeType The type of change event
* @throws EntityNotFoundException
*/
void onLabelChange( int labelId, org.neo4j.internal.kernel.api.NodeCursor node,
org.neo4j.internal.kernel.api.PropertyCursor propertyCursor, LabelChangeType changeType )
throws EntityNotFoundException
{
assert noSchemaChangedInTx( read );
assert noSchemaChangedInTx();

// Find properties of the changed node
PrimitiveIntSet nodePropertyIds = Primitive.intSet();
Expand Down Expand Up @@ -111,16 +109,93 @@ void onLabelChange( int labelId, org.neo4j.internal.kernel.api.NodeCursor node,
}
}

private boolean noSchemaChangedInTx( TxStateHolder state )
private boolean noSchemaChangedInTx()
{
return !(state.txState().hasChanges() && !state.txState().hasDataChanges());
return !(read.txState().hasChanges() && !read.txState().hasDataChanges());
}
// TODO PROPERTY CHANGES

//PROPERTY CHANGES

void onPropertyAdd( org.neo4j.internal.kernel.api.NodeCursor node,
org.neo4j.internal.kernel.api.PropertyCursor propertyCursor, int propertyKeyId, Value value )
{
assert noSchemaChangedInTx();
Iterator<IndexDescriptor> indexes =
storeReadLayer.indexesGetRelatedToProperty( propertyKeyId );
nodeIndexMatcher.onMatchingSchema( indexes, node, propertyCursor, propertyKeyId,
( index, propertyKeyIds ) ->
{
Validators.INDEX_VALUE_VALIDATOR.validate( value );
ValueTuple values =
getValueTuple( node, propertyCursor, propertyKeyId, value,
index.schema().getPropertyIds() );
read.txState().indexDoUpdateEntry( index.schema(), node.nodeReference(), null, values );
} );
}

void onPropertyRemove( org.neo4j.internal.kernel.api.NodeCursor node,
org.neo4j.internal.kernel.api.PropertyCursor propertyCursor, int propertyKeyId, Value value )
{
assert noSchemaChangedInTx();
Iterator<IndexDescriptor> indexes =
storeReadLayer.indexesGetRelatedToProperty( propertyKeyId );
nodeIndexMatcher.onMatchingSchema( indexes, node, propertyCursor, propertyKeyId,
( index, propertyKeyIds ) ->
{
ValueTuple values =
getValueTuple( node, propertyCursor, propertyKeyId, value,
index.schema().getPropertyIds() );
read.txState().indexDoUpdateEntry( index.schema(), node.nodeReference(), values, null );
} );
}

void onPropertyChange( org.neo4j.internal.kernel.api.NodeCursor node,
org.neo4j.internal.kernel.api.PropertyCursor propertyCursor, int propertyKeyId, Value beforeValue,
Value afterValue )
{
assert noSchemaChangedInTx();
Iterator<IndexDescriptor> indexes = storeReadLayer.indexesGetRelatedToProperty( propertyKeyId );
nodeIndexMatcher.onMatchingSchema( indexes, node, propertyCursor, propertyKeyId,
( index, propertyKeyIds ) ->
{
Validators.INDEX_VALUE_VALIDATOR.validate( afterValue );
int[] indexPropertyIds = index.schema().getPropertyIds();

Value[] valuesBefore = new Value[indexPropertyIds.length];
Value[] valuesAfter = new Value[indexPropertyIds.length];
for ( int i = 0; i < indexPropertyIds.length; i++ )
{
int indexPropertyId = indexPropertyIds[i];
if ( indexPropertyId == propertyKeyId )
{
valuesBefore[i] = beforeValue;
valuesAfter[i] = afterValue;
}
else
{
node.properties( propertyCursor );
Value value = NO_VALUE;
while (propertyCursor.next())
{
if (propertyCursor.propertyKey() == indexPropertyId)
{
value = propertyCursor.propertyValue();
}
}
valuesBefore[i] = value;
valuesAfter[i] = value;
}
}
read.txState().indexDoUpdateEntry( index.schema(), node.nodeReference(),
ValueTuple.of( valuesBefore ), ValueTuple.of( valuesAfter ) );
} );
}


private ValueTuple getValueTuple( org.neo4j.internal.kernel.api.NodeCursor node,
org.neo4j.internal.kernel.api.PropertyCursor propertyCursor, int[] indexPropertyIds )
{
return getValueTuple( node, propertyCursor, NO_SUCH_PROPERTY_KEY, Values.NO_VALUE, indexPropertyIds );
return getValueTuple( node, propertyCursor, NO_SUCH_PROPERTY_KEY, NO_VALUE, indexPropertyIds );
}

private ValueTuple getValueTuple( org.neo4j.internal.kernel.api.NodeCursor node,
Expand Down
@@ -0,0 +1,87 @@
package org.neo4j.kernel.impl.newapi;


import java.util.Iterator;

import org.neo4j.collection.primitive.Primitive;
import org.neo4j.collection.primitive.PrimitiveIntSet;
import org.neo4j.function.ThrowingBiConsumer;
import org.neo4j.kernel.api.schema.LabelSchemaDescriptor;
import org.neo4j.kernel.api.schema.LabelSchemaSupplier;

/**
* This class holds functionality to match LabelSchemaDescriptors to nodes
*/
class NodeSchemaMatcher
{
private final Read read;

NodeSchemaMatcher( Read read )
{
this.read = read;
}

/**
* Iterate over some schema suppliers, and invoke a callback for every supplier that matches the node. To match the
* node N the supplier must supply a LabelSchemaDescriptor D, such that N has the label of D, and values for all
* the properties of D.
*
* To avoid unnecessary store lookups, this implementation only gets propertyKeyIds for the node if some
* descriptor has a valid label.
*
* @param <SUPPLIER> the type to match. Must implement LabelSchemaDescriptor.Supplier
* @param <EXCEPTION> The type of exception that can be thrown when taking the action
* @param schemaSuppliers The suppliers to match
* @param node The node cursor
* @param property The property cursor
* @param specialPropertyId This property id will always count as a match for the descriptor, regardless of
* whether the node has this property or not
* @param callback The action to take on match
* @throws EXCEPTION This exception is propagated from the action
*/
<SUPPLIER extends LabelSchemaSupplier,EXCEPTION extends Exception> void onMatchingSchema(
Iterator<SUPPLIER> schemaSuppliers,
org.neo4j.internal.kernel.api.NodeCursor node,
org.neo4j.internal.kernel.api.PropertyCursor property,
int specialPropertyId,
ThrowingBiConsumer<SUPPLIER,PrimitiveIntSet,EXCEPTION> callback
) throws EXCEPTION
{
PrimitiveIntSet nodePropertyIds = null;
while ( schemaSuppliers.hasNext() )
{
SUPPLIER schemaSupplier = schemaSuppliers.next();
LabelSchemaDescriptor schema = schemaSupplier.schema();
if ( node.labels().contains( schema.getLabelId() ) )
{
if ( nodePropertyIds == null )
{
nodePropertyIds = Primitive.intSet();
node.properties( property );
while (property.next())
{
nodePropertyIds.add( property.propertyKey() );
}
}

if ( nodeHasSchemaProperties( nodePropertyIds, schema.getPropertyIds(), specialPropertyId ) )
{
callback.accept( schemaSupplier, nodePropertyIds );
}
}
}
}

private static boolean nodeHasSchemaProperties(
PrimitiveIntSet nodeProperties, int[] indexPropertyIds, int changedPropertyId )
{
for ( int indexPropertyId : indexPropertyIds )
{
if ( indexPropertyId != changedPropertyId && !nodeProperties.contains( indexPropertyId ) )
{
return false;
}
}
return true;
}
}
Expand Up @@ -438,7 +438,7 @@ public Value nodeSetProperty( long node, int propertyKey, Value value ) throws E
//no existing value, we just add it
//TODO autoIndexing.nodes().propertyAdded( ops, nodeId, propertyKeyId, value );
ktx.txState().nodeDoAddProperty( node, propertyKey, value );
//TODO updater.onPropertyAdd( state, node, propertyKeyId, value );
updater.onPropertyAdd( nodeCursor, propertyCursor, propertyKey, value );
return NO_VALUE;
}
else
Expand All @@ -448,7 +448,7 @@ public Value nodeSetProperty( long node, int propertyKey, Value value ) throws E
//the value has changed to a new value
//TODO autoIndexing.nodes().propertyChanged( ops, nodeId, propertyKeyId, existingValue, value );
ktx.txState().nodeDoChangeProperty( node, propertyKey, existingValue, value );
//TODO updater.onPropertyChange( state, node, propertyKeyId, existingValue, value );
updater.onPropertyChange( nodeCursor, propertyCursor, propertyKey, existingValue, value );
}
return existingValue;
}
Expand All @@ -465,7 +465,7 @@ public Value nodeRemoveProperty( long node, int propertyKey ) throws EntityNotFo
//no existing value,® we just add it
//TODO autoIndexing.nodes().propertyRemoved( ops, nodeId, propertyKeyId );
ktx.txState().nodeDoRemoveProperty( node, propertyKey, existingValue);
//TODO indexTxStateUpdater.onPropertyRemove( state, node, propertyKeyId, existingValue );
updater.onPropertyRemove( nodeCursor, propertyCursor, propertyKey, existingValue );
}

return existingValue;
Expand Down
Expand Up @@ -151,6 +151,54 @@ public void shouldUpdateIndexesOnRemovedLabel() throws EntityNotFoundException
verify( txState, times( 1 ) ).indexDoUpdateEntry( any(), anyLong(), any(), isNull() );
}

@Test
public void shouldNotUpdateIndexesOnChangedIrrelevantProperty() throws EntityNotFoundException
{
// WHEN
indexTxUpdater.onPropertyAdd( node, propertyCursor, unIndexedPropId, Values.of( "whAt" ) );
indexTxUpdater.onPropertyRemove( node, propertyCursor, unIndexedPropId, Values.of( "whAt" ) );
indexTxUpdater.onPropertyChange( node, propertyCursor, unIndexedPropId, Values.of( "whAt" ), Values.of( "whAt2" ) );

// THEN
verify( txState, times( 0 ) ).indexDoUpdateEntry( any(), anyInt(), any(), any() );
}

@Test
public void shouldUpdateIndexesOnAddedProperty() throws EntityNotFoundException
{
// WHEN
indexTxUpdater.onPropertyAdd( node, propertyCursor, newPropId, Values.of( "newHi" ) );

// THEN
verifyIndexUpdate( indexOn2_new.schema(), node.nodeReference(), null, values( "newHi" ) );
verifyIndexUpdate( indexOn1_1_new.schema(), node.nodeReference(), null, values( "hi1", "newHi" ) );
verify( txState, times( 2 ) ).indexDoUpdateEntry( any(), anyLong(), isNull(), any() );
}

@Test
public void shouldUpdateIndexesOnRemovedProperty() throws EntityNotFoundException
{
// WHEN
indexTxUpdater.onPropertyRemove( node, propertyCursor, propId2, Values.of( "hi2" ) );

// THEN
verifyIndexUpdate( uniqueOn1_2.schema(), node.nodeReference(), values( "hi2" ), null );
verifyIndexUpdate( uniqueOn2_2_3.schema(), node.nodeReference(), values( "hi2", "hi3" ), null );
verify( txState, times( 2 ) ).indexDoUpdateEntry( any(), anyLong(), any(), isNull() );
}

@Test
public void shouldUpdateIndexesOnChangesProperty() throws EntityNotFoundException
{
// WHEN
indexTxUpdater.onPropertyChange( node, propertyCursor, propId2, Values.of( "hi2" ), Values.of( "new2" ) );

// THEN
verifyIndexUpdate( uniqueOn1_2.schema(), node.nodeReference(), values( "hi2" ), values( "new2" ) );
verifyIndexUpdate( uniqueOn2_2_3.schema(), node.nodeReference(), values( "hi2", "hi3" ), values( "new2", "hi3" ) );
verify( txState, times( 2 ) ).indexDoUpdateEntry( any(), anyLong(), any(), any() );
}

private ValueTuple values( Object... values )
{
return ValueTuple.of( values );
Expand All @@ -161,5 +209,4 @@ private void verifyIndexUpdate(
{
verify( txState ).indexDoUpdateEntry( eq( schema ), eq( nodeId ), eq( before ), eq( after ) );
}

}

0 comments on commit 6166f41

Please sign in to comment.