org.neo4j.kernel.impl.newapi.IndexTxStateUpdater Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of neo4j-kernel Show documentation
Show all versions of neo4j-kernel Show documentation
Neo4j kernel is a lightweight, embedded Java database designed to
store data structured as graphs rather than tables. For more
information, see http://neo4j.org.
/*
* Copyright (c) "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 .
*/
package org.neo4j.kernel.impl.newapi;
import org.apache.commons.lang3.ArrayUtils;
import org.eclipse.collections.api.list.primitive.MutableIntList;
import org.eclipse.collections.api.map.primitive.MutableIntObjectMap;
import org.eclipse.collections.impl.factory.primitive.IntLists;
import org.eclipse.collections.impl.factory.primitive.IntObjectMaps;
import java.util.Arrays;
import java.util.Collection;
import org.neo4j.common.EntityType;
import org.neo4j.internal.kernel.api.EntityCursor;
import org.neo4j.internal.kernel.api.NodeCursor;
import org.neo4j.internal.kernel.api.PropertyCursor;
import org.neo4j.internal.kernel.api.RelationshipScanCursor;
import org.neo4j.internal.schema.IndexDescriptor;
import org.neo4j.internal.schema.SchemaDescriptor;
import org.neo4j.kernel.impl.api.index.IndexingService;
import org.neo4j.memory.MemoryTracker;
import org.neo4j.storageengine.api.PropertySelection;
import org.neo4j.storageengine.api.StorageReader;
import org.neo4j.values.storable.Value;
import org.neo4j.values.storable.ValueTuple;
import static org.neo4j.common.EntityType.NODE;
import static org.neo4j.common.EntityType.RELATIONSHIP;
import static org.neo4j.kernel.api.StatementConstants.NO_SUCH_PROPERTY_KEY;
import static org.neo4j.token.api.TokenConstants.ANY_PROPERTY_KEY;
import static org.neo4j.values.storable.Values.NO_VALUE;
/**
* Utility class that performs necessary updates for the transaction state.
*/
public class IndexTxStateUpdater
{
private final StorageReader storageReader;
private final Read read;
private final IndexingService indexingService;
// We can use the StorageReader directly instead of the SchemaReadOps, because we know that in transactions
// where this class is needed we will never have index changes.
public IndexTxStateUpdater( StorageReader storageReader, Read read, IndexingService indexingService )
{
this.storageReader = storageReader;
this.read = read;
this.indexingService = indexingService;
}
// LABEL CHANGES
public enum LabelChangeType
{
ADDED_LABEL,
REMOVED_LABEL
}
/**
* A label has been changed, figure out what updates are needed to tx state.
*
* @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
* @param indexes the indexes related to the node
*/
void onLabelChange( NodeCursor node, PropertyCursor propertyCursor, LabelChangeType changeType, Collection indexes )
{
assert noSchemaChangedInTx();
// Check all indexes of the changed label
if ( !indexes.isEmpty() )
{
MutableIntObjectMap materializedProperties = IntObjectMaps.mutable.empty();
for ( IndexDescriptor index : indexes )
{
MemoryTracker memoryTracker = read.txState().memoryTracker();
int[] indexPropertyIds = index.schema().getPropertyIds();
Value[] values = getValueTuple( node, propertyCursor, NO_SUCH_PROPERTY_KEY, NO_VALUE, indexPropertyIds,
materializedProperties, memoryTracker );
ValueTuple valueTuple = ValueTuple.of( values );
memoryTracker.allocateHeap( valueTuple.getShallowSize() );
switch ( changeType )
{
case ADDED_LABEL:
indexingService.validateBeforeCommit( index, values, node.nodeReference() );
read.txState().indexDoUpdateEntry( index.schema(), node.nodeReference(), null, valueTuple );
break;
case REMOVED_LABEL:
read.txState().indexDoUpdateEntry( index.schema(), node.nodeReference(), valueTuple, null );
break;
default:
throw new IllegalStateException( changeType + " is not a supported event" );
}
}
}
}
void onPropertyAdd( NodeCursor node, PropertyCursor propertyCursor, long[] labels, int propertyKeyId, int[] existingPropertyKeyIds, Value value )
{
onPropertyAdd( node, NODE, propertyCursor, labels, propertyKeyId, existingPropertyKeyIds, value );
}
void onPropertyRemove( NodeCursor node, PropertyCursor propertyCursor, long[] labels, int propertyKeyId, int[] existingPropertyKeyIds, Value value )
{
onPropertyRemove( node, NODE, propertyCursor, labels, propertyKeyId, existingPropertyKeyIds, value );
}
void onPropertyChange( NodeCursor node, PropertyCursor propertyCursor, long[] labels, int propertyKeyId, int[] existingPropertyKeyIds, Value beforeValue,
Value afterValue )
{
onPropertyChange( node, NODE, propertyCursor, labels, propertyKeyId, existingPropertyKeyIds, beforeValue, afterValue );
}
void onPropertyAdd( RelationshipScanCursor relationship, PropertyCursor propertyCursor, int type, int propertyKeyId, int[] existingPropertyKeyIds,
Value value )
{
onPropertyAdd( relationship, RELATIONSHIP, propertyCursor, new long[]{type}, propertyKeyId, existingPropertyKeyIds, value );
}
void onPropertyRemove( RelationshipScanCursor relationship, PropertyCursor propertyCursor, int type, int propertyKeyId, int[] existingPropertyKeyIds,
Value value )
{
onPropertyRemove( relationship, RELATIONSHIP, propertyCursor, new long[]{type}, propertyKeyId, existingPropertyKeyIds, value );
}
void onPropertyChange( RelationshipScanCursor relationship, PropertyCursor propertyCursor, int type, int propertyKeyId, int[] existingPropertyKeyIds,
Value beforeValue, Value afterValue )
{
onPropertyChange( relationship, RELATIONSHIP, propertyCursor, new long[]{type}, propertyKeyId, existingPropertyKeyIds, beforeValue,
afterValue );
}
void onDeleteUncreated( NodeCursor node, PropertyCursor propertyCursor )
{
onDeleteUncreated( node, NODE, propertyCursor, node.labels().all() );
}
void onDeleteUncreated( RelationshipScanCursor relationship, PropertyCursor propertyCursor )
{
onDeleteUncreated( relationship, RELATIONSHIP, propertyCursor, new long[]{relationship.type()} );
}
private boolean noSchemaChangedInTx()
{
return !(read.txState().hasChanges() && !read.txState().hasDataChanges());
}
//PROPERTY CHANGES
/**
* Creating an entity with its data in a transaction adds also adds that state to index transaction state (for matching indexes).
* When deleting an entity this method will delete this state from the index transaction state.
*
* @param entity entity that was deleted.
* @param propertyCursor property cursor for accessing the properties of the entity.
* @param tokens the entity tokens this entity has.
*/
private void onDeleteUncreated( EntityCursor entity, EntityType entityType, PropertyCursor propertyCursor, long[] tokens )
{
assert noSchemaChangedInTx();
entity.properties( propertyCursor, PropertySelection.ALL_PROPERTY_KEYS );
MutableIntList propertyKeyList = IntLists.mutable.empty();
while ( propertyCursor.next() )
{
propertyKeyList.add( propertyCursor.propertyKey() );
}
// Make sure to sort the propertyKeyIds since SchemaMatcher.onMatchingSchema requires it.
int[] propertyKeyIds = propertyKeyList.toSortedArray();
Collection indexes = storageReader.valueIndexesGetRelated( tokens, propertyKeyIds, entityType );
if ( !indexes.isEmpty() )
{
MutableIntObjectMap materializedProperties = IntObjectMaps.mutable.empty();
SchemaMatcher.onMatchingSchema( indexes.iterator(), ANY_PROPERTY_KEY, propertyKeyIds,
index ->
{
MemoryTracker memoryTracker = read.txState().memoryTracker();
SchemaDescriptor schema = index.schema();
Value[] values = getValueTuple( entity, propertyCursor, ANY_PROPERTY_KEY, NO_VALUE, schema.getPropertyIds(), materializedProperties,
memoryTracker );
ValueTuple valueTuple = ValueTuple.of( values );
memoryTracker.allocateHeap( valueTuple.getShallowSize() );
read.txState().indexDoUpdateEntry( schema, entity.reference(), valueTuple, null );
} );
}
}
private void onPropertyAdd( EntityCursor entity, EntityType entityType, PropertyCursor propertyCursor, long[] tokens, int propertyKeyId,
int[] existingPropertyKeyIds, Value value )
{
assert noSchemaChangedInTx();
Collection indexes = storageReader.valueIndexesGetRelated( tokens, propertyKeyId, entityType );
if ( !indexes.isEmpty() )
{
MutableIntObjectMap materializedProperties = IntObjectMaps.mutable.empty();
SchemaMatcher.onMatchingSchema( indexes.iterator(), propertyKeyId, existingPropertyKeyIds,
index ->
{
MemoryTracker memoryTracker = read.txState().memoryTracker();
SchemaDescriptor schema = index.schema();
Value[] values = getValueTuple( entity, propertyCursor, propertyKeyId, value, schema.getPropertyIds(), materializedProperties,
memoryTracker );
indexingService.validateBeforeCommit( index, values, entity.reference() );
ValueTuple valueTuple = ValueTuple.of( values );
memoryTracker.allocateHeap( valueTuple.getShallowSize() );
read.txState().indexDoUpdateEntry( schema, entity.reference(), null, valueTuple );
} );
}
}
private void onPropertyRemove( EntityCursor entity, EntityType entityType, PropertyCursor propertyCursor, long[] tokens, int propertyKeyId,
int[] existingPropertyKeyIds, Value value )
{
assert noSchemaChangedInTx();
Collection indexes = storageReader.valueIndexesGetRelated( tokens, propertyKeyId, entityType );
if ( !indexes.isEmpty() )
{
MutableIntObjectMap materializedProperties = IntObjectMaps.mutable.empty();
SchemaMatcher.onMatchingSchema( indexes.iterator(), propertyKeyId, existingPropertyKeyIds,
index ->
{
MemoryTracker memoryTracker = read.txState().memoryTracker();
SchemaDescriptor schema = index.schema();
Value[] values = getValueTuple( entity, propertyCursor, propertyKeyId, value, schema.getPropertyIds(), materializedProperties,
memoryTracker );
ValueTuple valueTuple = ValueTuple.of( values );
memoryTracker.allocateHeap( valueTuple.getShallowSize() );
read.txState().indexDoUpdateEntry( schema, entity.reference(), valueTuple, null );
} );
}
}
private void onPropertyChange( EntityCursor entity, EntityType entityType, PropertyCursor propertyCursor, long[] tokens, int propertyKeyId,
int[] existingPropertyKeyIds, Value beforeValue, Value afterValue )
{
assert noSchemaChangedInTx();
Collection indexes = storageReader.valueIndexesGetRelated( tokens, propertyKeyId, entityType );
if ( !indexes.isEmpty() )
{
MutableIntObjectMap materializedProperties = IntObjectMaps.mutable.empty();
SchemaMatcher.onMatchingSchema( indexes.iterator(), propertyKeyId, existingPropertyKeyIds,
index ->
{
MemoryTracker memoryTracker = read.txState().memoryTracker();
SchemaDescriptor schema = index.schema();
int[] propertyIds = schema.getPropertyIds();
Value[] valuesAfter =
getValueTuple( entity, propertyCursor, propertyKeyId, afterValue, propertyIds, materializedProperties, memoryTracker );
// The valuesBefore tuple is just like valuesAfter, except is has the afterValue instead of the beforeValue
Value[] valuesBefore = Arrays.copyOf( valuesAfter, valuesAfter.length );
int k = ArrayUtils.indexOf( propertyIds, propertyKeyId );
valuesBefore[k] = beforeValue;
indexingService.validateBeforeCommit( index, valuesAfter, entity.reference() );
ValueTuple valuesTupleBefore = ValueTuple.of( valuesBefore );
ValueTuple valuesTupleAfter = ValueTuple.of( valuesAfter );
memoryTracker.allocateHeap( valuesTupleBefore.getShallowSize() * 2 ); // They are copies and same shallow size
read.txState().indexDoUpdateEntry( schema, entity.reference(), valuesTupleBefore, valuesTupleAfter );
} );
}
}
private static Value[] getValueTuple( EntityCursor entity, PropertyCursor propertyCursor, int changedPropertyKeyId, Value changedValue,
int[] indexPropertyIds,
MutableIntObjectMap materializedValues, MemoryTracker memoryTracker )
{
Value[] values = new Value[indexPropertyIds.length];
int missing = 0;
// First get whatever values we already have on the stack, like the value change that provoked this update in the first place
// and already loaded values that we can get from the map of materialized values.
for ( int k = 0; k < indexPropertyIds.length; k++ )
{
values[k] = indexPropertyIds[k] == changedPropertyKeyId ? changedValue : materializedValues.getIfAbsent( indexPropertyIds[k], () -> NO_VALUE );
if ( values[k] == NO_VALUE )
{
missing++;
}
}
// If we couldn't get all values that we wanted we need to load from the entity. While we're loading values
// we'll place those values in the map so that other index updates from this change can just used them.
if ( missing > 0 )
{
entity.properties( propertyCursor, PropertySelection.selection( indexPropertyIds ) );
while ( missing > 0 && propertyCursor.next() )
{
int k = ArrayUtils.indexOf( indexPropertyIds, propertyCursor.propertyKey() );
assert k >= 0;
if ( values[k] == NO_VALUE )
{
int propertyKeyId = indexPropertyIds[k];
boolean thisIsTheChangedProperty = propertyKeyId == changedPropertyKeyId;
values[k] = thisIsTheChangedProperty ? changedValue : propertyCursor.propertyValue();
if ( !thisIsTheChangedProperty )
{
materializedValues.put( propertyKeyId, values[k] );
memoryTracker.allocateHeap( values[k].estimatedHeapUsage() );
}
missing--;
}
}
}
return values;
}
}