
org.neo4j.kernel.impl.nioneo.xa.WriteTransaction Maven / Gradle / Ivy
/**
* Copyright (c) 2002-2013 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.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.nioneo.xa;
import static org.neo4j.kernel.impl.nioneo.store.PropertyStore.encodeString;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.xa.XAException;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.helpers.Exceptions;
import org.neo4j.helpers.Pair;
import org.neo4j.kernel.impl.core.PropertyIndex;
import org.neo4j.kernel.impl.core.TransactionState;
import org.neo4j.kernel.impl.nioneo.store.ConstraintViolationException;
import org.neo4j.kernel.impl.nioneo.store.DynamicRecord;
import org.neo4j.kernel.impl.nioneo.store.InvalidRecordException;
import org.neo4j.kernel.impl.nioneo.store.NameData;
import org.neo4j.kernel.impl.nioneo.store.NeoStore;
import org.neo4j.kernel.impl.nioneo.store.NeoStoreRecord;
import org.neo4j.kernel.impl.nioneo.store.NodeRecord;
import org.neo4j.kernel.impl.nioneo.store.NodeStore;
import org.neo4j.kernel.impl.nioneo.store.PrimitiveRecord;
import org.neo4j.kernel.impl.nioneo.store.PropertyBlock;
import org.neo4j.kernel.impl.nioneo.store.PropertyData;
import org.neo4j.kernel.impl.nioneo.store.PropertyIndexRecord;
import org.neo4j.kernel.impl.nioneo.store.PropertyIndexStore;
import org.neo4j.kernel.impl.nioneo.store.PropertyRecord;
import org.neo4j.kernel.impl.nioneo.store.PropertyStore;
import org.neo4j.kernel.impl.nioneo.store.PropertyType;
import org.neo4j.kernel.impl.nioneo.store.Record;
import org.neo4j.kernel.impl.nioneo.store.RelationshipRecord;
import org.neo4j.kernel.impl.nioneo.store.RelationshipStore;
import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeRecord;
import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeStore;
import org.neo4j.kernel.impl.persistence.NeoStoreTransaction;
import org.neo4j.kernel.impl.transaction.xaframework.XaCommand;
import org.neo4j.kernel.impl.transaction.xaframework.XaConnection;
import org.neo4j.kernel.impl.transaction.xaframework.XaLogicalLog;
import org.neo4j.kernel.impl.transaction.xaframework.XaTransaction;
import org.neo4j.kernel.impl.util.ArrayMap;
import org.neo4j.kernel.impl.util.RelIdArray;
import org.neo4j.kernel.impl.util.RelIdArray.DirectionWrapper;
/**
* Transaction containing {@link Command commands} reflecting the operations
* performed in the transaction.
*/
public class WriteTransaction extends XaTransaction implements NeoStoreTransaction
{
private final Map nodeRecords = new HashMap();
private final Map propertyRecords = new HashMap();
private final Map relRecords = new HashMap();
private Map relTypeRecords;
private Map propIndexRecords;
private NeoStoreRecord neoStoreRecord;
private final ArrayList nodeCommands = new ArrayList();
private final ArrayList propCommands = new ArrayList();
private final ArrayList relCommands = new ArrayList();
private ArrayList relTypeCommands;
private ArrayList propIndexCommands;
private Command.NeoStoreCommand neoStoreCommand;
private final NeoStore neoStore;
private boolean committed = false;
private boolean prepared = false;
private final TransactionState state;
private XaConnection xaConnection;
WriteTransaction( int identifier, XaLogicalLog log, TransactionState state, NeoStore neoStore )
{
super( identifier, log, state );
this.neoStore = neoStore;
this.state = state;
}
@Override
public boolean isReadOnly()
{
if ( isRecovered() )
{
return nodeCommands.size() == 0 && propCommands.size() == 0 &&
relCommands.size() == 0 && relTypeCommands == null &&
propIndexCommands == null;
}
return nodeRecords.size() == 0 && relRecords.size() == 0 &&
propertyRecords.size() == 0 && relTypeRecords == null &&
propIndexRecords == null;
}
@Override
public void doAddCommand( XaCommand command )
{
// override
}
@Override
protected void doPrepare() throws XAException
{
int noOfCommands = nodeRecords.size() +
relRecords.size() +
propertyRecords.size() +
(propIndexRecords != null ? propIndexRecords.size() : 0) +
(relTypeRecords != null ? relTypeRecords.size() : 0);
List commands = new ArrayList( noOfCommands );
if ( committed )
{
throw new XAException( "Cannot prepare committed transaction["
+ getIdentifier() + "]" );
}
if ( prepared )
{
throw new XAException( "Cannot prepare prepared transaction["
+ getIdentifier() + "]" );
}
/*
* Generate records first, then write all together to logical log via
* addCommand method but before give the option to intercept.
*/
prepared = true;
if ( relTypeRecords != null )
{
relTypeCommands = new ArrayList();
for ( RelationshipTypeRecord record : relTypeRecords.values() )
{
Command.RelationshipTypeCommand command =
new Command.RelationshipTypeCommand(
neoStore.getRelationshipTypeStore(), record );
relTypeCommands.add( command );
commands.add( command );
}
}
for ( NodeRecord record : nodeRecords.values() )
{
if ( !record.inUse() && record.getNextRel() !=
Record.NO_NEXT_RELATIONSHIP.intValue() )
{
throw Exceptions.withCause( new XAException( XAException.XA_RBINTEGRITY ),
new ConstraintViolationException("Node record " + record + " still has relationships"));
}
Command.NodeCommand command = new Command.NodeCommand(
neoStore.getNodeStore(), record );
nodeCommands.add( command );
commands.add( command );
}
for ( RelationshipRecord record : relRecords.values() )
{
Command.RelationshipCommand command =
new Command.RelationshipCommand(
neoStore.getRelationshipStore(), record );
relCommands.add( command );
commands.add( command );
}
if ( neoStoreRecord != null )
{
neoStoreCommand = new Command.NeoStoreCommand( neoStore, neoStoreRecord );
addCommand( neoStoreCommand );
}
if ( propIndexRecords != null )
{
propIndexCommands = new ArrayList();
for ( PropertyIndexRecord record : propIndexRecords.values() )
{
Command.PropertyIndexCommand command =
new Command.PropertyIndexCommand(
neoStore.getPropertyStore().getIndexStore(), record );
propIndexCommands.add( command );
commands.add( command );
}
}
for ( PropertyRecord record : propertyRecords.values() )
{
Command.PropertyCommand command = new Command.PropertyCommand(
neoStore.getPropertyStore(), record );
propCommands.add( command );
commands.add( command );
}
assert commands.size() == noOfCommands : "Expected " + noOfCommands
+ " final commands, got "
+ commands.size() + " instead";
intercept( commands );
for ( Command command : commands )
{
addCommand(command);
}
}
protected void intercept( List commands )
{
// default no op
}
@Override
protected void injectCommand( XaCommand xaCommand )
{
if ( xaCommand instanceof Command.NodeCommand )
{
nodeCommands.add( (Command.NodeCommand) xaCommand );
}
else if ( xaCommand instanceof Command.RelationshipCommand )
{
relCommands.add( (Command.RelationshipCommand) xaCommand );
}
else if ( xaCommand instanceof Command.PropertyCommand )
{
propCommands.add( (Command.PropertyCommand) xaCommand );
}
else if ( xaCommand instanceof Command.PropertyIndexCommand )
{
if ( propIndexCommands == null ) propIndexCommands = new ArrayList();
propIndexCommands.add( (Command.PropertyIndexCommand) xaCommand );
}
else if ( xaCommand instanceof Command.RelationshipTypeCommand )
{
if ( relTypeCommands == null ) relTypeCommands = new ArrayList();
relTypeCommands.add( (Command.RelationshipTypeCommand) xaCommand );
}
else if ( xaCommand instanceof Command.NeoStoreCommand )
{
assert neoStoreCommand == null;
neoStoreCommand = (Command.NeoStoreCommand) xaCommand;
}
else
{
throw new IllegalArgumentException( "Unknown command " + xaCommand );
}
}
@Override
public void doRollback() throws XAException
{
if ( committed )
{
throw new XAException( "Cannot rollback partialy commited "
+ "transaction[" + getIdentifier() + "]. Recover and "
+ "commit" );
}
try
{
boolean freeIds = neoStore.freeIdsDuringRollback();
if ( relTypeRecords != null ) for ( RelationshipTypeRecord record : relTypeRecords.values() )
{
if ( record.isCreated() )
{
if ( freeIds ) getRelationshipTypeStore().freeId( record.getId() );
for ( DynamicRecord dynamicRecord : record.getNameRecords() )
{
if ( dynamicRecord.isCreated() )
{
getRelationshipTypeStore().freeBlockId(
(int) dynamicRecord.getId() );
}
}
}
removeRelationshipTypeFromCache( record.getId() );
}
for ( NodeRecord record : nodeRecords.values() )
{
if ( freeIds && record.isCreated() )
{
getNodeStore().freeId( record.getId() );
}
removeNodeFromCache( record.getId() );
}
for ( RelationshipRecord record : relRecords.values() )
{
if ( freeIds && record.isCreated() )
{
getRelationshipStore().freeId( record.getId() );
}
removeRelationshipFromCache( record.getId() );
patchDeletedRelationshipNodes( record.getId(), record.getFirstNode(), record.getFirstNextRel(),
record.getSecondNode(), record.getSecondNextRel() );
}
if ( neoStoreRecord != null )
{
removeGraphPropertiesFromCache();
}
if ( propIndexRecords != null ) for ( PropertyIndexRecord record : propIndexRecords.values() )
{
if ( record.isCreated() )
{
if ( freeIds ) getPropertyStore().getIndexStore().freeId( record.getId() );
for ( DynamicRecord dynamicRecord : record.getNameRecords() )
{
if ( dynamicRecord.isCreated() )
{
getPropertyStore().getIndexStore().freeBlockId(
(int) dynamicRecord.getId() );
}
}
}
}
for ( PropertyRecord record : propertyRecords.values() )
{
if ( record.getNodeId() != -1 )
{
removeNodeFromCache( record.getNodeId() );
}
else if ( record.getRelId() != -1 )
{
removeRelationshipFromCache( record.getRelId() );
}
if ( record.isCreated() )
{
if ( freeIds ) getPropertyStore().freeId( record.getId() );
for ( PropertyBlock block : record.getPropertyBlocks() )
{
for ( DynamicRecord dynamicRecord : block.getValueRecords() )
{
if ( dynamicRecord.isCreated() )
{
if ( dynamicRecord.getType() == PropertyType.STRING.intValue() )
{
getPropertyStore().freeStringBlockId(
dynamicRecord.getId() );
}
else if ( dynamicRecord.getType() == PropertyType.ARRAY.intValue() )
{
getPropertyStore().freeArrayBlockId(
dynamicRecord.getId() );
}
else
{
throw new InvalidRecordException(
"Unknown type on " + dynamicRecord );
}
}
}
}
}
}
}
finally
{
clear();
}
}
private void removeRelationshipTypeFromCache( int id )
{
state.removeRelationshipTypeFromCache( id );
}
private void patchDeletedRelationshipNodes( long id, long firstNodeId, long firstNodeNextRelId, long secondNodeId,
long secondNextRelId )
{
state.patchDeletedRelationshipNodes( id, firstNodeId, firstNodeNextRelId, secondNodeId, secondNextRelId );
}
private void removeRelationshipFromCache( long id )
{
state.removeRelationshipFromCache( id );
}
private void removeNodeFromCache( long id )
{
state.removeNodeFromCache( id );
}
private void removeGraphPropertiesFromCache()
{
state.removeGraphPropertiesFromCache();
}
private void addRelationshipType( int id )
{
setRecovered();
NameData type = isRecovered() ?
neoStore.getRelationshipTypeStore().getName( id, true ) :
neoStore.getRelationshipTypeStore().getName( id );
state.addRelationshipType( type );
}
private void addPropertyIndexCommand( int id )
{
NameData index = isRecovered() ?
neoStore.getPropertyStore().getIndexStore().getName( id, true ) :
neoStore.getPropertyStore().getIndexStore().getName( id );
state.addPropertyIndex( index );
}
@Override
public void doCommit() throws XAException
{
if ( !isRecovered() && !prepared )
{
throw new XAException( "Cannot commit non prepared transaction[" + getIdentifier() + "]" );
}
if ( isRecovered() )
{
applyCommit( true );
return;
}
if ( !isRecovered() && getCommitTxId() != neoStore.getLastCommittedTx() + 1 )
{
throw new RuntimeException( "Tx id: " + getCommitTxId() +
" not next transaction (" + neoStore.getLastCommittedTx() + ")" );
}
applyCommit( false );
}
private void applyCommit( boolean isRecovered )
{
try
{
committed = true;
CommandSorter sorter = new CommandSorter();
// reltypes
if ( relTypeCommands != null )
{
java.util.Collections.sort( relTypeCommands, sorter );
for ( Command.RelationshipTypeCommand command : relTypeCommands )
{
command.execute();
if ( isRecovered )
{
addRelationshipType( (int) command.getKey() );
}
}
}
// property keys
if ( propIndexCommands != null )
{
java.util.Collections.sort( propIndexCommands, sorter );
for ( Command.PropertyIndexCommand command : propIndexCommands )
{
command.execute();
if ( isRecovered )
{
addPropertyIndexCommand( (int) command.getKey() );
}
}
}
// primitives
java.util.Collections.sort( nodeCommands, sorter );
java.util.Collections.sort( relCommands, sorter );
java.util.Collections.sort( propCommands, sorter );
executeCreated( isRecovered, propCommands, relCommands, nodeCommands );
executeModified( isRecovered, propCommands, relCommands, nodeCommands );
executeDeleted( propCommands, relCommands, nodeCommands );
if ( isRecovered )
neoStore.setRecoveredStatus( true );
try
{
if ( neoStoreCommand != null )
{
neoStoreCommand.execute();
if ( isRecovered )
removeGraphPropertiesFromCache();
}
if ( !isRecovered )
{
updateFirstRelationships();
state.commitCows(); // updates the cached primitives
}
neoStore.setLastCommittedTx( getCommitTxId() );
}
finally
{
neoStore.setRecoveredStatus( false );
}
if ( isRecovered )
neoStore.updateIdGenerators();
}
finally
{
clear();
}
}
@Override
public boolean delistResource( Transaction tx, int tmsuccess )
throws SystemException
{
return xaConnection.delistResource( tx, tmsuccess );
}
private void updateFirstRelationships()
{
for ( NodeRecord record : nodeRecords.values() )
state.setFirstIds( record.getId(), record.getNextRel(), record.getNextProp() );
}
private void executeCreated( boolean removeFromCache, List extends Command>... commands )
{
for ( List extends Command> c : commands ) for ( Command command : c )
{
if ( command.isCreated() && !command.isDeleted() )
{
command.execute();
if ( removeFromCache )
{
command.removeFromCache( state );
}
}
}
}
private void executeModified( boolean removeFromCache, List extends Command>... commands )
{
for ( List extends Command> c : commands ) for ( Command command : c )
{
if ( !command.isCreated() && !command.isDeleted() )
{
command.execute();
if ( removeFromCache )
{
command.removeFromCache( state );
}
}
}
}
private void executeDeleted( List extends Command>... commands )
{
for ( List extends Command> c : commands ) for ( Command command : c )
{
if ( command.isDeleted() )
{
/*
* We always update the disk image and then always invalidate the cache. In the case of relationships
* this is expected to also patch the relChainPosition in the start and end NodeImpls (if they actually
* are in cache).
*/
command.execute();
command.removeFromCache( state );
}
}
}
private void clear()
{
nodeRecords.clear();
propertyRecords.clear();
relRecords.clear();
relTypeRecords = null;
propIndexRecords = null;
neoStoreRecord = null;
nodeCommands.clear();
propCommands.clear();
propIndexCommands = null;
relCommands.clear();
relTypeCommands = null;
neoStoreCommand = null;
}
private RelationshipTypeStore getRelationshipTypeStore()
{
return neoStore.getRelationshipTypeStore();
}
private int getRelGrabSize()
{
return neoStore.getRelationshipGrabSize();
}
private NodeStore getNodeStore()
{
return neoStore.getNodeStore();
}
private RelationshipStore getRelationshipStore()
{
return neoStore.getRelationshipStore();
}
private PropertyStore getPropertyStore()
{
return neoStore.getPropertyStore();
}
@Override
public NodeRecord nodeLoadLight( long nodeId )
{
NodeRecord nodeRecord = getNodeRecord( nodeId );
if ( nodeRecord != null ) return nodeRecord;
return getNodeStore().loadLightNode( nodeId );
}
@Override
public RelationshipRecord relLoadLight( long id )
{
RelationshipRecord relRecord = getRelationshipRecord( id );
if ( relRecord != null )
{
// if deleted in this tx still return it
// if ( !relRecord.inUse() )
// {
// return null;
// }
return relRecord;
}
relRecord = getRelationshipStore().getLightRel( id );
if ( relRecord != null )
{
return relRecord;
}
return null;
}
@Override
public ArrayMap nodeDelete( long nodeId )
{
NodeRecord nodeRecord = getNodeRecord( nodeId );
if ( nodeRecord == null )
{
nodeRecord = getNodeStore().getRecord( nodeId );
addNodeRecord( nodeRecord );
}
if ( !nodeRecord.inUse() )
{
throw new IllegalStateException( "Unable to delete Node[" + nodeId +
"] since it has already been deleted." );
}
nodeRecord.setInUse( false );
ArrayMap propertyMap = getAndDeletePropertyChain( nodeRecord );
return propertyMap;
}
@Override
public ArrayMap relDelete( long id )
{
RelationshipRecord record = getRelationshipRecord( id );
if ( record == null )
{
record = getRelationshipStore().getRecord( id );
addRelationshipRecord( record );
}
if ( !record.inUse() )
{
throw new IllegalStateException( "Unable to delete relationship[" +
id + "] since it is already deleted." );
}
ArrayMap propertyMap = getAndDeletePropertyChain( record );
disconnectRelationship( record );
updateNodes( record );
record.setInUse( false );
return propertyMap;
}
private ArrayMap getAndDeletePropertyChain(
PrimitiveRecord primitive )
{
ArrayMap result = new ArrayMap(
(byte)9, false, true );
long nextProp = primitive.getNextProp();
while ( nextProp != Record.NO_NEXT_PROPERTY.intValue() )
{
PropertyRecord propRecord = getPropertyRecord( nextProp, false,
true );
if ( !propRecord.isCreated() && propRecord.isChanged() )
{
// Being here means a new value could be on disk. Re-read and replace
propRecord = getPropertyStore().getRecord( propRecord.getId() );
addPropertyRecord( propRecord );
}
for ( PropertyBlock block : propRecord.getPropertyBlocks() )
{
if ( block.isLight() )
{
getPropertyStore().makeHeavy( block );
}
if ( !block.isCreated() && !propRecord.isChanged() )
{
result.put( block.getKeyIndexId(),
block.newPropertyData( propRecord,
propertyGetValueOrNull( block ) ) );
}
// TODO: update count on property index record
for ( DynamicRecord valueRecord : block.getValueRecords() )
{
assert valueRecord.inUse();
valueRecord.setInUse( false );
propRecord.addDeletedRecord( valueRecord );
}
}
nextProp = propRecord.getNextProp();
propRecord.setInUse( false );
propRecord.setChanged( primitive );
// We do not remove them individually, but all together here
propRecord.getPropertyBlocks().clear();
}
return result;
}
private void disconnectRelationship( RelationshipRecord rel )
{
// update first node prev
if ( rel.getFirstPrevRel() != Record.NO_NEXT_RELATIONSHIP.intValue() )
{
Relationship lockableRel = new LockableRelationship(
rel.getFirstPrevRel() );
getWriteLock( lockableRel );
RelationshipRecord prevRel = getRelationshipRecord(
rel.getFirstPrevRel() );
if ( prevRel == null )
{
prevRel = getRelationshipStore().getRecord(
rel.getFirstPrevRel() );
addRelationshipRecord( prevRel );
}
boolean changed = false;
if ( prevRel.getFirstNode() == rel.getFirstNode() )
{
prevRel.setFirstNextRel( rel.getFirstNextRel() );
changed = true;
}
if ( prevRel.getSecondNode() == rel.getFirstNode() )
{
prevRel.setSecondNextRel( rel.getFirstNextRel() );
changed = true;
}
if ( !changed )
{
throw new InvalidRecordException(
prevRel + " don't match " + rel );
}
}
// update first node next
if ( rel.getFirstNextRel() != Record.NO_NEXT_RELATIONSHIP.intValue() )
{
Relationship lockableRel = new LockableRelationship(
rel.getFirstNextRel() );
getWriteLock( lockableRel );
RelationshipRecord nextRel = getRelationshipRecord(
rel.getFirstNextRel() );
if ( nextRel == null )
{
nextRel = getRelationshipStore().getRecord(
rel.getFirstNextRel() );
addRelationshipRecord( nextRel );
}
boolean changed = false;
if ( nextRel.getFirstNode() == rel.getFirstNode() )
{
nextRel.setFirstPrevRel( rel.getFirstPrevRel() );
changed = true;
}
if ( nextRel.getSecondNode() == rel.getFirstNode() )
{
nextRel.setSecondPrevRel( rel.getFirstPrevRel() );
changed = true;
}
if ( !changed )
{
throw new InvalidRecordException( nextRel + " don't match "
+ rel );
}
}
// update second node prev
if ( rel.getSecondPrevRel() != Record.NO_NEXT_RELATIONSHIP.intValue() )
{
Relationship lockableRel = new LockableRelationship(
rel.getSecondPrevRel() );
getWriteLock( lockableRel );
RelationshipRecord prevRel = getRelationshipRecord(
rel.getSecondPrevRel() );
if ( prevRel == null )
{
prevRel = getRelationshipStore().getRecord(
rel.getSecondPrevRel() );
addRelationshipRecord( prevRel );
}
boolean changed = false;
if ( prevRel.getFirstNode() == rel.getSecondNode() )
{
prevRel.setFirstNextRel( rel.getSecondNextRel() );
changed = true;
}
if ( prevRel.getSecondNode() == rel.getSecondNode() )
{
prevRel.setSecondNextRel( rel.getSecondNextRel() );
changed = true;
}
if ( !changed )
{
throw new InvalidRecordException( prevRel + " don't match " +
rel );
}
}
// update second node next
if ( rel.getSecondNextRel() != Record.NO_NEXT_RELATIONSHIP.intValue() )
{
Relationship lockableRel = new LockableRelationship(
rel.getSecondNextRel() );
getWriteLock( lockableRel );
RelationshipRecord nextRel = getRelationshipRecord(
rel.getSecondNextRel() );
if ( nextRel == null )
{
nextRel = getRelationshipStore().getRecord(
rel.getSecondNextRel() );
addRelationshipRecord( nextRel );
}
boolean changed = false;
if ( nextRel.getFirstNode() == rel.getSecondNode() )
{
nextRel.setFirstPrevRel( rel.getSecondPrevRel() );
changed = true;
}
if ( nextRel.getSecondNode() == rel.getSecondNode() )
{
nextRel.setSecondPrevRel( rel.getSecondPrevRel() );
changed = true;
}
if ( !changed )
{
throw new InvalidRecordException( nextRel + " don't match " +
rel );
}
}
}
private void getWriteLock( Relationship lockableRel )
{
state.acquireWriteLock( lockableRel );
}
@Override
public long getRelationshipChainPosition( long nodeId )
{
NodeRecord nodeRecord = getNodeRecord( nodeId );
if ( nodeRecord != null && nodeRecord.isCreated() )
{
return Record.NO_NEXT_RELATIONSHIP.intValue();
}
return getNodeStore().getRecord( nodeId ).getNextRel();
}
@Override
public Pair
© 2015 - 2025 Weber Informatics LLC | Privacy Policy