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