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

org.neo4j.kernel.internal.event.TxStateTransactionDataSnapshot 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.internal.event;

import org.eclipse.collections.api.LongIterable;
import org.eclipse.collections.api.map.primitive.MutableLongObjectMap;
import org.eclipse.collections.api.set.primitive.LongSet;

import java.util.Iterator;
import java.util.Map;

import org.neo4j.collection.trackable.HeapTrackingArrayList;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.event.LabelEntry;
import org.neo4j.graphdb.event.PropertyEntry;
import org.neo4j.graphdb.event.TransactionData;
import org.neo4j.internal.kernel.api.TokenRead;
import org.neo4j.internal.kernel.api.exceptions.LabelNotFoundKernelException;
import org.neo4j.internal.kernel.api.exceptions.PropertyKeyIdNotFoundKernelException;
import org.neo4j.kernel.api.KernelTransaction;
import org.neo4j.kernel.impl.core.NodeEntity;
import org.neo4j.kernel.impl.core.RelationshipEntity;
import org.neo4j.kernel.impl.coreapi.InternalTransaction;
import org.neo4j.memory.MemoryTracker;
import org.neo4j.storageengine.api.PropertySelection;
import org.neo4j.storageengine.api.StorageEntityCursor;
import org.neo4j.storageengine.api.StorageNodeCursor;
import org.neo4j.storageengine.api.StorageProperty;
import org.neo4j.storageengine.api.StoragePropertyCursor;
import org.neo4j.storageengine.api.StorageReader;
import org.neo4j.storageengine.api.StorageRelationshipScanCursor;
import org.neo4j.storageengine.api.txstate.LongDiffSets;
import org.neo4j.storageengine.api.txstate.NodeState;
import org.neo4j.storageengine.api.txstate.ReadableTransactionState;
import org.neo4j.storageengine.api.txstate.RelationshipState;
import org.neo4j.values.storable.Value;

import static java.lang.Math.toIntExact;
import static org.neo4j.collection.trackable.HeapTrackingCollections.newArrayList;
import static org.neo4j.collection.trackable.HeapTrackingCollections.newLongObjectMap;
import static org.neo4j.storageengine.api.PropertySelection.ALL_PROPERTIES;
import static org.neo4j.values.storable.Values.NO_VALUE;

/**
 * Transform for {@link org.neo4j.storageengine.api.txstate.ReadableTransactionState} to make it accessible as {@link TransactionData}.
 */
public class TxStateTransactionDataSnapshot implements TransactionData, AutoCloseable
{
    private final ReadableTransactionState state;
    private final StorageReader store;
    private final KernelTransaction transaction;

    private final HeapTrackingArrayList> assignedNodeProperties;
    private final HeapTrackingArrayList> assignedRelationshipProperties;
    private final HeapTrackingArrayList assignedLabels;

    private final HeapTrackingArrayList> removedNodeProperties;
    private final HeapTrackingArrayList> removedRelationshipProperties;
    private final HeapTrackingArrayList removedLabels;
    private final MutableLongObjectMap relationshipsReadFromStore;
    private final StorageRelationshipScanCursor relationship;
    private final InternalTransaction internalTransaction;
    private final MemoryTracker memoryTracker;

    TxStateTransactionDataSnapshot( ReadableTransactionState state, StorageReader storageReader, KernelTransaction transaction )
    {
        this.state = state;
        this.store = storageReader;
        this.transaction = transaction;
        this.internalTransaction = transaction.internalTransaction();
        this.memoryTracker = transaction.memoryTracker();
        this.relationship = storageReader.allocateRelationshipScanCursor( transaction.cursorContext(), transaction.storeCursors() );
        this.relationshipsReadFromStore = newLongObjectMap( memoryTracker );
        this.removedLabels = newArrayList( memoryTracker );
        this.removedRelationshipProperties = newArrayList( memoryTracker );
        this.removedNodeProperties = newArrayList( memoryTracker );

        this.assignedLabels = newArrayList( memoryTracker );
        this.assignedRelationshipProperties = newArrayList( memoryTracker );
        this.assignedNodeProperties = newArrayList( memoryTracker );

        // Load changes that require store access eagerly, because we won't have access to the after-state
        // after the tx has been committed.
        takeSnapshot( memoryTracker );
    }

    @Override
    public Iterable createdNodes()
    {
        return map2Nodes( state.addedAndRemovedNodes().getAdded() );
    }

    @Override
    public Iterable deletedNodes()
    {
        return map2Nodes( state.addedAndRemovedNodes().getRemoved() );
    }

    @Override
    public Iterable createdRelationships()
    {
        return map2Rels( state.addedAndRemovedRelationships().getAdded() );
    }

    @Override
    public Iterable deletedRelationships()
    {
        return map2Rels( state.addedAndRemovedRelationships().getRemoved() );
    }

    @Override
    public boolean isDeleted( Node node )
    {
        return state.nodeIsDeletedInThisTx( node.getId() );
    }

    @Override
    public boolean isDeleted( Relationship relationship )
    {
        return state.relationshipIsDeletedInThisTx( relationship.getId() );
    }

    @Override
    public Iterable> assignedNodeProperties()
    {
        return assignedNodeProperties;
    }

    @Override
    public Iterable> removedNodeProperties()
    {
        return removedNodeProperties;
    }

    @Override
    public Iterable> assignedRelationshipProperties()
    {
        return assignedRelationshipProperties;
    }

    @Override
    public Iterable> removedRelationshipProperties()
    {
        return removedRelationshipProperties;
    }

    @Override
    public String username()
    {
        return transaction.securityContext().subject().executingUser();
    }

    @Override
    public Map metaData()
    {
        return transaction.getMetaData();
    }

    @Override
    public Iterable removedLabels()
    {
        return removedLabels;
    }

    @Override
    public Iterable assignedLabels()
    {
        return assignedLabels;
    }

    @Override
    public long getTransactionId()
    {
        return transaction.getTransactionId();
    }

    @Override
    public long getCommitTime()
    {
        return transaction.getCommitTime();
    }

    private void takeSnapshot( MemoryTracker memoryTracker )
    {
        var cursorContext = transaction.cursorContext();
        var storeCursors = transaction.storeCursors();
        try ( StorageNodeCursor node = store.allocateNodeCursor( cursorContext, storeCursors );
              StoragePropertyCursor properties = store.allocatePropertyCursor( cursorContext, storeCursors, memoryTracker ) )
        {
            TokenRead tokenRead = transaction.tokenRead();
            snapshotRemovedNodes( memoryTracker, node, properties, tokenRead );
            snapshotRemovedRelationships( memoryTracker, properties, tokenRead );
            snapshotModifiedNodes( memoryTracker, node, properties, tokenRead );
            snapshotModifiedRelationships( memoryTracker, properties, tokenRead );
        }
        catch ( PropertyKeyIdNotFoundKernelException e )
        {
            throw new IllegalStateException( "An entity that does not exist was modified.", e );
        }
    }

    private void snapshotModifiedRelationships( MemoryTracker memoryTracker, StoragePropertyCursor properties, TokenRead tokenRead )
            throws PropertyKeyIdNotFoundKernelException
    {
        for ( RelationshipState relState : state.modifiedRelationships() )
        {
            Relationship relationship = relationship( relState.getId() );
            Iterator added = relState.addedAndChangedProperties().iterator();
            while ( added.hasNext() )
            {
                StorageProperty property = added.next();
                assignedRelationshipProperties.add(
                        createRelationshipPropertyEntryView( memoryTracker, tokenRead, relationship, property.propertyKeyId(), property.value(),
                                committedValue( relState, property.propertyKeyId(), this.relationship, properties ) ) );
            }
            relState.removedProperties().each( id ->
            {
                try
                {
                    var entryView = createRelationshipPropertyEntryView( memoryTracker, tokenRead, relationship, id, null,
                            committedValue( relState, id, this.relationship, properties ) );
                    removedRelationshipProperties.add( entryView );
                }
                catch ( PropertyKeyIdNotFoundKernelException e )
                {
                    throw new IllegalStateException( "Not existing properties was modified for relationship " + relState.getId(), e );
                }
            } );
        }
    }

    private void snapshotModifiedNodes( MemoryTracker memoryTracker, StorageNodeCursor node, StoragePropertyCursor properties, TokenRead tokenRead )
            throws PropertyKeyIdNotFoundKernelException
    {
        for ( NodeState nodeState : state.modifiedNodes() )
        {
            Iterator added = nodeState.addedAndChangedProperties().iterator();
            long nodeId = nodeState.getId();
            while ( added.hasNext() )
            {
                StorageProperty property = added.next();
                var entryView = createNodePropertyEntryView( memoryTracker, tokenRead, nodeId, property.propertyKeyId(), property.value(),
                                committedValue( nodeState, property.propertyKeyId(), node, properties ) );
                assignedNodeProperties.add( entryView );
            }
            nodeState.removedProperties().each( id ->
            {
                try
                {
                    removedNodeProperties.add(
                            createNodePropertyEntryView( memoryTracker, tokenRead, nodeId, id, null, committedValue( nodeState, id, node, properties ) ) );
                }
                catch ( PropertyKeyIdNotFoundKernelException e )
                {
                    throw new IllegalStateException( "Not existing node properties was modified for node " + nodeId, e );
                }
            } );

            final LongDiffSets labels = nodeState.labelDiffSets();
            addLabelEntriesTo( nodeId, labels.getAdded(), assignedLabels );
            addLabelEntriesTo( nodeId, labels.getRemoved(), removedLabels );
        }
    }

    private void snapshotRemovedRelationships( MemoryTracker memoryTracker, StoragePropertyCursor properties, TokenRead tokenRead )
    {
        state.addedAndRemovedRelationships().getRemoved().each( relId ->
        {
            Relationship relationship = relationship( relId );
            this.relationship.single( relId );
            if ( this.relationship.next() )
            {
                this.relationship.properties( properties, ALL_PROPERTIES );
                while ( properties.next() )
                {
                    try
                    {
                        removedRelationshipProperties.add( createRelationshipPropertyEntryView( memoryTracker, tokenRead, relationship,
                                properties.propertyKey(), null, properties.propertyValue() ) );
                    }
                    catch ( PropertyKeyIdNotFoundKernelException e )
                    {
                        throw new IllegalStateException( "Not existing node properties was modified for relationship " + relId, e );
                    }
                }
            }
        } );
    }

    private void snapshotRemovedNodes( MemoryTracker memoryTracker, StorageNodeCursor node, StoragePropertyCursor properties, TokenRead tokenRead )
    {
        state.addedAndRemovedNodes().getRemoved().each( nodeId ->
        {
            node.single( nodeId );
            if ( node.next() )
            {
                node.properties( properties, ALL_PROPERTIES );
                while ( properties.next() )
                {
                    try
                    {
                        removedNodeProperties.add( createNodePropertyEntryView( memoryTracker, tokenRead, nodeId, properties.propertyKey(),
                                null, properties.propertyValue() ) );
                    }
                    catch ( PropertyKeyIdNotFoundKernelException e )
                    {
                        throw new IllegalStateException( "Not existing properties was modified for node " + nodeId, e );
                    }
                }

                for ( long labelId : node.labels() )
                {
                    try
                    {
                        removedLabels.add( createLabelView( memoryTracker, tokenRead, nodeId, labelId ) );
                    }
                    catch ( LabelNotFoundKernelException e )
                    {
                        throw new IllegalStateException( "Not existing label was modified for node " + nodeId, e );
                    }
                }
            }
        } );
    }

    @Override
    public void close()
    {
        relationship.close();
    }

    private void addLabelEntriesTo( long nodeId, LongSet labelIds, HeapTrackingArrayList target )
    {
        labelIds.each( labelId ->
        {
            try
            {
                target.add( createLabelView( memoryTracker, transaction.tokenRead(), nodeId, labelId ) );
            }
            catch ( LabelNotFoundKernelException e )
            {
                throw new IllegalStateException( "Not existing label was modified for node " + nodeId, e );
            }
        } );
    }

    private static RelationshipPropertyEntryView createRelationshipPropertyEntryView( MemoryTracker memoryTracker, TokenRead tokenRead,
            Relationship relationship, int key, Value newValue, Value oldValue ) throws PropertyKeyIdNotFoundKernelException
    {
        var entryView = new RelationshipPropertyEntryView( relationship, tokenRead.propertyKeyName( key ), newValue, oldValue );
        memoryTracker.allocateHeap( RelationshipPropertyEntryView.SHALLOW_SIZE );
        if ( oldValue != null )
        {
            memoryTracker.allocateHeap( oldValue.estimatedHeapUsage() );
        }
        return entryView;
    }

    private NodePropertyEntryView createNodePropertyEntryView( MemoryTracker memoryTracker, TokenRead tokenRead, long nodeId, int key, Value newValue,
            Value oldValue ) throws PropertyKeyIdNotFoundKernelException
    {
        memoryTracker.allocateHeap( NodePropertyEntryView.SHALLOW_SIZE );
        var entryView = new NodePropertyEntryView( internalTransaction, nodeId, tokenRead.propertyKeyName( key ), newValue, oldValue );
        if ( oldValue != null )
        {
            memoryTracker.allocateHeap( oldValue.estimatedHeapUsage() );
        }
        return entryView;
    }

    private LabelEntryView createLabelView( MemoryTracker memoryTracker, TokenRead tokenRead, long nodeId, long labelId ) throws LabelNotFoundKernelException
    {
        memoryTracker.allocateHeap( LabelEntryView.SHALLOW_SIZE );
        return new LabelEntryView( internalTransaction, nodeId, tokenRead.nodeLabelName( toIntExact( labelId ) ) );
    }

    private Relationship relationship( long relId )
    {
        RelationshipEntity relationship = (RelationshipEntity) internalTransaction.newRelationshipEntity( relId );
        if ( !state.relationshipVisit( relId, relationship ) )
        {   // This relationship has been created or changed in this transaction
            RelationshipEntity cached = relationshipsReadFromStore.get( relId );
            if ( cached != null )
            {
                return cached;
            }

            // Get this relationship data from the store
            this.relationship.single( relId );
            if ( !this.relationship.next() )
            {
                throw new IllegalStateException( "Getting deleted relationship data should have been covered by the tx state" );
            }
            relationship.visit( relId, this.relationship.type(), this.relationship.sourceNodeReference(), this.relationship.targetNodeReference() );
            memoryTracker.allocateHeap( RelationshipEntity.SHALLOW_SIZE );
            relationshipsReadFromStore.put( relId, relationship );
        }
        return relationship;
    }

    private Iterable map2Nodes( LongIterable ids )
    {
        return ids.asLazy().collect( id -> new NodeEntity( internalTransaction, id ) );
    }

    private Iterable map2Rels( LongIterable ids )
    {
        return ids.asLazy().collect( this::relationship );
    }

    private Value committedValue( NodeState nodeState, int property, StorageNodeCursor node, StoragePropertyCursor properties )
    {
        if ( state.nodeIsAddedInThisTx( nodeState.getId() ) )
        {
            return NO_VALUE;
        }

        node.single( nodeState.getId() );
        if ( !node.next() )
        {
            return NO_VALUE;
        }
        return committedValue( properties, node, property, node.entityReference() );
    }

    private static Value committedValue( StoragePropertyCursor properties, StorageEntityCursor cursor, int propertyKey, long ownerReference )
    {
        cursor.properties( properties, PropertySelection.selection( propertyKey ) );
        return properties.next() ? properties.propertyValue() : NO_VALUE;
    }

    private Value committedValue( RelationshipState relState, int property, StorageRelationshipScanCursor relationship, StoragePropertyCursor properties )
    {
        if ( state.relationshipIsAddedInThisTx( relState.getId() ) )
        {
            return NO_VALUE;
        }

        relationship.single( relState.getId() );
        if ( !relationship.next() )
        {
            return NO_VALUE;
        }

        return committedValue( properties, relationship, property, relationship.entityReference() );
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy