All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.kernel.impl.newapi.IndexTxStateUpdater Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 5.25.1
Show newest version
/*
 * 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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy